🎉 New! Web Push Notifications for Chatkit. Learn more in our latest blog post.
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Build a customer service chat app with React

  • Ayooluwa Isaiah
February 20th, 2019
You will need Node 6+ installed on your machine.

In this tutorial, I’ll describe how a customer support chat app can be achieved using React and Chatkit.

In order to answer your customers’ questions about your product, there must be some sort of support system in place. If you’re looking to get a chat system up and running for the purpose of helping customers and tending to their needs, Chatkit makes it easy to do with just a few lines of code.

Here’s a demo of what we’ll be building:

Prerequisites

To follow through with this tutorial, you need to have Node.js (v6 and later) and npm installed on your machine. If you need to install or update your Node version, you can check out this page for instructions on how to do so. Previous experience with building Node and React applications is also assumed for the purposes of this tutorial.

Sign up for Chatkit

Open this link to create a new Chatkit account. This is a necessary step so that you can create a new Chatkit instance for your application and manage your credentials. Once you’re logged in, create a new Chatkit instance for your application, then locate the Credentials tab on your instance’s dashboard and take note of the Instance Locator and Secret Key as we’ll be using later on.

Head over to the Console tab, and create a new user for your application. This will be the user account for the support staff who will interact with customers. The user identifier for this user should be support as shown below:

Set up the application server

Launch a new terminal window and create a new directory for your project as shown below:

    mkdir customer-service

Next, cd into the new customer-service directory and initialize your project with a package.json file using the npm init -y command. The -y flag allows us to accept all the defaults without being prompted to change the values.

Following that, run the command below to install all the dependencies that we’ll need to build up our Node server. The @pusher/chatkit-server package is the Node.js SDK that grants us the ability to make use of Chatkit features in out application.

    npm install express dotenv body-parser cors @pusher/chatkit-server --save

Next, create a .env file in the root of your project directory and paste in the credentials you retrieved from your Chatkit instance dashboard:

    // .env

    PORT=5200
    CHATKIT_INSTANCE_LOCATOR=<your chatkit instance locator>
    CHATKIT_SECRET_KEY=<your chatkit secret key>

Create another file, server.js in the root of your project directory, and add the following code into it to set up the Node server:

    // server.js

    require('dotenv').config({ path: '.env' });

    const express = require('express');
    const bodyParser = require('body-parser');
    const cors = require('cors');
    const Chatkit = require('@pusher/chatkit-server');

    const app = express();

    const chatkit = new Chatkit.default({
      instanceLocator: process.env.CHATKIT_INSTANCE_LOCATOR,
      key: process.env.CHATKIT_SECRET_KEY,
    });

    app.use(cors());
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));

    app.post('/users', (req, res) => {
      const { userId } = req.body;

      chatkit
        .createUser({
          id: userId,
          name: userId,
        })
        .then(() => {
          res.sendStatus(201);
        })
        .catch(err => {
          if (err.error === 'services/chatkit/user_already_exists') {
            console.log(`User already exists: ${userId}`);
            res.sendStatus(200);
          } else {
            res.status(err.status).json(err);
          }
        });
    });

    app.post('/authenticate', (req, res) => {
      const authData = chatkit.authenticate({
        userId: req.query.user_id,
      });
      res.status(authData.status).send(authData.body);
    });

    app.set('port', process.env.PORT || 5200);
    const server = app.listen(app.get('port'), () => {
      console.log(`Express running → PORT ${server.address().port}`);
    });

When a customer tries to contact support, we will request the name of the user which will be used as their userId. The userId is passed to the /users endpoint so that a new user account can be created on the Chatkit instance with the provided ID if it doesn’t exist already.

The other route, /authenticate, is used to authenticate users who try to connect to your Chatkit instance. I’ve opted not to include an authentication flow in this example so that anyone who tries to contact a support agent would be granted access immediately.

That’s all we need to do on the server. You can start the server by running node server.js to make it available on port 5200.

Bootstrap the React application

We will make use of the create-react-app package to bootstrap our React application so that we can get up and running quickly. You need to install it globally on your machine using the command below:

    npm install create-react-app -g

Then use it to create a new React app within the root of your project directory:

    create-react-app client

