elixirchat-js-sdk

3.1.0 • Public • Published

elixirchat-js-sdk

JavaScript SDK for https://elixir.chat

English | Русский

ElixirChat widget


There are two things you can do with ElixirChat JS SDK:

  1. Add a fully implemented Elixirchat widget (pictured above) to your website by simply writing a few lines of code. The widget's look and feel are customizable via CSS.
  2. Create your own custom widget that communicates with your ElixirChat admin panel via Elixirchat JS SDK.

Examples

ElixirChat Widget Demo ElixirChat JS SDK Demo
Fully implemented Elixirchat widget
Customized with CSS
How to add?
Simple custom widget
Written with pure JS from scratch
How to create?
See demo See demo
Code (~20 lines of JS) Code (~90 lines of JS)

1. How to add a fully implemented ElixirChat widget to your website

Check out the example /build/examples/widget.html

a) Via package manager

Run npm i elixirchat --save and then add this code:

import ElixirChatWidget from 'elixirchat-js-sdk/widget';

const elixirChatWidget = new ElixirChatWidget({
  apiUrl: 'https://elixirchat.yoursite.com/api', // your ElixirChat API URL
  socketUrl: 'wss://elixirchat.yoursite.com/socket', // your ElixirChat websocket URL
  companyId: 'your-company-id-here', // you will get companyId from ElixirChat team
  
  // You may also include optional "room" and "client" parameters here
  // Scroll down to "ElixirChat Config" for details

  debug: true // for verbose console output
});
elixirChatWidget.appendWidget({
  container: document.body,
  iframeStyles: `.your-custom-widget-css-code-here { color: green }`,
});

b) Via <script> tag:

Download /build/sdk.min.js and /build/default-widget.min.js from this repository and then include this snippet anywhere into your HTML-code:

<script src="[YOUR_PATH]/sdk.min.js"></script>
<script src="[YOUR_PATH]/default-widget.min.js"></script>
<script>
  const elixirChatWidget = new ElixirChatWidget({
    apiUrl: 'https://elixirchat.yoursite.com/api', // your ElixirChat API URL
    socketUrl: 'wss://elixirchat.yoursite.com/socket', // your ElixirChat websocket URL
    companyId: 'your-company-id-here', // you will get companyId from ElixirChat team
    
    // You may also include optional "room" and "client" parameters here
    // Scroll down to "ElixirChat Config" for details

    debug: true // for verbose console output
  });
  elixirChatWidget.appendWidget({
    container: document.body,
    styles: `.your-custom-widget-css-code-here { color: green }`,
  });
</script>

2. How to create your own custom widget

Check out the example /build/examples/sdk.html

Install:

npm i elixirchat --save

or include /build/sdk.min.js via the <script> tag anywhere into your HTML code

<script src="[YOUR_PATH]/sdk.min.js"></script>

Code:

import ElixirChat from 'elixirchat-js-sdk';
// Alternatively, if using `<script>` tag, the `ElixirChat` object be added to `window`.

const elixirChat = new ElixirChat({
  apiUrl: 'https://elixirchat.yoursite.com/api', // your ElixirChat API URL
  socketUrl: 'wss://elixirchat.yoursite.com/socket', // your ElixirChat websocket URL
  companyId: 'your-company-id-here', // you will get companyId from ElixirChat team
  
  // You may also include optional "room" and "client" parameters here
  // Scroll down to "ElixirChat Config" for details

  debug: true // for verbose console output
});


document.querySelector('#send-message-button').addEventListener('click', () => {
  // Submit new message
  elixirChat.sendMessage({
    text: document.querySelector('textarea').value, // new message text
    attachments: document.querySelector('input[type=file]').files, // attached files
    responseToMessageId: '225a5c-6cf5e0', // the ID of a message you reply to (if any)
  })
  .then(newMessage => console.log(newMessage));
});


// Subscribe to new messages in the room
elixirChat.onMessage((message) => {
  console.log('New message:', message.text);
  console.log('From:', message.sender.firstName, message.sender.lastName);
  console.log('Is reply to:', message.responseToMessage ? message.responseToMessage.text : 'none');
});

