# Custom App

Custom App extends the capabilities of the Desk by allowing our clients to embed their own apps in a dedicated panel.

## **General guidelines**

A complete breakdown of our guidelines for Custom Apps can be found in [our knowledge base](https://help.iadvize.com/hc/en-gb/articles/4404351307026-Conversation-Panel-Apps-Guidelines).

{% hint style="info" %}
You will also find a complete [step-by-step tutorial to create a Custom App here](https://docs.iadvize.dev/use-cases/agent-workspace/custom-app-example-and-step-by-step-tutorial).
{% endhint %}

## **App development**

The apps that can be embedded in the Desk are web apps coded in any web compliant technology - static web page, dynamic web pages such as php or jsp generated, single page applications written in Angular or React, etc...

These apps will be embedded in an `iframe` element in the Desk, and the web server serving these apps must allow them to run in an iframe on the iAdvize domains: [https://sd.iadvize.com](https://sd.iadvize.com/) or [https://ha.iadvize.com](https://ha.iadvize.com/). This is done by configuring the `X-FRAME-OPTIONS` header.

The app can communicate with the desk by using a library provided by iAdvize.

Please note that one iframe is created per conversation in order to keep a context for an app for each conversation. It is recommended to keep the app very lightweight and avoid heavy processing or streaming updates.

## **Library quick start**

To use the library an app must include a javascript bundle in the html with the following code.

```html
<script src="https://static.iadvize.com/conversation-panel-app-lib/2.9.0/idzcpa.umd.production.min.js"></script>
```

Then in the javascript code of the app the library can be used as follows.

```javascript
window.idzCpa.init().then(client => {
    client.insertTextInComposeBox('Hello world!');
});
```

⚠️ CPAs available on the iAdvize iOS and Android apps must use the version 2.0.3 or greater.

### **API reference**

#### **idzCpa**

`idzCpa` is a global variable used as the entry point of the CPA library.

#### **init**

```javascript
function init(): Promise
```

The client is obtained via the `idzCpa.init` function that returns a Promise.

```javascript
const clientPromise = idzCpa.init();
clientPromise.then(client => { / do something /});
```

#### **Client context**

```typescript
client.context // => Context

type Context = {
    conversationId: string;
    projectId: string;
    channel: Channel;
    language: string;
}

// list of available channels:
enum Channel {
  AppleBusinessChat = 'APPLE_BUSINESS_CHAT',
  Call = 'CALL',
  Chat = 'CHAT',
  Facebook = 'FACEBOOK',
  FacebookBusinessOnMessenger = 'FACEBOOK_BUSINESS_ON_MESSENGER',
  MobileApp = 'MOBILE_APP',
  Sms = 'SMS',
  Video = 'VIDEO',
  Whatsapp = 'WHATSAPP',
}
```

The `context` property on the client returns the conversation context: conversation ID and project ID.

```
client.context.conversationId // => '5701a92f-a8e3-49ad-81dc-ac801171f799'
client.context.projectId // => '3103'
client.context.channel // => 'CHAT'
client.context.language // => 'fr'
```

#### **List of commands available**

| Command                                          | Description                                                                                         |
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| `insertTextInComposeBox`                         | Allows the CPA to send some text to the active thread compose zone.                                 |
| `pushCardInConversationThread`                   | Allows the CPA to send a card to the active conversation thread.                                    |
| `pushCardBundleInConversationThread`             | Allows the CPA to send a card carousel to the active conversation thread.                           |
| `pushApplePayPaymentRequestInConversationThread` | Allows the CPA to send an Apple Pay Payment request in the conversation thread.                     |
| `getJWT`                                         | Allows the CPA to get a JWT token signed with the secret token defined in the connector of the CPA. |

#### **How to use each command**

**`insertTextInComposeBox`**

Allows the CPA to send some text to the active thread compose zone.

*Signature*

```typescript
function insertTextInComposeBox(value: string): void
```

⚠️ value field is required

*Example*

```javascript
client.insertTextInComposeBox('Hello world!');
```

*Result*

![insertTextInComposeBox](https://raw.githubusercontent.com/iadvize/documentation/master/docs/assets/images/cpa-insertTextInComposeBox.png)

**`pushCardInConversationThread`**

Allows the CPA to send a card to the active conversation thread.

*Signature*

```typescript
type Action = {
    type: 'LINK';
    title: string;
    url: string;
}

type Card = {
    title?: string;
    text?: string;
    actions: Action[],
    image?: {
        url: string;
        description: string;
    }
}

function pushCardInConversationThread(value: Card): void
```

⚠️ *actions* field is required and must contain at least one action of LINK type ⚠️ *title*, *text* and *image* fields are optional

*Example*

```typescript
const card: Card = {
    title: "Card 1 title",
    text: "Card 1 description",
    actions: [
        {
          type: 'LINK',
          title: "Click here",
          url: "https://..."
        },
    ],
    image: {
        url: "https://...",
        description: "Image description add to alt img attribute",
    }
}

client.pushCardInConversationThread(card)
```

*Result*

![pushCardInConversationThread](https://raw.githubusercontent.com/iadvize/documentation/master/docs/assets/images/cpa-pushCardInConversationThread.png)

**`pushCardBundleInConversationThread`**

Allows the CPA to send a card carousel to the active conversation thread.

*Signature*

```typescript
type Carousel = {
    title?: string;
    cards: Card[]; // See pushCardInConversationThread command for more information of Card type
}

function pushCardBundleInConversationThread(value: Carousel): void
```

⚠️ *title* field of Carousel is optional ⚠️ *cards* field of Carousel is required

*Example*

```typescript
const card1: Card = {
    title: "Card 1 title",
    text: "Card 1 description",
    actions: [
        {
          type: "LINK",
          title: "Click here",
          url: "https://...",
        }
    ],
    image: {
        url: "https://...",
        description: "Image description add to alt img attribute"
    }
}

const card2: Card = {
    title: "Card 2 title",
    text: "Card 2 description",
    actions: [
        {
          type: "LINK",
          title: "Click here",
          url: "https://...",
        }
    ],
    image: {
        url: "https://...",
        description: "Image description add to alt img attribute"
    }
}

const carousel: Carousel = { cards: [ card1, card2 ] }

client.pushCardBundleInConversationThread(carousel)
```

*Result*

![pushCardBundleInConversationThread](https://raw.githubusercontent.com/iadvize/documentation/master/docs/assets/images/cpa-pushCardBundleInConversationThread.png)

**`getJWT`**

* Allows the CPA to get a secured JWT token. This JWT is signed with the secret token defined in the connector of the CPA.

*Signature*

```typescript
function getJWT(): Promise
```

*Example*

```typescript
// use jwtToken on CPA for secure request
client.getJWT().then((jwtToken: string) => { / use JWT token / })
```

**`pushApplePayPaymentRequestInConversationThread`**

* Allows the CPA to send an Apple Pay Payment request to the active conversation thread.
* The CPA can only send an Apple Pay Payment request to a conversation from an Apple channel.

*Signature*

```typescript
type ApplePayPaymentRequestType = {
    requestIdentifier: UUID;
    payment: ApplePayPaymentRequest;
    receivedMessage: ApplePayReceivedMessage;
}

// Detail for payment field type
type ApplePayPaymentRequest = {
  currencyCode: string;
  lineItems: PaymentItem[];
  requiredBillingContactFields: ApplePayContactField[];
  requiredShippingContactFields: ApplePayContactField[];
  shippingMethods: ShippingMethod[];
  total: PaymentItem;
}

type PaymentItem = {
  amount: string;
  label: string;
  type: ApplePayLineItemType;
};

enum ApplePayLineItemType {
  final,
  pending,
}

type ShippingMethod = {
  amount: string;
  detail: string;
  identifier: string;
  label: string;
};

enum ApplePayContactField {
  email = 'email',
  name = 'name',
  phone = 'phone',
  postalAddress = 'postalAddress',
  phoneticName = 'phoneticName',
}

// type for receivedMessage field
type ApplePayReceivedMessage = {
  type: 'CARD';
  data: CardType;
}

type CardType = {
  title?: string;
  text?: string;
  image?: CardImage;
  actions: LinkAction[];
};

type CardImage = {
  url: string;
  description: string;
};

type LinkAction = {
  type: 'LINK';
  title: string;
  url: string;
};

// Error
type ActionError = {
    message: string;
    details?: string[];
}

function pushApplePayPaymentRequestInConversationThread(applePayPaymentRequest: ApplePayPaymentRequestType): Promise
```

**Note**

If the request of payment is not sent, you may have an error in your request payload. To help you fix your payload, you can catch the error Promise: an ActionError is available with more details on what's happening.

More details about the `ApplePayPaymentRequest` type are provided in the [official Apple developer documentation.](https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentrequest)

*Example*

```typescript
const applePayPaymentRequest: ApplePayPaymentRequestType = {
    requestIdentifier: "83f86edb-XXXXX", // UUID
    payment: {
        currencyCode: "USD",
        lineItems: [
            {
                amount: "45",
                label: "Earpods",
                type: "final"
            },
            {
                amount: "955",
                label: "iPhone 12 mini",
                type: "final"
            }
        ],
        requiredBillingContactFields: ["email"],
        requiredShippingContactFields: ["email"],
        shippingMethods: [
            {
                amount: "10",
                detail: "Available within an hour",
                identifier: "in_store_pickup",
                label: "In-StorePickup"
            }
        ],
        total: {
            amount: "1000",
            label: "TOTAL",
            type: "final"
        }
    },
    receivedMessage: {
        type: "CARD",
        data: {
            title: "Please check this payment request",
            text: "Check this payment request and choose your shipping method",
            actions: [],
            style: "icon"
        }
    }
}

client.pushApplePayPaymentRequestInConversationThread(applePayPaymentRequest)
    .then(() => /* success apple pay payment request */)
    .catch((error: ActionError) =>
        /* error.message -> Error on command request */
        /* error.details (if exists) -> More details about the error if it exists */
    )
```

*Result*

* *Message for an operator in the conversation thread when the CPA pushes an Apple Pay Payment Request:*

![applePaymentRequest](https://raw.githubusercontent.com/iadvize/documentation/master/docs/assets/images/cpa-apple-payment-request.png)

* *Success payment:*

![applePaymentSuccess](https://raw.githubusercontent.com/iadvize/documentation/master/docs/assets/images/cpa-apple-payment-success.png)

* *Failed payment:*

![applePaymentFailed](https://raw.githubusercontent.com/iadvize/documentation/master/docs/assets/images/cpa-apple-payment-failed.png)

### **Style sheet**

The library also provides a standalone stylesheet with CSS variables built-in to fit iAdvize's design guidelines.

An app can include it either in its HTML:

```html
<link rel="stylesheet" src="https://static.iadvize.com/conversation-panel-app-lib/2.9.0/idzcpa.base.css">
```

Or as a top-level import inside a preprocessed-stylesheet:

```css
@import 'https://static.iadvize.com/conversation-panel-app-lib/2.9.0/idzcpa.base.css';
```

A complete description of the provided variables can be found in [our knowledge base](https://help.iadvize.com/hc/en-gb/articles/4404351307026-Conversation-Panel-Apps-Guidelines#5-how-to-easily-style-your-custom-app-for-a-consistent-user-interface-integration-in-the-desk).

*Library Change log*

| Version | Description                                                                                                                                                                                                                     |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1.0.0   | Initial version with support for inserting text in the compose box via `insertTextInComposeBox`.                                                                                                                                |
| 1.2.0   | Add Conversation Panel App style sheet.                                                                                                                                                                                         |
| 2.0.3   | Add mobile apps support - from this version apps hosted in the iAdvize iOS and Android apps can use this library.                                                                                                               |
| 2.1.0   | Return the conversation context in the client.                                                                                                                                                                                  |
| 2.3.1   | Support for inserting image type card and carousel of image type cards in the conversation thread via `insertCardInConversationThread` and `insertCarouselInConversationThread`                                                 |
| 2.4.0   | Replace command name and signature of each command: `insertCardInConversationThread` is replaced by `pushCardInConversationThread` and `insertCarouselInConversationThread` is replaced by `pushCardBundleInConversationThread` |
| 2.6.0   | Add new action `getJWT` to get a secured JWT token                                                                                                                                                                              |
| 2.7.0   | Add new command `pushApplePayPaymentRequestInConversationThread` to send Apple Pay Payement request in the conversation thread                                                                                                  |
| 2.9.0   | Add channel on init context                                                                                                                                                                                                     |

## **Configuration**

Under the Plugins section create a Conversation Panel App and then edit the following fields:

### **App name**

This is the name that will show in the toolbar button that starts your app. The name must be provided as a json object that contains a `default` name and then names for each language the app needs to support.

For instance `{"default": "Orders", "EN": "Orders", "FR": "Commandes"}`.

### **Icon name**

The icon name refers to a set of predefined icons provided by iAdvize that will appear in the button that starts the app.

A live preview of each icon can be found [here](https://front.storybook.iadvize.net/icons/master/iframe.html?id=special-icons-for-conversation-panel-apps--page\&viewMode=story)

Here is the list of available options by domain - the name must be entered in upper case.

<pre class="language-markdown"><code class="lang-markdown"><strong>#Coupon
</strong>COUPON, DOLLAR, PERCENTAGE, TICKETS

#Qualify lead
STAMP

#Segmentation
TARGET, CARD

#Delivery
DELIVERY, PACKAGE
#Orders
ORDER, BOXES

#Stock
STOCK, WAREHOUSE

#Knowledge base
RECOMMENDATION, FILES, FOLDERS, SEARCH

#User account
PROFILE, TARGETING, PROFILECARDS

#Invoicing
INVOICING

#Payment
PAYMENT

#Shopping cart
SHOPPINGCART, SEARCHSHOPPINGCART

#Product availability
BAGQUESTION, BAGSEARCH, PACKAGESEARCH

#Stores
STORE

#Location
LOCATION, POSITION, LOCATIONPIN

#Assistance
TOOLS, TOOLING, HELP

#Note
NOTE

#Booking
BOOKING

#Tags
TAG

#Hotels &#x26; Services
HOTELS, HOTELOFFER, SERVICES

#Generic/Random
NOTEBOOK, DIAMOND, BOUSSOLE, SHIRT, GEAR, FRAME45, TARGETING
</code></pre>

<figure><img src="https://76519009-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FC3dwvuSeenw12Wd9Vfpf%2Fuploads%2Fgit-blob-875051fcfffd74580e828fb1fbcb75f6cde24152%2Fcahier%20des%20charges.png?alt=media" alt=""><figcaption></figcaption></figure>

### **iFrame URL**

This field provides the URL that the Desk will use to start your app. It must be a public URL that can be targeted by the browser that runs the desk and available via https.

For instance: `https://mycompany.com/ordersapp`

### **Enable for operators**

Check this checkbox to make the app available for operators.

### **Enable for experts**

Check this checkbox to make the app available for ibbü experts.

### **Use authentication**

Check this checkbox if your app requires a proof that the CPA is loaded within the desk or if you want the ID of the operator. You will receive a JWT in the url parameters that is signed with the secret token defined in the connector of the CPA. For instance a URL with a JWT will look like this:

<https://app.iadvize.com/order.html?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9>...

The JWT can be decoded and validated by standard JWT libraries, for instance the jsonwebtoken library from auth0 <https://github.com/auth0/node-jsonwebtoken>

The code to decode a JWT looks like this:

```typescript
const inputToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...";
const secrettoken = '97db6bc85144af406a0e0040ad6f354b';
try {
const verifiedToken = jwt.verify(    inputToken,     secretToken,     { algorithms: ['HS256'] });// do something with verifiedToken// example token// {//     iss: 'iadvize',//     sub: '270586',//     aud: 'https://app.iadvize.com/order.html',//     exp: 1644502986,//     iat: 1644502926,//     email: 'operator@company.com'// }
} catch(err) {
// handle the error by returning an unauthorised response
}
```

The JWT contains the following properties:

* `iss` the issuer is always `iadvize`
* `sub` the subject, it is the operator ID
* `aud` the audience is the url of the targeted CPA
* `iat` the issue time, unix timestamp in number of seconds
* `exp` the expiry time, unix timestamp in number of seconds
* `email` the operator email address

{% hint style="info" %}
Here is an [exemple of implementation](https://docs.iadvize.dev/use-cases/agent-workspace/custom-app-example-and-step-by-step-tutorial/jwt) to get the JWT and use a middleware to test it.
{% endhint %}