Once the command has finished running, cd into the new client directory and install the following additional dependencies that we’ll be needing in the course of building the React app:

    npm install prop-types @pusher/chatkit-client axios react-router-dom react-spinkit --save

Upon the installation of the above dependencies, run npm start from within the client folder to launch the development server on http://localhost:3000.

Set up routing

We installed the react-router-dom package in the previous section for the purpose of adding routing to our application. We’ll have two views in the app: the customer view which will be loaded on the root route, and the support view will be loaded when the user navigates to the /support route.

Open up client/src/index.js in your text editor, and change it to look like this:

    // client/src/index.js

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import { HashRouter } from 'react-router-dom';
    import App from './App';
    import * as serviceWorker from './serviceWorker';

    ReactDOM.render(
      <HashRouter>
        <App />
      </HashRouter>,
      document.getElementById('root')
    );

    // If you want your app to work offline and load faster, you can change
    // unregister() to register() below. Note this comes with some pitfalls.
    // Learn more about service workers: http://bit.ly/CRA-PWA
    serviceWorker.unregister();

We’re using HashRouter in this example since our routing is done entirely on the client. Let’s create two new files for each view within the client/src directory as follows:

    touch Customer.js Support.js

Then set up the routes in App.js as shown below:

    // client/src/App.js

    import { Route } from 'react-router-dom';
    import React, { Component } from 'react';
    import Customer from './Customer';
    import Support from './Support';

    import './App.css';

    class App extends Component {
      render() {
        return (
          <div className="App">
            <Route exact path="/" render={() => <Customer />} />
            <Route exact path="/support" render={() => <Support />} />
          </div>
        );
      }
    }

    export default App;

At the moment, an error is displayed on the page because, we imported and used the Customer and Support components within App.js, they haven’t been created and exported yet.

Let’s go ahead and do just that. Add the following code to Customer.js:

    // client/src/Customer.js

    import React, { Component } from 'react';

    class Customer extends Component {
      constructor() {
        super();
      }

      render() {
        return null;
      }
    }

    export default Customer;

And update the contents of Support.js as follows:

    // client/src/Support.js

    import React, { Component } from 'react';

    class Support extends Component {
      constructor() {
        super();
      }

      render() {
        return null;
      }
    }

    export default Support;

Add the application styles

To add a bit styling to our application, we’ll make use of the Skeleton CSS boilerplate. Download the zip file containing the styles, extract it, and copy normalize.css and skeleton.css from the css folder to your client/src/directory. Then open up client/src/App.js and change it to look like this:

    // client/src/App.js

    import { Route } from 'react-router-dom';
    import React, { Component } from 'react';
    import Customer from './Customer';
    import Support from './Support';

    import './normalize.css';
    import './skeleton.css';
    import './App.css';

    // rest of the file

Next, update your App.css file to look like this:

    // client/src/App.css

    html {
      box-sizing: border-box;
    }

    *, *::before, *::after {
      box-sizing: inherit;
      margin: 0;
      padding: 0;
    }

    .App {
      text-align: center;
      overflow: hidden;
    }

    svg {
      width: 28px;
      height: 28px;
    }

    input[type="text"]:focus {
      border: 1px solid #300d4f;
    }

    button {
      color: white;
      font-size: 14px;
      border-radius: 2px;
      background-color: #331550;
      border: 1px solid #331550;
      cursor: pointer;
      box-sizing: border-box;
    }

    button:hover {
      color: white;
      background-color: rebeccapurple;
    }

    .chat-widget {
      position: absolute;
      bottom: 40px;
      right: 40px;
      width: 400px;
      border: 1px solid #ccc;
    }

    .chat-header {
      width: 100%;
      height: 60px;
      background-color: #00de72;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .chat-header h2 {
      font-size: 18px;
      margin-bottom: 0;
    }

    .chat-body {
      height: 350px;
      overflow-y: auto;
      padding: 10px;
    }

    .status-messages {
      text-align: center;
      padding: 5px;
    }

    .message {
      background-color: #f6f6f6;
      clear: both;
      margin-bottom: 15px;
      padding: 10px;
      border-radius: 5px;
      max-width: 80%;
    }

    .message.user {
      float: right;
      background-color: peachpuff;
    }

    .message.support {
      float: left;
      background-color: #ddd;
    }

    .message-form, .message-input {
      width: 100%;
      margin-bottom: 0;
    }

    .message-input {
      border-radius: 0;
      border: none;
      border-top: 1px solid #ccc;
      height: 50px;
      padding: 20px;
      font-size: 16px;
      background-color: #f6f6f6
    }