// Load most recent messages
elixirChat.fetchMessageHistory(10).then(messages => {
  console.log('Fetched 10 latest messages', messages);
});

// Track who's currently typing in the room
elixirChat.onTyping((peopleWhoAreTyping) => {
  if (peopleWhoAreTyping.length) {
    document.querySelector('#typing').innerHTML = '${peopleWhoAreTyping.map(person => person.firstName).join(', ')} are typing...';
  }
  else {
    document.querySelector('#typing').innerHTML = 'Nobody is typing';
  }
});

// Take screenshot of your customer's screen
document.querySelector('#screenshot-button').addEventListener('click', () => {
  elixirChat.takeScreenshot().then(screenshot => {
    document.querySelector('img#preview').src = screenshot.dataUrl; // show screenshot preview

    // Send screenshot as attachment
    elixirChat.sendMessage({
      attachments: [ screenshot.file ] // screenshot.file is a `File()` instance
    });
  });
});

// Check out the extended example in /build/examples/sdk.html

Documentation


Before getting started: What are rooms?

ElixirChat Rooms

In your ElixirChat admin panel, all rooms are listed on the left

In ElixirChat, the customers and your customer support agents communicate in so-called rooms. There are two types of rooms:

  1. Private room: for one-on-one communication between a single customer and an assigned customer support manager.
  2. Public room: a group chat where all customers see each other's messages and replies from the assigned customer support manager.

ElixirChat Config

You have to pass over the config when initializing new ElixirChat or new ElixirChatWidget.

// Example:
new ElixirChat({
  apiUrl: 'https://elixirchat.yoursite.com/api',
  socketUrl: 'wss://elixirchat.yoursite.com/socket',
  companyId: 'your-company-id-here',
  room: {
    id: 'your-room-id-here',
    title: 'Your room title to be displayed in ElixirChat admin panel (on the left)',
    data: {
      custom_field_1: 'Optional custom field value to be displayed in ElixirChat admin panel (on the right)',
      custom_field_2: 'Another optional custom field value to be displayed in ElixirChat admin panel (on the right)',
    }
  },
  client: {
    id: 'your-own-id-you-may-use-to-identify-a-customer',
    firstName: 'you may pass your customer\'s first name here (to show in admin panel)',
    lastName: 'you may pass your customer\'s last name here (to show in admin panel)',
  },
  debug: true,
})

apiUrl: string

