From d0b19c5e0e6ca30737928ebf10e8f84a9a2ec096 Mon Sep 17 00:00:00 2001 From: Sravan S Date: Thu, 24 Aug 2023 15:44:15 +0900 Subject: [PATCH] v1.1.0(Aug 23, 2023) - Convert all sourcecode to TS - Upgrade ChatSDK to v4 - No more IE support - No error first callbacks - Use jest for testing - Use rollup for bundling Add new methods: - Add new optional parameter platform to Desk.init DeviceOsPlatform comes from ChatSDK. We recommend you set it as `MOBILE_WEB`, in case you are using ReactNative Link -> https://sendbird.com/docs/chat/sdk/v4/javascript/ref/enums/_sendbird_chat.DeviceOsPlatform.html `init(sendbird: SendbirdGroupChat, platform?: DeviceOsPlatform)` - instanceConfirmEndOfChat to Ticket instances ``` // confirmEndOfChat Ticket.confirmEndOfChat(msg, 'yes', (ticket, error) => { console.log(ticket, error); }); // instanceConfirmEndOfChat const t = new Ticket(); t.instanceConfirmEndOfChat(msg, 'yes', (ticket, error) => { console.log(ticket, error); }); ``` - instanceSubmitFeedback to Ticket instances instanceSubmitFeedback has same functionality and signature as submitFeedback Only difference is, instanceSubmitFeedback is a method on ticket instance ``` // submitFeedback Ticket.submitFeedback(message, score, comment, () => { /* callback */ }); // instanceSubmitFeedback const t = new Ticket(); t.instanceSubmitFeedback(msg, 'yes', (ticket, error) => { console.log(ticket, error); }); ``` --- CHANGELOG.md | 145 +-- CHANGELOG_old.md | 115 ++ LICENSE.md | 4 +- README.md | 541 +--------- SendBird.Desk.d.ts | 184 ---- SendBird.Desk.min.js | 6 - dist/debug/index.js | 1893 +++++++++++++++++++++++++++++++++ dist/esm/index.js | 2358 ++++++++++++++++++++++++++++++++++++++++++ dist/index.d.ts | 833 +++++++++++++++ package.json | 84 +- 10 files changed, 5350 insertions(+), 813 deletions(-) create mode 100644 CHANGELOG_old.md delete mode 100644 SendBird.Desk.d.ts delete mode 100644 SendBird.Desk.min.js create mode 100644 dist/debug/index.js create mode 100644 dist/esm/index.js create mode 100644 dist/index.d.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d3817c..9817847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,114 +1,47 @@ ## Change Log -### v1.0.23(Mar 10, 2023) +### v1.1.0(Aug 23, 2023) -feat: add Ticket.getList() -Add new API to get by groups, otherwise, we have to modify other methods to -handle callback to the end& JS doesnt really have first class support for polymorphism -``` -* @param {integer} filters.offset - list offset. -* @param {object} filters.customFieldFilter - customField filter. -* @param {string} filters.group - group key(to filter tickets by a team). -* @param {string} filters.status - status to get tickets. ('all', 'CLOSED', 'OPEN'). -* @param {function} callback - Function(list:Array, err:Error) -Ticket.getList(params, callback) -``` - -### v1.0.21(Mar 29, 2022) - -- Add `getAllTickets` interface in `Ticket`. - -### v1.0.20(JAN 11, 2022) - -- Bug-fix in sendbird version comparison logic. - -### v1.0.19(JUN 2, 2021) - -- Minor internal update. - -### v1.0.18(MAY 21, 2021) - -- Added `botKey` in `Ticket.create()`. -- Added `cancel()` in `Ticket`. -- Added `selectQuestion()` in `Ticket`. - -### v1.0.17(JAN 29, 2021) - -- (TypeScript) Fixed the type of `status` in `Ticket`. - -### v1.0.16(DEC 1, 2020) - -- Added `close()` in `Ticket`. - -### v1.0.15(Jul 3, 2020) - -- `SendBird.Desk.d.ts` to export `SendBirdDesk` as default. - -### v1.0.14(Apr 27, 2020) - -- Added `setRelatedCHannelUrls(channelUrls, callback)` in `Ticket`. -- Minor bug-fixes in response payload. - -### v1.0.13(Feb 21, 2020) - -- TypeScript d.ts interface refactoring. - -### v1.0.12(Feb 14, 2020) - -- TypeScript d.ts interface fix. - -### v1.0.11(Feb 6, 2020) +- Convert all sourcecode to TS +- Upgrade ChatSDK to v4 +- No more IE support +- No error first callbacks +- Use jest for testing +- Use rollup for bundling -- TypeScript d.ts interface fix. +Add new methods: -### v1.0.10(Jan 3, 2020) +- Add new optional parameter platform to Desk.init +DeviceOsPlatform comes from ChatSDK. +We recommend you set it as `MOBILE_WEB`, in case you are using ReactNative +Link -> https://sendbird.com/docs/chat/sdk/v4/javascript/ref/enums/_sendbird_chat.DeviceOsPlatform.html -- Added `setCustomFields(customFields, callback)` in `Ticket`. -- Added `setPriority(priority, callback)` in `Ticket`. +`init(sendbird: SendbirdGroupChat, platform?: DeviceOsPlatform)` -### v1.0.9(Dec 11, 2019) - -- Bug-fix in `Ticket.getOpenedList()` and `Ticket.getClosedList()`. - -### v1.0.8(Aug 23, 2019) - -- Added `setCustomerCustomFields()` in `SendBirdDesk`. -- Added `submitFeedback()` in `Ticket`. - -### v1.0.7(Jul 12, 2019) - -- Added `customFields` property to `Ticket`. - -### v1.0.6(May 13, 2019) - -- Added `reopen()` in `Ticket` -- Added `groupKey` and `customFields` parameters to `Ticket.create()`. -- Added `customFieldFilter` parameter to `getOpenedList()` and `getClosedList()` in `Ticket`. - -### v1.0.5(Nov 23, 2018) - -- Added SendBird parameter to SendBirdDesk.init(). -- Bug-fix in 'SendBird missing' error at init(). - -### v1.0.4(July 18, 2018) - -- Added setApiHost to customize host. - -### v1.0.3(July 4, 2018) - -- Corrected package.json to fix library path. - -### v1.0.2(May 21, 2018) - -- Applied ticket assignment update. - -### v1.0.1(Mar 30, 2018) - -- Added SendBirdDesk.init(). -- Added SendBirdDesk.isDeskChannel(channel). -- Added TypeScript interface - SendBird.Desk.d.ts. -- Added console logger in debug mode. - -### v1.0.0-zendesk(Mar 16, 2018) +- instanceConfirmEndOfChat to Ticket instances +``` +// confirmEndOfChat +Ticket.confirmEndOfChat(msg, 'yes', (ticket, error) => { + console.log(ticket, error); +}); + +// instanceConfirmEndOfChat +const t = new Ticket(); +t.instanceConfirmEndOfChat(msg, 'yes', (ticket, error) => { + console.log(ticket, error); +}); +``` +- instanceSubmitFeedback to Ticket instances +instanceSubmitFeedback has same functionality and signature as submitFeedback +Only difference is, instanceSubmitFeedback is a method on ticket instance -- Zendesk-integrated version release. +``` +// submitFeedback +Ticket.submitFeedback(message, score, comment, () => { /* callback */ }); + +// instanceSubmitFeedback +const t = new Ticket(); +t.instanceSubmitFeedback(msg, 'yes', (ticket, error) => { + console.log(ticket, error); +}); +``` diff --git a/CHANGELOG_old.md b/CHANGELOG_old.md new file mode 100644 index 0000000..03c9518 --- /dev/null +++ b/CHANGELOG_old.md @@ -0,0 +1,115 @@ +## Change Log + +### v1.0.23(Mar 10, 2023) + +feat: add Ticket.getList() +Add new API to get by groups, otherwise, we have to modify other methods to +handle callback to the end& JS doesnt really have first class support for polymorphism +``` +* @param {integer} filters.offset - list offset. +* @param {object} filters.customFieldFilter - customField filter. +* @param {string} filters.group - group key(to filter tickets by a team). +* @param {string} filters.status - status to get tickets. ('all', 'CLOSED', 'OPEN'). +* @param {function} callback - Function(list:Array, err:Error) +Ticket.getList(params, callback) +``` + +### v1.0.21(Mar 29, 2022) + +- Add `getAllTickets` interface in `Ticket`. + +### v1.0.20(JAN 11, 2022) + +- Bug-fix in sendbird version comparison logic. + +### v1.0.19(JUN 2, 2021) + +- Minor internal update. + +### v1.0.18(MAY 21, 2021) + +- Added `botKey` in `Ticket.create()`. +- Added `cancel()` in `Ticket`. +- Added `selectQuestion()` in `Ticket`. + +### v1.0.17(JAN 29, 2021) + +- (TypeScript) Fixed the type of `status` in `Ticket`. + +### v1.0.16(DEC 1, 2020) + +- Added `close()` in `Ticket`. + +### v1.0.15(Jul 3, 2020) + +- `SendBird.Desk.d.ts` to export `SendBirdDesk` as default. + +### v1.0.14(Apr 27, 2020) + +- Added `setRelatedCHannelUrls(channelUrls, callback)` in `Ticket`. +- Minor bug-fixes in response payload. + +### v1.0.13(Feb 21, 2020) + +- TypeScript d.ts interface refactoring. + +### v1.0.12(Feb 14, 2020) + +- TypeScript d.ts interface fix. + +### v1.0.11(Feb 6, 2020) + +- TypeScript d.ts interface fix. + +### v1.0.10(Jan 3, 2020) + +- Added `setCustomFields(customFields, callback)` in `Ticket`. +- Added `setPriority(priority, callback)` in `Ticket`. + +### v1.0.9(Dec 11, 2019) + +- Bug-fix in `Ticket.getOpenedList()` and `Ticket.getClosedList()`. + +### v1.0.8(Aug 23, 2019) + +- Added `setCustomerCustomFields()` in `SendBirdDesk`. +- Added `submitFeedback()` in `Ticket`. + +### v1.0.7(Jul 12, 2019) + +- Added `customFields` property to `Ticket`. + +### v1.0.6(May 13, 2019) + +- Added `reopen()` in `Ticket` +- Added `groupKey` and `customFields` parameters to `Ticket.create()`. +- Added `customFieldFilter` parameter to `getOpenedList()` and `getClosedList()` in `Ticket`. + +### v1.0.5(Nov 23, 2018) + +- Added SendBird parameter to SendBirdDesk.init(). +- Bug-fix in 'SendBird missing' error at init(). + +### v1.0.4(July 18, 2018) + +- Added setApiHost to customize host. + +### v1.0.3(July 4, 2018) + +- Corrected package.json to fix library path. + +### v1.0.2(May 21, 2018) + +- Applied ticket assignment update. + +### v1.0.1(Mar 30, 2018) + +- Added SendBirdDesk.init(). +- Added SendBirdDesk.isDeskChannel(channel). +- Added TypeScript interface - SendBird.Desk.d.ts. +- Added console logger in debug mode. + +### v1.0.0-zendesk(Mar 16, 2018) + +- Zendesk-integrated version release. + diff --git a/LICENSE.md b/LICENSE.md index 2f6f2ec..e40a6a8 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ -# SendBird SDK LICENSE +#SendBird SDK LICENSE -This is an agreement between You ("You") and SendBird, Inc., the developer of SendBird, located at 400 1st Ave, San Mateo, CA 94401, ("SendBird") regarding Your use of the SendBird SDK and any associated documentation or other materials made available by SendBird (collectively the "SDK"). This agreement applies to any updates or supplements for the SDK, unless other terms accompany those items. If so, those other terms apply. +This is an agreement between You ("You") and SendBird DBA (Smile Family, Inc.), the developer of SendBird, located at 541 Jefferson Ave. Suite 100 Redwood City, CA 94063, ("SendBird") regarding Your use of the SendBirdCS SDK and any associated documentation or other materials made available by SendBird (collectively the "SDK"). This agreement applies to any updates or supplements for the SDK, unless other terms accompany those items. If so, those other terms apply. By installing, accessing or otherwise using the SDK, You accept the terms of this agreement. If You do not agree to the terms of this agreement, do not install, access or use the SDK. diff --git a/README.md b/README.md index 5eee27c..c9ee941 100644 --- a/README.md +++ b/README.md @@ -1,524 +1,81 @@ -# [Sendbird](https://sendbird.com) Desk SDK for JavaScript -![Platform](https://img.shields.io/badge/platform-JAVASCRIPT-orange.svg) -![Languages](https://img.shields.io/badge/language-JAVASCRIPT-orange.svg) +# SendBird Desk SDK for JavaScript +This project provides SendBird Desk SDK for JavaScript which includes +SendBird messaging SDK for JavaScript, SendBird Desk SDK Core, +and SendBird Desk Widget sample -## Table of contents +## **Install** - 1. [Introduction](#introduction) - 1. [Before getting started](#before-getting-started) - 1. [Getting started](#getting-started) - 1. [Creating your first ticket](#creating-your-first-ticket) - 1. [Implementation guide](#implementation-guide) +> Note: Make sure you have node-js 18 and npm v9 or higher installed -
- -## Introduction - -**Sendbird Desk** enables strong customer engagement through live, in-app support. The Desk SDK lets you easily initialize, configure, and build customer support-related functionality into your JavaScript applications. - -### How it works - -Sendbird Desk is a plugin of the [Sendbird Chat Platform](https://sendbird.com/docs/chat) for managing tickets, and thus Desk events are handled by event handlers through the [Chat SDK](https://github.com/sendbird/Sendbird-JavaScript). - -Every ticket is assigned appropriate agents and will be directed to a chat's group channel, which implements real-time messaging on tickets with Sendbird Chat SDK. - -### Concepts - -These are a few of the main components of Desk SDK. - -- **Channels**: The various ways through which support can be requested e.g. in-app chats from different OS platforms or social media like Facebook and Instagram. -- **Tickets**: A ticket is created when a customer and agent start a conversation and is seen as a unit of customer’s inquiry. There are five types of tickets. -- **Agents**: An agent receives the requests and also handles the conversation in the [Sendbird Dashboard](https://dashboard.sendbird.com/auth/signin). -- **Admins**: Admins are agents who are granted the additional privileges of managing the overall dashboard settings and the tickets. -- **Messages**: Desk has two types of messages that fall into further subtypes. The following table shows the hierarchical structure of messages. - -||Sender|Subtypes| -|---|---|---| -| User message| Agent or customer|Rich messages | -| Admin message |Sent from the Desk server without a specific sender |Notification messages and System messages | - -> **Note**: Rich messages are further classified into [URL preview](#add-url-previews), [confirmation request for ticket closing](#request-confirmation-to-close-a-ticket), and [feedback request](#request-customer-feedback) messages. - -### More about Sendbird Desk SDK for JavaScript - -Find out more about Sendbird Desk SDK for JavaScript on [Desk SDK for JavaScript doc](https://sendbird.com/docs/desk/v1/javascript/getting-started/about-desk-sdk). If you have any comments or questions regarding bugs and feature requests, visit [Sendbird community](https://community.sendbird.com). - -
- -## Before getting started - -This section shows the prerequisites you need to check to use Sendbird Desk SDK for JavaScript. - -### Requirements - -- `NodeJS 10.13.x+ along with npm` -- [`Sendbird JavaScript SDK 3.0.55 or later`](https://github.com/sendbird/Sendbird-JavaScript) - -### Supported browsers - -- `All modern versions of Chrome, FireFox, Edge, Safari, and etc. supporting ES6+` -- `IE 11+` -- `Mobile browsers (Android/iOS)` - -
- -## Getting started - -This section gives you information you need to get started with Sendbird Desk SDK for JavaScript. - -### Try the sample app - -Our sample app demonstrates the core features of Sendbird Desk SDK. Download the app from our GitHub repository to get an idea of what you can do with the actual SDK and to get started building your own project. - -- https://github.com/sendbird/quickstart-desk-javascript - -### Step 1: Create a Sendbird application from your dashboard - -A Sendbird application comprises everything required in a chat service including users, message, and channels. To create an application: - -1. Go to the Sendbird Dashboard and enter your email and password, and create a new account. You can also sign up with a Google account. -2. When prompted by the setup wizard, enter your organization information to manage Sendbird applications. -3. Lastly, when your dashboard home appears after completing setup, click **Create +** at the top-right corner. - -Regardless of the platform, only one Sendbird application can be integrated per app; however, the application supports communication across all Sendbird’s provided platforms without any additional setup. - -> **Note**: All the data is limited to the scope of a single application, thus users in different Sendbird applications are unable to chat with each other. - -### Step 2: Download and install the Desk SDK - -Installing the Desk SDK is simple if you’re familiar with using external libraries or SDK’s in your projects. These are the following three methods for Desk SDK implementation: - -Install the Desk SDK with npm by entering the command below on the command line. - -```bash -# npm -~$ cd /path/to/your-project -~$ npm install --save sendbird-desk -``` - -Automatically download our Desk SDK with just an `npm` install command by adding `sendbird-desk` dependency to the `package.json` file of your project. - -Download the latest Desk SDK for JavaScript, copy the Desk SDK to your project classpath (most commonly the lib/ directory), and then include the Desk SDK file to your working file. - -```javascript - -``` - -
- -## Creating your first ticket - -After installation has been completed, a ticket can be created for communication between an agent and customer. Follow the step-by-step instructions below to create your first ticket. - -### Step 1: Initialize the Desk SDK - -First, a SendBirdDesk instance must be initialized when launching a client app. Before initializing `SendBirdDesk` instance, you need to initialize Sendbird with the `APP_ID` of your Sendbird application from the dashboard. Pass the initialized `SendBird` instance as an argument as a parameter in the `SendBirdDesk.init()` method. - -Implement the following command in your project path. - -```javascript -SendBirdDesk.init(SendBird); -``` - -> **Note**: The same `APP_ID` should be used for both Desk and Chat SDKs. If you initiate the Sendbird Desk with a `SendBird` instance of another `App_ID`, all existing data in the client app will be cleared. - -It is possible to use the Chat SDK only or both Chat and Desk SDKs together in your client app depending on the chat service you want to provide. - -|SDK|Used for| -|---|---| -|Chat SDK|In-app messenger where customers can chat with each other.| -|Chat and Desk SDKs|Tickets where customers can chat with agents.| - -### Step 2: Authenticate to Sendbird server - -Customers can request support from various types of channels: in-app chats or social media such as Facebook, Instagram and Twitter. To use these support features of Desk SDK, the `SendBirdDesk` instance should be connected with Sendbird server depending on which channel the request is from: - -- **Sendbird Chat Platform**: Authenticate using the `authenticate()` method with their user IDs. -- **Social media platforms**: No authentication needed as customers are automatically registered in the dashboard with their social media accounts. - -Once authenticated, customers can live-chat with agents based on Sendbird Chat platform. - -```javascript -const sb = new SendBird({ appId: APP_ID }); -sb.connect(USER_ID, ACCESS_TOKEN, (user, error) => { - if (error) throw error; - SendBirdDesk.init(SendBird); - SendBirdDesk.authenticate(USER_ID, ACCESS_TOKEN, (user, error) => { - if (error) throw error; - // SendBirdDesk is now initialized, and the customer is authenticated. - }); -} -``` - -> **Note**: **Customers from Sendbird Chat platform** signifies users who are already authenticated with the Chat SDK. If you’re implementing Chat SDK and Desk SDK at the same time, [connect a user to Sendbird server with their user ID and access token](https://sendbird.com/docs/chat/v3/javascript/guides/authentication) first. - -### Step 3: Create a ticket - -Implement the `Ticket.create()` method to create a new ticket either before or after the customer’s initial message. - -```javascript -Ticket.create(TICKET_TITLE, USER_NAME, (ticket, error) => { - // The ticket.channel property indicates the group channel object within the ticket. -}); -``` - -Once a ticket is successfully created on the Sendbird server, you can access the ticket and its channel in the `ticket.getChannel()` through the callback from the server. - -Before a customer sends the first message, agents can’t see the ticket in the dashboard and ticket assignment does not occur. When conversation starts, the ticket is assigned to an available agent by the Desk Dashboard while messages are sent and received through the Chat SDK. - -You can use the following parameters when creating a ticket. - -> **Note**: Only Groupkey and customFields need to be defined and are only accessible from the Dashboard. - -|Argument|Type|Description| -|---|---|---| -|TICKET_TITLE|string |Specifies the title of the ticket.| -|USER_NAME|string |Specifies the name of the user who submits or receives the ticket.| -|GROUP_KEY|string | Specifies the identifier of a specific team.| -|CUSTOM_FILEDS|nested object|Specifies additional information of the ticket that consists of **key-value** custom items. Only custom fields already registered in **Settings** > **Ticket** fields in your dashboard can be used as a key. | -|PRIORITY |string |Specifies the priority value of the ticket. Higher values stand for higher priority. Valid values are **LOW**, **MEDIUM**, **HIGH** and **URGENT**. | -|RELATED_CHANNEL_URLS|array | Specifies group channels in Sendbird Chat platform that are related to this ticket and consists of channel URLs and channel names. Up to 3 related channels can be added.| - -
- -## Implementation guide - -This section details the procedure to handle and close a ticket from your client app. - -### Add custom information to a ticket - -Use the `ticket.setCustomFields()` method to add additional information about a ticket. - -```javascript -const customFields = { - 'Product type': 'Desk', - 'line': '3' -}; - -ticket.setCustomFields(customFields, (ticket, error) => { - if (error) throw error; - // The 'customFields' property of a ticket has been updated. -}); ``` - -> **Note**: Only custom fields registered in **Desk** > **Settings** > **Ticket fields** of your dashboard can be used as a key. - -### Add custom information to a customer - -Use the `setCustomerCustomFields()` method of the `SendBirdDesk` to make your customers add additional information about themselves. - -```javascript -SendBirdDesk.setCustomerCustomFields( - { - gender: "male", - age: 20 - }, - (error) => { - if (!error) { - // Custom fields for the customer are set. - // Some fields can be ignored if their keys aren't registered in the dashboard. - } - } -); +npm install +npm run build ``` -> **Note**: Only custom fields registered in **Desk** > **Settings** > **Customer fields** of your dashboard can be used as a key. +Note: We use `dts-bundle-generator` because we need a single +`d.ts` file for the SDK. `rollup-plugin-typescript2` errors out +when we try to use `declaration: true` in `tsconfig.json`. -### Add related channels +## **Setting up Tests** -Related channels indicate group channels in Sendbird Chat platform that are related to a ticket. When creating a ticket, pass the `channel_url`s of the related group channels as an argument to the `relatedChannelUrls` parameter in the `Ticket.create()` method. To update the related channels, use the `ticket.setRelatedChannelUrls()` instead. The `ticket.relatedChannels` property in the callback indicates the group channel object of related channels and it contains channel names and their URLs. +Copy `.env.example` to `.env` and fill in the values. -```javascript -Ticket.create(TICKET_TITLE, USER_NAME, GROUP_KEY, CUSTOM_FIELDS, PRIORITY, RELATED_CHANNEL_URLS, (ticket, error) => { -}); +These variables should be setup in circle-ci as well. +https://app.circleci.com/settings/project/github/sendbird/desk-js/environment-variables -ticket.setRelatedChannelUrls(RELATED_CHANNEL_URLS, (ticket, error) => { - // The ticket's 'relatedChannels' property has been updated. -}); -``` +> Note: env variables can be found in 1password - engineering/desk_staging_e2e_test +> To login as admin in dashboard use `E2E Admin - Dev` in 1password -> **Note**: Up to 3 related channels can be added per ticket. -### Add URL previews +## **Test** -With URL previews, your application users can meet their expectations of what they’re going to get before they open the link during the conversations. +We use jest with js-dom for testing +Test cases are located in `test/cases` directory and are named `*.test.ts -To preview URLs, every text message should be checked if it includes any URLs. When a text message including a URL is successfully sent, the URL should be extracted and passed to Sendbird server using the `getUrlPreview()` method. Set the parsed data received from the server as a `JSON` object and stringify the object to pass it as an argument to a parameter in the `updateUserMessage()` method. Then the updated message with URL preview is delivered to the client apps through the `onMessageUpdated()` method of the channel event handler. +> Note: Try not to run tests in auto-watch mode. We would be connecting +> and disconnecting to the server for each test case -```javascript -ticket.channel.sendUserMessage(TEXT, (message, error) => { - if (!error) { - const message = message; - if (SendBirdDesk.Message.UrlRegExp.test(message.message)) { - const urlInMessage = SendBirdDesk.Message.UrlRegExp.exec(message.message)[0]; - SendBirdDesk.Ticket.getUrlPreview(urlInMessage, (response, error) => { - if (error) throw error; - this.ticket.channel.updateUserMessage( - message.messageId, - message.message, - JSON.stringify({ - type: SendBirdDesk.Message.DataType.URL_PREVIEW, - body: { - url: urlInMessage, - site_name: response.data.siteName, - title: response.data.title, - description: response.data.description, - image: response.data.image - } - }), - message.customType, - (response, error) => { - if (error) throw error; - this.updateMessage(response); - }); - }); - } - } -}); +### Local test ``` - -In the `onMessageUpdated()` method of the channel event handler, you can find the data for URL preview in the `message.data` property as below. - -```json -{ - "type": "SENDBIRD_DESK_URL_PREVIEW", - "body": { - "url": "https://sendbird.com/", - "site_name": "Sendbird", - "title": "Sendbird - A Complete Chat Platform, Messaging and Chat SDK and API", - "description": "Sendbird's chat, voice and video APIs and SDKs connect users through immersive, modern communication solutions that drive better user experiences and engagement.", - "image": "https://6cro14eml0v2yuvyx3v5j11j-wpengine.netdna-ssl.com/wp-content/uploads/sendbird_thumbnail.png" - } -} +npm run test ``` -### Receive system messages - -Admin messages are customizable messages that are sent by the system, and there are 2 types of admin messages. **Notifications** are messages that are sent and displayed to both customers and agents, such as welcome messages or delay messages. **System messages** are messages sent and displayed to agents in the Ticket details view when a ticket has some changes, such as changes in ticket status and assignee. - -> **Note**: You can customize notifications in **Desk** > **Settings** > **Triggers**, and system messages in **Desk** > **Settings** > **System messages** in your dashboard. +## **Publish** not implemented yet / maybe use github action -When the client app receives the message through the ‘onMessageReceived()’ method of the channel event handler, system messages are distinguished from notification messages by the value of the ‘message.custom_type’, and their subtype is specified in the ‘message.data’ as below. - -```json -{ - "message_id" : 40620745, - "type": "ADMM", - "custom_type": "SENDBIRD_DESK_ADMIN_MESSAGE_CUSTOM_TYPE", - "data": "{\"type\": \"SYSTEM_MESSAGE_TICKET_ASSIGNED_BY_SYSTEM\", \"ticket\": }", - "message": "The ticket is automatically assigned to Cindy." +1. Make sure main branch is up-to-date. +2. Cut a release branch - `release/v#.#.##`. +3. On release branch: + * Prepare changelog + * Update version in `package.json` file. +4. Make PR to main branch. +{ optional steps + 5. Make release -> `npm run build; npm publish --tag beta` + 6. Check release candidate is good to go. } -``` - -> **Note**: The `transfer` appears only when the `data` has `SYSTEM_MESSAGE_TICKET_TRANSFERRED_BY_AGENT`. - -System messages are intended to be displayed for agents only. Refer to the following sample code to avoid displaying them to your customers. +7. Merge PR to main branch. +8. Make release -> `npm run build; npm publish` +9. Add release note to github release. +10. Copy `CHANGELOG`, `LICENSE`, `dist` & `package.json` to `https://github.com/sendbird/SendBird-Desk-SDK-JavaScript` repo main branch. -```javascript -function isVisible(message) { - let data = {}; - try { - data = message.data ? JSON.parse(message.data) : null; - } catch (e) { - throw e; - } +## **Prettier** - message.isSystemMessage = message.customType === 'SENDBIRD_DESK_ADMIN_MESSAGE_CUSTOM_TYPE'; - message.isAssigned = data.type === SendBirdDesk.Message.DataType.TICKET_ASSIGN; - message.isTransferred = data.type === SendBirdDesk.Message.DataType.TICKET_TRANSFER; - message.isClosed = data.type === SendBirdDesk.Message.DataType.TICKET_CLOSE; - - return !message.isSystemMessage - && !message.isAssigned - && !message.isTransferred - && !message.isClosed; -} ``` - -### Request confirmation to close a ticket - -While admins have permission to directly close a ticket, agents can either close a ticket as admins or ask customers to close a ticket, which will depend on the agent permission settings. The confirmation request message can have 3 types of states as below. - -|State|Description| -|---|---| -|WAITING |Set when an agent sends a confirmation request message. | -|CONFIRMED| Set when a customer agrees to close the ticket. (Default: **true**)| -|DECLINED |Set when a customer declines to close the ticket. (Default: **false**)| - -When agents send confirmation request messages, the state is set to `WAITING`. The customer can reply **Yes** or **No** by calling `Ticket.confirmEndofChat()`, and the state will change to `CONFIRMED` or `DECLINED` according to this answer. - -```javascript -Ticket.confirmEndOfChat(USER_MESSAGE, 'yes'|'no', (ticket, error) => { - if (error) throw error; -}); +npm run format ``` +or +(in Visual Studio Code) +Install `Prettier - Code formatter` plugin. +Open file to adjust prettier, select all(`cmd+a`), and `cmd+k, cmd+f`. -Sendbird Desk server notifies the customer’s client app of updates through the `onMessageUpdate()` method of the channel event handler. +## **Linting** -```javascript -channelHandler.onMessageUpdated = (channel, message) => { - SendBirdDesk.Ticket.getByChannelUrl(channel.url, (ticket, error) => { - if (error) throw error; - - let data = JSON.parse(message.data); - const isClosureInquired = data.type === SendBirdDesk.Message.DataType.TICKET_INQUIRE_CLOSURE; - if (isClosureInquired) { - const closureInquiry = data.body; - switch (closureInquiry.state) { - case SendBirdDesk.Message.ClosureState.WAITING: - // TODO: Implement your code for the UI when there is no response from the customer. - break; - case SendBirdDesk.Message.ClosureState.CONFIRMED: - // TODO: Implement your code for the UI when the customer confirms to close the ticket. - break; - case SendBirdDesk.Message.ClosureState.DECLINED: - // TODO: Implement your code for the UI when the customer declines to close the ticket. - break; - } - } - }); -}; ``` - -> **Note**: You can find the stringified `JSON` object of the following in the `message.data` property within the `onMessageUpdate()` of the channel event handler. - -```json -{ - "type": "SENDBIRD_DESK_INQUIRE_TICKET_CLOSURE", - "body": { - "state": "CONFIRMED" - } -} +npm run lint ``` -### Request customer feedback - -You can send a message to customers right after closing a ticket to ask about their satisfaction level with the support. When the **Customer satisfaction rating** feature is turned on in your dashboard, customers will get a message asking to give a score and leave a comment as feedback. The message can have 2 states as below. - -|State|Description| -|---|---| -|WAITING|Set when an agent sends a customer feedback request message.| -|CONFIRMED|Set when a customer sends a response.| +## **Browser Support** -When a customer replies to the message, their score and comment for the ticket are sent to Sendbird server by calling the `ticket.submitFeedback()` method. Then, the state of the confirmation request message is changed to `CONFIRMED`. - -```javascript -ticket.submitFeedback(USER_MESSAGE, SCORE, COMMENT, (ticket, error) => { - if (error) throw error; -}); -``` - -Sendbird Desk server notifies the customer’s client app of updates through the `onMessageUpdate()` method of the channel event handler. - -```javascript -channelHandler.onMessageUpdated = (channel, message) => { - SendBirdDesk.Ticket.getByChannelUrl(channel.url, (ticket, error) => { - if (error) throw error; - - let data = JSON.parse(message.data); - const isFeedbackMessage = data.type === SendBirdDesk.Message.DataType.TICKET_FEEDBACK; - if (isFeedbackMessage) { - const feedback = data.body; - switch (feedback.state) { - case SendBirdDesk.Message.FeedbackState.WAITING: - // TODO: Implement your code for the UI when there is no response from the customer. - break; - case SendBirdDesk.Message.FeedbackState.CONFIRMED: - // TODO: Implement your code for the UI when there is a response from the customer. - break; - } - } - }); -}; -``` - -> **Note**: You can find the stringified `JSON` object of the following in the `message.data` property within the `onMessageUpdate()` of the channel event handler. - -```json -{ - "type": "SENDBIRD_DESK_CUSTOMER_SATISFACTION", - "body": { - "state": "CONFIRMED", - "customerSatisfactionScore": 3, - "customerSatisfactionComment": "It was really helpful :)" -} -``` - -### Reopen a closed ticket - -A closed ticket can be reopened by using the `reopen()` method in the `Ticket`. - -```javascript -closedTicket.reopen((openTicket, error) => { - if (error) throw error; - // Implement your code to update the ticket list with the 'openTicket' object. -}); -``` - -### Retrieve a list of tickets - -You can retrieve a list of the current customer’s open and closed tickets by using the `Ticket.getOpenedList()` and `Ticket.getClosedList()`. - -Use `getAllTickets()` to fetch all tickets without open/closed distinction (v1.0.21) - -> **Note**: Only 10 tickets can be retrieved per request by message creation time in descending order. - -```javascript -// getOpenedList() -Ticket.getOpenedList(OFFSET, (openedTickets, error) => { - if (error) throw error; - - const tickets = openedTickets; - // offset += tickets.length; for the next tickets. - // Implement your code to display the ticket list. -}); -``` - -```javascript -// getClosedList()’ -Ticket.getClosedList(OFFSET, (closedTickets, error) => { - if (error) throw error; - - const tickets = closedTickets; - // offset += tickets.length; for the next tickets. - // Implement your code to display the ticket list. -}); -``` - -For tickets set with custom fields, you can add a filter to the `getOpenList()` and `getClosedList()` to sort tickets by keys and values of custom fields. - -```javascript -const customFieldFilter = {'subject' : 'doggy_doggy'}; -Ticket.getOpenedList(OFFSET, customFieldFilter, (openedTickets, error) => { - if (error) throw error; - - const tickets = openedTickets; - // offset += tickets.length; for the next tickets. - // Implement your code to display the ticket list. -}); -``` - -### Retrieve a ticket - -You can retrieve a specific ticket with its channel URL. - -```javascript -Ticket.getByChannelUrl(CHANNEL_URL, (ticket, error) => { - if (error) throw error; -}); -``` - -### Display open ticket count - -You can display the number of open tickets on your client app by using the `Ticket.getOpenCount()`. - -```javascript -Ticket.getOpenCount((count, error) => { - if (error) throw error; - - const numberOfOpenTickets = count; - // Implement your code with the returned value. -}); -``` +- Modern browsers supporting ES6+ (Chrome, FireFox, Edge, Safari, etc) +- Mobile browsers (Android/iOS) diff --git a/SendBird.Desk.d.ts b/SendBird.Desk.d.ts deleted file mode 100644 index b07e0ad..0000000 --- a/SendBird.Desk.d.ts +++ /dev/null @@ -1,184 +0,0 @@ -/** - * Type Definitions for SendBird Desc SDK v1.0.23 - * homepage: https://sendbird.com/ - */ -import SendBird from 'sendbird'; - -export default SendBirdDesk; -export { Ticket, Agent, RelatedChannel }; - -declare const SendBirdDesk: SendBirdDeskStatic; -declare const Ticket: SendBirdDesk.TicketStatic; -declare const Agent: SendBirdDesk.AgentStatic; -declare const RelatedChannel: SendBirdDesk.RelatedChannelStatic; - -interface SendBirdDeskStatic { - version: string; - Ticket: SendBirdDesk.TicketStatic; - Agent: SendBirdDesk.AgentStatic; - Message: SendBirdDesk.MessageStatic; - RelatedChannel: SendBirdDesk.RelatedChannelStatic; - Error: SendBirdDesk.SendBirdDeskErrorStatic; - - init(SendBird: object): void; - authenticate(userId: string, callback: SendBirdDesk.CommonCallback): void; - authenticate(userId: string, accessToken: string, callback: SendBirdDesk.CommonCallback): void; - isDeskChannel(channel: SendBird.GroupChannel): boolean; - setDebugMode(): void; - setCustomerCustomFields(customFields: object, callback: SendBirdDesk.CommonCallback): void; -} - -declare namespace SendBirdDesk { - type TicketStatus = { - INITIALIZED: string; - PROACTIVE: string; - UNASSIGNED: string; - ASSIGNED: string; - OPEN: string; - CLOSED: string; - }; - type TicketPriority = { - URGENT: string; - HIGH: string; - MEDIUM: string; - LOW: string; - }; - type MessageCustomType = { - RICH_MESSAGE: string; - ADMIN_MESSAGE: string; - }; - type MessageDataType = { - TICKET_INQUIRE_CLOSURE: string; - TICKET_ASSIGN: string; - TICKET_TRANSFER: string; - TICKET_CLOSE: string; - TICKET_FEEDBACK: string; - URL_PREVIEW: string; - }; - type MessageClosureState = { - WAITING: string; - CONFIRMED: string; - DECLINED: string; - }; - type MessageFeedbackState = { - WAITING: string; - CONFIRMED: string; - }; - type CommonCallback = (res: object, error: Error) => void; - type TicketCallback = (ticket: Ticket, error: Error) => void; - type TicketArrayCallback = (list: Array, error: Error) => void; - - type GetTicketListParams = { - offset: integer; - customFieldFilter: object; - group: string; - status: string; - } - - interface TicketStatic { - Status: TicketStatus; - isStatus(val: string): boolean; - clearCache(channelUrl: string): void; - create(title: string, name: string, callback: TicketCallback): void; - create(title: string, name: string, groupKey: string, callback: TicketCallback): void; - create(title: string, name: string, groupKey: string, customFields: object, callback: TicketCallback): void; - create( - title: string, - name: string, - groupKey: string, - customFields: object, - priority: TicketPriority, - callback: TicketCallback - ): void; - create( - title: string, - name: string, - groupKey: string, - customFields: object, - priority: TicketPriority, - relatedChannelUrls: Array, - callback: TicketCallback - ): void; - create( - title: string, - name: string, - groupKey: string, - customFields: object, - priority: TicketPriority, - relatedChannelUrls: Array, - botKey: string, - callback: TicketCallback - ): void; - getOpenCount(callback: CommonCallback): void; - getByChannelUrl(channelUrl: string, callback: TicketCallback): void; - getByChannelUrl(channelUrl: string, cachingEnabled: boolean , callback: TicketCallback): void; - getList(params: GetTicketListParams, callback: TicketArrayCallback): void; - getAllTickets(offset: number, callback: TicketArrayCallback): void; - getAllTickets(offset: number, customFieldFilter: object, callback: TicketArrayCallback): void; - getOpenedList(offset: number, callback: TicketArrayCallback): void; - getOpenedList(offset: number, customFieldFilter: object, callback: TicketArrayCallback): void; - getClosedList(offset: number, callback: TicketArrayCallback): void; - getClosedList(offset: number, customFieldFilter: object, callback: TicketArrayCallback): void; - getUrlPreview(url: string, callback: CommonCallback): void; - confirmEndOfChat( - message: SendBird.UserMessage | SendBird.FileMessage | SendBird.AdminMessage, - confirmYN: string, - callback: CommonCallback - ): void; - submitFeedback( - message: SendBird.UserMessage | SendBird.FileMessage | SendBird.AdminMessage, - score: number, - comment: string, - callback: CommonCallback - ): void; - new(json: object): Ticket; - } - interface Ticket { - id: string; - title: string; - status: string; - agent: Agent; - customer: object; - info: object; - priority: TicketPriority; - relatedChannels: Array; - channel: object; - channelUrl: string; - customFields: object; - updatedAt: number; - fetchFromJSON(json: object): void; - refresh(callback: TicketCallback): void; - reopen(callback: TicketCallback): void; - cancel(callback: TicketCallback): void; - cancel(groupKeyForTransfer: string, callback: TicketCallback): void; - close(callback: TicketCallback): void; - setPriority(priority: TicketPriority, callback: CommonCallback): void; - setRelatedChannelUrls(relatedChannelUrls: Array, callback: CommonCallback): void; - setCustomFields(customFields: object, callback: CommonCallback): void; - selectQuestion(faqFileId: number, question: string, callback: CommonCallback): void; - } - interface AgentStatic { - new(json: object): Agent; - } - interface Agent { - userId: string; - name: string; - profileUrl: string; - fetchFromJSON(json: object): void; - } - interface RelatedChannelStatic { - new(json: object): Agent; - } - interface RelatedChannel { - name: string; - channelUrl: string; - fetchFromJSON(json: object): void; - } - interface MessageStatic { - CustomType: MessageCustomType; - DataType: MessageDataType; - ClosureState: MessageClosureState; - FeedbackState: MessageFeedbackState; - } - interface SendBirdDeskErrorStatic { } -} diff --git a/SendBird.Desk.min.js b/SendBird.Desk.min.js deleted file mode 100644 index 6564b3e..0000000 --- a/SendBird.Desk.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright(c) 2016 SendBird, Inc. - * SendBird Desk JavaScript SDK v1.0.23 - */ - -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).SendBirdDesk=e()}(this,function(){"use strict";function U(r,n){return function(){for(var t=new Array(arguments.length),e=0;eo;)!h(n,r=e[o++])||~Ve(a,r)||We(a,r);return a}function Gt(t){return tr[t]||(tr[t]=jt(t))}function Kt(){}function zt(t){t.write(or("")),t.close();var e=t.parentWindow.Object;return t=null,e}function Ht(t){cr[sr][t]=!0}function $t(t,e){return(t=Cr[_r(t)])==jr||t!=Pr&&(g(e)?r(e):!!e)}function Jt(t,e){var r,n,o,a=t.target,i=t.global,s=t.stat,c=i?u:s?u[a]||ee(a,{}):(u[a]||{}).prototype;if(c)for(r in e){if(n=e[r],o=t.dontCallGetSet?(o=Ur(c,r))&&o.value:c[r],!Fr(i?r:a+(s?".":"#")+r,t.forced)&&void 0!==o){if(typeof n==typeof o)continue;Dr(n,o)}(t.sham||o&&o.sham)&&fr(n,"sham",!0),I(c,r,n,t)}}function qt(){return this}function Vt(t,e){return{value:t,done:e}}var Wt,Qt,Xt,i,Yt,Zt,u=Ct("object"==typeof globalThis&&globalThis)||Ct("object"==typeof window&&window)||Ct("object"==typeof self&&self)||Ct("object"==typeof t&&t)||function(){return this}()||Function("return this")(),te=Object.defineProperty,ee=function(e,r){try{te(u,e,{value:r,configurable:!0,writable:!0})}catch(t){u[e]=r}return r},t="__core-js_shared__",s=u[t]||ee(t,{}),t=_t(function(t){(t.exports=function(t,e){return s[t]||(s[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.29.0",mode:"global",copyright:"© 2014-2023 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.29.0/LICENSE",source:"https://github.com/zloirock/core-js"})}),re=Object,ne=d({}.hasOwnProperty),h=Object.hasOwn||function(t,e){return ne(Pt(t),e)},oe=0,ae=Math.random(),ie=d(1..toString),l="undefined"!=typeof navigator&&String(navigator.userAgent)||"",f=u.process,y=u.Deno,f=f&&f.versions||y&&y.version,y=f&&f.v8,se=O=!(O=y?0<(S=y.split("."))[0]&&S[0]<4?1:+(S[0]+S[1]):O)&&l&&(!(S=l.match(/Edge\/(\d+)/))||74<=S[1])&&(S=l.match(/Chrome\/(\d+)/))?+S[1]:O,ce=!!Object.getOwnPropertySymbols&&!r(function(){var t=Symbol();return!String(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&&se&&se<41}),f=ce&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,ue=u.Symbol,le=t("wks"),fe=f?ue.for||ue:ue&&ue.withoutSetter||jt,y="object"==typeof document&&document.all,l={all:y,IS_HTMLDDA:void 0===y&&void 0!==y},de=l.all,g=l.IS_HTMLDDA?function(t){return"function"==typeof t||t===de}:function(t){return"function"==typeof t},pe=l.all,m=l.IS_HTMLDDA?function(t){return"object"==typeof t?null!==t:g(t)||t===pe}:function(t){return"object"==typeof t?null!==t:g(t)},he=String,ye=TypeError,b=function(t){if(m(t))return t;throw ye(he(t)+" is not an object")},E=!r(function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}),S=E&&r(function(){return 42!=Object.defineProperty(function(){},"prototype",{value:42,writable:!1}).prototype}),me=u.document,ge=m(me)&&m(me.createElement),be=function(t){return ge?me.createElement(t):{}},Ee=!E&&!r(function(){return 7!=Object.defineProperty(be("div"),"a",{get:function(){return 7}}).a}),Se=Function.prototype.call,v=vt?Se.bind(Se):function(){return Se.apply(Se,arguments)},ve=d({}.isPrototypeOf),Re=Object,Oe=f?function(t){return"symbol"==typeof t}:function(t){var e=Ft("Symbol");return g(e)&&ve(e.prototype,Re(t))},Ie=String,we=function(t){try{return Ie(t)}catch(t){return"Object"}},Ae=TypeError,ke=function(t){if(g(t))return t;throw Ae(we(t)+" is not a function")},Te=function(t,e){t=t[e];return Tt(t)?void 0:ke(t)},De=TypeError,Ne=function(t,e){var r,n;if("string"===e&&g(r=t.toString)&&!m(n=v(r,t)))return n;if(g(r=t.valueOf)&&!m(n=v(r,t)))return n;if("string"!==e&&g(r=t.toString)&&!m(n=v(r,t)))return n;throw De("Can't convert object to primitive value")},_e=TypeError,Ce=a("toPrimitive"),Pe=TypeError,je=Object.defineProperty,Fe=Object.getOwnPropertyDescriptor,Ue="enumerable",Me="configurable",Be="writable",R={f:E?S?function(t,e,r){var n;return b(t),e=Ut(e),b(r),"function"==typeof t&&"prototype"===e&&"value"in r&&Be in r&&!r[Be]&&(n=Fe(t,e))&&n[Be]&&(t[e]=r.value,r={configurable:(Me in r?r:n)[Me],enumerable:(Ue in r?r:n)[Ue],writable:!1}),je(t,e,r)}:je:function(t,e,r){if(b(t),e=Ut(e),b(r),Ee)try{return je(t,e,r)}catch(t){}if("get"in r||"set"in r)throw Pe("Accessors not supported");return"value"in r&&(t[e]=r.value),t}},xe=Math.ceil,Le=Math.floor,Ge=Math.trunc||function(t){t=+t;return(0"+t+""},ar=function(){try{Wt=new ActiveXObject("htmlfile")}catch(t){}ar="undefined"==typeof document||document.domain&&Wt?zt(Wt):(t=be("iframe"),e="java"+rr+":",t.style.display="none",Ze.appendChild(t),t.src=String(e),(e=t.contentWindow.document).open(),e.write(or("document.F=Object")),e.close(),e.F);for(var t,e,r=Qe.length;r--;)delete ar[er][Qe[r]];return ar()},ir=(qe[nr]=!0,Object.create||function(t,e){var r;return null!==t?(Kt[er]=b(t),r=new Kt,Kt[er]=null,r[nr]=t):r=ar(),void 0===e?r:Ye.f(r,e)}),y=R.f,sr=a("unscopables"),cr=Array.prototype,ur=(null==cr[sr]&&y(cr,sr,{configurable:!0,value:ir(null)}),{}),l=u.WeakMap,f=g(l)&&/native code/.test(String(l)),lr=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}},fr=E?function(t,e,r){return R.f(t,e,lr(1,r))}:function(t,e,r){return t[e]=r,t},dr="Object already initialized",pr=u.TypeError,O=u.WeakMap,hr=f||s.state?((i=s.state||(s.state=new O)).get=i.get,i.has=i.has,i.set=i.set,Qt=function(t,e){if(i.has(t))throw pr(dr);return e.facade=t,i.set(t,e),e},Xt=function(t){return i.get(t)||{}},function(t){return i.has(t)}):(Yt=Gt("state"),qe[Yt]=!0,Qt=function(t,e){if(h(t,Yt))throw pr(dr);return e.facade=t,fr(t,Yt,e),e},Xt=function(t){return h(t,Yt)?t[Yt]:{}},function(t){return h(t,Yt)}),yr={set:Qt,get:Xt,has:hr,enforce:function(t){return hr(t)?Xt(t):Qt(t,{})},getterFor:function(e){return function(t){if(m(t)&&(t=Xt(t)).type===e)return t;throw pr("Incompatible receiver, "+e+" required")}}},S={}.propertyIsEnumerable,mr=Object.getOwnPropertyDescriptor,gr={f:mr&&!S.call({1:2},1)?function(t){t=mr(this,t);return!!t&&t.enumerable}:S},br=Object.getOwnPropertyDescriptor,Er={f:E?br:function(t,e){if(t=St(t),e=Ut(e),Ee)try{return br(t,e)}catch(t){}if(h(t,e))return lr(!v(gr.f,t,e),t[e])}},t=Function.prototype,y=E&&Object.getOwnPropertyDescriptor,l=h(t,"name"),Sr={EXISTS:l,PROPER:l&&"something"===function(){}.name,CONFIGURABLE:l&&(!E||y(t,"name").configurable)},vr=d(Function.toString),Rr=(g(s.inspectSource)||(s.inspectSource=function(t){return vr(t)}),s.inspectSource),Or=_t(function(t){var n=Sr.CONFIGURABLE,o=yr.enforce,e=yr.get,a=String,i=Object.defineProperty,s=d("".slice),c=d("".replace),u=d([].join),l=E&&!r(function(){return 8!==i(function(){},"length",{value:8}).length}),f=String(String).split("String"),t=t.exports=function(t,e,r){"Symbol("===s(a(e),0,7)&&(e="["+c(a(e),/^Symbol\(([^)]*)\)/,"$1")+"]"),r&&r.getter&&(e="get "+e),r&&r.setter&&(e="set "+e),(!h(t,"name")||n&&t.name!==e)&&(E?i(t,"name",{value:e,configurable:!0}):t.name=e),l&&r&&h(r,"arity")&&t.length!==r.arity&&i(t,"length",{value:r.arity});try{r&&h(r,"constructor")&&r.constructor?E&&i(t,"prototype",{writable:!1}):t.prototype&&(t.prototype=void 0)}catch(t){}r=o(t);return h(r,"source")||(r.source=u(f,"string"==typeof e?e:"")),t};Function.prototype.toString=t(function(){return g(this)&&e(this).source||Rr(this)},"toString")}),I=function(t,e,r,n){var o=(n=n||{}).enumerable,a=void 0!==n.name?n.name:e;if(g(r)&&Or(r,a,n),n.global)o?t[e]=r:ee(e,r);else{try{n.unsafe?t[e]&&(o=!0):delete t[e]}catch(t){}o?t[e]=r:R.f(t,e,{value:r,enumerable:!1,configurable:!n.nonConfigurable,writable:!n.nonWritable})}return t},Ir=Qe.concat("length","prototype"),wr={f:Object.getOwnPropertyNames||function(t){return Lt(t,Ir)}},Ar={f:Object.getOwnPropertySymbols},kr=d([].concat),Tr=Ft("Reflect","ownKeys")||function(t){var e=wr.f(b(t)),r=Ar.f;return r?kr(e,r(t)):e},Dr=function(t,e,r){for(var n=Tr(e),o=R.f,a=Er.f,i=0;i=e.length?(t.target=void 0,Vt(void 0,!0)):Vt("keys"==r?n:"values"==r?e[n]:[n,e[n]],!1)},"values"),ur.Arguments=ur.Array);if(Ht("keys"),Ht("values"),Ht("entries"),E&&"values"!==y.name)try{l(y,"name",{value:"values"})}catch(t){}class w{constructor(){}static write(){A.isDebugMode&&console.log(...arguments)}static error(){A.isDebugMode&&console.error(...arguments)}}const sn=new WeakMap;let cn=null,un=null;class A{constructor(){}static get sendBird(){return cn}static set sendBird(t){cn=t}static get isDebugMode(){return sn.get(this)}static setDebugMode(){sn.set(this,!0)}static unsetDebugMode(){sn.set(this,!1)}static get apiHost(){var t;return un||(t=cn.getInstance(),un=`https://desk-api-${t.getApplicationId()}.sendbird.com/sapi`),un}static set apiHost(t){un=t}static get sdkVersion(){return"1.0.23"}static get minSendBirdVersion(){return"3.0.55"}static getParamsFromCallback(t,e){return{result:t,err:e,reversed:!1}}static callback(t,e,r){r&&((t=A.getParamsFromCallback(t,e)).err&&w.error(t.err),t.reversed?r(t.err,t.result):r(t.result,t.err))}static isSendBirdSdkCompatible(t){var e,r="3.0.55".split("."),n=t.split(".");if(r.length!==n.length)return!1;for(e in r){if(parseInt(r[e])parseInt(n[e]))return!1}return!0}}const ln={ERROR_SENDBIRD_SDK_MISSING:{code:100,message:"SendBird SDK is missing."},ERROR_SENDBIRD_SDK_VERSION_NOT_SUPPORTED:{code:101,message:"This SendBird SDK version is not supported."},ERROR_SENDBIRD_DESK_INIT_MISSING:{code:102,message:"SendBird Desk SDK is not initialized. It should be done before authentication."},ERROR_SENDBIRD_SDK_MESSAGE_LIST_FAILED:{code:200,message:"Cannot load messages in SDK client."},ERROR_INVALID_PARAMETER:{code:403,message:"Invalid parameter."},ERROR_DATA_NOT_FOUND:{code:404,message:"Data not found."},ERROR_REQUEST_TIMEOUT:{code:409,message:"Request timed-out."},ERROR_REQUEST:{code:500,message:"Request failed."},ERROR_REQUEST_CANCELED:{code:501,message:"Request canceled."}};class k extends Error{constructor(t,e){super(t),"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(t).stack,this.name="SendBirdDeskError",this.message=t,this.code=e}static get Type(){return ln}static create(t){return t?new k(t.message,t.code):new Error("SendBirdDeskError type missing.")}static throw(t){if(t)throw new k(t.message,t.code)}}function fn(t,e,r){for(var n in e)I(t,n,e[n],r)}function dn(t){if("Function"===n(t))return d(t)}function pn(t,e){return ke(t),void 0===e?t:vt?Cn(t,e):function(){return t.apply(e,arguments)}}function hn(t,e,r){var n,o;b(t);try{if(!(n=Te(t,"return"))){if("throw"===e)throw r;return}n=v(n,t)}catch(t){o=!0,n=t}if("throw"===e)throw r;if(o)throw n;b(n)}function yn(t,e){this.stopped=t,this.result=e}function mn(t,e,r){function n(t){return a&&hn(a,"normal",t),new yn(!0,t)}function o(t){return d?(b(t),y?m(t[0],t[1],n):m(t[0],t[1])):y?m(t,n):m(t)}var a,i,s,c,u,l,f=r&&r.that,d=!(!r||!r.AS_ENTRIES),p=!(!r||!r.IS_RECORD),h=!(!r||!r.IS_ITERATOR),y=!(!r||!r.INTERRUPTED),m=pn(e,f);if(p)a=t.iterator;else if(h)a=t;else{if(!(r=Un(t)))throw Bn(we(t)+" is not iterable");if(void 0!==(e=r)&&(ur.Array===e||jn[Pn]===e)){for(i=0,s=Bt(t);i{Ko.set(this,{deskToken:t.data.token}),w.write("[REQ] connection established"),A.callback(t.data,null,r)}).catch(t=>{A.callback(null,t,r)})):A.callback(null,k.create(k.Type.ERROR_SENDBIRD_SDK_MISSING),r)}static get header(){return{sendbirdDeskToken:Ko.get(this).deskToken,"content-type":"application/json","user-agent":"desk-js@1.0.23"}}static get getParam(){var t=A.sendBird;return t?"sendbirdAppId="+t.getInstance().getApplicationId():""}static get postParam(){var t=A.sendBird;return t?{sendbirdAppId:t.getInstance().getApplicationId()}:{}}}class zo{constructor(t){this.fetchFromJSON(t)}fetchFromJSON(t){this.userId=t?t.user:0,this.name=t?t.displayName:"",this.profileUrl=t?t.photoThumbnailUrl:""}}class Ho{constructor(){}static get CustomType(){return{RICH_MESSAGE:"SENDBIRD_DESK_RICH_MESSAGE",ADMIN_MESSAGE:"SENDBIRD_DESK_ADMIN_MESSAGE_CUSTOM_TYPE"}}static get DataType(){return{TICKET_INQUIRE_CLOSURE:"SENDBIRD_DESK_INQUIRE_TICKET_CLOSURE",TICKET_ASSIGN:"TICKET_ASSIGN",TICKET_TRANSFER:"TICKET_TRANSFER",TICKET_CLOSE:"TICKET_CLOSE",TICKET_FEEDBACK:"SENDBIRD_DESK_CUSTOMER_SATISFACTION",URL_PREVIEW:"SENDBIRD_DESK_URL_PREVIEW"}}static get ClosureState(){return{WAITING:"WAITING",CONFIRMED:"CONFIRMED",DECLINED:"DECLINED"}}static get FeedbackState(){return{WAITING:"WAITING",CONFIRMED:"CONFIRMED"}}static get UrlRegExp(){return/(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?/gi}}class $o{constructor(t){this.fetchFromJSON(t)}fetchFromJSON(t){this.channelUrl=t?t.channel_url:"",this.name=t?t.name:""}}const Jo="SENDBIRD_DESK_CHANNEL_CUSTOM_TYPE";let j={};const qo=(t,e)=>{var r=A.sendBird;r?((r=r.getInstance().GroupChannel.createMyGroupChannelListQuery()).customTypesFilter=[Jo],r.channelUrlsFilter=t,r.includeEmpty=!0,r.next(e)):A.callback(null,k.create(k.Type.ERROR_SENDBIRD_SDK_MISSING),e)},Vo=(e,a)=>{let i=F.Status.OPEN;var t=(i=F.isStatus(e.status)?e.status:i)!==F.Status.CLOSED?"status=ASSIGNED&status=UNASSIGNED":"status=CLOSED";let r=A.apiHost+"/tickets/"+`?${P.getParam}&limit=${e.limit||F.defaultLimit}&offset=`+(e.offset||0)+`&${t}&order=`+(e.order||"-updated_at");e&&"all"===e.status&&(r=A.apiHost+"/tickets/"+`?${P.getParam}&limit=${e.limit||F.defaultLimit}&offset=`+(e.offset||0)+"&order="+(e.order||"-updated_at")),e.channelUrl&&(r+="&channelUrl="+e.channelUrl),e.group&&(r+="&group="+e.group),e.customFieldFilter&&"object"==typeof e.customFieldFilter&&(r+="&customFields="+encodeURIComponent(Object.keys(e.customFieldFilter).map(t=>t+":"+encodeURIComponent(e.customFieldFilter[t])).join(","))),p.create({headers:P.header}).get(r).then(t=>{if(t&&t.data){const o=[];var e,r=[];for(e in t.data.results){var n=new F(t.data.results[e]);j[n.channelUrl]=n,r.push(n.channelUrl),o.push(n)}qo(r,(t,e)=>{t=A.getParamsFromCallback(t,e);const r=t.result;t.err||o.forEach(t=>{for(var e in r){e=r[e];if(t.channelUrl===e.url){t.channel=e;break}}}),w.write(`[REQ] ${i} ticket list:`,o),A.callback(o,t.err,a)})}else A.callback([],null,a)}).catch(t=>A.callback(null,t,a))};class F{constructor(t){this.fetchFromJSON(t)}fetchFromJSON(t){if(this.id=t?t.id:0,this.title=t?t.channelName:"",this.status=t?t.status:"",this.info=t&&t.info?JSON.parse(t.info):null,this.priority=t?t.priority:F.Priority.MEDIUM,this.agent=t&&t.recentAssignment&&t.recentAssignment.agent?new zo(t.recentAssignment.agent):null,this.customer=t?t.customer:null,this.customFields={},this.group=t?t.group:0,this.relatedChannels=[],this.channel=this.channel,this.channelUrl=t?t.channelUrl:null,this.updatedAt=(t?new Date(t.updatedAt):new Date).getTime(),t&&t.customFields){var e=t.customFields;for(let t=0;tnew $o(t)))}catch(t){}}refresh(r){var t=`${A.apiHost}/tickets/${this.id}/?`+P.getParam;p.create({headers:P.header}).get(t).then(t=>{this.fetchFromJSON(t.data),qo([this.channelUrl],(t,e)=>{t=A.getParamsFromCallback(t,e).result;0A.callback(null,t,r))}reopen(r){var t=`${A.apiHost}/tickets/${this.id}/reopen`,e=P.postParam;p.create({headers:P.header}).patch(t,e).then(t=>{t&&t.data&&t.data.channelUrl?(this.fetchFromJSON(t.data),qo([this.channelUrl],(t,e)=>{t=A.getParamsFromCallback(t,e).result;0A.callback(null,t,r))}cancel(t,r){var e,n;"function"==typeof t&&(r=t,t=null),t&&"string"!=typeof t?A.callback(null,k.create(k.Type.ERROR_INVALID_PARAMETER),r):(e=`${A.apiHost}/tickets/${this.id}/cancel`,n=P.postParam,t&&(n.groupKeyForTransfer=t),p.create({headers:P.header}).patch(e,n).then(t=>{t&&t.data&&t.data.channelUrl?(this.fetchFromJSON(t.data),qo([this.channelUrl],(t,e)=>{t=A.getParamsFromCallback(t,e).result;0A.callback(null,t,r)))}close(t,r){if(1===arguments.length){if("function"!=typeof t)return void A.callback(null,k.create(k.Type.ERROR_INVALID_PARAMETER),r);r=t,t=null}else if("string"!=typeof t||"function"!=typeof r)return void A.callback(null,k.create(k.Type.ERROR_INVALID_PARAMETER),r);var e=`${A.apiHost}/tickets/${this.id}/close`,n=P.postParam;t&&(n.closeComment=t),p.create({headers:P.header}).patch(e,n).then(t=>{t.data.channelUrl?(this.fetchFromJSON(t.data),qo([this.channelUrl],(t,e)=>{t=A.getParamsFromCallback(t,e).result;0A.callback(null,t,r))}selectQuestion(t,e,r){var n,o;"number"!=typeof t||"string"!=typeof e?A.callback(null,k.create(k.Type.ERROR_INVALID_PARAMETER),r):(n=`${A.apiHost}/tickets/${this.id}/select_question`,(o=P.postParam).faqFileId=t,o.question=e,p.create({headers:P.header}).post(n,o).then(t=>{w.write("[REQ] ticket select option:",this),A.callback(t.data,null,r)}).catch(t=>A.callback(null,t,r)))}setPriority(t,e){var r,n;Object.keys(F.Priority).map(t=>F.Priority[t]).indexOf(t)<0?A.callback(null,k.create(k.Type.ERROR_INVALID_PARAMETER),e):(r=`${A.apiHost}/tickets/${this.id}/`,(n=P.postParam).priority=t,p.create({headers:P.header}).patch(r,n).then(t=>{this.fetchFromJSON(t.data),A.callback(this,null,e)}).catch(t=>{A.callback(null,t,e)}))}setRelatedChannelUrls(t,e){var r,n;!Array.isArray(t)||t.some(t=>"string"!=typeof t)?A.callback(null,k.create(k.Type.ERROR_INVALID_PARAMETER),e):(r=`${A.apiHost}/tickets/${this.id}/`,(n=P.postParam).relatedChannelUrls=t.join(","),p.create({headers:P.header}).patch(r,n).then(t=>{this.fetchFromJSON(t.data),A.callback(this,null,e)}).catch(t=>{A.callback(null,t,e)}))}setCustomFields(t,e){if("object"!=typeof t||null===t||Array.isArray(t))A.callback(null,k.create(k.Type.ERROR_INVALID_PARAMETER),e);else{var r,n={};for(r in t)n[r]="string"==typeof t[r]?t[r]:JSON.stringify(t[r]);var o=`${A.apiHost}/tickets/${this.id}/custom_fields/`,a=P.postParam;a.customFields=JSON.stringify(n),p.create({headers:P.header}).patch(o,a).then(t=>{this.fetchFromJSON(t.data),A.callback(this,null,e)}).catch(t=>{A.callback(null,t,e)})}}static get Status(){return{INITIALIZED:"INITIALIZED",PROACTIVE:"PROACTIVE",UNASSIGNED:"UNASSIGNED",ASSIGNED:"ASSIGNED",OPEN:"OPEN",CLOSED:"CLOSED"}}static isStatus(t){for(var e in F.Status)if(F.Status[e]===t)return!0;return!1}static get Priority(){return{URGENT:"URGENT",HIGH:"HIGH",MEDIUM:"MEDIUM",LOW:"LOW"}}static isPriority(t){for(var e in F.Priority)if(F.Priority[e]===t)return!0;return!1}static isDeskCustomType(t){return t===Jo}static get defaultLimit(){return 10}static clearCache(t){t?(delete j[t],w.write(`[SYS] cached ticket for ${t} cleared`)):(j={},w.write("[SYS] all cached ticket cleared"))}static create(t,e,r,n,o,a,i,s){var c=A.sendBird;if(c)if("function"==typeof r&&(s=r,i=a=o=n=r=null),"function"==typeof n&&(s=n,i=a=o=n=null),"function"==typeof o&&(s=o,i=a=o=null),"function"==typeof a&&(s=a,i=a=null),"function"==typeof i&&(s=i,i=null),"string"!=typeof t||"string"!=typeof e||r&&"string"!=typeof r||n&&"object"!=typeof n||o&&!F.isPriority(o)||a&&(!Array.isArray(a)||a.some(t=>"string"!=typeof t))||Array.isArray(n)||i&&"string"!=typeof i)A.callback(null,k.create(k.Type.ERROR_INVALID_PARAMETER),s);else{var c=c.getInstance(),u=A.apiHost+"/tickets/",l=P.postParam;if(l.channelName=t,l.channelType="SENDBIRD_JAVASCRIPT",r&&(l.groupKey=r),n){var f,d={};for(f in n)d[f]="string"==typeof n[f]?n[f]:JSON.stringify(n[f]);l.customFields=JSON.stringify(d)}o&&(l.priority=o),a&&(l.relatedChannelUrls=a.join(",")),i&&(l.botKey=i),l.info=JSON.stringify({ticket:{subject:t,requester:{name:e,email:c.getCurrentUserId()}}}),p.create({headers:P.header}).post(u,l).then(t=>{const r=new F(t.data);qo([r.channelUrl],(t,e)=>{t=A.getParamsFromCallback(t,e).result;0A.callback(null,t,s))}else A.callback(null,k.create(k.Type.ERROR_SENDBIRD_SDK_MISSING),s)}static getOpenCount(e){var t=A.apiHost+"/tickets/count/?"+P.getParam;p.create({headers:P.header}).get(t).then(t=>A.callback(t.data,null,e)).catch(t=>A.callback(null,t,e))}static getByChannelUrl(n,t,e){let r=t,o=e;null==e&&(r=!1,o=t),j[n]&&r?A.callback(j[n],null,o):(e=`${A.apiHost}/tickets/?${P.getParam}&limit=10&channelUrl=`+n,p.create({headers:P.header}).get(e).then(t=>{let r=null;(r=t&&t.data&&0{t=A.getParamsFromCallback(t,e).result;0A.callback(null,t,o)))}static getList(t,e){var{offset:t,customFieldFilter:r,group:n,status:o="all"}=t;Vo({offset:t,group:n,customFieldFilter:r,status:o},e)}static getOpenedList(t,e,r){"function"==typeof e&&(r=e,e=null),Vo({offset:t,customFieldFilter:e,status:this.Status.OPEN},r)}static getAllTickets(t,e,r){"function"==typeof e&&(r=e,e=null),Vo({offset:t,customFieldFilter:e,status:"all"},r)}static getClosedList(t,e,r){"function"==typeof e&&(r=e,e=null),Vo({offset:t,customFieldFilter:e,status:this.Status.CLOSED},r)}static getUrlPreview(t,e){var r=A.apiHost+"/tickets/url_preview/",n=P.postParam;n.url=t,p.create({headers:P.header}).post(r,n).then(t=>A.callback(t.data,null,e)).catch(t=>A.callback(null,t,e))}static confirmEndOfChat(n,o,a){"string"==typeof o&&-1<["yes","no"].indexOf(o)?F.getByChannelUrl(n.channelUrl,(t,e)=>{const r=A.getParamsFromCallback(t,e).result;t=`${A.apiHost}/tickets/${r.id}/edit_message/`,e=P.postParam;e.messageId=n.messageId,e.messageData=JSON.stringify({type:Ho.DataType.TICKET_INQUIRE_CLOSURE,body:{state:"yes"===o?Ho.ClosureState.CONFIRMED:Ho.ClosureState.DECLINED,ticketId:r.id}}),p.create({headers:P.header}).patch(t,e).then(t=>{w.write("[REQ] confirmed end of chat:",r),A.callback(t.data,null,a)}).catch(t=>A.callback(null,t,a))}):A.callback(null,k.create(k.Type.ERROR_INVALID_PARAMETER))}static submitFeedback(n,o){let a=2{const r=A.getParamsFromCallback(t,e).result;t=`${A.apiHost}/tickets/${r.id}/edit_message/`,e=P.postParam;e.messageId=n.messageId,e.messageData=JSON.stringify({type:Ho.DataType.TICKET_FEEDBACK,body:{state:Ho.FeedbackState.CONFIRMED,customerSatisfactionScore:o,customerSatisfactionComment:a,ticketId:r.id}}),p.create({headers:P.header}).patch(t,e).then(t=>{w.write("[REQ] customer feedback:",r),A.callback(t.data,null,i)}).catch(t=>A.callback(null,t,i))}):A.callback(null,k.create(k.Type.ERROR_INVALID_PARAMETER))}}let Wo=!1;return class{constructor(){}static get version(){return A.sdkVersion}static get Error(){return k}static get Agent(){return zo}static get Ticket(){return F}static get Message(){return Ho}static get RelatedChannel(){return $o}static get UrlRegExp(){return Ho.UrlRegExp}static init(t){try{t||(window&&window.SendBird&&(t=window.SendBird),global&&global.SendBird&&(t=global.SendBird)),t&&t.version?(A.sendBird=t,A.isSendBirdSdkCompatible(t.version)?Wo=!0:k.throw(k.Type.ERROR_SENDBIRD_SDK_VERSION_NOT_SUPPORTED)):k.throw(k.Type.ERROR_SENDBIRD_SDK_MISSING)}catch(t){k.throw(k.Type.ERROR_SENDBIRD_SDK_MISSING)}}static authenticate(t,e,r){Wo?P.connect(t,e,r):k.throw(k.Type.ERROR_SENDBIRD_DESK_INIT_MISSING)}static isDeskChannel(t){return F.isDeskCustomType(t.customType)}static setApiHost(t){A.apiHost=t}static setDebugMode(){A.setDebugMode()}static setCustomerCustomFields(t,o){if("object"!=typeof t||null===t||Array.isArray(t))A.callback(null,k.create(k.Type.ERROR_INVALID_PARAMETER),o);else{var e,r={};for(e in t)r[e]="string"==typeof t[e]?t[e]:JSON.stringify(t[e]);var n=A.apiHost+"/customers/custom_fields/",a=P.postParam;a.customFields=JSON.stringify(r),p.create({headers:P.header}).patch(n,a).then(t=>{var e,r={};for(e in t.data.customFields){var n=t.data.customFields[e];r[n.key]=n.value}A.callback(r,null,o)}).catch(t=>{A.callback(null,t,o)})}}}}); \ No newline at end of file diff --git a/dist/debug/index.js b/dist/debug/index.js new file mode 100644 index 0000000..c9007e1 --- /dev/null +++ b/dist/debug/index.js @@ -0,0 +1,1893 @@ +/* Copyright(c) 2023 SendBird, Inc. +SendBird Desk JavaScript SDK v1.1.0 */ +import { DeviceOsPlatform, SendbirdProduct, SendbirdPlatform } from '@sendbird/chat'; + +var version = "1.1.0"; + +let _debug = false; +let _sendbird; +let _platform = DeviceOsPlatform.WEB; +let _deskApiHost; +const DESK_SDK_VERSION = version; +const MIN_SENDBIRD_VERSION = '4.9.6'; +/** + * @since 1.0.0 + * @ignore + */ +class Config { + /** + * @static + * @since 1.0.5 + * @ignore + * @desc Get Sendbird instance. + */ + static get sendbird() { + return _sendbird; + } + /** + * @static + * @since 1.0.5 + * @ignore + * @desc Set Sendbird instance. + */ + static set sendbird(sb) { + _sendbird = sb; + } + /** + * @static + * @since 1.1.0 + * @ignore + * @desc Get platform for setting in SendbirdInstance. + * @default DeviceOsPlatform.WEB + */ + static get platform() { + return _platform; + } + /** + * @static + * @since 1.1.0 + * @ignore + * @desc Set platform for setting in SendbirdInstance. + */ + static set platform(platform) { + _platform = platform; + } + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Check if SDK is in debug mode. + */ + static get isDebugMode() { + return _debug; + } + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Set SDK to debug mode which leads to change API endpoint and app ID into development server. + */ + static setDebugMode() { + _debug = true; + } + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Set SDK to production mode which leads to change API endpoint and app ID into production server. + */ + static unsetDebugMode() { + _debug = false; + } + /** + * @ignore + */ + static get apiHost() { + if (!_deskApiHost) { + const sb = _sendbird; + _deskApiHost = `https://desk-api-${sb.appId}.sendbird.com/sapi`; + } + return _deskApiHost; + } + /** + * @ignore + */ + static set apiHost(val) { + _deskApiHost = val; + } + /** + * @ignore + */ + static get sdkVersion() { + return DESK_SDK_VERSION; + } + /** + * @ignore + */ + static get minSendbirdVersion() { + return MIN_SENDBIRD_VERSION; + } + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Check if Sendbird SDK version meets the min supported version. + * @returns {boolean} - true if it's compatible. + */ + static isSendbirdSdkCompatible(version) { + const _minSupportedVersion = MIN_SENDBIRD_VERSION.split('.'); + const _targetVersion = version.split('.'); + if (_minSupportedVersion.length === _targetVersion.length) { + for (const i in _minSupportedVersion) { + if (parseInt(_minSupportedVersion[i]) < parseInt(_targetVersion[i])) { + return true; + } + else if (parseInt(_minSupportedVersion[i]) > parseInt(_targetVersion[i])) { + return false; + } + } + return true; + } + return false; + } +} + +/** + * @since 1.0.0 + * @desc Sendbird Desk specific errors. + * @property {IErrorType} ERROR_SENDBIRD_SDK_MISSING - 100 + * @property {IErrorType} ERROR_SENDBIRD_SDK_VERSION_NOT_SUPPORTED - 101 + * @property {IErrorType} ERROR_SENDBIRD_DESK_INIT_MISSING - 102 + * @property {IErrorType} ERROR_SENDBIRD_SDK_MESSAGE_LIST_FAILED - 200 + * @property {IErrorType} ERROR_INVALID_PARAMETER - 403 + * @property {IErrorType} ERROR_DATA_NOT_FOUND - 404 + * @property {IErrorType} ERROR_REQUEST - 500 + * @property {IErrorType} ERROR_REQUEST_CANCELED - 501 + */ +const ErrorType = { + ERROR_SENDBIRD_SDK_MISSING: { + code: 100, + message: 'Sendbird SDK is missing.', + }, + ERROR_SENDBIRD_SDK_VERSION_NOT_SUPPORTED: { + code: 101, + message: 'This Sendbird SDK version is not supported.', + }, + ERROR_SENDBIRD_DESK_INIT_MISSING: { + code: 102, + message: 'Sendbird Desk SDK is not initialized. It should be done before authentication.', + }, + ERROR_SENDBIRD_DESK_AUTH_FAILED: { + code: 103, + message: 'Sendbird Desk authentication failed.', + }, + ERROR_SENDBIRD_SDK_MESSAGE_LIST_FAILED: { + code: 200, + message: 'Cannot load messages in SDK client.', + }, + ERROR_INVALID_PARAMETER: { + code: 403, + message: 'Invalid parameter.', + }, + ERROR_DATA_NOT_FOUND: { + code: 404, + message: 'Data not found.', + }, + ERROR_REQUEST_TIMEOUT: { + code: 409, + message: 'Request timed-out.', + }, + ERROR_REQUEST: { + code: 500, + message: 'Request failed.', + }, + ERROR_REQUEST_CANCELED: { + code: 501, + message: 'Request canceled.', + }, +}; +/** + * An error class for Desk. + * @extends {Error} + * @since 1.0.0 + */ +class SendbirdDeskError extends Error { + code; + /** + * @since 1.0.0 + * @param {string} message - Error message. + * @param {number} code - Error code. + */ + constructor(message, code) { + super(message); + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, this.constructor); + } + else { + this.stack = new Error(message).stack; + } + this.name = 'SendbirdDeskError'; + this.code = code; + } + static get Type() { + return ErrorType; + } + /** + * @static + * @since 1.0.5 + * @ignore + * @desc Create an error. + */ + static create(type) { + return type ? new SendbirdDeskError(type.message, type.code) : new Error('SendbirdDeskError type missing.'); + } + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Throw an error. + */ + static throw(type) { + throw new SendbirdDeskError(type.message, type.code); + } +} + +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * @since 1.0.1 + * @ignore + */ +class Logger { + static write(...args) { + if (Config.isDebugMode) { + // eslint-disable-next-line no-console + console.log(...args); + } + } + static error(...args) { + if (Config.isDebugMode) { + // eslint-disable-next-line no-console + console.error(...args); + } + } +} + +const _privateData = {}; +/** + * @since 1.0.0 + * @ignore + */ +class Auth { + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Authenticate and connect to Desk server. + */ + static async connect(userId, accessToken) { + // this method is internal, so its okay to keep it async + const sb = Config.sendbird; + sb.addSendbirdExtensions([ + { + product: SendbirdProduct.DESK, + version: version, + platform: SendbirdPlatform.JS, + }, + ], { + platform: Config.platform, + }); + if (sb) { + const res = await fetch(`${Config.apiHost}/customers/auth/`, { + method: 'POST', + headers: { + sendbirdAccessToken: accessToken || '', + 'content-type': 'application/json', + }, + body: JSON.stringify({ + sendbirdAppId: sb.appId, + sendbirdId: userId, + }), + }); + const data = await res.json(); + _privateData.deskToken = data.token; + Logger.write('[REQ] connection established'); + } + else { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_SENDBIRD_SDK_MISSING); + } + } + /** + * @ignore + */ + static get header() { + if (_privateData.deskToken) { + return { + sendbirdDeskToken: _privateData.deskToken, + 'content-type': 'application/json', + 'user-agent': `desk-js@${version}`, + }; + } + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_SENDBIRD_DESK_INIT_MISSING); + } + /** + * @ignore + */ + static get getParam() { + const sb = Config.sendbird; + return sb ? `sendbirdAppId=${sb.appId}` : ''; + } + /** + * @ignore + */ + static get postParam() { + const sb = Config.sendbird; + return sb ? { sendbirdAppId: sb.appId } : {}; + } +} + +/** + * @module Agent + * @ignore + */ +/** + * @since 1.0.0 + */ +class Agent { + userId; + name; + profileUrl; + sendbirdId; + /** + * @since 1.0.0 + * @private + * @desc Create an agent. + */ + constructor(params) { + this.fetchFromJSON(params); + } + /** + * @since 1.0.0 + * @private + * @desc Parse JSON data and patch Agent object. + */ + fetchFromJSON(params) { + if ('id' in params) { + this.userId = params.id; + this.name = params.displayName ?? ''; + this.profileUrl = params.photoThumbnailUrl ?? ''; + this.sendbirdId = params.sendbirdId; + return; + } + // legacy + this.userId = params.user ?? 0; + this.name = params.displayName ?? ''; + this.profileUrl = params.photoThumbnailUrl ?? ''; + } +} + +/** + * @classdesc RelatedChannel + * @since 1.0.14 + */ +class RelatedChannel { + channelUrl; + name; + /** + * @since 1.0.14 + * @private + * @desc Create a related channel + */ + constructor(params) { + this.fetchFromJSON(params); + } + /** + * @since 1.0.14 + * @private + * @desc Parse JSON data and patch RelatedChannel object. + */ + fetchFromJSON(params) { + this.channelUrl = params.channel_url ?? ''; + this.name = params.name ?? ''; + } +} + +// --------- Misc Types --------- // +// Enums arent good match for runtime values. +// Would they work? Yeah.. but they would be annoying for customer +// to use. They just need to call create(..., 'URGENT', ...) instead of +// create(..., TicketPriority.URGENT, ...) +// to use. So we use const-enums instead. +const TicketPriorityMap = { + URGENT: 'URGENT', + HIGH: 'HIGH', + MEDIUM: 'MEDIUM', + LOW: 'LOW', +}; +const TicketStatusMap = { + INITIALIZED: 'INITIALIZED', + PROACTIVE: 'PROACTIVE', + UNASSIGNED: 'UNASSIGNED', + ASSIGNED: 'ASSIGNED', + OPEN: 'OPEN', + CLOSED: 'CLOSED', +}; + +function mapCreateTicketArgs(args) { + if (args?.length < 3 || args?.length > 8) { + Logger.error('[REQ] Close ticket should have at least 1 param.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (args.length === 3) { + const [title, name, cb] = args; + return { title, name, cb }; + } + if (args.length === 4) { + const [title, name, groupKey, cb] = args; + return { title, name, groupKey, cb }; + } + if (args.length === 5) { + const [title, name, groupKey, customFields, cb] = args; + return { title, name, groupKey, customFields, cb }; + } + if (args.length === 6) { + const [title, name, groupKey, customFields, priority, cb] = args; + return { title, name, groupKey, customFields, priority, cb }; + } + if (args.length === 7) { + const [title, name, groupKey, customFields, priority, relatedChannelUrls, cb] = args; + return { title, name, groupKey, customFields, priority, relatedChannelUrls, cb }; + } + if (args.length === 8) { + const [title, name, groupKey, customFields, priority, relatedChannelUrls, botKey, cb] = args; + return { title, name, groupKey, customFields, priority, relatedChannelUrls, botKey, cb }; + } + // TS dont know that we have checked for length + Logger.error('[REQ] Create ticket should have between 3 to 8 paramters.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); +} +function validateCreateTicketArgs(params) { + const { title, name, groupKey, customFields, relatedChannelUrls, botKey, priority } = params; + if (typeof title !== 'string') { + Logger.error('[REQ] Create ticket title should be a string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (typeof name !== 'string') { + Logger.error('[REQ] Create ticket name should be a string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (groupKey && typeof groupKey !== 'string') { + Logger.error('[REQ] Create ticket groupKey should be a string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (priority && TicketPriorityMap[priority] === undefined) { + Logger.error('[REQ] Create ticket priority should be a string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (customFields && typeof customFields !== 'object') { + Logger.error('[REQ] Create ticket customFields should be an object.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (Array.isArray(customFields)) { + Logger.error('[REQ] Create ticket customFields cannot be an array.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (relatedChannelUrls && (!Array.isArray(relatedChannelUrls) + || relatedChannelUrls.some((channelUrl) => typeof channelUrl !== 'string'))) { + Logger.error('[REQ] Create ticket relatedChannelUrls should be an array.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (botKey && typeof botKey !== 'string') { + Logger.error('[REQ] Create ticket botKey should be a string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (typeof params.cb !== 'function') { + Logger.error('[REQ] Create ticket callback should be a function.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +function noop() { } + +function mapCloseArgs(params) { + // more than 2 args + if (params.length > 2) { + Logger.error('[REQ] Close ticket should have only 2 params.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + // no args + let comment = ''; + let cb = noop; + // one arg + if (params.length === 1) { + if (typeof params[0] === 'string') { + comment = params[0]; + } + else if (typeof params[0] === 'function') { + cb = params[0]; + } + } + // both args + if (params.length === 2) { + comment = params[0]; + cb = params[1]; + } + return { + comment, + cb, + }; +} +function validateCloseArgs(params) { + const { comment, cb } = params; + if (typeof comment !== 'string') { + Logger.error('[REQ] first param must be string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (typeof cb !== 'function') { + Logger.error('[REQ] second param must be callback.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } +} + +/** + * @module Message + * @ignore + */ +/** + * @since 1.0.0 + */ +class Message { + /** + * @static + * @since 1.0.0 + * @desc message custom type. + * @property {string} RICH_MESSAGE - SENDBIRD_DESK_RICH_MESSAGE + * @property {string} ADMIN_MESSAGE - SENDBIRD_DESK_ADMIN_MESSAGE_CUSTOM_TYPE + */ + static get CustomType() { + return { + RICH_MESSAGE: 'SENDBIRD_DESK_RICH_MESSAGE', + ADMIN_MESSAGE: 'SENDBIRD_DESK_ADMIN_MESSAGE_CUSTOM_TYPE', + }; + } + /** + * @static + * @since 1.0.0 + * @desc message data type. + * @property {string} TICKET_INQUIRE_CLOSURE - SENDBIRD_DESK_INQUIRE_TICKET_CLOSURE + * @property {string} TICKET_ASSIGN - TICKET_ASSIGN + * @property {string} TICKET_TRANSFER - TICKET_TRANSFER + * @property {string} TICKET_CLOSE - TICKET_CLOSE + * @property {string} URL_PREVIEW - URL_PREVIEW + */ + static get DataType() { + return { + TICKET_INQUIRE_CLOSURE: 'SENDBIRD_DESK_INQUIRE_TICKET_CLOSURE', + TICKET_ASSIGN: 'TICKET_ASSIGN', + TICKET_TRANSFER: 'TICKET_TRANSFER', + TICKET_CLOSE: 'TICKET_CLOSE', + TICKET_FEEDBACK: 'SENDBIRD_DESK_CUSTOMER_SATISFACTION', + URL_PREVIEW: 'SENDBIRD_DESK_URL_PREVIEW', + }; + } + /** + * @static + * @since 1.0.0 + * @desc closure inquiry messsage state. + * @property {string} WAITING - WAITING + * @property {string} CONFIRMED - CONFIRMED + * @property {string} DECLINED - DECLINED + */ + static get ClosureState() { + return { + WAITING: 'WAITING', + CONFIRMED: 'CONFIRMED', + DECLINED: 'DECLINED', + }; + } + /** + * @module Message + * @ignore + */ + /** + * @static + * @since 1.0.8 + * @desc closure inquiry messsage state. + * @property {string} WAITING - WAITING + * @property {string} CONFIRMED - CONFIRMED + */ + static get FeedbackState() { + return { + WAITING: 'WAITING', + CONFIRMED: 'CONFIRMED', + }; + } + /** + * @ignore + */ + static get UrlRegExp() { + return /(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?/gi; + } +} + +function mapCancelArgs(params) { + if (params?.length < 1) { + Logger.error('Cancel ticket should have at least 1 param.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (params.length > 2) { + Logger.error('Cancel ticket should have only 2 params.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + let groupKeyForTransfer = ''; + let cb = noop; + if (params.length === 1 && typeof params[0] === 'function') { + cb = params[0]; + } + if (params.length === 1 && typeof params[0] === 'string') { + groupKeyForTransfer = params[0]; + } + if (params.length === 2) { + groupKeyForTransfer = params[0]; + cb = params[1]; + } + return { + groupKeyForTransfer, + cb, + }; +} +function validateCancelArgs(args) { + if (typeof args.groupKeyForTransfer !== 'string') { + Logger.error('First param must be string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (typeof args.cb !== 'function') { + Logger.error('Second param must be callback.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } +} + +function mapGetByChannelUrlArgs(params) { + if (params?.length < 2 || params?.length > 3) { + Logger.error('[REQ] Get ticket should have 2 or 3 paramters.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + // order of params is important + const channelUrl = params[0]; + const cachingEnabled = params[1]; + // last param is callback + const cb = params[params.length - 1] || noop; + // @ts-expect-error This will be validated in validateGetByChannelUrlArgs + return { channelUrl, cachingEnabled, cb }; +} + +const TICKET_CUSTOM_TYPE = 'SENDBIRD_DESK_CHANNEL_CUSTOM_TYPE'; +const DEFAULT_LIMIT = 10; + +const _getByChannelUrls = async (channelUrls) => { + const sb = Config.sendbird; + if (sb) { + const query = sb.groupChannel.createMyGroupChannelListQuery({ + customTypesFilter: [TICKET_CUSTOM_TYPE], + channelUrlsFilter: channelUrls, + includeEmpty: true, + }); + try { + return await query.next(); + } + catch (error) { + Logger.error('[REQ] ticket get by channel urls failed:', error); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + } + } + else { + Logger.error('[REQ] ticket get by channel urls failed: chat SDK is not initialized.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_SENDBIRD_SDK_MISSING); + } +}; + +function generatePath(apiHost, authParam, params) { + const { offset = 0, limit = DEFAULT_LIMIT, order = '-updated_at', channelUrl, customFieldFilter, group, status = 'ALL', } = params; + const statusQuery = status !== TicketStatusMap.CLOSED ? 'status=ASSIGNED&status=UNASSIGNED' : 'status=CLOSED'; + let path = `${apiHost}/tickets/` + + `?${authParam}&limit=${limit}&offset=${offset}` + + `${status === 'ALL' ? '' : `&${statusQuery}`}&order=${order}`; + if (channelUrl) { + path += `&channelUrl=${channelUrl}`; + } + if (group) { + path += `&group=${group}`; + } + if (customFieldFilter && typeof customFieldFilter === 'object') { + path += + '&customFields=' + + encodeURIComponent(Object.keys(customFieldFilter) + .map((key) => `${key}:${encodeURIComponent(customFieldFilter[key])}`) + .join(',')); + } + return path; +} + +function mapGetTicketArgs(args) { + if (args?.length < 2 || args?.length > 3) { + throw new Error('Get ticket should have 2 or 3 paramters.'); + } + let offset = 0; + let filter = {}; + let cb = noop; + if (args.length === 2) { + offset = args[0]; + cb = args[1]; + } + if (args.length === 3) { + offset = args[0]; + filter = args[1]; + cb = args[2]; + } + return { offset, filter, cb }; +} +function validateGetTicketArgs(arg) { + const { offset, filter, cb } = arg; + if (typeof offset !== 'number') { + throw new Error('Get ticket offset should be a number.'); + } + if (typeof cb !== 'function') { + throw new Error('Get ticket callback should be a function.'); + } + if (filter && typeof filter !== 'object') { + throw new Error('Get ticket filter should be an object.'); + } +} + +// bad scoping practice - rewrite this in v2 +let _cachedTicket = {}; +// Dear future me(July 2023), +// If you go through this code, you will see that the Ticket class is a mess. +// It's a mess because it's trying to do too many things. +// It has static methods to create tickets, and instance methods to update tickets. +// Ideally they should be separated in 2 places +// We should make a builder or something to create tickets +// and then we should have a Ticket instance to update/close/cancel tickets +// Also, I added some things that you might have found weird +// For example - why is there async _create and create methods? +// Desk(1.0.0) was written in 2017(with chatSDK 2 or 3) when cbs were the norm, and async/await was not a thing +// In 2023 management decided to update the chat SDK to chat 4. Now its a mess of cbs and async/await +// Sadly we were not allowed to make breaking changes, so we had to keep the old cb methods in Desk +// These _async methods are a workaround to keep the old cb methods and add async/await support +// Also, I have implemented in a way that - every difficult method is -> +// pub method(args_list) { +// args_map = argListToArgMap() -> validate() +// private async _method(args_map){ +// [ args_map -> fetch() ] -> output +// } +// } +// later if we can refactor - +// * you can remove the cb-based public methods +// * you can change the private async _method -> pub async method +// * you can remove the argListToArgMap() -> and just use the args directly +// --- Ticket class !important--- +// !Important: If you implement a public method here, please add it to type: `TicketClass`. +// --- Ticket class !important--- +/** + * @since 1.0.0 + */ +class Ticket { + id; + title; + status; + info; + priority; + agent; + customer; + customFields; + group; + relatedChannels; + channel; + channelUrl; + updatedAt; + /** + * @since 1.0.0 + * @private + * @desc Create a ticket. + */ + constructor(params) { + this.fetchFromJSON(params); + } + /** + * @static + * @since 1.0.0 + * @desc Ticket status + * @property {string} INITIALIZED - ticket is created but not able to assign. + * @property {string} PROACTIVE - ticket is introduced as proactive ticket. + * @property {string} UNASSIGNED - ticket is activated and able to assign. + * @property {string} ASSIGNED - ticket is assigned by an agent. + * @property {string} OPEN - ticket is activated. + * @property {string} CLOSED - ticket is closed. + */ + static get Status() { + return TicketStatusMap; + } + /** + * @since 1.0.0 + * @private + * @desc Parse JSON data and patch Ticket object. + */ + fetchFromJSON(params) { + this.id = params.id; + this.title = params.channelName ?? ''; + this.status = params.status ?? TicketStatusMap.UNASSIGNED; + this.info = params.info ? JSON.parse(params.info) : null; + this.priority = params.priority ?? TicketPriorityMap.MEDIUM; + this.agent = params.recentAssignment?.agent ? new Agent(params.recentAssignment.agent) : null; + this.customer = params.customer ?? null; + this.customFields = {}; + this.group = params.group ?? 0; + this.relatedChannels = []; + this.channelUrl = params.channelUrl ?? null; + this.updatedAt = params.updatedAt ?? 0; + if (params.customFields) { + const customFields = params.customFields; + for (let i = 0; i < customFields.length; i++) { + const key = customFields[i].key; + const value = customFields[i].value; + this.customFields[key] = value; + } + } + if (params.relatedChannels) { + try { + const relatedChannelsPayload = JSON.parse(params.relatedChannels); + if (Array.isArray(relatedChannelsPayload)) { + this.relatedChannels = relatedChannelsPayload.map((item) => new RelatedChannel(item)); + } + } + catch (e) { + // DO NOTHING + } + } + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + async _fetchChannel() { + const [channel] = await _getByChannelUrls([this.channelUrl]); + this.channel = channel; + _cachedTicket[this.channelUrl] = this; + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + static async _getTicketList(params) { + const path = generatePath(Config.apiHost, Auth.getParam, params); + const res = await fetch(path, { + method: 'GET', + headers: Auth.header, + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] ticket list failed:', data); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + } + const tickets = []; + const channelUrls = []; + if (Array.isArray(data.results)) { + for (const i in data.results) { + const ticket = new Ticket(data.results[i]); + _cachedTicket[ticket.channelUrl] = ticket; + channelUrls.push(ticket.channelUrl); + tickets.push(ticket); + } + const channels = await _getByChannelUrls(channelUrls); + tickets.forEach((ticket) => { + for (const i in channels) { + const channel = channels[i]; + if (ticket.channelUrl === channel.url) { + ticket.channel = channel; + break; + } + } + }); + Logger.write(`[REQ] ${status} ticket list:`, tickets); + } + return tickets; + } + /** + * @since 1.0.0 + * @desc Refresh ticket info. + * @param {function(ticket:Ticket, err:Error)} cb - cb function. + */ + refresh(cb) { + this._refresh() + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + async _refresh() { + const res = await fetch(`${Config.apiHost}/tickets/${this.id}/?${Auth.getParam}`, { + method: 'GET', + headers: Auth.header, + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] ticket refresh failed:', res); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + this.fetchFromJSON(data); + await this._fetchChannel(); + Logger.write('[REQ] ticket refresh:', this); + return this; + } + /** + * @since 1.0.6 + * @desc Reopen closed ticket. + * @param {function} cb - Function(res:Ticket, err:Error). + */ + reopen(cb) { + this._reopen() + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + async _reopen() { + try { + const res = await fetch(`${Config.apiHost}/tickets/${this.id}/reopen/`, { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify(Auth.postParam), + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] ticket reopen failed:', data); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + if (data.channelUrl) { + this.fetchFromJSON(data); + await this._fetchChannel(); + Logger.write('[REQ] ticket reopen:', this); + } + return this; + } + catch (e) { + Logger.error('[REQ] ticket reopen failed:', e); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + } + /** + * @since 1.0.18 + * @desc Cancel the assignment and set it to open. + * @param {string} groupKeyForTransfer - Group key for transfer(optional) + * @param {function} cb - Function(res:Ticket, err:Error). + */ + cancel(...params) { + const args = mapCancelArgs(params); + const { cb } = args; + this._cancel(args) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + async _cancel(args) { + validateCancelArgs(args); + const { groupKeyForTransfer } = args; + const requestBody = { + ...Auth.postParam, + }; + if (groupKeyForTransfer) { + requestBody['groupKeyForTransfer'] = groupKeyForTransfer; + } + const res = await fetch(`${Config.apiHost}/tickets/${this.id}/cancel`, { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify(requestBody), + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] ticket cancel failed:', data); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + if (data.channelUrl) { + this.fetchFromJSON(data); + await this._fetchChannel(); + Logger.write('[REQ] ticket cancel:', this); + } + return this; + } + /** + * @since 1.0.16 + * @desc Force close an assigned ticket. + * @param {string} comment - Comment for closing the ticket. + */ + close(...params) { + const args = mapCloseArgs(params); + const { cb } = args; + this._close(args) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + async _close(args) { + validateCloseArgs(args); + const { comment } = args; + const res = await fetch(`${Config.apiHost}/tickets/${this.id}/close`, { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify({ + ...Auth.postParam, + closeComment: comment, + }), + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] ticket close failed:', data); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + if (data.channelUrl) { + this.fetchFromJSON(data); + await this._fetchChannel(); + Logger.write('[REQ] ticket close:', this); + } + return this; + } + /** + * @since 1.0.18 + * @desc Select a question. + * @param {number} faqFileId - FAQ file ID. + * @param {string} question - Question text. + * @param {function} callback - Function(res:object, err:Error). + */ + selectQuestion(faqFileId, question, cb) { + this._selectQuestion(faqFileId, question) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + async _selectQuestion(faqFileId, question) { + if (typeof faqFileId !== 'number' || typeof question !== 'string') { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + const res = await fetch(`${Config.apiHost}/tickets/${this.id}/select_question`, { + method: 'POST', + headers: Auth.header, + body: JSON.stringify({ + ...Auth.postParam, + faqFileId, + question, + }), + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] ticket select question failed:', data); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + Logger.write('[REQ] ticket select option:', this); + return this; + } + /** + * @since 1.0.10 + * @desc Set ticket priority. + * @param {string} priority - priority. + * @param {function} callback - Function(res:Ticket, err:Error). + */ + setPriority(priority, cb) { + this._setPriority(priority) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + async _setPriority(priority) { + if (Object.keys(TicketPriorityMap) + .map((key) => TicketPriorityMap[key]) + .indexOf(priority) < 0) { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + const res = await fetch(`${Config.apiHost}/tickets/${this.id}`, { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify({ + ...Auth.postParam, + priority, + }), + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] ticket set priority failed:', data); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + this.fetchFromJSON(data); + Logger.write('[REQ] ticket set priority:', this); + return this; + } + /** + * @since 1.0.14 + * @desc Set ticket related channel URLs. + * @param {array} relatedChannelUrls - related channel URLs. + * @param {function} callback - Function(res:Ticket, err:Error). + */ + setRelatedChannelUrls(relatedChannelUrls, cb) { + this._setRelatedChannelUrls(relatedChannelUrls) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + async _setRelatedChannelUrls(relatedChannelUrls) { + if (!Array.isArray(relatedChannelUrls) || relatedChannelUrls.some((channelUrl) => typeof channelUrl !== 'string')) { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + const res = await fetch(`${Config.apiHost}/tickets/${this.id}`, { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify({ + ...Auth.postParam, + relatedChannelUrls: relatedChannelUrls.join(','), + }), + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] ticket set related channel URLs failed:', data); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + this.fetchFromJSON(data); + Logger.write('[REQ] ticket set related channel URLs:', this); + return this; + } + /** + * @since 1.0.10 + * @desc Set ticket customFields. + * @param {object} customFields - customFields object (key-value). + * @param {function} callback - Function(res:Ticket, err:Error). + */ + setCustomFields(customFields, cb) { + this._setCustomFields(customFields) + .then((data) => { + cb(data, null); + }) + .catch((err) => { + cb(null, err); + }); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + async _setCustomFields(customFields) { + if (typeof customFields !== 'object' || customFields === null || Array.isArray(customFields)) { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + const formattedCustomFields = {}; + for (const key in customFields) { + formattedCustomFields[key] = + typeof customFields[key] === 'string' ? customFields[key] : JSON.stringify(customFields[key]); + } + const res = await fetch(`${Config.apiHost}/tickets/${this.id}/custom_fields/`, { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify({ + ...Auth.postParam, + customFields: JSON.stringify(formattedCustomFields), + }), + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] ticket set custom fields failed:', data); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + this.fetchFromJSON(data); + Logger.write('[REQ] ticket set custom fields:', this); + return this; + } + /** + * @ignore + */ + static isDeskCustomType(customType) { + return customType === TICKET_CUSTOM_TYPE; + } + /** + * @ignore + */ + static get defaultLimit() { + // ^^ This method probably exist because of some old OOP dogma + return DEFAULT_LIMIT; + } + /** + * @static + * @since 1.0.0 + * @desc Clear cached ticket. Clear all if channelUrl is not specified. + */ + static clearCache(channelUrl) { + if (channelUrl) { + delete _cachedTicket[channelUrl]; + Logger.write(`[SYS] cached ticket for ${channelUrl} cleared`); + } + else { + _cachedTicket = {}; + Logger.write('[SYS] all cached ticket cleared'); + } + } + /** + * @static + * @since 1.0.0 + * @desc Create new ticket and returns the ticket within cb. + * @param {string} title - Ticket title. + * @param {string} name - User name. + * @param {string} groupKey - Agent group key (optional). + * @param {object} customFields - customField (optional). + * @param {string} priority - priority (optional). + * @param {array} relatedChannelUrls - related channel URLs (optional). + * @param {string} botKey - botKey (optional). + * @param {function} cb - Function(ticket:Ticket, err:Error). + */ + static create(...params) { + const args = mapCreateTicketArgs(params); + const { cb } = args; + this._create(args) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + static async _create(args) { + const { title, name, groupKey, customFields, priority, relatedChannelUrls, botKey } = args; + const sb = Config.sendbird; + if (!sb) { + Logger.error('[REQ] ticket create chat SDK is not initalized.'); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_SENDBIRD_SDK_MISSING); + throw err; + } + validateCreateTicketArgs(args); + const formattedCustomFields = {}; + for (const key in customFields) { + formattedCustomFields[key] = + typeof customFields[key] === 'string' ? customFields[key] : JSON.stringify(customFields[key]); + } + // todo: move the fetch-body body to a separate function + // and add tests + const fetchBody = { + ...Auth.postParam, + channelName: title, + channelType: 'SENDBIRD_JAVASCRIPT', + groupKey, + customFields: customFields ? JSON.stringify(formattedCustomFields) : undefined, + relatedChannelUrls: relatedChannelUrls?.join(','), + botKey, + info: JSON.stringify({ + // FIXME: Zendesk version only + ticket: { + subject: title, + requester: { + name, + email: sb?.currentUser?.userId || '', + }, + }, + }), + }; + if (priority) { + fetchBody['priority'] = priority; + } + const res = await fetch(`${Config.apiHost}/tickets/`, { + method: 'POST', + headers: Auth.header, + body: JSON.stringify(fetchBody), + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] ticket create failed:', data); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + const ticket = new Ticket(data); + const [channel] = await _getByChannelUrls([ticket.channelUrl]); + ticket.channel = channel; + _cachedTicket[channel.url] = ticket; + Logger.write('[REQ] ticket create success:', ticket); + return ticket; + } + /** + * @static + * @since 1.0.0 + * @desc Get ticket count for each state: UNASSIGNED, ASSIGNED, CLOSED. + * @param {function} callback - Function(result:GetOpenCountResponse, err:Error). + */ + static getOpenCount(cb) { + this._getOpenCount() + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + static async _getOpenCount() { + const res = await fetch(`${Config.apiHost}/tickets/count/?${Auth.getParam}`, { + method: 'GET', + headers: Auth.header, + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] get open ticket count failed:', data); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + } + Logger.write('[REQ] get open ticket count:', data); + return data; + } + /** + * @static + * @since 1.0.22 + * @desc Get ticket from channel URL. Use caching for optimization. + * @param {string} channelUrl - channel URL. + * @param {boolean} _cachingEnabled - to get ticket from cache or not. + * @param {function} _callback - Function(ticket:Ticket, err:Error). + */ + /** + * @static + * @since 1.0.22 + * @desc Get ticket from channel URL. no caching by default. + * @param {string} channelUrl - channel URL. + * @param {function} _callback - Function(ticket:Ticket, err:Error). + */ + static getByChannelUrl(...params) { + const args = mapGetByChannelUrlArgs(params); + const { cb } = args; + this._getByChannelUrl(args) + .then((data) => cb?.(data, null)) + .catch((err) => cb?.(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + static async _getByChannelUrl(args) { + const { channelUrl, cachingEnabled } = args; + // cachingEnabled is a workaround to use cache + if (!!_cachedTicket[channelUrl] && cachingEnabled) { + Logger.write(`[REQ] get ticket for channel ${channelUrl} from cache:`, _cachedTicket[channelUrl]); + return _cachedTicket[channelUrl]; + } + else { + let ticket; + const res = await fetch(`${Config.apiHost}/tickets/?${Auth.getParam}&limit=10&channelUrl=${channelUrl}`, { + method: 'GET', + headers: Auth.header, + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] get ticket for channel failed:', data); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + } + if (Array.isArray(data.results) && data.results.length > 0) { + ticket = new Ticket(data.results[0]); + const [channel] = await _getByChannelUrls([ticket.channelUrl]); + ticket.channel = channel; + _cachedTicket[channel.url] = ticket; + Logger.write(`[REQ] get ticket for channel ${channelUrl}:`, ticket); + return ticket; + } + else { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_DATA_NOT_FOUND); + } + } + } + /** + * @static + * @since 1.0.23 + * @desc Lists all tickets. + * @param {integer} filters.offset - list offset. + * @param {object} filters.customFieldFilter - customField filter. + * @param {string} filters.group - group key(to filter tickets by a team). + * @param {string} filters.status - status to get tickets. ('all', 'CLOSED', 'OPEN'). + * @param {function} callback - Function(list:Array, err:Error) + */ + static getList(params, cb) { + // note to developers - Always recommend this method to customer + // and getrid of getOpenedList and getClosedList etc etc + Ticket._getTicketList(params) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @static + * @since 1.0.0 + * @desc Load opened ticket list. + * @param {integer} offset - list offset. + * @param {object} customFieldFilter - customField filter. + * @param {function} callback - Function(list:Array, err:Error) + */ + static getOpenedList(...params) { + const { offset, filter, cb } = mapGetTicketArgs(params); + validateGetTicketArgs({ offset, filter, cb }); + Ticket._getTicketList({ + ...filter, + offset: offset, + status: 'OPEN', + }) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @static + * @since 1.0.21 + * @desc Lists all tickets. + * @param {integer} offset - list offset. + * @param {object} customFieldFilter - customField filter. + * @param {function} callback - Function(list:Array, err:Error) + */ + static getAllTickets(...params) { + const { offset, filter, cb } = mapGetTicketArgs(params); + validateGetTicketArgs({ offset, filter, cb }); + Ticket._getTicketList({ + ...filter, + offset: offset, + status: 'ALL', + }) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @static + * @since 1.0.0 + * @desc Load closed ticket list. + * @param {integer} offset - list offset. + * @param {object} customFieldFilter - customField filter. + * @param {function} callback - Function(list:Array, err:Error) + */ + static getClosedList(...params) { + const { offset, filter, cb } = mapGetTicketArgs(params); + validateGetTicketArgs({ offset, filter, cb }); + Ticket._getTicketList({ + ...filter, + offset: offset, + status: 'CLOSED', + }) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @static + * @since 1.0.0 + * @desc Get URL preview info from URL. + * @param {string} url - URL to load preview metadata. + * @param {function} callback - Function(result:Object, err:Error). + */ + static getUrlPreview(url, cb) { + this._getUrlPreview(url) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + static async _getUrlPreview(url) { + if (typeof url !== 'string') { + Logger.error('[REQ] get url preview failed: url should be a string'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + const res = await fetch(`${Config.apiHost}/tickets/url_preview/`, { + method: 'POST', + headers: Auth.header, + body: JSON.stringify({ + ...Auth.postParam, + url, + }), + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] get url preview failed:', data); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + } + Logger.write('[REQ] get url preview:', data); + return data; + } + /** + * @since 1.0.0 + * @desc Reply to confirm-end-of-chat request in yes or no. + */ + static confirmEndOfChat(message, confirm, cb) { + this._confirmEndOfChat(message, confirm) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @since 1.0.0 + * @desc Reply to confirm-end-of-chat request in yes or no. + * This shouldnt be static, but it is for backwards compatibility + */ + instanceConfirmEndOfChat(message, confirm, cb) { + Ticket._confirmEndOfChat(message, confirm) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + static async _confirmEndOfChat(message, confirm) { + // FIXME: specify return type + if (!(typeof confirm === 'string' && ['yes', 'no'].indexOf(confirm) > -1)) { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + const ticket = await Ticket._getByChannelUrl({ + channelUrl: message.channelUrl, + cachingEnabled: true, + }); + const res = await fetch(`${Config.apiHost}/tickets/${ticket.id}/edit_message/`, { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify({ + ...Auth.postParam, + messageId: message.messageId, + messageData: JSON.stringify({ + type: Message.DataType.TICKET_INQUIRE_CLOSURE, + body: { + state: confirm === 'yes' ? Message.ClosureState.CONFIRMED : Message.ClosureState.DECLINED, + ticketId: ticket.id, + }, + }), + }), + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] confirm end of chat failed:', data); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + Logger.write('[REQ] confirmed end of chat:', ticket); + return ticket; + } + /** + * @since 1.0.8 + * @desc Submit feedback with a score and a comment. + */ + static submitFeedback(message, score, comment = '', cb) { + this._submitFeedback(message, score, comment) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @since 1.0.8 + * @desc Submit feedback with a score and a comment. + */ + instanceSubmitFeedback(message, score, comment = '', cb) { + Ticket._submitFeedback(message, score, comment) + .then((data) => cb(data, null)) + .catch((err) => cb(null, err)); + } + /** + * @ignore + * @private + * @since 1.1.0 + * This shouldnt be static, but it is for backwards compatibility + */ + static async _submitFeedback(message, score, comment = '') { + // FIXME: specify return type + if (!(message && + message.messageId > 0 && + message.channelUrl && + typeof score === 'number' && + 1 <= score && + score <= 5 && + typeof comment === 'string')) { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + const ticket = await Ticket._getByChannelUrl({ + channelUrl: message.channelUrl, + cachingEnabled: true, + }); + const res = await fetch(`${Config.apiHost}/tickets/${ticket.id}/edit_message/`, { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify({ + ...Auth.postParam, + messageId: message.messageId, + messageData: JSON.stringify({ + type: Message.DataType.TICKET_FEEDBACK, + body: { + state: Message.FeedbackState.CONFIRMED, + customerSatisfactionScore: score, + customerSatisfactionComment: comment, + ticketId: ticket.id, + }, + }), + }), + }); + const data = await res.json(); + if (!res.ok) { + Logger.error('[REQ] submit feedback failed:', data); + const err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + Logger.write('[REQ] customer feedback:', ticket); + return data; + } +} + +function parseAuthArgs(params) { + let userId = ''; + let accessToken = ''; + let cb = noop; + if (!Array.isArray(params)) { + Logger.write('Arguments passed to SendbirdDesk.authenticate() must be an array'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (params.length < 1) { + Logger.write('Too few arguments passed to SendbirdDesk.authenticate()'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (params.length > 3) { + Logger.write('Too many arguments passed to SendbirdDesk.authenticate()'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + userId = params[0]; + // connect(userId: string, token: string) + if (typeof params[1] === 'string') { + accessToken = params[1]; + } + // connect(userId: string, cb: () => void) + if (typeof params[1] === 'function') { + cb = params[1]; + } + // connect(userId: string, token: string, cb: () => void) + if (typeof params[2] === 'function') { + cb = params[2]; + } + return { + userId, + accessToken, + cb, + }; +} +function validateAuthArgs(params) { + const { userId, accessToken, cb } = params; + if (typeof userId !== 'string') { + Logger.write('UserId to SendbirdDesk.authenticate(userId) must be a string'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (typeof accessToken !== 'string') { + Logger.write('AccessToken to SendbirdDesk.authenticate(userId, accessToken) must be a string'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (typeof cb !== 'function') { + Logger.write('Callback to SendbirdDesk.authenticate(userId, accessToken, cb) must be a function'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } +} + +let _initialized = false; +/** SendbirdDesk SDK + */ +class SendbirdDesk { + /** + * @static + * @since 1.0.0 + * @desc Get Desk SDK version. + */ + static get version() { + return Config.sdkVersion; + } + /** + * @static + * @since 1.0.0 + * @desc SendBirdDeskError class reference. + * @type {module:SendBirdDeskError} + */ + static get Error() { + return SendbirdDeskError; + } + /** + * @static + * @since 1.0.0 + * @desc Agent class reference. + * @type {module:Agent} + */ + static get Agent() { + return Agent; + } + /** + * @static + * @since 1.0.0 + * @desc Ticket class reference. + * @type {module:Ticket} + */ + static get Ticket() { + return Ticket; + } + /** + * @static + * @since 1.0.0 + * @desc Message class reference. + * @type {module:Message} - BaseMessage in Sendbird Messaging SDK + */ + static get Message() { + return Message; + } + /** + * @static + * @since 1.0.14 + * @desc RelatedChannel class reference. + * @type {module:RelatedChannel} + */ + static get RelatedChannel() { + return RelatedChannel; + } + /** + * @ignore + */ + static get UrlRegExp() { + return Message.UrlRegExp; + } + /** + * @static + * @since 1.0.1 + * @desc Initialize SDK. + */ + static init(sendbird, platform) { + Config.sendbird = sendbird; + if (platform) { + Config.platform = platform; + } + _initialized = true; + } + /** + * @static + * @since 1.0.0 + * @desc Authenticate and connect to Desk server. + * @param {string} userId - User ID. + * @param {string=} accessToken - Access token(Optional). + * @param {function} callback - Function() => void. + */ + static authenticate(...params) { + const { userId, accessToken, cb } = parseAuthArgs(params); + validateAuthArgs({ userId, accessToken, cb }); + this._authenticate({ userId, accessToken, cb }).finally(() => { + cb(); + }); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + static async _authenticate(params) { + const { userId, accessToken } = params; + if (_initialized) { + try { + await Auth.connect(userId, accessToken); + } + catch (err) { + SendbirdDeskError.throw(SendbirdDeskError.Type.ERROR_SENDBIRD_DESK_AUTH_FAILED); + } + } + else { + SendbirdDeskError.throw(SendbirdDeskError.Type.ERROR_SENDBIRD_DESK_INIT_MISSING); + } + } + /** + * @static + * @since 1.0.1 + * @desc Check if the channel belongs to Desk. + */ + static isDeskChannel(channel) { + return Ticket.isDeskCustomType(channel.customType); + } + /** + * @ignore + */ + static setApiHost(host) { + Config.apiHost = host; + } + /** + * @static + * @since 1.0.0 + * @desc Set SDK to debug mode which adds internal logging on desk event. + */ + static setDebugMode() { + Config.setDebugMode(); + } + /** + * @static + * @since 1.0.8 + * @desc Set customer customFields(Must be defined in dashboard). + * @param {object} customFields - customFields object (key-value). + * @param {function} callback - Function(res: object, err: Error). + */ + static setCustomerCustomFields(customFields, cb) { + this._setCustomerCustomFields(customFields).then((res) => { + cb(res, null); + }).catch((err) => { + cb(null, err); + }); + } + /** + * @ignore + * @private + * @since 1.1.0 + */ + static async _setCustomerCustomFields(customFields) { + if (typeof customFields !== 'object' || customFields === null || Array.isArray(customFields)) { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + const formattedCustomFields = {}; + for (const key in customFields) { + // all this conversion is to make sure that ts doesnt whine about the type + // for somereason formattedCustomFields[key] = customFields[key] is not allowed + const val = customFields[key]; + if (typeof val === 'string') { + formattedCustomFields[key] = val; + } + else { + formattedCustomFields[key] = JSON.stringify(val); + } + } + const res = await fetch(`${Config.apiHost}/customers/custom_fields/`, { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify({ + ...Auth.postParam, + customFields: JSON.stringify(formattedCustomFields), + }), + }); + const data = await res.json(); + // handle errors + if (!res.ok) { + throw new Error(JSON.stringify(data)); + } + // handle success + return this._resToCustomFields(data.customFields); + } + static _resToCustomFields(data) { + const customFields = {}; + for (const item of data) { + customFields[item.key] = item.value; + } + return customFields; + } +} + +export { Agent, RelatedChannel, SendbirdDeskError, Ticket, SendbirdDesk as default }; diff --git a/dist/esm/index.js b/dist/esm/index.js new file mode 100644 index 0000000..d64ee96 --- /dev/null +++ b/dist/esm/index.js @@ -0,0 +1,2358 @@ +/* Copyright(c) 2023 SendBird, Inc. +SendBird Desk JavaScript SDK v1.1.0 */ +import { DeviceOsPlatform, SendbirdProduct, SendbirdPlatform } from '@sendbird/chat'; + +/****************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +/* global Reflect, Promise, SuppressedError, Symbol */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +function __values(o) { + var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; + if (m) return m.call(o); + if (o && typeof o.length === "number") return { + next: function () { + if (o && i >= o.length) o = void 0; + return { value: o && o[i++], done: !o }; + } + }; + throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); +} + +function __read(o, n) { + var m = typeof Symbol === "function" && o[Symbol.iterator]; + if (!m) return o; + var i = m.call(o), r, ar = [], e; + try { + while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); + } + catch (error) { e = { error: error }; } + finally { + try { + if (r && !r.done && (m = i["return"])) m.call(i); + } + finally { if (e) throw e.error; } + } + return ar; +} + +function __spreadArray(to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +} + +typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; +}; + +var version = "1.1.0"; + +var _debug = false; +var _sendbird; +var _platform = DeviceOsPlatform.WEB; +var _deskApiHost; +var DESK_SDK_VERSION = version; +var MIN_SENDBIRD_VERSION = '4.9.6'; +/** + * @since 1.0.0 + * @ignore + */ +var Config = /** @class */ (function () { + function Config() { + } + Object.defineProperty(Config, "sendbird", { + /** + * @static + * @since 1.0.5 + * @ignore + * @desc Get Sendbird instance. + */ + get: function () { + return _sendbird; + }, + /** + * @static + * @since 1.0.5 + * @ignore + * @desc Set Sendbird instance. + */ + set: function (sb) { + _sendbird = sb; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Config, "platform", { + /** + * @static + * @since 1.1.0 + * @ignore + * @desc Get platform for setting in SendbirdInstance. + * @default DeviceOsPlatform.WEB + */ + get: function () { + return _platform; + }, + /** + * @static + * @since 1.1.0 + * @ignore + * @desc Set platform for setting in SendbirdInstance. + */ + set: function (platform) { + _platform = platform; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Config, "isDebugMode", { + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Check if SDK is in debug mode. + */ + get: function () { + return _debug; + }, + enumerable: false, + configurable: true + }); + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Set SDK to debug mode which leads to change API endpoint and app ID into development server. + */ + Config.setDebugMode = function () { + _debug = true; + }; + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Set SDK to production mode which leads to change API endpoint and app ID into production server. + */ + Config.unsetDebugMode = function () { + _debug = false; + }; + Object.defineProperty(Config, "apiHost", { + /** + * @ignore + */ + get: function () { + if (!_deskApiHost) { + var sb = _sendbird; + _deskApiHost = "https://desk-api-".concat(sb.appId, ".sendbird.com/sapi"); + } + return _deskApiHost; + }, + /** + * @ignore + */ + set: function (val) { + _deskApiHost = val; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Config, "sdkVersion", { + /** + * @ignore + */ + get: function () { + return DESK_SDK_VERSION; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Config, "minSendbirdVersion", { + /** + * @ignore + */ + get: function () { + return MIN_SENDBIRD_VERSION; + }, + enumerable: false, + configurable: true + }); + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Check if Sendbird SDK version meets the min supported version. + * @returns {boolean} - true if it's compatible. + */ + Config.isSendbirdSdkCompatible = function (version) { + var _minSupportedVersion = MIN_SENDBIRD_VERSION.split('.'); + var _targetVersion = version.split('.'); + if (_minSupportedVersion.length === _targetVersion.length) { + for (var i in _minSupportedVersion) { + if (parseInt(_minSupportedVersion[i]) < parseInt(_targetVersion[i])) { + return true; + } + else if (parseInt(_minSupportedVersion[i]) > parseInt(_targetVersion[i])) { + return false; + } + } + return true; + } + return false; + }; + return Config; +}()); + +/** + * @since 1.0.0 + * @desc Sendbird Desk specific errors. + * @property {IErrorType} ERROR_SENDBIRD_SDK_MISSING - 100 + * @property {IErrorType} ERROR_SENDBIRD_SDK_VERSION_NOT_SUPPORTED - 101 + * @property {IErrorType} ERROR_SENDBIRD_DESK_INIT_MISSING - 102 + * @property {IErrorType} ERROR_SENDBIRD_SDK_MESSAGE_LIST_FAILED - 200 + * @property {IErrorType} ERROR_INVALID_PARAMETER - 403 + * @property {IErrorType} ERROR_DATA_NOT_FOUND - 404 + * @property {IErrorType} ERROR_REQUEST - 500 + * @property {IErrorType} ERROR_REQUEST_CANCELED - 501 + */ +var ErrorType = { + ERROR_SENDBIRD_SDK_MISSING: { + code: 100, + message: 'Sendbird SDK is missing.', + }, + ERROR_SENDBIRD_SDK_VERSION_NOT_SUPPORTED: { + code: 101, + message: 'This Sendbird SDK version is not supported.', + }, + ERROR_SENDBIRD_DESK_INIT_MISSING: { + code: 102, + message: 'Sendbird Desk SDK is not initialized. It should be done before authentication.', + }, + ERROR_SENDBIRD_DESK_AUTH_FAILED: { + code: 103, + message: 'Sendbird Desk authentication failed.', + }, + ERROR_SENDBIRD_SDK_MESSAGE_LIST_FAILED: { + code: 200, + message: 'Cannot load messages in SDK client.', + }, + ERROR_INVALID_PARAMETER: { + code: 403, + message: 'Invalid parameter.', + }, + ERROR_DATA_NOT_FOUND: { + code: 404, + message: 'Data not found.', + }, + ERROR_REQUEST_TIMEOUT: { + code: 409, + message: 'Request timed-out.', + }, + ERROR_REQUEST: { + code: 500, + message: 'Request failed.', + }, + ERROR_REQUEST_CANCELED: { + code: 501, + message: 'Request canceled.', + }, +}; +/** + * An error class for Desk. + * @extends {Error} + * @since 1.0.0 + */ +var SendbirdDeskError = /** @class */ (function (_super) { + __extends(SendbirdDeskError, _super); + /** + * @since 1.0.0 + * @param {string} message - Error message. + * @param {number} code - Error code. + */ + function SendbirdDeskError(message, code) { + var _this = _super.call(this, message) || this; + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(_this, _this.constructor); + } + else { + _this.stack = new Error(message).stack; + } + _this.name = 'SendbirdDeskError'; + _this.code = code; + return _this; + } + Object.defineProperty(SendbirdDeskError, "Type", { + get: function () { + return ErrorType; + }, + enumerable: false, + configurable: true + }); + /** + * @static + * @since 1.0.5 + * @ignore + * @desc Create an error. + */ + SendbirdDeskError.create = function (type) { + return type ? new SendbirdDeskError(type.message, type.code) : new Error('SendbirdDeskError type missing.'); + }; + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Throw an error. + */ + SendbirdDeskError.throw = function (type) { + throw new SendbirdDeskError(type.message, type.code); + }; + return SendbirdDeskError; +}(Error)); + +/** + * @since 1.0.1 + * @ignore + */ +var Logger = /** @class */ (function () { + function Logger() { + } + Logger.write = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + if (Config.isDebugMode) { + // eslint-disable-next-line no-console + console.log.apply(console, __spreadArray([], __read(args), false)); + } + }; + Logger.error = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + if (Config.isDebugMode) { + // eslint-disable-next-line no-console + console.error.apply(console, __spreadArray([], __read(args), false)); + } + }; + return Logger; +}()); + +var _privateData = {}; +/** + * @since 1.0.0 + * @ignore + */ +var Auth = /** @class */ (function () { + function Auth() { + } + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Authenticate and connect to Desk server. + */ + Auth.connect = function (userId, accessToken) { + return __awaiter(this, void 0, void 0, function () { + var sb, res, data; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + sb = Config.sendbird; + sb.addSendbirdExtensions([ + { + product: SendbirdProduct.DESK, + version: version, + platform: SendbirdPlatform.JS, + }, + ], { + platform: Config.platform, + }); + if (!sb) return [3 /*break*/, 3]; + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/customers/auth/"), { + method: 'POST', + headers: { + sendbirdAccessToken: accessToken || '', + 'content-type': 'application/json', + }, + body: JSON.stringify({ + sendbirdAppId: sb.appId, + sendbirdId: userId, + }), + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + _privateData.deskToken = data.token; + Logger.write('[REQ] connection established'); + return [3 /*break*/, 4]; + case 3: throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_SENDBIRD_SDK_MISSING); + case 4: return [2 /*return*/]; + } + }); + }); + }; + Object.defineProperty(Auth, "header", { + /** + * @ignore + */ + get: function () { + if (_privateData.deskToken) { + return { + sendbirdDeskToken: _privateData.deskToken, + 'content-type': 'application/json', + 'user-agent': "desk-js@".concat(version), + }; + } + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_SENDBIRD_DESK_INIT_MISSING); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Auth, "getParam", { + /** + * @ignore + */ + get: function () { + var sb = Config.sendbird; + return sb ? "sendbirdAppId=".concat(sb.appId) : ''; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Auth, "postParam", { + /** + * @ignore + */ + get: function () { + var sb = Config.sendbird; + return sb ? { sendbirdAppId: sb.appId } : {}; + }, + enumerable: false, + configurable: true + }); + return Auth; +}()); + +/** + * @module Agent + * @ignore + */ +/** + * @since 1.0.0 + */ +var Agent = /** @class */ (function () { + /** + * @since 1.0.0 + * @private + * @desc Create an agent. + */ + function Agent(params) { + this.fetchFromJSON(params); + } + /** + * @since 1.0.0 + * @private + * @desc Parse JSON data and patch Agent object. + */ + Agent.prototype.fetchFromJSON = function (params) { + var _a, _b, _c, _d, _e; + if ('id' in params) { + this.userId = params.id; + this.name = (_a = params.displayName) !== null && _a !== void 0 ? _a : ''; + this.profileUrl = (_b = params.photoThumbnailUrl) !== null && _b !== void 0 ? _b : ''; + this.sendbirdId = params.sendbirdId; + return; + } + // legacy + this.userId = (_c = params.user) !== null && _c !== void 0 ? _c : 0; + this.name = (_d = params.displayName) !== null && _d !== void 0 ? _d : ''; + this.profileUrl = (_e = params.photoThumbnailUrl) !== null && _e !== void 0 ? _e : ''; + }; + return Agent; +}()); + +/** + * @classdesc RelatedChannel + * @since 1.0.14 + */ +var RelatedChannel = /** @class */ (function () { + /** + * @since 1.0.14 + * @private + * @desc Create a related channel + */ + function RelatedChannel(params) { + this.fetchFromJSON(params); + } + /** + * @since 1.0.14 + * @private + * @desc Parse JSON data and patch RelatedChannel object. + */ + RelatedChannel.prototype.fetchFromJSON = function (params) { + var _a, _b; + this.channelUrl = (_a = params.channel_url) !== null && _a !== void 0 ? _a : ''; + this.name = (_b = params.name) !== null && _b !== void 0 ? _b : ''; + }; + return RelatedChannel; +}()); + +// --------- Misc Types --------- // +// Enums arent good match for runtime values. +// Would they work? Yeah.. but they would be annoying for customer +// to use. They just need to call create(..., 'URGENT', ...) instead of +// create(..., TicketPriority.URGENT, ...) +// to use. So we use const-enums instead. +var TicketPriorityMap = { + URGENT: 'URGENT', + HIGH: 'HIGH', + MEDIUM: 'MEDIUM', + LOW: 'LOW', +}; +var TicketStatusMap = { + INITIALIZED: 'INITIALIZED', + PROACTIVE: 'PROACTIVE', + UNASSIGNED: 'UNASSIGNED', + ASSIGNED: 'ASSIGNED', + OPEN: 'OPEN', + CLOSED: 'CLOSED', +}; + +function mapCreateTicketArgs(args) { + if ((args === null || args === void 0 ? void 0 : args.length) < 3 || (args === null || args === void 0 ? void 0 : args.length) > 8) { + Logger.error('[REQ] Close ticket should have at least 1 param.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (args.length === 3) { + var _a = __read(args, 3), title = _a[0], name_1 = _a[1], cb = _a[2]; + return { title: title, name: name_1, cb: cb }; + } + if (args.length === 4) { + var _b = __read(args, 4), title = _b[0], name_2 = _b[1], groupKey = _b[2], cb = _b[3]; + return { title: title, name: name_2, groupKey: groupKey, cb: cb }; + } + if (args.length === 5) { + var _c = __read(args, 5), title = _c[0], name_3 = _c[1], groupKey = _c[2], customFields = _c[3], cb = _c[4]; + return { title: title, name: name_3, groupKey: groupKey, customFields: customFields, cb: cb }; + } + if (args.length === 6) { + var _d = __read(args, 6), title = _d[0], name_4 = _d[1], groupKey = _d[2], customFields = _d[3], priority = _d[4], cb = _d[5]; + return { title: title, name: name_4, groupKey: groupKey, customFields: customFields, priority: priority, cb: cb }; + } + if (args.length === 7) { + var _e = __read(args, 7), title = _e[0], name_5 = _e[1], groupKey = _e[2], customFields = _e[3], priority = _e[4], relatedChannelUrls = _e[5], cb = _e[6]; + return { title: title, name: name_5, groupKey: groupKey, customFields: customFields, priority: priority, relatedChannelUrls: relatedChannelUrls, cb: cb }; + } + if (args.length === 8) { + var _f = __read(args, 8), title = _f[0], name_6 = _f[1], groupKey = _f[2], customFields = _f[3], priority = _f[4], relatedChannelUrls = _f[5], botKey = _f[6], cb = _f[7]; + return { title: title, name: name_6, groupKey: groupKey, customFields: customFields, priority: priority, relatedChannelUrls: relatedChannelUrls, botKey: botKey, cb: cb }; + } + // TS dont know that we have checked for length + Logger.error('[REQ] Create ticket should have between 3 to 8 paramters.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); +} +function validateCreateTicketArgs(params) { + var title = params.title, name = params.name, groupKey = params.groupKey, customFields = params.customFields, relatedChannelUrls = params.relatedChannelUrls, botKey = params.botKey, priority = params.priority; + if (typeof title !== 'string') { + Logger.error('[REQ] Create ticket title should be a string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (typeof name !== 'string') { + Logger.error('[REQ] Create ticket name should be a string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (groupKey && typeof groupKey !== 'string') { + Logger.error('[REQ] Create ticket groupKey should be a string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (priority && TicketPriorityMap[priority] === undefined) { + Logger.error('[REQ] Create ticket priority should be a string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (customFields && typeof customFields !== 'object') { + Logger.error('[REQ] Create ticket customFields should be an object.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (Array.isArray(customFields)) { + Logger.error('[REQ] Create ticket customFields cannot be an array.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (relatedChannelUrls && (!Array.isArray(relatedChannelUrls) + || relatedChannelUrls.some(function (channelUrl) { return typeof channelUrl !== 'string'; }))) { + Logger.error('[REQ] Create ticket relatedChannelUrls should be an array.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (botKey && typeof botKey !== 'string') { + Logger.error('[REQ] Create ticket botKey should be a string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (typeof params.cb !== 'function') { + Logger.error('[REQ] Create ticket callback should be a function.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +function noop() { } + +function mapCloseArgs(params) { + // more than 2 args + if (params.length > 2) { + Logger.error('[REQ] Close ticket should have only 2 params.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + // no args + var comment = ''; + var cb = noop; + // one arg + if (params.length === 1) { + if (typeof params[0] === 'string') { + comment = params[0]; + } + else if (typeof params[0] === 'function') { + cb = params[0]; + } + } + // both args + if (params.length === 2) { + comment = params[0]; + cb = params[1]; + } + return { + comment: comment, + cb: cb, + }; +} +function validateCloseArgs(params) { + var comment = params.comment, cb = params.cb; + if (typeof comment !== 'string') { + Logger.error('[REQ] first param must be string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (typeof cb !== 'function') { + Logger.error('[REQ] second param must be callback.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } +} + +/** + * @module Message + * @ignore + */ +/** + * @since 1.0.0 + */ +var Message = /** @class */ (function () { + function Message() { + } + Object.defineProperty(Message, "CustomType", { + /** + * @static + * @since 1.0.0 + * @desc message custom type. + * @property {string} RICH_MESSAGE - SENDBIRD_DESK_RICH_MESSAGE + * @property {string} ADMIN_MESSAGE - SENDBIRD_DESK_ADMIN_MESSAGE_CUSTOM_TYPE + */ + get: function () { + return { + RICH_MESSAGE: 'SENDBIRD_DESK_RICH_MESSAGE', + ADMIN_MESSAGE: 'SENDBIRD_DESK_ADMIN_MESSAGE_CUSTOM_TYPE', + }; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Message, "DataType", { + /** + * @static + * @since 1.0.0 + * @desc message data type. + * @property {string} TICKET_INQUIRE_CLOSURE - SENDBIRD_DESK_INQUIRE_TICKET_CLOSURE + * @property {string} TICKET_ASSIGN - TICKET_ASSIGN + * @property {string} TICKET_TRANSFER - TICKET_TRANSFER + * @property {string} TICKET_CLOSE - TICKET_CLOSE + * @property {string} URL_PREVIEW - URL_PREVIEW + */ + get: function () { + return { + TICKET_INQUIRE_CLOSURE: 'SENDBIRD_DESK_INQUIRE_TICKET_CLOSURE', + TICKET_ASSIGN: 'TICKET_ASSIGN', + TICKET_TRANSFER: 'TICKET_TRANSFER', + TICKET_CLOSE: 'TICKET_CLOSE', + TICKET_FEEDBACK: 'SENDBIRD_DESK_CUSTOMER_SATISFACTION', + URL_PREVIEW: 'SENDBIRD_DESK_URL_PREVIEW', + }; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Message, "ClosureState", { + /** + * @static + * @since 1.0.0 + * @desc closure inquiry messsage state. + * @property {string} WAITING - WAITING + * @property {string} CONFIRMED - CONFIRMED + * @property {string} DECLINED - DECLINED + */ + get: function () { + return { + WAITING: 'WAITING', + CONFIRMED: 'CONFIRMED', + DECLINED: 'DECLINED', + }; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Message, "FeedbackState", { + /** + * @module Message + * @ignore + */ + /** + * @static + * @since 1.0.8 + * @desc closure inquiry messsage state. + * @property {string} WAITING - WAITING + * @property {string} CONFIRMED - CONFIRMED + */ + get: function () { + return { + WAITING: 'WAITING', + CONFIRMED: 'CONFIRMED', + }; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Message, "UrlRegExp", { + /** + * @ignore + */ + get: function () { + return /(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?/gi; + }, + enumerable: false, + configurable: true + }); + return Message; +}()); + +function mapCancelArgs(params) { + if ((params === null || params === void 0 ? void 0 : params.length) < 1) { + Logger.error('Cancel ticket should have at least 1 param.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (params.length > 2) { + Logger.error('Cancel ticket should have only 2 params.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + var groupKeyForTransfer = ''; + var cb = noop; + if (params.length === 1 && typeof params[0] === 'function') { + cb = params[0]; + } + if (params.length === 1 && typeof params[0] === 'string') { + groupKeyForTransfer = params[0]; + } + if (params.length === 2) { + groupKeyForTransfer = params[0]; + cb = params[1]; + } + return { + groupKeyForTransfer: groupKeyForTransfer, + cb: cb, + }; +} +function validateCancelArgs(args) { + if (typeof args.groupKeyForTransfer !== 'string') { + Logger.error('First param must be string.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (typeof args.cb !== 'function') { + Logger.error('Second param must be callback.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } +} + +function mapGetByChannelUrlArgs(params) { + if ((params === null || params === void 0 ? void 0 : params.length) < 2 || (params === null || params === void 0 ? void 0 : params.length) > 3) { + Logger.error('[REQ] Get ticket should have 2 or 3 paramters.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + // order of params is important + var channelUrl = params[0]; + var cachingEnabled = params[1]; + // last param is callback + var cb = params[params.length - 1] || noop; + // @ts-expect-error This will be validated in validateGetByChannelUrlArgs + return { channelUrl: channelUrl, cachingEnabled: cachingEnabled, cb: cb }; +} + +var TICKET_CUSTOM_TYPE = 'SENDBIRD_DESK_CHANNEL_CUSTOM_TYPE'; +var DEFAULT_LIMIT = 10; + +var _getByChannelUrls = function (channelUrls) { return __awaiter(void 0, void 0, void 0, function () { + var sb, query, error_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + sb = Config.sendbird; + if (!sb) return [3 /*break*/, 5]; + query = sb.groupChannel.createMyGroupChannelListQuery({ + customTypesFilter: [TICKET_CUSTOM_TYPE], + channelUrlsFilter: channelUrls, + includeEmpty: true, + }); + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4 /*yield*/, query.next()]; + case 2: return [2 /*return*/, _a.sent()]; + case 3: + error_1 = _a.sent(); + Logger.error('[REQ] ticket get by channel urls failed:', error_1); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + case 4: return [3 /*break*/, 6]; + case 5: + Logger.error('[REQ] ticket get by channel urls failed: chat SDK is not initialized.'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_SENDBIRD_SDK_MISSING); + case 6: return [2 /*return*/]; + } + }); +}); }; + +function generatePath(apiHost, authParam, params) { + var _a = params.offset, offset = _a === void 0 ? 0 : _a, _b = params.limit, limit = _b === void 0 ? DEFAULT_LIMIT : _b, _c = params.order, order = _c === void 0 ? '-updated_at' : _c, channelUrl = params.channelUrl, customFieldFilter = params.customFieldFilter, group = params.group, _d = params.status, status = _d === void 0 ? 'ALL' : _d; + var statusQuery = status !== TicketStatusMap.CLOSED ? 'status=ASSIGNED&status=UNASSIGNED' : 'status=CLOSED'; + var path = "".concat(apiHost, "/tickets/") + + "?".concat(authParam, "&limit=").concat(limit, "&offset=").concat(offset) + + "".concat(status === 'ALL' ? '' : "&".concat(statusQuery), "&order=").concat(order); + if (channelUrl) { + path += "&channelUrl=".concat(channelUrl); + } + if (group) { + path += "&group=".concat(group); + } + if (customFieldFilter && typeof customFieldFilter === 'object') { + path += + '&customFields=' + + encodeURIComponent(Object.keys(customFieldFilter) + .map(function (key) { return "".concat(key, ":").concat(encodeURIComponent(customFieldFilter[key])); }) + .join(',')); + } + return path; +} + +function mapGetTicketArgs(args) { + if ((args === null || args === void 0 ? void 0 : args.length) < 2 || (args === null || args === void 0 ? void 0 : args.length) > 3) { + throw new Error('Get ticket should have 2 or 3 paramters.'); + } + var offset = 0; + var filter = {}; + var cb = noop; + if (args.length === 2) { + offset = args[0]; + cb = args[1]; + } + if (args.length === 3) { + offset = args[0]; + filter = args[1]; + cb = args[2]; + } + return { offset: offset, filter: filter, cb: cb }; +} +function validateGetTicketArgs(arg) { + var offset = arg.offset, filter = arg.filter, cb = arg.cb; + if (typeof offset !== 'number') { + throw new Error('Get ticket offset should be a number.'); + } + if (typeof cb !== 'function') { + throw new Error('Get ticket callback should be a function.'); + } + if (filter && typeof filter !== 'object') { + throw new Error('Get ticket filter should be an object.'); + } +} + +// bad scoping practice - rewrite this in v2 +var _cachedTicket = {}; +// Dear future me(July 2023), +// If you go through this code, you will see that the Ticket class is a mess. +// It's a mess because it's trying to do too many things. +// It has static methods to create tickets, and instance methods to update tickets. +// Ideally they should be separated in 2 places +// We should make a builder or something to create tickets +// and then we should have a Ticket instance to update/close/cancel tickets +// Also, I added some things that you might have found weird +// For example - why is there async _create and create methods? +// Desk(1.0.0) was written in 2017(with chatSDK 2 or 3) when cbs were the norm, and async/await was not a thing +// In 2023 management decided to update the chat SDK to chat 4. Now its a mess of cbs and async/await +// Sadly we were not allowed to make breaking changes, so we had to keep the old cb methods in Desk +// These _async methods are a workaround to keep the old cb methods and add async/await support +// Also, I have implemented in a way that - every difficult method is -> +// pub method(args_list) { +// args_map = argListToArgMap() -> validate() +// private async _method(args_map){ +// [ args_map -> fetch() ] -> output +// } +// } +// later if we can refactor - +// * you can remove the cb-based public methods +// * you can change the private async _method -> pub async method +// * you can remove the argListToArgMap() -> and just use the args directly +// --- Ticket class !important--- +// !Important: If you implement a public method here, please add it to type: `TicketClass`. +// --- Ticket class !important--- +/** + * @since 1.0.0 + */ +var Ticket = /** @class */ (function () { + /** + * @since 1.0.0 + * @private + * @desc Create a ticket. + */ + function Ticket(params) { + this.fetchFromJSON(params); + } + Object.defineProperty(Ticket, "Status", { + /** + * @static + * @since 1.0.0 + * @desc Ticket status + * @property {string} INITIALIZED - ticket is created but not able to assign. + * @property {string} PROACTIVE - ticket is introduced as proactive ticket. + * @property {string} UNASSIGNED - ticket is activated and able to assign. + * @property {string} ASSIGNED - ticket is assigned by an agent. + * @property {string} OPEN - ticket is activated. + * @property {string} CLOSED - ticket is closed. + */ + get: function () { + return TicketStatusMap; + }, + enumerable: false, + configurable: true + }); + /** + * @since 1.0.0 + * @private + * @desc Parse JSON data and patch Ticket object. + */ + Ticket.prototype.fetchFromJSON = function (params) { + var _a, _b, _c, _d, _e, _f, _g, _h; + this.id = params.id; + this.title = (_a = params.channelName) !== null && _a !== void 0 ? _a : ''; + this.status = (_b = params.status) !== null && _b !== void 0 ? _b : TicketStatusMap.UNASSIGNED; + this.info = params.info ? JSON.parse(params.info) : null; + this.priority = (_c = params.priority) !== null && _c !== void 0 ? _c : TicketPriorityMap.MEDIUM; + this.agent = ((_d = params.recentAssignment) === null || _d === void 0 ? void 0 : _d.agent) ? new Agent(params.recentAssignment.agent) : null; + this.customer = (_e = params.customer) !== null && _e !== void 0 ? _e : null; + this.customFields = {}; + this.group = (_f = params.group) !== null && _f !== void 0 ? _f : 0; + this.relatedChannels = []; + this.channelUrl = (_g = params.channelUrl) !== null && _g !== void 0 ? _g : null; + this.updatedAt = (_h = params.updatedAt) !== null && _h !== void 0 ? _h : 0; + if (params.customFields) { + var customFields = params.customFields; + for (var i = 0; i < customFields.length; i++) { + var key = customFields[i].key; + var value = customFields[i].value; + this.customFields[key] = value; + } + } + if (params.relatedChannels) { + try { + var relatedChannelsPayload = JSON.parse(params.relatedChannels); + if (Array.isArray(relatedChannelsPayload)) { + this.relatedChannels = relatedChannelsPayload.map(function (item) { return new RelatedChannel(item); }); + } + } + catch (e) { + // DO NOTHING + } + } + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket.prototype._fetchChannel = function () { + return __awaiter(this, void 0, void 0, function () { + var _a, channel; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: return [4 /*yield*/, _getByChannelUrls([this.channelUrl])]; + case 1: + _a = __read.apply(void 0, [_b.sent(), 1]), channel = _a[0]; + this.channel = channel; + _cachedTicket[this.channelUrl] = this; + return [2 /*return*/]; + } + }); + }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket._getTicketList = function (params) { + return __awaiter(this, void 0, void 0, function () { + var path, res, data, tickets, channelUrls, i, ticket, channels_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + path = generatePath(Config.apiHost, Auth.getParam, params); + return [4 /*yield*/, fetch(path, { + method: 'GET', + headers: Auth.header, + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] ticket list failed:', data); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + } + tickets = []; + channelUrls = []; + if (!Array.isArray(data.results)) return [3 /*break*/, 4]; + for (i in data.results) { + ticket = new Ticket(data.results[i]); + _cachedTicket[ticket.channelUrl] = ticket; + channelUrls.push(ticket.channelUrl); + tickets.push(ticket); + } + return [4 /*yield*/, _getByChannelUrls(channelUrls)]; + case 3: + channels_1 = _a.sent(); + tickets.forEach(function (ticket) { + for (var i in channels_1) { + var channel = channels_1[i]; + if (ticket.channelUrl === channel.url) { + ticket.channel = channel; + break; + } + } + }); + Logger.write("[REQ] ".concat(status, " ticket list:"), tickets); + _a.label = 4; + case 4: return [2 /*return*/, tickets]; + } + }); + }); + }; + /** + * @since 1.0.0 + * @desc Refresh ticket info. + * @param {function(ticket:Ticket, err:Error)} cb - cb function. + */ + Ticket.prototype.refresh = function (cb) { + this._refresh() + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket.prototype._refresh = function () { + return __awaiter(this, void 0, void 0, function () { + var res, data, err; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/").concat(this.id, "/?").concat(Auth.getParam), { + method: 'GET', + headers: Auth.header, + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] ticket refresh failed:', res); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + this.fetchFromJSON(data); + return [4 /*yield*/, this._fetchChannel()]; + case 3: + _a.sent(); + Logger.write('[REQ] ticket refresh:', this); + return [2 /*return*/, this]; + } + }); + }); + }; + /** + * @since 1.0.6 + * @desc Reopen closed ticket. + * @param {function} cb - Function(res:Ticket, err:Error). + */ + Ticket.prototype.reopen = function (cb) { + this._reopen() + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket.prototype._reopen = function () { + return __awaiter(this, void 0, void 0, function () { + var res, data, err, e_1, err; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 5, , 6]); + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/").concat(this.id, "/reopen/"), { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify(Auth.postParam), + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] ticket reopen failed:', data); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + if (!data.channelUrl) return [3 /*break*/, 4]; + this.fetchFromJSON(data); + return [4 /*yield*/, this._fetchChannel()]; + case 3: + _a.sent(); + Logger.write('[REQ] ticket reopen:', this); + _a.label = 4; + case 4: return [2 /*return*/, this]; + case 5: + e_1 = _a.sent(); + Logger.error('[REQ] ticket reopen failed:', e_1); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + case 6: return [2 /*return*/]; + } + }); + }); + }; + /** + * @since 1.0.18 + * @desc Cancel the assignment and set it to open. + * @param {string} groupKeyForTransfer - Group key for transfer(optional) + * @param {function} cb - Function(res:Ticket, err:Error). + */ + Ticket.prototype.cancel = function () { + var params = []; + for (var _i = 0; _i < arguments.length; _i++) { + params[_i] = arguments[_i]; + } + var args = mapCancelArgs(params); + var cb = args.cb; + this._cancel(args) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket.prototype._cancel = function (args) { + return __awaiter(this, void 0, void 0, function () { + var groupKeyForTransfer, requestBody, res, data, err; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + validateCancelArgs(args); + groupKeyForTransfer = args.groupKeyForTransfer; + requestBody = __assign({}, Auth.postParam); + if (groupKeyForTransfer) { + requestBody['groupKeyForTransfer'] = groupKeyForTransfer; + } + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/").concat(this.id, "/cancel"), { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify(requestBody), + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] ticket cancel failed:', data); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + if (!data.channelUrl) return [3 /*break*/, 4]; + this.fetchFromJSON(data); + return [4 /*yield*/, this._fetchChannel()]; + case 3: + _a.sent(); + Logger.write('[REQ] ticket cancel:', this); + _a.label = 4; + case 4: return [2 /*return*/, this]; + } + }); + }); + }; + /** + * @since 1.0.16 + * @desc Force close an assigned ticket. + * @param {string} comment - Comment for closing the ticket. + */ + Ticket.prototype.close = function () { + var params = []; + for (var _i = 0; _i < arguments.length; _i++) { + params[_i] = arguments[_i]; + } + var args = mapCloseArgs(params); + var cb = args.cb; + this._close(args) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket.prototype._close = function (args) { + return __awaiter(this, void 0, void 0, function () { + var comment, res, data, err; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + validateCloseArgs(args); + comment = args.comment; + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/").concat(this.id, "/close"), { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify(__assign(__assign({}, Auth.postParam), { closeComment: comment })), + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] ticket close failed:', data); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + if (!data.channelUrl) return [3 /*break*/, 4]; + this.fetchFromJSON(data); + return [4 /*yield*/, this._fetchChannel()]; + case 3: + _a.sent(); + Logger.write('[REQ] ticket close:', this); + _a.label = 4; + case 4: return [2 /*return*/, this]; + } + }); + }); + }; + /** + * @since 1.0.18 + * @desc Select a question. + * @param {number} faqFileId - FAQ file ID. + * @param {string} question - Question text. + * @param {function} callback - Function(res:object, err:Error). + */ + Ticket.prototype.selectQuestion = function (faqFileId, question, cb) { + this._selectQuestion(faqFileId, question) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket.prototype._selectQuestion = function (faqFileId, question) { + return __awaiter(this, void 0, void 0, function () { + var res, data, err; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (typeof faqFileId !== 'number' || typeof question !== 'string') { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/").concat(this.id, "/select_question"), { + method: 'POST', + headers: Auth.header, + body: JSON.stringify(__assign(__assign({}, Auth.postParam), { faqFileId: faqFileId, question: question })), + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] ticket select question failed:', data); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + Logger.write('[REQ] ticket select option:', this); + return [2 /*return*/, this]; + } + }); + }); + }; + /** + * @since 1.0.10 + * @desc Set ticket priority. + * @param {string} priority - priority. + * @param {function} callback - Function(res:Ticket, err:Error). + */ + Ticket.prototype.setPriority = function (priority, cb) { + this._setPriority(priority) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket.prototype._setPriority = function (priority) { + return __awaiter(this, void 0, void 0, function () { + var res, data, err; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (Object.keys(TicketPriorityMap) + .map(function (key) { return TicketPriorityMap[key]; }) + .indexOf(priority) < 0) { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/").concat(this.id), { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify(__assign(__assign({}, Auth.postParam), { priority: priority })), + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] ticket set priority failed:', data); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + this.fetchFromJSON(data); + Logger.write('[REQ] ticket set priority:', this); + return [2 /*return*/, this]; + } + }); + }); + }; + /** + * @since 1.0.14 + * @desc Set ticket related channel URLs. + * @param {array} relatedChannelUrls - related channel URLs. + * @param {function} callback - Function(res:Ticket, err:Error). + */ + Ticket.prototype.setRelatedChannelUrls = function (relatedChannelUrls, cb) { + this._setRelatedChannelUrls(relatedChannelUrls) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket.prototype._setRelatedChannelUrls = function (relatedChannelUrls) { + return __awaiter(this, void 0, void 0, function () { + var res, data, err; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!Array.isArray(relatedChannelUrls) || relatedChannelUrls.some(function (channelUrl) { return typeof channelUrl !== 'string'; })) { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/").concat(this.id), { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify(__assign(__assign({}, Auth.postParam), { relatedChannelUrls: relatedChannelUrls.join(',') })), + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] ticket set related channel URLs failed:', data); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + this.fetchFromJSON(data); + Logger.write('[REQ] ticket set related channel URLs:', this); + return [2 /*return*/, this]; + } + }); + }); + }; + /** + * @since 1.0.10 + * @desc Set ticket customFields. + * @param {object} customFields - customFields object (key-value). + * @param {function} callback - Function(res:Ticket, err:Error). + */ + Ticket.prototype.setCustomFields = function (customFields, cb) { + this._setCustomFields(customFields) + .then(function (data) { + cb(data, null); + }) + .catch(function (err) { + cb(null, err); + }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket.prototype._setCustomFields = function (customFields) { + return __awaiter(this, void 0, void 0, function () { + var formattedCustomFields, key, res, data, err; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (typeof customFields !== 'object' || customFields === null || Array.isArray(customFields)) { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + formattedCustomFields = {}; + for (key in customFields) { + formattedCustomFields[key] = + typeof customFields[key] === 'string' ? customFields[key] : JSON.stringify(customFields[key]); + } + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/").concat(this.id, "/custom_fields/"), { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify(__assign(__assign({}, Auth.postParam), { customFields: JSON.stringify(formattedCustomFields) })), + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] ticket set custom fields failed:', data); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + this.fetchFromJSON(data); + Logger.write('[REQ] ticket set custom fields:', this); + return [2 /*return*/, this]; + } + }); + }); + }; + /** + * @ignore + */ + Ticket.isDeskCustomType = function (customType) { + return customType === TICKET_CUSTOM_TYPE; + }; + Object.defineProperty(Ticket, "defaultLimit", { + /** + * @ignore + */ + get: function () { + // ^^ This method probably exist because of some old OOP dogma + return DEFAULT_LIMIT; + }, + enumerable: false, + configurable: true + }); + /** + * @static + * @since 1.0.0 + * @desc Clear cached ticket. Clear all if channelUrl is not specified. + */ + Ticket.clearCache = function (channelUrl) { + if (channelUrl) { + delete _cachedTicket[channelUrl]; + Logger.write("[SYS] cached ticket for ".concat(channelUrl, " cleared")); + } + else { + _cachedTicket = {}; + Logger.write('[SYS] all cached ticket cleared'); + } + }; + /** + * @static + * @since 1.0.0 + * @desc Create new ticket and returns the ticket within cb. + * @param {string} title - Ticket title. + * @param {string} name - User name. + * @param {string} groupKey - Agent group key (optional). + * @param {object} customFields - customField (optional). + * @param {string} priority - priority (optional). + * @param {array} relatedChannelUrls - related channel URLs (optional). + * @param {string} botKey - botKey (optional). + * @param {function} cb - Function(ticket:Ticket, err:Error). + */ + Ticket.create = function () { + var params = []; + for (var _i = 0; _i < arguments.length; _i++) { + params[_i] = arguments[_i]; + } + var args = mapCreateTicketArgs(params); + var cb = args.cb; + this._create(args) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket._create = function (args) { + var _a; + return __awaiter(this, void 0, void 0, function () { + var title, name, groupKey, customFields, priority, relatedChannelUrls, botKey, sb, err, formattedCustomFields, key, fetchBody, res, data, err, ticket, _b, channel; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + title = args.title, name = args.name, groupKey = args.groupKey, customFields = args.customFields, priority = args.priority, relatedChannelUrls = args.relatedChannelUrls, botKey = args.botKey; + sb = Config.sendbird; + if (!sb) { + Logger.error('[REQ] ticket create chat SDK is not initalized.'); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_SENDBIRD_SDK_MISSING); + throw err; + } + validateCreateTicketArgs(args); + formattedCustomFields = {}; + for (key in customFields) { + formattedCustomFields[key] = + typeof customFields[key] === 'string' ? customFields[key] : JSON.stringify(customFields[key]); + } + fetchBody = __assign(__assign({}, Auth.postParam), { channelName: title, channelType: 'SENDBIRD_JAVASCRIPT', groupKey: groupKey, customFields: customFields ? JSON.stringify(formattedCustomFields) : undefined, relatedChannelUrls: relatedChannelUrls === null || relatedChannelUrls === void 0 ? void 0 : relatedChannelUrls.join(','), botKey: botKey, info: JSON.stringify({ + // FIXME: Zendesk version only + ticket: { + subject: title, + requester: { + name: name, + email: ((_a = sb === null || sb === void 0 ? void 0 : sb.currentUser) === null || _a === void 0 ? void 0 : _a.userId) || '', + }, + }, + }) }); + if (priority) { + fetchBody['priority'] = priority; + } + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/"), { + method: 'POST', + headers: Auth.header, + body: JSON.stringify(fetchBody), + })]; + case 1: + res = _c.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _c.sent(); + if (!res.ok) { + Logger.error('[REQ] ticket create failed:', data); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + ticket = new Ticket(data); + return [4 /*yield*/, _getByChannelUrls([ticket.channelUrl])]; + case 3: + _b = __read.apply(void 0, [_c.sent(), 1]), channel = _b[0]; + ticket.channel = channel; + _cachedTicket[channel.url] = ticket; + Logger.write('[REQ] ticket create success:', ticket); + return [2 /*return*/, ticket]; + } + }); + }); + }; + /** + * @static + * @since 1.0.0 + * @desc Get ticket count for each state: UNASSIGNED, ASSIGNED, CLOSED. + * @param {function} callback - Function(result:GetOpenCountResponse, err:Error). + */ + Ticket.getOpenCount = function (cb) { + this._getOpenCount() + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket._getOpenCount = function () { + return __awaiter(this, void 0, void 0, function () { + var res, data; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/count/?").concat(Auth.getParam), { + method: 'GET', + headers: Auth.header, + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] get open ticket count failed:', data); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + } + Logger.write('[REQ] get open ticket count:', data); + return [2 /*return*/, data]; + } + }); + }); + }; + /** + * @static + * @since 1.0.22 + * @desc Get ticket from channel URL. Use caching for optimization. + * @param {string} channelUrl - channel URL. + * @param {boolean} _cachingEnabled - to get ticket from cache or not. + * @param {function} _callback - Function(ticket:Ticket, err:Error). + */ + /** + * @static + * @since 1.0.22 + * @desc Get ticket from channel URL. no caching by default. + * @param {string} channelUrl - channel URL. + * @param {function} _callback - Function(ticket:Ticket, err:Error). + */ + Ticket.getByChannelUrl = function () { + var params = []; + for (var _i = 0; _i < arguments.length; _i++) { + params[_i] = arguments[_i]; + } + var args = mapGetByChannelUrlArgs(params); + var cb = args.cb; + this._getByChannelUrl(args) + .then(function (data) { return cb === null || cb === void 0 ? void 0 : cb(data, null); }) + .catch(function (err) { return cb === null || cb === void 0 ? void 0 : cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket._getByChannelUrl = function (args) { + return __awaiter(this, void 0, void 0, function () { + var channelUrl, cachingEnabled, ticket, res, data, _a, channel; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + channelUrl = args.channelUrl, cachingEnabled = args.cachingEnabled; + if (!(!!_cachedTicket[channelUrl] && cachingEnabled)) return [3 /*break*/, 1]; + Logger.write("[REQ] get ticket for channel ".concat(channelUrl, " from cache:"), _cachedTicket[channelUrl]); + return [2 /*return*/, _cachedTicket[channelUrl]]; + case 1: + ticket = void 0; + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/?").concat(Auth.getParam, "&limit=10&channelUrl=").concat(channelUrl), { + method: 'GET', + headers: Auth.header, + })]; + case 2: + res = _b.sent(); + return [4 /*yield*/, res.json()]; + case 3: + data = _b.sent(); + if (!res.ok) { + Logger.error('[REQ] get ticket for channel failed:', data); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + } + if (!(Array.isArray(data.results) && data.results.length > 0)) return [3 /*break*/, 5]; + ticket = new Ticket(data.results[0]); + return [4 /*yield*/, _getByChannelUrls([ticket.channelUrl])]; + case 4: + _a = __read.apply(void 0, [_b.sent(), 1]), channel = _a[0]; + ticket.channel = channel; + _cachedTicket[channel.url] = ticket; + Logger.write("[REQ] get ticket for channel ".concat(channelUrl, ":"), ticket); + return [2 /*return*/, ticket]; + case 5: throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_DATA_NOT_FOUND); + } + }); + }); + }; + /** + * @static + * @since 1.0.23 + * @desc Lists all tickets. + * @param {integer} filters.offset - list offset. + * @param {object} filters.customFieldFilter - customField filter. + * @param {string} filters.group - group key(to filter tickets by a team). + * @param {string} filters.status - status to get tickets. ('all', 'CLOSED', 'OPEN'). + * @param {function} callback - Function(list:Array, err:Error) + */ + Ticket.getList = function (params, cb) { + // note to developers - Always recommend this method to customer + // and getrid of getOpenedList and getClosedList etc etc + Ticket._getTicketList(params) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @static + * @since 1.0.0 + * @desc Load opened ticket list. + * @param {integer} offset - list offset. + * @param {object} customFieldFilter - customField filter. + * @param {function} callback - Function(list:Array, err:Error) + */ + Ticket.getOpenedList = function () { + var params = []; + for (var _i = 0; _i < arguments.length; _i++) { + params[_i] = arguments[_i]; + } + var _a = mapGetTicketArgs(params), offset = _a.offset, filter = _a.filter, cb = _a.cb; + validateGetTicketArgs({ offset: offset, filter: filter, cb: cb }); + Ticket._getTicketList(__assign(__assign({}, filter), { offset: offset, status: 'OPEN' })) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @static + * @since 1.0.21 + * @desc Lists all tickets. + * @param {integer} offset - list offset. + * @param {object} customFieldFilter - customField filter. + * @param {function} callback - Function(list:Array, err:Error) + */ + Ticket.getAllTickets = function () { + var params = []; + for (var _i = 0; _i < arguments.length; _i++) { + params[_i] = arguments[_i]; + } + var _a = mapGetTicketArgs(params), offset = _a.offset, filter = _a.filter, cb = _a.cb; + validateGetTicketArgs({ offset: offset, filter: filter, cb: cb }); + Ticket._getTicketList(__assign(__assign({}, filter), { offset: offset, status: 'ALL' })) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @static + * @since 1.0.0 + * @desc Load closed ticket list. + * @param {integer} offset - list offset. + * @param {object} customFieldFilter - customField filter. + * @param {function} callback - Function(list:Array, err:Error) + */ + Ticket.getClosedList = function () { + var params = []; + for (var _i = 0; _i < arguments.length; _i++) { + params[_i] = arguments[_i]; + } + var _a = mapGetTicketArgs(params), offset = _a.offset, filter = _a.filter, cb = _a.cb; + validateGetTicketArgs({ offset: offset, filter: filter, cb: cb }); + Ticket._getTicketList(__assign(__assign({}, filter), { offset: offset, status: 'CLOSED' })) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @static + * @since 1.0.0 + * @desc Get URL preview info from URL. + * @param {string} url - URL to load preview metadata. + * @param {function} callback - Function(result:Object, err:Error). + */ + Ticket.getUrlPreview = function (url, cb) { + this._getUrlPreview(url) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket._getUrlPreview = function (url) { + return __awaiter(this, void 0, void 0, function () { + var res, data; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (typeof url !== 'string') { + Logger.error('[REQ] get url preview failed: url should be a string'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/url_preview/"), { + method: 'POST', + headers: Auth.header, + body: JSON.stringify(__assign(__assign({}, Auth.postParam), { url: url })), + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] get url preview failed:', data); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + } + Logger.write('[REQ] get url preview:', data); + return [2 /*return*/, data]; + } + }); + }); + }; + /** + * @since 1.0.0 + * @desc Reply to confirm-end-of-chat request in yes or no. + */ + Ticket.confirmEndOfChat = function (message, confirm, cb) { + this._confirmEndOfChat(message, confirm) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @since 1.0.0 + * @desc Reply to confirm-end-of-chat request in yes or no. + * This shouldnt be static, but it is for backwards compatibility + */ + Ticket.prototype.instanceConfirmEndOfChat = function (message, confirm, cb) { + Ticket._confirmEndOfChat(message, confirm) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + Ticket._confirmEndOfChat = function (message, confirm) { + return __awaiter(this, void 0, void 0, function () { + var ticket, res, data, err; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + // FIXME: specify return type + if (!(typeof confirm === 'string' && ['yes', 'no'].indexOf(confirm) > -1)) { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + return [4 /*yield*/, Ticket._getByChannelUrl({ + channelUrl: message.channelUrl, + cachingEnabled: true, + })]; + case 1: + ticket = _a.sent(); + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/").concat(ticket.id, "/edit_message/"), { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify(__assign(__assign({}, Auth.postParam), { messageId: message.messageId, messageData: JSON.stringify({ + type: Message.DataType.TICKET_INQUIRE_CLOSURE, + body: { + state: confirm === 'yes' ? Message.ClosureState.CONFIRMED : Message.ClosureState.DECLINED, + ticketId: ticket.id, + }, + }) })), + })]; + case 2: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 3: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] confirm end of chat failed:', data); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + Logger.write('[REQ] confirmed end of chat:', ticket); + return [2 /*return*/, ticket]; + } + }); + }); + }; + /** + * @since 1.0.8 + * @desc Submit feedback with a score and a comment. + */ + Ticket.submitFeedback = function (message, score, comment, cb) { + if (comment === void 0) { comment = ''; } + this._submitFeedback(message, score, comment) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @since 1.0.8 + * @desc Submit feedback with a score and a comment. + */ + Ticket.prototype.instanceSubmitFeedback = function (message, score, comment, cb) { + if (comment === void 0) { comment = ''; } + Ticket._submitFeedback(message, score, comment) + .then(function (data) { return cb(data, null); }) + .catch(function (err) { return cb(null, err); }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + * This shouldnt be static, but it is for backwards compatibility + */ + Ticket._submitFeedback = function (message, score, comment) { + if (comment === void 0) { comment = ''; } + return __awaiter(this, void 0, void 0, function () { + var ticket, res, data, err; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + // FIXME: specify return type + if (!(message && + message.messageId > 0 && + message.channelUrl && + typeof score === 'number' && + 1 <= score && + score <= 5 && + typeof comment === 'string')) { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + return [4 /*yield*/, Ticket._getByChannelUrl({ + channelUrl: message.channelUrl, + cachingEnabled: true, + })]; + case 1: + ticket = _a.sent(); + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/tickets/").concat(ticket.id, "/edit_message/"), { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify(__assign(__assign({}, Auth.postParam), { messageId: message.messageId, messageData: JSON.stringify({ + type: Message.DataType.TICKET_FEEDBACK, + body: { + state: Message.FeedbackState.CONFIRMED, + customerSatisfactionScore: score, + customerSatisfactionComment: comment, + ticketId: ticket.id, + }, + }) })), + })]; + case 2: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 3: + data = _a.sent(); + if (!res.ok) { + Logger.error('[REQ] submit feedback failed:', data); + err = SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_REQUEST); + throw err; + } + Logger.write('[REQ] customer feedback:', ticket); + return [2 /*return*/, data]; + } + }); + }); + }; + return Ticket; +}()); + +function parseAuthArgs(params) { + var userId = ''; + var accessToken = ''; + var cb = noop; + if (!Array.isArray(params)) { + Logger.write('Arguments passed to SendbirdDesk.authenticate() must be an array'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (params.length < 1) { + Logger.write('Too few arguments passed to SendbirdDesk.authenticate()'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (params.length > 3) { + Logger.write('Too many arguments passed to SendbirdDesk.authenticate()'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + userId = params[0]; + // connect(userId: string, token: string) + if (typeof params[1] === 'string') { + accessToken = params[1]; + } + // connect(userId: string, cb: () => void) + if (typeof params[1] === 'function') { + cb = params[1]; + } + // connect(userId: string, token: string, cb: () => void) + if (typeof params[2] === 'function') { + cb = params[2]; + } + return { + userId: userId, + accessToken: accessToken, + cb: cb, + }; +} +function validateAuthArgs(params) { + var userId = params.userId, accessToken = params.accessToken, cb = params.cb; + if (typeof userId !== 'string') { + Logger.write('UserId to SendbirdDesk.authenticate(userId) must be a string'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (typeof accessToken !== 'string') { + Logger.write('AccessToken to SendbirdDesk.authenticate(userId, accessToken) must be a string'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + if (typeof cb !== 'function') { + Logger.write('Callback to SendbirdDesk.authenticate(userId, accessToken, cb) must be a function'); + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } +} + +var _initialized = false; +/** SendbirdDesk SDK + */ +var SendbirdDesk = /** @class */ (function () { + function SendbirdDesk() { + } + Object.defineProperty(SendbirdDesk, "version", { + /** + * @static + * @since 1.0.0 + * @desc Get Desk SDK version. + */ + get: function () { + return Config.sdkVersion; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(SendbirdDesk, "Error", { + /** + * @static + * @since 1.0.0 + * @desc SendBirdDeskError class reference. + * @type {module:SendBirdDeskError} + */ + get: function () { + return SendbirdDeskError; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(SendbirdDesk, "Agent", { + /** + * @static + * @since 1.0.0 + * @desc Agent class reference. + * @type {module:Agent} + */ + get: function () { + return Agent; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(SendbirdDesk, "Ticket", { + /** + * @static + * @since 1.0.0 + * @desc Ticket class reference. + * @type {module:Ticket} + */ + get: function () { + return Ticket; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(SendbirdDesk, "Message", { + /** + * @static + * @since 1.0.0 + * @desc Message class reference. + * @type {module:Message} - BaseMessage in Sendbird Messaging SDK + */ + get: function () { + return Message; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(SendbirdDesk, "RelatedChannel", { + /** + * @static + * @since 1.0.14 + * @desc RelatedChannel class reference. + * @type {module:RelatedChannel} + */ + get: function () { + return RelatedChannel; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(SendbirdDesk, "UrlRegExp", { + /** + * @ignore + */ + get: function () { + return Message.UrlRegExp; + }, + enumerable: false, + configurable: true + }); + /** + * @static + * @since 1.0.1 + * @desc Initialize SDK. + */ + SendbirdDesk.init = function (sendbird, platform) { + Config.sendbird = sendbird; + if (platform) { + Config.platform = platform; + } + _initialized = true; + }; + /** + * @static + * @since 1.0.0 + * @desc Authenticate and connect to Desk server. + * @param {string} userId - User ID. + * @param {string=} accessToken - Access token(Optional). + * @param {function} callback - Function() => void. + */ + SendbirdDesk.authenticate = function () { + var params = []; + for (var _i = 0; _i < arguments.length; _i++) { + params[_i] = arguments[_i]; + } + var _a = parseAuthArgs(params), userId = _a.userId, accessToken = _a.accessToken, cb = _a.cb; + validateAuthArgs({ userId: userId, accessToken: accessToken, cb: cb }); + this._authenticate({ userId: userId, accessToken: accessToken, cb: cb }).finally(function () { + cb(); + }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + SendbirdDesk._authenticate = function (params) { + return __awaiter(this, void 0, void 0, function () { + var userId, accessToken; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + userId = params.userId, accessToken = params.accessToken; + if (!_initialized) return [3 /*break*/, 5]; + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4 /*yield*/, Auth.connect(userId, accessToken)]; + case 2: + _a.sent(); + return [3 /*break*/, 4]; + case 3: + _a.sent(); + SendbirdDeskError.throw(SendbirdDeskError.Type.ERROR_SENDBIRD_DESK_AUTH_FAILED); + return [3 /*break*/, 4]; + case 4: return [3 /*break*/, 6]; + case 5: + SendbirdDeskError.throw(SendbirdDeskError.Type.ERROR_SENDBIRD_DESK_INIT_MISSING); + _a.label = 6; + case 6: return [2 /*return*/]; + } + }); + }); + }; + /** + * @static + * @since 1.0.1 + * @desc Check if the channel belongs to Desk. + */ + SendbirdDesk.isDeskChannel = function (channel) { + return Ticket.isDeskCustomType(channel.customType); + }; + /** + * @ignore + */ + SendbirdDesk.setApiHost = function (host) { + Config.apiHost = host; + }; + /** + * @static + * @since 1.0.0 + * @desc Set SDK to debug mode which adds internal logging on desk event. + */ + SendbirdDesk.setDebugMode = function () { + Config.setDebugMode(); + }; + /** + * @static + * @since 1.0.8 + * @desc Set customer customFields(Must be defined in dashboard). + * @param {object} customFields - customFields object (key-value). + * @param {function} callback - Function(res: object, err: Error). + */ + SendbirdDesk.setCustomerCustomFields = function (customFields, cb) { + this._setCustomerCustomFields(customFields).then(function (res) { + cb(res, null); + }).catch(function (err) { + cb(null, err); + }); + }; + /** + * @ignore + * @private + * @since 1.1.0 + */ + SendbirdDesk._setCustomerCustomFields = function (customFields) { + return __awaiter(this, void 0, void 0, function () { + var formattedCustomFields, key, val, res, data; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (typeof customFields !== 'object' || customFields === null || Array.isArray(customFields)) { + throw SendbirdDeskError.create(SendbirdDeskError.Type.ERROR_INVALID_PARAMETER); + } + formattedCustomFields = {}; + for (key in customFields) { + val = customFields[key]; + if (typeof val === 'string') { + formattedCustomFields[key] = val; + } + else { + formattedCustomFields[key] = JSON.stringify(val); + } + } + return [4 /*yield*/, fetch("".concat(Config.apiHost, "/customers/custom_fields/"), { + method: 'PATCH', + headers: Auth.header, + body: JSON.stringify(__assign(__assign({}, Auth.postParam), { customFields: JSON.stringify(formattedCustomFields) })), + })]; + case 1: + res = _a.sent(); + return [4 /*yield*/, res.json()]; + case 2: + data = _a.sent(); + // handle errors + if (!res.ok) { + throw new Error(JSON.stringify(data)); + } + // handle success + return [2 /*return*/, this._resToCustomFields(data.customFields)]; + } + }); + }); + }; + SendbirdDesk._resToCustomFields = function (data) { + var e_1, _a; + var customFields = {}; + try { + for (var data_1 = __values(data), data_1_1 = data_1.next(); !data_1_1.done; data_1_1 = data_1.next()) { + var item = data_1_1.value; + customFields[item.key] = item.value; + } + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { + try { + if (data_1_1 && !data_1_1.done && (_a = data_1.return)) _a.call(data_1); + } + finally { if (e_1) throw e_1.error; } + } + return customFields; + }; + return SendbirdDesk; +}()); + +export { Agent, RelatedChannel, SendbirdDeskError, Ticket, SendbirdDesk as default }; diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..225c5db --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,833 @@ +// Generated by dts-bundle-generator v8.0.1 + +import { DeviceOsPlatform } from '@sendbird/chat'; +import { GroupChannel, SendbirdGroupChat } from '@sendbird/chat/groupChannel'; +import { BaseMessage } from '@sendbird/chat/message'; + +/** + * @module SendbirdDeskError + * @ignore + */ +export interface IErrorType { + code: number; + message: string; +} +/** + * An error class for Desk. + * @extends {Error} + * @since 1.0.0 + */ +export declare class SendbirdDeskError extends Error { + readonly code: number; + /** + * @since 1.0.0 + * @param {string} message - Error message. + * @param {number} code - Error code. + */ + constructor(message: string, code: number); + static get Type(): Record; + /** + * @static + * @since 1.0.5 + * @ignore + * @desc Create an error. + */ + static create(type?: IErrorType): Error; + /** + * @static + * @since 1.0.0 + * @ignore + * @desc Throw an error. + */ + static throw(type: IErrorType): void; +} +/** + * @module Agent + * @ignore + */ +export type AgentParamsLegacy = { + user: number; + displayName?: string; + photoThumbnailUrl: string; +}; +export type AgentParams = { + id: number; + displayName?: string; + sendbirdId: string; + photoThumbnailUrl?: string; +} | AgentParamsLegacy; +/** + * @since 1.0.0 + */ +export declare class Agent { + userId: number; + name: string; + profileUrl: string; + sendbirdId?: string; + /** + * @since 1.0.0 + * @private + * @desc Create an agent. + */ + constructor(params: AgentParams); + /** + * @since 1.0.0 + * @private + * @desc Parse JSON data and patch Agent object. + */ + fetchFromJSON(params: AgentParams): void; +} +/** + * @module RelatedChannel + * @ignore + */ +export interface RelatedChannelParams { + channel_url: string; + name: string; +} +/** + * @classdesc RelatedChannel + * @since 1.0.14 + */ +export declare class RelatedChannel { + channelUrl: string; + name: string; + /** + * @since 1.0.14 + * @private + * @desc Create a related channel + */ + constructor(params: RelatedChannelParams); + /** + * @since 1.0.14 + * @private + * @desc Parse JSON data and patch RelatedChannel object. + */ + fetchFromJSON(params: RelatedChannelParams): void; +} +export type OverloadProps = Pick; +export type OverloadUnionRecursive = TOverload extends (...args: infer TArgs) => infer TReturn ? TPartialOverload extends TOverload ? never : OverloadUnionRecursive TReturn) & OverloadProps> | ((...args: TArgs) => TReturn) : never; +export type OverloadUnion any> = Exclude never) & TOverload>, TOverload extends () => never ? never : () => never>; +/** + This is to help with overloading optional functions in TypeScript. + For example CreateTicketParams in src/model/ticketUtils/types.ts + createTicket(title.required, name.required, groupKey?.optional, customFields?.optional, ..., callback.required) + in this case, you need union of all possible overloads, and this type helps with that. + ie: [title, name, callback] | [title, name, groupKey, callback] | [title, name, groupKey, customFields, callback]... + Slack: https://sendbird.slack.com/archives/G01290GCDCN/p1689903742398759 + example: + ``` + type SbDeskAuth1 = (userId: string) => void; + type SbDeskAuth2 = (userId: string, cb: () => void) => void; + type SbDeskAuth3 = (userId: string, nickname: string, cb: () => void) => void; + type SbDeskAuth4 = (userId: string, nickname: string, profile: string, cb: () => void) => void; + + type SendbirdDeskAuthType = OverloadParameters< + SbDeskAuth1 + | SbDeskAuth2 + | SbDeskAuth3 + | SbDeskAuth4 + >; + ``` + */ +export type OverloadParameters any> = Parameters>; +declare const TicketPriorityMap: { + readonly URGENT: "URGENT"; + readonly HIGH: "HIGH"; + readonly MEDIUM: "MEDIUM"; + readonly LOW: "LOW"; +}; +export type TicketPriority = typeof TicketPriorityMap[keyof typeof TicketPriorityMap]; +export type Customer = { + id: number; + sendbirdId: string; + project: number; + createdAt: string; + displayName: string; + photoThumbnailUrl: string; +}; +declare const TicketStatusMap: { + readonly INITIALIZED: "INITIALIZED"; + readonly PROACTIVE: "PROACTIVE"; + readonly UNASSIGNED: "UNASSIGNED"; + readonly ASSIGNED: "ASSIGNED"; + readonly OPEN: "OPEN"; + readonly CLOSED: "CLOSED"; +}; +export type TicketStatus = typeof TicketStatusMap[keyof typeof TicketStatusMap]; +export interface TicketCreateParams { + title: string; + name: string; + groupKey?: string; + customFields?: object; + priority?: TicketPriority; + relatedChannelUrls?: string[]; + botKey?: string; +} +export interface TicketParams { + id: number; + channelUrl: string; + channelName: string; + status?: TicketStatus; + info?: string; + priority?: TicketPriority; + recentAssignment?: { + agent?: AgentParams; + }; + customer?: Customer; + customFields?: { + key: string; + value: any; + }[]; + group?: number; + relatedChannels?: string; + updatedAt?: number; +} +export interface RawTicketListParams { + offset?: number; + limit?: number; + order?: string; + channelUrl?: string; + customFieldFilter?: object; + group?: string; + status?: "ALL" | "OPEN" | "CLOSED"; +} +export interface TicketListFilter { + limit?: number; + customFieldFilter?: object; + group?: string; +} +export interface TicketListParams extends TicketListFilter { + status?: "ALL" | "OPEN" | "CLOSED"; +} +export type CloseTicketCb = (succes: TicketClass | null, error: Error | null) => void; +export type CloseTicketParams1 = () => void; +export type CloseTicketParams2 = (comment: string) => void; +export type CloseTicketParams3 = (cb: CloseTicketCb) => void; +export type CloseTicketParams4 = (comment: string, cb: CloseTicketCb) => void; +export type CloseTicketParams = OverloadParameters; +export type CloseTicketParamsMap = { + comment: string; + cb: CloseTicketCb; +}; +export type CreateTicketCb = (succes: TicketClass | null, error: Error | null) => void; +export type CreateTicketParams1 = (title: string, name: string, cb: CreateTicketCb) => void; +export type CreateTicketParams2 = (title: string, name: string, groupKey: string | null, cb: CreateTicketCb) => void; +export type CreateTicketParams3 = (title: string, name: string, groupKey: string | null, customFields: object | null, cb: CreateTicketCb) => void; +export type CreateTicketParams4 = (title: string, name: string, groupKey: string | null, customFields: object | null, priority: TicketPriority | null, cb: CreateTicketCb) => void; +export type CreateTicketParams5 = (title: string, name: string, groupKey: string | null, customFields: object | null, priority: TicketPriority | null, relatedChannelUrls: string[] | null, cb: CreateTicketCb) => void; +export type CreateTicketParams6 = (title: string, name: string, groupKey: string | null, customFields: object | null, priority: TicketPriority | null, relatedChannelUrls: string[] | null, botKey: string | null, cb: CreateTicketCb) => void; +export type CreateTicketParams = OverloadParameters; +export type CreateTicketArgsMap = { + title: string; + name: string; + groupKey?: string | null; + customFields?: object | null; + priority?: TicketPriority | null; + relatedChannelUrls?: string[] | null; + botKey?: string | null; + cb: CreateTicketCb; +}; +export type RefreshTicketCb = (ticket: TicketClass | null, error: Error | null) => void; +export type Reopencb = (ticket: TicketClass | null, error: Error | null) => void; +export type CancelTicketCb = (ticket: TicketClass | null, error: Error | null) => void; +export type CancelTicketParams1 = (cb: CancelTicketCb) => void; +export type CancelTicketParams2 = (groupKeyForTransfer: string, cb: CancelTicketCb) => void; +export type CancelTicketParams = OverloadParameters; +export type CancelTicketParamsMap = { + groupKeyForTransfer: string; + cb: CancelTicketCb; +}; +export type ConfirmEndOfChatCb = (ticket: TicketClass | null, error: Error | null) => void; +export type SelectQuestionCb = (ticket: TicketClass | null, error: Error | null) => void; +export type SetPriorityCb = (ticket: TicketClass | null, error: Error | null) => void; +export type SetRelatedChannelUrlsCb = (ticket: TicketClass | null, error: Error | null) => void; +export type SetCustomFieldsCb = (ticket: TicketClass | null, error: Error | null) => void; +export type GetOpenCountResponse = { + ACTIVE: number; + ASSIGNED: number; + CLOSED: number; + IDLE: number; + PENDING: number; + UNASSIGNED: number; + WORK_IN_PROGRESS: number; +}; +export type GetOpenCountCb = (data: GetOpenCountResponse | null, error: Error | null) => void; +export type GetUrlPreviewResponse = { + url: string; + title: string; + siteName: string; + description: string; + image: string; +}; +export type GetUrlPreviewCb = (data: GetUrlPreviewResponse | null, error: Error | null) => void; +export type GetListCb = (tickets: TicketClass[] | null, error: Error | null) => void; +export type GetAllTicketsCb = (tickets: TicketClass[] | null, error: Error | null) => void; +export type GetAllTicketsParams1 = (offset: number, filter: TicketListFilter, cb: GetAllTicketsCb) => void; +export type GetAllTicketsParams2 = (offset: number, cb: GetAllTicketsCb) => void; +export type GetAllTicketsParams = OverloadParameters; +export interface OpenedTicketClass extends TicketClass { + status: "OPEN" | "UNASSIGNED" | "ASSIGNED" | "PROACTIVE"; +} +export type GetOpenedListCb = (tickets: OpenedTicketClass[] | null, error: Error | null) => void; +export type GetOpenedListParams1 = (offset: number, filter: TicketListFilter, cb: GetOpenedListCb) => void; +export type GetOpenedListParams2 = (offset: number, cb: GetOpenedListCb) => void; +export type GetOpenedListParams = OverloadParameters; +export interface ClosedTicketClass extends TicketClass { + status: "CLOSED"; +} +export type GetClosedListCb = (tickets: ClosedTicketClass[] | null, error: Error | null) => void; +export type GetClosedListParams1 = (offset: number, filter: TicketListFilter, cb: GetClosedListCb) => void; +export type GetClosedListParams2 = (offset: number, cb: GetClosedListCb) => void; +export type GetClosedListParams = OverloadParameters; +export type GetByChannelUrlParamsMap = { + channelUrl: string; + cachingEnabled: boolean; + cb: GetByChannelUrlCb; +}; +export type GetByChannelUrlCb = (tickets: TicketClass | null, error: Error | null) => void; +export type GetByChannelUrlParams1 = (channelUrl: string, cachingEnabled: boolean, callback: GetByChannelUrlCb) => void; +export type GetByChannelUrlParams2 = (channelUrl: string, callback: GetByChannelUrlCb) => void; +export type GetByChannelUrlParams = OverloadParameters; +export type SubmitFeedbackCb = (ticket: TicketClass | null, error: Error | null) => void; +declare class TicketClass { + id: number; + title: string; + status: TicketStatus; + info: object | null; + priority: TicketPriority; + agent: Agent | null; + customer: Customer | null; + customFields: object; + group: number; + relatedChannels: RelatedChannel[]; + channel: GroupChannel; + channelUrl: string; + updatedAt: number; + static create(...params: CreateTicketParams): void; + static isDeskCustomType(customType: string): boolean; + static defaultLimit(): number; + static clearCache(): void; + static getOpenCount(cb: GetOpenCountCb): void; + static getUrlPreview(url: string, cb: GetUrlPreviewCb): void; + static getList(params: RawTicketListParams, cb: GetListCb): void; + static getAllTickets(...params: GetAllTicketsParams): void; + static getOpenedList(...params: GetOpenedListParams): void; + static getClosedList(...params: GetClosedListParams): void; + static getByChannelUrl(...params: GetByChannelUrlParams): void; + static submitFeedback(message: BaseMessage, score: number, comment: string, cb: SubmitFeedbackCb): void; + static confirmEndOfChat(message: BaseMessage, confirm: "yes" | "no", cb: ConfirmEndOfChatCb): void; + instanceSubmitFeedback(message: BaseMessage, score: number, comment: string, cb: SubmitFeedbackCb): void; + instanceConfirmEndOfChat(message: BaseMessage, confirm: "yes" | "no", cb: ConfirmEndOfChatCb): void; + close(...params: CloseTicketParams): void; + refresh(cb: RefreshTicketCb): void; + reopen(cb: Reopencb): void; + cancel(...params: CancelTicketParams): void; + fetchFromJSON(json: TicketParams): void; + selectQuestion(faqFileId: number, question: string, cb: SelectQuestionCb): void; + setPriority(priority: TicketPriority, cb: SetPriorityCb): void; + setRelatedChannelUrls(relatedChannelUrls: string[], cb: SetRelatedChannelUrlsCb): void; + setCustomFields(customFields: object, cb: SetCustomFieldsCb): void; +} +/** + * @since 1.0.0 + */ +export declare class Ticket implements TicketClass { + id: number; + title: string; + status: TicketStatus; + info: object | null; + priority: TicketPriority; + agent: Agent | null; + customer: Customer | null; + customFields: object; + group: number; + relatedChannels: RelatedChannel[]; + channel: GroupChannel; + channelUrl: string; + updatedAt: number; + /** + * @since 1.0.0 + * @private + * @desc Create a ticket. + */ + constructor(params: TicketParams); + /** + * @static + * @since 1.0.0 + * @desc Ticket status + * @property {string} INITIALIZED - ticket is created but not able to assign. + * @property {string} PROACTIVE - ticket is introduced as proactive ticket. + * @property {string} UNASSIGNED - ticket is activated and able to assign. + * @property {string} ASSIGNED - ticket is assigned by an agent. + * @property {string} OPEN - ticket is activated. + * @property {string} CLOSED - ticket is closed. + */ + static get Status(): { + readonly INITIALIZED: "INITIALIZED"; + readonly PROACTIVE: "PROACTIVE"; + readonly UNASSIGNED: "UNASSIGNED"; + readonly ASSIGNED: "ASSIGNED"; + readonly OPEN: "OPEN"; + readonly CLOSED: "CLOSED"; + }; + /** + * @since 1.0.0 + * @private + * @desc Parse JSON data and patch Ticket object. + */ + fetchFromJSON(params: TicketParams): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + _fetchChannel(): Promise; + /** + * @ignore + * @private + * @since 1.1.0 + */ + static _getTicketList(params: RawTicketListParams): Promise; + /** + * @since 1.0.0 + * @desc Refresh ticket info. + * @param {function(ticket:Ticket, err:Error)} cb - cb function. + */ + refresh(cb: RefreshTicketCb): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + _refresh(): Promise; + /** + * @since 1.0.6 + * @desc Reopen closed ticket. + * @param {function} cb - Function(res:Ticket, err:Error). + */ + reopen(cb: Reopencb): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + _reopen(): Promise; + /** + * @since 1.0.18 + * @desc Cancel the assignment and set it to open. + * @param {string} groupKeyForTransfer - Group key for transfer(optional) + * @param {function} cb - Function(res:Ticket, err:Error). + */ + cancel(...params: CancelTicketParams): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + _cancel(args: CancelTicketParamsMap): Promise; + /** + * @since 1.0.16 + * @desc Force close an assigned ticket. + * @param {string} comment - Comment for closing the ticket. + */ + close(...params: CloseTicketParams): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + _close(args: CloseTicketParamsMap): Promise; + /** + * @since 1.0.18 + * @desc Select a question. + * @param {number} faqFileId - FAQ file ID. + * @param {string} question - Question text. + * @param {function} callback - Function(res:object, err:Error). + */ + selectQuestion(faqFileId: number, question: string, cb: SelectQuestionCb): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + _selectQuestion(faqFileId: number, question: string): Promise; + /** + * @since 1.0.10 + * @desc Set ticket priority. + * @param {string} priority - priority. + * @param {function} callback - Function(res:Ticket, err:Error). + */ + setPriority(priority: TicketPriority, cb: SetPriorityCb): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + _setPriority(priority: TicketPriority): Promise; + /** + * @since 1.0.14 + * @desc Set ticket related channel URLs. + * @param {array} relatedChannelUrls - related channel URLs. + * @param {function} callback - Function(res:Ticket, err:Error). + */ + setRelatedChannelUrls(relatedChannelUrls: string[], cb: SetRelatedChannelUrlsCb): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + _setRelatedChannelUrls(relatedChannelUrls: string[]): Promise; + /** + * @since 1.0.10 + * @desc Set ticket customFields. + * @param {object} customFields - customFields object (key-value). + * @param {function} callback - Function(res:Ticket, err:Error). + */ + setCustomFields(customFields: object, cb: SetCustomFieldsCb): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + _setCustomFields(customFields: object): Promise; + /** + * @ignore + */ + static isDeskCustomType(customType: string): boolean; + /** + * @ignore + */ + static get defaultLimit(): number; + /** + * @static + * @since 1.0.0 + * @desc Clear cached ticket. Clear all if channelUrl is not specified. + */ + static clearCache(channelUrl?: string): void; + /** + * @static + * @since 1.0.0 + * @desc Create new ticket and returns the ticket within cb. + * @param {string} title - Ticket title. + * @param {string} name - User name. + * @param {string} groupKey - Agent group key (optional). + * @param {object} customFields - customField (optional). + * @param {string} priority - priority (optional). + * @param {array} relatedChannelUrls - related channel URLs (optional). + * @param {string} botKey - botKey (optional). + * @param {function} cb - Function(ticket:Ticket, err:Error). + */ + static create(...params: CreateTicketParams): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + static _create(args: CreateTicketArgsMap): Promise; + /** + * @static + * @since 1.0.0 + * @desc Get ticket count for each state: UNASSIGNED, ASSIGNED, CLOSED. + * @param {function} callback - Function(result:GetOpenCountResponse, err:Error). + */ + static getOpenCount(cb: GetOpenCountCb): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + static _getOpenCount(): Promise; + /** + * @static + * @since 1.0.22 + * @desc Get ticket from channel URL. Use caching for optimization. + * @param {string} channelUrl - channel URL. + * @param {boolean} _cachingEnabled - to get ticket from cache or not. + * @param {function} _callback - Function(ticket:Ticket, err:Error). + */ + /** + * @static + * @since 1.0.22 + * @desc Get ticket from channel URL. no caching by default. + * @param {string} channelUrl - channel URL. + * @param {function} _callback - Function(ticket:Ticket, err:Error). + */ + static getByChannelUrl(...params: GetByChannelUrlParams): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + static _getByChannelUrl(args: Omit): Promise; + /** + * @static + * @since 1.0.23 + * @desc Lists all tickets. + * @param {integer} filters.offset - list offset. + * @param {object} filters.customFieldFilter - customField filter. + * @param {string} filters.group - group key(to filter tickets by a team). + * @param {string} filters.status - status to get tickets. ('all', 'CLOSED', 'OPEN'). + * @param {function} callback - Function(list:Array, err:Error) + */ + static getList(params: RawTicketListParams, cb: GetListCb): void; + /** + * @static + * @since 1.0.0 + * @desc Load opened ticket list. + * @param {integer} offset - list offset. + * @param {object} customFieldFilter - customField filter. + * @param {function} callback - Function(list:Array, err:Error) + */ + static getOpenedList(...params: GetOpenedListParams): void; + /** + * @static + * @since 1.0.21 + * @desc Lists all tickets. + * @param {integer} offset - list offset. + * @param {object} customFieldFilter - customField filter. + * @param {function} callback - Function(list:Array, err:Error) + */ + static getAllTickets(...params: GetAllTicketsParams): void; + /** + * @static + * @since 1.0.0 + * @desc Load closed ticket list. + * @param {integer} offset - list offset. + * @param {object} customFieldFilter - customField filter. + * @param {function} callback - Function(list:Array, err:Error) + */ + static getClosedList(...params: GetClosedListParams): void; + /** + * @static + * @since 1.0.0 + * @desc Get URL preview info from URL. + * @param {string} url - URL to load preview metadata. + * @param {function} callback - Function(result:Object, err:Error). + */ + static getUrlPreview(url: string, cb: GetUrlPreviewCb): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + static _getUrlPreview(url: string): Promise; + /** + * @since 1.0.0 + * @desc Reply to confirm-end-of-chat request in yes or no. + */ + static confirmEndOfChat(message: BaseMessage, confirm: "yes" | "no", cb: ConfirmEndOfChatCb): void; + /** + * @since 1.0.0 + * @desc Reply to confirm-end-of-chat request in yes or no. + * This shouldnt be static, but it is for backwards compatibility + */ + instanceConfirmEndOfChat(message: BaseMessage, confirm: "yes" | "no", cb: ConfirmEndOfChatCb): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + static _confirmEndOfChat(message: BaseMessage, confirm: "yes" | "no"): Promise; + /** + * @since 1.0.8 + * @desc Submit feedback with a score and a comment. + */ + static submitFeedback(message: BaseMessage, score: number, comment: string | undefined, cb: SubmitFeedbackCb): void; + /** + * @since 1.0.8 + * @desc Submit feedback with a score and a comment. + */ + instanceSubmitFeedback(message: BaseMessage, score: number, comment: string | undefined, cb: SubmitFeedbackCb): void; + /** + * @ignore + * @private + * @since 1.1.0 + * This shouldnt be static, but it is for backwards compatibility + */ + static _submitFeedback(message: BaseMessage, score: number, comment?: string): Promise; +} +declare class Message { + /** + * @static + * @since 1.0.0 + * @desc message custom type. + * @property {string} RICH_MESSAGE - SENDBIRD_DESK_RICH_MESSAGE + * @property {string} ADMIN_MESSAGE - SENDBIRD_DESK_ADMIN_MESSAGE_CUSTOM_TYPE + */ + static get CustomType(): { + RICH_MESSAGE: string; + ADMIN_MESSAGE: string; + }; + /** + * @static + * @since 1.0.0 + * @desc message data type. + * @property {string} TICKET_INQUIRE_CLOSURE - SENDBIRD_DESK_INQUIRE_TICKET_CLOSURE + * @property {string} TICKET_ASSIGN - TICKET_ASSIGN + * @property {string} TICKET_TRANSFER - TICKET_TRANSFER + * @property {string} TICKET_CLOSE - TICKET_CLOSE + * @property {string} URL_PREVIEW - URL_PREVIEW + */ + static get DataType(): { + TICKET_INQUIRE_CLOSURE: string; + TICKET_ASSIGN: string; + TICKET_TRANSFER: string; + TICKET_CLOSE: string; + TICKET_FEEDBACK: string; + URL_PREVIEW: string; + }; + /** + * @static + * @since 1.0.0 + * @desc closure inquiry messsage state. + * @property {string} WAITING - WAITING + * @property {string} CONFIRMED - CONFIRMED + * @property {string} DECLINED - DECLINED + */ + static get ClosureState(): { + WAITING: string; + CONFIRMED: string; + DECLINED: string; + }; + /** + * @module Message + * @ignore + */ + /** + * @static + * @since 1.0.8 + * @desc closure inquiry messsage state. + * @property {string} WAITING - WAITING + * @property {string} CONFIRMED - CONFIRMED + */ + static get FeedbackState(): { + WAITING: string; + CONFIRMED: string; + }; + /** + * @ignore + */ + static get UrlRegExp(): RegExp; +} +export type SbDeskAuthUserId = (userId: string) => void; +export type SbDeskAuthUserIdCb = (userId: string, cb: () => void) => void; +export type SbDeskAuthUserIdToken = (userId: string, accessToken: string) => void; +export type SbDeskAuthUserIdTokenCb = (userId: string, accessToken: string, cb?: () => void) => void; +export type SendbirdDeskAuthType = OverloadParameters; +export type SendbirdAuthParamsMap = { + userId: string; + accessToken: string; + cb: () => void; +}; +export type CusotmerResponseRaw = { + id: number; + key: string; + value: string; +}; +export type CustomerResponseFormatted = { + [key: string]: string; +}; +/** SendbirdDesk SDK + */ +export default class SendbirdDesk { + /** + * @static + * @since 1.0.0 + * @desc Get Desk SDK version. + */ + static get version(): string; + /** + * @static + * @since 1.0.0 + * @desc SendBirdDeskError class reference. + * @type {module:SendBirdDeskError} + */ + static get Error(): typeof SendbirdDeskError; + /** + * @static + * @since 1.0.0 + * @desc Agent class reference. + * @type {module:Agent} + */ + static get Agent(): typeof Agent; + /** + * @static + * @since 1.0.0 + * @desc Ticket class reference. + * @type {module:Ticket} + */ + static get Ticket(): typeof Ticket; + /** + * @static + * @since 1.0.0 + * @desc Message class reference. + * @type {module:Message} - BaseMessage in Sendbird Messaging SDK + */ + static get Message(): typeof Message; + /** + * @static + * @since 1.0.14 + * @desc RelatedChannel class reference. + * @type {module:RelatedChannel} + */ + static get RelatedChannel(): typeof RelatedChannel; + /** + * @ignore + */ + static get UrlRegExp(): RegExp; + /** + * @static + * @since 1.0.1 + * @desc Initialize SDK. + */ + static init(sendbird: SendbirdGroupChat, platform?: DeviceOsPlatform): void; + /** + * @static + * @since 1.0.0 + * @desc Authenticate and connect to Desk server. + * @param {string} userId - User ID. + * @param {string=} accessToken - Access token(Optional). + * @param {function} callback - Function() => void. + */ + static authenticate(...params: SendbirdDeskAuthType): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + static _authenticate(params: SendbirdAuthParamsMap): Promise; + /** + * @static + * @since 1.0.1 + * @desc Check if the channel belongs to Desk. + */ + static isDeskChannel(channel: GroupChannel): boolean; + /** + * @ignore + */ + static setApiHost(host: string): void; + /** + * @static + * @since 1.0.0 + * @desc Set SDK to debug mode which adds internal logging on desk event. + */ + static setDebugMode(): void; + /** + * @static + * @since 1.0.8 + * @desc Set customer customFields(Must be defined in dashboard). + * @param {object} customFields - customFields object (key-value). + * @param {function} callback - Function(res: object, err: Error). + */ + static setCustomerCustomFields(customFields: T, cb: (res: Partial | null, err: Error | null) => void): void; + /** + * @ignore + * @private + * @since 1.1.0 + */ + static _setCustomerCustomFields(customFields: T): Promise>; + static _resToCustomFields(data: CusotmerResponseRaw[]): CustomerResponseFormatted; +} + +export {}; diff --git a/package.json b/package.json index 53e514d..10e7b66 100644 --- a/package.json +++ b/package.json @@ -1,30 +1,68 @@ { "name": "sendbird-desk", - "version": "1.0.23", - "description": "SendBird Desk SDK Integration Guide for JavaScript =========== SendBird Desk is a chat customer service platform built on SendBird SDK and API.", - "main": "SendBird.Desk.min.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "version": "1.1.0", + "description": "SendBird Desk SDK for JavaScript", + "author": "SendBird", + "license": "see LICENSE.md", + "keywords": [], + "files": [ + "dist/**/*", + "LICENSE.md", + "CHANGELOG.md", + "CHANGELOG_old.md", + "README.md" + ], + "devEngines": { + "node": "18.x" }, - "repository": { - "type": "git", - "url": "git+https://github.com/sendbird/SendBird-Desk-SDK-JavaScript.git" + "main": "dist/esm/index.js", + "types": "dist/index.d.ts", + "scripts": { + "clean-dist": "rm -rf dist", + "dts": "dts-bundle-generator -o dist/index.d.ts ./src/app.ts", + "build": "npm run clean-dist; rollup --config rollup.config.js; npm run dts; npm run docs", + "docs": "typedoc --tsconfig ./tsconfig.rollup.json --options ./typedoc.config.js", + "format": "prettier ./src/**/*.ts --write", + "format:check": "prettier ./src/**/*.ts --check", + "lint": "eslint -c .eslintrc.js 'src/**'", + "prepare": "husky install", + "test": "jest --config test/jest.config.js" }, - "keywords": [ - "sendbird", - "sendbird.com", - "messaging", - "chat", - "js" - ], - "author": "SendBird ", - "license": "SEE LICENSE IN LICENSE.md", - "bugs": { - "url": "https://github.com/sendbird/SendBird-Desk-SDK-JavaScript/issues" + "devDependencies": { + "@react-native-async-storage/async-storage": "^1.19.0", + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^13.1.3", + "@types/jest": "^29.5.3", + "@types/node": "^14.14.34", + "@typescript-eslint/eslint-plugin": "^5.61.0", + "@typescript-eslint/parser": "^5.61.0", + "dotenv": "^16.3.1", + "dts-bundle-generator": "^8.0.1", + "eslint": "^8.44.0", + "husky": "^8.0.0", + "jest": "^29.6.1", + "jest-environment-jsdom": "^29.6.1", + "jsdom": "^22.1.0", + "prettier": "^1.19.1", + "rollup": "^2.47.0", + "rollup-plugin-cleanup": "^3.2.1", + "rollup-plugin-typescript2": "^0.35.0", + "ts-jest": "^29.1.1", + "ts-node": "^10.7.0", + "tslib": "^2.3.1", + "typedoc": "^0.24.8", + "typedoc-plugin-missing-exports": "^2.0.1", + "typescript": "^5.1.6", + "whatwg-fetch": "^3.6.2" }, - "homepage": "https://sendbird.com", "dependencies": { - "sendbird": "^3.0.117" + "@sendbird/chat": "^4.9.6", + "core-js": "^3.8.0" }, - "typings": "SendBird.Desk.d.ts" -} \ No newline at end of file + "sendbirdBundler": { + "outDir": "dist", + "entries": { + "index": "src/app.ts" + } + } +}