Set up the customer view

In this section, we’ll hone in on the view that the customer will interact with to speak to a support agent. Open up Customer.js and change it to look like this:

    // client/src/Customer.js

    import React, { Component } from 'react';

    class Customer extends Component {
      constructor() {
        super();
      }

      render() {

        return (
          <div className="customer-chat">
            <h1>Imaginary Service</h1>
            <p>Need help? Chat with us</p>
            <button className="contact-btn">Contact Support</button>
          </div>
        );
      }
    }

    export default Customer;

Once the user hits the Contact Support button, we will open up a dialog and request their name. Create a new file Dialog.js in client/src and populate it with the following contents:

    // client/src/Dialog.js

    import React from 'react';
    import Proptypes from 'prop-types';

    import './Dialog.css';

    const Dialog = props => {
      const { username, handleInput, launchChat } = props;

      return (
        <div className="dialog-container">
          <div className="dialog">
            <form className="dialog-form" onSubmit={launchChat}>
              <label className="username-label" htmlFor="username">
                What is your name?
              </label>
              <input
                id="username"
                className="username-input"
                autoFocus
                type="text"
                name="userId"
                value={username}
                onChange={handleInput}
              />
              <button type="submit" className="submit-btn">
                Submit
              </button>
            </form>
          </div>
        </div>
      );
    };

    Dialog.proptypes = {
      username: Proptypes.string.isRequired,
      handleInput: Proptypes.func.isRequired,
      launchChat: Proptypes.func.isRequired,
    };

    export default Dialog;

The Dialog component takes three props: username which is the name the user enters in the input, handleInput which updates the username as the user types and launchChat which will connect to our Chatkit instance once the .dialog-form is submitted. All of the above will be passed down as props from Customer.js.

You can add the styles for the Dialog component within a new Dialog.css file in the same directory:

    // client/src/Dialog.css

    .dialog-container {
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      background-color: rgba(0, 0, 0, 0.9);
      display: flex;
      justify-content:center;
      align-items: center;
    }

    .dialog {
      width: 500px;
      background-color: white;
      display: flex;
      align-items:  center;
    }

    .dialog-form {
      width: 100%;
      margin-bottom: 0;
      padding: 20px;
    }

    .dialog-form > * {
      display: block;
    }

    .username-label {
      text-align: left;
      font-size: 16px;
    }

    .username-input {
      width: 100%;
    }

    .submit-btn {
      width: 100%;
    }

Create the handleInput method in a new file called sharedMethods.js. The reason for this is so we can avoid repeating the same code in multiple places since we’re going to make use of it in the Support component as well.

    // client/src/sharedMethods.js

    function handleInput(event) {
      const { value, name } = event.target;

      this.setState({
        [name]: value,
      });
    }

    export { handleInput }

Now let’s import Dialog into Customer.js and set it up as follows:

    // client/src/Customer.js

    import React, { Component } from "react";
    import Dialog from "./Dialog";
    import { handleInput } from "./sharedMethods";

    class Customer extends Component {
      constructor() {
        super();

        this.state = {
          userId: "",
          isDialogOpen: false
        };
        this.handleInput = handleInput.bind(this);
      }

      showDialog = () => {
        this.setState({
          isDialogOpen: !this.state.isDialogOpen
        });
      };

      launchChat = event => {
        event.preventDefault();
      };

      render() {
        const { isDialogOpen, userId } = this.state;

        return (
          <div className="customer-chat">
            <h1>Imaginary Service</h1>
            <p>Need help? Chat with us</p>

            <button onClick={this.showDialog} className="contact-btn">
              Contact Support
            </button>

            {isDialogOpen ? (
              <Dialog
                username={userId}
                handleInput={this.handleInput}
                launchChat={this.launchChat}
              />
            ) : null}
          </div>
        );
      }
    }

    export default Customer;

