This is the abridged developer documentation for ConnectyCube # Send first chat message > Step-by-step guide to sending your first chat message using ConnectyCube Web Chat SDK - what exactly to do when you want to build a chat. The **ConnectyCube Chat API** is a set of tools that enables developers to integrate real-time messaging into their web and mobile applications. With this API, users can build powerful chat functionalities that support one-on-one messaging, group chats, typing indicators, message history, delivery receipts, and push notifications. If you’re planning to build a new app, we recommend starting with one of our [code samples apps](/js/getting-started/code-samples) as a foundation for your client app.\ If you already have an app and you are looking to add a chat to it, proceed with this guide. This guide walks you through installing the ConnectyCube SDK in your app, configure it and then sending your first message to the opponent in 1-1 chat. ## Before you start [Section titled “Before you start”](#before-you-start) Before you start, make sure: 1. You have access to your ConnectyCube account. If you don’t have an account, [sign up here](https://admin.connectycube.com/register). 2. An app created in ConnectyCube dashboard. Once logged into [your ConnectyCube account](https://admin.connectycube.com), create a new application and make a note of the app credentials (app ID and auth key) that you’ll need for authentication. ## Step 1: Configure SDK [Section titled “Step 1: Configure SDK”](#step-1-configure-sdk) To use chat in a client app, you should install, import and configure ConnectyCube SDK. **Note:** If the app is already created during the onboarding process and you followed all the instructions, you can skip the ‘Configure SDK’ step and start with [Create and Authorise User](#step-2-create-and-authorise-user). ### Install SDK [Section titled “Install SDK”](#install-sdk) #### React, Angular, Vue etc. [Section titled “React, Angular, Vue etc.”](#react-angular-vue-etc) Install package from the command line: * npm ```bash npm install --save connectycube ``` * yarn ```bash yarn add connectycube ``` #### Plain HTML [Section titled “Plain HTML”](#plain-html) Сonnect SDK js file as a normal script: ```html ``` #### Vite [Section titled “Vite”](#vite) If you use [Vite](https://vite.dev/) build tool, update `vite.config.ts` and add the following: ```js export default defineConfig({ plugins: ... esbuild: ... define: { // add this line for ConnectyCube to work properly global: {}, }, }) ``` Also, it needs to add polyfils for some node libs. Install the package as a dev dependency: * npm ```bash npm install --save-dev vite-plugin-node-polyfills ``` * yarn ```bash yarn add --dev vite-plugin-node-polyfills ``` Add the plugin to your **vite.config.ts** file: ```js import { defineConfig } from 'vite' import { nodePolyfills } from 'vite-plugin-node-polyfills' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ nodePolyfills(), ], }) ``` For Vite 6 to work, add the following code in **package.json**: ```json "overrides": { "vite-plugin-node-polyfills": { "vite": "^6.0.0" } } ``` ### Remix SSR (Server-Side Rendering) guide [Section titled “Remix SSR (Server-Side Rendering) guide”](#remix-ssr-server-side-rendering-guide) When using the ConnectyCube SDK in SSR environments like Remix, native Node.js modules such as “events” may cause runtime errors due to differences between Node and browser environments.\ To ensure smooth SSR integration, follow the steps below. Install ConnectyCube SDK and apply SSR patches: * npm ```bash npm install --save connectycube npx connectycube patch-ssr # apply SSR patches # npx connectycube revert-ssr # revert SSR patches (if needed) ``` * yarn ```bash yarn add connectycube yarn connectycube patch-ssr # apply SSR patches # yarn connectycube revert-ssr # revert SSR patches (if needed) ``` #### Remix Config Update [Section titled “Remix Config Update”](#remix-config-update) Add the following option in your **remix.config.js** or **remix.config.ts** file to polyfill the “events” module, which is required by ConnectyCube’s dependencies: ```javascript /** * @type {import('@remix-run/dev').AppConfig} */ const commonConfig = { // ... your existing config options ... browserNodeBuiltinsPolyfill: { modules: { events: true, }, }, }; export default remixConfig; ``` This enables polyfilling of the Node.js “events” module in the browser environment, preventing errors related to “@xmpp/events” and similar packages used internally by ConnectyCube SDK. ### Import SDK [Section titled “Import SDK”](#import-sdk) Add the following import statement to start using all classes and methods. ```javascript import ConnectyCube from 'connectycube'; ``` \**this is not needed for Plain HTML* ### Initialize SDK [Section titled “Initialize SDK”](#initialize-sdk) Initialize framework with your ConnectyCube application credentials. You can access your application credentials in [ConnectyCube Dashboard](https://admin.connectycube.com): * SDK v4 ```javascript const CREDENTIALS = { appId: 21, authKey: "hhf87hfushuiwef", }; ConnectyCube.init(CREDENTIALS); ``` * SDK v3 ```javascript const CREDENTIALS = { appId: 21, authKey: "hhf87hfushuiwef", authSecret: "jjsdf898hfsdfk", }; ConnectyCube.init(CREDENTIALS); ``` ## Step 2: Create and Authorise User [Section titled “Step 2: Create and Authorise User”](#step-2-create-and-authorise-user) As a starting point, the user’s session token needs to be created allowing to send and receive messages in chat. ```javascript const userCredentials = { login: "marvin18", password: "supersecurepwd" }; ConnectyCube.createSession(userCredentials) .then((session) => {}) .catch((error) => {}); ``` **Note:** With the request above, **the user is created automatically on the fly upon session creation** using the login (or email) and password from the request parameters. **Important:** such approach with the automatic user creation works well for testing purposes and while the application isn’t launched on production. For better security it is recommended to deny the session creation without an existing user.\ For this, set ‘Session creation without an existing user entity’ to **Deny** under the **Application -> Overview -> Permissions** in the [admin panel](https://admin.connectycube.com/apps). ## Step 3: Connect User to chat [Section titled “Step 3: Connect User to chat”](#step-3-connect-user-to-chat) Connecting to the chat is an essential step in enabling real-time communication. By establishing a connection, the user is authenticated on the chat server, allowing them to send and receive messages instantly. Without this connection, the app won’t be able to interact with other users in the chat. ```javascript const userCredentials = { userId: 4448514, password: "supersecurepwd", }; ConnectyCube.chat .connect(userCredentials) .then(() => { // connected }) .catch((error) => {}); ``` ## Step 4: Create 1-1 chat [Section titled “Step 4: Create 1-1 chat”](#step-4-create-1-1-chat) Creating a 1-1 chat gives a unique conversation ID to correctly route and organize your message to the intended user. You need to pass `type: 3` (1-1 chat) and an **id** of an opponent you want to create a chat with: ```javascript const params = { type: 3, occupants_ids: [56], }; ConnectyCube.chat.dialog .create(params) .then((dialog) => {}) .catch((error) => {}); ``` ## Step 5: Send / Receive chat messages [Section titled “Step 5: Send / Receive chat messages”](#step-5-send--receive-chat-messages) Once the 1-1 chat is set up, you can use it to exchange messages seamlessly. The code below demonstrates how to send and receive messages within a specific 1-1 chat. ```javascript const dialog = ...; const opponentId = 56; const message = { type: dialog.type === 3 ? 'chat' : 'groupchat', body: "How are you today?", extension: { save_to_history: 1, dialog_id: dialog._id } }; message.id = ConnectyCube.chat.send(opponentId, message); // ... ConnectyCube.chat.onMessageListener = onMessage; function onMessage(userId, message) { console.log('[ConnectyCube.chat.onMessageListener] callback:', userId, message) } ``` Congratulations! You’ve mastered the basics of sending a chat message in ConnectyCube. #### What’s next? [Section titled “What’s next?”](#whats-next) To take your chat experience to the next level, explore ConnectyCube advanced functionalities, like adding typing indicators, using emojis, sending attachments, and more. Follow the [Chat API documentation](/js/messaging/) to enrich your app and engage your users even further! # Make first call > A guide with the essential steps of how to make the first call via ConnectyCube Web Video SDK - from session creation to initialising and accepting the call. **ConnectyCube Video Calling Peer-to-Peer (P2P) API** provides a solution for integrating real-time video and audio calling into your application. This API enables you to create smooth one-on-one and group video calls, supporting a wide range of use cases like virtual meetings, telemedicine consultations, social interactions, and more. The P2P approach ensures that media streams are transferred directly between users whenever possible, minimizing latency and delivering high-quality audio and video. If you’re planning to build a new app, we recommend starting with one of our [code samples apps](/js/getting-started/code-samples) as a foundation for your client app.\ If you already have an app and you are looking to add chat and voice/video calls to it, proceed with this guide. This guide walks you through installing the ConnectyCube SDK in your app, configure it and then initiating the call to the opponent. ## Before you start [Section titled “Before you start”](#before-you-start) Before you start, make sure: 1. You have access to ConnectyCube account. If you don’t have an account, [sign up here](https://admin.connectycube.com/register). 2. An app created in ConnectyCube dashboard. Once logged into [your ConnectyCube account](https://admin.connectycube.com/signin), create a new application and make a note of the app credentials (app ID and auth key) that you’ll need for authentication. ## Step 1: Configure SDK [Section titled “Step 1: Configure SDK”](#step-1-configure-sdk) To use chat in a client app, you should install, import and configure ConnectyCube SDK. **Note:** If the app is already created during the onboarding process and you followed all the instructions, you can skip the ‘Configure SDK’ step and start with [Create and Authorise User](#step-2-create-and-authorise-user). ### Install SDK [Section titled “Install SDK”](#install-sdk) #### React, Angular, Vue etc. [Section titled “React, Angular, Vue etc.”](#react-angular-vue-etc) Install package from the command line: * npm ```bash npm install --save connectycube ``` * yarn ```bash yarn add connectycube ``` #### Plain HTML [Section titled “Plain HTML”](#plain-html) Сonnect SDK js file as a normal script: ```html ``` #### Vite [Section titled “Vite”](#vite) If you use [Vite](https://vite.dev/) build tool, update `vite.config.ts` and add the following: ```js export default defineConfig({ plugins: ... esbuild: ... define: { // add this line for ConnectyCube to work properly global: {}, }, }) ``` Also, it needs to add polyfils for some node libs. Install the package as a dev dependency: * npm ```bash npm install --save-dev vite-plugin-node-polyfills ``` * yarn ```bash yarn add --dev vite-plugin-node-polyfills ``` Add the plugin to your **vite.config.ts** file: ```js import { defineConfig } from 'vite' import { nodePolyfills } from 'vite-plugin-node-polyfills' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ nodePolyfills(), ], }) ``` For Vite 6 to work, add the following code in **package.json**: ```json "overrides": { "vite-plugin-node-polyfills": { "vite": "^6.0.0" } } ``` ### Remix SSR (Server-Side Rendering) guide [Section titled “Remix SSR (Server-Side Rendering) guide”](#remix-ssr-server-side-rendering-guide) When using the ConnectyCube SDK in SSR environments like Remix, native Node.js modules such as “events” may cause runtime errors due to differences between Node and browser environments.\ To ensure smooth SSR integration, follow the steps below. Install ConnectyCube SDK and apply SSR patches: * npm ```bash npm install --save connectycube npx connectycube patch-ssr # apply SSR patches # npx connectycube revert-ssr # revert SSR patches (if needed) ``` * yarn ```bash yarn add connectycube yarn connectycube patch-ssr # apply SSR patches # yarn connectycube revert-ssr # revert SSR patches (if needed) ``` #### Remix Config Update [Section titled “Remix Config Update”](#remix-config-update) Add the following option in your **remix.config.js** or **remix.config.ts** file to polyfill the “events” module, which is required by ConnectyCube’s dependencies: ```javascript /** * @type {import('@remix-run/dev').AppConfig} */ const commonConfig = { // ... your existing config options ... browserNodeBuiltinsPolyfill: { modules: { events: true, }, }, }; export default remixConfig; ``` This enables polyfilling of the Node.js “events” module in the browser environment, preventing errors related to “@xmpp/events” and similar packages used internally by ConnectyCube SDK. ### Import SDK [Section titled “Import SDK”](#import-sdk) Add the following import statement to start using all classes and methods. ```javascript import ConnectyCube from 'connectycube'; ``` \**this is not needed for Plain HTML* ### Initialize SDK [Section titled “Initialize SDK”](#initialize-sdk) Initialize framework with your ConnectyCube application credentials. You can access your application credentials in [ConnectyCube Dashboard](https://admin.connectycube.com): * SDK v4 ```javascript const CREDENTIALS = { appId: 21, authKey: "hhf87hfushuiwef", }; ConnectyCube.init(CREDENTIALS); ``` * SDK v3 ```javascript const CREDENTIALS = { appId: 21, authKey: "hhf87hfushuiwef", authSecret: "jjsdf898hfsdfk", }; ConnectyCube.init(CREDENTIALS); ``` ## Step 2: Create and Authorise User [Section titled “Step 2: Create and Authorise User”](#step-2-create-and-authorise-user) As a starting point, the user’s session token needs to be created allowing to participate in calls. ```javascript const userCredentials = { login: "marvin18", password: "supersecurepwd" }; ConnectyCube.createSession(userCredentials) .then((session) => {}) .catch((error) => {}); ``` **Note:** With the request above, **the user is created automatically on the fly upon session creation** using the login (or email) and password from the request parameters. **Important:** such approach with the automatic user creation works well for testing purposes and while the application isn’t launched on production. For better security it is recommended to deny the session creation without an existing user.\ For this, set ‘Session creation without an existing user entity’ to **Deny** under the **Application -> Overview -> Permissions** in the [admin panel](https://admin.connectycube.com/apps). ## Step 3: Connect User to chat [Section titled “Step 3: Connect User to chat”](#step-3-connect-user-to-chat) Connecting to the chat is an essential step in enabling real-time communication. To start using Video Calling API you need to connect user to Chat as ConnectyCube Chat API is used as a signalling transport for Video Calling API: ```javascript const userCredentials = { userId: 4448514, password: "supersecurepwd", }; ConnectyCube.chat .connect(userCredentials) .then(() => { // connected }) .catch((error) => {}); ``` ## Step 4: Create video session [Section titled “Step 4: Create video session”](#step-4-create-video-session) In order to use Video Calling API you need to create a call session object - choose your opponents with whom you will have a call and a type of session (VIDEO or AUDIO). > **Note** > > The calleesIds array must contain the opponents ids only and exclude current user id. ```javascript const calleesIds = [56, 76, 34]; // User's ids const sessionType = ConnectyCube.videochat.CallType.VIDEO; // AUDIO is also possible const additionalOptions = {}; const session = ConnectyCube.videochat.createNewSession(calleesIds, sessionType, additionalOptions); ``` ## Step 5: Access local media stream [Section titled “Step 5: Access local media stream”](#step-5-access-local-media-stream) In order to have a video chat session you need to get an access to the user’s devices (webcam / microphone): ```javascript const mediaParams = { audio: true, video: true }; session .getUserMedia(mediaParams) .then((localStream) => { // display local stream session.attachMediaStream("myVideoElementId", localStream, { muted: true, mirror: true, }); }) .catch((error) => {}); ``` This method lets the browser ask the user for permission to use devices. You should allow this dialog to access the stream. Otherwise, the browser can’t obtain access and will throw an error for `getUserMedia` callback function. ## Step 6: Initiate a call [Section titled “Step 6: Initiate a call”](#step-6-initiate-a-call) Use the code below to initiate call with the opponent(s): ```javascript const extension = {}; session.call(extension, (error) => {}); ``` The extension is used to pass any extra parameters in the request to your opponents. After this, your opponents will receive a callback call: ```javascript ConnectyCube.videochat.onCallListener = function (session, extension) {}; ``` Or if your opponents are offline or did not answer the call request: ```javascript ConnectyCube.videochat.onUserNotAnswerListener = function (session, userId) {}; ``` ## Step 7: Accept a call [Section titled “Step 7: Accept a call”](#step-7-accept-a-call) To accept a call the following code snippet is used: ```javascript ConnectyCube.videochat.onCallListener = function (session, extension) { // Here we need to show a dialog with 2 buttons - Accept & Reject. // By accepting -> run the following code: // // 1. await session.getUserMedia (...) // // 2. Accept call request: const extension = {}; session.accept(extension); }; ``` After this, you will get a confirmation in the following callback: ```javascript ConnectyCube.videochat.onAcceptCallListener = function (session, userId, extension) {}; ``` Also, both the caller and opponents will get a special callback with the remote stream: ```javascript ConnectyCube.videochat.onRemoteStreamListener = function (session, userID, remoteStream) { // attach the remote stream to DOM element session.attachMediaStream("remoteOpponentVideoElementId", remoteStream); }; ``` Great work! You’ve completed the essentials of making a call in ConnectyCube. From this point, you and your opponents should start seeing each other. #### What’s next? [Section titled “What’s next?”](#whats-next) To enhance your calling feature with advanced functionalities, such as call recording, screen sharing, or integrating emojis and attachments during calls, follow the API guides below. These additions will help create a more dynamic and engaging experience for your users! * [Voice/video calling SDK documentation](/js/videocalling) * [Conference calling SDK documentation](/js/videocalling-conference/) # Chat > Integrate powerful chat functionality into your Web app effortlessly with our versatile Chat APIs. Enhance user communication and engagement. ConnectyCube Chat API is built on top of Real-time(XMPP) protocol. In order to use it you need to setup real-time connection with ConnectyCube Chat server and use it to exchange data. By default Real-time Chat works over secure TLS connection. ## Connect to chat [Section titled “Connect to chat”](#connect-to-chat) ```javascript const userCredentials = { userId: 4448514, password: "awesomepwd", }; ConnectyCube.chat .connect(userCredentials) .then(() => { // connected }) .catch((error) => {}); ``` ### Connect to chat using custom authentication providers [Section titled “Connect to chat using custom authentication providers”](#connect-to-chat-using-custom-authentication-providers) In some cases we don’t have a user’s password, for example when login via: * Facebook * Twitter * Firebase phone authorization * Custom identity authentication * etc. In such cases ConnectyCube API provides possibility to use ConnectyCube session token as a password for chat connection: ```java // get current ConnectyCube session token and set as user's password const token = ConnectyCube.service.sdkInstance.session.token; const userCredentials = { userId: 4448514, password: token }; ``` ## Connection status [Section titled “Connection status”](#connection-status) The following snippet can be used to determine whether a user is connected to chat or not: ```javascript const isConnected = ConnectyCube.chat.isConnected; ``` ## Disconnect [Section titled “Disconnect”](#disconnect) ```javascript ConnectyCube.chat.disconnect(); ConnectyCube.chat.onDisconnectedListener = onDisconnectedListener; function onDisconnectedListener() {} ``` ## Reconnection [Section titled “Reconnection”](#reconnection) The SDK reconnects automatically when connection to Chat server is lost. The following 2 callbacks are used to track the state of connection: ```javascript ConnectyCube.chat.onDisconnectedListener = onDisconnectedListener; ConnectyCube.chat.onReconnectListener = onReconnectListener; function onDisconnectedListener() {} function onReconnectListener() {} ``` ## Dialogs [Section titled “Dialogs”](#dialogs) All chats between users are organized in dialogs. The are 4 types of dialogs: * 1-1 chat - a dialog between 2 users. * group chat - a dialog between specified list of users. * public group chat - an open dialog. Any user from your app can chat there. * broadcast - chat where a message is sent to all users within application at once. All the users from the application are able to join this group. Broadcast dialogs can be created only via Admin panel. You need to create a new dialog and then use it to chat with other users. You also can obtain a list of your existing dialogs. ## Create new dialog [Section titled “Create new dialog”](#create-new-dialog) ### Create 1-1 chat [Section titled “Create 1-1 chat”](#create-1-1-chat) You need to pass `type: 3` (1-1 chat) and an id of an opponent you want to create a chat with: ```javascript const params = { type: 3, occupants_ids: [56], }; ConnectyCube.chat.dialog .create(params) .then((dialog) => {}) .catch((error) => {}); ``` ### Create group chat [Section titled “Create group chat”](#create-group-chat) You need to pass `type: 2` and ids of opponents you want to create a chat with: ```javascript const params = { type: 2, name: "Friday party", occupants_ids: [29085, 29086, 29087], description: "lets dance the night away", photo: "party.jpg", }; ConnectyCube.chat.dialog .create(params) .then((dialog) => {}) .catch((error) => {}); ``` ### Create public group chat [Section titled “Create public group chat”](#create-public-group-chat) It’s possible to create a public group chat, so any user from you application can join it. There is no a list with occupants, this chat is just open for everybody. You need to pass `type: 4` and ids of opponents you want to create a chat with: ```javascript const params = { type: 4, name: "Blockchain trends", }; ConnectyCube.chat.dialog .create(params) .then((dialog) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.create(params)` - [see](/server/chat#response-1) ### Chat metadata [Section titled “Chat metadata”](#chat-metadata) A dialog can have up to 3 custom sub-fields to store additional information that can be linked to chat. To start using extensions, allowed fields should be added first. Go to [Admin panel](https://admin.connectycube.com) > Chat > Custom Fields and provide allowed custom fields. ![Dialog Extensions fields configuration example](/_astro/dialog_custom_params.CrGT0s8Z_1XWSCw.webp) When create a dialog, the `extensions` field object must contain allowed fields only. Others fields will be ignored. The values will be casted to string. ```javascript const params = { type: 2, name: "Friday party", occupants_ids: [29085, 29086, 29087], description: "lets dance the night away", extensions: {location: "Sun bar"}, }; ConnectyCube.chat.dialog .create(params) .then((dialog) => {}) .catch((error) => {}); ``` When remove custom field in Admin panel, this field will be removed in all dialogs respectively. These parameters also can be used as a filter for retrieving dialogs. ### Chat permissions [Section titled “Chat permissions”](#chat-permissions) Chat could have different permissions to managa data access. This is managed via `permissions` field. At the moment, only one permission available - `allow_preview` - which allows to retrieve dialog’s messages for user who is not a member of dialog. This is useful when implement feature like Channels where a user can open chat and preview messages w/o joining it. ## List dialogs [Section titled “List dialogs”](#list-dialogs) It’s common to request all your dialogs on every app login: ```javascript const filters = {}; ConnectyCube.chat.dialog .list(filters) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.list(filters)` - [see](/server/chat#response) More filters available [here](/server/chat#retrieve-chat-dialogs) If you want to retrieve only dialogs updated after some specific date time, you can use `updated_at[gt]` filter. This is useful if you cache dialogs somehow and do not want to obtain the whole list of your dialogs on every app start. ## Update dialog [Section titled “Update dialog”](#update-dialog) User can update group chat name, photo or add/remove occupants: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const toUpdateParams = { name: "Crossfit2" }; ConnectyCube.chat.dialog .update(dialogId, toUpdateParams) .then((dialog) => {}) .catch((error) => {}); ``` ## Add/Remove occupants [Section titled “Add/Remove occupants”](#addremove-occupants) To add more occupants use `push_all` operator. To remove yourself from the dialog use `pull_all` operator: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const toUpdateParams = { push_all: { occupants_ids: [97, 789] } }; ConnectyCube.chat.dialog .update(dialogId, toUpdateParams) .then((dialog) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.update(dialogId, toUpdateParams)` - [see](/server/chat#response-2) > **Note** > > Only group chat owner can remove other users from group chat. ## Remove dialog [Section titled “Remove dialog”](#remove-dialog) The following snippet is used to delete a dialog: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; // const dialogIds = ['5356c64ab35c12bd3b108a41', ..., '5356c64ab35c12bd3b108a84'] ConnectyCube.chat.dialog.delete(dialogId).catch((error) => {}); ``` This request will remove this dialog for current user, but other users still will be able to chat there. Only group chat owner can remove the group dialog for all users. You can also delete multiple dialogs in a single request. ## Clear dialog history [Section titled “Clear dialog history”](#clear-dialog-history) The following snippet is used to clear dialog history by ID: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog.clearHistory(dialogId).catch((error) => {}); ``` This request will clear all messages in the dialog for current user, but not for other users. ## Subscribe to dialog [Section titled “Subscribe to dialog”](#subscribe-to-dialog) In order to be able to chat in public dialog, you need to subscribe to it: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog .subscribe(dialogId) .then((dialog) => {}) .catch((error) => {}); ``` It’s also possible to subscribe to group chat dialog. Response example from `ConnectyCube.chat.dialog.subscribe(dialogId)` - [see](/server/chat#response-5) ## Unsubscribe from dialog [Section titled “Unsubscribe from dialog”](#unsubscribe-from-dialog) ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog.unsubscribe(dialogId).catch((error) => {}); ``` ## Retrieve public dialog occupants [Section titled “Retrieve public dialog occupants”](#retrieve-public-dialog-occupants) A public chat dialog can have many occupants. There is a separated API to retrieve a list of public dialog occupants: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const params = { limit: 100, skip: 0, }; ConnectyCube.chat.dialog .getPublicOccupants(dialogId, params) .then((result) => { // result.items }) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.getPublicOccupants(dialogId, params)`: ```json { "items": [ { "id": 51941, "full_name": "Dacia Kail", "email": "dacia_k@domain.com", "login": "Dacia", "phone": "+6110797757", "website": null, "created_at": "2018-12-06T09:16:26Z", "updated_at": "2018-12-06T09:16:26Z", "last_request_at": null, "external_user_id": 52691165, "facebook_id": "91234409", "twitter_id": "83510562734", "blob_id": null, "custom_data": null, "avatar": null, "user_tags": null }, { "id": 51946, "full_name": "Gabrielle Corcoran", "email": "gabrielle.corcoran@domain.com", "login": "gabby", "phone": "+6192622155", "website": "http://gabby.com", "created_at": "2018-12-06T09:29:57Z", "updated_at": "2018-12-06T09:29:57Z", "last_request_at": null, "external_user_id": null, "facebook_id": "95610574", "twitter_id": null, "blob_id": null, "custom_data": "Responsible for signing documents", "avatar": null, "user_tags": "vip,accountant" } ... ] } ``` ## Add / Remove admins [Section titled “Add / Remove admins”](#add--remove-admins) Options to add or remove admins from the dialog can be done by Super admin (dialog’s creator) only. Options are supported in group chat, public or broadcast. Up to 5 admins can be added to chat. ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const adminsUsersIds = [45, 89]; ConnectyCube.chat.dialog .addAdmins(dialogId, adminsUsersIds) .then((dialog) => {}) .catch((error) => {}); ``` ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const adminsUsersIds = [45, 89]; ConnectyCube.chat.dialog .removeAdmins(dialogId, adminsUsersIds) .then((dialog) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.addAdmins(dialogId, adminsUsersIds)`/`ConnectyCube.chat.dialog.removeAdmins(dialogId, adminsUsersIds)` - [see](/server/chat#response-7) ## Update notifications settings [Section titled “Update notifications settings”](#update-notifications-settings) A user can turn on/off push notifications for offline messages in a dialog. By default push notification are turned ON, so offline user receives push notifications for new messages in a chat. ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const enabled = false; ConnectyCube.chat.dialog .updateNotificationsSettings(dialogId, enabled) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.updateNotificationsSettings(dialogId, enabled)` - [see](/server/chat#response-8) ## Get notifications settings [Section titled “Get notifications settings”](#get-notifications-settings) Check a status of notifications setting - either it is ON or OFF for a particular chat. Available responses: 1 - enabled, 0 - disabled. ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog .getNotificationsSettings(dialogId) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.getNotificationsSettings(dialogId)` - [see](/server/chat#response-9) ## Chat history [Section titled “Chat history”](#chat-history) Every chat dialog stores its chat history which you can retrieve: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const params = { chat_dialog_id: dialogId, sort_desc: "date_sent", limit: 100, skip: 0, }; ConnectyCube.chat.message .list(params) .then((messages) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.message.list(params)` - [see](/server/chat#response-10) If you want to retrieve chat messages that were sent after or before specific date time only, you can use `date_sent[gt]` or `date_sent[lt]` filter. This is useful if you implement pagination for loading messages in your app. ## Send/Receive chat messages [Section titled “Send/Receive chat messages”](#sendreceive-chat-messages) ### 1-1 chat [Section titled “1-1 chat”](#1-1-chat) ```javascript const dialog = ...; const opponentId = 78; const message = { type: dialog.type === 3 ? 'chat' : 'groupchat', body: "How are you today?", extension: { save_to_history: 1, dialog_id: dialog._id } }; message.id = ConnectyCube.chat.send(opponentId, message); // ... ConnectyCube.chat.onMessageListener = onMessage; function onMessage(userId, message) { console.log('[ConnectyCube.chat.onMessageListener] callback:', userId, message) } ``` ### Group chat [Section titled “Group chat”](#group-chat) > The group chat join is not a required step anymore. You can send/receive chat messages in a group chat w/o joining it. Before you start chatting in a group dialog, you need to join it by calling `join` function: ```javascript const dialog = ...; ConnectyCube.chat.muc.join(dialog._id).catch(error => {}); ``` Then you are able to send/receive messages: ```javascript const message = { type: dialog.type === 3 ? "chat" : "groupchat", body: "How are you today?", extension: { save_to_history: 1, dialog_id: dialog._id, } }; message.id = ConnectyCube.chat.send(dialog._id, message); // ... ConnectyCube.chat.onMessageListener = onMessage; function onMessage(userId, message) { console.log("[ConnectyCube.chat.onMessageListener] callback:", userId, message); } ``` When it’s done you can leave the group dialog by calling `leave` function: ```javascript ConnectyCube.chat.muc.leave(dialog._id).catch((error) => {}); ``` ## Message metadata [Section titled “Message metadata”](#message-metadata) A chat message can have custom sub-fields to store additional information that can be linked to the particular chat message. When create a message, the custom data can be attached via `extension` field: ```javascript const message = { ... extension: { field_one: "value_one", field_two: "value_two" } }; ``` ## ‘Sent’ status [Section titled “‘Sent’ status”](#sent-status) There is a ‘sent’ status to ensure that message is delivered to the server. In order to use the feature you need to enable it when you pass config in `ConnectyCube.init`: ```javascript chat: { streamManagement: { enable: true; } } ``` The following callback is used to track it: ```javascript ConnectyCube.chat.onSentMessageCallback = function (messageLost, messageSent) {}; ``` ## ‘Delivered’ status [Section titled “‘Delivered’ status”](#delivered-status) The following callback is used to track the ‘delivered’ status: ```javascript ConnectyCube.chat.onDeliveredStatusListener = function (messageId, dialogId, userId) { console.log("[ConnectyCube.chat.onDeliveredStatusListener] callback:", messageId, dialogId, userId); }; ``` The SDK sends the ‘delivered’ status automatically when the message is received by the recipient. This is controlled by `markable: 1` parameter when you send a message. If `markable` is `0` or omitted, then you can send the delivered status manually: ```javascript const params = { messageId: "557f1f22bcf86cd784439022", userId: 21, dialogId: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.sendDeliveredStatus(params); ``` ## ‘Read’ status [Section titled “‘Read’ status”](#read-status) Send the ‘read’ status: ```javascript const params = { messageId: "557f1f22bcf86cd784439022", userId: 21, dialogId: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.sendReadStatus(params); // ... ConnectyCube.chat.onReadStatusListener = function (messageId, dialogId, userId) { console.log("[ConnectyCube.chat.onReadStatusListener] callback:", messageId, dialogId, userId); }; ``` ## ‘Is typing’ status [Section titled “‘Is typing’ status”](#is-typing-status) The following ‘typing’ notifications are supported: * typing: The user is composing a message. The user is actively interacting with a message input interface specific to this chat session (e.g., by typing in the input area of a chat window) * stopped: The user had been composing but now has stopped. The user has been composing but has not interacted with the message input interface for a short period of time (e.g., 30 seconds) Send the ‘is typing’ status: ```javascript const opponentId = 78; // for 1-1 chats // const dialogJid = ..; // for group chat ConnectyCube.chat.sendIsTypingStatus(opponentId); ConnectyCube.chat.sendIsStopTypingStatus(opponentId); // ... ConnectyCube.chat.onMessageTypingListener = function (isTyping, userId, dialogId) { console.log("[ConnectyCube.chat.onMessageTypingListener] callback:", isTyping, userId, dialogId); }; ``` ## Attachments (photo / video) [Section titled “Attachments (photo / video)”](#attachments-photo--video) Chat attachments are supported with the cloud storage API. In order to send a chat attachment you need to upload the file to ConnectyCube cloud storage and obtain a link to the file (file UID). Then you need to include this UID into chat message and send it. ```javascript // for example, a file from HTML form input field const inputFile = $("input[type=file]")[0].files[0]; const fileParams = { name: inputFile.name, file: inputFile, type: inputFile.type, size: inputFile.size, public: false, }; const prepareMessageWithAttachmentAndSend = (file) => { const message = { type: dialog.type === 3 ? "chat" : "groupchat", body: "attachment", extension: { save_to_history: 1, dialog_id: dialog._id, attachments: [{ uid: file.uid, id: file.id, type: "photo" }], }, }; // send the message message.id = ConnectyCube.chat.send(dialog._id, message); }; ConnectyCube.storage .createAndUpload(fileParams) .then(prepareMessageWithAttachmentAndSend) .catch((error) => {}); ``` Response example from `ConnectyCube.storage.createAndUpload(fileParams)`: ```json { "account_id": 7, "app_id": 12, "blob_object_access": { "blob_id": 421517, "expires": "2020-10-06T15:51:38Z", "id": 421517, "object_access_type": "Write", "params": "https://s3.amazonaws.com/cb-shared-s3?Content-Type=text%2Fplain..." }, "blob_status": null, "content_type": "text/plain", "created_at": "2020-10-06T14:51:38Z", "id": 421517, "name": "awesome.txt", "public": false, "set_completed_at": null, "size": 11, "uid": "7cafb6030d3e4348ba49cab24c0cf10800", "updated_at": "2020-10-06T14:51:38Z" } ``` If you are running **Node.js** environment, the following code snippet can be used to access a file: ```javascript const fs = require("fs"); const imagePath = __dirname + "/dog.jpg"; let fileParams; fs.stat(imagePath, (error, stats) => { fs.readFile(srcIMG, (error, data) => { if (error) { throw error; } else { fileParams = { file: data, name: "image.jpg", type: "image/jpeg", size: stats.size, }; // upload // ... } }); }); ``` The same flow is supported on the receiver’s side. When you receive a message, you need to get the file UID and then download the file from the cloud storage. ```javascript ConnectyCube.chat.onMessageListener = (userId, message) => { if (message.extension.hasOwnProperty("attachments")) { if (message.extension.attachments.length > 0) { const fileUID = message.extension.attachments[0].uid; const fileUrl = ConnectyCube.storage.privateUrl(fileUID); const imageHTML = "photo"; // insert the imageHTML as HTML template } } }; ``` In a case you want remove a shared attachment from server: ```javascript ConnectyCube.storage .delete(file.id) .then(() => {}) .catch((error) => {}); ``` ## Attachments (location) [Section titled “Attachments (location)”](#attachments-location) Sharing location attachments is nothing but sending 2 numbers: **latitude** and **longitude**. These values can be accessed using any JS library available in npm registry. ```javascript const latitude = "64.7964274"; const longitude = "-23.7391878"; const message = { type: dialog.type === 3 ? "chat" : "groupchat", body: "attachment", extension: { save_to_history: 1, dialog_id: dialog._id, attachments: [{ latitude, longitude, type: "place" }], }, }; // send the message message.id = ConnectyCube.chat.send(dialog._id, message); ``` On the receiver’s side the location attachment can be accessed the following way: ```javascript ConnectyCube.chat.onMessageListener = (userId, message) => { if (message.extension.hasOwnProperty("attachments")) { if (message.extension.attachments.length > 0) { const attachment = message.extension.attachments[0]; const latitude = attachment.latitude; const longitude = attachment.longitude; // and now display the map // ... } } }; ``` ## Edit message [Section titled “Edit message”](#edit-message) Use the following code snippet to edit a message (correct message body). Other user(s) will receive the ‘edit’ message info via callback: ```javascript ConnectyCube.chat.editMessage({ to: 123, // either a user id if this is 1-1 chat or a chat dialog id dialogId: "52e6a9c8a18f3a3ea6001f18", body: "corrected message body", originMessageId: "58e6a9c8a1834a3ea6001f15", // origin message id to edit last: false // pass 'true' if edit last (the newest) message in history }) ... ConnectyCube.chat.onMessageUpdateListener = (messageId, isLast, updatedBody, dialogId, userId) => { } ``` Also, you can update a message via HTTP API: ```javascript // const messageIds = ""; // to update all const messageIds = ["55fd42369575c12c2e234c64", "55fd42369575c12c2e234c68"].join(","); // or one - "55fd42369575c12c2e234c64" const params = { read: 1, // mark message as read delivered: 1, // mark message as delivered message: "corrected message body", // update message body chat_dialog_id: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.message .update(messageIds, params) .then(() => {}) .catch((error) => {}); ``` ## Message reactions [Section titled “Message reactions”](#message-reactions) ### Add/Remove reactions [Section titled “Add/Remove reactions”](#addremove-reactions) User can add/remove message reactions and listen message reaction events Add ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' const reaction = '🔥' ConnectyCube.chat.message.addReaction(messageId, reaction) .then(() => {}) .catch(err => {}) ``` Remove ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' const reaction = '👎' ConnectyCube.chat.message.removeReaction(messageId, reaction) .then(() => {}) .catch(err => {}) ``` Add/Remove ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' const reactionToAdd = '👎' const reactionToRemove = '🚀' ConnectyCube.chat.message.updateReaction(messageId, reactionToAdd, reactionToRemove) .then(() => {}) .catch(err => {}) ``` ### Listen reactions [Section titled “Listen reactions”](#listen-reactions) ```javascript ConnectyCube.chat.onMessageReactionsListener = (messageId, userId, dialogId, addReaction, removeReaction) => { } ``` ### List message reactions [Section titled “List message reactions”](#list-message-reactions) User can list message reactions ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' ConnectyCube.chat.message.listReactions(messageId) .then(reactions => reactions) .catch(err => {}) ``` Response example from `ConnectyCube.chat.message.listReactions(messageId)` - [see](/server/chat#response-19) ## Delete messages [Section titled “Delete messages”](#delete-messages) Use the following code snippet to delete a message. Other user(s) will receive the ‘delete’ message info via callback: ```javascript ConnectyCube.chat.deleteMessage({ to: 123, // either a user id if this is 1-1 chat or a chat dialog id dialogId: "52e6a9c8a18f3a3ea6001f18", messageId: "58e6a9c8a1834a3ea6001f15" // message id to delete }) ... ConnectyCube.chat.onMessageDeleteListener = (messageId, dialogId, userId) => { } ``` If you want to delete a message for yourself only, use the following API: ```javascript const messageIds = ["55fd42369575c12c2e234c64", "55fd42369575c12c2e234c68"].join(","); const params = {}; ConnectyCube.chat.message .delete(messageIds, params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.message.delete(messageIds)` - [see](/server/chat#response-14) This request will remove the messages from current user history only, without affecting the history of other users. ## Unread messages count [Section titled “Unread messages count”](#unread-messages-count) You can request total unread messages count and unread count for particular dialog: ```javascript const params = { dialogs_ids: ["5356c64ab35c12bd3b108a41"] }; ConnectyCube.chat.message .unreadCount(params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.message.unreadCount(params)` - [see](/server/chat#response-11) ## Mark as read all chat messages [Section titled “Mark as read all chat messages”](#mark-as-read-all-chat-messages) The following snippet is used to mark all messages as read on a backend for dialog ID: ```javascript const messageIds = ""; // use "" to update all const params = { read: 1, chat_dialog_id: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.message .update("", params) .then(() => {}) .catch((error) => {}); ``` ## Search [Section titled “Search”](#search) The following API is used to search for messages and chat dialogs: ```javascript const params = { /* ... */ }; ConnectyCube.chat .search(params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.search(params)` - [see](/server/chat#response-15) Please refer to [Global search parameters](/server/chat#global-search) for more info on how to form search params. ## Chat alerts [Section titled “Chat alerts”](#chat-alerts) When you send a chat message and the recipient/recipients is offline, then automatic push notification will be fired. In order to receive push notifications you need to subscribe for it. Please refer to [Push Notifications](/js/push-notifications) guide. To configure push template which users receive - go to [Dashboard Console, Chat Alerts page](https://admin.connectycube.com/) Also, here is a way to avoid automatically sending push notifications to offline recipient/recipients. For it add the `silent` parameter with value `1` to the `extension` of the message. ```javascript const message = { ... extension: { ... silent: 1, }, }; ``` After sending such a message, the server won’t create the push notification for offline recipient/recipients. > **Note** > > Currently push notifications are supported on mobile environment only. ## Mark a client as Active/Inactive [Section titled “Mark a client as Active/Inactive”](#mark-a-client-as-activeinactive) When you send a chat message and the recipient/recipients is offline, then automatic push notification will be fired. Sometimes a client app can be in a background mode, but still online. In this case it’s useful to let server know that a user wants to receive push noificattions while still is connected to chat. For this particular case we have 2 handy methods: ‘markInactive’ and ‘markActive’: ```javascript ConnectyCube.chat.markInactive(); ``` ```javascript ConnectyCube.chat.markActive(); ``` The common use case for these APIs is to call ‘markInactive’ when an app goes to background mode and to call ‘markActive’ when an app goes to foreground mode. ## Get last activity [Section titled “Get last activity”](#get-last-activity) There is a way to get an info when a user was active last time, in seconds. This is a modern approach for messengers apps, e.g. to display this info on a Contacts screen or on a User Profile screen. ```javascript const userId = 123234; ConnectyCube.chat .getLastUserActivity(userId) .then((result) => { const userId = result.userId; const seconds = result.seconds; // 'userId' was 'seconds' ago }) .catch((error) => {}); ``` ## Last activity subscription [Section titled “Last activity subscription”](#last-activity-subscription) Listen to user last activity status via subscription. ```javascript ConnectyCube.chat.subscribeToUserLastActivityStatus(userId); ConnectyCube.chat.unsubscribeFromUserLastActivityStatus(userId); ConnectyCube.chat.onLastUserActivityListener = (userId, seconds) => {}; ``` ## System messages [Section titled “System messages”](#system-messages) In a case you want to send a non text message data, e.g. some meta data about chat, some events or so - there is a system notifications API to do so: ```javascript const userId = 123234; const msg = { body: "dialog/UPDATE_DIALOG", extension: { photo_uid: "7cafb6030d3e4348ba49cab24c0cf10800", name: "Our photos", }, }; ConnectyCube.chat.sendSystemMessage(userId, msg); ConnectyCube.chat.onSystemMessageListener = function (msg) {}; ``` ## Moderation [Section titled “Moderation”](#moderation) The moderation capabilities help maintain a safe and respectful chat environment. We have options that allow users to report inappropriate content and manage their personal block lists, giving them more control over their experience. ### Report user [Section titled “Report user”](#report-user) For user reporting to work, it requires the following: 1. Go to [ConnectyCube Daashboard](https://admin.connectycube.com/) 2. select your Application 3. Navigate to **Custom** module via left sidebar 4. Create new table called **UserReports** with the following fields: * **reportedUserId** - integer * **reason** - string ![Chat widget: report table in ConnectyCube dashboard](/images/chat_widget/chat-widget-report-table.png) Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```javascript const reportedUserId = 45 const reason = "User is spamming with bad words" await ConnectyCube.data.create('UserReports', { reportedUserId, reason }); ``` ### Report message [Section titled “Report message”](#report-message) For message reporting to work, the same approach to user reporting above could be used. You need to create new table called **MessageReports** with the following fields: * **reportedMessageId** - integer * **reason** - string Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```javascript const reportedMessageId = "58e6a9c8a1834a3ea6001f15" const reason = "The message contains phishing links" await ConnectyCube.data.create('MessageReports', { reportedMessageId, reason }); ``` ### Block user [Section titled “Block user”](#block-user) Block list (aka Privacy list) allows enabling or disabling communication with other users. You can create, modify, or delete privacy lists, define a default list. > The user can have multiple privacy lists, but only one can be active. #### Create privacy list [Section titled “Create privacy list”](#create-privacy-list) A privacy list must have at least one element in order to be created. You can choose a type of blocked logic. There are 2 types: * Block in one way. When you blocked a user, but you can send messages to him. * Block in two ways. When you blocked a user and you also can’t send messages to him. ```javascript const users = [ { user_id: 34, action: "deny" }, { user_id: 48, action: "deny", mutualBlock: true }, // it means you can't write to user { user_id: 18, action: "allow" }, ]; const list = { name: "myList", items: users }; ConnectyCube.chat.privacylist.create(list).catch((error) => {}); ``` > In order to be used the privacy list should be not only set, but also activated(set as default). #### Activate privacy list [Section titled “Activate privacy list”](#activate-privacy-list) In order to activate rules from a privacy list you should set it as default: ```javascript const listName = "myList"; ConnectyCube.chat.privacylist.setAsDefault(listName).catch((error) => {}); ``` #### Update privacy list [Section titled “Update privacy list”](#update-privacy-list) There is a rule you should follow to update a privacy list: * If you want to update or set new privacy list instead of current one, you should decline current default list first. ```javascript const listName = "myList"; const list = { name: listName, items: [{ user_id: 34, action: "allow" }], }; ConnectyCube.chat.privacylist .setAsDefault(null) .then(() => ConnectyCube.chat.privacylist.update(list)) .then(() => ConnectyCube.chat.privacylist.setAsDefault(listName)) .catch((error) => {}); ``` #### Retrieve privacy list names [Section titled “Retrieve privacy list names”](#retrieve-privacy-list-names) To get a list of all your privacy lists’ names use the following request: ```javascript let names; ConnectyCube.chat.privacylist .getNames() .then((response) => (names = response.names)) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.privacylist.getNames()`: ```json { "active": null, "default": null, "names": ["myList", "blockedusers"] } ``` #### Retrieve privacy list with name [Section titled “Retrieve privacy list with name”](#retrieve-privacy-list-with-name) To get the privacy list by name you should use the following method: ```javascript const listName = "myList"; let name, items; ConnectyCube.chat.privacylist.getList(listName).then((response) => { name = response.name; items = response.items; }); ``` Response example from `ConnectyCube.chat.privacylist.getList(listName)`: ```json { "name": "myList", "items": [ { "user_id": 34, "action": "deny" }, { "user_id": 48, "action": "deny", "mutualBlock": true }, { "user_id": 18, "action": "allow" } ] } ``` #### Remove privacy list [Section titled “Remove privacy list”](#remove-privacy-list) To delete a list you can call a method below or you can edit a list and set items to `nil`. ```javascript const listName = "myList"; ConnectyCube.chat.privacylist.delete(listName).catch((error) => {}); ``` #### Blocked user attempts to communicate with user [Section titled “Blocked user attempts to communicate with user”](#blocked-user-attempts-to-communicate-with-user) Blocked users will be receiving an error when trying to chat with a user in a 1-1 chat and will be receiving nothing in a group chat: ```javascript ConnectyCube.chat.onMessageErrorListener = function (messageId, error) {}; ``` ## Ping server [Section titled “Ping server”](#ping-server) Sometimes, it can be cases where TCP connection to Chat server can go down without the application layer knowing about it. To check that chat connection is still alive or to keep it to be alive there is a ping method: ```javascript const PING_TIMEOUT = 3000; // default is 5000 ms ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT) .then(() => { // Chat connection is alive }).catch((error) => { // No connection with chat server // Let's try to re-connect }) ``` ### Pause chat connection when ping fails [Section titled “Pause chat connection when ping fails”](#pause-chat-connection-when-ping-fails) Temporarily stop the chat connection if the server does not respond to the ping request. This allows the client to attempt automatic reconnection as soon as the chat connection becomes available again: ```javascript const PING_TIMEOUT = 1000; try { await ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT) } catch (error) { ConnectyCube.chat.terminate() } ``` ### Handle browser offline event with ping check [Section titled “Handle browser offline event with ping check”](#handle-browser-offline-event-with-ping-check) Listen for the browser’s `offline` event and try to ping the chat server if the event fires: ```javascript const PING_TIMEOUT = 3000; window.addEventListener("offline", () => { try { await ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT); } catch (error) { ConnectyCube.chat.terminate(); } }); ``` ### Maintain chat connection with periodic pings (VPN-friendly) [Section titled “Maintain chat connection with periodic pings (VPN-friendly)”](#maintain-chat-connection-with-periodic-pings-vpn-friendly) Regularly send pings to check if the chat connection is still alive. If a ping fails, send another ping after a short delay to handle cases where the first failure was temporary; terminate the connection if the second ping also fails: ```javascript const PING_INTERVAL = 40000; const PING_TIMEOUT = 1000; const PING_DELAY = 3000; let pingInterval = null; const startPingWithInterval = () => { pingInterval = setInterval(async () => { try { await ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT); } catch (error) { setTimeout(async () => { try { await ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT); } catch (error) { ConnectyCube.chat.terminate(); } }, PING_DELAY); } }, PING_INTERVAL); } const stopPingWithInterval = () => { if (pingInterval) { clearInterval(pingInterval); pingInterval = null; } } ``` # Video Calling > Empower your Web applications with our JavaScript Video Calling P2P API. Enable secure and immersive peer-to-peer video calls for enhanced user experience ConnectyCube **Video Calling P2P API** is built on top of [WebRTC](https://webrtc.org/) protocol and based on top of [WebRTC Mesh](https://webrtcglossary.com/mesh/) architecture. Max people per P2P call is 4. > To get a difference between **P2P calling** and **Conference calling** please read our [ConnectyCube Calling API comparison](https://connectycube.com/2020/04/15/connectycube-calling-api-comparison/) blog page. ## Preparations [Section titled “Preparations”](#preparations) [ConnectyCube Chat API](/js/messaging) is used as a signaling transport for Video Calling API, so in order to start using Video Calling API you need to [connect user to Chat](/js/messaging#connect-to-chat). ## Create video session [Section titled “Create video session”](#create-video-session) In order to use Video Calling API you need to create a call session object - choose your opponents with whom you will have a call and a type of session (VIDEO or AUDIO): > **Note** > > The `calleesIds` array must contain the opponents ids only and exclude current user id. ```javascript const calleesIds = [56, 76, 34]; // User's ids const sessionType = ConnectyCube.videochat.CallType.VIDEO; // AUDIO is also possible const additionalOptions = {}; const session = ConnectyCube.videochat.createNewSession(calleesIds, sessionType, additionalOptions); ``` > **Note**: In a case of low bandwidth network, you can try to limit the call bandwidth cap to get better quality vs stability results. It can be done by passing `const additionalOptions = {bandwidth: 256};` or 128 value. ## Access local media stream [Section titled “Access local media stream”](#access-local-media-stream) In order to have a video chat session you need to get an access to the user’s devices (webcam / microphone): ```javascript const mediaParams = { audio: true, video: true }; session .getUserMedia(mediaParams) .then((localStream) => {}) .catch((error) => {}); ``` This method lets the browser ask the user for permission to use devices. You should allow this dialog to access the stream. Otherwise, the browser can’t obtain access and will throw an error for `getUserMedia` callback function. For more information about possible audio/video constraints, here is a good code sample from WebRTC team how to work with getUserMedia constraints: ## Call quality [Section titled “Call quality”](#call-quality) **Limit bandwidth** Despite WebRTC engine uses automatic quality adjustement based on available Internet bandwidth, sometimes it’s better to set the max available bandwidth cap which will result in a better and smoother user experience. For example, if you know you have a bad internet connection, you can limit the max available bandwidth to e.g. 256 Kbit/s. This can be done either when initiate a call (see below ‘Initiate a call’ API documentation): ```javascript session.call({maxBandwidth: 256}); // default is 0 - unlimited ``` which will result in limiting the max vailable bandwidth for ALL participants or/and during a call: ```javascript session.setMaxBandwidth(512); // set 0 to remove the limit ``` which will result in limiting the max available bandwidth for current user only. **HD video quality** If HD video quality is required - the following audio/video constraints are required to pass: ```javascript {video: { width: 1280, height: 720 }, audio: true} ``` More info about all possible is are available here ## Attach local media stream [Section titled “Attach local media stream”](#attach-local-media-stream) Then you should attach your local media stream to HTML video element. For **Web-like environments**, including Cordova - use the following method: ```javascript session.attachMediaStream("myVideoElementId", localStream, { muted: true, mirror: true, }); ``` ## Initiate a call [Section titled “Initiate a call”](#initiate-a-call) ```javascript const extension = {}; session.call(extension, (error) => {}); ``` The extension is used to pass any extra parameters in the request to your opponents. After this, your opponents will receive a callback call: ```javascript ConnectyCube.videochat.onCallListener = function (session, extension) {}; ``` Or if your opponents are offline or did not answer the call request: ```javascript ConnectyCube.videochat.onUserNotAnswerListener = function (session, userId) {}; ``` ## Accept a call [Section titled “Accept a call”](#accept-a-call) To accept a call the following code snippet is used: ```javascript ConnectyCube.videochat.onCallListener = function (session, extension) { // Here we need to show a dialog with 2 buttons - Accept & Reject. // By accepting -> run the following code: // // 1. await session.getUserMedia (...) // // 2. Accept call request: const extension = {}; session.accept(extension); }; ``` After this, you will get a confirmation in the following callback: ```javascript ConnectyCube.videochat.onAcceptCallListener = function (session, userId, extension) {}; ``` Also, both the caller and opponents will get a special callback with the remote stream: ```javascript ConnectyCube.videochat.onRemoteStreamListener = function (session, userID, remoteStream) { // attach the remote stream to DOM element session.attachMediaStream("remoteOpponentVideoElementId", remoteStream); }; ``` From this point, you and your opponents should start seeing each other. ## Receive a call in background [Section titled “Receive a call in background”](#receive-a-call-in-background) For mobile apps, it can be a situation when an opponent’s user app is either in closed (killed) or background (inactive) state. In this case, to be able to still receive a call request, you can use Push Notifications. The flow should be as follows: * a call initiator should send a push notification along with a call request * when an opponent’s app is killed or in background state - an opponent will receive a push notification about an incoming call, and will be able to accept/reject the call. If accepted or pressed on a push notification - an app will be opened, a user should auto login and connect to chat and then will be able to join an incoming call. Please refer to Push Notifications API guides regarding how to integrate Push Notifications in your app: * [Push Notifications API guide: JS](/js/push-notifications) For even better integration - CallKit and VoIP push notifications can be used. Please check `CallKit and VoIP push notifications` section on each platform Push Notifications API guide page. ## Reject a call [Section titled “Reject a call”](#reject-a-call) ```javascript const extension = {}; session.reject(extension); ``` After this, the caller will get a confirmation in the following callback: ```javascript ConnectyCube.videochat.onRejectCallListener = function (session, userId, extension) {}; ``` Sometimes, it could a situation when you received a call request and want to reject, but the call session object has not arrived yet. It could be in a case when you integrated CallKit to receive call requests while an app is in background/killed state. To do a reject in this case, the following snippet can be used: ```javascript const params = { sessionID: callId, recipientId: callInitiatorID, platform: 'web' }; await ConnectyCube.videochat.callRejectRequest(params); ``` ## End a call [Section titled “End a call”](#end-a-call) ```javascript const extension = {}; session.stop(extension); ``` After this, the opponents will get a confirmation in the following callback: ```javascript ConnectyCube.videochat.onStopCallListener = function (session, userId, extension) {}; ``` ## Mute audio [Section titled “Mute audio”](#mute-audio) ```javascript session.mute("audio"); session.unmute("audio"); ``` ## Mute video [Section titled “Mute video”](#mute-video) ```javascript session.mute("video"); session.unmute("video"); ``` ## Switch video cameras [Section titled “Switch video cameras”](#switch-video-cameras) First of all you need to obtain all your device’s available cameras: ```javascript let deviceInfo, deviceId, deviceLabel; ConnectyCube.videochat .getMediaDevices("videoinput") .then((devices) => { if (devices.length) { // here is a list of all available cameras for (let i = 0; i !== devices.length; ++i) { deviceInfo = devices[i]; deviceId = deviceInfo.deviceId; deviceLabel = deviceInfo.label; } } }) .catch((error) => {}); ``` Then you can choose some `deviceId` and switch the video stream to exact this device: ```javascript const constraints = { video: deviceId }; session .switchMediaTracks(constraints) .then((stream) => {}) .catch((error) => {}); ``` ## Switch audio output [Section titled “Switch audio output”](#switch-audio-output) For switching audio - use the same above flow for switching camera. Just replace a `videoinput` to `audioinput` in `getMediaDevices` and `video` to `audio` in `constraints`. ## Screen Sharing [Section titled “Screen Sharing”](#screen-sharing) Request a desktop stream by calling `getDisplayMedia`: ```javascript const constraints = { video: { width: 1280, height: 720, frameRate: { ideal: 10, max: 15 }, }, audio: true, }; session .getDisplayMedia(constraints) .then((localDesktopStream) => {}) .catch((error) => {}); ``` More info about what else options can be passed can be found here If the local stream already exists, the next call to getUserMedia or getDisplayMedia will update the tracks in the stream and preserve the track’s enabled state for the audio track. ## Group video calls [Section titled “Group video calls”](#group-video-calls) Because of [Mesh architecture](https://webrtcglossary.com/mesh/) we use for multipoint where every participant sends and receives its media to all other participants, current solution supports group calls with up to 4 people. Also ConnectyCube provides an alternative solution for up to 12 people - [Multiparty Video Conferencing API](/js/videocalling-conference). ## Monitor connection state [Section titled “Monitor connection state”](#monitor-connection-state) There is a callback function to track the session connection state: ```javascript ConnectyCube.videochat.onSessionConnectionStateChangedListener = ( session, userID, connectionState ) => { }; ``` The possible values of connectionState are those of an enum of type `ConnectyCube.videochat.SessionConnectionState`: * ConnectyCube.videochat.SessionConnectionState.UNDEFINED * ConnectyCube.videochat.SessionConnectionState.CONNECTING * ConnectyCube.videochat.SessionConnectionState.CONNECTED * ConnectyCube.videochat.SessionConnectionState.DISCONNECTED * ConnectyCube.videochat.SessionConnectionState.FAILED * ConnectyCube.videochat.SessionConnectionState.CLOSED * ConnectyCube.videochat.SessionConnectionState.COMPLETED ## Tackling Network changes [Section titled “Tackling Network changes”](#tackling-network-changes) If a user’s network environment changes (e.g., switching from Wi-Fi to mobile data), the existing call connection might no longer be valid. Normally, in a case of short network interruptions, the ConnectyCube SDK will automatically restore the call so you can see via `onSessionConnectionStateChangedListener` callback with `connectionState` changing to `DISCONNECTED` and then again to `CONNECTED`. But not all cases are the same, and in some of them the connection needs to be **manually** refreshed due to various issues like NAT or firewall behavior changes or even longer network environment changes, e.g. when a user is offline for more than 30 seconds. This is where ICE restart helps to re-establish the connection to find a new network path for communication. The correct and recommended way for an application to handle all such ‘bad’ cases is to trigger an ICE restart when the connection state goes to either `FAILED` or `DISCONNECTED` for an extended period of time (e.g. > 30 seconds). Following is the preliminary code snippet regarding how to work with ICE restart: * Firstly, we have to disable the call termination logic after the network is disconnected for > 30 seconds by increasing the `videochat.disconnectTimeInterval` value to e.g. 5 mins. ```plaintext const appConfig = { videochat: { disconnectTimeInterval: 300, } }; ConnectyCube.init(credentials, appConfig); ``` * Secondly, define a variable which can track the Internet connection state: ```plaintext let isOnline = window.navigator.onLine; window.onoffline = () => { isOnline = false; }; window.ononline = () => { isOnline = true; }; ``` * Thirdly, define a function that will perform ICE restart: ```plaintext async maybeDoIceRestart(session, userID) { try { // firstly let's check if we are still connected to chat await ConnectyCube.chat.pingWithTimeout(); // do ICE restart if (session.canInitiateIceRestart(userID)) { session.iceRestart(userID); } } catch (error) { console.error(error.message); // chat ping request has timed out, // so need to reconnect to Chat await ConnectyCube.chat.disconnect(); await ConnectyCube.chat.connect({ userId: currentUser.id, password: currentUser.password, }) // do ICE restart if (session.canInitiateIceRestart(userID)) { session.iceRestart(userID); } } } ``` * Fourthly, define a `ConnectyCube.videochat.onSessionConnectionStateChangedListener` callback and try to perform ICE restart if not automatic call restoration happened after 30 seconds: ```plaintext iceRestartTimeout = null; needIceRestartForUsersIds = []; ConnectyCube.videochat.onSessionConnectionStateChangedListener = ( session, userID, connectionState ) => { console.log( "[onSessionConnectionStateChangedListener]", userID, connectionState ); const { DISCONNECTED, FAILED, CONNECTED, CLOSED } = ConnectyCube.videochat.SessionConnectionState; if (connectionState === DISCONNECTED || connectionState === FAILED) { iceRestartTimeout = setTimeout(() => { // Connection not restored within 30 seconds, trying ICE restart... if (isOnline) { maybeDoIceRestart(session, userID); } else { // Skip ICE restart, no Internet connection this.needIceRestartForUsersIds.push(userID); } }, 30000); } else if (connectionState === CONNECTED) { clearTimeout(iceRestartTimeout); iceRestartTimeout = null; needIceRestartForUsersIds = []; } else if (connectionState === CLOSED) { needIceRestartForUsersIds = []; } }; ``` * Finally, in a case a user got working Internet connection later, do ICE restart there: ```plaintext window.ononline = () => { if (!isOnline) { if (session && needIceRestartForUsersIds.length > 0) { for (let userID of needIceRestartForUsersIds) { maybeDoIceRestart(session, userID); } } } isOnline = true; }; ``` After these changes the call connection should be restored to working state again. ## Configuration [Section titled “Configuration”](#configuration) There are various calling related configs that can be changed. ### alwaysRelayCalls [Section titled “alwaysRelayCalls”](#alwaysrelaycalls) The `alwaysRelayCalls` config sets the WebRTC `RTCConfiguration.iceTransportPolicy` [config](/js/#default-configuration). Setting it to `true` means the calling media will be routed through TURN server all the time, and not directly P2P between users even if the network path allows it: ```javascript const appConfig = { videochat: { alwaysRelayCalls: true, }, }; ``` ## Recording [Section titled “Recording”](#recording) For the recording feature implementation you can use [MediaStream Recording API](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API) The recorder accepts a media stream and record it to file. Both local and remote streams can be recorded and saved to file. There is also a good article about client-side recording implementation ## Continue calling in background [Section titled “Continue calling in background”](#continue-calling-in-background) If you are developing dedicated apps for iOS and Android - it’s required to apply additional configure for the app to continue playing calling audio when it goes into the background. On iOS: there is no way to continue a video call in background because of some OS restrictions. What is supported there is to continue with voice calling while an app is in background. Basically, the recommended to achieve this is to switch off device camera when an app goes to background and then switch camera on back when an app goes to foreground. Furthermore, even voice background call are blocked by default on iOS. To unblock - you need to setup proper background mode capabilities in your project. Please find the [Enabling Background Audio link](https://developer.apple.com/documentation/avfoundation/media_playback_and_selection/creating_a_basic_video_player_ios_and_tvos/enabling_background_audio) with more information how to do it properly. Generally speaking, you need to enable `Voice over IP` and `Remote notifications` capabilities: ![Setup Xcode VOIP capabilities](/_astro/voip_capabilities.DFieoPnY_15jdKd.webp) For Android, we also recommend to implement the same camera switch flow when go to background and then return to foreground. # Video Conferencing > Discover the simplicity of integrating conference video calling into your Web app with our easy-to-use API. Empower users to connect from anywhere. ConnectyCube **Multiparty Video Conferencing API** is built on top of [WebRTC](https://webrtc.org/) protocol and based on top of [WebRTC SFU](https://webrtcglossary.com/sfu/) architecture. Max people per Conference call is 12. Video Conferencing is available starting from [Advanced plan](https://connectycube.com/pricing/). > To get a difference between **P2P calling** and **Conference calling** please read our [ConnectyCube Calling API comparison](https://connectycube.com/2020/04/15/connectycube-calling-api-comparison/) blog page. ## Features supported [Section titled “Features supported”](#features-supported) * Video/Audio Conference with up to 12 people * Join-Rejoin video room functionality (like Skype) * Guest rooms * Mute/Unmute audio/video streams * Display bitrate * Switch video input device (camera) * Switch audio input device (microphone) ## Preparations [Section titled “Preparations”](#preparations) ### Connect adapter.js [Section titled “Connect adapter.js”](#connect-adapterjs) Make sure you connected the latest version of [adapter.js](https://github.com/webrtc/adapter/tree/master/release) Note If you use Chrome browser and emulate iOS device - you may get “The adapter.js addTrack polyfill only supports a single stream which is associated with the specified track” error. That’s a known issue with adapter.js where it wrongly parses the user agent. Either use real iOS device or Safari browser to test iOS behaviour. ### Set up config [Section titled “Set up config”](#set-up-config) In order to start working with Multiparty Video Conferencing API you need to initialize a client: ```javascript const credentials = { appId: ..., authKey: "...", } const MULTIPARTY_SERVER_ENDPOINT = 'wss://...:8989'; const appConfig = { debug: { mode: 1 }, conference: { server: MULTIPARTY_SERVER_ENDPOINT }, } ConnectyCube.init(credentials, appConfig) ``` > Default multiparty server endpoint is wss\://janus.connectycube.com:8989 ([see](/js/#default-configuration)). ## Create meeting [Section titled “Create meeting”](#create-meeting) In order to have a conference call, a meeting object has to be created. ```javascript const params = { name: "My meeting", start_date: timestamp, end_date: timestamp attendees: [ {id: 123, email: "..."}, {id: 124, email: "..."} ], record: false, chat: false }; ConnectyCube.meeting.create(params) .then(meeting => { const confRoomId = meeting._id; }) .catch(error => { }); ``` * As for `attendees` - either ConnectyCube users ids or external emails can be provided. * If you want to schedule a meeting - pass `start_date` and `end_date`. * Pass `chat: true` if you want to have a chat connected to meeting. * Pass `record: true` if you want to have a meeting call recorded. Read more about Recording feature Once meeting is created, you can use `meeting._id` as a conf room identifier in the below requests when join a call. ## Create call session [Section titled “Create call session”](#create-call-session) Once a meeting is created/retrieved, you can create a conf call session. Normally, each browser tab will need a single session with the server: ```javascript const session = ConnectyCube.videochatconference.createNewSession(); ``` Once a session is created, you can interact with a Video Conferencing API. ## Access local media stream [Section titled “Access local media stream”](#access-local-media-stream) In order to have a video chat session you need to get an access to the user’s devices (webcam / microphone): ```javascript const mediaParams = { audio: true, video: true, }; session .getUserMedia(mediaParams) .then((localStream) => {}) .catch((error) => {}); ``` This method lets the browser ask the user for permission to use devices. You should allow this dialog to access the stream. Otherwise, the browser can’t obtain access and will throw an error for `getUserMedia` callback function. For more information about possible audio/video constraints, here is a good code sample from WebRTC team how to work with getUserMedia constraints: ### HD video quality [Section titled “HD video quality”](#hd-video-quality) If HD video quality is required - the following audio/video constraints are required to pass: ```javascript {video: { width: 1280, height: 720 }, audio: true} ``` More info about all possible is are available here ## Attach local media stream [Section titled “Attach local media stream”](#attach-local-media-stream) Then you should attach your local media stream to HTML video element. For **Web-like environments**, including Cordova - use the following method: ```javascript session.attachMediaStream("myVideoElementId", localStream, { muted: true, mirror: true, }); ``` For **ReactNative environment** - use the following method: ```javascript import {RTCView} from 'react-native-connectycube'; // pass a local or remote stream to the RTCView component ... ... ``` ## Join room [Section titled “Join room”](#join-room) To jump into video chat with users you should join it: ```javascript session .join(confRoomId, userId, userDisplayName) .then(() => {}) .catch((error) => {}); ``` To check current joined video room use the following property: ```javascript const confRoomId = session.currentRoomId; ``` ## Join as listener [Section titled “Join as listener”](#join-as-listener) It can be a requirement where it needs to join a room as listener only, w/o publishing own media stream. It can be useful for a case with a teacher and many students where normally students join a call as listeners and only a teacher publishes own media stream: ```javascript session .joinAsListener(confRoomId, userId, userDisplayName) .then(() => {}) .catch((error) => {}); ``` ## List online participants [Section titled “List online participants”](#list-online-participants) To list online users in a room: ```javascript session .listOfOnlineParticipants() .then((participants) => {}) .catch((error) => {}); ``` ## Events [Section titled “Events”](#events) There are 6 events you can listen for: ```javascript ConnectyCube.videochatconference.onParticipantJoinedListener = ( session, userId, userDisplayName, isExistingParticipant ) => {}; ConnectyCube.videochatconference.onParticipantLeftListener = (session, userId) => {}; ConnectyCube.videochatconference.onRemoteStreamListener = (session, userId, stream) => {}; ConnectyCube.videochatconference.onSlowLinkListener = (session, userId, uplink, nacks) => {}; ConnectyCube.videochatconference.onRemoteConnectionStateChangedListener = (session, userId, iceState) => {}; ConnectyCube.videochatconference.onSessionConnectionStateChangedListener = (session, iceState) => {}; ConnectyCube.videochatconference.onErrorListener = (session, error) => {}; ``` ## Mute/Unmute audio [Section titled “Mute/Unmute audio”](#muteunmute-audio) You can mute/unmute your own audio: ```javascript // mute session.muteAudio(); // unmute session.unmuteAudio(); //check mute state session.isAudioMuted(); // true/false ``` ## Mute/Unmute video [Section titled “Mute/Unmute video”](#muteunmute-video) You can mute/unmute your own video: ```javascript // mute session.muteVideo(); // unmute session.unmuteVideo(); //check mute state session.isVideoMuted(); // true/false ``` ## List of devices [Section titled “List of devices”](#list-of-devices) ```javascript // get all devices ConnectyCube.videochatconference .getMediaDevices() .then((allDevices) => {}) .catch((error) => {}); // only video devices ConnectyCube.videochatconference .getMediaDevices(ConnectyCube.videochatconference.DEVICE_INPUT_TYPES.VIDEO) .then((videoDevices) => {}) .catch((error) => {}); // only audio devices ConnectyCube.videochatconference .getMediaDevices(ConnectyCube.videochatconference.DEVICE_INPUT_TYPES.AUDIO) .then((audioDevices) => {}) .catch((error) => {}); ``` ## Switch video(camera)/audio(microphone) input device [Section titled “Switch video(camera)/audio(microphone) input device”](#switch-videocameraaudiomicrophone-input-device) ```javascript const deviceId = "..."; // switch video session .switchMediaTracks({ video: deviceId }) .then((updatedLocaStream) => {}) // you can reattach local stream .catch((error) => {}); // switch audio session .switchMediaTracks({ audio: deviceId }) .then((updatedLocaStream) => {}) // you can reattach local stream .catch((error) => {}); ``` ## Screen Sharing [Section titled “Screen Sharing”](#screen-sharing) Request a desktop stream by calling `getDisplayMedia`: ```javascript const constraints = { video: { width: 1280, height: 720, frameRate: { ideal: 10, max: 15 }, }, audio: true, }; session .getDisplayMedia(constraints) .then((localDesktopStream) => {}) .catch((error) => {}); ``` More info about what else options can be passed can be found here If the local stream already exists, the next call to getUserMedia or getDisplayMedia will update the tracks in the stream and preserve the track’s enabled state for the audio track. ## Get remote user bitrate [Section titled “Get remote user bitrate”](#get-remote-user-bitrate) ```javascript const bitrate = session.getRemoteUserBitrate(userId); ``` ## Get remote user mic level [Section titled “Get remote user mic level”](#get-remote-user-mic-level) ```javascript const micLevel = session.getRemoteUserVolume(userId); ``` ## Leave room and destroy conf session [Section titled “Leave room and destroy conf session”](#leave-room-and-destroy-conf-session) To leave current joined video room: ```javascript session .leave() .then(() => {}) .catch((error) => {}); ``` ## Retrieve meetings [Section titled “Retrieve meetings”](#retrieve-meetings) Retrieve a meeting by id: ```javascript const params = { _id: meetingId, }; ConnectyCube.meeting .get(params) .then((meeting) => {}) .catch((error) => {}); ``` Retrieve a list of meetings: ```javascript const params = { limit: 5, offset: 0 }; ConnectyCube.meeting .get(params) .then((meetings) => {}) .catch((error) => {}); ``` ## Edit meeting [Section titled “Edit meeting”](#edit-meeting) A meeting creator can edit a meeting: ```javascript const params = { name, start_date, end_date }; ConnectyCube.meeting .update(meetingId, params) .then((meeting) => {}) .catch((error) => {}); ``` ## Delete meeting [Section titled “Delete meeting”](#delete-meeting) A meeting creator can delete a meeting: ```javascript ConnectyCube.meeting .delete(meetingId) .then(() => {}) .catch((error) => {}); ``` ## Recording [Section titled “Recording”](#recording) Server-side recording is available. Read more about Recording feature ### Retrieve recordings with download url [Section titled “Retrieve recordings with download url”](#retrieve-recordings-with-download-url) ```javascript ConnectyCube.meeting .getRecordings(meetingId) .then(() => {}) .catch((error) => {}); ``` ## Continue calling in background [Section titled “Continue calling in background”](#continue-calling-in-background) If you are developing dedicated apps for iOS and Android - it’s required to apply additional configure for the app to continue playing calling audio when it goes into the background. On iOS: there is no way to continue a video call in background because of some OS restrictions. What is supported there is to continue with voice calling while an app is in background. Basically, the recommended to achieve this is to switch off device camera when an app goes to background and then switch camera on back when an app goes to foreground. Furthermore, even voice background call are blocked by default on iOS. To unblock - you need to setup proper background mode capabilities in your project. Please find the [Enabling Background Audio link](https://developer.apple.com/documentation/avfoundation/media_playback_and_selection/creating_a_basic_video_player_ios_and_tvos/enabling_background_audio) with more information how to do it properly. Generally speaking, you need to enable `Voice over IP` and `Remote notifications` capabilities: ![Setup Xcode VOIP capabilities](/_astro/voip_capabilities.DFieoPnY_15jdKd.webp) For Android, we also recommend to implement the same camera switch flow when go to background and then return to foreground. # Address Book > Effortlessly upload, sync, and access ConnectyCube users from your phone contacts in your Web app with Address Book API. Address Book API provides an interface to work with phone address book, upload it to server and retrieve already registered ConnectyCube users from your address book. With conjunction of [User authentication via phone number](/js/authentication-and-users#authentication-via-phone-number) you can easily organise a state of the art logic in your App where you can easily start chatting with your phone contacts, without adding them manually as friends - the same what you can see in WhatsApp, Telegram, Facebook Messenger and Viber. ## Upload Address Book [Section titled “Upload Address Book”](#upload-address-book) First of all you need to upload your Address Book to the backend. It’s a normal practice to do a full upload for the 1st time and then upload diffs on future app logins. ```javascript const CONTACTS = [ { name: "Gordie Kann", phone: "1879108395", }, { name: "Wildon Gilleon", phone: "2759108396", }, { name: "Gaston Center", phone: "3759108396", }, ]; const options = {}; // const options = {'force': 1, 'udid': 'XXX'}; ConnectyCube.addressbook .uploadAddressBook(CONTACTS, options) .then(() => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.addressbook.uploadAddressBook(params)` - [see](/server/address_book#response) * You also can edit an existing contact by providing a new name for it. * You also can upload more contacts, not just all in one request - they will be added to your address book on the backend. If you want to override the whole address book on the backend - just provide `force: 1` option. * You also can remove a contact by setting `contact.destroy = 1;` * A device UDID is used in cases where user has 2 or more devices and contacts sync is off. Otherwise - user has a single global address book. ## Retrieve Address Book [Section titled “Retrieve Address Book”](#retrieve-address-book) If you want you can retrieve your uploaded address book: ```javascript ConnectyCube.addressbook .get() .then((result) => {}) .catch((error) => {}); ``` or with UDID: ```javascript const UDID = "XXX"; ConnectyCube.addressbook .get(UDID) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.addressbook.get()` - [see](/server/address_book#response-1) ## Retrieve Registered Users [Section titled “Retrieve Registered Users”](#retrieve-registered-users) Using this request you can easily retrieve the ConnectyCube users - you phone Address Book contacts that already registered in your app, so you can start communicate with these users right away: ```javascript ConnectyCube.addressbook .getRegisteredUsers() .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.addressbook.getRegisteredUsers()` - [see](/server/address_book#response-2) If isCompact = true - server will return only id and phone fields of User. Otherwise - all User’s fields will be returned. ## Push notification on new contact joined [Section titled “Push notification on new contact joined”](#push-notification-on-new-contact-joined) There is a way to get a push notification when some contact from your Address Book registered in an app. You can enable this feature at [ConnectyCube Dashboard](https://admin.connectycube.com), Users module, Settings tab: ![Setup push notification on new contact joined](/_astro/setup_push_notification_on_new_contact_joined.DTG1vj8m_1gVrvB.webp) # AI Features > Add chat history summarization and message tone changing features using Google Generative AI directly on client-side. Learn how to add AI features to your application through real-time chat.: * **Chat history summarization** — summarize recent chat messages. * **Change message tone** — rewrite the draft message into a chosen tone before send. > **Important security note:** all examples below call the Google generative AI. That **will** expose the API key in the client bundle. Only proceed if this risk is acceptable for your project (internal demo, protected app, etc.). *** ## Getting started [Section titled “Getting started”](#getting-started) ### Install [Section titled “Install”](#install) To enable AI capabilities, install the official SDKs: * NPM ```bash npm install --save @ai-sdk/google ai ``` * Yarn ```bash yarn add @ai-sdk/google ai ``` ### Get your API key [Section titled “Get your API key”](#get-your-api-key) Create or log in to your Google AI Studio account, then get an API key from\ 👉 Keep this key safe — anyone with access can use your quota. *** ### Base client init [Section titled “Base client init”](#base-client-init) By default, the SDK reads the key from your .env file using the `GOOGLE_GENERATIVE_AI_API_KEY` variable: ```env GOOGLE_GENERATIVE_AI_API_KEY=*** ``` Import the Google model: ```ts import { google } from '@ai-sdk/google'; const googleModelAI = google('gemini-2.5-flash'); ``` > This is the simplest and safest approach when building locally or in a controlled client environment. ### Custom client init [Section titled “Custom client init”](#custom-client-init) If you prefer to initialize manually (for example, inside a browser component), you can do it like this: ```ts import { createGoogleGenerativeAI } from '@ai-sdk/google'; const google = createGoogleGenerativeAI({ apiKey: '***' }); const googleModelAI = google('gemini-2.5-flash'); ``` > ⚠️ Note: In this setup, the API key will be visible to anyone who inspects your app bundle. Use this only for client-only experiments or demos. *** ## Chat history summarization [Section titled “Chat history summarization”](#chat-history-summarization) **Goal:** use an AI assistant to summarize chat messages in a conversation — helpful for users who rejoin long threads or support chats. ### How it works [Section titled “How it works”](#how-it-works) 1. Select relevant messages (for example, only from the last week). 2. Build a readable transcript string. 3. Send it to the model with a summarization prompt. 4. Display the generated summary in the UI. ### Helpers [Section titled “Helpers”](#helpers) Some small utilities to format messages and pick time ranges: ```ts import type { Users, Messages } from 'connectycube'; export const filterMessagesByDaysAgo = ( messages: Messages.Message[] = [], days: number = 0 ): Messages.Message[] => { const daysAgoTimestamp = days ? Math.floor(Date.now() / 1000) - days * 24 * 60 * 60 : 0; return messages.filter((message) => message.date_sent >= daysAgoTimestamp) } export const buildMessagesString = ( messages: Messages.Message[] = [], users: Users.User[] = [], currentUserId: number = 0 ): string => { return messages.reduce((result, {sender_id, message, attachments}) => { const user = users[sender_id] ?? {}; const userName = user.full_name || user.login || ''; const postfix = sender_id === currentUserId ? '(me)' : ''; const attachmentsInfo = attachments ? `[attachments: ${JSON.stringify(attachments)}]` : ''; result += `- ${userName} ${postfix}: ${message || ''} ${attachmentsInfo}\n`; return result; }, ''); } ``` ### Example [Section titled “Example”](#example) **Flow:** filter messages → build messages string → build a prompt → call `generateText` → render summary in UI. ```ts // ... import type { Users, Messages } from 'connectycube'; import { generateText } from 'ai'; export const summarizeChatHistory = async ({ messages: Messages.Message[] = [], users: Users.User[] = [], currentUserId: number = 0, lng: string = 'English', days: number = 0 }) => { // 1. filter (by period in days) const filteredMessages = filterMessagesByDaysAgo(messages, 7); // 2. build the transcript const messagesString = await buildMessagesString(filteredMessages, users, currentUserId); // 3. build the prompt const prompt = `You are a helpful assistant. Provide a concise summary of the following chat messages in ${lng}. Use bullet points or a short paragraph. Focus on meaningful conversation and skip system messages or irrelevant technical info. If a username ends with \" (me)", it means the message was sent by ME. Messages:\n${messagesString}`; // 4. generate the summary try { const result = await generateText({ model: googleModelAI, prompt }); return result.text; // developer decides where to render the chat summarization } catch (error) { console.error('Summarization failed', error); throw error; } } ``` ### UI integration ideas [Section titled “UI integration ideas”](#ui-integration-ideas) * Add a Summarize button in the conversation. * Show a small loader while generating. * Render the summary in a popup, modal, or pinned message. * Optionally store summaries in local cache to avoid re-calling the model. *** ## Change message tone [Section titled “Change message tone”](#change-message-tone) **Goal:** rewrite a user’s draft message in a different tone before sending. This can make conversations sound more positive, diplomatic, or even humorous — depending on your tone options. ### How it works [Section titled “How it works”](#how-it-works-1) 1. Take the current input text (before send). 2. Ask AI to rewrite it in a specific tone. 3. Replace the draft text with the rewritten version. ### Example [Section titled “Example”](#example-1) **Flow:** obtain a draft message → build a prompt → call `generateText` → change the draft message. ```ts // ... import { generateText } from 'ai'; export enum MessageTones { POSITIVE = 'positive', NEGATIVE = 'negative', CRINGE = 'cringe', }; export const changeMessageTone = async( message: string = '', tone: MessageTones = MessageTones.POSITIVE, lng: string = 'English' ): Promise => { const draft = message.trim(); const prompt = `Rewrite the text \"${draft}\" in ${tone} tone. Use its original language, or ${lng} if unclear. Avoid using italicized emotional interjections. Instead, express the same emotions directly using plain text or appropriate emojis. Keep all meaning. Output only the rewritten text.` try { const result = await generateText({ model: googleModelAI, prompt }); return result.text; } catch (error) { console.error('Change message tone failed', error) throw error; } } ``` ### React usage example [Section titled “React usage example”](#react-usage-example) ```tsx // ... import type { Dialogs } from 'connectycube'; import ConnectyCube, { ChatType, DialogType } from 'connectycube'; import React, { useState, useCallback } from 'react'; type ChatInputProps = { dialog: Dialogs.dialog, currentUserId: number, lng?: string } const ChatInput: React.FC = ({ dialog, currentUserId, lng = 'English' }) => { // ... const [text, setText] = useState(''); const [loading, setLoading] = useState(false); const sendMessage = useCallback(() => { const message = { type: dialog.type === DialogType.PRIVATE ? ChatType.CHAT : ChatType.GROUPCHAT, body: text, extension: { save_to_history: 1, dialog_id: dialog._id } }; const to = dialog.type === DialogType.PRIVATE ? dialog.occupants_ids.find((occupantId) => occupantId !== currentUserId) : dialog._id ConnectyCube.chat.send(to, message) }, [dialog, text]); const handleTone = async (tone: MessageTones): Promise => { setLoading(true); try { const changedText = await changeMessageTone(text, tone, lng); setText(changedText); } catch (error) { // show friendly UI error if needed } finally { setLoading(false); } }; return (