Your ElixirChat backend GraphQL URL (for example https://elixirchat.yourcompany.com/api)


socketUrl: string

Your ElixirChat backend WebSocket URL starting with ws:/wss: protocol (for example wss://elixirchat.yourcompany.com/socket)


companyId: string

Your company ID. You will get it from ElixirChat team.


room: { id, title, data } (optional)

Set the room option if you need a public room. How it works:

  • When you pass room for the first time, ElixirChat JS SDK creates a new public room (with the specified id and title).
  • When you initialize ElixirChat JS SDK with the same room.id again, the SDK connects to the same room that's been previously created with this id.
  • If you don't pass room at all, a new private room would be created for every unique visitor.

Parameters:

  • room.id: string — Arbitrary string you can use to identify the room.
  • room.title: string — Your public room title that is displayed in your ElixirChat admin panel (on the left). Feel free to change it over time if you need to — these changes will be reflected in the admin panel as well.
  • room.data: object — Object with your room's custom fields that will be displayed in the admin panel on the right (e.g. "Subscription", "Last Activity" or "API" fields on the screenshot above). Custom fields are different for each room. Contact ElixirChat team to enable them in the admin panel.

client: { id, firstName, lastName } (optional)

Pass it if you want firstName and lastName to be displayed in the ElixirChat admin panel.

If you don't pass the client object, a random name will be generated using unique-names-generator (name examples: "Spotty Jade", "Italian Crimson", "Hot Aquamarine", etc) and stored in localStorage so that the generated name persists after page refreshes.

Parameters:

  • client.id: string - Arbitrary string you can use to identify a particular customer
  • client.firstName: string - Customer's first name to be displayed in the ElixirChat admin panel
  • client.lastName: string - Customer's last name to be displayed in the ElixirChat admin panel

debug: boolean (default=false) (optional)

Enables ElixirChat JS SDK verbose console output


ElixirChat API

Class ElixirChatWidget extends ElixirChat therefore they both share all methods and properties except these that are only present in ElixirChatWidget.


ElixirChat methods:

sendMessage({ text, attachments, responseToMessageId })

Send customer's message to the room. Passing at least either text or attachments is required.

Argument parameters {...}:

  • text: string (optional) - message text
  • attachments: Array<File> (optional) - list of attachments in a File() format
  • responseToMessageId: string (optional) - the ID of a message your customer replies to (if any)

Returns: Promise() whose then callback has these arguments:

// Example:
elixirChat.sendMessage({
  text: 'my message text',
  attachments: document.querySelector('#inputFile').files,
  responseToMessageId: '6a4t24-y43th3',
})
.then(yourMessage => console.log(yourMessage));

onMessage((message) => { ... })

Subscribe to the event that fires every time a new message is sent to the room.

Arguments:

  • callback: function - Function that runs every time a new message is sent.

Callback parameters:

  • message: object:
    • message.id: string - message ID
    • message.text: string - message text
    • message.timestamp: string - message timestamp in ISO format
    • message.cursor: string - message cursor needed for fetchMessageHistory()
    • message.sender: object - sender info:
      • sender.elixirChatId: string - user ID of the sender generated by ElixirChat backend (it's NOT a client.id from config)
      • sender.firstName: string - sender's first name
      • sender.lastName: string - sender's last name
      • sender.isCurrentClient: boolean - true if sender is the current client that was passed on to ElixirChat config as client
      • sender.isOperator: boolean - true if the sender is a customer support agent; false if the sender is another client (in case of public room)
      • sender.id: string | undefined - ElixirChar client.id of the sender (however, if sender a customer support agent, then sender.id is undefined)
    • message.responseToMessage: object | null - contains original message info (if this is a reply to another message) or null (if this message is not a reply)
      • responseToMessage.id: string - original message ID
      • responseToMessage.text: string - original message text
      • responseToMessage.sender: object - original message sender (same format as message.sender above)
// Example:
elixirChat.onMessage((message) => {
  if (message.sender.isCurrentClient) {
    console.log('You sent a message ', message.text);
  }
  else {
    console.log('New message from ', message.sender.isOperator ? 'agent' : 'client');
    console.log(message.text);
  }
  if (message.responseToMessage) {
    console.log('This is a reply to ', message.responseToMessage.text);
  }
});

fetchMessageHistory(limit, firstMessageCursor)

Fetch a chunk of message history.

  • Chunk's size is determined by the limit.
  • If no firstMessageCursor is provided, chunk contains the latest messages.
  • Otherwise, if firstMessageCursor is provided, the chunk contains messages chronologically preceding the message with the cursor that equals firstMessageCursor.

Arguments:

  • limit: number - the size of the returned message chunk
  • firstMessageCursor: string (optional) - the cursor field of a message prior to which you'd like to fetch messages. If not provided, the latest messages would be fetched.

Returns: Promise() whose then callback has these arguments:

// Example:
let messageStorage = [];

// Fetch 10 most recent messages
elixirChat.fetchMessageHistory(10).then(latestMessages => {
  messageStorage = latestMessages;
  console.log('Fetched 10 latest messages', latestMessages);
});

document.querySelector('button#load-previous-messages').addEventListener('click', e => {
  elixirChat.fetchMessageHistory(10, messageStorage[0].cursor).then(fetchedChunk => {

    // Prepend fetched history into the messageStorage
    messageStorage = fetchedChunk.concat(messageStorage);

    // Disable "Load previous messages" button when all history is loaded
    if (elixirChat.reachedBeginningOfMessageHistory) {
      e.target.innerText = 'All messages loaded';
      e.target.disabled = true;
    }
  });
});

onTyping((peopleWhoAreTyping) => { ... })

Subscribe to the event that fires when other participants start or finish typing text in the current room.

Arguments:

  • callback: function - Function that runs when other participants start or finish typing text in the current room.

Callback arguments:

  • peopleWhoAreTyping: Array<{user}> - Array of people who are currently typing text in this room. If the array is empty, then other participants are not currently typing anything.
    • {user}.id - participant's ID
    • {user}.firstName - participant's first name
    • {user}.lastName - participant's last name
// Example:
elixirChat.onTyping((peopleWhoAreTyping) => {
  if (peopleWhoAreTyping.length) {
    document.querySelector('#typing').innerHTML = '${peopleWhoAreTyping.map(person => person.firstName).join(', ')} are typing...';
  }
  else {
    document.querySelector('#typing').innerHTML = 'Nobody is typing';
  }
});

dispatchTypedText(typedText)

Dispatch the text typed so far by the client to ElixirChat admin panel. This method doesn't send a message but only reports that current client has typed a certain text. This would be displayed in the "typing..." status in ElixirChat admin panel.

Arguments:

  • typedText: string | false - The text typed so far by the client, OR false in case the client submitted his message
// Example:
document.querySelector('textarea#message').addEventListener('keyup', (e) => {
  elixirChat.dispatchTypedText(e.target.value);
});

document.querySelector('button#submit').addEventListener('click', (e) => {
  elixirChat.dispatchTypedText(false);
});

takeScreenshot()

Make a screenshot of the customer's screen. This would open a standard browser window asking to share user's screen.

Returns: Promise() whose then callback has these arguments:

  • screenshot: object:
    • screenshot.dataUrl: string - a base64 data URL string of the screenshot in PNG format
    • screenshot.file: File - a File() instance of the screenshot in PNG format
// Example:
elixirChat.takeScreenshot().then(screenshot => {
  // Render preview
  document.querySelector('img#preview').src = screenshot.dataUrl;
  
  // Send to ElixirChat admin as attachment
  elixirchat.sendMessage({ attachments: [ screenshot.file ] });
})
.catch(e => alert(e.message));

reconnect({room,client})

Change room or client (or both) after you already initialized ElixirChat or ElixirChatWidget.

  • If you pass a new room only, SDK will reconnect you to a new room with the same client data.
  • If you pass a new client only, SDK will reconnect you to the same room with new client data.
    • BUT, if you were previously connected to a private room (i.e. without passing a room ID in the first place), and you pass a new client only, you will be reconnected to a new private room.

Argument parameters {...}:

Returns: Promise()

// Example 1: change both room and client
elixirChat.reconnect({
  room: {
    id: 'new-room-id',
    title: 'My new room title', // or don't pass the title to keep it the same
  },
  client: {
    id: MyApp.currentUser.id,
    firstName: MyApp.currentUser.full_name.split(' ')[0],
    lastName: MyApp.currentUser.full_name.split(' ')[1],
  },
}).then(status => console.log(status));

// Example 2: change client info only but keep the same room (unless that room was private)
elixirChat.reconnect({
  client: {
    id: MyApp.currentUser.id,
    firstName: MyApp.currentUser.full_name.split(' ')[0],
    lastName: MyApp.currentUser.full_name.split(' ')[1],
  },
});

// Example 3: change room but keep the same client data
elixirChat.reconnect({
  room: {
    id: 'new-room-id',
    title: 'My new room title', // or don't pass the title to keep it the same
  },
});

onConnectSuccess(() => { ... })

Subscribe to the event that fires after establishing a successful connection to a room. This happens either after initial SDK initialization, or after invoking reconnect() method.

Arguments:

  • callback: function - Function that runs after establishing a successful connection to a room.
elixirChat.onConnectSuccess(() => {
  console.log(elixirChat.companyId);
  console.log(elixirChat.room);
  console.log(elixirChat.client);
});

onConnectError(error => { ... })

Subscribe to the event that fires if connection to the room failed. This might happen either after initial SDK initialization, or after invoking reconnect() method.

Arguments:

  • callback: function - Function that fires if connecting to the room failed.
elixirChat.onConnectError((error) => {
  console.log('Could not connect to a room', error);
});

ElixirChat properties:

  • apiUrl: string - Same as passed to config
  • socketUrl: string - Same as passed to config
  • companyId: string - Same as passed to config
  • room: object - Same as passed to config
  • client: object - Same as passed to config
  • debug: boolean - Same as passed to config
  • elixirChatRoomId: string - current room ID generated by ElixirChat backend (it's NOT the same as room.id in config)
  • elixirChatClientId: string - current client ID generated by ElixirChat backend (it's NOT the same as client.id in config)
  • authToken: string - token that's been generated by ElixirChat backend after successful connection to a room
  • connected: boolean - true, if SDK is currently connected to a room
  • reachedBeginningOfMessageHistory: boolean - true, if the chunk of first messages in room history was ever requested via fetchMessageHistory() (meaning that as the user scrolled up to the beginning of chat history, fetchMessageHistory() was requesting consequent message chunks and ultimately reached the beginning of history)
// Examples:
document.querySelector('button#load-previous-messages').addEventListener('click', e => {
  elixirChat.fetchMessageHistory(5, messages[0].cursor).then(history => {
    messages = [...history, ...messages];

    // Disable "Load previous messages" button when all history is loaded
    if (elixirChat.reachedBeginningOfMessageHistory) {
      e.target.innerText = 'All messages loaded';
      e.target.disabled = true;
    }
  });
});

if (elixirChat.isConnected) {
  document.getElementById('status').className = 'active';
}

elixirChat.onConnectSuccess(() => {
  document.getElementById('status').className = 'active';
  console.log('Connected to ', elixirChat.room, elixirChat.elixirChatRoomId);
});

ElixirChatWidget API

There are a few more methods and properties specifically in ElixirChatWidget.


ElixirChatWidget methods:

toggleChatVisibility()

Programmatically show or hide the widget chat window.

// Example:
elixirChatWidget.togglePopup();
console.log('Chat window is now ', elixirChatWidget.isWidgetPopupOpen ? 'open' : 'closed');

onToggleChatVisibility(callback)

Subscribe to open/close events of the widget chat window.

Arguments:

  • callback: function - a function that fires every time the chat window is opened or closed
// Example:
elixirChatWidget.onToggleChatVisibility((isVisible) => {
  console.log('Chat window is now ', isVisible ? 'open' : 'closed');
});

appendWidget({ container, iframeStyles })

Append ElixirChat widget to a container, customize via CSS if needed.

Argument parameters {...}:

  • container: HTMLElement - DOM element the widget would be appended to (at the end of it).
  • iframeStyles: string - your custom CSS code applied to ElixirChat Widget inside the iframe so that you can easily change look and feel of the chat window.

Returns:

  • JSX.Element - Widget React component (rendered inside the <iframe> element)
// Example:
elixirChatWidget.appendWidget({
  container: document.body,
  iframeStyles: `
    .elixirchat-chat-container { background: #eeeeee }
    .elixirchat-chat-messages__item { background: #53B561 } 
  `,
});

ElixirChatWidget properties:

  • container: HTMLElement - Same as passed to appendWidget()
  • iframeStyles: string - Same as passed to appendWidget()
  • widgetIsVisible: boolean - Flag indicating whether the chat window is currently open
  • widgetIFrameDocument: Document - Document of the IFrame element of the Chat window
  • widgetChatReactComponent: JSX.Element - Widget React component (rendered inside the IFrame element)
// Examples:
console.log('Widget React component state is', elixirChatWidget.widgetReactComponent.state);

elixirChatWidget.widgetIFrameDocument.body.style = 'background: yellow';

if (elixirChatWidget.isWidgetPopupOpen) {
  document.getElementById('my-app-column').className = 'shrinked';
}

For SDK developers

If you want to roll out ElixirChat JS SDK and widget as a developer:

# Clone the repo and install dependencies
git clone git@github.com:elixirchat/elixirchat-js-sdk.git
npm install

# Run dev version on http://localhost:8001/
npm run dev

# Compile `build/sdk.min.js` & `build/default-widget.min.js` out of your current code
npm run build

# Run SDK and widget examples on http://localhost:8002
npm run examples

Readme

Keywords

none

Package Sidebar

Install

npm i elixirchat-js-sdk

Weekly Downloads

54

Version

3.1.0

License

ISC

Unpacked Size

2.82 MB

Total Files

68

Last publish

Collaborators

  • egorvinogradov