Once the button is clicked, the showDialog() method toggles the value of isDialogOpen to true causing the dialog to show up on the page. The user can then enter their name into the input field and submit the form to launch the chat session.

The launchChat() method will take care of that part for us, but right now it only prevents the form submission from reloading the page. What it needs to do is connect to our Chatkit instance and open up the chat widget so that the user can begin to chat with the customer service agent.

Before we get into that, let’s create a component for the chat widget that the user will interact with. Create a new ChatWidget.js file within the client/src directory and add the following code to it:

    // client/src/ChatWidget.js

    import React from "react";
    import Proptypes from "prop-types";

    const ChatWidget = props => {
      const { newMessage, sendMessage, handleInput, currentUser, messages } = props;

      const ChatSession = messages.map(message => {
        const user = message.senderId === currentUser.id ? "user" : "support";
        return <span className={`${user} message`}>{message.text}</span>;
      });

      return (
        <section className="chat">
          <div className="chat-widget">
            <header className="chat-header">
              <h2>Got Questions? Chat with us</h2>
            </header>
            <section className="chat-body">{ChatSession}</section>

            <form onSubmit={sendMessage} className="message-form">
              <input
                className="message-input"
                autoFocus
                name="newMessage"
                placeholder="Compose your message and hit ENTER to send"
                onChange={handleInput}
                value={newMessage}
              />
            </form>
          </div>
        </section>
      );
    };

    ChatWidget.proptypes = {
      newMessage: Proptypes.string.isRequired,
      handleInput: Proptypes.func.isRequired,
      sendMessage: Proptypes.func.isRequired,
      messages: Proptypes.arrayOf(Proptypes.object).isRequired,
      currentUser: Proptypes.object.isRequired
    };

    export default ChatWidget;

Then update Customer.js like this:

    // client/src/Customer.js

    import React, { Component } from "react";
    import axios from "axios";
    import Chatkit from "@pusher/chatkit-client";
    import Spinner from "react-spinkit";
    import Dialog from "./Dialog";
    import ChatWidget from "./ChatWidget";
    import { handleInput, sendMessage, connectToRoom } from "./sharedMethods";

    class Customer extends Component {
      constructor() {
        super();

        this.state = {
          currentUser: null,
          currentRoom: null,
          newMessage: "",
          messages: [],
          isLoading: false,
          userId: "",
          isDialogOpen: false,
        };

        this.connectToRoom = connectToRoom.bind(this);
        this.sendMessage = sendMessage.bind(this);
        this.handleInput = handleInput.bind(this);
      }

      showDialog = () => {
        this.setState({
          isDialogOpen: !this.state.isDialogOpen
        });
      };

      addSupportStaffToRoom = () => {
        const { currentRoom, currentUser } = this.state;

        return currentUser.addUserToRoom({
          userId: "support",
          roomId: currentRoom.id
        });
      };

      createRoom = () => {
        const { currentUser } = this.state;

        currentUser
          .createRoom({
            name: currentUser.name,
            private: true
          })
          .then(room => this.connectToRoom(room.id))
          .then(() => this.addSupportStaffToRoom())
          .catch(console.error);
      };

      launchChat = event => {
        event.preventDefault();

        this.setState({
          isDialogOpen: false,
          isLoading: true
        });

        const { userId } = this.state;

        if (userId === null || userId.trim() === "") {
          alert("Invalid userId");
        } else {
          axios
            .post("http://localhost:5200/users", { userId })
            .then(() => {
              const tokenProvider = new Chatkit.TokenProvider({
                url: "http://localhost:5200/authenticate"
              });

              const chatManager = new Chatkit.ChatManager({
                instanceLocator: "<your chatkit instance locator>",
                userId,
                tokenProvider
              });

              return chatManager.connect().then(currentUser => {
                this.setState(
                  {
                    currentUser,
                    isLoading: false
                  },
                  () => this.createRoom()
                );
              });
            })
            .catch(console.error);
        }
      };

      render() {
        const {
          newMessage,
          messages,
          currentUser,
          currentRoom,
          isDialogOpen,
          userId,
          isLoading
        } = this.state;

        return (
          <div className="customer-chat">
            <h1>Imaginary Service</h1>
            <p>Need help? Chat with us</p>

            {currentRoom ? (
              <ChatWidget
                newMessage={newMessage}
                sendMessage={this.sendMessage}
                handleInput={this.handleInput}
                currentUser={currentUser}
                messages={messages}
              />
            ) : (
              <button onClick={this.showDialog} className="contact-btn">
                Contact Support
              </button>
            )}

            {isLoading ? <Spinner name="three-bounce" color="#300d4f" /> : null}

            {isDialogOpen ? (
              <Dialog
                username={userId}
                handleInput={this.handleInput}
                launchChat={this.launchChat}
              />
            ) : null}
          </div>
        );
      }
    }

    export default Customer;

