
# Introduction

Chat.io Customer JS SDK is a set of tools to build a custom chat widget. It allows you to manage multiple chats via chat.io as a
customer using JavaScript.


## Is it for me?

If you need to customize the chat.io widget, using chat.io Customer JS SDK is
one of the options to do this. If you need a fully custom solution and you feel
brave, dive into chat.io Customer JS SDK: we provide [methods](#methods) and
[events](#events) for deep integration with the chat.io environment.

Keep in mind, however, that interacting with this API requires **some
development skills**.

## About chat.io Customer JS SDK

We provide an asynchronous API, where most methods interacting with a server
return **promises**. To get the promise's fulfillment result, subscribe your handler to
the promise's
[`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
method. You can also subscribe to the emitted events with `on` and `off` methods.

<div class="callout type-info">Not familiar with promises? Read <a href="https://developer.mozilla.org/pl/docs/Web/JavaScript/Reference/Global_Objects/Promise">this article</a> to learn more.</div>

We authenticate your sessions by using
[chat.io-customer-auth package](https://www.npmjs.com/package/@livechat/chat.io-customer-auth)
and expose the created `auth` object on the returned SDK's instance. In general,
you don't have to worry about it nor use the exposed object, but if you need to
get the authentication token you can get it through the SDK like this:

```js
customerSDK.auth.getToken().then(token => console.log(token))
```

## Examples

We have prepared a sample chat widget implementation to present the features of chat.io Customer JS SDK:

* [Sample chat.io widget at CodeSandbox](https://codesandbox.io/s/rm3prxw88n)

# How to start

This tutorial will help you get started with using chat.io Customer JS SDK.

## Create an application

First, you need to create an application in the
[Developers Console](https://console.chat.io/) (select the _Web app (frontend,
eg. JavaScript)_ type).

## Install Customer JS SDK

You can use chat.io Customer JS SDK in two different ways:

### Using npm

`npm install --save @livechat/chat.io-customer-sdk`

Now, you can import SDK in your code:

`import chatIoCustomerSDK from '@livechat/chat.io-customer-sdk'`

or with a node-style `require` call:

`const chatIoCustomerSDK = require('@livechat/chat.io-customer-sdk')`

### Using script tag - UMD module hosted on unpkg's CDN

`<script
src="https://unpkg.com/@livechat/chat.io-customer-sdk@0.8.0/dist/chat.io-customer-sdk.min.js"></script>`

If you just want to look around and play with the SDK, check out our
[sample chat widget implementation](https://codesandbox.io/s/rm3prxw88n).

<div class="callout type-warning">For the time being you need to register your application in the <a href="https://console.chat.io/" target="_blank">Developers Console</a>
as a "Web app (frontend, eg. JavaScript)" type. Then, you have to pass the configured <code>redirectUri</code> to the <code>init</code>, along with the regular required properties (<code>license</code> and <code>clientId</code>).</div>

## Use the API

Now run the `init` function with the configuration, replacing `LICENSE_NUMBER`
with your chat.io license number. The function will return the customerSDK
instance:

```js
const customerSDK = CustomerSDK.init({
  license: LICENSE_NUMBER,
  clientId: CLIENT_ID,
})
```

With `customerSDK`, you can attach [events](#events):

```js
customerSDK.on('new_event', newEvent => {
  console.log(newEvent)
})
```

or execute [methods](#methods):

```js
const chatId = 'OU0V0P0OWT'
customerSDK
  .sendMessage(chatId, {
    text: 'Hi!',
  })
  .then(response => {
    console.log(response)
  })
  .catch(error => {
    console.log(error)
  })
```

### Using the API in React Native

If you want to use chat.io Customer SDK in React Native, keep in mind that we
use cookies to authenticate your sessions, so we need some sort of browser
environment for that. We've prepared a special wrapper for you to use in React
Native, which opens a WebView component to get an authentication token. All you
have to do is to import it from our authentication package (no need to install
it - the SDK depends on it, so you have it already) and mount it in your React
Native application:

```js
import { AuthWebView } from '@livechat/chat.io-customer-auth'
import { init } from '@livechat/chat.io-customer-sdk'

export default class App extends React.Component {
  componentDidMount() {
    const customerSDK = init({
      license: LICENSE_NUMBER,
      clientId: CLIENT_ID,
      redirectUri: REDIRECT_URI,
    })
    // you can start using customerSDK from now
  }

  render() {
    return (
      <View>
        <AuthWebView />
      </View>
    )
  }
}
```

# Key Concepts

The chat.io system is based on four basic types of entities - users, chats, events and
threads.

* chats consist of threads and threads consist of events
* threads are parts of chats,
* users can add events to chats, which then are automatically added to threads
* users can participate in many chats at the same time

Threads are a vital part of chat.io architecture. They provide
continuous chat experience (i.e. they never end and you can always add to
them) and they group events in smaller logical chunks, e.g. for reporting and
caching purposes. However, threads tend to complicate handling
various operations like loading more history events. The good part is that you don't
have to worry about them most of the time and this SDK is doing the heavy lifting
behind the scenes for you. You will get notified about threads'
metadata only if you explicitly ask for it - most SDK methods expect only chat
IDs.

## User

```js
{
	id: 'ed9d4095-45d6-428d-5093-f8ec7f1f81b9',
	type: 'agent',
	name: 'Jane Doe',
	email: 'jane.doe@chat.io',
	avatar: 'https://chatio-static.com/assets/images/chatio_logo.ae4271fe1a0a2db838dcf075388ee844.png',
}
```

## Chat

```js
{
	id: 'OU0V0P0OWT',
	users: [{
        id: 'ed9d4095-45d6-428d-5093-f8ec7f1f81b9',
        lastSeenTimestamp: 1503062591000, // might be null
    }],
	threads: ['OU0V0U3IMN'],
}
```

## Event

```js
{
	type: 'message',
	text: 'hi!',
    author: 'ed9d4095-45d6-428d-5093-f8ec7f1f81b9', // assigned by server
	id: 'OU0V0U3IMN_1', // assigned by server
    timestamp: 1503062591000, // assigned by server
    customnId: '814.3316641404942', // optional
    thread: 'OU0V4R0OXP',
}
```

## Threads

```js
{
	id: 'OU0V0U3IMN',
	active: true,
	order: 3,
	users: ['ed9d4095-45d6-428d-5093-f8ec7f1f81b9'],
	events: [ /* events */ ],
}
```

## Methods

### closeThread

```js
customerSDK
  .closeThread('ON0X0R0L67')
  .then(response => {
    console.log(response)
  })
  .catch(error => {
    console.log(error)
  })
```

Arguments:

| arguments | type   | description                                 |
| --------- | ------ | ------------------------------------------- |
| chat      | string | Chat's id in which thread should get closed |

Returned value:

| shape   | type    |
| ------- | ------- |
| success | boolean |

### destroy

This method clears any held resources, removes all listeners and
disconnects from the network. After using this method you won't be able to use
the destroyed SDK's instance.

```js
customerSDK.destroy()
```

### disconnect

```js
customerSDK.disconnect()
```

### getChatHistory

This method facilitates loading more history events. You need to
get access to the `history` object for certain chat by calling this method. The
returned `history` object has only one method, `next`, which gives you a promise
of `{ done, value }` pair.

* `done` - indicates if there is anything more to load
* `value` - it's an array of loaded events

You can keep calling `history.next()` multiple times to load more and more
history events (useful for infinite scroll feature). Keep in mind, though,
that you generally shouldn't call `next` while the history is being loaded - we
queue those requests so the previous one must resolve before we proceed with the
next one.

Such structure like our `history` object is called an **async iterator**.

```js
let wholeChatHistoryLoaded = false

const history = customerSDK.getChatHistory('OU0V0P0OWT')

history.next().then(result => {
  if (result.done) {
    wholeChatHistoryLoaded = true
  }

  const events = result.value
  console.log(events)
})
```

Arguments:

| arguments | type   | description                                           |
| --------- | ------ | ----------------------------------------------------- |
| chat      | string | Chat's id for which history object should be returned |

### getChatsSummary

```js
customerSDK
  .getChatsSummary({
    offset: 0,
    limit: 10
  })
  .then(({ chatsSummary, totalChats }) => {
    console.log(chatsSummary);
    console.log(totalChats);
  })
  .catch(error => {
    console.log(error);
  });
```

Arguments:

| arguments  | shape  | type   | default | max | description |
| ---------- | ------ | ------ | ------- | --- | ----------- |
| pagination |        |        |         |     |             |
|            | offset | number | 0       |     |             |
|            | limit  | number | 10      | 25  |             |

Returned value:

| shape        | type     | shape              | type     | description                                        |
| ------------ | -------- | ------------------ | -------- | -------------------------------------------------- |
| chatsSummary | object[] |                    |          |                                                    |
|              |          | id                 | string   | Chat's id                                          |
|              |          | users              | string[] | Users' ids                                         |
|              |          | lastEvent          | object   | Event                                              |
|              |          | lastEventsPerType  | object   | Map from event types to event objects              |
|              |          | lastSeenTimestamps | object   | Map from Users' ids to optional lastSeenTimestamps |
| totalChats   | number   |                    |          |                                                    |

### getChatThreads

In most cases you do not need to use this method directly. If you want to load
more events, consider using [`getChatHistory`](#getchathistory).

```js
const threads = ["OS0C0W0Z1B", "OS0I0M0J0G", "OT01080705", "OT0E02082U", "OT0E08040G"]
customerSDK.getChatThreads("ON0X0R0L67", threads)
    .then(threads => {
        console.log(threads)
    })
    .catch(error => {
        console.log(rror
    })
```

Arguments:

| arguments | shape | type     |
| --------- | ----- | -------- |
| chat      |       | string   |
| threads   | page  | string[] |

Returned value:

| array of shapes | type     | description                     |
| --------------- | -------- | ------------------------------- |
| id              | string   | Thread's id                     |
| chat            | string   | Chat's id                       |
| active          | string[] | Active state                    |
| order           | number   | order (can be used for sorting) |
| users           | string[] | Users' ids                      |
| events          | object[] | Events                          |

### getChatThreadsSummary

In most cases you do not need to use this method directly. If you want to load
more events, consider using [`getChatHistory`](#getchathistory).

```js
customerSDK
  .getChatThreadsSummary('ON0X0R0L67', {
    offset: 0,
    limit: 10,
  })
  .then(summary => {
    console.log(summary.threadsSummary)
    console.log(summary.totalThreads)
  })
  .catch(error => {
    console.log(error)
  })
```

Arguments:

| arguments  | shape  | type   | default | max  | description |
| ---------- | ------ | ------ | ------- | ---- | ----------- |
| chat       |        | string |         |      |             |
| pagination |        |        |         |      |             |
|            | offset | number | 0       |      |             |
|            | limit  | number | 25      | 1000 |             |

Returned value:

| shape          | type     | shape       | type   |
| -------------- | -------- | ----------- | ------ |
| threadsSummary | object[] |             |        |
|                |          | id          | string |
|                |          | order       | number |
|                |          | totalEvents | number |
| totalThreads   | number   |             |        |

### off

This method unsubscribes from emitted events which are described [here](#events).

### on

This method subscribes to emitted events which are described [here](#events).

### sendEvent

```js
const event = {
  type: 'message',
  // ... other properties specific for the event's type
}

customerSDK
  .sendEvent('ON0X0R0L67', event)
  .then(event => {
    console.log(event)
  })
  .catch(error => {
    console.log(error)
  })
```

Arguments:

| arguments | shape | type   | description           |
| --------- | ----- | ------ | --------------------- |
| chat      |       | string | Destination chat's id |
| event     |       |        |                       |
|           | type  | string | Type of the event     |
|           | ...   |        | Other properties      |

### sendFile

This method is a little bit special - it returns regular `then`/`catch` methods
of a Promise **and** a `cancel` method which you can use to abort the upload of
the file. It also lets you pass `onProgress` callback function. Keep in mind
that the maximum accepted file size is 10 MB.

```js
customerSDK
  .sendFile(
    'ON0X0R0L67',
    {
      file,
      customId, // optional
    },
    {
      onProgress: progress => console.log(`upload progress: ${progress}`),
    },
  )
  .then(response => {
    console.log(response.url)
  })
  .catch(error => {
    console.log(error)
  })
```

Arguments:

| arguments | shape      | type     | description                                                                   |
| --------- | ---------- | -------- | ----------------------------------------------------------------------------- |
| chat      |            | string   | Destination chat's id                                                         |
| data      |            |          |                                                                               |
|           | file       | Blob     |                                                                               |
|           | customId   | string   | Optional customId for the event                                               |
| spec      |            |          |                                                                               |
|           | onProgress | function | This callback function will receive a progress value - number between 0 and 1 |

Returned value:

| shape | type   |
| ----- | ------ |
| url   | string |

In React Native instead of passing a blob you need to pass an object of
[such shape](https://github.com/facebook/react-native/blob/56fef9b6225ffc1ba87f784660eebe842866c57d/Libraries/Network/FormData.js#L34-L38):

```js
const file = {
  uri: uriFromCameraRoll,
  type: 'image/jpeg', // optional
  name: 'photo.jpg', // optional
}
```

### sendMessage

```js
const message = { text: 'Hello' }

customerSDK
  .sendMessage('ON0X0R0L67', message)
  .then(message => {
    console.log(message)
  })
  .catch(error => {
    console.log(error)
  })
```

Arguments:

| arguments | shape | type   | description           |
| --------- | ----- | ------ | --------------------- |
| chat      |       | string | Destination chat's id |
| message   |       |        |                       |
|           | text  | string | Customer's message    |

### sendRating - not implemented yet

```js
customerSDK
  .sendRating('ON0X0R0L67', {
    value: 'good',
    comment: 'Agent helped me a lot!',
  })
  .then(rating => {
    console.log(rating)
  })
  .catch(error => {
    console.log(error)
  })
```

Arguments:

| arguments | shape   | type           | description           |
| --------- | ------- | -------------- | --------------------- |
| chat      |         |                | Destination chat's id |
| rating    |         |                |                       |
|           | value   | 'good' / 'bad' | Rating value          |
|           | comment | string         | Optional comment      |

### setSneakPeek

This method doesn't return a promise. It just sets the internal sneak peek
value. It will be sent to the server only if the target chat is active and only
once per 2 seconds (it's [throttled](https://lodash.com/docs/4.17.4#throttle)).

```js
customerSDK.setSneakPeek('ON0X0R0L67', 'what is the price for your ')
```

Arguments:

| arguments | type   | description                              |
| --------- | ------ | ---------------------------------------- |
| chat      | string | Destination chat id                      |
| text      | string | Message preview broadcasted to the agent |

### startChat

```js
customerSDK
  .startChat({
    events: [],
  })
  .then(chat => {
    console.log(chat)
  })
  .catch(error => {
    console.log(error)
  })
```

Arguments:

| arguments     | shape  | type     | description        |
| ------------- | ------ | -------- | ------------------ |
| specification |        |          | For advanced usage |
|               | scope  |          |                    |
|               | events | events[] |                    |

### updateChatProperties

```js
const properties = {
  property_namespace: {
    sample: 'property',
  },
}
customerSDK
  .updateChatProperties('ON0X0R0L67', properties)
  .then(response => {
    console.log(response)
  })
  .catch(error => {
    console.log(error)
  })
```

Arguments:

| arguments  | shape | type   | description |
| ---------- | ----- | ------ | ----------- |
| chat       |       | string |             |
| properties |       |        |             |

### updateChatThreadProperties

```js
const properties = {
  property_namespace: {
    sample: 'property',
  },
}
customerSDK
  .updateChatThreadProperties('ON0X0R0L67', 'OS0C0W0Z1B', properties)
  .then(response => {
    console.log(response)
  })
  .catch(error => {
    console.log(error)
  })
```

Arguments:

| arguments  | shape | type   | description |
| ---------- | ----- | ------ | ----------- |
| chat       |       | string |             |
| thread     |       | string |             |
| properties |       |        |             |

### updateCustomer

```js
const properties = {
  name: 'John Doe',
  email: 'john.doe@example.com',
  fields: {
    custom_property: 'BasketValue=10usd',
    any_key_is_ok: 'sample custom field',
  },
}
customerSDK
  .updateCustomer(properties)
  .then(response => {
    console.log(response)
  })
  .catch(error => {
    console.log(error)
  })
```

Arguments:

| arguments  | shape  | type   | description                              |
| ---------- | ------ | ------ | ---------------------------------------- |
| properties |        |        |                                          |
|            | name   | string | Optional name                            |
|            | email  | string | Optional email                           |
|            | fields | object | Optionl custom fields with string values |

Returned value:

| shape   | type    |
| ------- | ------- |
| success | boolean |

### updateLastSeenTimestamp

```js
customerSDK
  .updateLastSeenTimestamp('ON0X0R0L67', 1500646701447)
  .then(response => {
    console.log(response)
  })
  .catch(error => {
    console.log(error)
  })
```

Arguments:

| arguments | type   | description |
| --------- | ------ | ----------- |
| chat      | string |             |
| timestamp | number | optional    |

Returned value:

| shape     | type   |
| --------- | ------ |
| timestamp | number |

### $$observable

You can consume all emitted events as a stream with
[most](https://github.com/mostjs/core) of stream libraries like in example
[RxJS](https://github.com/reactivex/rxjs). We provide an interop point for this,
so you can easily convert our SDK to a stream like this:

```js
import Rx from '@reactivex/rxjs'

Rx.Observable.from(visitorSDK).subscribe(([eventName, eventData]) => {
  console.log(eventName, eventData)
})
```

## Events

You can listen for emitted events by subscribing to them (using
[on method](#on)) with your custom JavaScript function. For example, your
function can be executed every time a message has been received.

### connected

```js
customerSDK.on('connected', payload => {
  console.log('connected')
  console.log(payload.chatsSummary)
  console.log(payload.totalChats)
})
```

Payload:

| shape        | type     | shape              | type     | description                                        |
| ------------ | -------- | ------------------ | -------- | -------------------------------------------------- |
| chatsSummary | object[] |                    |          |                                                    |
|              |          | id                 |          | Chat's id                                          |
|              |          | users              | string[] | Users' ids                                         |
|              |          | lastEvent          | object   | Event                                              |
|              |          | lastEventsPerType  | object   | Map from event types to event objects              |
|              |          | lastSeenTimestamps | object   | Map from Users' ids to optional lastSeenTimestamps |
| totalChats   | number   |                    |          |                                                    |

### connection_lost

```js
customerSDK.on('connection_lost', () => {
  console.log('connection_lost')
})
```

This event doesn't carry any additional payload.

### connection_restored

```js
customerSDK.on('connection_restored', payload => {
  console.log('connection_restored')
  console.log(payload.chatsSummary)
  console.log(payload.totalChats)
})
```

Payload:

| shape        | type     | shape              | type     | description                                        |
| ------------ | -------- | ------------------ | -------- | -------------------------------------------------- |
| chatsSummary | object[] |                    |          |                                                    |
|              |          | id                 |          | Chat's id                                          |
|              |          | users              | string[] | Users' ids                                         |
|              |          | lastEvent          | object   | Event                                              |
|              |          | lastEventsPerType  | object   | Map from event types to event objects              |
|              |          | lastSeenTimestamps | object   | Map from Users' ids to optional lastSeenTimestamps |
| totalChats   | number   |                    |          |                                                    |

### customer_id

```js
customerSDK.on('customer_id', id => {
  console.log('customer id is', id)
})
```

Payload:

| argument | type   |
| -------- | ------ |
| id       | string |

### disconnected

```js
customerSDK.on('disconnected', reason => {
  console.log(reason)
})
```

Payload:

| argument | type   | description |
| -------- | ------ | ----------- |
| reason   | string | Optional    |

### chat_properties_updated

```js
customerSDK.on('chat_properties_updated', payload => {
  console.log(payload.chat)
  console.log(payload.properties)
})
```

Payload:

| shape      | type   | description     |
| ---------- | ------ | --------------- |
| chat       | string | Chat's id       |
| properties | object | Chat properties |

### chat_thread_properties_updated

```js
customerSDK.on('chat_thread_properties_updated', payload => {
  console.log(payload.chat)
  console.log(payload.thread)
  console.log(payload.properties)
})
```

Payload:

| shape      | type   | description       |
| ---------- | ------ | ----------------- |
| chat       | string | Chat's id         |
| thread     | string | Thread's id       |
| properties | object | Thread properties |

### last_seen_timestamp_updated

```js
customerSDK.on('last_seen_timestamp_updated', payload => {
  console.log(payload.chat)
  console.log(payload.user)
  console.log(payload.timestamp)
})
```

Payload:

| shape     | type   | description |
| --------- | ------ | ----------- |
| chat      | string | Chat's id   |
| user      | string | User's id   |
| timestamp | number |             |

### new_event

You should distinguish received events by their types.

```js
customerSDK.on('new_event', ({ chat, event }) => {
  switch (event.type) {
    case 'message':
      console.log('new message - ', event.text)
      break
    default:
      break
  }
})
```

Payload:

| shape | type   | description      |
| ----- | ------ | ---------------- |
| type  | string | Event's type     |
| ...   |        | Other properties |

### user_data

```js
customerSDK.on('user_data', user => {
  console.log(user)
})
```

User:

| shape | type | description |
| ----- | ---- | ----------- |
|       |      |             |

### user_joined_chat

```js
customerSDK.on('user_joined_chat', ({ user, chat }) => {
  console.log({ user, chat })
})
```

Payload:

| shape | type   | description |
| ----- | ------ | ----------- |
| user  | string | User's ID   |
| chat  | string | Chat's ID   |

### user_left_chat

```js
customerSDK.on('user_left_chat', ({ user, chat }) => {
  console.log({ user, chat })
})
```

Payload:

| shape | type   | description |
| ----- | ------ | ----------- |
| user  | string | User's ID   |
| chat  | string | Chat's ID   |

### user_is_typing

```js
customerSDK.on('user_is_typing', payload => {
  console.log(
    'user with ' + payload.user + ' id is writing something in ' + payload.chat,
  )
})
```

Payload:

| shape | type   | description |
| ----- | ------ | ----------- |
| chat  | string | Chat's id   |
| user  | string | User's id   |

### user_stopped_typing

```js
customerSDK.on('user_stopped_typing', payload => {
  console.log(
    'user with ' + payload.user + ' id stopped writing in ' + payload.chat,
  )
})
```

Payload:

| shape | type   | description |
| ----- | ------ | ----------- |
| chat  | string | Chat's id   |
| user  | string | User's id   |

### thread_closed

```js
customerSDK.on('thread_closed', ({ chat }) => {
  console.log(chat)
})
```

Payload:

| shape | type   | description |
| ----- | ------ | ----------- |
| chat  | string | Chat's id   |

### thread_summary

```js
customerSDK.on('thread_summary', summary => {
  console.log(summary)
})
```

Payload:

| shape       | type   | description |
| ----------- | ------ | ----------- |
| id          | string |             |
| chat        | string | Chat's ID   |
| order       | number |             |
| totalEvents | number |             |

# Changelog

#### [v0.9.0] - 2018-03-02

##### Changed

* internal change: upgraded to 0.5 version of server protocol

##### Changed

#### [v0.8.0] - 2018-01-05

##### Added

* chatsSummary objects (returned from `getChatsSummary` method and available in
  payloads of `connected`/`connection_restored` events) got a new property -
  `lastEventsPerType`, which is an object that maps event types to event objects
* `updateChatProperties` method and corresponding `chat_properties_updated`
  event
* `updateChatThreadProperties` method and corresponding
  `chat_thread_properties_updated` event

##### Fixed

* using multiple instances in React Native
* using in React Native on Android

#### [v0.7.0] - 2017-11-24

##### Changed

* `sendFile` returns `{ url }` now instead of `{ success: true }`
* events no longer have `order` property
* users no longer have `present` property

##### Fixed

* it's possible now to create 2 independent instances of the SDK
* `file` events with `url` property

#### [v0.6.0] - 2017-11-23

##### Changed

* `customId` is added only if it has been explicitly passed in as part of an event
* `customId` is not reused as request's id anymore
* made it impossible to send 2 requests with identical ids until a response
  comes in

#### [v0.5.0] - 2017-11-22

##### Added

* `sendFile` method

##### Changed

* UMD export was renamed `CustomerSDK`

#### [v0.4.2] - 2017-11-18

##### Fixed

* server errors handling - promises should get rejected correctly now

#### [v0.4.1] - 2017-11-18

##### Fixed

* UMD builds from `dist` directory

#### [v0.4.0] - 2017-11-18

##### Added

* `chatSummary` object (from `getChatsSummary`'s response and from `connected`
  and `connection_restored` events) got new property: `lastSeenTimestamps` which
  is a map from user IDs to optional timestamps

##### Changed

* `init` now accepts configuration object (`{ license, clientId }`) instead of
  just a license number (in React Native the configuration object expects
  additionally `redirectUri`)
* `getChatThreads` now returns an array of `threads` instead of `{ threads, users }` object
* removed `lastSeenTimestamp` from user objects
* `AuthWebView` exposed from `@livechat/chat.io-customer-auth` for React Native
  integration no longer needs a license prop

#### [v0.3.1] - 2017-11-16

##### Fixed

* parsing server events

#### [v0.3.0] - 2017-11-16

##### Added

* support for React Native apps, you can read more in "How to start" section
* `getChatsSummary` method
* `destroy` method
* `user_joined_chat` and `user_left_chat` events
* threads and thread summaries got a new property - `chat`

##### Changed

* `updateCustomer` now accepts the explicit `fields` property in its argument
* `thread_metadata` event was renamed `thread_summary`