Be sure to update the <your chatkit instance locator> placeholder within the launchChat() method before proceeding.

We’ve imported two new methods from sharedMethods.js but these haven't been created yet, so let’s do just that:

    // client/src/sharedMethods.js

    function handleInput(event) {
      const { value, name } = event.target;

      this.setState({
        [name]: value
      });
    }

    function sendMessage(event) {
      event.preventDefault();
      const { newMessage, currentUser, currentRoom } = this.state;

      if (newMessage.trim() === "") return;

      currentUser.sendMessage({
        text: newMessage,
        roomId: `${currentRoom.id}`
      });

      this.setState({
        newMessage: ""
      });
    }

    function connectToRoom(id) {
      const { currentUser } = this.state;

      return currentUser
        .subscribeToRoom({
          roomId: `${id}`,
          messageLimit: 100,
          hooks: {
            onMessage: message => {
              this.setState({
                messages: [...this.state.messages, message]
              });
            },
          }
        })
        .then(currentRoom => {
          this.setState({
            currentRoom
          });
        });
    }

    export { handleInput, sendMessage, connectToRoom };

If you open the app the browser, you should be able to enter a username, and see the chat widget popup. You should also be able to send a message and see it display on the screen. Let’s walk through the huge chunk of code we’ve just written so how everything is achieved is clear to you.

In Customer.js, we imported the Chatkit client library and made use of it in launchChat() method to connect to our Chatkit instance. If the connection is successful, we receive a currentUser object which is stored in the application state. This currentUser object is the primary interface to interact with Chatkit.

Immediately following that, we create a new room for this interaction via the createRoom() method. We’re choosing to make the room private so that it’s not publicly available to anyone. Once the room is created, we have to connect to it before we can send any messages. This is done via the connectToRoom() method which takes the id of the room that was created and adds the user to the room.

Chatkit’s room subscription hooks allow us to perform actions when some event occurs in the current room. Here’s we’ve set up the onMessage hook to append new messages to the messages array so that the new message is displayed in the chat widget.

Finally, we add the support agent to the room via the addSupportStaffToRoom() method which means that the support agent will be able to login to their own interface and see all ongoing conversations instantly.

Set up the support view

The next phase is to set up the view where the support agent will be able to interact will all customers at once. Open up Support.js and paste the following code into it:

    // client/src/Support.js

    import React, { Component } from "react";
    import axios from "axios";
    import Chatkit from "@pusher/chatkit-client";
    import { sendMessage, handleInput, connectToRoom } from "./sharedMethods";

    import "./Support.css";

    class Support extends Component {
      constructor() {
        super();
        this.state = {
          currentUser: null,
          currentRoom: null,
          newMessage: "",
          messages: [],
          rooms: []
        };

        this.sendMessage = sendMessage.bind(this);
        this.handleInput = handleInput.bind(this);
        this.connectToRoom = connectToRoom.bind(this);
      }

      componentDidMount() {
        const userId = "support";

        axios
          .post("http://localhost:5200/users", { userId })
          .then(() => {
            const tokenProvider = new Chatkit.TokenProvider({
              url: "http://localhost:5200/authenticate"
            });

            const chatManager = new Chatkit.ChatManager({
              instanceLocator: "<your chatkit instance locator>",
              userId,
              tokenProvider
            });

            return chatManager
              .connect({
                onAddedToRoom: room => {
                  this.setState({
                    rooms: [...this.state.rooms, room]
                  });
                }
              })
              .then(currentUser => {
                this.setState(
                  {
                    currentUser,
                    rooms: currentUser.rooms
                  },
                  () => {
                    if (this.state.rooms.length >= 1) {
                      this.connectToRoom(this.state.rooms[0].id);
                    }
                  }
                );
              });
          })
          .catch(console.error);
      }

      joinRoom = id => {
        this.setState(
          {
            messages: []
          },
          () => this.connectToRoom(id)
        );
      };

      render() {
        const {
          newMessage,
          rooms,
          currentRoom,
          messages,
          currentUser
        } = this.state;

        const RoomList = rooms.map(room => {
          const isActive =
            currentRoom && currentRoom.id === room.id ? "active" : "";
          return (
            <li
              key={room.id}
              onClick={() => this.joinRoom(room.id)}
              className={`${isActive} room`}
            >
              {room.name}
            </li>
          );
        });

        const ChatSession = messages.map((message, index) => {
          const user = message.senderId === currentUser.id ? "support" : "user";
          return (
            <span key={index} className={`${user} message`}>
              {message.text}
            </span>
          );
        });

        return (
          <div className="support-area">
            <aside className="support-sidebar">
              <h3>Rooms</h3>
              <ul>{RoomList}</ul>
            </aside>
            <section className="support-session">
              <header className="current-chat">
                <h3>{currentRoom ? currentRoom.name : "Chat"}</h3>
              </header>
              <div className="chat-session">{ChatSession}</div>
              <form onSubmit={this.sendMessage} className="message-form">
                <input
                  className="message-input"
                  autoFocus
                  placeholder="Compose your message and hit ENTER to send"
                  onChange={this.handleInput}
                  value={newMessage}
                  name="newMessage"
                />
              </form>
            </section>
          </div>
        );
      }
    }

    export default Support;

Make sure you update the <your chatkit instance locator> placeholder with the appropriate value from your Chatkit dashboard. Next, add the styles for this view in a new Support.css file in the same directory:

    // client/src/Support.css

    .support-area {
      width: 100vw;
      height: 100vh;
      display: flex;
    }

    .support-sidebar {
      width: 20%;
      background-color: #300d4f;
      height: 100%;
    }

    .support-sidebar ul {
      list-style: none;
    }

    .support-sidebar h3 {
      color: white;
      margin-bottom: 0;
      text-align: left;
      padding: 10px 20px;
    }

    .room {
      font-size: 22px;
      color: white;
      cursor: pointer;
      text-align: left;
      padding: 10px 20px;
      margin-bottom: 10px;
    }

    .room:hover {
      color: yellowgreen;
    }

    .room.active {
      background-color: yellowgreen;
      color: white;
    }

    .support-session {
      width: 80%;
      height: 100%;
      display: flex;
      flex-direction: column;
    }

    .current-chat {
      border-bottom: 1px solid #ccc;
      text-align: left;
      padding: 10px 20px;
      display: flex;
    }

    .current-chat h3 {
      margin-bottom: 0;
    }

    .chat-session {
      flex-grow: 1;
      overflow-y: auto;
      padding: 10px;
    }

The support agent needs to be able to interact with multiple customers at once, so we have a sidebar where all the rooms are listed and the main chat area for sending and viewing messages.

We’re immediately connecting to the Chatkit instance on page load (via componetDidMount()) and listing all the connected customers in the sidebar. We can jump between chats by clicking on each room name which triggers the joinRoom() method. This method simply connects to the selected room and changes the value of currentRoom so that the screen is updated appropriately.

And due to the onAddedToRoom() connection hook, we do not need to refresh the page to see new chats that have been initiated. Everything is updated in realtime without much effort on your part.

Wrap up

In this tutorial, we went through how a complete customer service solution can be built with React and Chatkit. Although we wrote a fair amount of code, none of it was particularly complicated since Chatkit does all the heavy lifting for us.

You can checkout other things Chatkit can do by viewing its extensive documentation. Don't forget to grab the full source code used in this tutorial here.

Clone the project repository
  • Chat
  • JavaScript
  • Node.js
  • React
  • Social
  • Social Interactions
  • Chatkit

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.