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 connectycube --save ``` * 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 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 connectycube --save ``` * 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 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.  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 = ""; // 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  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:  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:  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: 
# Authentication and Users
> Simplify user authentication in your Web app with our definitive API guide. Fortify your app's defenses and protect user data effectively.
Every user has to authenticate with ConnectyCube before using any ConnectyCube functionality. When someone connects with an application using ConnectyCube, the application will need to obtain a session token which provides temporary, secure access to ConnectyCube APIs. A session token is an opaque string that identifies a user and an application. ## Create session token [Section titled “Create session token”](#create-session-token) As a starting point, the user’s session token needs to be created allowing user any further actions within the app. Pass login/email and password to identify a user: ```javascript const userCredentials = { login: "cubeuser", password: "awesomepwd" }; 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:** 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). ### Authentication via phone number [Section titled “Authentication via phone number”](#authentication-via-phone-number) Sign In with phone number is supported with ([Firebase integration](https://firebase.google.com/docs/auth/web/phone-auth)). You need to create Firebase `project_id` and obtain Firebase `access_token` after SMS code verification, then pass these parameters to `login` method: ```javascript const userCredentials = { provider: "firebase_phone", "firebase_phone[project_id]": "...", "firebase_phone[access_token]": "...", }; ConnectyCube.createSession(userCredentials) .then((user) => {}) .catch((error) => {}); ``` > **Note** > > In order to login via phone number you need to create a session token first. ### Authentication via Firebase email [Section titled “Authentication via Firebase email”](#authentication-via-firebase-email) Sign In with email is supported with ([Firebase integration](https://firebase.google.com/docs/auth/web/password-auth)). You need to create Firebase `project_id` and obtain Firebase `access_token` after email/password verification, then pass these parameters to `login` method: ```javascript const userCredentials = { provider: "firebase_email", firebase_email: { project_id: "XXXXXXXXXXX", access_token: "XXXXXXXXXXXYYYYYY", }, }; ConnectyCube.createSession(userCredentials) .then((user) => {}) .catch((error) => {}); ``` > **Note** > > In order to login via email you need to create a session token first. ### Authentication via external identity provider [Section titled “Authentication via external identity provider”](#authentication-via-external-identity-provider) **Custom Identity Provider (CIdP)** feature is necessary if you have your own user database and want to authenticate users in ConnectyCube against it. It works the same way as Facebook/Twitter SSO. With Custom Identity Provider feature you can continue use your user database instead of storing/copying user data to ConnectyCube database. #### CIdP high level integration flow [Section titled “CIdP high level integration flow”](#cidp-high-level-integration-flow) To get started with **CIdP** integration, check the [Custom Identity Provider guide](/guides/custom-identity-provider) which describes high level integration flow. #### How to login via CIdP [Section titled “How to login via CIdP”](#how-to-login-via-cidp) Once you done with the setup mapping in ConnectyCube Dashboard, it’s time to verify the integration. To perform CIdP login, the same ConnectyCube [User Login API](/js/authentication-and-users/#upgrade-session-token-user-login) is used. You just use existing login request params to pass your external user token: ```javascript const userCredentials = { login: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzNDU2Nzg5LCJuYW1lIjoiSm9zZXBoIn0.OpOSSw7e485LOP5PrzScxHb7SR6sAOMRckfFwi4rp7o" }; ConnectyCube.createSession(userCredentials) .then((user) => {}) .catch((error) => {}); ``` Once the login is successful, ConnectyCube will create an underalying User entity, so then you can use ConnectyCube APIs in a same way as you do with a normal login. With CIdP we do not have/store any user password in ConnectyCube User entity. Following further integration, you may need to connect to Chat. In a case of CIdP login, you do not have a user password. In such cases you should use ConnectyCube session token as a password for chat connection. [Follow the Connect to Chat with CIdP guide](/js/messaging/#connect-to-chat-using-custom-authentication-providers). ### Create guest session [Section titled “Create guest session”](#create-guest-session) To create a session with guest user use the following code: ```javascript const guestUserCredentials = { guest: "1", full_name: "Awesome Smith" }; ConnectyCube.createSession(guestUserCredentials) .then((session) => {}) .catch((error) => {}); ``` ## Session expiration [Section titled “Session expiration”](#session-expiration) Expiration time for session token is 2 hours after last request to API. If you perform query with expired token, you will receive the error **Required session does not exist**. In this case you need to recreate a session token. There is a special callback function to handle this case: ```javascript const CONFIG = { on: { sessionExpired: (handleResponse, retry) => { // call handleResponse() if you do not want to process a session expiration, // so an error will be returned to origin request // handleResponse(); ConnectyCube.createSession() .then(retry) .catch((error) => {}); }, }, }; ConnectyCube.init(CREDENTIALS, CONFIG); ``` ## Destroy session token [Section titled “Destroy session token”](#destroy-session-token) To destroy a session use the following code: ```javascript ConnectyCube.destroySession().catch((error) => {}); ``` ## User signup [Section titled “User signup”](#user-signup) Only login (or email) + password are required for user to be created. All other parameters are optional: ```javascript const userProfile = { login: "marvin18", password: "supersecurepwd", email: "awesomeman@gmail.com", full_name: "Marvin Simon", phone: "47802323143", website: "https://dozensofdreams.com", tag_list: ["iphone", "apple"], custom_data: JSON.stringify({ middle_name: "Bartoleo" }), }; ConnectyCube.users .signup(userProfile) .then((user) => {}) .catch((error) => {}); ``` ## User profile update [Section titled “User profile update”](#user-profile-update) ```javascript const updatedUserProfile = { login: "marvin18sim", full_name: "Mar Sim", }; ConnectyCube.users .update(updatedUserProfile) .then((user) => {}) .catch((error) => {}); ``` If you want to change your password, you need to provide 2 parameters: `password` and `old_password`. Updated `user` entity will be returned. ## User avatar [Section titled “User avatar”](#user-avatar) You can set a user’s avatar. You just need to upload it to the ConnectyCube cloud storage and then connect to user. ```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 updateUser = (uploadedFile) => { const updatedUserProfile = { avatar: uploadedFile.uid }; return ConnectyCube.users.update(updatedUserProfile); }; ConnectyCube.storage .createAndUpload(fileParams) .then(updateUser) .then((updatedUser) => {}) .catch((error) => {}); ``` Now, other users can get you avatar: ```javascript const avatarUID = updatedUser.avatar; const avatarURL = ConnectyCube.storage.privateUrl(avatarUID); const avatarHTML = ""; ``` ## Password reset [Section titled “Password reset”](#password-reset) It’s possible to reset a password via email: ```javascript ConnectyCube.users .resetPassword("awesomeman@gmail.com") .then((result) => {}) .catch((error) => {}); ``` If provided email is valid - an email with password reset instruction will be sent to it. ## Retrieve users V2 [Section titled “Retrieve users V2”](#retrieve-users-v2) ### Examples [Section titled “Examples”](#examples) Retrieve users by ID ```javascript const searchParams = { limit: 10, offset: 50, id: { in: [51941, 51946] }, }; ConnectyCube.users .getV2(searchParams) .then((result) => {}) .catch((error) => {}); ``` Retrieve users by login ```javascript const searchParams = { login: "adminFirstUser" }; ConnectyCube.users .getV2(searchParams) .then((result) => {}) .catch((error) => {}); ``` Retrieve users by last\_request\_at ```javascript const date = new Date(2017, 10, 10); const searchParams = { last_request_at: { gt: date } }; ConnectyCube.users .getV2(searchParams) .then((result) => {}) .catch((error) => {}); ``` More information (fields, operators, request rules) available [here](/server/users#retrieve-users-v2) ## Retrieve users V1 (**Deprecated**) [Section titled “Retrieve users V1 (Deprecated)”](#retrieve-users-v1-deprecated) ### Retrieve users by ID (**Deprecated**) [Section titled “Retrieve users by ID (Deprecated)”](#retrieve-users-by-id-deprecated) ```javascript const params = { page: 1, per_page: 5, filter: { field: "id", param: "in", value: [51941, 51946], }, }; ConnectyCube.users .get(params) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve user by login (**Deprecated**) [Section titled “Retrieve user by login (Deprecated)”](#retrieve-user-by-login-deprecated) ```javascript const searchParams = { login: "marvin18" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve user by email (**Deprecated**) [Section titled “Retrieve user by email (Deprecated)”](#retrieve-user-by-email-deprecated) ```javascript const searchParams = { email: "marvin18@example.com" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve users by full name (**Deprecated**) [Section titled “Retrieve users by full name (Deprecated)”](#retrieve-users-by-full-name-deprecated) ```javascript const searchParams = { full_name: "Marvin Samuel" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve user by phone number (**Deprecated**) [Section titled “Retrieve user by phone number (Deprecated)”](#retrieve-user-by-phone-number-deprecated) ```javascript const searchParams = { phone: "44678162873" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve user by external ID (**Deprecated**) [Section titled “Retrieve user by external ID (Deprecated)”](#retrieve-user-by-external-id-deprecated) ```javascript const searchParams = { external_user_id: "675373912" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve users by tags [Section titled “Retrieve users by tags”](#retrieve-users-by-tags) ```javascript const searchParams = { tags: ["apple"] }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ## Delete user [Section titled “Delete user”](#delete-user) A user can delete himself from the platform: ```javascript ConnectyCube.users .delete() .then((result) => {}) .catch((error) => {}); ```
# Custom Data
> Maximize your Web app's potential with customizable data cloud storage solutions. Tailor data structures to match your application's unique requirements.
Custom Data, also known as cloud tables or custom objects, provide users with the flexibility to define and manage data in a way that is specific to their application’s requirements. Here are some common reasons why you might need use custom data in your app: * Custom Data allows you to define data structures that align precisely with your application’s needs. This is particularly useful when dealing with complex or unique data types that don’t fit well into standard ConnectyCube models. * In certain applications, there may be entities or objects that are unique to that particular use case. Custom Data enable the representation of these entities in the database, ensuring that the data storage is optimized for the application’s logic. * Custom Data allows you to extend the functionality of the app by introducing new types of data that go beyond what the ConnectyCube platform’s standard models support. Custom Data empower you to introduce these extensions and additional features. * In situations where data needs to be migrated from an existing system or transformed in a specific way, Custom Data offer the flexibility to accommodate these requirements. * Your application needs to integrate with external systems or APIs, Custom Data can be designed to align seamlessly with the data structures of your external systems. ## Get started with SDK [Section titled “Get started with SDK”](#get-started-with-sdk) Follow the [Getting Started guide](/js/) on how to connect ConnectyCube SDK and start building your first app. ## Preparations [Section titled “Preparations”](#preparations) In order to start using Custom Data you need first create the data scheme in the ConnectyCube Admin panel. For it navigate to **`Home -> Your App -> Custom -> List`** then click on **`ADD`** and from the dropdown menu select **`ADD NEW CLASS`**. In the opened dialog, enter your model name and add the necessary fields. The ConnectyCube Custom Data models’ fields support various data types or arrays (except `Location`). These include: * Integer (e.g. `8222456`); * Float (e.g. `1.25`); * Boolean (`true` or `false`); * String (e.g. `"some text"`); * Location (the array what consist of **two** `numbers`s); After adding all the required fields, click the **`CREATE CLASS`** button to save your scheme.  Newly created class is now available and contains the following data: * **\_id** - record identifier generated by system automatically * **user\_id** - identifier of user who created a record * **\_parent\_id** - by default is null * **field\_1** - field defined in a scheme * **field\_2** - field defined in a scheme * … * **field\_N** - field defined in a scheme * **created\_at** - date and time when a record is created  After that you can perform all **CRUD** operations with your Custom Data. > **Note**: The **Class name** field will be represented as the DB table name and will be used for identification of your requests during the work with Custom Data. ## Permissions [Section titled “Permissions”](#permissions) Access control list (ACL) is a list of permissions attached to some object. An ACL specifies which users have an access to objects, as well as what operations are allowed with such objects. Each entry in a typical ACL specifies a subject and an operation. ACL models may be applied to collections of objects as well as to individual entities within the system’s hierarchy. Adding the Access Control list is only available within the Custom Data module. ### Permissions scheme [Section titled “Permissions scheme”](#permissions-scheme) ConnectyCube permission scheme contains five permissions levels: * **Open** (open) - any user within the application can access the record(s) in the class and perform actions with the record * **Owner** (owner) - only the Owner (the user who created a record) is authorized to perform actions with the record * **Not allowed** (not\_allowed) - no one (except the Account Administrator) can proceed with a chosen action * **Open for groups** (open\_for\_groups) - users with the specified tag(s) will be included in the group that is authorized to perform actions with a record. Multiple groups can be specified (number of groups is not limited). * **Open for users ids** (open\_for\_users\_ids) - only users with listed IDs can perform actions with a record. Actions for work with an entity: * **Create** - create a new record * **Read** - retrieve information about a record and view it in the read-only mode * **Update** - update any parameter of the chosen record that can be updated by user * **Delete** - delete a record To set a permission schema for the Class, go to ConnectyCube dashboard and find a required class within Custom Data module Click on **`EDIT PERMISSION`** button to open permissions schema to edit. Each listed action has a separate permission level to select. The exception is a ‘Create’ action that isn’t available for ‘Owner’ permission level.  ### Permission levels [Section titled “Permission levels”](#permission-levels) Two access levels are available in the ConnectyCube: access to Class and access to Record. Only one permission schema can be applied for the record. Using the Class permission schema means that the Record permission schema won’t be affected on a reсord. **Class entity** **Class** is an entity that contains records. Class can be created via ConnectyCube dashboard only within Custom data module. Operations with Class entity are not allowed in API. All actions (Create, Read, Update and Delete) that are available for the ‘Class’ entity are also applicable for all records within a class. Default Class permission schema is using during the creation of a class: * **Create:** Open * **Read:** Open * **Update:** Owner * **Delete:** Owner To enable applying Class permissions for any of the action types, ‘Use Class permissions’ check box should be ticked. It means that the record permission schema (if existing) won’t be affected on a record. **Record entity** **Record** is an entity within the Class in the Custom Data module that can be created in ConnectyCube dashboard and via API. Each record within a Class has its own permission level. Unlike Class entity, ‘Not allowed’ permission level isn’t available for a record as well as only three actions are available to work with a record - read, update and delete. Default values for Record permission scheme: * Read: Open * Update: Owner * Delete: Owner To set a separate permission scheme for a record, open a record to edit and click on **`SET PERMISSION ON RECORD`** button:  Define the permission level for each of available actions. ## Create a new record [Section titled “Create a new record”](#create-a-new-record) Create a new record with the defined parameters in the class. Fields that weren’t defined in the request but are available in the scheme (class) will have null values. ```javascript const className = 'call_history_item'; const record = { 'call_name': 'Group call', 'call_participants': [2325293, 563541, 563543], 'call_start_time': 1701789791673, 'call_end_time': 0, 'call_duration': 0, 'call_state': 'accepted', 'is_group_call': true, 'call_id': 'f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454', 'user_id': 2325293, 'caller_location': [50.004444, 36.234380] } try { const result = await ConnectyCube.data.create(className, record); console.log(result) } catch (error) { console.error(error) } ``` ### Create a record with permissions [Section titled “Create a record with permissions”](#create-a-record-with-permissions) To create a new record with permissions, add the `permissions` parameter to a record. In this case, the request will look like this: ```javascript const className = 'call_history_item'; const record = { call_name: 'Group call', call_participants: [2325293, 563541, 563543], call_start_time: 1701789791673, call_end_time: 0, call_duration: 0, call_state: 'accepted', is_group_call: true, call_id: 'f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454', user_id: 2325293, caller_location: [50.004444, 36.234380], permissions: { read: { access: 'owner' }, update: { access: 'open_for_users_ids', ids: [563541, 563543] }, delete: { access: 'open_for_groups', groups: ['group87', 'group108'] } } } try { const result = await ConnectyCube.data.create(className, record); console.log(result) } catch (error) { console.error(error) } ``` ## Create multi records [Section titled “Create multi records”](#create-multi-records) Create several new records in the class. Fields that weren’t defined in the request but available in the scheme would have null values. ```javascript const className = 'call_history_item'; const record_1 = { 'call_name': 'Group call 1', 'call_participants': [2325293, 563541, 563543], 'call_start_time': 1701789791673, 'call_end_time': 0, 'call_duration': 0, 'call_state': 'accepted', 'is_group_call': true, 'call_id': 'f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454', 'user_id': 2325293, 'caller_location': [50.004444, 36.234380] } const record_2 = { 'call_name': 'Group call 2', 'call_participants': [2325293, 563541, 563543], 'call_start_time': 1701789832955, 'call_end_time': 0, 'call_duration': 0, 'call_state': 'accepted', 'is_group_call': true, 'call_id': 'a2a7bc3f-2eeb-3d72-11b0-566d3c4c2748', 'user_id': 2325293, 'caller_location': [50.004444, 36.234380] } const records = [record_1, record_2] try { const result = await ConnectyCube.data.create(className, records); console.log(result.items) } catch (error) { console.error(error) } ``` ## Retrieve record by ID [Section titled “Retrieve record by ID”](#retrieve-record-by-id) Retrieve the record by specifying its identifier. ```javascript const className = 'call_history_item'; const id = '656f407e29d6c5002fce89dc'; try { const result = await ConnectyCube.data.list(className, id); console.log(result.items) } catch (error) { console.error(error) } ``` ## Retrieve records by IDs [Section titled “Retrieve records by IDs”](#retrieve-records-by-ids) Retrieve records by specifying their identifiers. ```javascript const className = 'call_history_item'; const ids = ['656f407e29d6c5002fce89dc', '5f985984ca8bf43530e81233']; // or `const ids = '656f407e29d6c5002fce89dc,5f985984ca8bf43530e81233'`; try { const result = await ConnectyCube.data.list(className, ids); console.log(result.items) } catch (error) { console.error(error) } ``` ## Retrieve records within a class [Section titled “Retrieve records within a class”](#retrieve-records-within-a-class) Search records within the particular class. > **Note:** If you are sorting records by time, use the `_id` field. It is indexed and it will be much faster than sorting by `created_at` field. The list of additional parameter for sorting, filtering, aggregation of the search response is provided by link ```javascript const className = 'call_history_item'; const filters = { call_start_time: { gt: 1701789791673 } }; try { const result = await ConnectyCube.data.list(className, filter); console.log(result.items) } catch (error) { console.error(error) } ``` ## Retrieve the record’s permissions [Section titled “Retrieve the record’s permissions”](#retrieve-the-records-permissions) > **Note:** record permissions are checked during request processing. Only the owner has an ability to view a record’s permissions. ```javascript const className = 'call_history_item'; const id = '656f407e29d6c5002fce89dc'; try { const result = await ConnectyCube.data.readPermissions(className, id); console.log(result.permissions) console.log(result.record_is) } catch (error) { console.error(error) } ``` ## Update record by ID [Section titled “Update record by ID”](#update-record-by-id) Update record data by specifying its ID. ```javascript const className = 'call_history_item'; const params = { id: '656f407e29d6c5002fce89dc', call_end_time: 1701945033120, }; try { const result = await ConnectyCube.data.update(className, params); console.log(result) } catch (error) { console.error(error) } ``` ## Update records by criteria [Section titled “Update records by criteria”](#update-records-by-criteria) Update records found by the specified search criteria with a new parameter(s). The structure of the parameter `search_criteria` and the list of available operators provided by link ```javascript const className = 'call_history_item'; const params = { search_criteria: { user_id: 1234567 }, call_name: 'Deleted user' }; try { const result = await ConnectyCube.data.update(className, params); console.log(result.items) } catch (error) { console.error(error) } ``` ## Update multi records [Section titled “Update multi records”](#update-multi-records) Update several records within a class by specifying new values. ```javascript const className = 'call_history_item'; const params = [{ id: '656f407e29d6c5002fce89dc', call_end_time: 1701945033120, }, { id: '5f998d3bca8bf4140543f79a', call_end_time: 1701945033120, }]; try { const result = await ConnectyCube.data.update(className, params); console.log(result.items) } catch (error) { console.error(error) } ``` ## Delete record by ID [Section titled “Delete record by ID”](#delete-record-by-id) Delete a record from a class by record identifier. ```javascript const className = 'call_history_item'; const id = '5f998d3bca8bf4140543f79a'; try { await ConnectyCube.data.delete(className, id); } catch (error) { console.error(error) } ``` ## Delete several records by their IDs [Section titled “Delete several records by their IDs”](#delete-several-records-by-their-ids) Delete several records from a class by specifying their identifiers. If one or more records can not be deleted, an appropriate error is returned for that record(s). ```javascript const className = 'call_history_item'; const ids = ['656f407e29d6c5002fce89dc', '5f998d3bca8bf4140543f79a']; try { const result = await ConnectyCube.data.delete(className, id); console.log(result) } catch (error) { console.error(error) } ``` ## Delete records by criteria [Section titled “Delete records by criteria”](#delete-records-by-criteria) Delete records from the class by specifying a criteria to find records to delete. The search query format is provided by link ```javascript const className = 'call_history_item'; const params = { call_id: { in: ['f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454'] } }; try { const result = await ConnectyCube.data.delete(className, params); console.log(result) } catch (error) { console.error(error) } ``` ## Relations [Section titled “Relations”](#relations) Objects (records) in different classes can be linked with a `parentId` field. If the record from the **Class 1** is pointed with a record from **Class 2** via its `parentId`, the `parentId` field will contain an ID of a record from the **Class 2**. If a record from the **Class 2** is deleted, all its children (records of the **Class 1** with `parentId` field set to the **Class 2** record ID) will be automatically deleted as well. The linked children can be retrieved with `_parent_id={id_of_parent_class_record}` parameter.
# How to Setup Firebase
> A complete, step-by-step installation guide to integrate Firebase to your Web application, empowering to easily create feature-rich, scalable applications.
## Firebase account and project registration [Section titled “Firebase account and project registration”](#firebase-account-and-project-registration) Follow these steps to register your Firebase account and create a Firebase project: 1. **Register a Firebase account** at [Firebase console](https://console.firebase.google.com/) . You can use your Google account to authenticate at Firebase. 2. Click **Create project**  > **Note**: If you have a Google project registered for your mobile app, select it from the **Project name** dropdown menu. You can also edit your **Project ID** if you need. A unique ID is assigned automatically to each project. This ID is used in publicly visible Firebase features. For example, it will be used in database URLs and in your Firebase Hosting subdomain. If you need to use a specific subdomain, you can change it. 3. Fill in the fields required (Project name, Project ID, etc.) and click **Continue**.   4. Configure Google Analytics for your project and click **Create project**.  Then click Continue.  5. Select platform for which you need Firebase  If you already have an app in your project, go to project Overview page and click Add another app link:  ## Connect Firebase SDK [Section titled “Connect Firebase SDK”](#connect-firebase-sdk) Here is a step by step guide how to connect Firebase SDK to your **Web** Project: 1. Copy the code from **Add Firebase to your web app** screen and paste it in the ****tag of your HTML page before other script tags:  > **Note**: The above code snippet should be used to initialize Firebase Javascript SDK for *Authentication, Cloud Storage, Realtime Database*, and *Cloud Firestore*. You can include only the features that you need. In our case **Authentication (firebase-auth)** component is necessary. If you use **FCM push notifications on Web**, then you need to include **Firebase Cloud Messaging (firebase-messaging)** component as well. 2. From the CDN you need to **include the following components**: ```html ``` In case you are using a bundler like Browserify or webpack, you can just require() the components that you use: ```javascript // Firebase App is always required and must be first const firebase = require("firebase/app"); // Add additional services that you want to use require("firebase/auth"); require("firebase/messaging"); const config = { // ... }; firebase.initializeApp(config); ``` ## Firebase authentication [Section titled “Firebase authentication”](#firebase-authentication) This option allows users in your app authenticate themselves via phone number. If you use this method for user authentication, the user receives an SMS with verification code and authenticates via that code in your app. You need to follow these steps to add Firebase authentication to your Web project: 1. Go to **Firebase console >> Authentication >> Sign-in method** section:  2. Enable **Phone** number sign-in method:  3. In **Firebase console >> Authentication >> Sign-in method** section check **Authorized domains** block and if your domain that will host your app is not added there, add it:  Then enter your domain name on the pop-up window that appears and click **Add**:  4. Set up **reCAPTCHA verification**: > **Note**: If you use Firebase SDK’s `RecaptchaVerifier` object, Firebase creates and handles any necessary client keys and secrets automatically, so you do not need to set up a reCAPTCHA client manually. **reCAPTCHA** options: 1. **Invisible reCAPTCHA** `RecaptchaVerifier` object supports [invisible reCAPTCHA](https://www.google.com/recaptcha/intro/invisible.html), which can verify the user without requiring any user action. In order to set it you need to create a `RecaptchaVerifier` object with the `size` parameter set to `invisible`, specifying the ID of the button that submits your sign-in form: ```javascript window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier("sign-in-button", { size: "invisible", callback: (response) => { // reCAPTCHA solved, allow signInWithPhoneNumber. onSignInSubmit(); }, }); ``` ````plaintext 2) **reCAPTCHA widget** `RecaptchaVerifier` object supports reCAPTCHA widget, which always requires user interaction to complete successfully. In order to set it you need create an element on your page to contain the widget, and then create a `RecaptchaVerifier` object, specifying the ID of the container when you do so. ```javascript window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container'); ```` **Optional features**: 1. **Localization** reCAPTCHA can be localized by updating the language code on the Auth instance before rendering the reCAPTCHA. This localization will apply to the SMS message with verification code sent to the user as well. ```javascript firebase.auth().languageCode = "it"; // To apply the default browser preference instead of explicitly setting it. // firebase.auth().useDeviceLanguage(); ``` 2. **reCAPTCHA parameters** It is possible to set callback functions on the `RecaptchaVerifier` object that are called when the user solves the reCAPTCHA or the reCAPTCHA expires before the user submits the form: ```javascript window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier("recaptcha-container", { size: "normal", callback: (response) => { // reCAPTCHA solved, allow signInWithPhoneNumber. // ... }, "expired-callback": () => { // Response expired. Ask user to solve reCAPTCHA again. // ... }, }); ``` 3. **Pre-render the reCAPTCHA** It is possible to pre-render the reCAPTCHA before you submit a sign-in request, if you call `render`: ```javascript recaptchaVerifier.render().then((widgetId) => { window.recaptchaWidgetId = widgetId; }); ``` After `render` resolves, you get the **reCAPTCHA’s widget ID**. You can use it to make calls to the [reCAPTCHA](https://developers.google.com/recaptcha/intro) API: ```javascript const recaptchaResponse = grecaptcha.getResponse(window.recaptchaWidgetId); ``` 5. Send a verification code to user’s phone: In order to request that Firebase send an authentication code to the user’s phone by SMS, set an interface that prompts users to provide their phone number, and then call `signInWithPhoneNumber` as follows: 1. Get the user’s phone number > **Note** > > As a best practice please do not forget to inform your users that if they use phone sign-in, they might receive an SMS message for verification and standard rates apply. 2. Call `signInWithPhoneNumber`, passing to it the user’s phone number and `RecaptchaVerifier` you created earlier: ```javascript const phoneNumber = getPhoneNumberFromUserInput(); const appVerifier = window.recaptchaVerifier; firebase .auth() .signInWithPhoneNumber(phoneNumber, appVerifier) .then((confirmationResult) => { // SMS sent. Prompt user to type the code from the message, then sign the // user in with confirmationResult.confirm(code). window.confirmationResult = confirmationResult; }) .catch((error) => { // Error; SMS not sent // ... }); ``` If `signInWithPhoneNumber` results in an error, reset reCAPTCHA so that the user can try verifying again: ```javascript grecaptcha.reset(window.recaptchaWidgetId); // Or, if you haven't stored the widget ID: window.recaptchaVerifier.render().then(widgetId => { grecaptcha.reset(widgetId); } ``` > **Note**: `signInWithPhoneNumber` method issues reCAPTCHA challenge to the user, and if the user passes the challenge, requests that Firebase Authentication send an SMS message containing a verification code to the user’s phone. 6. Sign in the user with the verification code: 1. Once the call to `signInWithPhoneNumber` succeeds, you need to prompt the user to type the verification code they received by SMS. 2. Then, sign in the user by passing the code to `confirm` method of `ConfirmationResult` object that was passed to `signInWithPhoneNumber`’s fulfillment handler (that is, its `then` block): ```javascript const code = getCodeFromUserInput(); let user; confirmationResult .confirm(code) .then((result) => { // User signed in successfully. user = result.user; // ... }) .catch((error) => { // User couldn't sign in (bad verification code?) // ... }); ``` If the call to `confirm` succeeded, the user is successfully signed in. 3. Get the intermediate `AuthCredential` object: If you need to get an `AuthCredential` object for the user’s account, pass the verification code from the confirmation result and the verification code to `PhoneAuthProvider.credential` instead of calling `confirm`: ```javascript const credential = firebase.auth.PhoneAuthProvider.credential(confirmationResult.verificationId, code); ``` Then, you can sign in the user with the credential: ```javascript let user; firebase .auth() .signInAndRetrieveDataWithCredential(credential) .then((result) => { // User signed in successfully. user = result.user; }) .catch((error) => {}); ``` 7. Get **Firebase accessToken** after SMS code verification as follows: ```javascript let accessToken; user.getIdToken().then((idToken) => { accessToken = idToken; }); ``` 8. Get your **Project ID** from your **Firebase console**:  9. Pass your Firebase `project_id` and Firebase `access_token` parameters to `login` method: ```javascript const userCredentials = { provider: 'firebase_phone', firebase_phone[project_id]: '...', firebase_phone[access_token]: accessToken }; ConnectyCube.login(userCredentials) .then(user => {}); .catch(error => {}); ``` 10. Try running a test. Once your user is logged in successfully, you will find him/her in your **Dashboard >> Your App >> Users** section.  So now you know how to use Firebase features in your ConnectyCube apps. If you have any difficulties - please let us know via [support channel](mailto:support@connectycube.com) ```plaintext ```
# Push Notifications
> Elevate your Web app's performance with push notifications API guide. Keep users engaged with real-time updates, ensuring seamless interaction on the go.
Push Notifications provide a way to deliver some information to users while they are not using your app actively. The following use cases can be covered additionally with push notifications: * send a chat message when a recipient is offline (a push notification will be initiated automatically in this case) * make a video call with offline opponents (need to send a push notification manually) ## Configuration [Section titled “Configuration”](#configuration) In order to start work with push notifications you need to configure it. 1. Generate VAPID keys via npm package: run `npx web-push generate-vapid-keys` to get **Public Key** and **Private Key** values. 2. Set keys in ConnectyCube dashboard: * Open your ConnectyCube Dashboard at [admin.connectycube.com](https://admin.connectycube.com) * Go to **Push notifications** module, **Credentials** page * Set **Public Key** and **Private Key** values in the fields for “WebPush Notifications”. Set `mailto:example@yourdomain.org` for Subject field.  ## Subscribe to push notifications [Section titled “Subscribe to push notifications”](#subscribe-to-push-notifications) 1. Create a service worker file (`*.js`) and place it on your site. 2. Use the following example to register the Service Worker at the correct path and subscribe to [PushManager](https://developer.mozilla.org/en-US/docs/Web/API/PushManager) using the **Public Key** as the `applicationServerKey`: ```javascript const registration = await navigator.serviceWorker .register("path_to/serviceWorker.js"); const subscription = await registration.pushManager .subscribe({ applicationServerKey: "", userVisibleOnly: true }); const subscriptionData = subscription.toJSON(); ``` 3. Retrieve `subscriptionData` to create ConnectyCube subscription: ```javascript const uniqDeviceIdentifier = "" // Any unique fingerprint to identify the subscription with the browser tab, e.g. https://github.com/fingerprintjs/fingerprintjs const params = { notification_channels: "web", device: { platform: "web", udid: uniqDeviceIdentifier, }, push_token: { environment: "development", web_endpoint: subscriptionData.endpoint, web_auth: subscriptionData.auth, web_p256dh: subscriptionData.p256dh, }, }; const result = await ConnectyCube.pushnotifications.subscriptions.create(params); ``` ## Send push notifications [Section titled “Send push notifications”](#send-push-notifications) You can manually initiate a push notification to user/users on any event in your application. To do so you need to form a push notification parameters (payload) and set the push recipients: ```javascript const payload = JSON.stringify({ message: "This is a new chat message" }); const params = { notification_type: "push", user: { ids: [21, 12] }, // recipients. environment: "development", // environment, can be 'production'. message: ConnectyCube.pushnotifications.base64Encode(payload), }; const result = await ConnectyCube.pushnotifications.events.create(params); ``` ## Receive push notifications [Section titled “Receive push notifications”](#receive-push-notifications) You will receive notifications in the service worker and display them using the [Notification API](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API). To successfully use the [`ServiceWorkerRegistration: showNotification()`](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification) method, you first need to request permission via the [`Notification: requestPermission()`](https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission_static) method. The following code demonstrates how to handle push notifications in a service worker: ```javascript self.addEventListener('install', (event) => { self.skipWaiting(); }); self.addEventListener('push', (event) => { const data = event.data.json().data; const title = data.title; const options = { body: data.message, icon: data.photo }; event.waitUntil(self.registration.showNotification(title, options)); }); self.addEventListener('notificationclick', (event) => { event.notification.close(); event.waitUntil(clients.openWindow('/')); }); ``` ## Unsubscribe [Section titled “Unsubscribe”](#unsubscribe) In order to unsubscribe and stop receiving push notifications you need to list your current subscriptions and then choose those to be deleted: ```javascript const subscriptions = await ConnectyCube.pushnotifications.subscriptions .list(); const subscriptionId = subscriptions[0].subscription.id; const result = await ConnectyCube.pushnotifications.subscriptions .delete(subscriptionId); ```
# Streaming
> Leverage ConnectyCube’s streaming feature for dynamic real-time interactions in Web app. Ideal for interactive sessions, such as teachers broadcasting to multiple students.
ConnectyCube streaming is built on top of [WebRTC](https://webrtc.org/) protocol. The main use case for streaming is with a teacher and many students where normally students join a call as listeners and only a teacher publishes own media stream: **Streaming API** is built on top of [Video Conferencing API](/js/videocalling-conference), hence same API documentation should be followed. The only difference is when join a room - a `session.joinAsListener(...)` API should be used at students side instead of `session.join(...)` API at teacher’s side. This method allows to join a conference room w/o publishing own media stream.
# use-chat React hook
> Simplify chat app state management with use-chat lightweight React hook for real-time chat.
**use-chat** is a React hook for state management in ConnectyCube-powered chat solutions. This library provides a headless solution for managing chat functionality in ConnectyCube. Similar to how Formik simplifies form handling, this library streamlines the development of chat applications. The core purpose is to handle essential chat features like state management, handling subsequent events and APIs properly etc, so the end user takes care about UI building only. ## Features [Section titled “Features”](#features) * Handle chats and messages states, including the currently active conversation * Manage chat participants states * Maintain typing indicators and users last activity. * Support attachments download * Message drafts * Moderation via user reporting and block ## Installation [Section titled “Installation”](#installation) ```plaintext npm install @connectycube/use-chat ``` or ```plaintext yarn add @connectycube/use-chat ``` ## Usage [Section titled “Usage”](#usage) ```ts import { useChat } from "@connectycube/use-chat"; const MyComponent = () => { const { connect, createChat, sendMessage, selectedDialog } = useChat(); const handleConnect = async () => { const chatCredentials = { userId: 22, password: "password", }; await connect(chatCredentials); }; const handleCreateChat = async () => { const userId = 456; const dialog = await createChat(userId); await selectDialog(dialog); }; const handleSendMessage = async () => { // send message to selected dialog sendMessage("Hi there"); }; return (
); }; export default MyComponent; ``` For more complex example please check [React chat code sample](https://github.com/ConnectyCube/connectycube-web-samples/tree/master/chat-react) ## API [Section titled “API”](#api) Check types for more API examples ## Have an issue? [Section titled “Have an issue?”](#have-an-issue) Join our [Discord](https://discord.com/invite/zqbBWNCCFJ) for quick answers to your questions ## Changelog [Section titled “Changelog”](#changelog)
# Whiteboard
> Enable dynamic collaboration in chat with ConnectyCube Whiteboard API. Ideal for remote meetings, teaching environments, sales demos, real-time workflows.
ConnectyCube **Whiteboard API** allows to create whiteboard functionality and associate it with a chat dialog. Chat dialog’s users can collaborate and draw simultaneously on a whiteboard. You can do freehand drawing with a number of tools, add shapes, lines, text and erase. To share boards, you just get an easy link which you can email. Your whiteboard stays safe in the cloud until you’re ready to return to it.  The most popular use cases for using the whiteboard: * Remote meetings * Remote teaching * Sales presentations * Workflows * Real-time collaboration ## Get started with SDK [Section titled “Get started with SDK”](#get-started-with-sdk) Follow the [Getting Started guide](/js/) on how to connect ConnectyCube SDK and start building your first app. ## Preparations [Section titled “Preparations”](#preparations) In order to start using whiteboard, an additional config has to be provided: ```javascript const CONFIG = { ... whiteboard: { server: 'https://whiteboard.connectycube.com' } } ConnectyCube.init(CREDS, CONFIG); ``` Then, ConnectyCube whiteboard is associated with a chat dialog. In order to create a whiteboard, you need to have a chat dialog. Refer to [chat dialog creation API](/js/messaging#create-new-dialog). ## Create whiteboard [Section titled “Create whiteboard”](#create-whiteboard) When create a whiteboard you need to pass a name (any) and a chat dialog id to which whiteboard will be connected. ```javascript const params = { name: 'New Whiteboard', chat_dialog_id: "5356c64ab35c12bd3b108a41" }; ConnectyCube.whiteboard.create(params) .then(whiteboard => { const wid = whiteboard._id; }) .catch(error => { }); ``` Once whiteboard is created - a user can display it in app in a WebView using the following url: `https://whiteboard.connectycube.com?whiteboardid=<_id>&username=&title=` For `username` - any value can be provided. This is to display a text hint near the drawing arrow. ## Retrieve whiteboards [Section titled “Retrieve whiteboards”](#retrieve-whiteboards) Use the following code snippet to retrieve a list of whiteboards associated with a chat dialog: ```javascript const params = {chat_dialog_id: "5456c64ab35c17bd3b108a76"}; ConnectyCube.whiteboard.get(params) .then(whiteboard => { }) .catch(error => { }); ``` ## Update whiteboard [Section titled “Update whiteboard”](#update-whiteboard) A whiteboard can be updated, e.g. its name: ```javascript const whiteboardId = "5456c64ab35c17bd3b108a76"; const params = { name: 'New Whiteboard', }; ConnectyCube.whiteboard.update(whiteboardId, params) .then(whiteboard => { }) .catch(error => { }); ``` ## Delete whiteboard [Section titled “Delete whiteboard”](#delete-whiteboard) ```javascript const whiteboardId = "5456c64ab35c17bd3b108a76"; ConnectyCube.whiteboard.delete(whiteboardId) .then(whiteboard => { }) .catch(error => { }); ```
# Send first chat message
> Step-by-step guide to sending your first chat message using ConnectyCube ReactNative 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](/reactnative/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) Install package from the command line: * npm ```bash npm install react-native-connectycube --save ``` * yarn ```bash yarn add react-native-connectycube ``` ### Import SDK [Section titled “Import SDK”](#import-sdk) Add the following import statement to start using all classes and methods. ```javascript import ConnectyCube from 'react-native-connectycube'; ``` ### Initialize SDK [Section titled “Initialize SDK”](#initialize-sdk) Initialize the SDK 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: "cubeuser", password: "awesomepwd" }; // const userCredentials = { email: 'cubeuser@gmail.com', password: 'awesomepwd' }; // const userCredentials = { provider: 'facebook', keys: {token: 'a876as7db...asg34dasd8wqe'} }; 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. This code example demonstrates how to send and receive messages in the created 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) } ``` That’s it! 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](/reactnative/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 ReactNative Video SDK - from session creation to initialising and accepting the call
**ConnectyCube’s 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](/reactnative/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 voice and video calls 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 [Connect React Native WerRTC lib](#step-2-сonnect-react-native-webrtc-lib). ### Install SDK [Section titled “Install SDK”](#install-sdk) Install package from the command line: * npm ```bash npm install react-native-connectycube --save ``` * yarn ```bash yarn add react-native-connectycube ``` ### Import SDK [Section titled “Import SDK”](#import-sdk) Add the following import statement to start using all classes and methods. ```javascript import ConnectyCube from 'react-native-connectycube'; ``` ### Initialize SDK [Section titled “Initialize SDK”](#initialize-sdk) Initialize the SDK 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: Сonnect React Native WebRTC lib [Section titled “Step 2: Сonnect React Native WebRTC lib”](#step-2-сonnect-react-native-webrtc-lib) Since **react-native-connectycube** version 3.34.0, the **react-native-webrtc** has been replaced from dependencies to peerDependencies to support autolinking. Install the **react-native-webrtc** and follow the [Getting started](https://github.com/react-native-webrtc/react-native-webrtc?tab=readme-ov-file#getting-started). Configure your [iOS](https://github.com/react-native-webrtc/react-native-webrtc/blob/master/Documentation/iOSInstallation.md) and [Android](https://github.com/react-native-webrtc/react-native-webrtc/blob/master/Documentation/AndroidInstallation.md) projects. ## Step 3: Create and Authorise User [Section titled “Step 3: Create and Authorise User”](#step-3-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: "cubeuser", password: "awesomepwd" }; // const userCredentials = { email: 'cubeuser@gmail.com', password: 'awesomepwd' }; // const userCredentials = { provider: 'facebook', keys: {token: 'a876as7db...asg34dasd8wqe'} }; 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 4: Connect User to chat [Section titled “Step 4: Connect User to chat”](#step-4-connect-user-to-chat) Connecting to the chat is an essential step in enabling real-time communication. **ConnectyCube Chat API** is used as a signaling transport for Video Calling API, so to start using Video Calling API you need to connect user to Chat: ```javascript const userCredentials = { userId: 4448514, password: "supersecurepwd", }; ConnectyCube.chat .connect(userCredentials) .then(() => { // connected }) .catch((error) => {}); ``` ## Step 5: Create video session [Section titled “Step 5: Create video session”](#step-5-create-video-session) Also, to use Video Calling API you need to create a call session object - choose your opponent(s) you will have a call with and a type of session (VIDEO or AUDIO): ```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 6: Access local media stream [Section titled “Step 6: Access local media stream”](#step-6-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) => { // pass a local or remote stream to the RTCView component // // }) .catch((error) => {}); ``` ## Step 7: Initiate a call [Section titled “Step 7: Initiate a call”](#step-7-initiate-a-call) The *initiate call* function is essential for starting a video call session between users, enabling real-time communication by establishing the initial connection: ```javascript const extension = {}; session.call(extension); ``` 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 8: Accept call [Section titled “Step 8: Accept call”](#step-8-accept-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 a video element // import {RTCView} from 'react-native-connectycube'; // // }; ``` 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](/reactnative/videocalling) * [Conference calling SDK documentation](/reactnative/videocalling-conference/)
# Chat
> Integrate powerful chat functionality into your ReactNative 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.  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"; ConnectyCube.chat.dialog .getPublicOccupants(dialogId) .then((result) => { // result.items }) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.getPublicOccupants(dialogId)`: ```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 = ""; // 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](/reactnative/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  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 network offline event with ping check [Section titled “Handle network offline event with ping check”](#handle-network-offline-event-with-ping-check) In React Native, use the [@react-native-community/netinfo](https://github.com/react-native-netinfo/react-native-netinfo) library to listen for network status changes. When the device goes offline, attempt to ping the chat server and terminate the chat connection if the ping fails: ```javascript import { addEventListener } from "@react-native-community/netinfo"; const PING_TIMEOUT = 3000; const unsubscribe = addEventListener(state => { if (!state.isConnected) { 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 ReactNative applications with our 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](/reactnative/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](/reactnative/messaging#connect-to-chat). ### Connect WebRTC lib [Section titled “Connect WebRTC lib”](#connect-webrtc-lib) Since “react-native-connectycube” version 3.34.0, the “react-native-webrtc” has been replaced from dependencies to peerDependencies to support autolinking. Install the “react-native-webrtc” and follow the [Getting started](https://github.com/react-native-webrtc/react-native-webrtc?tab=readme-ov-file#getting-started). Configure your [iOS](https://github.com/react-native-webrtc/react-native-webrtc/blob/master/Documentation/iOSInstallation.md) and [Android](https://github.com/react-native-webrtc/react-native-webrtc/blob/master/Documentation/AndroidInstallation.md) projects. Until “react-native-connectycube” version 3.34.0, the “react-native-webrtc” is included to “react-native-connectycube”. Follow the [Android Manual Linking](https://github.com/react-native-webrtc/react-native-webrtc/blob/1.98.0/Documentation/AndroidInstallation.md) and [iOS Manual Linking](https://github.com/react-native-webrtc/react-native-webrtc/blob/1.98.0/Documentation/iOSInstallation.md) guides. ## 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): ```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 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. ## Attach local media stream [Section titled “Attach local media stream”](#attach-local-media-stream) Then you should attach your local media stream to a video element: ```javascript import {RTCView} from 'react-native-connectycube'; // pass a local or remote stream to the RTCView component ... ... ``` ## Initiate a call [Section titled “Initiate a call”](#initiate-a-call) ```javascript const extension = {}; session.call(extension); ``` 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 a video element // import {RTCView} from 'react-native-connectycube'; // // }; ``` 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) See [CallKit section](#callkit) below. ## 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 sesison 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: Platform.OS }; 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) ```javascript localStream.getVideoTracks().forEach(track => track._switchCamera()); ``` ## Switch audio output [Section titled “Switch audio output”](#switch-audio-output) 1. connect and install lib 2. Use `InCallManager` class to switch audio output: ```javascript import InCallManager from 'react-native-incall-manager'; ... let isSpeakerOn = true; // false InCallManager.setSpeakerphoneOn(isSpeakerOn); ``` Also, pay attention to [InCall Manager lib](https://github.com/react-native-webrtc/react-native-incall-manager) if you need to use one of the options below: * Manage devices events like wired-headset plugged-in state, proximity sensors and expose functionalities to javascript. * Automatically route audio to proper devices based on events and platform API. * Toggle speaker or microphone on/off, toggle flashlight on/off * Play ringtone/ringback/dtmftone ## Screen Sharing [Section titled “Screen Sharing”](#screen-sharing) Request a desktop stream by calling `getDisplayMedia`: ```javascript session .getDisplayMedia() .then((localDesktopStream) => {}) .catch((error) => {}); ``` In React Native, the [getDisplayMedia](https://github.com/react-native-webrtc/react-native-webrtc/blob/fe5ce9798cae5eb53fa028309b0217d7c22f255d/src/getDisplayMedia.ts#L9) method does not accept constrains as a parameter due to [react-native-webrtc](https://github.com/react-native-webrtc/react-native-webrtc) limitations. 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. ### Screen Sharing on Android [Section titled “Screen Sharing on Android”](#screen-sharing-on-android) Follow this guide to configure [Screen Sharing on Android](https://github.com/react-native-webrtc/react-native-webrtc/blob/master/Documentation/AndroidInstallation.md#screen-sharing) and this to support [Screen Capture on Android 10+](https://github.com/react-native-webrtc/react-native-webrtc/blob/master/Documentation/AndroidInstallation.md#screen-capture-support---android-10). Call the `getDisplayMedia` after starting a ForegroundService: ```javascript import { Platform } from 'react-native'; import notifee, { AndroidImportance } from '@notifee/react-native'; /* ... */ const ScreenSharingComponent() { const mediaParams = { audio: true, video: true, }; const startShareScreen = async () => { if (Platform.OS === 'android') { const channelId = await notifee.createChannel({ id: 'screen_capture', name: 'Screen Capture', lights: false, vibration: false, importance: AndroidImportance.DEFAULT, }); await notifee.displayNotification({ title: 'Screen Capture', body: 'Capturing...', android: { channelId, asForegroundService: true, }, }); } // Capturing a screen const stream = await session.getDisplayMedia(); }; const stopShareScreen = async () => { if (Platform.OS === 'android') { await notifee.stopForegroundService(); } //Get back media stream from camera or call `session.stop(extension)` const stream = await session.getUserMedia(mediaParams); }; return <>{/* ... */}> } export default ScreenSharingComponent; ``` ### Screen Sharing on iOS [Section titled “Screen Sharing on iOS”](#screen-sharing-on-ios) Preform the [Screen Sharing integration for iOS](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-ios-sdk/#screen-sharing-integration) guide from Jitsi. Use `` component from ‘react-native-webrtc’ to prepare: ```javascript import React from 'react'; import { findNodeHandle, Platform, NativeModules } from 'react-native'; import { ScreenCapturePickerView } from 'react-native-webrtc'; /* ... */ const ScreenSharingComponent() { const screenCaptureView = React.useRef(null); const mediaParams = { audio: true, video: true, }; const startShareScreen = async () => { if (Platform.OS === 'ios') { const reactTag = findNodeHandle(screenCaptureView.current); await NativeModules.ScreenCapturePickerViewManager.show(reactTag); } // Capturing a screen const stream = await session.getDisplayMedia(); }; const stopShareScreen = async () => { // Get back media stream from camera or call `session.stop(extension)` const stream = await session.getUserMedia(mediaParams); }; return <> {/* ... */} {Platform.OS === 'ios' && ( )} > } export default ScreenSharingComponent; ``` ## 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](/reactnative/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 import { fetch, addEventListener } from "@react-native-community/netinfo"; let isOnline; fetch().then(state => { isOnline = state.isConnected; }); const unsubscribe = addEventListener(state => { isOnline = state.isConnected; }); ``` * 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 import { addEventListener } from "@react-native-community/netinfo"; const unsubscribe = addEventListener(state => { if (!isOnline && state.isConnected) { if (session && needIceRestartForUsersIds.length > 0) { for (let userID of needIceRestartForUsersIds) { maybeDoIceRestart(session, userID); } } } isOnline = state.isConnected; }); ``` 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, }, }; ``` ### Echo cancelation issue on some Android devices [Section titled “Echo cancelation issue on some Android devices”](#echo-cancelation-issue-on-some-android-devices) A possible solution can be found here ## Recording [Section titled “Recording”](#recording) Not available as for now ## Continue call in background [Section titled “Continue call in background”](#continue-call-in-background) If you are developing dedicated apps for iOS and Android - it’s required to apply additional configuration for the app to keeping the call alive when it goes into the background. \*\*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:  \*\*Android: \*\* For Android, we also recommend to implement the same camera switch flow when go to background and then return to foreground. To keep the call while in background, we recomment to use the **react-native-background-actions** library. The flow is the following: * when start a call or go background -> display notification * when stop call or return to foreground -> hide notification ```javascript // Background Activity import BackgroundService from 'react-native-background-actions'; startBackgroundMode = () => { this.stopBackgroundMode(); const options = { taskName: 'AppName', taskTitle: 'You have an active call', taskDesc: 'Press to return', taskIcon: { name: 'ic_notification', type: 'mipmap', }, linkingURI: 'appName://foreground', }; BackgroundService.start(async (params) => { await new Promise(async () => {}); }, options); }; stopBackgroundMode = (force = false) => { if (BackgroundService.isRunning() || force) { return BackgroundService.stop(); } }; ``` ## CallKit [Section titled “CallKit”](#callkit) > A ready [RNVideoChat code sample](https://github.com/ConnectyCube/connectycube-reactnative-samples/tree/master/RNVideoChat) with CallKit integrated is available at GitHub. All the below code snippets will taken from it. 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, a flow called CallKit is used. It’s a mix of CallKit API + Push Notifications API + VOIP Push Notifications API. The complete flow is similar to the following: * a call initiator should send a push notification (for iOS it’s VOIP 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, so the CallKit incoming call screen will be displayed, where a user can accept/reject the call. The following libs should be used to integrate CallKit functionality: * [CallKeep](https://github.com/react-native-webrtc/react-native-callkeep) lib for accessing CallKit API * [react-native-notifications](https://github.com/wix/react-native-notifications) lib along with [ConnectyCube Push Notifications API guide](/reactnative/push-notifications) for both push notifications and VOIP push notifications integration. Below we provide a detailed guide on additional steps that needs to be performed in order to integrate CallKit into a RN app. **Initiate a call** When initiate a call via `session.call()`, additionally we need to send a push notification (standard for Android user and VOIP for iOS). This is required for an opponent(s) to be able to receive an incoming call request when an app is in background or killed state. The following request will initiate a standard push notification for Android and a VOIP push notification for iOS: ```javascript const callType = "video" // "voice" const callInitiatorName = "..." const callOpponentsIds = [...] const pushParams = { message: `Incoming call from ${callInitiatorName}`, ios_voip: 1, handle: callInitiatorName, initiatorId: callSession.initiatorID, opponentsIds: callOpponentsIds.join(","), uuid: callSession.ID, callType }; PushNotificationsService.sendPushNotification(callOpponentsIds, pushParams); ... // PushNotificationsService: sendPushNotification(recipientsUsersIds, params) { const payload = JSON.stringify(params); const pushParameters = { notification_type: "push", user: { ids: recipientsUsersIds }, environment: __DEV__ ? "development" : "production", message: ConnectyCube.pushnotifications.base64Encode(payload), }; ConnectyCube.pushnotifications.events.create(pushParameters) .then(result => { console.log("[PushNotificationsService][sendPushNotification] Ok"); }).catch(error => { console.warn("[PushNotificationsService][sendPushNotification] Error", error); }); } ``` We recommend to simply copy-past the entire [src/services/pushnotifications-service.js](https://github.com/ConnectyCube/connectycube-reactnative-samples/blob/master/RNVideoChat/src/services/pushnotifications-service.js) file from RNVideoChat code sample into your app. ### Receive call request in background/killed state [Section titled “Receive call request in background/killed state”](#receive-call-request-in-backgroundkilled-state) The goal of CallKit is to receive call request when an app is in background or killed state. For iOS we will use CallKit and for Android we will use standard capabilities. **Android** First of all, we need to setup callbacks to receive push notification - in background and in killed state (there is a dedicated doc regarding how to setup a callback to receive pushes in killed state ): ```javascript import invokeApp from 'react-native-invoke-app'; class PushNotificationsService { constructor() { console.log("[PushNotificationsService][constructor]"); this._registerBackgroundTasks(); } init() { Notifications.events().registerNotificationReceivedBackground(async (notification, completion) => { console.log("[PushNotificationService] Notification Received - Background", notification.payload, notification?.payload?.message); if (Platform.OS === 'android') { if (await PermissionsService.isDrawOverlaysPermisisonGranted()) { invokeApp(); const dummyCallSession = { initiatorID: notificationBundle.initiatorId, opponentsIDs: notificationBundle.opponentsIds.split(","), ID: notificationBundle.uuid } store.dispatch(setCallSession(dummyCallSession, true, true)); } else { PushNotificationsService.displayNotification(notification.payload); } } // Calling completion on iOS with `alert: true` will present the native iOS inApp notification. completion({alert: true, sound: true, badge: false}); }); } _registerBackgroundTasks() { if (Platform.OS === 'ios') { return; } const { AppRegistry } = require("react-native"); // https://reactnative.dev/docs/headless-js-android // AppRegistry.registerHeadlessTask( "JSNotifyWhenKilledTask", () => { return async (notificationBundle) => { console.log('[JSNotifyWhenKilledTask] notificationBundle', notificationBundle); if (await PermissionsService.isDrawOverlaysPermisisonGranted()) { invokeApp(); const dummyCallSession = { initiatorID: notificationBundle.initiatorId, opponentsIDs: notificationBundle.opponentsIds.split(","), ID: notificationBundle.uuid } store.dispatch(setCallSession(dummyCallSession, true, true)); } else { PushNotificationsService.displayNotification(notificationBundle); } } }, ); } } ``` What we do is we simply open app (bringing the app to foreground) once a push re incoming call is received and display an incoming call screen. This is done via [react-native-invoke-app](https://github.com/ConnectyCube/react-native-invoke-app) lib. Also, we have `PermisisonsService` to check if a user granted a `DrawOverlays` permission to make the switch to foreground possible: ```javascript import { isOverlayPermissionGranted, requestOverlayPermission } from 'react-native-can-draw-overlays'; import { Alert } from "react-native"; class PermisisonsService { async checkAndRequestDrawOverlaysPermission() { if (Platform.OS !== 'android') { return true; } const isGranted = await this.isDrawOverlaysPermisisonGranted(); if (!isGranted) { Alert.alert( "Permission required", "For accepting calls in background you should provide access to show System Alerts from in background. Would you like to do it now?", [ { text: "Later", onPress: () => {}, style: "cancel" }, { text: "Request", onPress: () => { this.requestOverlayPermission(); }} ] ); } } async isDrawOverlaysPermisisonGranted() { const isGranted = await isOverlayPermissionGranted(); console.log("[PermisisonsService][isDrawOverlaysPermisisonGranted]", isGranted); return isGranted; } async requestOverlayPermission() { const granted = await requestOverlayPermission(); console.log("[PermisisonsService][requestOverlayPermission]", granted); return granted; } } const permisisonsService = new PermisisonsService(); export default permisisonsService; ``` We recommend to simply copy-past the entire [src/services/permissions-service.js](https://github.com/ConnectyCube/connectycube-reactnative-samples/blob/master/RNVideoChat/src/services/permissions-service.js) file from RNVideoChat code sample into your app. **iOS** For iOS we need to setup CallKit. For this a [react-native-callkeep](https://github.com/react-native-webrtc/react-native-callkeep) library will be used. We recommend to connect a version from github rather than froim npm: `"react-native-callkeep": "github:react-native-webrtc/react-native-callkeep#master"` All the logic is presented in `call-service.js` file: ```javascript // CallService import RNCallKeep, { CONSTANTS as CK_CONSTANTS } from 'react-native-callkeep'; import { getApplicationName } from 'react-native-device-info'; import RNUserdefaults from '@tranzerdev/react-native-user-defaults'; initCallKit() { if (Platform.OS !== 'ios') { return; } const options = { ios: { appName: getApplicationName(), includesCallsInRecents: false, } }; RNCallKeep.setup(options).then(accepted => { console.log('[CallKitService][setup] Ok'); }).catch(err => { console.error('[CallKitService][setup] Error:', err.message); }); // Add RNCallKeep Events // RNCallKeep.addEventListener('didReceiveStartCallAction', this.didReceiveStartCallAction); RNCallKeep.addEventListener('answerCall', this.onAnswerCallAction); RNCallKeep.addEventListener('endCall', this.onEndCallAction); RNCallKeep.addEventListener('didPerformSetMutedCallAction', this.onToggleMute); RNCallKeep.addEventListener('didChangeAudioRoute', this.onChangeAudioRoute); RNCallKeep.addEventListener('didLoadWithEvents', this.onLoadWithEvents); } onAnswerCallAction = (data) => { console.log('[CallKitService][onAnswerCallAction]', data); // let { callUUID } = data; // Called when the user answers an incoming call via Call Kit if (!this.isAccepted) { // by some reason, this event could fire > 1 times this.acceptCall({}, true); } }; onEndCallAction = async (data) => { console.log('[CallKitService][onEndCallAction]', data); let { callUUID } = data; if (this.callSession) { if (this.isAccepted) { this.rejectCall({}, true); } else { this.stopCall({}, true); } } else { const voipIncomingCallSessions = await RNUserdefaults.get("voipIncomingCallSessions"); if (voipIncomingCallSessions) { const sessionInfo = voipIncomingCallSessions[callUUID]; if (sessionInfo) { const initiatorId = sessionInfo["initiatorId"]; // most probably this is a call reject, so let's reject it via HTTP API ConnectyCube.videochat.callRejectRequest({ sessionID: callUUID, platform: Platform.OS, recipientId: initiatorId }).then(res => { console.log("[CallKitService][onEndCallAction] [callRejectRequest] done") }); } } } }; onToggleMute = (data) => { console.log('[CallKitService][onToggleMute]', data); let { muted, callUUID } = data; // Called when the system or user mutes a call this.muteMicrophone(muted, true) }; onChangeAudioRoute = (data) => { console.log('[CallKitService][onChangeAudioRoute]', data); const output = data.output; // could be Speaker or Receiver }; onLoadWithEvents = (events) => { console.log('[CallKitService][onLoadWithEvents]', events); // `events` is passed as an Array chronologically, handle or ignore events based on the app's logic // see example usage in https://github.com/react-native-webrtc/react-native-callkeep/pull/169 or https://github.com/react-native-webrtc/react-native-callkeep/pull/20 }; ``` Also, when perform any operations e.g. start call, accept, reject, stop etc, we need to report back to CallKit lib - to have both app UI and CallKit UI in sync: ```javascript RNCallKeep.startCall(callUUID, handle, contactIdentifier, handleType, hasVideo); ... RNCallKeep.answerIncomingCall(callUUID); ... RNCallKeep.rejectCall(callUUID); ... RNCallKeep.endCall(callUUID); ... RNCallKeep.setMutedCall(callUUID, isMuted); ... RNCallKeep.reportEndCallWithUUID(callUUID, reason); ``` For the `callUUID` we will be using call’s `session.ID`. The last point is to do the needed changes at iOS native code. When receive a VOIP push notification in background/killed state, we must immediately display an incoming CallKit screen. Otherwise, the app will be banned with an error. To do so, the following changes in `AppDelegate.mm` should be done: ```javascript #import "RNNotifications.h" #import "RNEventEmitter.h" #import "RNCallKeep.h" ... - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [RNNotifications startMonitorNotifications]; [RNNotifications startMonitorPushKitNotifications]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handlePushKitNotificationReceived:) name:RNPushKitNotificationReceived object:nil]; // cleanup [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"voipIncomingCallSessions"]; return YES; } ... - (void)handlePushKitNotificationReceived:(NSNotification *)notification { UIApplicationState state = [[UIApplication sharedApplication] applicationState]; if (state == UIApplicationStateBackground || state == UIApplicationStateInactive) { // save call info to user defaults NSMutableDictionary *callsInfo = [[[NSUserDefaults standardUserDefaults] objectForKey:@"voipIncomingCallSessions"] mutableCopy]; if (callsInfo == nil) { callsInfo = [NSMutableDictionary dictionary]; } [callsInfo setObject:@{ @"initiatorId": notification.userInfo[@"initiatorId"], @"opponentsIds": notification.userInfo[@"opponentsIds"], @"handle": notification.userInfo[@"handle"], @"callType": notification.userInfo[@"callType"] } forKey:notification.userInfo[@"uuid"]]; [[NSUserDefaults standardUserDefaults] setObject:callsInfo forKey:@"voipIncomingCallSessions"]; // show CallKit incoming call screen [RNCallKeep reportNewIncomingCall: notification.userInfo[@"uuid"] handle: notification.userInfo[@"handle"] handleType: @"generic" hasVideo: [notification.userInfo[@"callType"] isEqual: @"video"] localizedCallerName: notification.userInfo[@"handle"] supportsHolding: YES supportsDTMF: YES supportsGrouping: YES supportsUngrouping: YES fromPushKit: YES payload: notification.userInfo withCompletionHandler: nil]; } else { // when an app is in foreground -> will show the in-app UI for incoming call } } ``` > All the above code snippets can be found in a ready [RNVideoChat code sample](https://github.com/ConnectyCube/connectycube-reactnative-samples/tree/master/RNVideoChat) with CallKit integrated.
# Video Conferencing
> Discover the simplicity of integrating conference video calling into your ReactNative 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) ### 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)). ## Connect WebRTC lib [Section titled “Connect WebRTC lib”](#connect-webrtc-lib) Since “react-native-connectycube” version 3.34.0, the “react-native-webrtc” has been replaced from dependencies to peerDependencies to support autolinking. Install the “react-native-webrtc” and follow the [Getting started](https://github.com/react-native-webrtc/react-native-webrtc?tab=readme-ov-file#getting-started). Configure your [iOS](https://github.com/react-native-webrtc/react-native-webrtc/blob/master/Documentation/iOSInstallation.md) and [Android](https://github.com/react-native-webrtc/react-native-webrtc/blob/master/Documentation/AndroidInstallation.md) projects. Until “react-native-connectycube” version 3.34.0, the “react-native-webrtc” is included to “react-native-connectycube”. Follow the [Android Manual Linking](https://github.com/react-native-webrtc/react-native-webrtc/blob/1.98.0/Documentation/AndroidInstallation.md) and [iOS Manual Linking](https://github.com/react-native-webrtc/react-native-webrtc/blob/1.98.0/Documentation/iOSInstallation.md) guides. ## 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: ```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: ## Attach local media stream [Section titled “Attach local media stream”](#attach-local-media-stream) Then you should attach your local media stream to a video element: ```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(roomId, userId, userDisplayName) .then(() => {}) .catch((error) => {}); ``` To check current joined video room use the following property: ```javascript const roomId = 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) => {}; ``` ## 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 cameras [Section titled “Switch video cameras”](#switch-video-cameras) ```javascript localStream.getVideoTracks().forEach((track) => track._switchCamera()); ``` ## Switch audio output [Section titled “Switch audio output”](#switch-audio-output) 1. connect and install lib 2. Use `InCallManager` class to switch audio output: ```javascript import InCallManager from 'react-native-incall-manager'; ... let isSpeakerOn = true; // false InCallManager.setSpeakerphoneOn(isSpeakerOn); ``` Also, pay attention to [InCall Manager lib](https://github.com/react-native-webrtc/react-native-incall-manager) if you need to use one of the options below: * Manage devices events like wired-headset plugged-in state, proximity sensors and expose functionalities to javascript. * Automatically route audio to proper devices based on events and platform API. * Toggle speaker or microphone on/off, toggle flashlight on/off * Play ringtone/ringback/dtmftone ## Screen Sharing [Section titled “Screen Sharing”](#screen-sharing) Request a desktop stream by calling `getDisplayMedia`: ```javascript session .getDisplayMedia() .then((localDesktopStream) => {}) .catch((error) => {}); ``` In React Native, the [getDisplayMedia](https://github.com/react-native-webrtc/react-native-webrtc/blob/fe5ce9798cae5eb53fa028309b0217d7c22f255d/src/getDisplayMedia.ts#L9) method does not accept constrains as a parameter due to [react-native-webrtc](https://github.com/react-native-webrtc/react-native-webrtc) limitations. 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. ### Screen Sharing on Android [Section titled “Screen Sharing on Android”](#screen-sharing-on-android) Follow this guide to configure [Screen Sharing on Android](https://github.com/react-native-webrtc/react-native-webrtc/blob/master/Documentation/AndroidInstallation.md#screen-sharing) and this to support [Screen Capture on Android 10+](https://github.com/react-native-webrtc/react-native-webrtc/blob/master/Documentation/AndroidInstallation.md#screen-capture-support---android-10). Call the `getDisplayMedia` after starting a ForegroundService: ```javascript import { Platform } from 'react-native'; import notifee, { AndroidImportance } from '@notifee/react-native'; /* ... */ const ScreenSharingComponent() { const mediaParams = { audio: true, video: true, }; const startShareScreen = async () => { if (Platform.OS === 'android') { const channelId = await notifee.createChannel({ id: 'screen_capture', name: 'Screen Capture', lights: false, vibration: false, importance: AndroidImportance.DEFAULT, }); await notifee.displayNotification({ title: 'Screen Capture', body: 'Capturing...', android: { channelId, asForegroundService: true, }, }); } // Capturing a screen const stream = await session.getDisplayMedia(); }; const stopShareScreen = async () => { if (Platform.OS === 'android') { await notifee.stopForegroundService(); } //Get back media stream from camera or call `session.stop(extension)` const stream = await session.getUserMedia(mediaParams); }; return <>{/* ... */}> } export default ScreenSharingComponent; ``` ### Screen Sharing on iOS [Section titled “Screen Sharing on iOS”](#screen-sharing-on-ios) Preform the [Screen Sharing integration for iOS](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-ios-sdk/#screen-sharing-integration) guide from Jitsi. Use `` component from ‘react-native-webrtc’ to prepare: ```javascript import React from 'react'; import { findNodeHandle, Platform, NativeModules } from 'react-native'; import { ScreenCapturePickerView } from 'react-native-webrtc'; /* ... */ const ScreenSharingComponent() { const screenCaptureView = React.useRef(null); const mediaParams = { audio: true, video: true, }; const startShareScreen = async () => { if (Platform.OS === 'ios') { const reactTag = findNodeHandle(screenCaptureView.current); await NativeModules.ScreenCapturePickerViewManager.show(reactTag); } // Capturing a screen const stream = await session.getDisplayMedia(); }; const stopShareScreen = async () => { // Get back media stream from camera or call `session.stop(extension)` const stream = await session.getUserMedia(mediaParams); }; return <> {/* ... */} {Platform.OS === 'ios' && ( )} > } export default ScreenSharingComponent; ``` ## 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 call in background [Section titled “Continue call in background”](#continue-call-in-background) If you are developing dedicated apps for iOS and Android - it’s required to apply additional configuration for the app to keeping the call alive when it goes into the background. \*\*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:  \*\*Android: \*\* For Android, we also recommend to implement the same camera switch flow when go to background and then return to foreground. To keep the call while in background, we recomment to use the **react-native-background-actions** library. The flow is the following: * when start a call or go background -> display notification * when stop call or return to foreground -> hide notification ```javascript // Background Activity import BackgroundService from "react-native-background-actions"; startBackgroundMode = () => { this.stopBackgroundMode(); const options = { taskName: "AppName", taskTitle: "You have an active call", taskDesc: "Press to return", taskIcon: { name: "ic_notification", type: "mipmap", }, linkingURI: "appName://foreground", }; BackgroundService.start(async (params) => { await new Promise(async () => {}); }, options); }; stopBackgroundMode = (force = false) => { if (BackgroundService.isRunning() || force) { return BackgroundService.stop(); } }; ```
# Address Book
> Effortlessly upload, sync, and access ConnectyCube users from your phone contacts in your ReactNative 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](/reactnative/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: 
# Authentication and Users
> Simplify user authentication in your ReactNative app with our definitive API guide. Fortify your app's defenses and protect user data effectively.
Every user has to authenticate with ConnectyCube before using any ConnectyCube functionality. When someone connects with an application using ConnectyCube, the application will need to obtain a session token which provides temporary, secure access to ConnectyCube APIs. A session token is an opaque string that identifies a user and an application. ## Create session token [Section titled “Create session token”](#create-session-token) As a starting point, the user’s session token needs to be created allowing user any further actions within the app. Pass login/email and password to identify a user: ```javascript const userCredentials = { login: "cubeuser", password: "awesomepwd" }; 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:** 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). ### Authentication via phone number [Section titled “Authentication via phone number”](#authentication-via-phone-number) Sign In with phone number is supported with ([Firebase integration](https://firebase.google.com/docs/auth/web/phone-auth)). You need to create Firebase `project_id` and obtain Firebase `access_token` after SMS code verification, then pass these parameters to `login` method: ```javascript const userCredentials = { provider: "firebase_phone", "firebase_phone[project_id]": "...", "firebase_phone[access_token]": "...", }; ConnectyCube.createSession(userCredentials) .then((user) => {}) .catch((error) => {}); ``` > **Note** > > In order to login via phone number you need to create a session token first. ### Authentication via Firebase email [Section titled “Authentication via Firebase email”](#authentication-via-firebase-email) Sign In with email is supported with ([Firebase integration](https://firebase.google.com/docs/auth/web/password-auth)). You need to create Firebase `project_id` and obtain Firebase `access_token` after email/password verification, then pass these parameters to `login` method: ```javascript const userCredentials = { provider: 'firebase_email', firebase_email: { project_id: 'XXXXXXXXXXX', access_token: 'XXXXXXXXXXXYYYYYY' } }; ConnectyCube.createSession(userCredentials) .then((user) => {}) .catch((error) => {}); ``` > **Note** > > In order to login via email you need to create a session token first. ### Authentication via external identity provider [Section titled “Authentication via external identity provider”](#authentication-via-external-identity-provider) **Custom Identity Provider (CIdP)** feature is necessary if you have your own user database and want to authenticate users in ConnectyCube against it. It works the same way as Facebook/Twitter SSO. With Custom Identity Provider feature you can continue use your user database instead of storing/copying user data to ConnectyCube database. #### CIdP high level integration flow [Section titled “CIdP high level integration flow”](#cidp-high-level-integration-flow) To get started with **CIdP** integration, check the [Custom Identity Provider guide](/guides/custom-identity-provider) which describes high level integration flow. #### How to login via CIdP [Section titled “How to login via CIdP”](#how-to-login-via-cidp) Once you done with the setup mapping in ConnectyCube Dashboard, it’s time to verify the integration. To perform CIdP login, the same ConnectyCube [User Login API](/reactnative/authentication-and-users/#upgrade-session-token-user-login) is used. You just use existing login request params to pass your external user token: ```javascript const userCredentials = { login: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzNDU2Nzg5LCJuYW1lIjoiSm9zZXBoIn0.OpOSSw7e485LOP5PrzScxHb7SR6sAOMRckfFwi4rp7o" }; ConnectyCube.createSession(userCredentials) .then((user) => {}) .catch((error) => {}); ``` Once the login is successful, ConnectyCube will create an underalying User entity, so then you can use ConnectyCube APIs in a same way as you do with a normal login. With CIdP we do not have/store any user password in ConnectyCube User entity. Following further integration, you may need to connect to Chat. In a case of CIdP login, you do not have a user password. In such cases you should use ConnectyCube session token as a password for chat connection. [Follow the Connect to Chat with CIdP guide](/reactnative/messaging/#connect-to-chat-using-custom-authentication-providers). ### Create guest session [Section titled “Create guest session”](#create-guest-session) To create a session with guest user use the following code: ```javascript const guestUserCredentials = { guest: '1', full_name: 'Awesome Smith' }; ConnectyCube.createSession(guestUserCredentials) .then((session) => {}) .catch((error) => {}); ``` ## Session expiration [Section titled “Session expiration”](#session-expiration) Expiration time for session token is 2 hours after last request to API. If you perform query with expired token, you will receive the error **Required session does not exist**. In this case you need to recreate a session token. There is a special callback function to handle this case: ```javascript const CONFIG = { on: { sessionExpired: (handleResponse, retry) => { // call handleResponse() if you do not want to process a session expiration, // so an error will be returned to origin request // handleResponse(); ConnectyCube.createSession() .then(retry) .catch((error) => {}); }, }, }; ConnectyCube.init(CREDENTIALS, CONFIG); ``` ## Destroy session token [Section titled “Destroy session token”](#destroy-session-token) To destroy a session use the following code: ```javascript ConnectyCube.destroySession().catch((error) => {}); ``` ## User signup [Section titled “User signup”](#user-signup) Only login (or email) + password are required for user to sign up. Other parameters are optional: ```javascript const userProfile = { login: "marvin18", password: "supersecurepwd", email: "awesomeman@gmail.com", full_name: "Marvin Simon", phone: "47802323143", website: "https://dozensofdreams.com", tag_list: ["iphone", "apple"], custom_data: JSON.stringify({ middle_name: "Bartoleo" }), }; ConnectyCube.users .signup(userProfile) .then((user) => {}) .catch((error) => {}); ``` ## User profile update [Section titled “User profile update”](#user-profile-update) ```javascript const updatedUserProfile = { login: "marvin18sim", full_name: "Mar Sim", }; ConnectyCube.users .update(updatedUserProfile) .then((user) => {}) .catch((error) => {}); ``` If you want to change your password, you need to provide 2 parameters: `password` and `old_password`. Updated `user` entity will be returned. ## User avatar [Section titled “User avatar”](#user-avatar) You can set a user’s avatar. You just need to upload it to the ConnectyCube cloud storage and then connect to user. ```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 updateUser = (uploadedFile) => { const updatedUserProfile = { avatar: uploadedFile.uid }; return ConnectyCube.users.update(updatedUserProfile); }; ConnectyCube.storage .createAndUpload(fileParams) .then(updateUser) .then((updatedUser) => {}) .catch((error) => {}); ``` Now, other users can get you avatar: ```javascript const avatarUID = updatedUser.avatar; const avatarURL = ConnectyCube.storage.privateUrl(avatarUID); const avatarHTML = ""; ``` ## Password reset [Section titled “Password reset”](#password-reset) It’s possible to reset a password via email: ```javascript ConnectyCube.users .resetPassword("awesomeman@gmail.com") .then((result) => {}) .catch((error) => {}); ``` If provided email is valid - an email with password reset instruction will be sent to it. ## Retrieve users V2 [Section titled “Retrieve users V2”](#retrieve-users-v2) ### Examples [Section titled “Examples”](#examples) Retrieve users by ID ```javascript const searchParams = { limit: 10, offset: 50, id: { in: [51941, 51946] } } ConnectyCube.users.getV2(searchParams) .then((result) => {}) .catch((error) => {}); ``` Retrieve users by login ```javascript const searchParams = { login: 'adminFirstUser' } ConnectyCube.users.getV2(searchParams) .then((result) => {}) .catch((error) => {}); ``` Retrieve users by last\_request\_at ```javascript const date = new Date(2017, 10, 10) const searchParams = { last_request_at: { gt: date } } ConnectyCube.users.getV2(searchParams) .then((result) => {}) .catch((error) => {}); ``` More information (fields, operators, request rules) available [here](/server/users#retrieve-users-v2) ## Retrieve users V1 (**Deprecated**) [Section titled “Retrieve users V1 (Deprecated)”](#retrieve-users-v1-deprecated) ### Retrieve users by ID (**Deprecated**) [Section titled “Retrieve users by ID (Deprecated)”](#retrieve-users-by-id-deprecated) ```javascript const params = { page: 1, per_page: 5, filter: { field: "id", param: "in", value: [51941, 51946], }, }; ConnectyCube.users .get(params) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve user by login (**Deprecated**) [Section titled “Retrieve user by login (Deprecated)”](#retrieve-user-by-login-deprecated) ```javascript const searchParams = { login: "marvin18" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve user by email (**Deprecated**) [Section titled “Retrieve user by email (Deprecated)”](#retrieve-user-by-email-deprecated) ```javascript const searchParams = { email: "marvin18@example.com" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve users by full name (**Deprecated**) [Section titled “Retrieve users by full name (Deprecated)”](#retrieve-users-by-full-name-deprecated) ```javascript const searchParams = { full_name: "Marvin Samuel" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve user by phone number (**Deprecated**) [Section titled “Retrieve user by phone number (Deprecated)”](#retrieve-user-by-phone-number-deprecated) ```javascript const searchParams = { phone: "44678162873" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve user by external ID (**Deprecated**) [Section titled “Retrieve user by external ID (Deprecated)”](#retrieve-user-by-external-id-deprecated) ```javascript const searchParams = { external_user_id: "675373912" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve users by tags (**Deprecated**) [Section titled “Retrieve users by tags (Deprecated)”](#retrieve-users-by-tags-deprecated) ```javascript const searchParams = { tags: ["apple"] }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ## Delete user [Section titled “Delete user”](#delete-user) A user can delete himself from the platform: ```javascript ConnectyCube.users .delete() .then((result) => {}) .catch((error) => {}); ```
# Custom Data
> Maximize your ReactNative app's potential with customizable data cloud storage solutions. Tailor data structures to match your application's unique requirements.
Custom Data, also known as cloud tables or custom objects, provide users with the flexibility to define and manage data in a way that is specific to their application’s requirements. Here are some common reasons why you might need use custom data in your app: * Custom Data allows you to define data structures that align precisely with your application’s needs. This is particularly useful when dealing with complex or unique data types that don’t fit well into standard ConnectyCube models. * In certain applications, there may be entities or objects that are unique to that particular use case. Custom Data enable the representation of these entities in the database, ensuring that the data storage is optimized for the application’s logic. * Custom Data allows you to extend the functionality of the app by introducing new types of data that go beyond what the ConnectyCube platform’s standard models support. Custom Data empower you to introduce these extensions and additional features. * In situations where data needs to be migrated from an existing system or transformed in a specific way, Custom Data offer the flexibility to accommodate these requirements. * Your application needs to integrate with external systems or APIs, Custom Data can be designed to align seamlessly with the data structures of your external systems. ## Get started with SDK [Section titled “Get started with SDK”](#get-started-with-sdk) Follow the [Getting Started guide](/js/) on how to connect ConnectyCube SDK and start building your first app. ## Preparations [Section titled “Preparations”](#preparations) In order to start using Custom Data you need first create the data scheme in the ConnectyCube Admin panel. For it navigate to **`Home -> Your App -> Custom -> List`** then click on **`ADD`** and from the dropdown menu select **`ADD NEW CLASS`**. In the opened dialog, enter your model name and add the necessary fields. The ConnectyCube Custom Data models’ fields support various data types or arrays (except `Location`). These include: * Integer (e.g. `8222456`); * Float (e.g. `1.25`); * Boolean (`true` or `false`); * String (e.g. `"some text"`); * Location (the array what consist of **two** `numbers`s); After adding all the required fields, click the **`CREATE CLASS`** button to save your scheme.  Newly created class is now available and contains the following data: * **\_id** - record identifier generated by system automatically * **user\_id** - identifier of user who created a record * **\_parent\_id** - by default is null * **field\_1** - field defined in a scheme * **field\_2** - field defined in a scheme * … * **field\_N** - field defined in a scheme * **created\_at** - date and time when a record is created  After that you can perform all **CRUD** operations with your Custom Data. > **Note**: The **Class name** field will be represented as the DB table name and will be used for identification of your requests during the work with Custom Data. ## Permissions [Section titled “Permissions”](#permissions) Access control list (ACL) is a list of permissions attached to some object. An ACL specifies which users have an access to objects, as well as what operations are allowed with such objects. Each entry in a typical ACL specifies a subject and an operation. ACL models may be applied to collections of objects as well as to individual entities within the system’s hierarchy. Adding the Access Control list is only available within the Custom Data module. ### Permissions scheme [Section titled “Permissions scheme”](#permissions-scheme) ConnectyCube permission scheme contains five permissions levels: * **Open** (open) - any user within the application can access the record(s) in the class and perform actions with the record * **Owner** (owner) - only the Owner (the user who created a record) is authorized to perform actions with the record * **Not allowed** (not\_allowed) - no one (except the Account Administrator) can proceed with a chosen action * **Open for groups** (open\_for\_groups) - users with the specified tag(s) will be included in the group that is authorized to perform actions with a record. Multiple groups can be specified (number of groups is not limited). * **Open for users ids** (open\_for\_users\_ids) - only users with listed IDs can perform actions with a record. Actions for work with an entity: * **Create** - create a new record * **Read** - retrieve information about a record and view it in the read-only mode * **Update** - update any parameter of the chosen record that can be updated by user * **Delete** - delete a record To set a permission schema for the Class, go to ConnectyCube dashboard and find a required class within Custom Data module Click on **`EDIT PERMISSION`** button to open permissions schema to edit. Each listed action has a separate permission level to select. The exception is a ‘Create’ action that isn’t available for ‘Owner’ permission level.  ### Permission levels [Section titled “Permission levels”](#permission-levels) Two access levels are available in the ConnectyCube: access to Class and access to Record. Only one permission schema can be applied for the record. Using the Class permission schema means that the Record permission schema won’t be affected on a reсord. **Class entity** **Class** is an entity that contains records. Class can be created via ConnectyCube dashboard only within Custom data module. Operations with Class entity are not allowed in API. All actions (Create, Read, Update and Delete) that are available for the ‘Class’ entity are also applicable for all records within a class. Default Class permission schema is using during the creation of a class: * **Create:** Open * **Read:** Open * **Update:** Owner * **Delete:** Owner To enable applying Class permissions for any of the action types, ‘Use Class permissions’ check box should be ticked. It means that the record permission schema (if existing) won’t be affected on a record. **Record entity** **Record** is an entity within the Class in the Custom Data module that can be created in ConnectyCube dashboard and via API. Each record within a Class has its own permission level. Unlike Class entity, ‘Not allowed’ permission level isn’t available for a record as well as only three actions are available to work with a record - read, update and delete. Default values for Record permission scheme: * Read: Open * Update: Owner * Delete: Owner To set a separate permission scheme for a record, open a record to edit and click on **`SET PERMISSION ON RECORD`** button:  Define the permission level for each of available actions. ## Create a new record [Section titled “Create a new record”](#create-a-new-record) Create a new record with the defined parameters in the class. Fields that weren’t defined in the request but are available in the scheme (class) will have null values. ```javascript const className = 'call_history_item'; const record = { 'call_name': 'Group call', 'call_participants': [2325293, 563541, 563543], 'call_start_time': 1701789791673, 'call_end_time': 0, 'call_duration': 0, 'call_state': 'accepted', 'is_group_call': true, 'call_id': 'f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454', 'user_id': 2325293, 'caller_location': [50.004444, 36.234380] } try { const result = await ConnectyCube.data.create(className, record); console.log(result) } catch (error) { console.error(error) } ``` ### Create a record with permissions [Section titled “Create a record with permissions”](#create-a-record-with-permissions) To create a new record with permissions, add the `permissions` parameter to a record. In this case, the request will look like this: ```javascript const className = 'call_history_item'; const record = { call_name: 'Group call', call_participants: [2325293, 563541, 563543], call_start_time: 1701789791673, call_end_time: 0, call_duration: 0, call_state: 'accepted', is_group_call: true, call_id: 'f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454', user_id: 2325293, caller_location: [50.004444, 36.234380], permissions: { read: { access: 'owner' }, update: { access: 'open_for_users_ids', ids: [563541, 563543] }, delete: { access: 'open_for_groups', groups: ['group87', 'group108'] } } } try { const result = await ConnectyCube.data.create(className, record); console.log(result) } catch (error) { console.error(error) } ``` ## Create multi records [Section titled “Create multi records”](#create-multi-records) Create several new records in the class. Fields that weren’t defined in the request but available in the scheme would have null values. ```javascript const className = 'call_history_item'; const record_1 = { 'call_name': 'Group call 1', 'call_participants': [2325293, 563541, 563543], 'call_start_time': 1701789791673, 'call_end_time': 0, 'call_duration': 0, 'call_state': 'accepted', 'is_group_call': true, 'call_id': 'f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454', 'user_id': 2325293, 'caller_location': [50.004444, 36.234380] } const record_2 = { 'call_name': 'Group call 2', 'call_participants': [2325293, 563541, 563543], 'call_start_time': 1701789832955, 'call_end_time': 0, 'call_duration': 0, 'call_state': 'accepted', 'is_group_call': true, 'call_id': 'a2a7bc3f-2eeb-3d72-11b0-566d3c4c2748', 'user_id': 2325293, 'caller_location': [50.004444, 36.234380] } const records = [record_1, record_2] try { const result = await ConnectyCube.data.create(className, records); console.log(result.items) } catch (error) { console.error(error) } ``` ## Retrieve record by ID [Section titled “Retrieve record by ID”](#retrieve-record-by-id) Retrieve the record by specifying its identifier. ```javascript const className = 'call_history_item'; const id = '656f407e29d6c5002fce89dc'; try { const result = await ConnectyCube.data.list(className, id); console.log(result.items) } catch (error) { console.error(error) } ``` ## Retrieve records by IDs [Section titled “Retrieve records by IDs”](#retrieve-records-by-ids) Retrieve records by specifying their identifiers. ```javascript const className = 'call_history_item'; const ids = ['656f407e29d6c5002fce89dc', '5f985984ca8bf43530e81233']; // or `const ids = '656f407e29d6c5002fce89dc,5f985984ca8bf43530e81233'`; try { const result = await ConnectyCube.data.list(className, ids); console.log(result.items) } catch (error) { console.error(error) } ``` ## Retrieve records within a class [Section titled “Retrieve records within a class”](#retrieve-records-within-a-class) Search records within the particular class. > **Note:** If you are sorting records by time, use the `_id` field. It is indexed and it will be much faster than sorting by `created_at` field. The list of additional parameter for sorting, filtering, aggregation of the search response is provided by link ```javascript const className = 'call_history_item'; const filters = { call_start_time: { gt: 1701789791673 } }; try { const result = await ConnectyCube.data.list(className, filter); console.log(result.items) } catch (error) { console.error(error) } ``` ## Retrieve the record’s permissions [Section titled “Retrieve the record’s permissions”](#retrieve-the-records-permissions) > **Note:** record permissions are checked during request processing. Only the owner has an ability to view a record’s permissions. ```javascript const className = 'call_history_item'; const id = '656f407e29d6c5002fce89dc'; try { const result = await ConnectyCube.data.readPermissions(className, id); console.log(result.permissions) console.log(result.record_is) } catch (error) { console.error(error) } ``` ## Update record by ID [Section titled “Update record by ID”](#update-record-by-id) Update record data by specifying its ID. ```javascript const className = 'call_history_item'; const params = { id: '656f407e29d6c5002fce89dc', call_end_time: 1701945033120, }; try { const result = await ConnectyCube.data.update(className, params); console.log(result) } catch (error) { console.error(error) } ``` ## Update records by criteria [Section titled “Update records by criteria”](#update-records-by-criteria) Update records found by the specified search criteria with a new parameter(s). The structure of the parameter `search_criteria` and the list of available operators provided by link ```javascript const className = 'call_history_item'; const params = { search_criteria: { user_id: 1234567 }, call_name: 'Deleted user' }; try { const result = await ConnectyCube.data.update(className, params); console.log(result.items) } catch (error) { console.error(error) } ``` ## Update multi records [Section titled “Update multi records”](#update-multi-records) Update several records within a class by specifying new values. ```javascript const className = 'call_history_item'; const params = [{ id: '656f407e29d6c5002fce89dc', call_end_time: 1701945033120, }, { id: '5f998d3bca8bf4140543f79a', call_end_time: 1701945033120, }]; try { const result = await ConnectyCube.data.update(className, params); console.log(result.items) } catch (error) { console.error(error) } ``` ## Delete record by ID [Section titled “Delete record by ID”](#delete-record-by-id) Delete a record from a class by record identifier. ```javascript const className = 'call_history_item'; const id = '5f998d3bca8bf4140543f79a'; try { await ConnectyCube.data.delete(className, id); } catch (error) { console.error(error) } ``` ## Delete several records by their IDs [Section titled “Delete several records by their IDs”](#delete-several-records-by-their-ids) Delete several records from a class by specifying their identifiers. If one or more records can not be deleted, an appropriate error is returned for that record(s). ```javascript const className = 'call_history_item'; const ids = ['656f407e29d6c5002fce89dc', '5f998d3bca8bf4140543f79a']; try { const result = await ConnectyCube.data.delete(className, id); console.log(result) } catch (error) { console.error(error) } ``` ## Delete records by criteria [Section titled “Delete records by criteria”](#delete-records-by-criteria) Delete records from the class by specifying a criteria to find records to delete. The search query format is provided by link ```javascript const className = 'call_history_item'; const params = { call_id: { in: ['f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454'] } }; try { const result = await ConnectyCube.data.delete(className, params); console.log(result) } catch (error) { console.error(error) } ``` ## Relations [Section titled “Relations”](#relations) Objects (records) in different classes can be linked with a `parentId` field. If the record from the **Class 1** is pointed with a record from **Class 2** via its `parentId`, the `parentId` field will contain an ID of a record from the **Class 2**. If a record from the **Class 2** is deleted, all its children (records of the **Class 1** with `parentId` field set to the **Class 2** record ID) will be automatically deleted as well. The linked children can be retrieved with `_parent_id={id_of_parent_class_record}` parameter.
# Push Notifications
> Elevate ReactNative app's performance with push notifications API guide. Keep users engaged with real-time updates, ensuring seamless interaction on the go.
Push Notifications provide a way to deliver some information to users while they are not using your app actively. The following use cases can be covered additionally with push notifications: * send a chat message when a recipient is offline (a push notification will be initiated automatically in this case) * make a video call with offline opponents (need to send a push notification manually) > Ready push notifications code sample DEMO is available as a part of [React Native Chat code sample](https://github.com/ConnectyCube/connectycube-reactnative-samples/tree/master/RNChat) (src/services/push-notification.js) > Ready push notifications + VOIP push notifications + CallKit code sample DEMO is available as a part of [React Native Video Chat code sample](https://github.com/ConnectyCube/connectycube-reactnative-samples/tree/master/RNVideoChat) ## Configuration [Section titled “Configuration”](#configuration) In order to start work with push notifications you need to configure it. First of all we need to install [react-native-notifications](https://github.com/wix/react-native-notifications) lib. Just follow the ‘Installation’ guide in the lib’s README and then do the rest manual setup steps. Then follow the platform specific steps. ### iOS [Section titled “iOS”](#ios) 1. First of all you need to generate Apple push certificate (\*.p12 file) and upload it to ConnectyCube dashboard. Here is a guide on how to create a certificate 2. Upload Apple push certificate (\*.p12 file) to ConnectyCube dashboard: * Open your ConnectyCube Dashboard at [admin.connectycube.com](https://admin.connectycube.com) * Go to **Push notifications** module, **Credentials** page * Upload the newly created APNS certificate on **Apple Push Notification Service (APNS)** form.  3. Lastly, open Xcode project of your Flutter app and enable Push Notifications capabilities. Open Xcode, choose your project file, Signing & Capabilities tab and then add a Push Notifications capability. Also - tick a ‘Remote notifications’ checkbox in Background Modes section.  ### Android [Section titled “Android”](#android) #### Configure Firebase project and Service account key (recommended) [Section titled “Configure Firebase project and Service account key (recommended)”](#configure-firebase-project-and-service-account-key-recommended) In order to start working with push notifications functionality you need to configure it. 1. Create and configure your [Firebase project](https://console.firebase.google.com) and obtain the **Service account key**. If you have any difficulties with Firebase project registration, [follow our guide](/android/firebase-setup-guide). To find your **FCM service account key** go to your **Firebase console > Cloud Messaging > Manage Service Accounts** section:  2. Select and configure **Manage Keys** option:  3. Select **ADD KEY**, **Create new key**:  4. Select **Key type** (json recommended) and create:  5. Save it locally:  6. Browse your saved **FCM Service account key** in your **Dashboard > Your App > Push Notifications > Credentials**, select the environment for which you are adding the key. Use the same key for development and production zones.  7. Copy **Sender ID** value from your Firebase console **Cloud Messaging** section. It will be required on a following step, when init **react-native-push-notification** lib:  8. In order to use push notifications on Android, you need to create `google-services.json` file and copy it into project’s `android/app` folder. Also, you need to update the `applicationId` in `android/app/build.gradle` to the one which is specified in `google-services.json`, so they must match. If you have no existing API project yet, the easiest way to go about in creating one is using this [step-by-step installation process](https://firebase.google.com/docs/android/setup) #### Configure Firebase project and Server key (DEPRECATED) [Section titled “Configure Firebase project and Server key (DEPRECATED)”](#configure-firebase-project-and-server-key-deprecated) 1. Create and configure your [Firebase project](https://console.firebase.google.com) and obtain the **Server key**. If you have any difficulties with Firebase project registration, [follow our guide](/android/firebase-setup-guide). To find your **FCM server key** go to your **Firebase console > Cloud Messaging** section:  2. Copy your **FCM server key** to your **Dashboard > Your App > Push Notifications > Credentials**, select the environment for which you are adding the key and hit **Save key**. Use the same key for development and production zones.  3. Copy **Sender ID** value from your Firebase console **Cloud Messaging** section. It will be required on a following step, when init **react-native-push-notification** lib:  4. In order to use push notifications on Android, you need to create `google-services.json` file and copy it into project’s `android/app` folder. Also, you need to update the `applicationId` in `android/app/build.gradle` to the one which is specified in `google-services.json`, so they must match. If you have no existing API project yet, the easiest way to go about in creating one is using this [step-by-step installation process](https://firebase.google.com/docs/android/setup) ## react-native-notifications lib installation [Section titled “react-native-notifications lib installation”](#react-native-notifications-lib-installation) * [react-native-notifications iOS Installation](https://wix.github.io/react-native-notifications/docs/installation-ios) * [react-native-notifications Android Installation](https://wix.github.io/react-native-notifications/docs/installation-android) ## Init react-native-notifications lib [Section titled “Init react-native-notifications lib”](#init-react-native-notifications-lib) The following installation steps are a TL;DR of [react-native-notifications Getting Started](https://wix.github.io/react-native-notifications/docs/getting-started). You can entierly follow it OR use our guide below. ```javascript import { Notifications } from 'react-native-notifications'; ... init() { if (Platform.OS === 'ios') { Notifications.ios.checkPermissions().then((currentPermissions) => { console.log('Badges enabled: ' + !!currentPermissions.badge); console.log('Sounds enabled: ' + !!currentPermissions.sound); console.log('Alerts enabled: ' + !!currentPermissions.alert); console.log('Car Play enabled: ' + !!currentPermissions.carPlay); console.log('Critical Alerts enabled: ' + !!currentPermissions.criticalAlert); console.log('Provisional enabled: ' + !!currentPermissions.provisional); console.log('Provides App Notification Settings enabled: ' + !!currentPermissions.providesAppNotificationSettings); console.log('Announcement enabled: ' + !!currentPermissions.announcement); }); } Notifications.getInitialNotification().then((notification) => { console.log("Initial notification was:", (notification ? notification.payload : 'N/A')); // this.displayNotification({message: "hello"}); }) .catch((err) => console.error("getInitialNotifiation() failed", err)); Notifications.events().registerRemoteNotificationsRegistered((event) => { // TODO: Send the token to my server so it could send back push notifications... console.log("[PushNotificationService] Device Token Received", event.deviceToken); this.subscribeToPushNotification(event.deviceToken) }); Notifications.events().registerRemoteNotificationsRegistrationFailed((event) => { console.error("[PushNotificationService] Failed to get Device Token", event); }); Notifications.events().registerNotificationReceivedForeground((notification, completion) => { console.log(`[PushNotificationService] Notification received in foreground`, notification.payload, notification?.payload?.message); if (Platform.OS === 'android') { PushNotificationService.displayNotification(notification.payload); } completion({alert: false, sound: false, badge: false}); }); Notifications.events().registerNotificationReceivedBackground((notification, completion) => { console.log("[PushNotificationService] Notification Received - Background", notification.payload, notification?.payload?.message); if (Platform.OS === 'android') { PushNotificationService.displayNotification(notification.payload); } // Calling completion on iOS with `alert: true` will present the native iOS inApp notification. completion({alert: true, sound: true, badge: false}); }); Notifications.events().registerNotificationOpened(async (notification, completion) => { console.log(`[PushNotificationService] Notification opened`, notification.payload); await this.onNotificationOpened(notification.payload) completion(); }); Notifications.registerRemoteNotifications(); } ``` ## Subscribe to push notifications [Section titled “Subscribe to push notifications”](#subscribe-to-push-notifications) In order to start receiving push notifications you need to subscribe your current device as follows: ```javascript import ConnectyCube from "react-native-connectycube"; ... Notifications.events().registerRemoteNotificationsRegistered((event) => { // TODO: Send the token to my server so it could send back push notifications... console.log("[PushNotificationService] Device Token Received", event.deviceToken); this.subscribeToPushNotification(event.deviceToken) }); ... subscribeToPushNotification(deviceToken) { const DeviceInfo = require('react-native-device-info').default const params = { // for iOS VoIP it should be 'apns_voip' notification_channel: Platform.OS === 'ios' ? 'apns' : 'gcm', device: { platform: Platform.OS, udid: DeviceInfo.getUniqueID() }, push_token: { environment: __DEV__ ? 'development' : 'production', client_identification_sequence: deviceToken, bundle_identifier: "com.your.app.package.id" } } ConnectyCube.pushnotifications.subscriptions.create(params) .then(result => {}) .catch(error => {}); } ``` ## Send push notifications [Section titled “Send push notifications”](#send-push-notifications) You can manually initiate a push notification to user/users on any event in your application. To do so you need to form a push notification parameters (payload) and set the push recipients: ```javascript const payload = JSON.stringify({ message: "Alice is calling you", ios_badge: 1, // ios_voip: 1 }); const pushParameters = { notification_type: "push", user: { ids: [21, 12] }, // recipients. environment: __DEV__ ? "development" : "production", message: ConnectyCube.pushnotifications.base64Encode(payload), }; ConnectyCube.pushnotifications.events .create(pushParameters) .then((result) => {}) .catch((error) => {}); ``` Please refer [Universal Push Notifications standard parameters](/server/push_notifications#universal-push-notifications) section on how to form the payload. ## Receive push notifications [Section titled “Receive push notifications”](#receive-push-notifications) ```javascript Notifications.events().registerNotificationReceivedForeground((notification, completion) => { console.log(`[PushNotificationService] Notification received in foreground`, notification.payload, notification?.payload?.message); if (Platform.OS === 'android') { PushNotificationService.displayNotification(notification.payload); } completion({alert: false, sound: false, badge: false}); }); Notifications.events().registerNotificationReceivedBackground((notification, completion) => { console.log("[PushNotificationService] Notification Received - Background", notification.payload, notification?.payload?.message); if (Platform.OS === 'android') { PushNotificationService.displayNotification(notification.payload); } // Calling completion on iOS with `alert: true` will present the native iOS inApp notification. completion({alert: true, sound: true, badge: false}); }); static displayNotification(payload) { const extra = {dialog_id: payload.dialog_id, isLocal: true} const localNotification = Notifications.postLocalNotification({ body: payload.message, title: "New message", // TODO: to use here chat name/sender name // sound: "chime.aiff", silent: false, category: "SOME_CATEGORY", userInfo: extra, extra, }); } ``` Here you can add an appropriate logic in your app. The things can be one of the following: * If this is a chat message, once clicked on it - we can redirect a user to an appropriate chat by **dialog\_id** data param * Raise a local notification with an alternative info to show for a user ## Receive pushes in killed state (Android) [Section titled “Receive pushes in killed state (Android)”](#receive-pushes-in-killed-state-android) There was an issue on Android with receiving a callback when a push arrived and an app is in killed/dead state. We addressed it here: * we created a patch to push notification lib - [patches/react-native-notifications+4.3.1.patch](https://github.com/ConnectyCube/connectycube-reactnative-samples/blob/master/RNChat/patches/react-native-notifications%2B4.3.1.patch) - which is applied automatically when you do yarn (it’s specified as a part of `package.json` postinstall step - `"postinstall": "patch-package && npx jetify && cd ios && pod install"`) * you also need to add `` into `AndroidManifest.xml` file * and now you can process the notifications in killed/dead state via the following code snippet: ```javascript const { AppRegistry } = require("react-native"); // https://reactnative.dev/docs/headless-js-android // AppRegistry.registerHeadlessTask( "JSNotifyWhenKilledTask", () => { return async (notificationBundle) => { console.log('[JSNotifyWhenKilledTask] notificationBundle', notificationBundle); PushNotificationService.displayNotification(notificationBundle); } }, ); ``` > Ready push notifications code sample DEMO is available as a part of [React Native Chat code sample](https://github.com/ConnectyCube/connectycube-reactnative-samples/tree/master/RNChat) (src/services/push-notification.js) ## Unsubscribe from push notifications [Section titled “Unsubscribe from push notifications”](#unsubscribe-from-push-notifications) In order to unsubscribe and stop receiving push notifications you need to list your current subscriptions and then choose those to be deleted: ```javascript const deleteSubscription = (subscriptions) => { let subscriptionIdToDelete; subscriptions.forEach((sbs) => { if (sbs.subscription.device.platform === Platform.OS && sbs.subscription.device.udid === DeviceInfo.getUniqueID()) { subscriptionIdToDelete = sbs.subscription.id; } }); if (subscriptionIdToDelete) { ConnectyCube.pushnotifications.subscriptions.delete(subscriptionIdToDelete); } }; ConnectyCube.pushnotifications.subscriptions .list() .then(deleteSubscription) .catch((error) => {}); ``` ## VoIP push notifications [Section titled “VoIP push notifications”](#voip-push-notifications) ConnectyCube supports iOS VoIP push notifications via same API described above. The main use case for iOS VoIP push notifications is to show a native calling interface on incoming call when an app is in killed/background state - via CallKit. The common flow of using VoIP push notifications is the following: * for VoIP pushes it requires to generate a separated VoIP device token. With [react-native-notifications](https://github.com/wix/react-native-notifications) lib it can be done the following way: ```javascript if (Platform.OS === 'ios') { Notifications.ios.registerPushKit(); } ``` * then when token is retrieved, you need to subscribe to voip pushes by passing a `notification_channel: apns_voip` channel in a subscription request: ```javascript if (Platform.OS === 'ios') { Notifications.ios.events().registerPushKitRegistered(event => { this.subscribeToVOIPPushNotifications(event.pushKitToken); }); } ... subscribeToVOIPPushNotifications(deviceToken) { const params = { notification_channel: 'apns_voip', device: { platform: Platform.OS, udid: getUniqueId() }, push_token: { environment: __DEV__ ? 'development' : 'production', client_identification_sequence: deviceToken } } ConnectyCube.pushnotifications.subscriptions.create(params) .then(result => { console.log("[PushNotificationsService][subscribeToVOIPPushNotifications] Ok"); }).catch(error => { console.warn("[PushNotificationsService][subscribeToVOIPPushNotifications] Error", error); }); } ``` * then when you want to send a voip push notification, use `ios_voip: 1` parameter in a push payload in a create event request: ```javascript const pushParams = { message: `Incoming call from ${currentUser.full_name}`, ios_voip: 1, handle: currentUser.full_name, initiatorId: callSession.initiatorID, opponentsIds: selectedOpponentsIds.join(","), uuid: callSession.ID, callType: callType === ConnectyCube.videochat.CallType.VIDEO ? "video" : "audio" }; this.sendPushNotification(selectedOpponentsIds, pushParams); ... sendPushNotification(recipientsUsersIds, params) { const payload = JSON.stringify(params); const pushParameters = { notification_type: "push", user: { ids: recipientsUsersIds }, environment: __DEV__ ? "development" : "production", message: ConnectyCube.pushnotifications.base64Encode(payload), }; ConnectyCube.pushnotifications.events.create(pushParameters) .then(result => { console.log("[PushNotificationsService][sendPushNotification] Ok"); }).catch(error => { console.warn("[PushNotificationsService][sendPushNotification] Error", error); }); } ``` ## react-native-firebase lib [Section titled “react-native-firebase lib”](#react-native-firebase-lib) In a case you use a [react-native-firebase](https://rnfirebase.io) lib for push notifications integration - please refer a GitHub issue for any potential drawnbacks ## notifee lib [Section titled “notifee lib”](#notifee-lib) In a case you use a [notifee](https://notifee.app/react-native/docs/ios/remote-notification-support) lib for push notifications integration - please refer a GitHub issue for any potential drawnbacks ## Expo [Section titled “Expo”](#expo) If you use React Native [Expo](https://expo.dev), then it requires to provide an [experienceId](https://docs.expo.dev/push-notifications/sending-notifications-custom/) in a push payload for a push to be delivered successfully. It is passed to the expo app so if the device has more than one Expo app it knows which messages are for which app. Without this Expo can’t handle the data messages. **experienceId** follows this format: **@yourExpoUsername/yourProjectSlug**. For the push notifications initiated via API you can provide it by yourself. For the push notifications initiate automatically for offline users in chat - you can provide this filed at **Admin panel > Chat > Offline notifications**. There is a React Native block with ‘experienceId’ field value.
# Streaming
> Leverage ConnectyCube's streaming feature for dynamic real-time interactions in React Native app. Ideal for interactive sessions, such as teachers broadcasting to multiple students.
ConnectyCube streaming is built on top of [WebRTC](https://webrtc.org/) protocol. The main use case for streaming is with a teacher and many students where normally students join a call as listeners and only a teacher publishes own media stream: **Streaming API** is built on top of [Video Conferencing API](/reactnative/videocalling-conference), hence same API documentation should be followed. The only difference is when join a room - a `session.joinAsListener(...)` API should be used at students side instead of `session.join(...)` API at teacher’s side. This method allows to join a conference room w/o publishing own media stream.
# Whiteboard
> Enable dynamic collaboration in chat with ConnectyCube Whiteboard API. Ideal for remote meetings, teaching environments, sales demos, real-time workflows.
ConnectyCube **Whiteboard API** allows to create whiteboard functionality and associate it with a chat dialog. Chat dialog’s users can collaborate and draw simultaneously on a whiteboard. You can do freehand drawing with a number of tools, add shapes, lines, text and erase. To share boards, you just get an easy link which you can email. Your whiteboard stays safe in the cloud until you’re ready to return to it.  The most popular use cases for using the whiteboard: * Remote meetings * Remote teaching * Sales presentations * Workflows * Real-time collaboration ## Get started with SDK [Section titled “Get started with SDK”](#get-started-with-sdk) Follow the [Getting Started guide](/reactnative/) on how to connect ConnectyCube SDK and start building your first app. ## Preparations [Section titled “Preparations”](#preparations) In order to start using whiteboard, an additional config has to be provided: ```javascript const CONFIG = { ... whiteboard: { server: 'https://whiteboard.connectycube.com' } } ConnectyCube.init(CREDS, CONFIG); ``` Then, ConnectyCube whiteboard is associated with a chat dialog. In order to create a whiteboard, you need to have a chat dialog. Refer to [chat dialog creation API](/js/messaging#create-new-dialog). ## Create whiteboard [Section titled “Create whiteboard”](#create-whiteboard) When create a whiteboard you need to pass a name (any) and a chat dialog id to which whiteboard will be connected. ```javascript const params = { name: 'New Whiteboard', chat_dialog_id: "5356c64ab35c12bd3b108a41" }; ConnectyCube.whiteboard.create(params) .then(whiteboard => { const wid = whiteboard._id; }) .catch(error => { }); ``` Once whiteboard is created - a user can display it in app in a WebView using the following url: `https://whiteboard.connectycube.com?whiteboardid=<_id>&username=&title=` For `username` - any value can be provided. This is to display a text hint near the drawing arrow. ## Retrieve whiteboards [Section titled “Retrieve whiteboards”](#retrieve-whiteboards) Use the following code snippet to retrieve a list of whiteboards associated with a chat dialog: ```javascript const params = {chat_dialog_id: "5456c64ab35c17bd3b108a76"}; ConnectyCube.whiteboard.get(params) .then(whiteboard => { }) .catch(error => { }); ``` ## Update whiteboard [Section titled “Update whiteboard”](#update-whiteboard) A whiteboard can be updated, e.g. its name: ```javascript const whiteboardId = "5456c64ab35c17bd3b108a76"; const params = { name: 'New Whiteboard', }; ConnectyCube.whiteboard.update(whiteboardId, params) .then(whiteboard => { }) .catch(error => { }); ``` ## Delete whiteboard [Section titled “Delete whiteboard”](#delete-whiteboard) ```javascript const whiteboardId = "5456c64ab35c17bd3b108a76"; ConnectyCube.whiteboard.delete(whiteboardId) .then(whiteboard => { }) .catch(error => { }); ```
# Send first chat message
> Step-by-step guide to sending your first chat message using ConnectyCube Flutter 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](/flutter/getting-started/code-samples/) as a foundation for your client app.\ If you already have an app and you are looking to add 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 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 [Required preparations for supported platforms](#step-2-required-preparations-for-supported-platforms) . ### Install SDK [Section titled “Install SDK”](#install-sdk) Install package from the command line: ```bash flutter pub add connectycube_sdk ``` ### Import SDK [Section titled “Import SDK”](#import-sdk) Add the following import statement to start using all classes and methods. ```dart import 'package:connectycube_sdk/connectycube_sdk.dart'; ``` ### Initialize SDK [Section titled “Initialize SDK”](#initialize-sdk) Initialize the SDK with your ConnectyCube application credentials. You can access your application credentials in [ConnectyCube Dashboard](https://admin.connectycube.com): * SDK v3 ```dart String appId = ""; String authKey = ""; init(appId, authKey); ``` * SDK v2 ```dart String appId = ""; String authKey = ""; String authSecret = ""; init(appId, authKey, authSecret); ``` After all the above is done, the app is ready to be enriched with chat functionality. ## 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. ```dart CubeUser user = CubeUser(login: "user_login", password: "super_sequre_password"); createSession(user) .then((cubeSession) {}) .catchError((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. ```dart CubeUser user = CubeUser(id: 4448514, password: "supersecurepwd"); CubeChatConnection.instance.login(user) .then((loggedUser) {}) .catchError((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 is essential because it gives a unique conversation ID to correctly route and organize your message to the intended user. You need to pass `type = CubeDialogType.PRIVATE` and an **ID** of an opponent you want to create a chat with: ```dart CubeDialog dialog = CubeDialog( CubeDialogType.PRIVATE, occupantsIds: [56]); createDialog(dialog) .then((createdDialog) {}) .catchError((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. This code example demonstrates how to send and receive messages in the created 1-1 chat: ```dart // some dialog, which must contains opponent's id in 'occupantsIds' for CubeDialogType.PRIVATE and // 'dialogId' for other types of dialogs CubeDialog createdDialog = ...; CubeMessage message = CubeMessage(); message.body = "How are you today?"; message.dateSent = DateTime.now().millisecondsSinceEpoch; message.markable = true; message.saveToHistory = true; createdDialog.sendMessage(message) .then((cubeMessage) {}) .catchError((error) {}); // to listen messages ChatMessagesManager chatMessagesManager = CubeChatConnection.instance.chatMessagesManager; chatMessagesManager.chatMessagesStream.listen((newMessage) { // new message received }).onError((error) { // error received }); ``` 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](/flutter/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 Flutter Video SDK - from session creation to initialising and accepting the call.
**ConnectyCube’s 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](/flutter/getting-started/code-samples/) as a foundation for your client app.\ If you already have an app and you are looking to add chat 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 voice and video calls 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 [Required preparations for supported platforms](#step-2-required-preparations-for-supported-platforms) . ### Install SDK [Section titled “Install SDK”](#install-sdk) Install package from the command line: ```bash flutter pub add connectycube_sdk ``` ### Import SDK [Section titled “Import SDK”](#import-sdk) Add the following import statement to start using all classes and methods. ```dart import 'package:connectycube_sdk/connectycube_sdk.dart'; ``` ### Initialize SDK [Section titled “Initialize SDK”](#initialize-sdk) Initialize the SDK with your ConnectyCube application credentials. You can access your application credentials in [ConnectyCube Dashboard](https://admin.connectycube.com): * SDK v3 ```dart String appId = ""; String authKey = ""; init(appId, authKey); ``` * SDK v2 ```dart String appId = ""; String authKey = ""; String authSecret = ""; init(appId, authKey, authSecret); ``` After all the above is done, the app is ready to be enriched with the voice and video calls functionality. ## Step 2: Required preparations for supported platforms [Section titled “Step 2: Required preparations for supported platforms”](#step-2-required-preparations-for-supported-platforms) #### iOS [Section titled “iOS”](#ios) Add the following entries to your *Info.plist* file, located in *project root/ios/Runner/Info.plist*: ```dart NSCameraUsageDescription$(PRODUCT_NAME) Camera Usage!NSMicrophoneUsageDescription$(PRODUCT_NAME) Microphone Usage! ``` These entries allow your app to access the camera and microphone. #### Android [Section titled “Android”](#android) Ensure the following permission is present in your Android Manifest file, located in *project root/android/app/src/main/AndroidManifest.xml*: ```dart ``` If you need to use a Bluetooth device, please add: ```dart ``` The Flutter project template adds it, so it may already be there. Also you will need to set your build settings to Java 8, because official WebRTC jar now uses static methods in EglBase interface. Just add this to your app level build.gradle: ```dart android { //... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } ``` If necessary, in the same build.gradle you will need to increase minSdkVersion of defaultConfig up to 18 (currently default Flutter generator set it to 16). #### macOS [Section titled “macOS”](#macos) Add the following entries to your *.entitlements* files, located in *project root/macos/Runner*: ```dart com.apple.security.network.clientcom.apple.security.device.audio-inputcom.apple.security.device.camera ``` These entries allow your app to access the internet, microphone, and camera. #### Windows / Web / Linux [Section titled “Windows / Web / Linux”](#windows--web--linux) It does not require any special preparations. ## Step 3: Create and Authorise User [Section titled “Step 3: Create and Authorise User”](#step-3-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. ```dart CubeUser user = CubeUser(login: "user_login", password: "super_sequre_password"); createSession(user) .then((cubeSession) {}) .catchError((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 4: Connect User to chat [Section titled “Step 4: Connect User to chat”](#step-4-connect-user-to-chat) Connecting to the chat is an essential step in enabling real-time communication. **ConnectyCube Chat API** is used as a signaling transport for Video Calling API, so in order to start using Video Calling API you need to connect to Chat: ```dart CubeUser user = CubeUser(id: 4448514, password: "supersecurepwd"); CubeChatConnection.instance.login(user) .then((loggedUser) {}) .catchError((error) {}); ``` ## Step 5: P2PClient setup [Section titled “Step 5: P2PClient setup”](#step-5-p2pclient-setup) To manage P2P calls in Flutter you should use `P2PClient`. The code below shows how to proceed with the functionality: ```dart P2PClient callClient = P2PClient.instance; // returns instance of P2PClient callClient.init(); // starts listening of incoming calls //callClient.destroy(); // stops listening incoming calls and clears callbacks // calls when P2PClient receives new incoming call callClient.onReceiveNewSession = (incomingCallSession) { }; // calls when any callSession closed callClient.onSessionClosed = (closedCallSession) { }; // creates new P2PSession callClient.createCallSession(callType, opponentsIds); ``` ## Step 6: Create call session [Section titled “Step 6: Create call session”](#step-6-create-call-session) In order to use Video Calling API you need to create a call session object - choose your opponents you will have a call with and a type of session (VIDEO or AUDIO). `P2PSession` creates via `P2PClient`: ```dart P2PClient callClient; //callClient created somewhere Set opponentsIds = {}; int callType = CallType.VIDEO_CALL; // or CallType.AUDIO_CALL P2PSession callSession = callClient.createCallSession(callType, opponentsIds); ``` ## Step 7: Add listeners [Section titled “Step 7: Add listeners”](#step-7-add-listeners) Below described the main helpful callbacks and listeners: ```dart callSession.onLocalStreamReceived = (mediaStream) { // called when local media stream completely prepared // display the stream in UI // ... }; callSession.onRemoteStreamReceived = (callSession, opponentId, mediaStream) { // called when remote media stream received from opponent }; callSession.onRemoteStreamRemoved = (callSession, opponentId, mediaStream) { // called when remote media was removed }; callSession.onUserNoAnswer = (callSession, opponentId) { // called when did not receive an answer from opponent during timeout (default timeout is 60 seconds) }; callSession.onCallRejectedByUser = (callSession, opponentId, [userInfo]) { // called when received 'reject' signal from opponent }; callSession.onCallAcceptedByUser = (callSession, opponentId, [userInfo]){ // called when received 'accept' signal from opponent }; callSession.onReceiveHungUpFromUser = (callSession, opponentId, [userInfo]){ // called when received 'hungUp' signal from opponent }; callSession.onSessionClosed = (callSession){ // called when current session was closed }; ``` ## Step 8: Initiate call [Section titled “Step 8: Initiate call”](#step-8-initiate-call) ```dart Map userInfo = {}; callSession.startCall(userInfo); ``` The `userInfo` is used to pass any extra parameters in the request to your opponents. After this, your opponents will receive a new call session in callback: ```dart callClient.onReceiveNewSession = (incomingCallSession) { }; ``` ## Step 9: Accept call [Section titled “Step 9: Accept call”](#step-9-accept-call) To accept a call the following code snippet is used: ```dart Map userInfo = {}; // additional info for other call members callSession.acceptCall(userInfo); ``` After this, you will get a confirmation in the following callback: ```dart callSession.onCallAcceptedByUser = (callSession, opponentId, [userInfo]){ }; ``` Also, both the caller and opponent will get a special callback with the remote stream: ```dart callSession.onRemoteStreamReceived = (callSession, opponentId, mediaStream) { // create video renderer and set media stream to it RTCVideoRenderer streamRender = RTCVideoRenderer(); await streamRender.initialize(); streamRender.srcObject = mediaStream; streamRender.objectFit = RTCVideoViewObjectFit.RTCVideoViewObjectFitCover; // create view to put it somewhere on screen RTCVideoView videoView = RTCVideoView(streamRender); }; ``` 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](/flutter/videocalling) * [Conference calling SDK documentation](/flutter/videocalling-conference/)
# Chat
> Integrate powerful chat functionality into your Flutter 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) ```dart CubeUser user = CubeUser(id: 4448514, password: "awesomepwd"); CubeChatConnection.instance.login(user) .then((loggedUser) {}) .catchError((error) {}); ``` Use `connectionStateStream` to listen different connection states: ```dart CubeChatConnection.instance.connectionStateStream.listen((state) { log("New chat connection state is $state", TAG); switch (state){ case CubeChatConnectionState.Idle: // instance of connection was created break; case CubeChatConnectionState.Authenticated: // user successfully authorised on ConnectyCube server break; case CubeChatConnectionState.AuthenticationFailure: // error(s) was occurred during authorisation on ConnectyCube server break; case CubeChatConnectionState.Reconnecting: // started reconnection to the chat break; case CubeChatConnectionState.Resumed: // chat connection was resumed break; case CubeChatConnectionState.Ready: // chat connection fully ready for realtime communications break; case CubeChatConnectionState.ForceClosed: // chat connection was interrupted break; case CubeChatConnectionState.Closed: // chat connection was closed break; } }); ``` ### 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: ```dart // get current ConnectyCube session token and set as user's password String token = CubeSessionManager.instance.activeSession?.token; CubeUser user = CubeUser(id: 4448514, password: token); ``` ## Disconnect [Section titled “Disconnect”](#disconnect) ```dart CubeChatConnection.instance.logout(); ``` To **fully destroy** chat connection use `cubeChatConnection.destroy`: ```dart CubeChatConnection.instance.destroy(); ``` ## Reconnection [Section titled “Reconnection”](#reconnection) The SDK will try to reconnect to the chat after lost connection. To configure internal reconnection manager use next code snippet: ```dart CubeChatConnectionSettings chatConnectionSettings = CubeChatConnectionSettings.instance; chatConnectionSettings.totalReconnections = 5; // set 0 to disable internal reconnection manager or value more than 0 to set quantity of times to try to reconnect, default 5 times chatConnectionSettings.reconnectionTimeout = 5000; // timeout in milliseconds between reconnection attempts, default 5000 milliseconds ``` Additional to the internal reconnection manager or instead of it you can use your own reconnection manager. For it you can use for example [connectivity](https://pub.dev/packages/connectivity) library from [pub.dev](https://pub.dev/) repo. You can use code snippet below to listen internet connection state and start relogin to the chat when internet connection will be established: ```dart connectivityStateSubscription = Connectivity().onConnectivityChanged.listen((connectivityType) { if (connectivityType != ConnectivityResult.none) { bool isChatDisconnected = CubeChatConnection.instance.chatConnectionState == CubeChatConnectionState.Closed; if (isChatDisconnected && CubeChatConnection.instance.currentUser != null) { CubeChatConnection.instance.relogin(); } } }); ``` Additionaly you can try reconnect to the chat immediately after lost chat connection, for it use: ```dart cubeChatConnectionStateSubscription = CubeChatConnection.instance.connectionStateStream.listen((state) { if (state == CubeChatConnectionState.Closed) { Connectivity().checkConnectivity().then((connectivityType) { if (connectivityType != ConnectivityResult.none) { if (CubeChatConnection.instance.currentUser != null) { CubeChatConnection.instance.relogin(); } } }); } }); ``` ## 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 = CubeDialogType.PRIVATE` and an id of an opponent you want to create a chat with: ```dart CubeDialog newDialog = CubeDialog( CubeDialogType.PRIVATE, occupantsIds: [56]); createDialog(newDialog) .then((createdDialog) {}) .catchError((error) {}); ``` ### Create group chat [Section titled “Create group chat”](#create-group-chat) You need to pass `type = CubeDialogType.GROUP` and ids of opponents you want to create a chat with: ```dart CubeDialog newDialog = CubeDialog( CubeDialogType.GROUP, name: "Hawaii relax team", description: "Some description", occupantsIds: [56, 98, 34], photo: "https://some.url/to/avatar.jpeg"); createDialog(newDialog) .then((createdDialog) {}) .catchError((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 = CubeDialogType.PUBLIC` and ids of opponents you want to create a chat with: ```dart CubeDialog newDialog = CubeDialog( CubeDialogType.PUBLIC, name: "Blockchain trends", description: "Public dialog Description", photo: "https://some.url/to/avatar.jpeg"); createDialog(newDialog) .then((createdDialog) {}) .catchError((error) {}); ``` ### 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.  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. ```dart CubeDialog newDialog = CubeDialog(CubeDialogType.GROUP); newDialog.name = 'Friday party'; newDialog.occupantsIds = [29085, 29086, 29087]; newDialog.description = 'lets dance the night away'; newDialog.extensions = {'location': 'Sun bar'}; await createDialog(newDialog) .then((createdDialog) {}) .catchError((onError) {}); ``` 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. > **Note** > > To preview messages w/o joining to dialog pass `preview` operator in request to get messages. ## List dialogs [Section titled “List dialogs”](#list-dialogs) It’s common to request all your dialogs on every app login: ```dart Map additionalParams = { 'updated_at[gt]': 1583402980 }; getDialogs(additionalParams) .then((pagedResult) {}) .catchError((error) {}); ``` 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: ```dart String dialogId = "5356c64ab35c12bd3b108a41"; CubeDialogCustomData customData = CubeDialogCustomData("CustomDataClassName"); customData.fields['integer_field_name'] = 12345; customData.fields['string_field_name'] = "test string"; UpdateDialogParams updateDialogParams = UpdateDialogParams(); updateDialogParams.customData = customData; updateDialogParams.newName = "New name"; updateDialogParams.newDescription = "New decription"; updateDialogParams.newPhoto = ""; updateDialogParams.addOccupantIds = {563547, 563549, 563550}; updateDialogParams.deleteOccupantIds = {88708}; updateDialogParams.addAdminIds = {563550}; updateDialogParams.deleteAdminIds = {88709}; updateDialogParams.addPinnedMsgIds = {"88709sdfkahfkahfk"}; updateDialogParams.deletePinnedMsgIds = {"87987sdjkgskldglsksdfkahfkahfk"}; updateDialog(dialogId, updateDialogParams.getUpdateDialogParams()) .then((updatedDialog) {}) .catchError((error) {}); ``` > **Note** > > Only group chat owner or admins can remove other users from group chat. ## Remove dialog [Section titled “Remove dialog”](#remove-dialog) The following snippet is used to delete a dialog: ```dart String dialogId = "5e343635ca8bf479f70453f2"; bool force = false; // true - to delete everywhere, false - to delete for himself deleteDialog(dialogId, force) .then((voidResult) {}) .catchError((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. ```dart Set ids = {"5e3434e0ca8bf479f70452f1", "5e3302f0ca8bf42b6c"}; bool force = false; // true - to delete everywhere, false - to delete for himself deleteDialogs(ids, force) .then((deleteItemsResult) {}) .catchError((error) {}); ``` ## Subscribe to public dialog [Section titled “Subscribe to public dialog”](#subscribe-to-public-dialog) In order to be able to chat in public dialog, you need to subscribe to it: ```dart String dialogId = "5356c64ab35c12bd3b108a41"; subscribeToPublicDialog(dialogId) .then((cubeDialog) {}) .catchError((error) {}); ``` ## Unsubscribe from public dialog [Section titled “Unsubscribe from public dialog”](#unsubscribe-from-public-dialog) ```dart String dialogId = "5356c64ab35c12bd3b108a41"; unSubscribeFromPublicDialog(dialogId) .then((voidResult) {}) .catchError((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: ```dart String dialogId = "5356c64ab35c12bd3b108a41"; getDialogOccupants(dialogId) .then((pagedResult) {}) .catchError((error) {}); ``` ## 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. ```dart String dialogId = "5356c64ab35c12bd3b108a41"; addRemoveAdmins(dialogId, toAddIds: {45, 59}, toRemoveIds: {88708, 88709}) .then((cubeDialog) {}) .catchError((error) {}); ``` ## 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. ```dart String dialogId = "5356c64ab35c12bd3b108a41"; bool enable = true; // true - to enable, false - to disable updateDialogNotificationsSettings(dialogId, enable) .then((isEnabled) {}) .catchError((error) {}); ``` ## 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: true - enabled, false - disabled. ```dart String dialogId = "5356c64ab35c12bd3b108a41"; getDialogNotificationsSettings(dialogId) .then((isEnabled) {}) .catchError((error) {}); ``` ## Chat history [Section titled “Chat history”](#chat-history) Every chat dialog stores its chat history which you can retrieve: ```dart String dialogId = "5356c64ab35c12bd3b108a41"; GetMessagesParameters params = GetMessagesParameters(); params.limit = 100; params.filters = [RequestFilter("", "date_sent", QueryRule.GT, 1583402980)]; params.markAsRead = true; params.sorter = RequestSorter(OrderType.DESC, "", "date_sent"); getMessages(dialogId, params.getRequestParameters()) .then((pagedResult) {}) .catchError((error) {}); ``` If you want to retrieve chat messages that were sent after or before specific date time only, you can use `"date_sent", QueryRule.GT` or `"date_sent", QueryRule.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) ```dart // some dialog, which must contains opponent's id in 'occupantsIds' for CubeDialogType.PRIVATE and // 'dialogId' for other types of dialogs CubeDialog cubeDialog; // some dialog, which must contains opponent's id in 'occupantsIds' CubeMessage message = CubeMessage(); message.body = "How are you today?"; message.dateSent = DateTime.now().millisecondsSinceEpoch; message.markable = true; message.saveToHistory = true; cubeDialog.sendMessage(message) .then((cubeMessage) {}) .catchError((error) {}); // to listen messages ChatMessagesManager chatMessagesManager = CubeChatConnection.instance.chatMessagesManager; chatMessagesManager.chatMessagesStream.listen((newMessage) { // new message received }).onError((error) { // error received }); ``` ## 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 `properties` field: ```dart CubeMessage message = CubeMessage(); message.properties["field_one"] = "value_one"; message.properties["field_two"] = "value_two"; ``` ## ‘Sent’ status [Section titled “‘Sent’ status”](#sent-status) (coming soon) ## ‘Delivered’ status [Section titled “‘Delivered’ status”](#delivered-status) The following callback is used to track the ‘delivered’ status: ```dart MessagesStatusesManager messagesStatusesManager = CubeChatConnection.instance.messagesStatusesManager; messagesStatusesManager.deliveredStream.listen((messageStatuses){ print("Message RECEIVED ${messageStatuses.userId}, ${messageStatuses.messageId}, ${messageStatuses.dialogId}"); }); ``` The SDK sends the ‘delivered’ status automatically when the message is received by the recipient. This is controlled by `markable = true` parameter when you send a message. If `markable` is `false` or omitted,\ then you can send the delivered status manually: ```dart CubeDialog cubeDialog; // some dialog CubeMessage originalMessage; // message to be marked as delivered cubeDialog.deliverMessage(originalMessage) .then((voidResult) {}) .catchError((error) {}); ``` ## ‘Read’ status [Section titled “‘Read’ status”](#read-status) Send the ‘read’ status: ```dart CubeDialog cubeDialog; // some dialog CubeMessage originalMessage; // message to be marked as read cubeDialog.readMessage(originalMessage) .then((voidResult) {}) .catchError((error) {}); // listen read status MessagesStatusesManager messagesStatusesManager = CubeChatConnection.instance.messagesStatusesManager; messagesStatusesManager.readStream.listen((messageStatuses){ print("Message READ ${messageStatuses.userId}, ${messageStatuses.messageId}, ${messageStatuses.dialogId}"); }); ``` ## ‘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 screen) * 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: ```dart CubeDialog cubeDialog; // some dialog cubeDialog.sendIsTypingStatus(); cubeDialog.sendStopTypingStatus(); TypingStatusesManager typingStatusesManager = CubeChatConnection.instance.typingStatusesManager; typingStatusesManager.isTypingStream.listen((typingStatus){ // for CubeDialogType.PRIVATE typingStatus.dialogId will be null log("IS_TYPING received: ${typingStatus.userId}, ${typingStatus.dialogId}"); }); typingStatusesManager.stopTypingStream.listen((typingStatus){ // for CubeDialogType.PRIVATE typingStatus.dialogId will be null log("STOP_TYPING received: ${typingStatus.userId}, ${typingStatus.dialogId}"); }); ``` ## Attachments [Section titled “Attachments”](#attachments) 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. ```dart CubeDialog cubeDialog; // some dialog File file; // some file on device storage uploadFile(file) .then((cubeFile) { CubeMessage message = CubeMessage(); message.body = "Attachment"; message.saveToHistory = true; message.markable = true; CubeAttachment attachment = CubeAttachment(); attachment.uid = cubeFile.uid; attachment.type = CubeAttachmentType.IMAGE_TYPE; message.attachments = [attachment]; return cubeDialog.sendMessage(message); }).catchError((error) {}); ``` 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. ```dart ChatMessagesManager chatMessagesManager = CubeChatConnection.instance.chatMessagesManager; chatMessagesManager.chatMessagesStream.listen((incomingMessage) { String attachmentUid = incomingMessage.attachments?.first?.uid; if (attachmentUid != null) { String attachmentUrl = getPrivateUrlForUid(attachmentUid); } }); ``` ## Update chat message [Section titled “Update chat message”](#update-chat-message) **Via HTTP API** Update message/messages on a backend for dialog ID: ```dart String messageId = "5e3938d3ca8bf410bc80008d"; // id of message to be updated String dialogId = "5356c64ab35c12bd3b108a41"; // id of dialog, from which is message UpdateMessageParameters updateMessageParameters = UpdateMessageParameters(); updateMessageParameters.newBody = "Updated body"; //updateMessageParameters.delivered = true; // mark message as delivered updateMessageParameters.read = true; // mark message as read updateMessage(messageId, dialogId, updateMessageParameters.getRequestParameters()) .then((voidResult) {}) .catchError((error) {}); //update multiple messages String dialogId = "5356c64ab35c12bd3b108a41"; // id of dialog, from which are messages Set messagesIds = {"5e3938d3ca8bf410bc80008d", "5e3938d3ca8bf410bc800bc80"}; //messages ids to be marked UpdateMessageParameters updateMessageParameters = UpdateMessageParameters(); //updateMessageParameters.delivered = true; // mark message as delivered updateMessageParameters.read = true; // mark message as read updateMessages(dialogId, parameters.getRequestParameters(), messagesIds) .then((voidResult) {}) .catchError((error) {}); ``` **Via Chat connection** Use the following code snippet to edit a message (correct message body). ```dart CubeDialog cubeDialog; // the dialog where you want to update a message CubeMessage originalMessage; // the original message with updated body bool isLast = true; // set `true` if original message is last in chat history, or `false` if not cubeDialog.editMessage(originalMessage, isLast).then((value) { // the message successfully updated }).catchError((onError) { // got error during update the message }); ``` Other user(s) will receive the ‘edit’ message info to the listener: ```dart CubeChatConnection.instance.messagesStatusesManager!.editedStream.listen((editStatus) { // the message was edited, update your UI }); ``` ## 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: ```dart String dialogId = "5356c64ab35c12bd3b108a41"; // id of dialog, from which is message UpdateMessageParameters updateMessageParameters = UpdateMessageParameters(); //updateMessageParameters.delivered = true; // mark message as delivered updateMessageParameters.read = true; // mark messages as read updateMessages(dialogId, updateMessageParameters.getRequestParameters()) .then((voidResult) {}) .catchError((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 ```dart var messageId = '58e6a9c8a1834a3ea6001f15'; var reaction = '🔥'; addMessageReaction(messageId, reaction) .then((_) {}) .catchError((onError) {}); ``` Remove ```dart var messageId = '58e6a9c8a1834a3ea6001f15'; var reaction = '👎'; removeMessageReaction(messageId, reaction) .then((_) {}) .catchError((onError) {}); ``` Add/Remove ```dart var messageId = '58e6a9c8a1834a3ea6001f15'; var reactionToAdd = '👎'; var reactionToRemove = '🚀'; updateMessageReactions( messageId, addReaction: reactionToAdd, removeReaction: reactionToRemove) .then((_) {}) .catchError((onError) {}); ``` ### Listen reactions [Section titled “Listen reactions”](#listen-reactions) ```dart CubeChatConnection.instance.messagesReactionsManager?.reactionsStream.listen((reaction) { // var dialogId = reaction.dialogId; // var messageId = reaction.messageId; // var addReaction = reaction.addReaction; // var removeReaction = reaction.removeReaction; }); ``` ### List message reactions [Section titled “List message reactions”](#list-message-reactions) User can list message reactions ```dart var messageId = '58e6a9c8a1834a3ea6001f15'; getMessageReactions(messageId).then((reactions) { // the result contains the map where key is the reaction and value is the list of users' ids who reacted with this reaction }).catchError((onError) {}); ``` Response example from `getMessageReactions(messageId)` - [see](/server/chat#response-22) ## Delete chat messages [Section titled “Delete chat messages”](#delete-chat-messages) **Via HTTP API** The following snippet is used to remove chat message/messages: ```dart List ids = ["5e394e7bca8bf410bc8017b0", "5e394e7bca8bf466137fa1eb"]; bool force = false; // true - to delete everywhere, false - to delete for himself deleteMessages(ids, force) .then((deleteItemsResult) {}) .catchError((error) {}); ``` This request will remove the messages from current user history only, without affecting the history of other users. **Via Chat connection** Use the following code snippet to delete a message. ```dart CubeDialog cubeDialog; // the dialog where you want to delete a message CubeMessage originalMessage; // the original message you want to delete cubeDialog.deleteMessage(originalMessage).then((value) { // the message successfully deleted }).catchError((onError) { // got error during delete the message }); ``` Other user(s) will receive the ‘delete’ message info to the listener: ```dart CubeChatConnection.instance.messagesStatusesManager!.deletedStream.listen((status) { // the message was deleted, update your UI }); ``` ## Unread messages count [Section titled “Unread messages count”](#unread-messages-count) You can request total unread messages count and unread count for particular dialog: ```dart List dialogsIds = ["5e3938d3ca8bf410bc80008c"]; // skip this parameter to get data about all dialogs getUnreadMessagesCount(dialogsIds) .then((unreadCount) {}) // map contains 'total' field and matches dialogId:unreadCount .catchError((error) {}); ``` ## Global search [Section titled “Global search”](#global-search) The following API is used to search for messages and chat dialogs: ```dart GlobalSearchParams additionalParams = GlobalSearchParams(); additionalParams.limit = 3; additionalParams.endDate = DateTime(2020, 1, 1); additionalParams.startDate = DateTime.now(); additionalParams.dialogIds = ["5e3438c7ca8bf479f704560c"]; searchText("Search query", additionalParams.getSearchParams()) .then((globalSearchResult) {}) .catchError((error) {}); ``` 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](/flutter/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 `properties` field of the instance of a `CubeMessage`. ```dart var message; // some instance of `CubeMessage` message.properties['silent'] = '1'; ``` After sending such a message, the server won’t create the push notification for offline recipient/recipients. ## 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 notifications while still is connected to chat. For this particular case we have 2 handy methods: `markInactive` and `markActive`: ```dart CubeChatConnection.instance.markActive(); ``` ```dart CubeChatConnection.instance.markInactive(); ``` 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. ```dart int userId = 123234; CubeChatConnection.instance.getLasUserActivity(userId) .then((seconds) { // 'userId' was 'seconds' ago }).catchError((error){ // 'userId' never logged to the chat }); ``` ## Last activity subscription [Section titled “Last activity subscription”](#last-activity-subscription) Listen to user last activity status via subscription. Use the code below to subscribe for the last activity events. You can leave the `callback` at `null` if you don’t want to listen to evens for the specific user. ```dart CubeChatConnection.instance.lastActivityManager?.subscribeToUserLastActivityStatus(userId, callback: (seconds){ }); ``` Use the code below to unsubscribe from the last activity events. ```dart CubeChatConnection.instance.lastActivityManager?.unsubscribeFromUserLastActivityStatus(userId); ``` Use the listener below if you want to listen to events from all users you subscribed to: ```dart CubeChatConnection.instance.lastActivityManager?.lastActivityStream.listen((lastActivityEvent) { log("lastActivityEvent: userId = ${lastActivityEvent.userId}, seconds = ${lastActivityEvent.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: ```dart SystemMessagesManager systemMessagesManager = CubeChatConnection.instance.systemMessagesManager; CubeMessage systemMessage = CubeMessage(); systemMessage.recipientId = 563550; systemMessage.properties["custom_param_1"] = "custom_param_1"; systemMessage.properties["custom_param_2"] = "custom_param_2"; systemMessage.properties["custom_param_3"] = "custom_param_3"; systemMessagesManager.sendSystemMessage(systemMessage); // listen system messages systemMessagesManager.systemMessagesStream.listen((systemMessage) { log("Receive NEW system message: $systemMessage"); }).onError((error) { log("Receive system message ERROR $error")); }); ``` ## 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  Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```dart CubeCustomObject cubeCustomObject = CubeCustomObject("UserReports"); cubeCustomObject.fields = { 'reportedUserId': 45, 'reason': 'User is spamming with bad words', }; createCustomObject(cubeCustomObject).then((createdObject) { }).catchError((onError) {}); ``` ### 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: ```dart CubeCustomObject cubeCustomObject = CubeCustomObject("MessageReports"); cubeCustomObject.fields = { 'reportedMessageId': '58e6a9c8a1834a3ea6001f15', 'reason': 'The message contains phishing links', }; createCustomObject(cubeCustomObject).then((createdObject) { }).catchError((onError) {}); ``` ### 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. ```dart var listName = 'custom'; var items = [ CubePrivacyListItem(34, CubePrivacyAction.deny, isMutual: false), CubePrivacyListItem(48, CubePrivacyAction.deny), CubePrivacyListItem(18, CubePrivacyAction.allow) ]; CubeChatConnection.instance.privacyListsManager?.createList(listName, items).then((users) { // privacy list was created successfully }).catchError((exception) { // error occurred during creation privacy list }); ``` > 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: ```dart var listName = 'custom'; CubeChatConnection.instance.privacyListsManager?.setDefaultList(listName).then((users) { }).catchError((exception) { }); ``` #### 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. ```dart var listName = 'custom'; var items = [ CubePrivacyListItem(34, CubePrivacyAction.deny, isMutual: false), CubePrivacyListItem(48, CubePrivacyAction.deny), CubePrivacyListItem(18, CubePrivacyAction.allow) ]; CubeChatConnection.instance.privacyListsManager ?.declineDefaultList() .then((voidResult) => CubeChatConnection.instance.privacyListsManager ?.createList(listName, items)) .then((list) => CubeChatConnection.instance.privacyListsManager ?.setDefaultList(listName)) .then((updatedList) { }); ``` #### 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: ```dart CubeChatConnection.instance.privacyListsManager?.getAllLists().then((result) { log('active: ${result.activeList}'); log('default: ${result.defaultList}'); log('allPrivacyLists: ${result.allPrivacyLists}'); }).catchError((exception) { }); ``` #### 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: ```dart var listName = 'custom'; CubeChatConnection.instance.privacyListsManager?.getList(listName).then((items) { log('items: $items}'); }).catchError((exception) { }); ``` #### Remove privacy list [Section titled “Remove privacy list”](#remove-privacy-list) To delete a list you can call a method below: ```dart var listName = 'custom'; CubeChatConnection.instance.privacyListsManager?.removeList(listName).then((voidResult) { }).catchError((exception) { }); ``` #### 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: ```dart CubeChatConnection.instance.chatMessagesManager?.chatMessagesStream.handleError((errorPacket){ }); ``` ## 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: ```dart const msTimeout = 3000; CubeChatConnection.instance.pingWithTimeout(msTimeout: msTimeout).then((_) { }).catchError((e) { // No connection with server // Let's try to re-connect // ... }); ```
# Video Calling
> Empower your Flutter applications with our 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. ## Required preparations for supported platforms [Section titled “Required preparations for supported platforms”](#required-preparations-for-supported-platforms) #### iOS [Section titled “iOS”](#ios) Add the following entries to your *Info.plist* file, located in `/ios/Runner/Info.plist`: ```xml NSCameraUsageDescription$(PRODUCT_NAME) Camera Usage!NSMicrophoneUsageDescription$(PRODUCT_NAME) Microphone Usage! ``` This entries allow your app to access the camera and microphone. #### Android [Section titled “Android”](#android) Ensure the following permission is present in your Android Manifest file, located in `/android/app/src/main/AndroidManifest.xml`: ```xml ``` If you need to use a Bluetooth device, please add: ```xml ``` The Flutter project template adds it, so it may already be there. Also you will need to set your build settings to Java 8, because official WebRTC jar now uses static methods in `EglBase` interface. Just add this to your app level `build.gradle`: ```groovy android { //... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } ``` If necessary, in the same `build.gradle` you will need to increase `minSdkVersion` of `defaultConfig` up to `18` (currently default Flutter generator set it to `16`). #### macOS [Section titled “macOS”](#macos) Add the following entries to your *\*.entitlements* files, located in `/macos/Runner`: ```xml com.apple.security.network.clientcom.apple.security.device.audio-inputcom.apple.security.device.camera ``` This entries allow your app to access the internet, microphone, and camera. #### Windows [Section titled “Windows”](#windows) It does not require any special preparations. #### Web [Section titled “Web”](#web) It does not require any special preparations. #### Linux [Section titled “Linux”](#linux) It does not require any special preparations. ## P2PClient setup [Section titled “P2PClient setup”](#p2pclient-setup) [ConnectyCube Chat API](/flutter/messaging) is used as a signaling transport for Video Calling API, so in order to start using Video Calling API you need to [connect to Chat](/flutter/messaging#connect-to-chat). To manage P2P calls in flutter you should use `P2PClient`. Please see code below to find out possible functionality. ```dart P2PClient callClient = P2PClient.instance; // returns instance of P2PClient callClient.init(); // starts listening of incoming calls callClient.destroy(); // stops listening incoming calls and clears callbacks // calls when P2PClient receives new incoming call callClient.onReceiveNewSession = (incomingCallSession) { }; // calls when any callSession closed callClient.onSessionClosed = (closedCallSession) { }; // creates new P2PSession callClient.createCallSession(callType, opponentsIds); ``` ## Create call session [Section titled “Create call session”](#create-call-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). `P2PSession` creates via `P2PClient`: ```dart P2PClient callClient; //callClient created somewhere Set opponentsIds = {}; int callType = CallType.VIDEO_CALL; // or CallType.AUDIO_CALL P2PSession callSession = callClient.createCallSession(callType, opponentsIds); ``` ## Add listeners [Section titled “Add listeners”](#add-listeners) Below described main helpful callbacks and listeners: ```dart callSession.onLocalStreamReceived = (mediaStream) { // called when local media stream completely prepared // display the stream in UI // ... }; callSession.onRemoteStreamReceived = (callSession, opponentId, mediaStream) { // called when remote media stream received from opponent }; callSession.onRemoteStreamRemoved = (callSession, opponentId, mediaStream) { // called when remote media was removed }; callSession.onUserNoAnswer = (callSession, opponentId) { // called when did not receive an answer from opponent during timeout (default timeout is 60 seconds) }; callSession.onCallRejectedByUser = (callSession, opponentId, [userInfo]) { // called when received 'reject' signal from opponent }; callSession.onCallAcceptedByUser = (callSession, opponentId, [userInfo]){ // called when received 'accept' signal from opponent }; callSession.onReceiveHungUpFromUser = (callSession, opponentId, [userInfo]){ // called when received 'hungUp' signal from opponent }; callSession.onSessionClosed = (callSession){ // called when current session was closed }; ``` ## Initiate a call [Section titled “Initiate a call”](#initiate-a-call) ```dart Map userInfo = {}; callSession.startCall(userInfo); ``` The `userInfo` is used to pass any extra parameters in the request to your opponents. After this, your opponents will receive a new call session in callback: ```dart callClient.onReceiveNewSession = (incomingCallSession) { }; ``` ## Accept a call [Section titled “Accept a call”](#accept-a-call) To accept a call the following code snippet is used: ```dart Map userInfo = {}; // additional info for other call members callSession.acceptCall(userInfo); ``` After this, you will get a confirmation in the following callback: ```dart callSession.onCallAcceptedByUser = (callSession, opponentId, [userInfo]){ }; ``` Also, both the caller and opponents will get a special callback with the remote stream: ```dart callSession.onRemoteStreamReceived = (callSession, opponentId, mediaStream) { // create video renderer and set media stream to it RTCVideoRenderer streamRender = RTCVideoRenderer(); await streamRender.initialize(); streamRender.srcObject = mediaStream; streamRender.objectFit = RTCVideoViewObjectFit.RTCVideoViewObjectFitCover; // create view to put it somewhere on screen RTCVideoView videoView = RTCVideoView(streamRender); }; ``` 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) See [CallKit section](#callkit) below. ## Reject a call [Section titled “Reject a call”](#reject-a-call) ```dart Map userInfo = {}; // additional info for other call members callSession.reject(userInfo); ``` After this, the caller will get a confirmation in the following callback: ```dart callSession.onCallRejectedByUser = (callSession, opponentId, [userInfo]) { }; ``` Sometimes, it could a situation when you received a call request and want to reject it, 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: ```dart String callSessionId; // the id of incoming call session Set callMembers; // the ids of all call members including the caller and excluding the current user Map userInfo = {}; // additional info about performed action (optional) rejectCall(callSessionId, callMembers, userInfo: userInfo); ``` ## End a call [Section titled “End a call”](#end-a-call) ```dart Map userInfo = {}; // additional info for other call members callSession.hungUp(userInfo); ``` After this, the opponents will get a confirmation in the following callback: ```dart callSession.onReceiveHungUpFromUser = (callSession, opponentId, [userInfo]){ }; ``` ## Monitor connection state [Section titled “Monitor connection state”](#monitor-connection-state) To monitor the states of your peer connections (users) you need to implement the `RTCSessionStateCallback` abstract class: ```dart callSession.setSessionCallbacksListener(this); callSession.removeSessionCallbacksListener(); ``` ```dart /// Called in case when connection with the opponent is started establishing @override void onConnectingToUser(P2PSession session, int userId) {} /// Called in case when connection with the opponent is established @override void onConnectedToUser(P2PSession session, int userId) {} /// Called in case when connection is closed @override void onConnectionClosedForUser(P2PSession session, int userId) {} /// Called in case when the opponent is disconnected @override void onDisconnectedFromUser(P2PSession session, int userId) {} /// Called in case when connection has failed with the opponent @override void onConnectionFailedWithUser(P2PSession session, int userId) {} ``` ## 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 `RTCSessionStateCallback` callback with `peer connection state` changing to `onDisconnectedFromUser` and then again to `onConnectedToUser`. 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). ```dart code snippet with ice restart (coming soon) ``` ## Mute audio [Section titled “Mute audio”](#mute-audio) ```dart bool mute = true; // false - to unmute, default value is false callSession.setMicrophoneMute(mute); ``` ## Switch audio output [Section titled “Switch audio output”](#switch-audio-output) For iOS/Android platforms use: ```dart bool enabled = false; // true - to switch to sreakerphone, default value is false callSession.enableSpeakerphone(enable); ``` For Chrome-based browsers and Desktop platforms use: ```dart if (kIsWeb) { remoteRenderers.forEach(renderer) { renderer.audioOutput(deviceId); }); } else { callSession.selectAudioOutput(deviceId); } ``` ## Mute video [Section titled “Mute video”](#mute-video) ```dart bool enabled = false; // true - to enable local video track, default value for video calls is true callSession.setVideoEnabled(enabled); ``` ## Switch video cameras [Section titled “Switch video cameras”](#switch-video-cameras) For iOS/Android platforms use: ```dart callSession.switchCamera().then((isFrontCameraSelected){ if(isFrontCameraSelected) { // front camera selected } else { // back camera selected } }).catchError((error) { // switching camera failed }); ``` For the Web platform and Desktop platforms use: ```dart callSession.switchCamera(deviceId: deviceId); ``` ## Get available cameras list [Section titled “Get available cameras list”](#get-available-cameras-list) ```dart var cameras = await callSession.getCameras(); // call only after `initLocalMediaStream()` ``` ## Get available Audio input devices list [Section titled “Get available Audio input devices list”](#get-available-audio-input-devices-list) ```dart var audioInputs = await callSession.getAudioInputs(); // call only after `initLocalMediaStream()` ``` ## Get available Audio output devices list [Section titled “Get available Audio output devices list”](#get-available-audio-output-devices-list) ```dart var audioOutputs = await callSession.getAudioOutputs(); // call only after `initLocalMediaStream()` ``` ## Use the custom media stream [Section titled “Use the custom media stream”](#use-the-custom-media-stream) ```dart MediaStream customMediaStream; callSession.replaceMediaStream(customMediaStream); ``` ## Toggle the torch [Section titled “Toggle the torch”](#toggle-the-torch) ```dart var enable = true; // false - to disable the torch callSession.setTorchEnabled(enable); ``` ## Screen Sharing [Section titled “Screen Sharing”](#screen-sharing) The Screen Sharing feature allows you to share the screen from your device to other call members. Currently the Connectycube Flutter SDK supports the Screen Sharing feature for all supported platforms. For switching to the screen sharing during the call use next code snippet: ```dart P2PSession callSession; // the existing call session callSession.enableScreenSharing(true, requestAudioForScreenSharing: true); // for switching to the screen sharing callSession.enableScreenSharing(false); // for switching to the camera streaming ``` ### Android specifics of targeting the `targetSdkVersion` to the version **31** and above [Section titled “Android specifics of targeting the targetSdkVersion to the version 31 and above”](#android-specifics-of-targeting-the-targetsdkversion-to-the-version-31-and-above) After updating the `targetSdkVersion` to the version **31** you can encounter an error: ```log java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION ``` To avoid it do the next changes and modifications in your project: 1. Connect the `flutter_background` plugin to your project using: ```yaml flutter_background: ^x.x.x ``` 2. Add to the file `app_name/android/app/src/main/AndroidManifest.xml` to section `manifest` next permissions: ```xml ``` 3. Add to the file `app_name/android/app/src/main/AndroidManifest.xml` to section `application` next service: ```xml ``` 4. Create the next function somewhere in your project: ```dart Future initForegroundService() async { final androidConfig = FlutterBackgroundAndroidConfig( notificationTitle: 'App name', notificationText: 'Screen sharing is in progress', notificationImportance: AndroidNotificationImportance.Default, notificationIcon: AndroidResource( name: 'ic_launcher_foreground', defType: 'drawable'), ); return FlutterBackground.initialize(androidConfig: androidConfig); } ``` and call it somewhere after the initialization of the app or before starting the screen sharing. 5. Call the function `FlutterBackground.enableBackgroundExecution()` just before starting the screen sharing and function `FlutterBackground.disableBackgroundExecution()` after ending the screen sharing or finishing the call. ### IOS screen sharing using the Screen Broadcasting feature. [Section titled “IOS screen sharing using the Screen Broadcasting feature.”](#ios-screen-sharing-using-the-screen-broadcasting-feature) The Connectycube Flutter SDK supports two types of Screen sharing on the iOS platform. There are **In-app screen sharing** and **Screen Broadcasting**. The **In-app screen sharing** doesn’t require any additional preparation on the app side. But the **Screen Broadcasting** feature requires some. All required features we already added to our [P2P Calls sample](https://github.com/ConnectyCube/connectycube-flutter-samples/tree/master/p2p_call_sample). Below is the step-by-step guide on adding it to your app. It contains the following steps: 1. Add the `Broadcast Upload Extension`; 2. Add required files from our sample to your iOS project; 3. Update project configuration files with your credentials; #### Add the `Broadcast Upload Extension` [Section titled “Add the Broadcast Upload Extension”](#add-the-broadcast-upload-extension) For creating the extension you need to add a new target to your application, selecting the `Broadcast Upload Extension` template. Fill in the desired name, change the language to Swift, make sure `Include UI Extension` (see screenshot) is not selected, as we don’t need custom UI for our case, then press Finish. You will see that a new folder with the extension’s name was added to the project’s tree, containing the `SampleHandler.swift` class. Also, make sure to update the Deployment Info, for the newly created extension, to iOS 14 or newer. To learn more about creating App Extensions check the [official documentation](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionCreation.html). `` #### Add the required files from our sample to your own iOS project [Section titled “Add the required files from our sample to your own iOS project”](#add-the-required-files-from-our-sample-to-your-own-ios-project) After adding the extension you should add prepared files from our sample to your own project. Copy next files from our [`Broadcast Extension`](https://github.com/ConnectyCube/connectycube-flutter-samples/tree/master/p2p_call_sample/ios/Broadcast%20Extension) directory: `Atomic.swift`, `Broadcast Extension.entitlements` (the name can be different according to your extension’s name), `DarwinNotificationCenter.swift`, `SampleHandler.swift` (replace the automatically created file), `SampleUploader.swift`, `SocketConnection.swift`. Then open your project in Xcode and link these files with your iOS project using Xcode tools. For it, call the context menu of your extension directory, select ‘Add Files to “Runner”…’ (see screenshot) and select files copied to your extension directory before. `` #### Update project configuration files [Section titled “Update project configuration files”](#update-project-configuration-files) Do the following for your iOS project configuration files: 1. Add both the app and the extension to the same App Group. For it, add to both (app and extension) `*.entitlements` files next lines: ```xml com.apple.security.application-groupsgroup.com.connectycube.flutter ``` where the `group.com.connectycube.flutter` is your App group. To learn about working with app groups, see [Adding an App to an App Group](https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW19). We recommend you create the app group in the Apple Developer Console before. Next, add the App group id value to the app’s `Info.plist` of your app for the `RTCAppGroupIdentifier` key: ```xml RTCAppGroupIdentifiergroup.com.connectycube.flutter ``` where the `group.com.connectycube.flutter` is your App group. 2. Add a new key `RTCScreenSharingExtension` to the app’s `Info.plist` with the extension’s `Bundle Identifier` as the value: ```xml RTCScreenSharingExtensioncom.connectycube.flutter.p2p-call-sample.app.Broadcast-Extension ``` where the `com.connectycube.flutter.p2p-call-sample.app.Broadcast-Extension` is the Bundle ID of your Broadcast Extension. Take it from the Xcode `` 3. Update `SampleHandler.swift`’s `appGroupIdentifier` constant with the App Group name your app and extension are both registered to. ```swift static let appGroupIdentifier = "group.com.connectycube.flutter" ``` where the `group.com.connectycube.flutter` is your app group. 4. Make sure `voip` is added to `UIBackgroundModes`, in the app’s `Info.plist`, in order to work when the app is in the background. ```xml UIBackgroundModesremote-notificationvoipaudio ``` After performing mentioned actions you can switch to Screen sharing during the call using `useIOSBroadcasting = true`: ```dart _callSession.enableScreenSharing(true, useIOSBroadcasting: true); ``` ### Requesting desktop capture source [Section titled “Requesting desktop capture source”](#requesting-desktop-capture-source) Desktop platforms require the capture source (Screen or Window) for screen sharing. We prepared a widget ready for using that requests the available sources from the system and provides them to a user for selection. After that, you can use it as the source for screen sharing. In code it can look in a next way: ```dart var desktopCapturerSource = isDesktop ? await showDialog( context: context, builder: (context) => ScreenSelectDialog()) : null; callSession.enableScreenSharing(true, desktopCapturerSource: desktopCapturerSource); ``` The default capture source (usually it is the default screen) will be captured if set `null` as a capture source for the desktop platform. ## WebRTC Stats reporting [Section titled “WebRTC Stats reporting”](#webrtc-stats-reporting) Stats reporting is an insanely powerful tool that can provide detailed info about a call. There is info about the media, peer connection, codecs, certificates, etc. To enable stats report you should first set stats reporting frequency using `RTCConfig`. ```dart RTCConfig.instance.statsReportsInterval = 200; // receive stats report every 200 milliseconds ``` Then you can subscribe to the stream with reports using the instance of the call session: ```dart _callSession.statsReports.listen((event) { var userId = event.userId; // the user's id the stats related to var stats = event.stats; // available stats }); ``` To disable fetching Stats reports set this parameter as 0. ### Monitoring mic level and video bitrate using Stats [Section titled “Monitoring mic level and video bitrate using Stats”](#monitoring-mic-level-and-video-bitrate-using-stats) Also, we prepared the helpful manager `CubeStatsReportsManager` for processing Stats reports and getting some helpful information like the opponent’s mic level and video bitrate. For its work, you just need to configure the `RTCConfig` as described above. Then create the instance of `CubeStatsReportsManager` and initialize it with the call session. ```dart final CubeStatsReportsManager _statsReportsManager = CubeStatsReportsManager(); _statsReportsManager.init(_callSession); ``` After that you can subscribe on the interested data: ```dart _statsReportsManager.micLevelStream.listen((event) { var userId = event.userId; var micLevel = event.micLevel; // the mic level from 0 to 1 }); _statsReportsManager.videoBitrateStream.listen((event) { var userId = event.userId; var bitRate = event.bitRate; // the video bitrate in kbits/sec }); ``` After finishing the call you should dispose of the manager for avoiding memory leaks. You can do it in the `onSessionClosed` callback: ```dart void _onSessionClosed(session) { // ... _statsReportsManager.dispose(); // .. } ``` ## Configurations [Section titled “Configurations”](#configurations) ConnectyCube Flutter SDK provides possibility to change some default parameters for call session. ### Media stream configurations [Section titled “Media stream configurations”](#media-stream-configurations) Use instance of `RTCMediaConfig` class to change some default media stream configs. ```dart RTCMediaConfig mediaConfig = RTCMediaConfig.instance; mediaConfig.minHeight = 720; // sets preferred minimal height for local video stream, default value is 320 mediaConfig.minWidth = 1280; // sets preferred minimal width for local video stream, default value is 480 mediaConfig.minFrameRate = 30; // sets preferred minimal framerate for local video stream, default value is 25 mediaConfig.maxBandwidth = 512; // sets initial maximum bandwidth in kbps, set to `0` or `null` for disabling the limitation, default value is 0 ``` ### 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 via `RTCMediaConfig.instance.maxBandwidth = 512` which will result in limiting the max vailable bandwidth for ALL participants or/and during a call: ```dart callSession.setMaxBandwidth(256); // set some maximum value to increase the limit ``` which will result in limiting the max available bandwidth for current user only. ### Call connection configurations [Section titled “Call connection configurations”](#call-connection-configurations) Use instance of `RTCConfig` class to change some default call connection configs. ```dart RTCConfig config = RTCConfig.instance; config.noAnswerTimeout = 90; // sets timeout in seconds before stop dilling to opponents, default value is 60 config.dillingTimeInterval = 5; // time interval in seconds between 'invite call' packages, default value is 3 seconds, min value is 3 seconds config.statsReportsInterval = 300; // the time interval in milliseconds of periodic fetching reports, default value is 0 (disabled) ``` ## Recording [Section titled “Recording”](#recording) (coming soon) ## CallKit [Section titled “CallKit”](#callkit) > A ready [Flutter P2P Calls sample](https://github.com/ConnectyCube/connectycube-flutter-samples/tree/master/p2p_call_sample) with CallKit integrated is available at GitHub. All the below code snippets will be taken from it. 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, a flow called CallKit is used. It’s a mix of CallKit API + Push Notifications API + VOIP Push Notifications API. The complete flow is similar to the following: * a call initiator should send a push notification (for iOS it’s a VOIP push notification) along with a call request * when an opponent’s app is killed or in a background state - an opponent will receive a push notification about an incoming call, so the CallKit incoming call screen will be displayed, where a user can accept/reject the call. The [ConnectyCube Flutter Call Kit](https://pub.dev/packages/connectycube_flutter_call_kit) plugin should be used to integrate CallKit functionality. Below we provide a detailed guide on additional steps that needs to be performed in order to integrate CallKit into a Flutter app. ### Connect the [ConnectyCube Flutter Call Kit](https://pub.dev/packages/connectycube_flutter_call_kit) plugin [Section titled “Connect the ConnectyCube Flutter Call Kit plugin”](#connect-theconnectycube-flutter-call-kit-plugin) Please follow the [plugin’s guide](https://pub.dev/packages/connectycube_flutter_call_kit#configure-your-project) on how to connect it to your project. **Initiate a call** When initiate a call via `callSession.startCall()`, additionally we need to send a push notification (standard for Android user and VOIP for iOS). This is required for an opponent(s) to be able to receive an incoming call request when an app is in the background or killed state. The following request will initiate a standard push notification for Android and a VOIP push notification for iOS: ```dart P2PSession callSession; // the call session created before CreateEventParams params = CreateEventParams(); params.parameters = { 'message': "Incoming ${callSession.callType == CallType.VIDEO_CALL ? "Video" : "Audio"} call", 'call_type': callSession.callType, 'session_id': callSession.sessionId, 'caller_id': callSession.callerId, 'caller_name': callerName, 'call_opponents': callSession.opponentsIds.join(','), 'signal_type': 'startCall', 'ios_voip': 1, }; params.notificationType = NotificationType.PUSH; params.environment = kReleaseMode ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT; params.usersIds = callSession.opponentsIds.toList(); createEvent(params.getEventForRequest()).then((cubeEvent) { // event was created }).catchError((error) { // something went wrong during event creation }); ``` ### Receive call request in background/killed state [Section titled “Receive call request in background/killed state”](#receive-call-request-in-backgroundkilled-state) The goal of CallKit is to receive call requests when an app is in the background or killed state. For iOS we will use CallKit and for Android we will use standard capabilities. The [ConnectyCube Flutter Call Kit](https://pub.dev/packages/connectycube_flutter_call_kit) plugin covers all main functionality for receiving the calls in the background. First of all, you need to subscribe on a push notifications to have the possibility of receiving a push notifications in the background. You can request the subscription token from the [ConnectyCube Flutter Call Kit](https://pub.dev/packages/connectycube_flutter_call_kit) plugin. It will return the VoIP token for iOS and the common FCM token for Android. Then you can create the subscription using the token above. In code it will look next: ```dart ConnectycubeFlutterCallKit.getToken().then((token) { if (token != null) { subscribe(token); } }); subscribe(String token) async { CreateSubscriptionParameters parameters = CreateSubscriptionParameters(); parameters.pushToken = token; parameters.environment = kReleaseMode ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT; if (Platform.isAndroid) { parameters.channel = NotificationsChannels.GCM; parameters.platform = CubePlatform.ANDROID; } else if (Platform.isIOS) { parameters.channel = NotificationsChannels.APNS_VOIP; parameters.platform = CubePlatform.IOS; } String? deviceId = await PlatformDeviceId.getDeviceId; parameters.udid = deviceId; var packageInfo = await PackageInfo.fromPlatform(); parameters.bundleIdentifier = packageInfo.packageName; createSubscription(parameters.getRequestParameters()).then((cubeSubscriptions) { log('[subscribe] subscription SUCCESS'); }).catchError((error) { log('[subscribe] subscription ERROR: $error'); }); } ``` During the Android app life, the FCM token can be refreshed. Use the next code snippet to listen to this event and renew the subscription on the Connectycube back-end: ```dart ConnectycubeFlutterCallKit.onTokenRefreshed = (token) { subscribe(token); }; ``` After correctly performing the steps above the [ConnectyCube Flutter Call Kit](https://pub.dev/packages/connectycube_flutter_call_kit) plugin automatically will show the CallKit screen for iOS and the Incoming Call notification for Android. > **Note**: Pay attention: the **Android** platform requires some preparations for showing the notifications, working in the background, and starting the app from a notification. It includes next: * Request the permission on showing notifications (using the [permission\_handler](https://pub.dev/packages/permission_handler) plugin): ```dart requestNotificationsPermission() async { var isPermissionGranted = await Permission.notification.isGranted; if(!isPermissionGranted){ await Permission.notification.request(); } } ``` * Request permission for starting the app from notification (using the [permission\_handler](https://pub.dev/packages/permission_handler) plugin): ```dart Future checkSystemAlertWindowPermission(BuildContext context) async { if (Platform.isAndroid) { var androidInfo = await DeviceInfoPlugin().androidInfo; var sdkInt = androidInfo.version.sdkInt!; if (sdkInt >= 31) { if (await Permission.systemAlertWindow.isDenied) { showDialog( context: context, builder: (BuildContext context) { return Expanded( child: AlertDialog( title: Text('Permission required'), content: Text( 'For accepting the calls in the background you should provide access to show System Alerts from the background. Would you like to do it now?'), actions: [ TextButton( onPressed: () { Permission.systemAlertWindow.request().then((status) { if (status.isGranted) { Navigator.of(context).pop(); } }); }, child: Text( 'Allow', ), ), TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text( 'Later', ), ), ], ), ); }, ); } } } } ``` * Start/Stop foreground service when navigating the app to the background/foreground during the call (using [flutter\_background](https://pub.dev/packages/flutter_background) plugin): ```dart Future startBackgroundExecution() async { if (Platform.isAndroid) { return initForegroundService().then((_) { return FlutterBackground.enableBackgroundExecution(); }); } else { return Future.value(true); } } Future stopBackgroundExecution() async { if (Platform.isAndroid && FlutterBackground.isBackgroundExecutionEnabled) { return FlutterBackground.disableBackgroundExecution(); } else { return Future.value(true); } } ``` Follow the official [plugin’s doc](https://pub.dev/packages/flutter_background) for more details. ### Listen to the callbacks from [ConnectyCube Flutter Call Kit](https://pub.dev/packages/connectycube_flutter_call_kit) plugin [Section titled “Listen to the callbacks from ConnectyCube Flutter Call Kit plugin”](#listen-to-the-callbacks-from-connectycube-flutter-call-kit-plugin) After displaying the CallKit/Call Notification you need to react to callbacks produced by this screen. Use the next code snippets to start listening to them: ```dart ConnectycubeFlutterCallKit.instance.init( onCallAccepted: _onCallAccepted, onCallRejected: _onCallRejected, ); if (Platform.isIOS) { ConnectycubeFlutterCallKit.onCallMuted = _onCallMuted; } ConnectycubeFlutterCallKit.onCallRejectedWhenTerminated = onCallRejectedWhenTerminated; Future _onCallMuted(bool mute, String uuid) async { // Called when the system or user mutes a call currentCall?.setMicrophoneMute(mute); } Future _onCallAccepted(CallEvent callEvent) async { // Called when the user answers an incoming call via Call Kit ConnectycubeFlutterCallKit.setOnLockScreenVisibility(isVisible: true); // useful on Android when accept the call from the Lockscreen currentCall?.acceptCall(); } Future _onCallRejected(CallEvent callEvent) async { // Called when the user ends an incoming call via Call Kit if (!CubeChatConnection.instance.isAuthenticated()) { // reject the call via HTTP request rejectCall( callEvent.sessionId, {...callEvent.opponentsIds, callEvent.callerId}); } else { currentCall?.reject(); } } /// This provided handler must be a top-level function and cannot be /// anonymous otherwise an [ArgumentError] will be thrown. @pragma('vm:entry-point') Future onCallRejectedWhenTerminated(CallEvent callEvent) async { var currentUser = await SharedPrefs.getUser(); initConnectycubeContextLess(); return rejectCall( callEvent.sessionId, {...callEvent.opponentsIds.where((userId) => currentUser!.id != userId), callEvent.callerId}); } initConnectycubeContextLess() { CubeSettings.instance.applicationId = config.APP_ID; CubeSettings.instance.authorizationKey = config.AUTH_KEY; CubeSettings.instance.authorizationSecret = config.AUTH_SECRET; CubeSettings.instance.onSessionRestore = () { return SharedPrefs.getUser().then((savedUser) { return createSession(savedUser); }); }; } ``` Also, when performing any operations e.g. start call, accept, reject, stop, etc, we need to report back to CallKit lib - to have both app UI and CallKit UI in sync: ```dart ConnectycubeFlutterCallKit.reportCallAccepted(sessionId: callUUID); ConnectycubeFlutterCallKit.reportCallEnded(sessionId: callUUID); ``` For the `callUUID` we will use call’s `currentCall!.sessionId`. > All the above code snippets can be found in a ready [Flutter P2P Calls sample](https://github.com/ConnectyCube/connectycube-flutter-samples/tree/master/p2p_call_sample) with CallKit integrated.
# Multiparty Video Conferencing feature overview
> Discover the simplicity of integrating conference video calling into your Flutter 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 (coming soon) * Mute/Unmute audio/video streams * Display bitrate * Display mic level * Switch video input device (camera) * Switch audio input device (microphone) * Switch audio output device (desktop platforms and chrome-based browsers) * Screen sharing * Simulcasting ## Preparations [Section titled “Preparations”](#preparations) ### Required preparations for supported platforms [Section titled “Required preparations for supported platforms”](#required-preparations-for-supported-platforms) #### iOS [Section titled “iOS”](#ios) Add the following entries to your *Info.plist* file, located in `/ios/Runner/Info.plist`: ```xml NSCameraUsageDescription$(PRODUCT_NAME) Camera Usage!NSMicrophoneUsageDescription$(PRODUCT_NAME) Microphone Usage! ``` This entries allow your app to access the camera and microphone. #### Android [Section titled “Android”](#android) Ensure the following permission is present in your Android Manifest file, located in `/android/app/src/main/AndroidManifest.xml`: ```xml ``` If you need to use a Bluetooth device, please add: ```xml ``` The Flutter project template adds it, so it may already be there. Also you will need to set your build settings to Java 8, because official WebRTC jar now uses static methods in `EglBase` interface. Just add this to your app level `build.gradle`: ```groovy android { //... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } ``` If necessary, in the same `build.gradle` you will need to increase `minSdkVersion` of `defaultConfig` up to `18` (currently default Flutter generator set it to `16`). #### macOS [Section titled “macOS”](#macos) Add the following entries to your *\*.entitlements* files, located in `/macos/Runner`: ```xml com.apple.security.network.clientcom.apple.security.device.audio-inputcom.apple.security.device.camera ``` This entries allow your app to access the internet, microphone, and camera. #### Windows [Section titled “Windows”](#windows) It does not require any special preparations. #### Web [Section titled “Web”](#web) It does not require any special preparations. #### Linux [Section titled “Linux”](#linux) It does not require any special preparations. ## Create meeting [Section titled “Create meeting”](#create-meeting) In order to have a conference call, a meeting object has to be created. ```dart int now = DateTime.now().millisecondsSinceEpoch ~/ 1000; CubeMeeting meeting = CubeMeeting() ..name = 'My meeting' ..attendees = [ CubeMeetingAttendee(userId: 123, email: '...'), CubeMeetingAttendee(userId: 124, email: '...') ] ..startDate = now ..endDate = now + 60 * 60 ..withChat = false ..record = false ..public = true // ..notify = true //notify feature is available starting from the [Advanced plan](https://connectycube.com/pricing/) // ..notifyBefore = CubeMeetingNotifyBefore(TimeMetric.HOURS, 1) //notify feature is available starting from the [Advanced plan](https://connectycube.com/pricing/) ..scheduled = false; createMeeting(meeting) .then((createdMeeting) { var confRoomId = createdMeeting.meetingId; }) .catchError((onError) {}); ``` * `name` - the meeting name. * As for `attendees` - either ConnectyCube users ids or external emails can be provided. * `start_date` - the date when meeting starts. * `end_date` - the date when meeting ends. * Pass `withChat = 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.meetingId` as a conf room identifier in the below requests when join a call. ## ConferenceClient setup [Section titled “ConferenceClient setup”](#conferenceclient-setup) To manage Conference calls in flutter you should use `ConferenceClient`. Please see code below to find out possible functionality. ```dart ConferenceClient callClient = ConferenceClient.instance; // returns instance of ConferenceClient ``` ## Create call session [Section titled “Create call session”](#create-call-session) In order to use Conference Calling API you need to create a call session object - set your current user and a type of session (VIDEO or AUDIO optional). `ConferenceSession `creates via `ConferenceClient `: ```dart ConferenceClient callClient = ConferenceClient.instance; int callType = CallType.VIDEO_CALL; // or CallType.AUDIO_CALL ConferenceSession callSession = await callClient.createCallSession(currentUserId, callType: callType); ``` ## Events [Section titled “Events”](#events) There are following events you can listen for: ```dart callSession.onLocalStreamReceived = (mediaStream) { // called when local media stream completely prepared // display the stream in UI // ... }; callSession.onRemoteStreamTrackReceived = (callSession, opponentId, mediaStream, {String? trackId}) { // called when remote media stream received from opponent }; callSession.onPublishersReceived = (publishers) { // called when received new opponents/publishers }; callSession.onPublisherLeft= (publisher) { // called when opponent/publisher left room }; callSession.onError= (ex) { // called when received some exception from conference }; callSession.onSessionClosed = (callSession){ // called when current session was closed }; ``` Also, there are a few callbacks in the `callSession` to manage the connection state with user: ```dart callSession.onPublisherLeft = (userId){ }; callSession.onSubscribedOnPublisher = (userId){ }; callSession.onSubscriberAttached = (userId){ }; ``` ## Join video room [Section titled “Join video room”](#join-video-room) Just join the room and for example, send invite to some opponent: ```dart callSession.joinDialog(roomId, ((publishers) { startCall(roomId, opponents, callSession.currentUserId);// event by system message e.g. } })); ``` ## List online participants [Section titled “List online participants”](#list-online-participants) To list online users in a room: ```dart callSession.listOnlineParticipants().then((participants) { }).catchError((error) {}); ``` ## Subscribe/unsubscribe [Section titled “Subscribe/unsubscribe”](#subscribeunsubscribe) The SDK automatically subscribes to publishers when get the *onPublishersReceived* event, but you may need to subscribe manually (for example after unsubscribing) to the active publisher: ```dart callSession.subscribeToPublisher(publisher) ``` To unsubscribe from publisher: ```dart callSession.unsubscribeFromPublisher(publisher); ``` ## Leave [Section titled “Leave”](#leave) To leave current room session: ```dart callSession.leave(); ``` ## Mute audio [Section titled “Mute audio”](#mute-audio) ```dart bool mute = true; // false - to unmute, default value is false callSession.setMicrophoneMute(mute); ``` ## Switch audio output [Section titled “Switch audio output”](#switch-audio-output) For iOS/Android platforms use: ```dart bool enabled = false; // true - to switch to sreakerphone, default value is false callSession.enableSpeakerphone(enable); ``` For Chrome-based browsers and Desktop platforms use: ```dart if (kIsWeb) { remoteRenderers.forEach(renderer) { renderer.audioOutput(deviceId); }); } else { callSession.selectAudioOutput(deviceId); } ``` ## Mute video [Section titled “Mute video”](#mute-video) ```dart bool enabled = false; // true - to enable local video track, default value for video calls is true callSession.setVideoEnabled(enabled); ``` ## Switch video cameras [Section titled “Switch video cameras”](#switch-video-cameras) For iOS/Android platforms use: ```dart callSession.switchCamera().then((isFrontCameraSelected){ if(isFrontCameraSelected) { // front camera selected } else { // back camera selected } }).catchError((error) { // switching camera failed }); ``` For the Web platform and Desktop platforms use: ```dart callSession.switchCamera(deviceId: deviceId); ``` ## Get available cameras list [Section titled “Get available cameras list”](#get-available-cameras-list) ```dart var cameras = await callSession.getCameras(); // call only after `initLocalMediaStream()` ``` ## Get available Audio input devices list [Section titled “Get available Audio input devices list”](#get-available-audio-input-devices-list) ```dart var audioInputs = await callSession.getAudioInputs(); // call only after `initLocalMediaStream()` ``` ## Get available Audio output devices list [Section titled “Get available Audio output devices list”](#get-available-audio-output-devices-list) ```dart var audioOutputs = await callSession.getAudioOutputs(); // call only after `initLocalMediaStream()` ``` ## Use the custom media stream [Section titled “Use the custom media stream”](#use-the-custom-media-stream) ```dart MediaStream customMediaStream; callSession.replaceMediaStream(customMediaStream); ``` ## Toggle the torch [Section titled “Toggle the torch”](#toggle-the-torch) ```dart var enable = true; // false - to disable the torch callSession.setTorchEnabled(enable); ``` ## Screen Sharing [Section titled “Screen Sharing”](#screen-sharing) The Screen Sharing feature allows you to share the screen from your device to other call members. Currently the Connectycube Flutter SDK supports the Screen Sharing feature for all supported platforms. For switching to the screen sharing during the call use next code snippet: ```dart ConferenceSession callSession; // the existing call session callSession.enableScreenSharing(true, requestAudioForScreenSharing: true); // for switching to the screen sharing callSession.enableScreenSharing(false); // for switching to the camera streaming ``` ### Android specifics of targeting the `targetSdkVersion` to the version **31** and above [Section titled “Android specifics of targeting the targetSdkVersion to the version 31 and above”](#android-specifics-of-targeting-the-targetsdkversion-to-the-version-31-and-above) After updating the `targetSdkVersion` to the version **31** you can encounter an error: ```log java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION ``` To avoid it do the next changes and modifications in your project: 1. Connect the `flutter_background` plugin to your project using: ```yaml flutter_background: ^x.x.x ``` 2. Add to the file `app_name/android/app/src/main/AndroidManifest.xml` to section `manifest` next permissions: ```xml ``` 3. Add to the file `app_name/android/app/src/main/AndroidManifest.xml` to section `application` next service: ```xml ``` 4. Create the next function somewhere in your project: ```dart Future initForegroundService() async { final androidConfig = FlutterBackgroundAndroidConfig( notificationTitle: 'App name', notificationText: 'Screen sharing is in progress', notificationImportance: AndroidNotificationImportance.Default, notificationIcon: AndroidResource( name: 'ic_launcher_foreground', defType: 'drawable'), ); return FlutterBackground.initialize(androidConfig: androidConfig); } ``` and call it somewhere after the initialization of the app or before starting the screen sharing. 5. Call the function `FlutterBackground.enableBackgroundExecution()` just before starting the screen sharing and function `FlutterBackground.disableBackgroundExecution()` after ending the screen sharing or finishing the call. ### IOS screen sharing using the Screen Broadcasting feature. [Section titled “IOS screen sharing using the Screen Broadcasting feature.”](#ios-screen-sharing-using-the-screen-broadcasting-feature) The Connectycube Flutter SDK supports two types of Screen sharing on the iOS platform. There are **In-app screen sharing** and **Screen Broadcasting**. The **In-app screen sharing** doesn’t require any additional preparation on the app side. But the **Screen Broadcasting** feature requires some. All required features we already added to our [P2P Calls sample](https://github.com/ConnectyCube/connectycube-flutter-samples/tree/master/p2p_call_sample). Below is the step-by-step guide on adding it to your app. It contains the following steps: 1. Add the `Broadcast Upload Extension`; 2. Add required files from our sample to your iOS project; 3. Update project configuration files with your credentials; #### Add the `Broadcast Upload Extension` [Section titled “Add the Broadcast Upload Extension”](#add-the-broadcast-upload-extension) For creating the extension you need to add a new target to your application, selecting the `Broadcast Upload Extension` template. Fill in the desired name, change the language to Swift, make sure `Include UI Extension` (see screenshot) is not selected, as we don’t need custom UI for our case, then press Finish. You will see that a new folder with the extension’s name was added to the project’s tree, containing the `SampleHandler.swift` class. Also, make sure to update the Deployment Info, for the newly created extension, to iOS 14 or newer. To learn more about creating App Extensions check the [official documentation](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionCreation.html). `` #### Add the required files from our sample to your own iOS project [Section titled “Add the required files from our sample to your own iOS project”](#add-the-required-files-from-our-sample-to-your-own-ios-project) After adding the extension you should add prepared files from our sample to your own project. Copy next files from our [`Broadcast Extension`](https://github.com/ConnectyCube/connectycube-flutter-samples/tree/master/p2p_call_sample/ios/Broadcast%20Extension) directory: `Atomic.swift`, `Broadcast Extension.entitlements` (the name can be different according to your extension’s name), `DarwinNotificationCenter.swift`, `SampleHandler.swift` (replace the automatically created file), `SampleUploader.swift`, `SocketConnection.swift`. Then open your project in Xcode and link these files with your iOS project using Xcode tools. For it, call the context menu of your extension directory, select ‘Add Files to “Runner”…’ (see screenshot) and select files copied to your extension directory before. `` #### Update project configuration files [Section titled “Update project configuration files”](#update-project-configuration-files) Do the following for your iOS project configuration files: 1. Add both the app and the extension to the same App Group. For it, add to both (app and extension) `*.entitlements` files next lines: ```xml com.apple.security.application-groupsgroup.com.connectycube.flutter ``` where the `group.com.connectycube.flutter` is your App group. To learn about working with app groups, see [Adding an App to an App Group](https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW19). We recommend you create the app group in the Apple Developer Console before. Next, add the App group id value to the app’s `Info.plist` of your app for the `RTCAppGroupIdentifier` key: ```xml RTCAppGroupIdentifiergroup.com.connectycube.flutter ``` where the `group.com.connectycube.flutter` is your App group. 2. Add a new key `RTCScreenSharingExtension` to the app’s `Info.plist` with the extension’s `Bundle Identifier` as the value: ```xml RTCScreenSharingExtensioncom.connectycube.flutter.p2p-call-sample.app.Broadcast-Extension ``` where the `com.connectycube.flutter.p2p-call-sample.app.Broadcast-Extension` is the Bundle ID of your Broadcast Extension. Take it from the Xcode `` 3. Update `SampleHandler.swift`’s `appGroupIdentifier` constant with the App Group name your app and extension are both registered to. ```swift static let appGroupIdentifier = "group.com.connectycube.flutter" ``` where the `group.com.connectycube.flutter` is your app group. 4. Make sure `voip` is added to `UIBackgroundModes`, in the app’s `Info.plist`, in order to work when the app is in the background. ```xml UIBackgroundModesremote-notificationvoipaudio ``` After performing mentioned actions you can switch to Screen sharing during the call using `useIOSBroadcasting = true`: ```dart _callSession.enableScreenSharing(true, useIOSBroadcasting: true); ``` ### Requesting desktop capture source [Section titled “Requesting desktop capture source”](#requesting-desktop-capture-source) Desktop platforms require the capture source (Screen or Window) for screen sharing. We prepared a widget ready for using that requests the available sources from the system and provides them to a user for selection. After that, you can use it as the source for screen sharing. In code it can look in a next way: ```dart var desktopCapturerSource = isDesktop ? await showDialog( context: context, builder: (context) => ScreenSelectDialog()) : null; callSession.enableScreenSharing(true, desktopCapturerSource: desktopCapturerSource); ``` The default capture source (usually it is the default screen) will be captured if set `null` as a capture source for the desktop platform. ## WebRTC Stats reporting [Section titled “WebRTC Stats reporting”](#webrtc-stats-reporting) Stats reporting is an insanely powerful tool that can provide detailed info about a call. There is info about the media, peer connection, codecs, certificates, etc. To enable stats report you should first set stats reporting frequency using `RTCConfig`. ```dart RTCConfig.instance.statsReportsInterval = 200; // receive stats report every 200 milliseconds ``` Then you can subscribe to the stream with reports using the instance of the call session: ```dart _callSession.statsReports.listen((event) { var userId = event.userId; // the user's id the stats related to var stats = event.stats; // available stats }); ``` To disable fetching Stats reports set this parameter as 0. ### Monitoring mic level and video bitrate using Stats [Section titled “Monitoring mic level and video bitrate using Stats”](#monitoring-mic-level-and-video-bitrate-using-stats) Also, we prepared the helpful manager `CubeStatsReportsManager` for processing Stats reports and getting some helpful information like the opponent’s mic level and video bitrate. For its work, you just need to configure the `RTCConfig` as described above. Then create the instance of `CubeStatsReportsManager` and initialize it with the call session. ```dart final CubeStatsReportsManager _statsReportsManager = CubeStatsReportsManager(); _statsReportsManager.init(_callSession); ``` After that you can subscribe on the interested data: ```dart _statsReportsManager.micLevelStream.listen((event) { var userId = event.userId; var micLevel = event.micLevel; // the mic level from 0 to 1 }); _statsReportsManager.videoBitrateStream.listen((event) { var userId = event.userId; var bitRate = event.bitRate; // the video bitrate in kbits/sec }); ``` After finishing the call you should dispose of the manager for avoiding memory leaks. You can do it in the `onSessionClosed` callback: ```dart void _onSessionClosed(session) { // ... _statsReportsManager.dispose(); // .. } ``` ## Configurations [Section titled “Configurations”](#configurations) ConnectyCube Flutter SDK provides possibility to change some default parameters for call session. ### Media stream configurations [Section titled “Media stream configurations”](#media-stream-configurations) Use instance of `RTCMediaConfig` class to change some default media stream configs. ```dart RTCMediaConfig mediaConfig = RTCMediaConfig.instance; mediaConfig.minHeight = 720; // sets preferred minimal height for local video stream, default value is 320 mediaConfig.minWidth = 1280; // sets preferred minimal width for local video stream, default value is 480 mediaConfig.minFrameRate = 30; // sets preferred minimal framerate for local video stream, default value is 25 mediaConfig.simulcastConfig = SimulcastConfig( highVideoBitrate: 1024, mediumVideoBitrate: 512, lowVideoBitrate: 96, ); // sets the bitrate for different stream's qualities ``` ### Call connection configurations [Section titled “Call connection configurations”](#call-connection-configurations) ```dart ConferenceConfig.instance.url = SERVER_ENDPOINT // 'wss://...:8989'; ``` # Signaling implementation [Section titled “Signaling implementation”](#signaling-implementation) To implement regular calls with events such as call, reject, hang up there should be used some signaling mechanism. ## Signaling [Section titled “Signaling”](#signaling) As signaling mechanism there can be used [ConnectyCube system-messages](/flutter/messaging#system-messages) with predefined custom properties. ## Start Call [Section titled “Start Call”](#start-call) Just join the room and send an invitation start call message to opponents: ```dart var systemMessagesManager = CubeChatConnection.instance.systemMessagesManager; ... sendCallMessage(String roomId, List participantIds) { List callMsgList = _buildCallMessages(roomId, participantIds); callMsgList.forEach((callMsg) { callMsg.properties["callStart"] = '1'; callMsg.properties["participantIds"] = participantIds.join(','); }); callMsgList.forEach((msg) => systemMessagesManager.sendSystemMessage(msg)); } List buildCallMessages(String roomId, List participantIds) { return participantIds.map((userId) { var msg = CubeMessage(); msg.recipientId = userId; msg.properties = {"janusRoomId": roomId}; return msg; }).toList(); } ``` ## Reject Call [Section titled “Reject Call”](#reject-call) Send reject message when *decline/busy* call: ```dart sendRejectMessage(String roomId, bool isBusy, int participantId) { List callMsgList = buildCallMessages(roomId, [participantId]); callMsgList.forEach((callMsg) { callMsg.properties["callRejected"] = '1'; callMsg.properties["busy"] = isBusy.toString(); }); callMsgList.forEach((msg) => systemMessagesManager.sendSystemMessage(msg)); } ``` ## End call [Section titled “End call”](#end-call) Send end call message when *hangup/answer\_timeout* call: ```dart sendEndCallMessage(String roomId, List participantIds) { List callMsgList = _buildCallMessages(roomId, participantIds); callMsgList.forEach((callMsg) { callMsg.properties["callEnd"] = '1'; }); callMsgList.forEach((msg) => systemMessagesManager.sendSystemMessage(msg)); } ``` ## Get call events [Section titled “Get call events”](#get-call-events) Listen and parse all call events with *systemMessagesManager*: ```dart systemMessagesManager.systemMessagesStream.listen((cubeMessage) => parseCallMessage(cubeMessage)); ... parseCallMessage(CubeMessage cubeMessage) { final properties = cubeMessage.properties; if(properties.containsKey("callStart")) { String roomId = properties["janusRoomId"]; List participantIds = properties["participantIds"].split(',').map((id) => int.parse(id)).toList(); if(this._roomId == null) { this._roomId = roomId; this._initiatorId = cubeMessage.senderId; this._participantIds = participantIds; // handleNewCall(); } } else if(properties.containsKey("callRejected")) { String roomId = properties["janusRoomId"]; bool isBusy = properties["busy"] == 'true'; if(this._roomId == roomId) { // handleRejectCall(); } } else if(properties.containsKey("callEnd")) { String roomId = properties["janusRoomId"]; if(this._roomId == roomId) { // handleEndCall(); } } } ``` ## Adding user to call [Section titled “Adding user to call”](#adding-user-to-call) For adding user to current call you can send invite message with current *roomId* and *participantIds*: ```dart sendCallMessage(String roomId, List participantIds) { List callMsgList = _buildCallMessages(roomId, participantIds); callMsgList.forEach((callMsg) { callMsg.properties["callStart"] = '1'; callMsg.properties["participantIds"] = participantIds.join(','); }); callMsgList.forEach((msg) => systemMessagesManager.sendSystemMessage(msg)); } ``` And then on the receiver side when the new user successfully joins the room, he automatically subscribes to all active participants in current call (at the same time, other participants will receive *onPublishersReceived* and will be able to subscribe to that new user). ## Retrieve meetings [Section titled “Retrieve meetings”](#retrieve-meetings) Retrieve a meeting by id: ```dart getMeetings({'_id': meetingId}) .then((meetings) {}) .catchError((onError) {}); ``` Retrieve a list of meetings: ```dart getMeetings() .then((meetings) {}) .catchError((onError) {}); ``` or use the `getMeetingsPaged` for requesting meeting using the pagination feature: ```dart getMeetingsPaged(limit: 10, skip: 20, params: {'scheduled': true}) .then((result) async { var meetings = result.items; }) .catchError((onError) {}); ``` ## Edit meeting [Section titled “Edit meeting”](#edit-meeting) A meeting creator can edit a meeting: ```dart CubeMeeting originalMeeting; //some meeting which was created before, should cantain `meetingId` originalMeeting.name = updatedName; originalMeeting.startDate = updatedStartDate; originalMeeting.endDate = updatedEndDate; originalMeeting.attendees = [ CubeMeetingAttendee( userId: 125, email: 'test@email.com'), ]; updateMeeting(originalMeeting) .then((updatedMeeting) {}) .catchError((onError){}); ``` or use the method `updateMeetingById` for updating only some [fields](/server/meetings#parameters-2) of the meeting model: ```dart updateMeetingById(meetingId, {'record': true}) .then((updatedMeeting) {}) .catchError((onError){}); ``` ## Delete meeting [Section titled “Delete meeting”](#delete-meeting) A meeting creator can delete a meeting: ```dart deleteMeeting(meetingId) .then((voidResult) {}) .catchError((onError) {}); ``` ## Recording [Section titled “Recording”](#recording) Server-side recording is available. Read more about Recording feature ## Simulcasting [Section titled “Simulcasting”](#simulcasting) Simulcast is a feature in video conferencing that enables the simultaneous broadcasting of multiple versions of the same video stream, each with different resolutions and bitrates. This feature is commonly used to optimize the video quality for all participants in a video call, especially when some participants have slower or less reliable internet connections. In a video call, the video stream is sent from the sender’s device to the recipient’s device. If the recipient has a slow or unreliable internet connection, the video quality may suffer, and the video may lag or freeze. Simulcast allows the sender to send multiple versions of the video stream, each with different resolutions and bitrates. Overall, simulcast is a useful feature in video conferencing that can help improve the quality and reliability of video calls, especially when participants have different network conditions. ### Request preferred quality of incoming streams [Section titled “Request preferred quality of incoming streams”](#request-preferred-quality-of-incoming-streams) Use the next code snippet to request the preferred quality of the remote stream for user: ```dart var confCallSession; // some `ConferenceSession` var userId; // user's id you want to change the quality of the received stream var streamType = StreamType.low; // the type of the stream you want to request for user, there are tree possible values `StreamType.high`, `StreamType.medium` and `StreamType.low` confCallSession.requestPreferredStreamForOpponent(userId, streamType); ``` Or there is a separate method for requesting the different qualities using a single request for the set of users: ```dart var confCallSession; // some `ConferenceSession` var config = { 123: StreamType.low, 124: StreamType.high }; // is the map where key is the opponent's id and the value is the required quality of the video stream confCallSession.requestPreferredStreamsForOpponents(config); ``` ### Limit the bitrate of your own stream [Section titled “Limit the bitrate of your own stream”](#limit-the-bitrate-of-your-own-stream) The sender can limit the bitrate of the own video stream according to the app logic using the next code snippet: ```dart var confCallSession; // some `ConferenceSession` confCallSession.setMaxBandwidth(960); // set `0` or `null` to remove any limitations ``` ### Listen to the stream quality changes [Section titled “Listen to the stream quality changes”](#listen-to-the-stream-quality-changes) When the sender limits the stream’s quality or the server limits the stream’s bitrate according to its internal logic the receivers will receive a notification about it. To listen to these events just subscribe to the next event broadcaster: ```dart var confCallSession; // some `ConferenceSession` confCallSession.onSubStreamChanged = onSubStreamChanged; void onSubStreamChanged(int userId, StreamType streamType) { log("[onSubStreamChanged] userId: $userId, streamType: $streamType", TAG); } ``` ### Temporal layers [Section titled “Temporal layers”](#temporal-layers) During a conference call, video is typically transmitted in real-time between multiple participants. However, the quality of the video may suffer due to network bandwidth limitations, especially when there are multiple participants with varying network conditions. Temporal layers allow for the efficient transmission of video by dividing the video frames into different layers. The lower layers contain basic information about the video, such as color and shape, while the higher layers contain more detailed information, such as facial expressions or small movements. #### Request the layer of the stream [Section titled “Request the layer of the stream”](#request-the-layer-of-the-stream) By default, the server sends the layer with the highest quality but you can request another one according to your app needs. Use the next code snippet for requesting of the preferred layer: ```dart var confCallSession; // some `ConferenceSession` var userId; // user's id you want to change the FPS of the received stream var layer; // there available three layers: `0` - lowest FPS, `1` - medium FPS and `2` - highest FPS confCallSession.requestPreferredLayerForOpponentStream(userId, layer) ``` Or use the next method for requesting different layers for a lot of users in a single request: ```dart var confCallSession; // some `ConferenceSession` var config = { 123: 0, 124: 2, }; confCallSession.requestPreferredLayersForOpponentsStreams(config); ``` > **Note**: Pay attention: not all platforms support the layer feature in full. For example, iOS/Android platforms support three layers and the Chrome browser supports only one layer, etc. #### Listen to the stream layer changes [Section titled “Listen to the stream layer changes”](#listen-to-the-stream-layer-changes) After manually requesting the preferred layer the server sends the event the about success of changing the layer for user. To listen to these events just subscribe to the next event broadcaster: ```dart var confCallSession; // some `ConferenceSession` confCallSession.onLayerChanged = onLayerChanged; void onLayerChanged(int userId, int layer) { log("[onLayerChanged] userId: $userId, layer: $layer", TAG); } ```
# Address Book
> Effortlessly upload, sync, and access ConnectyCube users from your phone contacts in your Flutter 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](/flutter/authentication-and-users#authentication-via-phone-number) you can easily organize a modern 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. ```dart List contacts = []; contacts.add(CubeContact("Gordie Kann", "1879108395")); contacts.add(CubeContact("Wildon Gilleon", "2759108396")); contacts.add(CubeContact("Gaston Center", "3759108396")); String uuid = ""; // unique identificator for Addressbook, skip it to use global Addressbook for user bool force = true; // true - to overwrite Addressbook, false - to update uploadAddressBook(contacts, force, uuid) .then((addressBookResult) {}) .catchError((error) {}); ``` * 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 = true` option. * You also can remove a contact by setting `destroy: true`. * 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: ```dart String uuid = ""; // unique identificator for Addressbook, skip it to use global Addressbook for user getAddressBook(uuid) .then((contactsList) {}) .catchError((error) {}); ``` ## 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: ```dart bool compact = true; // true - server will return only id, phone and addressBookName fields of User. Otherwise - all User's fields will be returned. String uuid = ""; // unique identificator for Addressbook, skip it to use global Addressbook for user getRegisteredUsersFromAddressBook(compact, uuid) .then((usersList) {}) .catchError((error) {}); ``` ## 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: 
# Authentication and Users
> Simplify user authentication in your Flutter app with our definitive API guide. Fortify your app's defenses and protect user data effectively.
Every user has to authenticate with ConnectyCube before using any ConnectyCube functionality. When someone connects with an application using ConnectyCube, the application will need to obtain a session token which provides temporary, secure access to ConnectyCube APIs. A session token is an opaque string that identifies a user and an application. ## Create session token [Section titled “Create session token”](#create-session-token) As a starting point, the user’s session token needs to be created allowing user any further actions within the app. Pass login/email and password to identify a user: ```dart CubeUser user = CubeUser(login: "user_login", password: "super_sequre_password"); createSession(user) .then((cubeSession) {}) .catchError((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:** 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). ### Authentication via social provider [Section titled “Authentication via social provider”](#authentication-via-social-provider) Flutter SDK provides support for next social providers: * `CubeProvider.FACEBOOK`; * `CubeProvider.TWITTER`; The generalized method for signing-in via social provider is: ```dart String socialProvider; // possible providers CubeProvider.FACEBOOK, CubeProvider.TWITTER String accessToken; String accessTokenSecret; // required only for CubeProvider.TWITTER provider createSessionUsingSocialProvider(socialProvider, accessToken, accessTokenSecret) .then((cubeUser) {}) .catchError((error){}); ``` #### Get Facebook Access token [Section titled “Get Facebook Access token”](#get-facebook-access-token) In order to use Facebook authentication you need to get an Access token first. Flutter has a few plugins that provide this feature. We will use the [flutter\_facebook\_auth](https://pub.dev/packages/flutter_facebook_auth) in our example. First, you must configure each supported platform according to the plugin [official documentation](https://facebook.meedu.app) web page. Then you can request the login via Facebook API and sign in to ConnectyCube using its credentials. Follow the code below on how to do this: ```dart FacebookAuth.instance.login(permissions: ['email', 'public_profile']).then((result) { if (result.status == LoginStatus.success) { var accessToken = result.accessToken!.token; createSessionUsingSocialProvider(CubeProvider.FACEBOOK, accessToken).then((cubeUser) { // `cubeUser` - the instance of the signed-in CubeUser }).catchError((onError){ // process an error of signing-in to the ConnectyCube }); } else { // process other statuses (cancelled, failed, operationInProgress) } }); ``` #### Get Twitter Auth token and Auth Token Secret [Section titled “Get Twitter Auth token and Auth Token Secret”](#get-twitter-auth-token-and-auth-token-secret) In order to use Twitter authentication you need to get an Auth token and Auth Token Secret first. Flutter has a few plugins that provide this feature. We will use the [twitter\_login](https://pub.dev/packages/twitter_login) in our example. First, you must configure each supported platform according to the plugin [README](https://pub.dev/packages/twitter_login). Then you can request the login via Twitter API and sign in to ConnectyCube using its credentials. Follow the code below on how to do this: ```dart var apiKey = ''; // the API Key from the Twitter developer console var apiSecretKey = ''; // the API Secret from the Twitter developer console var redirectURI = ''; // the registered Callback URLs in TwitterApp TwitterLogin(apiKey: apiKey, apiSecretKey: apiSecretKey, redirectURI: redirectURI).loginV2(forceLogin: true).then((authResult) { if (authResult.status == TwitterLoginStatus.loggedIn) { var authToken = authResult.authToken!; var authSecret = authResult.authTokenSecret!; createSessionUsingSocialProvider(CubeProvider.TWITTER, authToken, authSecret).then((cubeUser) { // `cubeUser` - the instance of the signed-in CubeUser }).catchError((onError){ // process an error of signing-in to the ConnectyCube }); } else { // process other statuses (cancelledByUser, error) } }); ``` ### Authentication via phone number [Section titled “Authentication via phone number”](#authentication-via-phone-number) Sign In with phone number is supported with [Firebase Phone Authentication](https://firebase.google.com/docs/auth/flutter/phone-auth). The detailed guides on [How to configure Firebase in your project](/flutter/firebase-setup-guide#connect-firebase-sdk) and [How to get `accessToken`](/flutter/firebase-setup-guide#firebase-phone-authentication) are presented in our separated section [How to Setup Firebase](/flutter/firebase-setup-guide#how-to-setup-firebase). After receiving the `accessToken` you can use it for authentication using next API: ```dart createSessionUsingFirebasePhone(projectId, accessToken).then((cubeUser) { // user successfully authenticated on the Connectycube }).catchError((onError){ // error occurs during authentication on the Connectycube }); ``` ### Authentication via Firebase Email/Google Sign-In: [Section titled “Authentication via Firebase Email/Google Sign-In:”](#authentication-via-firebase-emailgoogle-sign-in) This authentication method supports [Firebase Email/password sign-in](https://firebase.google.com/docs/auth/flutter/password-auth) and [Google Sign-In](https://firebase.google.com/docs/auth/flutter/federated-auth#google). The detailed guides on [How to configure Firebase in your project](/flutter/firebase-setup-guide#connect-firebase-sdk) and [How to get `accessToken`](/flutter/firebase-setup-guide#firebase-e-mail-authentication) are presented in our separated section [How to Setup Firebase](/flutter/firebase-setup-guide#how-to-setup-firebase). After receiving the `accessToken` you can use it for authentication using next API: ```dart createSessionUsingFirebaseEmail( DefaultFirebaseOptions.currentPlatform.projectId, idToken) .then((cubeUser) { // user successfully authenticated on the Connectycube }).catchError((onError) { // error occurs during authentication on the Connectycube }); ``` ### Authentication via external identity provider [Section titled “Authentication via external identity provider”](#authentication-via-external-identity-provider) **Custom Identity Provider (CIdP)** feature is necessary if you have your own user database and want to authenticate users in ConnectyCube against it. It works the same way as Facebook/Twitter SSO. With Custom Identity Provider feature you can continue use your user database instead of storing/copying user data to ConnectyCube database. #### CIdP high level integration flow [Section titled “CIdP high level integration flow”](#cidp-high-level-integration-flow) To get started with **CIdP** integration, check the [Custom Identity Provider guide](/guides/custom-identity-provider) which describes high level integration flow. #### How to login via CIdP [Section titled “How to login via CIdP”](#how-to-login-via-cidp) Once you done with the setup mapping in ConnectyCube Dashboard, it’s time to verify the integration. To perform CIdP login, the same ConnectyCube [User Login API](/flutter/authentication-and-users/#upgrade-session-token-user-login) is used. You just use existing login request params to pass your external user token: ```dart CubeUser user = CubeUser(login: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzNDU2Nzg5LCJuYW1lIjoiSm9zZXBoIn0.OpOSSw7e485LOP5PrzScxHb7SR6sAOMRckfFwi4rp7o"); createSession(user) .then((cubeUser) {}) .catchError((error){}); ``` Once the login is successful, ConnectyCube will create an underalying User entity, so then you can use ConnectyCube APIs in a same way as you do with a normal login. With CIdP we do not have/store any user password in ConnectyCube User entity. Following further integration, you may need to connect to Chat. In a case of CIdP login, you do not have a user password. In such cases you should use ConnectyCube session token as a password for chat connection. [Follow the Connect to Chat with CIdP guide](/flutter/messaging/#connect-to-chat-using-custom-authentication-providers). ### Create guest session [Section titled “Create guest session”](#create-guest-session) To create a session with guest user use the following code: ```dart var user = CubeUser(isGuest: true, fullName: 'Awesome Smith'); createSession(user) .then((cubeSession) {}) .catchError((error){}); ``` ## Session expiration [Section titled “Session expiration”](#session-expiration) Expiration time for session token is 2 hours after last request to API. If you perform query with expired token,\ you will receive the error **Required session does not exist**. In this case you need to recreate a session token. Use `CubeSessionManager` to get information about current session state. ```dart bool isValid = CubeSessionManager.instance.isActiveSessionValid(); /// isValid == true - you have an active session /// isValid == false - you have an expired session and you should create new one ``` ConnectyCube Flutter SDK has the special callbаck for automatic session restoring. Just set it during the initialization ConnectyCube Flutter SDK in your project. ```dart initConnectyCube() { init("XXX", "XXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXX", onSessionRestore: restoreSession); } Future restoreSession() { CubeUser savedUser; //some CubeUser, which was saved before return createSession(savedUser); } ``` ## Destroy session token [Section titled “Destroy session token”](#destroy-session-token) To destroy a session use the following code: ```dart int sessionId = 12345; // skip this parameter to delete current session deleteSession(sessionId) .then((voidResult) {}) .catchError((error){}); // used to delete all session except current deleteSessionsExceptCurrent() .then((voidResult) {}) .catchError((error){}); ``` ## User signup [Section titled “User signup”](#user-signup) Only login (or email) + password are required for user to be created. Other parameters are optional: ```dart CubeUser user = CubeUser( login: 'marvin18', password: 'supersecurepwd', email: 'awesomeman@gmail.com', fullName: 'Marvin Simon', phone: '47802323143', website: 'https://dozensofdreams.com', customData: "{middle_name: 'Bartoleo'}"); signUp(user) .then((cubeUser) {}) .catchError((error){}); ``` ## User profile update [Section titled “User profile update”](#user-profile-update) ```dart CubeUser user = CubeUser( login: 'marvin18', fullName: 'Marvin Simon'); updateUser(user) .then((updatedUser) {}) .catchError((error) {}); ``` If you want to change your password, you need to provide 2 fields: `password` and `oldPassword`. Updated `user` entity will be returned. ```dart CubeUser user = CubeUser( login: 'marvin18', password: "newPassword", oldPassword: "oldPassword"); updateUser(user) .then((updatedUser) {}) .catchError((error) {}); ``` ## User avatar [Section titled “User avatar”](#user-avatar) You can set a user’s avatar. You just need to upload it to the ConnectyCube cloud storage and then connect to user. ```dart File file; //some file from device storage CubeUser user; // some user to set avatar uploadFile(file, false) .then((cubeFile) { user.avatar = cubeFile.uid; return updateUser(user); }) .catchError((error) {}); ``` Now, other users can get you avatar: ```dart CubeUser user; // some user with avatar String avatarUrl = getPrivateUrlForUid(user.avatar); ``` ## Password reset [Section titled “Password reset”](#password-reset) It’s possible to reset a password via email: ```dart resetPassword("awesomeman@gmail.com") .then((voidResult) {}) .catchError((error) {}); ``` If provided email is valid - an email with password reset instruction will be sent to it. ## Retrieve users [Section titled “Retrieve users”](#retrieve-users) ### Retrieve user by ID [Section titled “Retrieve user by ID”](#retrieve-user-by-id) ```dart int userId = 22; getUserById(userId) .then((cubeUser) {}) .catchError((error) {}); ``` ### Retrieve users by IDs [Section titled “Retrieve users by IDs”](#retrieve-users-by-ids) ```dart Set ids = {22, 33}; getAllUsersByIds(ids) .then((pagedResult) {}) .catchError((error) {}); ``` ### Retrieve user by login [Section titled “Retrieve user by login”](#retrieve-user-by-login) ```dart String login = "marvin18"; getUserByLogin(login) .then((cubeUser) {}) .catchError((error) {}); ``` ### Retrieve user by email [Section titled “Retrieve user by email”](#retrieve-user-by-email) ```dart String email = "marvin18@example.com"; getUserByEmail(email) .then((cubeUser) {}) .catchError((error) {}); ``` ### Retrieve users by full name [Section titled “Retrieve users by full name”](#retrieve-users-by-full-name) ```dart String fullName = "Marvin Samuel"; getUsersByFullName(fullName) .then((pagedResult) {}) .catchError((error) {}); ``` ### Retrieve user by phone number [Section titled “Retrieve user by phone number”](#retrieve-user-by-phone-number) ```dart String phoneNumber = "47802323143"; getUserByPhoneNumber(phoneNumber) .then((cubeUser) {}) .catchError((error) {}); ``` ### Retrieve user by external ID [Section titled “Retrieve user by external ID”](#retrieve-user-by-external-id) ```dart var externalUserId = '6304a4399cf9db00200c1bd7'; getUserByExternalUserId(externalUserId) .then((cubeUser) {}) .catchError((error) {}); ``` ### Retrieve users by tags [Section titled “Retrieve users by tags”](#retrieve-users-by-tags) ```dart Set tags = {"apple"}; getUsersByTags(tags) .then((pagedResult) {}) .catchError((error) {}); ``` ### Retrieve users by parameters [Section titled “Retrieve users by parameters”](#retrieve-users-by-parameters) There is an available function for getting users by all available parameters and filters provided by [link](/server/users#retrieve-users-v2) ```dart var paginator = RequestPaginator(50, page: 0); var sorter = RequestSorter.desc('created_at'); var parameters = { 'login': 'smith1', 'phone': '6754987345566', 'user_tags[nin]': ['vip'], 'updated_at[lte]': '2018-12-06T09:21:41Z' }; getUsers(parameters, paginator: paginator, sorter: sorter).then((response) { var foundUsers = response?.items; }).catchError((onError){ }); ``` ## Delete user [Section titled “Delete user”](#delete-user) A user can delete himself from the platform: ```dart int userId = 123456; deleteUser(userId) .then((cubeUser) {}) .catchError((error) {}); ```
# Custom Data
> Maximize your Flutter app's potential with customizable data cloud storage solutions. Tailor data structures to match your application's unique requirements.
Custom Data, also known as cloud tables or custom objects, provide users with the flexibility to define and manage data in a way that is specific to their application’s requirements. Here are some common reasons why you might need use custom data in your app: * Custom Data allows you to define data structures that align precisely with your application’s needs. This is particularly useful when dealing with complex or unique data types that don’t fit well into standard ConnectyCube models. * In certain applications, there may be entities or objects that are unique to that particular use case. Custom Data enable the representation of these entities in the database, ensuring that the data storage is optimized for the application’s logic. * Custom Data allows you to extend the functionality of the app by introducing new types of data that go beyond what the ConnectyCube platform’s standard models support. Custom Data empower you to introduce these extensions and additional features. * In situations where data needs to be migrated from an existing system or transformed in a specific way, Custom Data offer the flexibility to accommodate these requirements. * Your application needs to integrate with external systems or APIs, Custom Data can be designed to align seamlessly with the data structures of your external systems. ## Get started with SDK [Section titled “Get started with SDK”](#get-started-with-sdk) Follow the [Getting Started guide](/flutter/) on how to connect ConnectyCube SDK and start building your first app. ## Preparations [Section titled “Preparations”](#preparations) In order to start using Custom Data you need first create the data scheme in the ConnectyCube Admin panel. For it navigate to **`Home -> Your App -> Custom -> List`** then click on **`ADD`** and from the dropdown menu select **`ADD NEW CLASS`**. In the opened dialog, enter your model name and add the necessary fields. The ConnectyCube Custom Data models’ fields support various data types or arrays (except `Location`). These include: * Integer([`int`](https://api.dart.dev/stable/dart-core/int-class.html)\` in the **Dart**); * Float ([`double`](https://api.dart.dev/stable/dart-core/double-class.html) in the **Dart**); * Boolean ([`bool`](https://dart.dev/language/built-in-types#booleans) in the **Dart**); * String ([`String`](https://dart.dev/language/built-in-types#strings) in the **Dart**); * Location (the array what consist of **two** `double`s); After adding all the required fields, click the **`CREATE CLASS`** button to save your scheme.  Newly created class is now available and contains the following data: * **\_id** - record identifier generated by system automatically * **user\_id** - identifier of user who created a record * **\_parent\_id** - by default is null * **field\_1** - field defined in a scheme * **field\_2** - field defined in a scheme * … * **field\_N** - field defined in a scheme * **created\_at** - date and time when a record is created  After that you can perform all **CRUD** operations with your Custom Data. > **Note**: The **Class name** field will be represented as the DB table name and will be used for identification of your requests during the work with Custom Data. ## Permissions [Section titled “Permissions”](#permissions) Access control list (ACL) is a list of permissions attached to some object. An ACL specifies which users have an access to objects, as well as what operations are allowed with such objects. Each entry in a typical ACL specifies a subject and an operation. ACL models may be applied to collections of objects as well as to individual entities within the system’s hierarchy. Adding the Access Control list is only available within the Custom Data module. ### Permissions scheme [Section titled “Permissions scheme”](#permissions-scheme) ConnectyCube permission scheme contains five permissions levels: * **Open** (open) - any user within the application can access the record(s) in the class and perform actions with the record * **Owner** (owner) - only the Owner (the user who created a record) is authorized to perform actions with the record * **Not allowed** (not\_allowed) - no one (except the Account Administrator) can proceed with a chosen action * **Open for groups** (open\_for\_groups) - users with the specified tag(s) will be included in the group that is authorized to perform actions with a record. Multiple groups can be specified (number of groups is not limited). * **Open for users ids** (open\_for\_users\_ids) - only users with listed IDs can perform actions with a record. Actions for work with an entity: * **Create** - create a new record * **Read** - retrieve information about a record and view it in the read-only mode * **Update** - update any parameter of the chosen record that can be updated by user * **Delete** - delete a record To set a permission schema for the Class, go to ConnectyCube dashboard and find a required class within Custom Data module Click on **`EDIT PERMISSION`** button to open permissions schema to edit. Each listed action has a separate permission level to select. The exception is a ‘Create’ action that isn’t available for ‘Owner’ permission level.  ### Permission levels [Section titled “Permission levels”](#permission-levels) Two access levels are available in the ConnectyCube: access to Class and access to Record. Only one permission schema can be applied for the record. Using the Class permission schema means that the Record permission schema won’t be affected on a reсord. **Class entity** **Class** is an entity that contains records. Class can be created via ConnectyCube dashboard only within Custom data module. Operations with Class entity are not allowed in API. All actions (Create, Read, Update and Delete) that are available for the ‘Class’ entity are also applicable for all records within a class. Default Class permission schema is using during the creation of a class: * **Create:** Open * **Read:** Open * **Update:** Owner * **Delete:** Owner To enable applying Class permissions for any of the action types, ‘Use Class permissions’ check box should be ticked. It means that the record permission schema (if existing) won’t be affected on a record. **Record entity** **Record** is an entity within the Class in the Custom Data module that can be created in ConnectyCube dashboard and via API. Each record within a Class has its own permission level. Unlike Class entity, ‘Not allowed’ permission level isn’t available for a record as well as only three actions are available to work with a record - read, update and delete. Default values for Record permission scheme: * Read: Open * Update: Owner * Delete: Owner To set a separate permission scheme for a record, open a record to edit and click on **`SET PERMISSION ON RECORD`** button:  Define the permission level for each of available actions. ## Create a new record [Section titled “Create a new record”](#create-a-new-record) Create a new record with the defined parameters in the class. Fields that weren’t defined in the request but are available in the scheme (class) will have null values. ```dart var className = 'call_history_item'; CubeCustomObject cubeCustomObject = CubeCustomObject(className); cubeCustomObject.fields = { 'call_name': 'Group call', 'call_participants': [2325293, 563541, 563543], 'call_start_time': 1701789791673, 'call_end_time': 0, 'call_duration': 0, 'call_state': 'accepted', 'is_group_call': true, 'call_id': 'f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454', 'user_id': 2325293, 'caller_location': [50.004444, 36.234380] }; createCustomObject(cubeCustomObject).then((createdObject) { var objectFields = createdObject.fields; // the fields of your object saved on the ConnectyCube server }).catchError((onError) {}); ``` The Dart language allows you to use serialization and deserialization [feature](https://docs.flutter.dev/data-and-backend/serialization/json) for your objects. You can use it here for the serialization and deserialization of your objects. In this case, the code will appear as follows: ```dart var className = 'call_history_item'; CubeCustomObject cubeCustomObject = CubeCustomObject(className); CallItem callItem; // the instance of the class that implements the `toJson()` method and has the CallItem.fromJson(Map json) constructor cubeCustomObject.fields = callItem.toJson(); createCustomObject(cubeCustomObject).then((createdObject) { var callItem = CallItem.fromJson(createdObject.fields); }).catchError((onError) {}); ``` ### Create a record with permissions [Section titled “Create a record with permissions”](#create-a-record-with-permissions) To create a new record with permissions, add the `permissions` parameter to the instance of the `CubeCustomObject` you use to create the object. In this case, the request will look like this: ```dart var className = 'call_history_item'; CubeCustomObject cubeCustomObject = CubeCustomObject(className); CallItem callItem; // the instance of the class that implements the `toJson()` method and has the CallItem.fromJson(Map json) constructor cubeCustomObject.fields = callItem.toJson(); CubeCustomObjectPermission updatePermission = CubeCustomObjectPermission(Level.OPEN_FOR_USERS_IDS, ids: [2325293, 563541, 563543]); CubeCustomObjectPermissions permissions = CubeCustomObjectPermissions(updatePermission: updatePermission); cubeCustomObject.permissions = permissions; createCustomObject(cubeCustomObject).then((createdObject) { var permissions = createdObject.permissions; // the permissions of the created object }).catchError((onError) {}); ``` ## Retrieve record by ID [Section titled “Retrieve record by ID”](#retrieve-record-by-id) Retrieve the record by specifying its identifier. ```dart var className = 'call_history_item'; var id = '656f407e29d6c5002fce89dc'; getCustomObjectById(className, id).then((record) async { var foundRecord = record; // the found record or null if requested record not found }).catchError((onError) {}); ``` ## Retrieve records by IDs [Section titled “Retrieve records by IDs”](#retrieve-records-by-ids) Retrieve records by specifying their identifiers. ```dart var className = 'call_history_item'; var ids = ['656f407e29d6c5002fce89dc', '5f985984ca8bf43530e81233']; getCustomObjectsByIds(className, ids).then((result) async { var foundItems = result.items; // the list of the found items }).catchError((onError) {}); ``` ## Retrieve records within a class [Section titled “Retrieve records within a class”](#retrieve-records-within-a-class) Search records within the particular class. > **Note:** If you are sorting records by time, use the `_id` field. It is indexed and it will be much faster than sorting by `created_at` field. The list of additional parameter for sorting, filtering, aggregation of the search response is provided by link ```dart var className = 'call_history_item'; var params = { 'call_start_time[gt]': 1701789791673 }; getCustomObjectsByClassName(className, params).then((result) async { var foundItems = result.items; // the list of the found items }).catchError((onError) {}); ``` ## Retrieve the record’s permissions [Section titled “Retrieve the record’s permissions”](#retrieve-the-records-permissions) > **Note:** record permissions are checked during request processing. Only the owner has an ability to view a record’s permissions. ```dart var className = 'call_history_item'; var id = '656f407e29d6c5002fce89dc'; getCustomObjectPermissions(className, id).then((permissions) async { var recordPermissions = permissions; // the permissions applied for a searched record }).catchError((onError) {}); ``` ## Update record by ID [Section titled “Update record by ID”](#update-record-by-id) Update record data by specifying its ID. ```dart var className = 'call_history_item'; var id = '656f407e29d6c5002fce89dc'; var params = { 'call_end_time': 1701945033120, }; updateCustomObject(className, id, params).then((updatedObject) async { var updatedCustomObject = updatedObject.fields; // updated object // or // `CallItem` class that implements the `toJson()` method and has the CallItem.fromJson(Map json) constructor var updatedItem = CallItem.fromJson(updatedObject.fields); // updated object }).catchError((onError) {}); ``` ## Update records by criteria [Section titled “Update records by criteria”](#update-records-by-criteria) Update records found by the specified search criteria with a new parameter(s). The structure of the parameter `search_criteria` and the list of available operators provided by link ```dart var className = 'call_history_item'; var params = { 'search_criteria': { 'user_id': 1234567 }, 'call_name': 'Deleted user' }; updateCustomObjectsByCriteria(className, params).then((result) async { var updatedItems = result.items; // the list of updated items }).catchError((onError) {}); ``` ## Delete record by ID [Section titled “Delete record by ID”](#delete-record-by-id) Delete a record from a class by record identifier. ```dart var className = 'call_history_item'; var id = '656f407e29d6c5002fce89dc'; deleteCustomObjectById(className, id).then((_) { // the record was successfully deleted }).catchError((onError) {}); ``` ## Delete several records by their IDs [Section titled “Delete several records by their IDs”](#delete-several-records-by-their-ids) Delete several records from a class by specifying their identifiers. If one or more records can not be deleted, an appropriate error is returned for that record(s). ```dart var className = 'call_history_item'; var ids = ['656f407e29d6c5002fce89dc', '5f998d3bca8bf4140543f79a']; deleteCustomObjectsByIds(className, ids).then((result) { // `result` is the result of the deleting records request }).catchError((onError) {}); ``` ## Delete records by criteria [Section titled “Delete records by criteria”](#delete-records-by-criteria) Delete records from the class by specifying a criteria to find records to delete. The search query format is provided by link ```dart var className = 'call_history_item'; var params = { 'call_id[in]': ['f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454'] }; deleteCustomObjectsByCriteria(className, params).then((totalDeleted) { // `totalDeleted` is the count of deleted items }).catchError((onError) {}); ``` ## Relations [Section titled “Relations”](#relations) Objects (records) in different classes can be linked with a `parentId` field. If the record from the **Class 1** is pointed with a record from **Class 2** via its `parentId`, the `parentId` field will contain an ID of a record from the **Class 2**. If a record from the **Class 2** is deleted, all its children (records of the **Class 1** with `parentId` field set to the **Class 2** record ID) will be automatically deleted as well. The linked children can be retrieved with `_parent_id={id_of_parent_class_record}` parameter.
# How to Setup Firebase
> A complete, step-by-step installation guide to integrate Firebase to your Flutter application, empowering to easily create feature-rich, scalable applications.
You might need to use Firebase for the following reasons: 1. Firebase authentication of users in your app via phone number. 2. Firebase Cloud Messaging (FCM) push notifications (former GCM). It might look a bit scary at first. But don’t panic and let’s check how to do this right step by step. ## Firebase account and project registration [Section titled “Firebase account and project registration”](#firebase-account-and-project-registration) Follow these steps to register your Firebase account and create a Firebase project: 1. **Register a Firebase account** at [Firebase console](https://console.firebase.google.com/) . You can use your Google account to authenticate at Firebase. 2. Click **Create a project**  > **Note**: If you have a Google project registered for your mobile app, select it from the \*\* Project name\*\* dropdown menu. You can also edit your **Project ID** if you need. A unique ID is assigned automatically to each project. This ID is used in publicly visible Firebase features. For example, it will be used in database URLs and in your Firebase Hosting subdomain. If you need to use a specific subdomain, you can change it. 3. Fill in the fields required (Project name, Project ID, etc.) and click **Continue**.   4. Configure Google Analytics for your project and click **Create project**.  Then click Continue.  5. Select platform for which you need Firebase  ## Connect Firebase SDK [Section titled “Connect Firebase SDK”](#connect-firebase-sdk) The Firebase team has developed the convenient tool for integration the Firebase features to your **Flutter** project. For using this tool just do few steps provided in the guide. The main steps include: * [Install the Firebase CLI](https://firebase.google.com/docs/cli?authuser=1\&hl=en#install_the_firebase_cli) You can install the Firebase CLI using a method that matches your operating system, experience level, and/or use case. Regardless of how you install the CLI, you have access to the same functionality and the `firebase` command. [**`Windows`**](https://firebase.google.com/docs/cli?authuser=1\&hl=en#install-cli-windows) [**`macOS/Linux`**](https://firebase.google.com/docs/cli?authuser=1\&hl=en#install-cli-mac-linux) * [Log in the Firebase CLI](https://firebase.google.com/docs/cli?authuser=1\&hl=en#sign-in-test-cli) After installing the CLI, you must authenticate. Then you can confirm authentication by listing your Firebase projects. 1. Log into Firebase using your Google account by running the following command: ```commandline firebase login ``` This command connects your local machine to Firebase and grants you access to your Firebase projects. 2. Install the FlutterFire CLI by running the following command from any directory: ```commandline dart pub global activate flutterfire_cli ``` * [Configure your app to use Firebase](https://firebase.google.com/docs/flutter/setup?platform=ios#configure-firebase) Use the FlutterFire CLI to configure your Flutter apps to connect to Firebase. From your Flutter project directory, run the following command to start the app configuration workflow: ```commandline flutterfire configure ``` The `flutterfire configure` workflow does the following: * Asks you to select the platforms (iOS, Android, Web) supported in your Flutter app. For each selected platform, the FlutterFire CLI creates a new Firebase app in your Firebase project.\ \ You can select either to use an existing Firebase project or to create a new Firebase project. If you already have apps registered in an existing Firebase project, the FlutterFire CLI will attempt to match them based on your current Flutter project configuration. * Creates a Firebase configuration file (`firebase_options.dart`) and adds it to your `lib/` directory. * (for Crashlytics or Performance Monitoring on Android) Adds the required product-specific Gradle plugins to your Flutter app. > **Note**: After this initial running of `flutterfire configure`, you need to re-run the command any time that you:\ > - Start supporting a new platform in your Flutter app.\ > - Start using a new Firebase service or product in your Flutter app, especially if you start using sign-in with Google, Crashlytics, Performance Monitoring, or Realtime Database. > > Re-running the command ensures that your Flutter app’s Firebase configuration is up-to-date and (for Android) automatically adds any required Gradle plugins to your app. * [Initialize Firebase in your app](https://firebase.google.com/docs/flutter/setup?platform=ios#initialize-firebase) 1. From your Flutter project directory, run the following command to install the core plugin: ```commandline flutter pub add firebase_core ``` 2. From your Flutter project directory, run the following command to ensure that your Flutter app’s Firebase configuration is up-to-date: ```commandline flutterfire configure ``` 3. In your `lib/main.dart` file, import the Firebase core plugin and the configuration file you generated earlier: ```dart import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; ``` 4. Also in your `lib/main.dart` file, initialize Firebase using the `DefaultFirebaseOptions` object exported by the configuration file: ```dart await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); ``` 5. Rebuild your Flutter application: ```commandline flutter run ``` ## Firebase Phone authentication [Section titled “Firebase Phone authentication”](#firebase-phone-authentication) This option allows users in your app authenticate themselves via phone number. If you use this method for user authentication, the user receives an SMS with verification code and authenticates via that code in your app. You need to follow these steps to add Firebase authentication to your Flutter project: 1. Setup the Firebase console for using Phone auth per every supported platform your project support according [this guide](https://firebase.google.com/docs/auth/flutter/phone-auth#setup). 2. Install the [**Firebase UI Auth**](https://pub.dev/packages/firebase_ui_auth) plugin in your project using next command: ```commandline flutter pub add firebase_ui_auth ``` 3. Configure the routing of your project for opening needed screens according to the plugin logic: ```dart MaterialApp( routes: { // ...other routes '/phone': (context) => PhoneInputScreen( actions: [ SMSCodeRequestedAction((context, action, flowKey, phoneNumber) { Navigator.of(context).push( MaterialPageRoute( builder: (context) => SMSCodeInputScreen( flowKey: flowKey, ), ), ); }), ] ), } ); ``` 4. Add the action for listening the auth state changes and using it for the ConnectyCube API requests: ```dart AuthStateChangeAction( (context, state) { state.user?.getIdToken().then((idToken) { signInUsingFirebasePhone( DefaultFirebaseOptions.currentPlatform.projectId, idToken ).then((cubeUser) { // the user was successfully signed in // start required logic (processing the user, opening screen with chats, etc.) Navigator.of(context).popUntil((route) { return route.isFirst; }); }); }); }); ``` After it the rout will look similar to the next: ```dart MaterialApp( routes: { // ...other routes '/phone': (context) => PhoneInputScreen( actions: [ SMSCodeRequestedAction((context, action, flowKey, phoneNumber) { Navigator.of(context).push( MaterialPageRoute( builder: (context) => SMSCodeInputScreen( flowKey: flowKey, actions: [ AuthStateChangeAction( (context, state) { state.user?.getIdToken().then((idToken) { signInUsingFirebasePhone( DefaultFirebaseOptions.currentPlatform.projectId, idToken ).then((cubeUser) { // the user was successfully signed in // start required logic (processing the user, opening screen with chats, etc.) // in our example we just go to the first screen Navigator.of(context).popUntil((route) { return route.isFirst; }); }); }); }) ], ), ), ); }), ] ), } ); ``` 5. Try running a test. Once your user is logged in successfully, you will find him/her in your **Dashboard >> Your App >> Users >> Users List** section.  > The simplest method for using the Firebase Phone Auth feature in your Flutter project was provided. It is available the manual user verification without using the [`firebase_ui_auth`](https://pub.dev/packages/firebase_ui_auth) plugin too. Use [this guide](https://firebase.google.com/docs/auth/flutter/phone-auth#usage) for manual implementation of the Firebase Phone Auth feature. ## Firebase E-mail authentication [Section titled “Firebase E-mail authentication”](#firebase-e-mail-authentication) This option allows users in your app authenticate themselves via **Firebase Email/Password** or **Firebase OAuth (Google Sign In)**. The Firebase E-mail authentication setup flow is very similar to the [Firebase Phone authentication](./flutter/firebase-setup-guide#firebase-phone-authentication) but has its own features. As this authentication type supports two auth providers [EmailAuthProvider](https://github.com/firebase/flutterfire/blob/master/packages/firebase_ui_auth/doc/providers/email.md#firebase-ui-email-auth-provider) and [GoogleProvider](https://github.com/firebase/flutterfire/blob/master/packages/firebase_ui_auth/doc/providers/oauth.md#google-sign-in) we will separate this guide on two sub-sections: * [Firebase Email auth](./flutter/firebase-setup-guide#firebase-email-auth-provider); * [Google Sign In](./flutter/firebase-setup-guide#google-sign-in); ### Firebase Email auth provider [Section titled “Firebase Email auth provider”](#firebase-email-auth-provider) The official FlutterFire [guide](https://github.com/firebase/flutterfire/blob/master/packages/firebase_ui_auth/doc/providers/email.md#firebase-ui-email-auth-provider) is detailed enough and we will just provide the main aspects of required configurations. 1. To support email as a provider, first ensure that the “Email/Password” provider is enabled in the [Firebase Console](https://console.firebase.google.com/project/_/authentication/providers): * In the Firebase console’s **Authentication** section, open the [Sign in method](https://console.firebase.google.com/project/_/authentication/providers?_gl=1*9tft23*_ga*ODk2NjM0OTA4LjE2NzY0NjQ2MDM.*_ga_CW55HF8NVT*MTY5MTU3MjY2NC4zOS4xLjE2OTE1NzMwNzguMC4wLjA.) page; * From the **Sign in method** page, enable the **Email/password sign-in** method and click **Save**.  2. Install the [**Firebase UI Auth**](https://pub.dev/packages/firebase_ui_auth) plugin in your project using next command: ```commandline flutter pub add firebase_ui_auth ``` 3. Configure the routing of your project for opening the `SignInScreen` that contains the suggestion to input the e-mail and password of the Firebase user: ```dart MaterialApp( routes: { // ...other routes '/firebase-email': (context) => SignInScreen( providers: [EmailAuthProvider()], ), } ); ``` 4. Add the action(s) for listening the auth state changes and using it for the ConnectyCube API requests: ```dart actions: [ AuthStateChangeAction((context, state) { state.user?.getIdToken().then((idToken) { if (idToken != null) { signInUsingFirebaseEmail( DefaultFirebaseOptions.currentPlatform.projectId, idToken) .then((cubeUser) { // user successfully authenticated on the Connectycube }).catchError((onError) { // error occurs during authentication on the Connectycube }); } }); }), ], ``` ```plaintext After it the rout will look similar to the next: ``` ```dart MaterialApp( routes: { // ...other routes '/firebase-email': (context) => SignInScreen( providers: [EmailAuthProvider()], actions: [ AuthStateChangeAction((context, state) { state.user?.getIdToken().then((idToken) { if (idToken != null) { signInUsingFirebaseEmail( DefaultFirebaseOptions.currentPlatform.projectId, idToken) .then((cubeUser) { // user successfully authenticated on the Connectycube }).catchError((onError) { // error occurs during authentication on the Connectycube }); } }); }), ], );, } ); ``` Starting this point the app will show separated screen for authentication via Firebase Email provider. After successful login to the Firebase the action `SignedIn` will be performed and the request to the Connectycube will run. ### Google Sign In [Section titled “Google Sign In”](#google-sign-in) The configuration of the Firebase Google OAuth requires few veri simple steps: 1. Install the official [google\_sign\_in](https://pub.dev/packages/google_sign_in) plugin to your project as described in the README. > **Note**: you need go through configuration steps for each platform as described on the [README](https://pub.dev/packages/google_sign_in). 2. Enable the “Google” provider in the Firebase Console and configure it: * In the Firebase console’s **Authentication** section, open the [Sign in method](https://console.firebase.google.com/project/_/authentication/providers?_gl=1*9tft23*_ga*ODk2NjM0OTA4LjE2NzY0NjQ2MDM.*_ga_CW55HF8NVT*MTY5MTU3MjY2NC4zOS4xLjE2OTE1NzMwNzguMC4wLjA.) page; * From the **Sign in method** page, enable the **Google** method and click **Save**.  > To ensure cross-platform support, please ensure you have followed installation instructions for both the `google_sign_in` package and the provider on the Firebase Console (such as adding a [SHA1 fingerprint](https://developers.google.com/android/guides/client-auth?authuser=0) for Android applications). 3. Install the [firebase\_ui\_oauth\_google](https://pub.dev/packages/firebase_ui_oauth_google) plugin: ```commandline flutter pub add firebase_ui_oauth_google ``` 4. Copy and save the `clientId` property (which can be found in the Firebase Console):  Almost done. After these configurations your app is ready for requesting the Google data from user. The `firebase_ui_oauth_google` support few variants of requesting the data. We will use the `OAuthProviderButton` with the `GoogleProvider`: ```dart AuthStateListener( listener: (oldState, newState, controller) { if (newState is SignedIn) { newState.user?.getIdToken().then((idToken) { if (idToken != null) { signInUsingFirebaseEmail( DefaultFirebaseOptions.currentPlatform.projectId, idToken) .then((cubeUser) { // user successfully authenticated on the Connectycube }).catchError((onError) { // error occurs during authentication on the Connectycube }); } }); } }, child: OAuthProviderButton( provider: GoogleProvider( clientId: ''), // set the `clientId` from the value copied in the p.4 of this guide ), ) ``` So now you know how to use Firebase features in your ConnectyCube apps. If you have any difficulties - please let us know via [support channel](mailto:support@connectycube.com)
# Push Notifications
> Elevate your Flutter app's performance with push notifications API guide. Keep users engaged with real-time updates, ensuring seamless interaction on the go.
Push Notifications provide a way to deliver some information to users while they are not using your app actively. The following use cases can be covered additionally with push notifications: * send a chat message when a recipient is offline (a push notification will be initiated automatically in this case) * make a video call with offline opponents (need to send a push notification manually) ## Configuration [Section titled “Configuration”](#configuration) In order to start work with push notifications you need to configure it for all required platforms. ### iOS and macOS [Section titled “iOS and macOS”](#ios-and-macos) 1. First of all you need to generate Apple push certificate (\*.p12 file) and upload it to ConnectyCube dashboard. Here is a guide on how to create a certificate 2. Upload Apple push certificate (\*.p12 file) to ConnectyCube dashboard: * Open your ConnectyCube Dashboard at [admin.connectycube.com](https://admin.connectycube.com) * Go to **Push notifications** module, **Credentials** page * Upload the newly created APNS certificate on **Apple Push Notification Service (APNS)** form.  3. Lastly, open Xcode project of your Flutter app and enable Push Notifications capabilities. Open Xcode, choose your project file, Signing & Capabilities tab and then add a Push Notifications capability. Also - tick a ‘Remote notifications’ checkbox in Background Modes section.  ### Android and Web [Section titled “Android and Web”](#android-and-web) #### Configure Firebase project and Service account key (recommended) [Section titled “Configure Firebase project and Service account key (recommended)”](#configure-firebase-project-and-service-account-key-recommended) In order to start working with push notifications functionality you need to configure it. 1. Create and configure your [Firebase project](https://console.firebase.google.com) and obtain the **Service account key**. If you have any difficulties with Firebase project registration, [follow our guide](/android/firebase-setup-guide). To find your **FCM service account key** go to your **Firebase console > Cloud Messaging > Manage Service Accounts** section:  2. Select and configure **Manage Keys** option:  3. Select **ADD KEY**, **Create new key**:  4. Select **Key type** (json recommended) and create:  5. Save it locally:  6. Browse your saved **FCM Service account key** in your **Dashboard > Your App > Push Notifications > Credentials**, select the environment for which you are adding the key. Use the same key for development and production zones.  7. Copy **Sender ID** value from your Firebase console **Cloud Messaging** section. You may require it later.  8. In order to use push notifications on Android, you need to create `google-services.json` file and copy it into project’s `android/app` folder. Also, you need to update the `applicationId` in `android/app/build.gradle` to the one which is specified in `google-services.json`, so they must match. If you have no existing API project yet, the easiest way to go about in creating one is using this [step-by-step installation process](https://firebase.google.com/docs/android/setup) #### Configure Firebase project and Server key (DEPRECATED) [Section titled “Configure Firebase project and Server key (DEPRECATED)”](#configure-firebase-project-and-server-key-deprecated) 1. Create and configure your [Firebase project](https://console.firebase.google.com) and obtain the **Server key**. If you have any difficulties with Firebase project registration, [follow our guide](/android/firebase-setup-guide). To find your **FCM server key** go to your **Firebase console > Cloud Messaging** section:  2. Copy your **FCM server key** to your **Dashboard > Your App > Push Notifications > Credentials**, select the environment for which you are adding the key and hit **Save key**. Use the same key for development and production zones.  3. Copy **Sender ID** value from your Firebase console **Cloud Messaging** section. You may require it later.  4. In order to use push notifications on Android, you need to create `google-services.json` file and copy it into project’s `android/app` folder. Also, you need to update the `applicationId` in `android/app/build.gradle` to the one which is specified in `google-services.json`, so they must match. If you have no existing API project yet, the easiest way to go about in creating one is using this [step-by-step installation process](https://firebase.google.com/docs/android/setup) ### Config `firebase_messaging` plugin [Section titled “Config firebase\_messaging plugin”](#config-firebase_messaging-plugin) > Note: Below provided the short guide for configuring the `firebase_messaging`. For more details and specific platform configs please follow the [FlutterFire](https://firebase.flutter.dev/docs/overview) official documentation. There is [**firebase\_messaging**](https://pub.dev/packages/firebase_messaging) plugin’s official repo. #### Add dependency [Section titled “Add dependency”](#add-dependency) Add this to your package’s `pubspec.yaml` file: ```yaml dependencies: ... firebase_core: ^x.x.x firebase_messaging: ^x.x.x ... ``` #### Install it [Section titled “Install it”](#install-it) You can install packages from the command line: ```bash flutter pub get ``` #### Add classpath for goggle services plugin [Section titled “Add classpath for goggle services plugin”](#add-classpath-for-goggle-services-plugin) Add classpath to your `build.gradle` file by path `android/build.gradle` ```groovy buildscript { ... } dependencies { ... classpath 'com.google.gms:google-services:4.3.3' } } ``` #### Apply Google Services plugin [Section titled “Apply Google Services plugin”](#apply-google-services-plugin) Add at bottom of `android/app/build.gradle` next line: ```groovy apply plugin: 'com.google.gms.google-services' ``` #### Add required files from Firebase development console [Section titled “Add required files from Firebase development console”](#add-required-files-from-firebase-development-console) * add `google-services.json` to Android project; * add `GoogleService-Info.plist` to iOS project; #### Add imports [Section titled “Add imports”](#add-imports) ```dart import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; ``` #### Init and configure plugin in your app [Section titled “Init and configure plugin in your app”](#init-and-configure-plugin-in-your-app) ```dart ... void init() { Firebase.initializeApp(); FirebaseMessaging firebaseMessaging = FirebaseMessaging.instance; // request permissions for showing notification in iOS firebaseMessaging.requestPermission(alert: true, badge: true, sound: true); // add listener for foreground push notifications FirebaseMessaging.onMessage.listen((remoteMessage) { log('[onMessage] message: $remoteMessage'); showNotification(remoteMessage); }); // set listener for push notifications, which will be received when app in background or killed FirebaseMessaging.onBackgroundMessage(onBackgroundMessage); } ``` ### Automatic config using FlutterFire CLI [Section titled “Automatic config using FlutterFire CLI”](#automatic-config-using-flutterfire-cli) There is alternative way for configuring the FCM in your Flutter project. It is the [FlutterFire CLI](https://firebase.flutter.dev/docs/cli/). Using this tool you can configure FCM in automatic mode. Just follow the guide on how to generate and use the configuration file. ## Subscribe to push notifications [Section titled “Subscribe to push notifications”](#subscribe-to-push-notifications) In order to start receiving push notifications you need to subscribe your current device. First of all you have to get `token`: ```dart FirebaseMessaging firebaseMessaging = FirebaseMessaging.instance; String token; if (Platform.isAndroid || kIsWeb) { token = await firebaseMessaging.getToken(); } else if (Platform.isIOS || Platform.isMacOS) { token = await firebaseMessaging.getAPNSToken(); } if (!isEmpty(token)) { subscribe(token); } firebaseMessaging.onTokenRefresh.listen((newToken) { subscribe(newToken); }); ``` Then you can subscribe for push notifications using `token`: ```dart subscribe(String token) async { log('[subscribe] token: $token'); bool isProduction = bool.fromEnvironment('dart.vm.product'); CreateSubscriptionParameters parameters = CreateSubscriptionParameters(); parameters.environment = isProduction ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT; if (Platform.isAndroid) { parameters.channel = NotificationsChannels.GCM; parameters.platform = CubePlatform.ANDROID; parameters.bundleIdentifier = "com.connectycube.flutter.chat_sample"; } else if (Platform.isIOS) { parameters.channel = NotificationsChannels.APNS; parameters.platform = CubePlatform.IOS; parameters.bundleIdentifier = Platform.isIOS ? "com.connectycube.flutter.chatSample.app" : "com.connectycube.flutter.chatSample.macOS"; } String deviceId = await DeviceId.getID; parameters.udid = deviceId; parameters.pushToken = token; createSubscription(parameters.getRequestParameters()) .then((cubeSubscription) {}) .catchError((error) {}); } ``` ### FCM on the iOS/macOS [Section titled “FCM on the iOS/macOS”](#fcm-on-the-iosmacos) Alternatively, there is a way to use Firebase (FCM) push notifications on the iOS and macOS platforms. While we **recommend use native Apple push notifications on iOS** (as described above), you may have a requirement to use (FCM) push notifications on iOS. The configuration of the Flutter project is easy and you can do it in a few simple steps by following the official [FlutterFire documentation](https://firebase.flutter.dev/docs/messaging/overview#4-apple-integration). The main steps include: * [Configuring your app](https://firebase.flutter.dev/docs/messaging/apple-integration#configuring-your-app); * [Enable Push Notifications](https://firebase.flutter.dev/docs/messaging/apple-integration#enable-push-notifications) (in the same way as for APNS); * [Enable Background Modes](https://firebase.flutter.dev/docs/messaging/apple-integration#enable-background-modes-ios-only) (iOS only) (in the same way as for APNS); * [Linking APNs with FCM](https://firebase.flutter.dev/docs/messaging/apple-integration#linking-apns-with-fcm); After that you can use the FCM push notifications on the iOS/macOS in the same way as the Android/Web. It means that you should use the FCM configuration for making the subscription on the iOS/macOS, namely: ```dart CreateSubscriptionParameters parameters = CreateSubscriptionParameters(); if (Platform.isAndroid || kIsWeb) { parameters.channel = NotificationsChannels.GCM; parameters.platform = CubePlatform.ANDROID; } else if (Platform.isIOS || Platform.isMacOS) { parameters.channel = NotificationsChannels.GCM; parameters.platform = CubePlatform.IOS; } FirebaseMessaging firebaseMessaging = FirebaseMessaging.instance; var token = await firebaseMessaging.getToken(); parameters.pushToken = token; // and other required parameters for subscription on the push notifications ``` ## Send push notifications [Section titled “Send push notifications”](#send-push-notifications) You can manually initiate a push notification to user/users on any event in your application. To do so you need to form a push notification parameters (payload) and set the push recipients: ```dart bool isProduction = bool.fromEnvironment('dart.vm.product'); CreateEventParams params = CreateEventParams(); params.parameters = { 'message': "Some message in push", // 'message' field is required 'custom_parameter1': "custom parameter value 1", 'custom_parameter2': "custom parameter value 2", 'ios_voip': 1 // to send VoIP push notification to iOS //more standard parameters you can found by link https://developers.connectycube.com/server/push_notifications#universal-push-notifications }; params.notificationType = NotificationType.PUSH; params.environment = isProduction ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT; params.usersIds = [88707, 88708]; createEvent(params.getEventForRequest()) .then((cubeEvent) {}) .catchError((error) {}); ``` ## Receive push notifications [Section titled “Receive push notifications”](#receive-push-notifications) Depending on a devices state, incoming messages are handled differently. They will be received in one of the registered callbacks which you set during initialisation plugin according to [documentation](https://firebase.flutter.dev/docs/messaging/usage#handling-messages). ```dart FirebaseMessaging.onMessage.listen((RemoteMessage message) { log('[onMessage] message: $message', TAG); showNotification(message); }); FirebaseMessaging.onBackgroundMessage(onBackgroundMessage); Future onBackgroundMessage(RemoteMessage message) { log('[onBackgroundMessage] message: $message'); showNotification(message); return Future.value(); } ``` Here you can add an appropriate logic in your app. The things can be one of the following: * If this is a chat message, once clicked on it - we can redirect a user to an appropriate chat by **dialog\_id** data param * Raise a local notification with an alternative info to show for a user ## Unsubscribe [Section titled “Unsubscribe”](#unsubscribe) In order to unsubscribe and stop receiving push notifications you need to list your current subscriptions and then choose those to be deleted: ```dart getSubscriptions() .then((subscriptionsList) { int subscriptionIdToDelete = subscriptionsList[0].id; // or other subscription's id return deleteSubscription(subscriptionIdToDelete); }) .then((voidResult) {}) .catchError((error) {}); ``` ## VoIP push notifications [Section titled “VoIP push notifications”](#voip-push-notifications) ConnectyCube supports iOS VoIP push notifications via same API described above. The main use case for iOS VoIP push notifications is to show a native calling interface on incoming call when an app is in killed/background state - via CallKit. The common flow of using VoIP push notifications is the following: * for VoIP pushes it requires to generate a separated VoIP device token. The Flutter platform has a lot of plugins for getting the VoIP token from the system. There are: * [connectycube\_flutter\_call\_kit](https://pub.dev/packages/connectycube_flutter_call_kit); * [firebase\_messaging](https://pub.dev/packages/firebase_messaging); * [flutter\_voip\_push\_notification](https://pub.dev/packages/flutter_voip_push_notification); * etc. We will use our own development [connectycube\_flutter\_call\_kit](https://pub.dev/packages/connectycube_flutter_call_kit) in examples below. So, get the VoIP token: ```dart var token = await ConnectycubeFlutterCallKit.getToken(); ``` * then when token is retrieved, you need to subscribe to voip pushes by passing a `notification_channel: apns_voip` channel in a subscription request: ```dart var token = await ConnectycubeFlutterCallKit.getToken(); subscribe(String token) async { CreateSubscriptionParameters parameters = CreateSubscriptionParameters(); parameters.pushToken = token; bool isProduction = kReleaseMode ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT; if (Platform.isIOS) { parameters.channel = NotificationsChannels.APNS_VOIP; parameters.platform = CubePlatform.IOS; } String? deviceId = await PlatformDeviceId.getDeviceId; parameters.udid = deviceId; var packageInfo = await PackageInfo.fromPlatform(); parameters.bundleIdentifier = packageInfo.packageName; createSubscription(parameters.getRequestParameters()) .then((cubeSubscriptions) { log('[subscribe] subscription SUCCESS', TAG); }).catchError((error) { log('[subscribe] subscription ERROR: $error', TAG); }); } ``` * then when you want to send a voip push notification, use `ios_voip: 1` parameter in a push payload in a create event request: ```dart P2PSession callSession; // the call session created before CreateEventParams params = CreateEventParams(); params.parameters = { 'message': "Incoming ${callSession.callType == CallType.VIDEO_CALL ? "Video" : "Audio"} call", 'call_type': callSession.callType, 'session_id': callSession.sessionId, 'caller_id': callSession.callerId, 'caller_name': callerName, 'call_opponents': callSession.opponentsIds.join(','), 'signal_type': 'startCall', 'ios_voip': 1, }; params.notificationType = NotificationType.PUSH; params.environment = kReleaseMode ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT; params.usersIds = callSession.opponentsIds.toList(); createEvent(params.getEventForRequest()).then((cubeEvent) { // event was created }).catchError((error) { // something went wrong during event creation }); ```
# Whiteboard
> Enable dynamic collaboration in chat with ConnectyCube Whiteboard API. Ideal for remote meetings, teaching environments, sales demos, real-time workflows.
ConnectyCube **Whiteboard API** allows to create whiteboard functionality and associate it with a chat dialog. Chat dialog’s users can collaborate and draw simultaneously on a whiteboard. You can do freehand drawing with a number of tools, add shapes, lines, text and erase. To share boards, you just get an easy link which you can email. Your whiteboard stays safe in the cloud until you’re ready to return to it.  The most popular use cases for using the whiteboard: * Remote meetings * Remote teaching * Sales presentations * Workflows * Real-time collaboration ## Get started with SDK [Section titled “Get started with SDK”](#get-started-with-sdk) Follow the [Getting Started guide](/flutter/) on how to connect ConnectyCube SDK and start building your first app. ## Preparations [Section titled “Preparations”](#preparations) In order to start using whiteboard, an additional config has to be provided: ```dart CubeSettings.instance.whiteboardUrl = 'https://whiteboard.connectycube.com'; ``` Then, ConnectyCube whiteboard is associated with a chat dialog. In order to create a whiteboard, you need to have a chat dialog. Refer to [chat dialog creation API](/flutter/messaging#create-new-dialog). ## Create whiteboard [Section titled “Create whiteboard”](#create-whiteboard) When create a whiteboard you need to pass a name (any) and a chat dialog id to which whiteboard will be connected. ```dart CubeWhiteboard whiteboard = CubeWhiteboard() ..name = "New Whiteboard" ..chatDialogId = "5356c64ab35c12bd3b108a41"; createWhiteboard(whiteboard) .then((createdWhiteboard) { var wid = createdWhiteboard.whiteboardId; }) .catchError((onError) {}); ``` Once a whiteboard is created - a user can display it in the app in a WebView using the URL from the following method `whiteboard.getUrlForUser(username)`. For `username` any value can be provided but it should contain only Latin symbols. This is to display a text hint near the drawing arrow. You can build URL by yourself, for it use next template `https://whiteboard.connectycube.com?whiteboardid=&username=&title=` ## Retrieve whiteboards [Section titled “Retrieve whiteboards”](#retrieve-whiteboards) Use the following code snippet to retrieve a list of whiteboards associated with a chat dialog: ```dart var chatDialogId = "5356c64ab35c12bd3b108a41"; getWhiteboards(chatDialogId) .then((whiteboards) {}) .catchError((onError) {}); ``` ## Update whiteboard [Section titled “Update whiteboard”](#update-whiteboard) A whiteboard can be updated, e.g. its name: ```dart var whiteboardId = "5456c64ab35c17bd3b108a76"; var updatedName = 'Updated Name'; updateWhiteboard(whiteboardId, updatedName) .then((updatedWhiteboard) {}) .catchError((onError) {}); ``` ## Delete whiteboard [Section titled “Delete whiteboard”](#delete-whiteboard) ```dart var whiteboardId = "5456c64ab35c17bd3b108a76";; deleteWhiteboard(whiteboardId) .then((voidResult) {}) .catchError((onError) {}); ```
# Send first chat message
> Step-by-step guide to sending your first chat message using ConnectyCube Cordova 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](/cordova/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) Сonnect SDK js file as a normal script: ```html ``` ### Initialize SDK [Section titled “Initialize SDK”](#initialize-sdk) Initialize the SDK 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" }; // const userCredentials = { email: 'awesomeman@gmail.com', password: 'supersecurepwd' }; // const userCredentials = { provider: 'facebook', keys: {token: 'a876as7db...asg34dasd8wqe'} }; ConnectyCube.createSession(userCredentials) .then((user) => {}) .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 is essential because it 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 messages [Section titled “Step 5: Send / Receive messages”](#step-5-send--receive-messages) Once the 1-1 chat is set up, you can use it to exchange messages seamlessly. This code example demonstrates how to send and receive messages in the created 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](/cordova/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 Android 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](/cordova/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, 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) Сonnect SDK js file as a normal script: ```html ``` ### Initialize SDK [Section titled “Initialize SDK”](#initialize-sdk) Initialize the SDK 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" }; // const userCredentials = { email: 'awesomeman@gmail.com', password: 'supersecurepwd' }; // const userCredentials = { provider: 'facebook', keys: {token: 'a876as7db...asg34dasd8wqe'} }; ConnectyCube.createSession(userCredentials) .then((user) => {}) .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: Connect WebRTC lib [Section titled “Step 4: Connect WebRTC lib”](#step-4-connect-webrtc-lib) For Android - there is nothing extra required. Android Cordova WebView supports all the WebRTC API stack. For iOS - a [cordova-plugin-iosrtc](https://github.com/cordova-rtc/cordova-plugin-iosrtc) has to be used. ## Step 5: Create video session [Section titled “Step 5: Create video session”](#step-5-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): ```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 6: Access local media stream [Section titled “Step 6: Access local media stream”](#step-6-access-local-media-stream) 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. For more information about possible audio/video constraints, here is a good code sample from WebRTC team how to work with getUserMedia constraints: ## Step 7: Initiate call [Section titled “Step 7: Initiate call”](#step-7-initiate-call) Initiate Call function sets up the foundational connection between the caller and the selected opponents: ```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 8: Accept a call [Section titled “Step 8: Accept a call”](#step-8-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 and hearing 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](/cordova/videocalling) * [Conference calling SDK documentation](/cordova/videocalling-conference)
# Chat
> Integrate powerful chat functionality into your Cordova 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.  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. ## 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"; ConnectyCube.chat.dialog .getPublicOccupants(dialogId) .then((result) => { // result.items }) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.getPublicOccupants(dialogId)`: ```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 = ""; // 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](/cordova/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  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 application offline event with ping check [Section titled “Handle application offline event with ping check”](#handle-application-offline-event-with-ping-check) You can use the [cordova-plugin-network-information](https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-network-information/index.html) plugin to detect network changes in a Cordova. Listen for the Cordova’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 Cordova applications with our 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](/cordova/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](/cordova/messaging#connect-to-chat). ### Connect WebRTC lib [Section titled “Connect WebRTC lib”](#connect-webrtc-lib) For Android - there is nothing extra required. Android Cordova WebView supports all the WebRTC API stack. For iOS - a [cordova-plugin-iosrtc](https://github.com/cordova-rtc/cordova-plugin-iosrtc) has to be used. ## 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): ```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. ## 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: Cordova](/cordova/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 sesison 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: 'android' }; 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) Not available as for now. ## 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](/cordova/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) > **Note**: The recording feature may work at Android environment only as for now. 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:  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 Cordova 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. ### Connect WebRTC lib [Section titled “Connect WebRTC lib”](#connect-webrtc-lib) For Android - there is nothing extra required. Android Cordova WebView supports all the WebRTC API stack. For iOS - a [cordova-plugin-iosrtc](https://github.com/cordova-rtc/cordova-plugin-iosrtc) has to be used. ### 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: ```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: ## 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, }); ``` ## Join room [Section titled “Join room”](#join-room) To jump into video chat with users you should join it: ```javascript session .join(roomId, userId, userDisplayName) .then(() => {}) .catch((error) => {}); ``` To check current joined video room use the following property: ```javascript const roomId = 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) => {}; ``` ## 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) Not available as for now ## 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:  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 Cordova 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](/cordova/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: 
# Authentication and Users
> Simplify user authentication in your Cordova app with our definitive API guide. Fortify your app's defenses and protect user data effectively.
Every user has to authenticate with ConnectyCube before using any ConnectyCube functionality. When someone connects with an application using ConnectyCube, the application will need to obtain a session token which provides temporary, secure access to ConnectyCube APIs. A session token is an opaque string that identifies a user and an application. ## Create session token [Section titled “Create session token”](#create-session-token) As a starting point, the user’s session token needs to be created allowing user any further actions within the app. Pass login/email and password to identify a user: ```javascript const userCredentials = { login: "cubeuser", password: "awesomepwd" }; 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:** 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). ### Authentication via phone number [Section titled “Authentication via phone number”](#authentication-via-phone-number) Sign In with phone number is supported with ([Firebase integration](https://firebase.google.com/docs/auth/web/phone-auth)). You need to create Firebase `project_id` and obtain Firebase `access_token` after SMS code verification, then pass these parameters to `login` method: ```javascript const userCredentials = { provider: "firebase_phone", "firebase_phone[project_id]": "...", "firebase_phone[access_token]": "...", }; ConnectyCube.createSession(userCredentials) .then((user) => {}) .catch((error) => {}); ``` > **Note** > > in order to login via phone number you need to create a session token first. ### Authentication via Firebase email [Section titled “Authentication via Firebase email”](#authentication-via-firebase-email) Sign In with email is supported with ([Firebase integration](https://firebase.google.com/docs/auth/web/password-auth)). You need to create Firebase `project_id` and obtain Firebase `access_token` after email/password verification, then pass these parameters to `login` method: ```javascript const userCredentials = { provider: 'firebase_email', firebase_email: { project_id: 'XXXXXXXXXXX', access_token: 'XXXXXXXXXXXYYYYYY' } }; ConnectyCube.createSession(userCredentials) .then((user) => {}) .catch((error) => {}); ``` > **Note** > > in order to login via email you need to create a session token first. ### Authentication via external identity provider [Section titled “Authentication via external identity provider”](#authentication-via-external-identity-provider) **Custom Identity Provider (CIdP)** feature is necessary if you have your own user database and want to authenticate users in ConnectyCube against it. It works the same way as Facebook/Twitter SSO. With Custom Identity Provider feature you can continue use your user database instead of storing/copying user data to ConnectyCube database. #### CIdP high level integration flow [Section titled “CIdP high level integration flow”](#cidp-high-level-integration-flow) To get started with **CIdP** integration, check the [Custom Identity Provider guide](/guides/custom-identity-provider) which describes high level integration flow. #### How to login via CIdP [Section titled “How to login via CIdP”](#how-to-login-via-cidp) Once you done with the setup mapping in ConnectyCube Dashboard, it’s time to verify the integration. To perform CIdP login, the same ConnectyCube [User Login API](/cordova/authentication-and-users/#upgrade-session-token-user-login) is used. You just use existing login request params to pass your external user token: ```javascript const userCredentials = { login: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzNDU2Nzg5LCJuYW1lIjoiSm9zZXBoIn0.OpOSSw7e485LOP5PrzScxHb7SR6sAOMRckfFwi4rp7o" }; ConnectyCube.createSession(userCredentials) .then((user) => {}) .catch((error) => {}); ``` Once the login is successful, ConnectyCube will create an underalying User entity, so then you can use ConnectyCube APIs in a same way as you do with a normal login. With CIdP we do not have/store any user password in ConnectyCube User entity. Following further integration, you may need to connect to Chat. In a case of CIdP login, you do not have a user password. In such cases you should use ConnectyCube session token as a password for chat connection. [Follow the Connect to Chat with CIdP guide](/cordova/messaging/#connect-to-chat-using-custom-authentication-providers). ### Create guest session [Section titled “Create guest session”](#create-guest-session) To create a session with guest user use the following code: ```javascript const guestUserCredentials = { guest: '1', full_name: 'Awesome Smith' }; ConnectyCube.createSession(guestUserCredentials) .then((session) => {}) .catch((error) => {}); ``` ## Session expiration [Section titled “Session expiration”](#session-expiration) Expiration time for session token is 2 hours after last request to API. If you perform query with expired token, you will receive the error **Required session does not exist**. In this case you need to recreate a session token. There is a special callback function to handle this case: ```javascript const CONFIG = { on: { sessionExpired: (handleResponse, retry) => { // call handleResponse() if you do not want to process a session expiration, // so an error will be returned to origin request // handleResponse(); ConnectyCube.createSession() .then(retry) .catch((error) => {}); }, }, }; ConnectyCube.init(CREDENTIALS, CONFIG); ``` ## Destroy session token [Section titled “Destroy session token”](#destroy-session-token) To destroy a session use the following code: ```javascript ConnectyCube.destroySession().catch((error) => {}); ``` ## User signup [Section titled “User signup”](#user-signup) ```javascript const userProfile = { login: "marvin18", password: "supersecurepwd", email: "awesomeman@gmail.com", full_name: "Marvin Simon", phone: "47802323143", website: "https://dozensofdreams.com", tag_list: ["iphone", "apple"], custom_data: JSON.stringify({ middle_name: "Bartoleo" }), }; ConnectyCube.users .signup(userProfile) .then((user) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.users.signup(userProfile)`: ```json { "id": 81, "full_name": "Marvin Simon", "email": "awesomeman@gmail.com", "login": "marvin18", "phone": "47802323143", "website": "https://dozensofdreams.com", "created_at": "2018-06-15T14:20:54Z", "updated_at": "2018-12-05T11:58:02Z", "last_request_at": "2018-12-05T11:58:02Z", "external_user_id": null, "facebook_id": null, "twitter_id": null, "custom_data": "{\"middle_name\":\"Bartoleo\"}", "blob_id": null, "avatar": "", "user_tags": "iphone,apple" } ``` Only login (or email) + password are required. ## User profile update [Section titled “User profile update”](#user-profile-update) ```javascript const updatedUserProfile = { login: "marvin18sim", full_name: "Mar Sim", }; ConnectyCube.users .update(updatedUserProfile) .then((user) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.users.update(updatedUserProfile)`: ```json { "id": 81, "full_name": "Mar Sim", "email": "awesomeman@gmail.com", "login": "marv18sim", "phone": "47802323143", "website": "https://dozensofdreams.com", "created_at": "2018-06-15T14:20:54Z", "updated_at": "2018-12-05T11:58:02Z", "last_request_at": "2018-12-05T11:58:02Z", "external_user_id": null, "facebook_id": null, "twitter_id": null, "custom_data": "{\"middle_name\": \"Bartoleo\"}", "blob_id": null, "avatar": "", "user_tags": "iphone,apple" } ``` If you want to change your password, you need to provide 2 parameters: `password` and `old_password`. Updated `user` entity will be returned. ## User avatar [Section titled “User avatar”](#user-avatar) You can set a user’s avatar. You just need to upload it to the ConnectyCube cloud storage and then connect to user. ```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 updateUser = (uploadedFile) => { const updatedUserProfile = { avatar: uploadedFile.uid }; return ConnectyCube.users.update(updatedUserProfile); }; ConnectyCube.storage .createAndUpload(fileParams) .then(updateUser) .then((updatedUser) => {}) .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" } ``` Now, other users can get you avatar: ```javascript const avatarUID = updatedUser.avatar; const avatarURL = ConnectyCube.storage.privateUrl(avatarUID); const avatarHTML = ""; ``` ## Password reset [Section titled “Password reset”](#password-reset) It’s possible to reset a password via email: ```javascript ConnectyCube.users .resetPassword("awesomeman@gmail.com") .then((result) => {}) .catch((error) => {}); ``` If provided email is valid - an email with password reset instruction will be sent to it. ## Retrieve users V2 [Section titled “Retrieve users V2”](#retrieve-users-v2) ### Examples [Section titled “Examples”](#examples) Retrieve users by ID ```javascript const searchParams = { limit: 10, offset: 50, id: { in: [51941, 51946] } } ConnectyCube.users.getV2(searchParams) .then((result) => {}) .catch((error) => {}); ``` Retrieve users by login ```javascript const searchParams = { login: 'adminFirstUser' } ConnectyCube.users.getV2(searchParams) .then((result) => {}) .catch((error) => {}); ``` Retrieve users by last\_request\_at ```javascript const date = new Date(2017, 10, 10) const searchParams = { last_request_at: { gt: date } } ConnectyCube.users.getV2(searchParams) .then((result) => {}) .catch((error) => {}); ``` More information (fields, operators, request rules) available [here](/server/users#retrieve-users-v2) ## Retrieve users V1 (**Deprecated**) [Section titled “Retrieve users V1 (Deprecated)”](#retrieve-users-v1-deprecated) ### Retrieve users by ID (**Deprecated**) [Section titled “Retrieve users by ID (Deprecated)”](#retrieve-users-by-id-deprecated) ```javascript const params = { page: 1, per_page: 5, filter: { field: "id", param: "in", value: [51941, 51946], }, }; ConnectyCube.users .get(params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.users.get(params)` - [see](/server/users#response-1) ### Retrieve user by login (**Deprecated**) [Section titled “Retrieve user by login (Deprecated)”](#retrieve-user-by-login-deprecated) ```javascript const searchParams = { login: "marvin18" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve user by email (**Deprecated**) [Section titled “Retrieve user by email (Deprecated)”](#retrieve-user-by-email-deprecated) ```javascript const searchParams = { email: "marvin18@example.com" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve users by full name (**Deprecated**) [Section titled “Retrieve users by full name (Deprecated)”](#retrieve-users-by-full-name-deprecated) ```javascript const searchParams = { full_name: "Marvin Samuel" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve user by phone number (**Deprecated**) [Section titled “Retrieve user by phone number (Deprecated)”](#retrieve-user-by-phone-number-deprecated) ```javascript const searchParams = { phone: "44678162873" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve user by external ID (**Deprecated**) [Section titled “Retrieve user by external ID (Deprecated)”](#retrieve-user-by-external-id-deprecated) ```javascript const searchParams = { external_user_id: "675373912" }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ### Retrieve users by tags (**Deprecated**) [Section titled “Retrieve users by tags (Deprecated)”](#retrieve-users-by-tags-deprecated) ```javascript const searchParams = { tags: ["apple"] }; ConnectyCube.users .get(searchParams) .then((result) => {}) .catch((error) => {}); ``` ## Delete user [Section titled “Delete user”](#delete-user) A user can delete himself from the platform: ```javascript ConnectyCube.users .delete() .then((result) => {}) .catch((error) => {}); ```
# Custom Data
> Maximize your Cordova app's potential with customizable data cloud storage solutions. Tailor data structures to match your application's unique requirements.
Custom Data, also known as cloud tables or custom objects, provide users with the flexibility to define and manage data in a way that is specific to their application’s requirements. Here are some common reasons why you might need use custom data in your app: * Custom Data allows you to define data structures that align precisely with your application’s needs. This is particularly useful when dealing with complex or unique data types that don’t fit well into standard ConnectyCube models. * In certain applications, there may be entities or objects that are unique to that particular use case. Custom Data enable the representation of these entities in the database, ensuring that the data storage is optimized for the application’s logic. * Custom Data allows you to extend the functionality of the app by introducing new types of data that go beyond what the ConnectyCube platform’s standard models support. Custom Data empower you to introduce these extensions and additional features. * In situations where data needs to be migrated from an existing system or transformed in a specific way, Custom Data offer the flexibility to accommodate these requirements. * Your application needs to integrate with external systems or APIs, Custom Data can be designed to align seamlessly with the data structures of your external systems. ## Get started with SDK [Section titled “Get started with SDK”](#get-started-with-sdk) Follow the [Getting Started guide](/js/) on how to connect ConnectyCube SDK and start building your first app. ## Preparations [Section titled “Preparations”](#preparations) In order to start using Custom Data you need first create the data scheme in the ConnectyCube Admin panel. For it navigate to **`Home -> Your App -> Custom -> List`** then click on **`ADD`** and from the dropdown menu select **`ADD NEW CLASS`**. In the opened dialog, enter your model name and add the necessary fields. The ConnectyCube Custom Data models’ fields support various data types or arrays (except `Location`). These include: * Integer (e.g. `8222456`); * Float (e.g. `1.25`); * Boolean (`true` or `false`); * String (e.g. `"some text"`); * Location (the array what consist of **two** `numbers`s); After adding all the required fields, click the **`CREATE CLASS`** button to save your scheme.  Newly created class is now available and contains the following data: * **\_id** - record identifier generated by system automatically * **user\_id** - identifier of user who created a record * **\_parent\_id** - by default is null * **field\_1** - field defined in a scheme * **field\_2** - field defined in a scheme * … * **field\_N** - field defined in a scheme * **created\_at** - date and time when a record is created  After that you can perform all **CRUD** operations with your Custom Data. > **Note**: The **Class name** field will be represented as the DB table name and will be used for identification of your requests during the work with Custom Data. ## Permissions [Section titled “Permissions”](#permissions) Access control list (ACL) is a list of permissions attached to some object. An ACL specifies which users have an access to objects, as well as what operations are allowed with such objects. Each entry in a typical ACL specifies a subject and an operation. ACL models may be applied to collections of objects as well as to individual entities within the system’s hierarchy. Adding the Access Control list is only available within the Custom Data module. ### Permissions scheme [Section titled “Permissions scheme”](#permissions-scheme) ConnectyCube permission scheme contains five permissions levels: * **Open** (open) - any user within the application can access the record(s) in the class and perform actions with the record * **Owner** (owner) - only the Owner (the user who created a record) is authorized to perform actions with the record * **Not allowed** (not\_allowed) - no one (except the Account Administrator) can proceed with a chosen action * **Open for groups** (open\_for\_groups) - users with the specified tag(s) will be included in the group that is authorized to perform actions with a record. Multiple groups can be specified (number of groups is not limited). * **Open for users ids** (open\_for\_users\_ids) - only users with listed IDs can perform actions with a record. Actions for work with an entity: * **Create** - create a new record * **Read** - retrieve information about a record and view it in the read-only mode * **Update** - update any parameter of the chosen record that can be updated by user * **Delete** - delete a record To set a permission schema for the Class, go to ConnectyCube dashboard and find a required class within Custom Data module Click on **`EDIT PERMISSION`** button to open permissions schema to edit. Each listed action has a separate permission level to select. The exception is a ‘Create’ action that isn’t available for ‘Owner’ permission level.  ### Permission levels [Section titled “Permission levels”](#permission-levels) Two access levels are available in the ConnectyCube: access to Class and access to Record. Only one permission schema can be applied for the record. Using the Class permission schema means that the Record permission schema won’t be affected on a reсord. **Class entity** **Class** is an entity that contains records. Class can be created via ConnectyCube dashboard only within Custom data module. Operations with Class entity are not allowed in API. All actions (Create, Read, Update and Delete) that are available for the ‘Class’ entity are also applicable for all records within a class. Default Class permission schema is using during the creation of a class: * **Create:** Open * **Read:** Open * **Update:** Owner * **Delete:** Owner To enable applying Class permissions for any of the action types, ‘Use Class permissions’ check box should be ticked. It means that the record permission schema (if existing) won’t be affected on a record. **Record entity** **Record** is an entity within the Class in the Custom Data module that can be created in ConnectyCube dashboard and via API. Each record within a Class has its own permission level. Unlike Class entity, ‘Not allowed’ permission level isn’t available for a record as well as only three actions are available to work with a record - read, update and delete. Default values for Record permission scheme: * Read: Open * Update: Owner * Delete: Owner To set a separate permission scheme for a record, open a record to edit and click on **`SET PERMISSION ON RECORD`** button:  Define the permission level for each of available actions. ## Create a new record [Section titled “Create a new record”](#create-a-new-record) Create a new record with the defined parameters in the class. Fields that weren’t defined in the request but are available in the scheme (class) will have null values. ```javascript const className = 'call_history_item'; const record = { 'call_name': 'Group call', 'call_participants': [2325293, 563541, 563543], 'call_start_time': 1701789791673, 'call_end_time': 0, 'call_duration': 0, 'call_state': 'accepted', 'is_group_call': true, 'call_id': 'f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454', 'user_id': 2325293, 'caller_location': [50.004444, 36.234380] } try { const result = await ConnectyCube.data.create(className, record); console.log(result) } catch (error) { console.error(error) } ``` ### Create a record with permissions [Section titled “Create a record with permissions”](#create-a-record-with-permissions) To create a new record with permissions, add the `permissions` parameter to a record. In this case, the request will look like this: ```javascript const className = 'call_history_item'; const record = { call_name: 'Group call', call_participants: [2325293, 563541, 563543], call_start_time: 1701789791673, call_end_time: 0, call_duration: 0, call_state: 'accepted', is_group_call: true, call_id: 'f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454', user_id: 2325293, caller_location: [50.004444, 36.234380], permissions: { read: { access: 'owner' }, update: { access: 'open_for_users_ids', ids: [563541, 563543] }, delete: { access: 'open_for_groups', groups: ['group87', 'group108'] } } } try { const result = await ConnectyCube.data.create(className, record); console.log(result) } catch (error) { console.error(error) } ``` ## Create multi records [Section titled “Create multi records”](#create-multi-records) Create several new records in the class. Fields that weren’t defined in the request but available in the scheme would have null values. ```javascript const className = 'call_history_item'; const record_1 = { 'call_name': 'Group call 1', 'call_participants': [2325293, 563541, 563543], 'call_start_time': 1701789791673, 'call_end_time': 0, 'call_duration': 0, 'call_state': 'accepted', 'is_group_call': true, 'call_id': 'f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454', 'user_id': 2325293, 'caller_location': [50.004444, 36.234380] } const record_2 = { 'call_name': 'Group call 2', 'call_participants': [2325293, 563541, 563543], 'call_start_time': 1701789832955, 'call_end_time': 0, 'call_duration': 0, 'call_state': 'accepted', 'is_group_call': true, 'call_id': 'a2a7bc3f-2eeb-3d72-11b0-566d3c4c2748', 'user_id': 2325293, 'caller_location': [50.004444, 36.234380] } const records = [record_1, record_2] try { const result = await ConnectyCube.data.create(className, records); console.log(result.items) } catch (error) { console.error(error) } ``` ## Retrieve record by ID [Section titled “Retrieve record by ID”](#retrieve-record-by-id) Retrieve the record by specifying its identifier. ```javascript const className = 'call_history_item'; const id = '656f407e29d6c5002fce89dc'; try { const result = await ConnectyCube.data.list(className, id); console.log(result.items) } catch (error) { console.error(error) } ``` ## Retrieve records by IDs [Section titled “Retrieve records by IDs”](#retrieve-records-by-ids) Retrieve records by specifying their identifiers. ```javascript const className = 'call_history_item'; const ids = ['656f407e29d6c5002fce89dc', '5f985984ca8bf43530e81233']; // or `const ids = '656f407e29d6c5002fce89dc,5f985984ca8bf43530e81233'`; try { const result = await ConnectyCube.data.list(className, ids); console.log(result.items) } catch (error) { console.error(error) } ``` ## Retrieve records within a class [Section titled “Retrieve records within a class”](#retrieve-records-within-a-class) Search records within the particular class. > **Note:** If you are sorting records by time, use the `_id` field. It is indexed and it will be much faster than sorting by `created_at` field. The list of additional parameter for sorting, filtering, aggregation of the search response is provided by link ```javascript const className = 'call_history_item'; const filters = { call_start_time: { gt: 1701789791673 } }; try { const result = await ConnectyCube.data.list(className, filter); console.log(result.items) } catch (error) { console.error(error) } ``` ## Retrieve the record’s permissions [Section titled “Retrieve the record’s permissions”](#retrieve-the-records-permissions) > **Note:** record permissions are checked during request processing. Only the owner has an ability to view a record’s permissions. ```javascript const className = 'call_history_item'; const id = '656f407e29d6c5002fce89dc'; try { const result = await ConnectyCube.data.readPermissions(className, id); console.log(result.permissions) console.log(result.record_is) } catch (error) { console.error(error) } ``` ## Update record by ID [Section titled “Update record by ID”](#update-record-by-id) Update record data by specifying its ID. ```javascript const className = 'call_history_item'; const params = { id: '656f407e29d6c5002fce89dc', call_end_time: 1701945033120, }; try { const result = await ConnectyCube.data.update(className, params); console.log(result) } catch (error) { console.error(error) } ``` ## Update records by criteria [Section titled “Update records by criteria”](#update-records-by-criteria) Update records found by the specified search criteria with a new parameter(s). The structure of the parameter `search_criteria` and the list of available operators provided by link ```javascript const className = 'call_history_item'; const params = { search_criteria: { user_id: 1234567 }, call_name: 'Deleted user' }; try { const result = await ConnectyCube.data.update(className, params); console.log(result.items) } catch (error) { console.error(error) } ``` ## Update multi records [Section titled “Update multi records”](#update-multi-records) Update several records within a class by specifying new values. ```javascript const className = 'call_history_item'; const params = [{ id: '656f407e29d6c5002fce89dc', call_end_time: 1701945033120, }, { id: '5f998d3bca8bf4140543f79a', call_end_time: 1701945033120, }]; try { const result = await ConnectyCube.data.update(className, params); console.log(result.items) } catch (error) { console.error(error) } ``` ## Delete record by ID [Section titled “Delete record by ID”](#delete-record-by-id) Delete a record from a class by record identifier. ```javascript const className = 'call_history_item'; const id = '5f998d3bca8bf4140543f79a'; try { await ConnectyCube.data.delete(className, id); } catch (error) { console.error(error) } ``` ## Delete several records by their IDs [Section titled “Delete several records by their IDs”](#delete-several-records-by-their-ids) Delete several records from a class by specifying their identifiers. If one or more records can not be deleted, an appropriate error is returned for that record(s). ```javascript const className = 'call_history_item'; const ids = ['656f407e29d6c5002fce89dc', '5f998d3bca8bf4140543f79a']; try { const result = await ConnectyCube.data.delete(className, id); console.log(result) } catch (error) { console.error(error) } ``` ## Delete records by criteria [Section titled “Delete records by criteria”](#delete-records-by-criteria) Delete records from the class by specifying a criteria to find records to delete. The search query format is provided by link ```javascript const className = 'call_history_item'; const params = { call_id: { in: ['f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454'] } }; try { const result = await ConnectyCube.data.delete(className, params); console.log(result) } catch (error) { console.error(error) } ``` ## Relations [Section titled “Relations”](#relations) Objects (records) in different classes can be linked with a `parentId` field. If the record from the **Class 1** is pointed with a record from **Class 2** via its `parentId`, the `parentId` field will contain an ID of a record from the **Class 2**. If a record from the **Class 2** is deleted, all its children (records of the **Class 1** with `parentId` field set to the **Class 2** record ID) will be automatically deleted as well. The linked children can be retrieved with `_parent_id={id_of_parent_class_record}` parameter.
# Push Notifications
> Elevate your Cordova app's performance with push notifications API guide. Keep users engaged with real-time updates, ensuring seamless interaction on the go.
Push Notifications provide a way to deliver some information to users while they are not using your app actively. The following use cases can be covered additionally with push notifications: * send a chat message when a recipient is offline (a push notification will be initiated automatically in this case) * make a video call with offline opponents (need to send a push notification manually) ## Configuration [Section titled “Configuration”](#configuration) In order to start work with push notifications you need to configure it. There are various client-side libs for Cordova push notifications: * [cordova-plugin-firebasex](https://github.com/dpa99c/cordova-plugin-firebasex) * [cordova-plugin-firebase](https://github.com/arnesson/cordova-plugin-firebase) This guide is based on **cordova-plugin-firebasex** lib. First of all we need to install it. Just follow the [cordova-plugin-firebasex installation](https://github.com/dpa99c/cordova-plugin-firebasex#installation) guide in the lib’s README. Then make sure you followed [cordova-plugin-firebasex Firebase config setup](https://github.com/dpa99c/cordova-plugin-firebasex#firebase-config-setup) Then follow the platform specific steps. ### iOS [Section titled “iOS”](#ios) 1. First of all you need to generate Apple push certificate (\*.p12 file) and upload it to ConnectyCube dashboard. Here is a guide on how to create a certificate 2. Upload Apple push certificate (\*.p12 file) to ConnectyCube dashboard: * Open your ConnectyCube Dashboard at [admin.connectycube.com](https://admin.connectycube.com) * Go to **Push notifications** module, **Credentials** page * Upload the newly created APNS certificate on **Apple Push Notification Service (APNS)** form.  3. Lastly, open Xcode project of your Flutter app and enable Push Notifications capabilities. Open Xcode, choose your project file, Signing & Capabilities tab and then add a Push Notifications capability. Also - tick a ‘Remote notifications’ checkbox in Background Modes section.  ### Android [Section titled “Android”](#android) #### Configure Firebase project and Service account key (recommended) [Section titled “Configure Firebase project and Service account key (recommended)”](#configure-firebase-project-and-service-account-key-recommended) In order to start working with push notifications functionality you need to configure it. 1. Create and configure your [Firebase project](https://console.firebase.google.com) and obtain the **Service account key**. If you have any difficulties with Firebase project registration, [follow our guide](/android/firebase-setup-guide). To find your **FCM service account key** go to your **Firebase console > Cloud Messaging > Manage Service Accounts** section:  2. Select and configure **Manage Keys** option:  3. Select **ADD KEY**, **Create new key**:  4. Select **Key type** (json recommended) and create:  5. Save it locally:  6. Browse your saved **FCM Service account key** in your **Dashboard > Your App > Push Notifications > Credentials**, select the environment for which you are adding the key. Use the same key for development and production zones.  7. Copy **Sender ID** value from your Firebase console **Cloud Messaging** section. You may require it later.  8. In order to use push notifications on Android, you need to create `google-services.json` file and copy it into project’s `android/app` folder. Also, you need to update the `applicationId` in `android/app/build.gradle` to the one which is specified in `google-services.json`, so they must match. If you have no existing API project yet, the easiest way to go about in creating one is using this [step-by-step installation process](https://firebase.google.com/docs/android/setup) #### Configure Firebase project and Server key (DEPRECATED) [Section titled “Configure Firebase project and Server key (DEPRECATED)”](#configure-firebase-project-and-server-key-deprecated) 1. Create and configure your [Firebase project](https://console.firebase.google.com) and obtain the **Server key**. If you have any difficulties with Firebase project registration, [follow our guide](/android/firebase-setup-guide). To find your **FCM server key** go to your **Firebase console > Cloud Messaging** section:  2. Copy your **FCM server key** to your **Dashboard > Your App > Push Notifications > Credentials**, select the environment for which you are adding the key and hit **Save key**. Use the same key for development and production zones.  3. Copy **Sender ID** value from your Firebase console **Cloud Messaging** section. You may require it later.  4. In order to use push notifications on Android, you need to create `google-services.json` file and copy it into project’s `android/app` folder. Also, you need to update the `applicationId` in `android/app/build.gradle` to the one which is specified in `google-services.json`, so they must match. If you have no existing API project yet, the easiest way to go about in creating one is using this [step-by-step installation process](https://firebase.google.com/docs/android/setup) ## cordova-plugin-firebasex API [Section titled “cordova-plugin-firebasex API”](#cordova-plugin-firebasex-api) The list of available methods for the **cordova-plugin-firebasex** plugin is described here . It’s useful to have a quick overview before further integration. ## Init cordova-plugin-firebasex lib [Section titled “Init cordova-plugin-firebasex lib”](#init-cordova-plugin-firebasex-lib) Next step is to initialize the **cordova-plugin-firebasex** lib. The following code requests permissions for iOS and then does the initialization of main functions: ```javascript document.addEventListener('deviceready', () => { if (typeof device !== 'undefined') { if (device.platform === 'iOS') { try { FirebasePlugin.grantPermission(hasPermission => { console.log("Permission was " + (hasPermission ? "granted" : "denied")); }); firebaseSetup(); } catch (e) { console.error("FirebasePlugin grantPermission error", e); } } else { firebaseSetup(); } } }) ... firebaseSetup() { const isOniOS = return typeof device !== "undefined" && !!device.platform && device.platform.toUpperCase() === "IOS"; // We use FCM only for Android. For iOS - we use plain APNS if (!isOniOS) { FirebasePlugin.onTokenRefresh(fcmToken => { console.log(fcmToken); }, error => { console.error(error); }); } // iOS device token FirebasePlugin.onApnsTokenReceived(apnsToken => { console.log(apnsToken); }, error => { console.error(error); }); FirebasePlugin.onMessageReceived(message => { console.log(message); }, error => { console.error(error); }); } ``` ## Subscribe to push notifications [Section titled “Subscribe to push notifications”](#subscribe-to-push-notifications) In order to start receiving push notifications you need to subscribe your current device as follows: ```javascript const isOniOS = return typeof device !== "undefined" && !!device.platform && device.platform.toUpperCase() === "IOS"; // We use FCM only for Android. For iOS - we use plain APNS if (!isOniOS) { FirebasePlugin.onTokenRefresh(fcmToken => { if (token && token.trim().length > 0) { this.subscribeToPushNotification(fcmToken); } }, error => { console.error(error); }); } // iOS device token FirebasePlugin.onApnsTokenReceived(apnsToken => { if (token && token.trim().length > 0) { this.subscribeToPushNotification(apnsToken); } }, error => { console.error(error); }); ... subscribeToPushNotification(deviceToken) { const isOniOS = return typeof device !== "undefined" && !!device.platform && device.platform.toUpperCase() === "IOS"; const platform = isOniOS ? "ios" : "android"; // https://stackoverflow.com/questions/51585819/how-to-detect-environment-in-a-cordova-android-application let IS_DEV_ENV; cordova.plugins.IsDebug.getIsDebug(isDebug => { IS_DEV_ENV = isDebug; }, err => { console.error("cordova.plugins.IsDebug error", err); }); const params = { // for iOS VoIP it should be 'apns_voip' notification_channel: isOniOS ? 'apns' : 'gcm', device: { platform: platform, udid: device.uuid }, push_token: { environment: IS_DEV_ENV ? 'development' : 'production', client_identification_sequence: deviceToken, bundle_identifier: "com.your.app.package.id" } } ConnectyCube.pushnotifications.subscriptions.create(params) .then(result => {}) .catch(error => {}); } ``` ## Send push notifications [Section titled “Send push notifications”](#send-push-notifications) You can manually initiate a push notification to user/users on any event in your application. To do so you need to form a push notification parameters (payload) and set the push recipients: ```javascript const payload = JSON.stringify({ message: "Alice is calling you", ios_badge: 1, // ios_voip: 1 }); // https://stackoverflow.com/questions/51585819/how-to-detect-environment-in-a-cordova-android-application let IS_DEV_ENV; cordova.plugins.IsDebug.getIsDebug( (isDebug) => { IS_DEV_ENV = isDebug; }, (err) => { console.error("cordova.plugins.IsDebug error", err); } ); const pushParameters = { notification_type: "push", user: { ids: [21, 12] }, // recipients. environment: IS_DEV_ENV ? "development" : "production", message: ConnectyCube.pushnotifications.base64Encode(payload), }; ConnectyCube.pushnotifications.events .create(pushParameters) .then((result) => {}) .catch((error) => {}); ``` Please refer [Universal Push Notifications standard parameters](/server/push_notifications#universal-push-notifications) section on how to form the payload. ## Receive push notifications [Section titled “Receive push notifications”](#receive-push-notifications) When any notification is opened or received the callback `onMessageReceived` is called passing an object with the notification data: ```javascript FirebasePlugin.onMessageReceived( (message) => { console.dir(message); }, (error) => {} ); ``` You can check more details about a `message` object fields Here you can add an appropriate logic in your app. The things can be one of the following: * If this is a chat message, once clicked on it - we can redirect a user to an appropriate chat by **dialog\_id** data param * Raise a local notification with an alternative info to show for a user ## Unsubscribe from push notifications [Section titled “Unsubscribe from push notifications”](#unsubscribe-from-push-notifications) In order to unsubscribe and stop receiving push notifications you need to list your current subscriptions and then choose those to be deleted: ```javascript FirebasePlugin.unregister(); const deleteSubscription = (subscriptions) => { let subscriptionIdToDelete; subscriptions.forEach((sbs) => { if (sbs.subscription.device.platform === "ios" && sbs.subscription.device.udid === device.uuid) { subscriptionIdToDelete = sbs.subscription.id; } }); if (subscriptionIdToDelete) { ConnectyCube.pushnotifications.subscriptions.delete(subscriptionIdToDelete); } }; ConnectyCube.pushnotifications.subscriptions .list() .then(deleteSubscription) .catch((error) => {}); ``` ## CallKit and VoIP push notifications [Section titled “CallKit and VoIP push notifications”](#callkit-and-voip-push-notifications) In a case you need to show a native calling interface on incoming call - you need to integrate a CallKit functionality via the following libs - [CordovaCall](https://github.com/WebsiteBeaver/CordovaCall) or [cordova-plugin-callkit](https://github.com/mattkhaw/cordova-plugin-callkit) For iOS, this will also require to integrate VoIP push notifications along with it. A [cordova-ios-voip-push](https://github.com/Hitman666/cordova-ios-voip-push) can be used for it. ConnectyCube supports iOS VoIP push notifications via same API described above: * for VoIP pushes it requires to generate a separated VoIP device token. * then when token is retrieved, you need to subscribe to voip pushes by passing a `notification_channel: apns_voip` channel in a subscription request * then when you want to send a voip push notification, use `ios_voip: 1` parameter in a push payload in a create event request. ## CallKit and VoIP push notifications (Ionic) [Section titled “CallKit and VoIP push notifications (Ionic)”](#callkit-and-voip-push-notifications-ionic) The above flow is valid for Ionic. The following Ionic libs can be used for it: * [ionic-plugin-callkit](https://github.com/Taracque/ionic-plugin-callkit) for CallKit * [cordova-ios-voip-push](https://github.com/Hitman666/cordova-ios-voip-push) for VoIP push notifications
# Streaming
> Leverage ConnectyCube's streaming feature for dynamic real-time interactions in Cordova app. Ideal for interactive sessions, such as teachers broadcasting to multiple students.
ConnectyCube streaming is built on top of [WebRTC](https://webrtc.org/) protocol. The main use case for streaming is with a teacher and many students where normally students join a call as listeners and only a teacher publishes own media stream: **Streaming API** is built on top of [Video Conferencing API](/cordova/videocalling-conference), hence same API documentation should be followed. The only difference is when join a room - a `session.joinAsListener(...)` API should be used at students side instead of `session.join(...)` API at teacher’s side. This method allows to join a conference room w/o publishing own media stream.
# Whiteboard
> Enable dynamic collaboration in chat with ConnectyCube Whiteboard API. Ideal for remote meetings, teaching environments, sales demos, real-time workflows.
ConnectyCube **Whiteboard API** allows to create whiteboard functionality and associate it with a chat dialog. Chat dialog’s users can collaborate and draw simultaneously on a whiteboard. You can do freehand drawing with a number of tools, add shapes, lines, text and erase. To share boards, you just get an easy link which you can email. Your whiteboard stays safe in the cloud until you’re ready to return to it.  The most popular use cases for using the whiteboard: * Remote meetings * Remote teaching * Sales presentations * Workflows * Real-time collaboration ## Get started with SDK [Section titled “Get started with SDK”](#get-started-with-sdk) Follow the [Getting Started guide](/cordova/) on how to connect ConnectyCube SDK and start building your first app. ## Preparations [Section titled “Preparations”](#preparations) In order to start using whiteboard, an additional config has to be provided: ```javascript const CONFIG = { ... whiteboard: { server: 'https://whiteboard.connectycube.com' } } ConnectyCube.init(CREDS, CONFIG); ``` Then, ConnectyCube whiteboard is associated with a chat dialog. In order to create a whiteboard, you need to have a chat dialog. Refer to [chat dialog creation API](/js/messaging#create-new-dialog). ## Create whiteboard [Section titled “Create whiteboard”](#create-whiteboard) When create a whiteboard you need to pass a name (any) and a chat dialog id to which whiteboard will be connected. ```javascript const params = { name: 'New Whiteboard', chat_dialog_id: "5356c64ab35c12bd3b108a41" }; ConnectyCube.whiteboard.create(params) .then(whiteboard => { const wid = whiteboard._id; }) .catch(error => { }); ``` Once whiteboard is created - a user can display it in app in a WebView using the following url: `https://whiteboard.connectycube.com?whiteboardid=<_id>&username=&title=` For `username` - any value can be provided. This is to display a text hint near the drawing arrow. ## Retrieve whiteboards [Section titled “Retrieve whiteboards”](#retrieve-whiteboards) Use the following code snippet to retrieve a list of whiteboards associated with a chat dialog: ```javascript const params = {chat_dialog_id: "5456c64ab35c17bd3b108a76"}; ConnectyCube.whiteboard.get(params) .then(whiteboard => { }) .catch(error => { }); ``` ## Update whiteboard [Section titled “Update whiteboard”](#update-whiteboard) A whiteboard can be updated, e.g. its name: ```javascript const whiteboardId = "5456c64ab35c17bd3b108a76"; const params = { name: 'New Whiteboard', }; ConnectyCube.whiteboard.update(whiteboardId, params) .then(whiteboard => { }) .catch(error => { }); ``` ## Delete whiteboard [Section titled “Delete whiteboard”](#delete-whiteboard) ```javascript const whiteboardId = "5456c64ab35c17bd3b108a76"; ConnectyCube.whiteboard.delete(whiteboardId) .then(whiteboard => { }) .catch(error => { }); ```
# Getting Started
> Implement a chat widget on your website with our concrete, step-by-step guide.
The ConnectyCube Web Chat Widget is designed to simplify the process of adding chat functionality to your Web apps. This widget offers an out-of-the-box solution for embedding chat features - such as instant messaging, user presence, and file sharing - without the overhead of building a complete chat system from scratch. Key benefits include: * **Easy integration:** plug the widget into your existing Web projects. * **Customizable interface:** adjust the look and feel to match your brand. * **Real-time messaging:** leverage ConnectyCube’s reliable backend for instant communication. * **Responsive design:** works seamlessly on both desktop and mobile devices. * **Modular and extensible:** adapt the widget to your unique requirements. ## Demo [Section titled “Demo”](#demo) [](https://connectycube-chat-widget.onrender.com)\ \ `` ### Code samples [Section titled “Code samples”](#code-samples) See chat widget [code samples](https://github.com/ConnectyCube/connectycube-chat-widget-samples) as a reference for faster integration. ## Installation [Section titled “Installation”](#installation) * React ```bash # npm npm install @connectycube/chat-widget # yarn yarn add @connectycube/chat-widget ``` * Vanilla JS Add the following scripts on your html page somewhere in `head` element: ```html ``` * Angular ```bash # npm npm install @connectycube/chat-widget-angular # yarn yarn add @connectycube/chat-widget-angular ``` As this component uses wrapped `@connectycube/chat-widget`, install types for React and ReactDOM as `devDependencies`: ```bash # npm npm install --save-dev @types/react @types/react-dom # yarn yarn add --dev @types/react @types/react-dom ``` ## Display widget [Section titled “Display widget”](#display-widget) ### 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. ### React [Section titled “React”](#react) Import and place the following component in your app: ```js import ConnectyCubeChatWidget from "@connectycube/chat-widget"; ... // userName - how other users will see your user name // userId - a User Id from your system ``` See chat widget [code samples](https://github.com/ConnectyCube/connectycube-chat-widget-samples) as a reference for faster integration. Detailed YouTube guide **how to add ConnectyCube Chat Widget to React app**: [Play](https://youtube.com/watch?v=jmCQtCC6o1M) #### React version support [Section titled “React version support”](#react-version-support) Since v0.35.0, the default build of the widget targets React 19 and is provided as an ESM module. For React 18 projects, use the dedicated ESM-only build: ```js // v0.35.0 and later: import ConnectyCubeChatWidget from '@connectycube/chat-widget'; // default: React 19 import ConnectyCubeChatWidget from '@connectycube/chat-widget/react19'; // explicit React 19 build import ConnectyCubeChatWidget from '@connectycube/chat-widget/react18'; // dedicated React 18 build // v0.34.0 and earlier: import ConnectyCubeChatWidget from '@connectycube/chat-widget'; // default: React 18 import ConnectyCubeChatWidget from '@connectycube/chat-widget/react19'; // React 19 build ``` ### Vanilla JS [Section titled “Vanilla JS”](#vanilla-js) Place the following script in your app: ```html ``` See chat widget [code samples](https://github.com/ConnectyCube/connectycube-chat-widget-samples) as a reference for faster integration. Detailed YouTube guide **how to add ConnectyCube Chat Widget to Vanilla JS app**: [Play](https://youtube.com/watch?v=P9mUtWE6u1M) ### Angular [Section titled “Angular”](#angular) Import and connect the `ConnectyCubeChatWidgetComponent` to your component (e.g., in `app.ts`): ```js import { RouterOutlet } from '@angular/router'; import { ConnectyCubeChatWidgetComponent } from '@connectycube/chat-widget-angular'; @Component({ selector: 'app-root', imports: [RouterOutlet, ConnectyCubeChatWidgetComponent], templateUrl: './app.html', styleUrl: './app.css', }) export class App { ... protected readonly connectycubeChatWidgetProps = { appId: 111, authKey: '11111111-2222-3333-4444-55555555', config: { debug: { mode: 1 } }, userId: '112233', // a User Id from your system userName: 'Samuel', // how other users will see your user name showOnlineUsersTab: false, splitView: true, }; } ``` Place the `` component in your template (`app.html`) and pass the props: ```html
...
... ``` ## Feature configuration [Section titled “Feature configuration”](#feature-configuration) This section explains how to control specific features of the chat widget by enabling or disabling them through configuration [props](#props). Customize your widget behavior to meet your specific requirements without having to modify the core code. ### Notifications [Section titled “Notifications”](#notifications) This section allows you to configure how users are notified about new messages. You can control notifications, browser push notifications, and notification sounds independently for a fully customizable experience: * **showNotifications** - show browser notifications about new messages received * **webPushNotifications** - show browser push notifications about new messages received * **playSound** - play sound about new messages received ### Online users [Section titled “Online users”](#online-users) We can displays a dedicated tab within the chat widget that lists all currently online users. This feature is useful in team or community-based chat environments where it’s helpful to see who’s available before starting a conversation. * React ```js ``` * Vanilla JS ```js const props = { ... showOnlineUsersTab: true, }; ``` * Angular ```ts @Component({ ... }) export class MyComponent { props = { ... showOnlineUsersTab: true, }; } ``` To make this feature work you also need to enable **Online Users API** in [ConnectyCube Daashboard](https://admin.connectycube.com/) in your app’s permissions  ### Toggle Button visibility [Section titled “Toggle Button visibility”](#toggle-button-visibility) **hideWidgetButton** - controls the visibility of the floating button that users click to open or close the chat widget. This is useful when you want to launch the widget programmatically or embed it inside a custom UI element instead of showing the default button. Read how to open a widget [by clicking on custom button](#open-widget-on-custom-button) ### New chat buttons [Section titled “New chat buttons”](#new-chat-buttons) These settings let you control whether users can start new chats or initiate one-on-one conversations. Use them to restrict chat creation in specific environments or use cases, such as read-only communities or customer support-only contexts. * **hideNewChatButton** - controls the visibility of the main “New Chat” button in the chat widget interface. * **hideNewUserChatOption** - hides the option to start a new 1-on-1 conversation from within the “Create Chat” flow. This is useful when you only want to allow group chats or predefined channels. ### Moderation [Section titled “Moderation”](#moderation) The moderation settings help maintain a safe and respectful chat environment. These options allow users to report inappropriate content and manage their personal block lists, giving them more control over their experience. At then moment the following moderation features supported: * **enableContentReporting** - users can report messages via a UI action * **enableBlockList** - users can block and unblock others from their profile #### Report user [Section titled “Report user”](#report-user) To enable Report user feature, you need to pass `enableContentReporting: true` prop. This will display a **Report** button in user profile:  For user reporting to work properly, 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  Once the table is created, you can test the user reporting in Widget. You will see the reports appear in this table. #### Block user [Section titled “Block user”](#block-user) To enable Block user feature, you need to pass `enableBlockList: true` prop. This will display a **Block** button in user profile (see above). ### Voice & video calls [Section titled “Voice & video calls”](#voice--video-calls) To enable audio and video calling in the chat widget, simply set the `enableCalls` prop to true: * React ```js ``` * Vanilla JS ```js const props = { ... enableCalls: true, }; ``` * Angular ```ts @Component({ ... }) export class MyComponent { props = { ... enableCalls: true, }; } ``` This will activate the call buttons in the chat UI, allowing users to initiate audio or video calls directly from the widget. ### AI [Section titled “AI”](#ai) The chat widget comes with advanced AI features to make conversations more effective and personalized. At then moment the following AI features supported: * **changeMessageTone** - adjust the tone of their messages before sending * **textSummarization** - instead of scrolling through long chats, users can generate concise summaries of past conversations. #### Change message tone [Section titled “Change message tone”](#change-message-tone) Users can adjust the tone of their messages before sending. Whether they want to sound formal, casual, friendly, or professional, the widget helps rephrase messages instantly—ensuring the right style for every context. * React ```js ``` * Vanilla JS ```js const props = { ... ai: { changeMessageTone: true, apiKey: 'AIza1231231231231231231231-wHBRQv1M', }, }; ``` * Angular ```ts @Component({ ... }) export class MyComponent { props = { ... ai: { changeMessageTone: true, apiKey: 'AIza1231231231231231231231-wHBRQv1M', }, }; } ``` \  #### Chat history summarisation [Section titled “Chat history summarisation”](#chat-history-summarisation) Instead of scrolling through long chats, users can generate concise summaries of past conversations. This helps quickly recall key points, decisions, and action items without reading every single message. * React ```js ``` * Vanilla JS ```js const props = { ... ai: { textSummarization: true, apiKey: 'AIza1231231231231231231231-wHBRQv1M', }, }; ``` * Angular ```ts @Component({ ... }) export class MyComponent { props = { ... ai: { textSummarization: true, apiKey: 'AIza1231231231231231231231-wHBRQv1M', }, }; } ```  ## Appearance & Styling [Section titled “Appearance & Styling”](#appearance--styling) The Appearance & Styling settings let you fully customize the visual presentation of the chat widget. You can control the look of the toggle button, badge, and chat portal using inline styles or CSS class names. This ensures your widget blends seamlessly into your app or site design. ### Chat toggle button [Section titled “Chat toggle button”](#chat-toggle-button) * **buttonTitle** - sets the accessible title (title attribute) of the chat toggle button. This is typically used for accessibility or tooltip text when hovering over the button * **buttonStyle** - applies inline styles to the chat toggle button ### Portal [Section titled “Portal”](#portal) * **portalStyle** - applies inline styles to the main chat window (portal) ### Badge [Section titled “Badge”](#badge) * **badgeStyle** - applies inline styles to the notification badge that appears on the toggle button (e.g., unread messages count) ## Props [Section titled “Props”](#props) | Prop Name | Type | Description | Default Value | | -------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `appId` | `number \| string` | The ConnectyCube Application ID | | | `authKey` | `string` | The ConnectyCube Authentication Key | | | `config` | `object` | *(Optional)* Configuration options for ConnectyCube SDK | | | `userId` | `string` | A User Id from your system | | | `userName` | `string` | User name. This is how other users will see your user name | | | `userAvatar` | `string` | *(Optional)* User Avatar URL | | | `userProfileLink` | `string` | *(Optional)* User profile link URL | | | `enableUserLogin` | `boolean` | *(Optional)* Enables user login/register feature | false | | `translation` | `string` | *(Optional)* Specifies the language for the chat widget. See [Supported Translations](#supported-translations) | ”en” | | `disableClickOutside` | `boolean` | *(Optional)* Hide chat widget by click or touch outside the widget’s containers | false | | `disableEscKeyPress` | `boolean` | *(Optional)* Hide chat widget by press “Escape” button | false | | `hideWidgetButton` | `boolean` | *(Optional)* Allows to hide the button that opens/hides the chat widget | false | | `buttonTitle` | `string` | *(Optional)* The text displayed on the chat button | ”Chat” | | `portalStyle` | `React.CSSProperties` | *(Optional)* Inline styles for the portal | | | `overlayStyle` | `React.CSSProperties` | *(Optional)* Inline styles for the overlay | | | `buttonStyle` | `React.CSSProperties` | *(Optional)* Inline styles for the button | | | `badgeStyle` | `React.CSSProperties` | *(Optional)* Inline styles for the unread messages badge | | | `onlineBadgeStyle` | `React.CSSProperties` | *(Optional)* Inline styles for the online users count badge | | | `open` | `boolean` | *(Optional)* To control the visibility state of the chat widget | false | | `embedView` | `boolean` | *(Optional)* Embeds chat view by filling parent block. Props `open` and `hideNewChatButton` won’t react and will be `true` by default | false | | `splitView` | `boolean` | *(Optional)* Displays the widget in split view (chats list and chat) or mobile view (only chats list or active chat) | false | | `showChatStatus` | `boolean` | *(Optional)* Displays the chat connection status indicator | false | | `showOnlineUsersTab` | `boolean` | *(Optional)* Displays users tab with the list of online users | false | | `hideNewChatButton` | `boolean` | *(Optional)* Allows to hide the chat creation button | false | | `hideNewUserChatOption` | `boolean` | *(Optional)* Allows to hide the New Message option in New Chat dropdown | false | | `hideNewGroupChatOption` | `boolean` | *(Optional)* Allows to hide the New Group option in New Chat dropdown | false | | `imgLogoSource` | `string` | *(Optional)* Allows to use custom logo source that is a relative path to the site’s `index.html` | ”/logo.png” | | `muted` | `boolean` | *(Optional)* Mutes or unmutes notifications and sounds | false | | `showNotifications` | `boolean` | *(Optional)* Allows receiving browser notifications | false | | `playSound` | `boolean` | *(Optional)* Enables or disables playing sound on incoming messages | true | | `webPushNotifications` | `boolean` | *(Optional)* Allows receiving browser push notifications | false | | `webPushVapidPublicKey` | `string` | *(Optional)* Vapid Public Key for push notifications | false | | `serviceWorkerPath` | `string` | *(Optional)* Path to service worker for push notifications | false | | `attachmentsAccept` | `string \| null` | *(Optional)* This prop sets to the accept attribute in [HTML \](https://www.w3schools.com/TAGs/att_input_accept.asp). Set `null` to disable the feature and hide attachment button | ”\*/\*“ | | `enableCalls` | `boolean` | *(Optional)* Enables the calls feature | false | | `enableUserStatuses` | `boolean` | *(Optional)* Enable user statuses, such as “Available”, “Busy” and “Away” | false | | `enableLastSeen` | `boolean` | *(Optional)* Displays green dot on user avatar in chats list when user is online and last seen information on chat header | false | | `enableContentReporting` | `boolean` | *(Optional)* Enable reporting bad content feature (will show Report button in user profile) | false | | `enableBlockList` | `boolean` | *(Optional)* Enable block user feature (will show Block button in user profile) | false | | `enableOnlineUsersBadge` | `boolean` | *(Optional)* Displays online users badge on chat widget button | false | | `getOnlineUsersInterval` | `number` | *(Optional)* Allows to set how frequently the badge should be updated, in seconds. Min is 30 seconds | 300 | | `enableUrlPreview` | `boolean` | *(Optional)* Allows to unfurl a link once posted in chat | false | | `limitUrlsPreviews` | `number` | *(Optional)* Allows to set maximum displayed URLs preview in single message. Max is 5 | 1 | | `quickActions` | `object` | *(Optional)* Configuration for quick actions dialog. See [Quick Actions](#quick-actions) | | | `defaultChat` | `object` | *(Optional)* Force widget to open particular chat. See [Default Chat](#default-chat) | | | `singleView` | `boolean` | *(Optional)* Enables customer support mode, allowing direct chat between users and product support teams. | false | | `singleViewChat` | `object` | *(Optional)* The chat widget will launch in support chat mode. Prop `singleView` must be enabled and [Quick Actions](#quick-actions) must be set. See [Single View Chat](#single-view-chat) | | | `termsAndConditions` | `string` | *(Optional)* Specifies the link for “Terms and Conditions” shown in the confirm email form when `singleView` is enabled. | | | `ai` | `object` | *(Optional)* Allows to enable and use AI features, such as get chat summary and change tone of a typed messages. See [AI](#ai) | | | `onUnreadCountChange` | `(count: boolean) => void` | *(Optional)* Callback function to get update about unread messages count | | | `onOnlineUsersCountChange` | `(count: boolean) => void` | *(Optional)* Callback function to get update about online users count | | | `onOpenChange` | `(open: boolean) => void` | *(Optional)* Callback function to get update about chat widget visibility | | ### Quick Actions [Section titled “Quick Actions”](#quick-actions) | Prop Name | Type | Description | | ------------- | ---------- | ------------------------------------------------ | | `title` | `string` | *(Optional)* Title for the quick actions section | | `description` | `string` | *(Optional)* Description for the quick actions | | `actions` | `string[]` | List of action strings | `` ### Supported Translations [Section titled “Supported Translations”](#supported-translations) | Language Code | Language | | ------------- | --------- | | `en` | English | | `el` | Greek | | `ua` | Ukrainian | ### Default Chat [Section titled “Default Chat”](#default-chat) | Prop Name | Type | Description | | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------- | | `id` | `string` | A key that will be used to identify the chat. Must be same value for each particular user between page reloads. | | `opponentUserId` | `string` | User id to create chat with. Must be a User Id from your system, similar to **userId** prop | | `type` | `string` | Type of chat to create if not exist: 1on1 or group | | `name` | `string` | Name of chat | | `metadata` | `map` | Chat metadata | ### Single View [Section titled “Single View”](#single-view) | Prop Name | Type | Description | | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | `string` | A key that will be used to identify the chat. Must be same value for each particular user between page reloads. | | `opponentUserIds` | `string` | Opponent(s) ID/IDs for a support chat. Use separators, such as `","`, `", "`, or `" "`, or array of strings to add more IDs to the chat. Must be a User Id from your system, similar to **userId** prop | | `name` | `string` | *(Optional)* Name of support chat | | `photo` | `string` | *(Optional)* Photo of support chat. Relative path to an image file, a URL, or ConnectyCube file’s UID | ### Push notifications [Section titled “Push notifications”](#push-notifications) Push notifications allow to receive a message while the browser tab with widget is closed. For push notifications to work it need to do the following: 1. pass `webPushNotifications: true` prop 2. go to [ConnectyCube Dashboard](https://admin.connectycube.com/), Push Notifications, Credentials, WEBPUSH NOTIFICATIONS and fill all the required fields (Subject, PublicKey, PrivateKey) 3. for `webPushVapidPublicKey` prop to set the same PublicKey which you set in ConnectyCube Dashboard. 4. create a service worker and provide path to it via `serviceWorkerPath` prop. See chat widget [code samples](https://github.com/ConnectyCube/connectycube-chat-widget-samples) as a reference. ### AI [Section titled “AI”](#ai-1) | Prop Name | Type | Description | | ------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------- | | `apiKey` | `string` | An `GOOGLE_GENERATIVE_AI_API_KEY` for Google AI “gemini-2.5-flash” model. Register here | | `textSummarization` | `boolean` | *(Optional)* Activates the ability to get a summary of a conversation for the selected time period | | `changeMessageTone` | `boolean` | *(Optional)* Allows the ability to change the tone of a typed message to “positive”, “negative”, or “cringe” | ## Recipes [Section titled “Recipes”](#recipes) ### Split view [Section titled “Split view”](#split-view) To display a widget with 2 separate UI blocks: left for chats list and the right one for messages stream. * React ```js ``` * Vanilla JS ```js const props = { ... splitView: true, }; ``` * Angular ```ts @Component({ ... }) export class MyComponent { props = { ... splitView: false, }; } ``` `` ### Mobile view [Section titled “Mobile view”](#mobile-view) To display a widget with 1 UI block, which displays chats list and once selected a chat - it will display the messages stream. * React ```js ``` * Vanilla JS ```js const props = { ... splitView: false, }; ``` * Angular ```ts @Component({ ... }) export class MyComponent { props = { ... splitView: true, }; } ``` `` ### Embedded view [Section titled “Embedded view”](#embedded-view) To embed the widget onto a page and display it e.g. full screen. * React ```js ``` * Vanilla JS ```js const props = { ... embedView: true, }; ``` * Angular ```ts @Component({ ... }) export class MyComponent { props = { ... embedView: true, }; } ``` `` ### Single View [Section titled “Single View”](#single-view-1) To display a widget in a single view mode - most popular for support chat use case. * React ```js ``` * Vanilla JS ```js const props = { ..., singleView: true, termsAndConditions: 'https://cats-store.com/terms-and-conditions', singleViewChat: { name: 'Cats Store', photo: 'https://cataas.com/cat', opponentUserIds: ['cat_support#007', 'cat_support#069'], }, quickActions: { title: 'Quick Actions', description: 'Select an action from the options below or type a first message to start a conversation.', actions: [ '🛒 I have a question about my order', '💳 I need help with payment or billing', '⚙️ I’m having a technical issue', '❓ Something else – connect me to support', ], }, }; ``` * Angular ```ts @Component({ ... }) export class MyComponent { props = { ..., singleView: true, termsAndConditions: 'https://cats-store.com/terms-and-conditions', singleViewChat: { name: 'Cats Store', photo: 'https://cataas.com/cat', opponentUserIds: ['cat_support#007', 'cat_support#069'], }, quickActions: { title: 'Quick Actions', description: 'Select an action from the options below or type a first message to start a conversation.', actions: [ '🛒 I have a question about my order', '💳 I need help with payment or billing', '⚙️ I’m having a technical issue', '❓ Something else – connect me to support', ], }, } } ``` `` ### Display Chat button bottom left [Section titled “Display Chat button bottom left”](#display-chat-button-bottom-left) * React ```js ``` * Vanilla JS ```js const props = { ... buttonStyle: {"left": "0.5rem", "right": "auto"} portalStyle: {"left": "0.5rem", "right": "auto"}, }; ``` * Angular ```ts @Component({ ... }) export class MyComponent { props = { ... buttonStyle: {"left": "0.5rem", "right": "auto"} portalStyle: {"left": "0.5rem", "right": "auto"}, }; } ``` ### Open widget on custom button [Section titled “Open widget on custom button”](#open-widget-on-custom-button) * React ```js import { useState } from 'react'; import { ConnectyCubeChatWidget } from '@connectycube/chat-widget'; export default function App() { const [visible, setVisible] = useState(false); return (
``` ## Have an issue? [Section titled “Have an issue?”](#have-an-issue) Join our [Discord](https://discord.com/invite/zqbBWNCCFJ) for quick answers to your questions ## Changelog [Section titled “Changelog”](#changelog)
# Send first chat message
> Step-by-step guide to sending your first chat message using ConnectyCube Android 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](/android/getting-started/code-samples/) as a foundation for your client app.\ If you already have an app and you are looking to add 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/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) To get the ConnectyCube SDK project running you will need [Android Studio](https://developer.android.com/studio/) and [Maven](https://maven.apache.org/) installed. The repository contains binary distributions of ConnectyCube Android SDK and an instruction how to connect SDK to your project. Check it out. Include reference to sdk repository in your build.gradle file at the app level (top level): ```groovy repositories { maven { url "https://github.com/ConnectyCube/connectycube-android-sdk-releases/raw/master/" } } ``` Then include dependencies to particular sdk-modules in build.gradle project file: ```groovy dependencies { implementation "com.connectycube.sdk:connectycube-android:2.0.0-beta05" } ``` ### Import SDK [Section titled “Import SDK”](#import-sdk) Add the following import statement to start using all classes and methods. ```kotlin import com.connectycube.ConnectyCubeAndroid; ``` ### Initialize SDK [Section titled “Initialize SDK”](#initialize-sdk) Initialize the SDK with your ConnectyCube application credentials. You can access your application credentials in [ConnectyCube Dashboard](https://admin.connectycube.com): ```kotlin ConnectyCubeAndroid.init(APP_ID, AUTH_KEY, context = applicationContext) ``` ## 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.\ With the request below, the user is created automatically on the fly upon session creation using the login (or email) and password from the parameters: ```kotlin val user = ConnectycubeUser(login = "marvin18", password = "supersecurepwd") ConnectyCube.createSession(user, { session -> }, { 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. ```kotlin val user = ConnectycubeUser().apply { id = 21 password = "supersecurepwd" } // or just // val user = user { // id = 21 // password = "supersecurepwd" //} ConnectyCube.chat.login(user, {}, { ex -> Log.d(tag, "login ex= $ex") }) ``` ## 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 is essential because it gives a unique conversation ID to correctly route and organize your message to the intended user. You need to pass `ConnectycubeDialogType.PRIVATE` as a type and an **id** of an opponent you want to create a chat with: ```kotlin val dialog = ConnectycubeDialog(type = ConnectycubeDialogType.PRIVATE, occupantsIds = occupantIds) ConnectyCube.createDialog(cubeDialog, { dialog -> }, { error -> }) ``` ## Step 5: Send / Receive messages [Section titled “Step 5: Send / Receive messages”](#step-5-send--receive-messages) **Receive messages** In order to send and receive messages, first it is required to add listener. There is `IncomingMessagesManager` to listen for all incoming messages from all dialogs. ```kotlin ConnectyCube.chat.addMessageListener(object: ConnectycubeMessageListener { override fun onError(message: ConnectycubeMessage, ex: Throwable) { } override fun onMessage(message: ConnectycubeMessage) { } }) ``` **Send messages in 1-1 chat** Creating a 1-1 chat is essential because it gives a unique conversation ID to correctly route and organize your message to the intended user. ```kotlin ConnectyCube.chat.sendMessage(message { recipientId = dialog.getRecipientId() body = "How are you today?" }) ``` That’s it! You’ve mastered the basics of sending a chat message in 1-1 chat 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](/android/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 Android 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](/android/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) To get the ConnectyCube SDK project running you will need [Android Studio](https://developer.android.com/studio/) and [Maven](https://maven.apache.org/) installed. The repository contains binary distributions of ConnectyCube Android SDK and an instruction how to connect SDK to your project. Check it out. Include reference to sdk repository in your build.gradle file at the app level (top level): ```groovy repositories { maven { url "https://github.com/ConnectyCube/connectycube-android-sdk-releases/raw/master/" } } ``` Then include dependencies to particular sdk-modules in build.gradle project file: ```groovy dependencies { implementation "com.connectycube.sdk:connectycube-android:2.0.0-beta05" } ``` ### Import SDK [Section titled “Import SDK”](#import-sdk) Add the following import statement to start using all classes and methods. ```kotlin import com.connectycube.ConnectyCubeAndroid; ``` ### Initialize SDK [Section titled “Initialize SDK”](#initialize-sdk) Initialize the SDK with your ConnectyCube application credentials. You can access your application credentials in [ConnectyCube Dashboard](https://admin.connectycube.com): ```kotlin ConnectyCubeAndroid.init(APP_ID, AUTH_KEY, context = applicationContext) ``` ## 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.\ With the request below, the user is created automatically on the fly upon session creation using the login (or email) and password from the parameters: ```kotlin val user = ConnectycubeUser(login = "marvin18", password = "supersecurepwd") ConnectyCube.createSession(user, { session -> }, { 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](/android/messaging) is used as a signalling transport for Video Calling API: ```kotlin val user = ConnectycubeUser().apply { id = 21 password = "supersecurepwd" } // or just // val user = user { // id = 21 // password = "supersecurepwd" //} ConnectyCube.chat.login(user, {}, { ex -> Log.d(tag, "login ex= $ex") }) ``` ## Step 4: Permissions to access camera, microphone and internet [Section titled “Step 4: Permissions to access camera, microphone and internet”](#step-4-permissions-to-access-camera-microphone-and-internet) Before starting the audio / video calling with the opponent(s), the video chat module requires camera, microphone and internet permissions. Make sure you add relevant permissions to your app manifest: ```xml ``` You can get more info on how to work with app permissions at [Android Permissions Overview](https://developer.android.com/guide/topics/permissions/overview) ## Step 5: Initiate a call [Section titled “Step 5: Initiate a call”](#step-5-initiate-a-call) To call users, you should create a session and start a call: ```kotlin val opponents: MutableList = ArrayList() opponents.add(21) // User can pass an additional info along with the call request val userInfo: HashMap = HashMap() userInfo["key1"] = "value1" //Init session val session = P2PCalls.createSession(opponents, CallType.VIDEO) session.startCall(userInfo) ``` After this, your opponents will receive a call request callback `onReceiveNewSession` via `RTCClientSessionCallbacks`. ## Step 6: Accept a call [Section titled “Step 6: Accept a call”](#step-6-accept-a-call) You will receive all incoming call requests in `RTCClientSessionCallbacks.onReceiveNewSession(session)` callback. To accept the call request use the following code snippet: ```kotlin // RTCCallSessionCallback override fun onReceiveNewSession(session: P2PSession) { // obtain received user info // val userInfo = session.getUserInfo() // set your user info if needed val userInfo = HashMap() userInfo["key1"] = "value1" // Accept the incoming call session.acceptCall(userInfo) } ``` After this, you will receive an accept callback: ```kotlin // RTCSessionEventsCallback override fun onCallAcceptByUser(session: P2PSession, opponentId: Int, userInfo: Map?) { } ``` Great work! You’ve completed the essentials of making a call in ConnectyCube. From this point, you and your opponents should start seeing and hearing 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](/android/videocalling) * [Conference calling SDK documentation](/android/videocalling-conference)
# Chat
> Integrate powerful chat functionality into your Android 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) * SDK v2 kotlin ```kotlin val user = ConnectycubeUser().apply { id = 21 password = "supersecurepwd" } // or just val user = user { id = 21 password = "supersecurepwd" } ConnectyCube.chat.login(user, {}, { ex -> Log.d(tag, "login ex= $ex") }) ``` * SDK v1 kotlin (deprecated) ```kotlin // Provide chat connection configuration val chatServiceConfigurationBuilder = ConfigurationBuilder().apply { socketTimeout = 60 isKeepAlive = true isUseTls = true //By default TLS is disabled. } ConnectycubeChatService.setConnectionFabric(TcpChatConnectionFabric(chatServiceConfigurationBuilder)) val chatService = ConnectycubeChatService.getInstance() val user = ConnectycubeUser().apply { id = 21 password = "supersecurepwd" } chatService.login(user, object : EntityCallback { override fun onError(error: ResponseException) { } override fun onSuccess(result: Void?, args: Bundle) { } }) ``` * SDK v1 java (deprecated) ```java // Provide chat connection configuration ConnectycubeChatService.ConfigurationBuilder chatServiceConfigurationBuilder = new ConnectycubeChatService.ConfigurationBuilder(); chatServiceConfigurationBuilder.setSocketTimeout(60); chatServiceConfigurationBuilder.setKeepAlive(true); chatServiceConfigurationBuilder.setUseTls(true); //By default TLS is disabled. ConnectycubeChatService.setConfigurationBuilder(chatServiceConfigurationBuilder); ConnectycubeChatService chatService = ConnectycubeChatService.getInstance(); final ConnectycubeUser user = new ConnectycubeUser(); user.setId(21); user.setPassword("supersecurepwd"); chatService.login(user, new EntityCallback() { @Override public void onSuccess() { } @Override public void onError(ResponseException errors) { } }); ``` Use **ConnectionListener** to handle different connection states: * SDK v2 kotlin ```kotlin ConnectyCube.chat.addConnectionListener(object : ConnectycubeConnectionListener { override fun onConnected() { } override fun onDisconnected() { } }) ``` * SDK v1 kotlin (deprecated) ```kotlin val connectionListener: ConnectionListener = object : ConnectionListener { override fun connected(connection: XMPPConnection) { } override fun connectionClosed() { } override fun connectionClosedOnError(e: Exception) { } override fun reconnectingIn(seconds: Int) { } override fun reconnectionSuccessful() { } override fun authenticated(connection: XMPPConnection, resumed: Boolean) { } override fun reconnectionFailed(e: Exception) { } } ConnectycubeChatService.getInstance().addConnectionListener(connectionListener) ``` * SDK v1 java (deprecated) ```java ConnectionListener connectionListener = new ConnectionListener() { @Override public void connected(XMPPConnection connection) { } @Override public void authenticated(XMPPConnection connection) { } @Override public void connectionClosed() { } @Override public void connectionClosedOnError(Exception e) { } @Override public void reconnectingIn(int seconds) { } @Override public void reconnectionSuccessful() { } @Override public void reconnectionFailed(Exception e) { } }; ConnectycubeChatService.getInstance().addConnectionListener(connectionListener); ``` ### 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: * SDK v2 kotlin ```kotlin val token = ConnectycubeSessionManager.getToken() val user = ConnectycubeUser().apply { id = 21 password = token } ``` * SDK v1 kotlin (deprecated) ```kotlin // get current ConnectyCube session token and set as user's password val token = ConnectycubeSessionManager.getInstance().token val user = ConnectycubeUser().apply { id = 21 password = token } ``` * SDK v1 java (deprecated) ```java // get current ConnectyCube session token and set as user's password String token = ConnectycubeSessionManager.getInstance().getToken(); final ConnectycubeUser user = new ConnectycubeUser(); user.setId(21); user.setPassword(token); ``` ## Disconnect [Section titled “Disconnect”](#disconnect) To **logout from chat** connection use logout method: * SDK v2 kotlin ```kotlin val isLoggedIn = ConnectyCube.chat.isLoggedIn() if (!isLoggedIn) { return } ConnectyCube.chat.logout({}, { ex -> Log.d(tag, "logout ex= $ex") }) ``` * SDK v1 kotlin (deprecated) ```kotlin val isLoggedIn = chatService.isLoggedIn if (!isLoggedIn) { return } chatService.logout(object : EntityCallback { override fun onError(error: ResponseException) { } override fun onSuccess(result: Void?, args: Bundle) { } }) ``` * SDK v1 java (deprecated) ```java boolean isLoggedIn = chatService.isLoggedIn(); if(!isLoggedIn){ return; } chatService.logout(new EntityCallback() { @Override public void onSuccess() { } @Override public void onError(ResponseException errors) { } }); ``` To **fully destroy** chat connection use destroy method: * SDK v2 kotlin ```kotlin ConnectyCube.chat.destroy() ``` * SDK v1 kotlin (deprecated) ```kotlin chatService.destroy() ``` * SDK v1 java (deprecated) ```java chatService.destroy(); ``` ## Reconnection [Section titled “Reconnection”](#reconnection) The SDK reconnects automatically when connection to Chat server is lost. There is a way to disable it and then manage it manually: * SDK v2 kotlin ```kotlin //Coming soon ``` * SDK v1 kotlin (deprecated) ```kotlin ConnectycubeChatService.getInstance().isReconnectionAllowed = false ``` * SDK v1 java (deprecated) ```java ConnectycubeChatService.getInstance().setReconnectionAllowed(false); ``` ## Dialogs [Section titled “Dialogs”](#dialogs) All chats between users are organized in dialogs. The are 4 types of dialogs: * 1-1 chat - a conversation between 2 users. * group chat - a conversation between specified list of users. * public chat - an open conversation. Any user from your app can subscribe to it. * 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 `ConnectycubeDialogType.PRIVATE` as a type and an id of an opponent you want to create a chat with: * SDK v2 kotlin ```kotlin val dialog = ConnectycubeDialog(type = ConnectycubeDialogType.PRIVATE, occupantsIds = occupantIds) ConnectyCube.createDialog(cubeDialog, { dialog -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val occupantIds = ArrayList().apply { add(34) } val dialog = ConnectycubeChatDialog().apply { type = ConnectycubeDialogType.PRIVATE setOccupantsIds(occupantIds) } // or just use DialogUtils // val dialog = DialogUtils.buildPrivateDialog(34) ConnectycubeRestChatService.createChatDialog(dialog) .performAsync(object : EntityCallback { override fun onSuccess(createdDialog: ConnectycubeChatDialog, params: Bundle) { } override fun onError(exception: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ArrayList occupantIds = new ArrayList(); occupantIds.add(34); ConnectycubeChatDialog dialog = new ConnectycubeChatDialog(); dialog.setType(ConnectycubeDialogType.PRIVATE); dialog.setOccupantsIds(occupantIds); //or just use DialogUtils //ConnectycubeChatDialog dialog = DialogUtils.buildPrivateDialog(recipientId); ConnectycubeRestChatService.createChatDialog(dialog).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeChatDialog createdDialog, Bundle params) { } @Override public void onError(ResponseException exception) { } }); ``` ### Create group chat [Section titled “Create group chat”](#create-group-chat) You need to pass `ConnectycubeDialogType.GROUP` as a type and ids of opponents you want to create a chat with: * SDK v2 kotlin ```kotlin val dialog = ConnectycubeDialog( type = ConnectycubeDialogType.GROUP, name = "Hawaii party", occupantsIds = occupantIds) ConnectyCube.createDialog(cubeDialog, { dialog -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val occupantIds = ArrayList().apply { add(34) add(35) add(36) } val dialog = ConnectycubeChatDialog().apply { type = ConnectycubeDialogType.GROUP name = "Hawaii party" // photo = "..." // description = "..." setOccupantsIds(occupantIds) } //or just use DialogUtils //val dialog = DialogUtils.buildDialog("Hawaii party", ConnectycubeDialogType.GROUP, occupantIds).apply { // photo = "..." // description = "..." //} ConnectycubeRestChatService.createChatDialog(dialog) .performAsync(object : EntityCallback { override fun onSuccess(createdDialog: ConnectycubeChatDialog, params: Bundle) { } override fun onError(exception: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ArrayList occupantIds = new ArrayList(); occupantIds.add(34); occupantIds.add(35); occupantIds.add(36); ConnectycubeChatDialog dialog = new ConnectycubeChatDialog(); dialog.setType(ConnectycubeDialogType.GROUP); dialog.setOccupantsIds(occupantIds); dialog.setName("Hawaii party"); //dialog.setPhoto("..."); //dialog.setDescription("..."); //or just use DialogUtils //ConnectycubeChatDialog dialog = DialogUtils.buildDialog("Hawaii party", ConnectycubeDialogType.GROUP, occupantIds); ConnectycubeRestChatService.createChatDialog(dialog).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeChatDialog createdDialog, Bundle params) { } @Override public void onError(ResponseException exception) { } }); ``` ### Create public chat [Section titled “Create public chat”](#create-public-chat) It’s possible to create a public chat, so any user from your application can subscribe to it. You need to pass `ConnectycubeDialogType.PUBLIC` as a type to create a chat with: * SDK v2 kotlin ```kotlin val dialog = ConnectycubeDialog(type = ConnectycubeDialogType.PUBLIC, name = "Blockchain trends") ConnectyCube.createDialog(cubeDialog, { dialog -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val dialog = ConnectycubeChatDialog().apply { type = ConnectycubeDialogType.PUBLIC name = "Blockchain trends" // photo = "..." // description = "..." } ConnectycubeRestChatService.createChatDialog(dialog) .performAsync(object : EntityCallback { override fun onSuccess(createdDialog: ConnectycubeChatDialog, params: Bundle) { } override fun onError(exception: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeChatDialog dialog = new ConnectycubeChatDialog(); dialog.setType(ConnectycubeDialogType.PUBLIC); dialog.setName("Blockchain trends"); //dialog.setPhoto("..."); //dialog.setDescription("..."); ConnectycubeRestChatService.createChatDialog(dialog).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeChatDialog createdDialog, Bundle params) { } @Override public void onError(ResponseException exception) { } }); ``` With public dialog ID any a user can subscribe to the public dialog via the following code: * SDK v2 kotlin ```kotlin ConnectyCube.subscribeToDialog(dialogId, { dialog -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin ConnectycubeRestChatService.subscribePublicDialog(dialogID) .performAsync(object : EntityCallback { override fun onSuccess(dialog: ConnectycubeChatDialog, params: Bundle) { } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeRestChatService.subscribePublicDialog(dialogID).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeChatDialog dialog, Bundle params) { } @Override public void onError(ResponseException responseException) { } }); ``` After dialog subscription, this dialog will be listed in retrieve dialogs request and you also will be able to chat in it. You also can unsubscribe if you do not want to be in this public dialog anymore: * SDK v2 kotlin ```kotlin ConnectyCube.unSubscribeFromDialog(dialogId, { }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin ConnectycubeRestChatService.unsubscribePublicDialog(dialogID) .performAsync(object : EntityCallback { override fun onSuccess(result: ConnectycubeChatDialog, params: Bundle) { } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeRestChatService.unsubscribePublicDialog(dialogID).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeChatDialog result, Bundle params) { } @Override public void onError(ResponseException responseException) { } }); ``` ### 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.  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. * SDK v2 kotlin ```kotlin val dialog = ConnectycubeDialog(ConnectycubeDialogType.GROUP) dialog.name = "Friday party" dialog.occupantsIds = arrayListOf(29085, 29086, 29087) dialog.description = "lets dance the night away" dialog.extensions = hashMapOf("location" to "Sun bar") ConnectyCube.createDialog(dialog, { createdDialog -> }, { 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. > **Note** > > To preview messages w/o joining to dialog pass `preview` operator in request to get messages. ## Retrieve list of dialogs [Section titled “Retrieve list of dialogs”](#retrieve-list-of-dialogs) It’s common to request all your conversations on every app login: * SDK v2 kotlin ```kotlin val params: HashMap = hashMapOf( "limit" to 50, "skip" to 100 ) ConnectyCube.getDialogs(params, successCallback = { resultDialogs -> }, errorCallback = { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val requestBuilder = RequestGetBuilder().apply { limit = 50 skip = 100 // sortAsc(Consts.DIALOG_LAST_MESSAGE_DATE_SENT_FIELD_NAME) } ConnectycubeRestChatService.getChatDialogs(null as ConnectycubeDialogType, requestBuilder) .performAsync(object : EntityCallback> { override fun onSuccess(dialogs: ArrayList, params: Bundle) { val totalEntries = params.getInt(Consts.TOTAL_ENTRIES) } override fun onError(exception: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java RequestGetBuilder requestBuilder = new RequestGetBuilder(); requestBuilder.setLimit(50); requestBuilder.setSkip(100); //requestBuilder.sortAsc(Consts.DIALOG_LAST_MESSAGE_DATE_SENT_FIELD_NAME); ConnectycubeRestChatService.getChatDialogs((ConnectycubeDialogType)null, requestBuilder).performAsync(new EntityCallback>() { @Override public void onSuccess(ArrayList dialogs, Bundle params) { int totalEntries = params.getInt(Consts.TOTAL_ENTRIES); } @Override public void onError(ResponseException exception) { } }); ``` It will return all your 1-1 dialogs, group dialog and also public dialogs your are subscribed to. If you want to retrieve only conversations updated after some specific date time, you can use `requestBuilder.gt("updated_at", "1455098137");` filter. This is useful if you cache conversations somehow and do not want to obtain the whole list of your conversations on every app start. ## Update dialog’s name, description, photo [Section titled “Update dialog’s name, description, photo”](#update-dialogs-name-description-photo) User can update group chat name, description, photo: * SDK v2 kotlin ```kotlin val dialogId = "5356c64ab35c12bd3b108a41" val params = UpdateDialogParams() //class-helper to simple config search request params.newName = "Hawaii party" params.newPhoto = "https://new_photo_url" params.newDescription = "New dialog description" ConnectyCube.updateDialog(dialogId, params.getUpdateDialogParams(), successCallback = { resultDialog -> }, errorCallback = { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val dialog = ConnectycubeChatDialog().apply { dialogId = "5356c64ab35c12bd3b108a41" name = "Hawaii party" photo = "https://new_photo_url" // or it can be an ID to some file in Storage module description = "New dialog description" } ConnectycubeRestChatService.updateChatDialog(dialog, null) .performAsync(object : EntityCallback { override fun onSuccess(updatedDialog: ConnectycubeChatDialog, bundle: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeChatDialog dialog = new ConnectycubeChatDialog(); dialog.setDialogId("5356c64ab35c12bd3b108a41"); dialog.setName("Hawaii party"); dialog.setPhoto("https://new_photo_url"); // or it can be an ID to some file in Storage module dialog.setDescription("New dialog description"); ConnectycubeRestChatService.updateChatDialog(dialog, null).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeChatDialog updatedDialog, Bundle bundle) { } @Override public void onError(ResponseException error) { } }); ``` ## Add/Remove occupants [Section titled “Add/Remove occupants”](#addremove-occupants) You can add/remove occupants in group and public dialogs: * SDK v2 kotlin ```kotlin val dialogId = "5356c64ab35c12bd3b108a41" val params = UpdateDialogParams() params.addOccupantIds = hashSetOf(378) // params.deleteOccupantIds = hashSetOf(22) ConnectyCube.updateDialog(dialogId, params.getUpdateDialogParams(), successCallback = { resultDialog -> }, errorCallback = { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val requestBuilder = DialogRequestBuilder().apply { addUsers(378) // removeUsers(22) } val dialog = ConnectycubeChatDialog("5356c64ab35c12bd3b108a41") ConnectycubeRestChatService.updateChatDialog(dialog, requestBuilder) .performAsync(object : EntityCallback { override fun onSuccess(updatedDialog: ConnectycubeChatDialog, bundle: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java DialogRequestBuilder requestBuilder = new DialogRequestBuilder(); requestBuilder.addUsers(378); // requestBuilder.removeUsers(22); ConnectycubeChatDialog dialog = new ConnectycubeChatDialog(); dialog.setDialogId("5356c64ab35c12bd3b108a41"); ConnectycubeRestChatService.updateChatDialog(dialog, requestBuilder).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeChatDialog updatedDialog, Bundle bundle) { } @Override public void onError(ResponseException error) { } }); ``` > **Note** > > Only group chat owner and admins can remove other users from group chat. ## Add / Remove admins [Section titled “Add / Remove admins”](#add--remove-admins) Admins it’s a special role in chats. They have the same permissions as a dialog’s creator except add/remove other admins and remove dialog. Owner of the group chat dialog can add admins: * SDK v2 kotlin ```kotlin val dialogId = "5356c64ab35c12bd3b108a41" val params = UpdateDialogParams() params.addAdminIds = hashSetOf(17616, 17617) ConnectyCube.updateDialog(dialogId, params.getUpdateDialogParams(), successCallback = { resultDialog -> }, errorCallback = { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val updateBuilder = DialogRequestBuilder().apply { addAdminsIds(17616, 17617) } ConnectycubeRestChatService.updateChatDialog(groupDialog, updateBuilder) .performAsync(object : EntityCallback { override fun onSuccess(result: ConnectycubeChatDialog, params: Bundle) { } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java DialogRequestBuilder updateBuilder = new DialogRequestBuilder(); updateBuilder.addAdminsIds(17616, 17617); ConnectycubeRestChatService.updateChatDialog(groupDialog, updateBuilder).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeChatDialog result, Bundle params) { } @Override public void onError(ResponseException responseException) { } }); ``` and remove: * SDK v2 kotlin ```kotlin val dialogId = "5356c64ab35c12bd3b108a41" val params = UpdateDialogParams() params.deleteAdminIds = hashSetOf(17616, 17617) ConnectyCube.updateDialog(dialogId, params.getUpdateDialogParams(), successCallback = { resultDialog -> }, errorCallback = { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val updateBuilder = DialogRequestBuilder().apply { removeAdminsIds(17616, 17617) } ConnectycubeRestChatService.updateChatDialog(groupDialog, updateBuilder) .performAsync(object : EntityCallback { override fun onSuccess(result: ConnectycubeChatDialog, params: Bundle) { } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java DialogRequestBuilder updateBuilder = new DialogRequestBuilder(); updateBuilder.removeAdminsIds(17616, 17617); ConnectycubeRestChatService.updateChatDialog(groupDialog, updateBuilder).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeChatDialog result, Bundle params) { } @Override public void onError(ResponseException responseException) { } }); ``` ## Pin messages [Section titled “Pin messages”](#pin-messages) Pinning a message allows group owner or chat admins to easily store messages which are important, so that all users in chat have a quick access to them. The following code pins some messages to a particular group dialog: * SDK v2 kotlin ```kotlin val dialogId = "5356c64ab35c12bd3b108a41" val params = UpdateDialogParams() params.addPinnedMsgIds = hashSetOf("5356c64ab35c12bd3b10ba32", "5356c64ab35c12bd3b10wa65") // params.deletePinnedMsgIds = hashSetOf("5356c64ab35c12bd3b10ba32", "5356c64ab35c12bd3b10wa65") ConnectyCube.updateDialog(dialogId, params.getUpdateDialogParams(), successCallback = { resultDialog -> }, errorCallback = { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val updateBuilder = DialogRequestBuilder().apply { addPinnedMessagesIds( "5356c64ab35c12bd3b10ba32", "5356c64ab35c12bd3b10wa65" ) // removePinnedMessagesIds( // "5356c64ab35c12bd3b10ba32", // "5356c64ab35c12bd3b10wa65" // ) } ConnectycubeRestChatService.updateChatDialog(groupDialog, updateBuilder) .performAsync(object : EntityCallback { override fun onSuccess(result: ConnectycubeChatDialog, params: Bundle) { } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java DialogRequestBuilder updateBuilder = new DialogRequestBuilder(); updateBuilder.addPinnedMessagesIds("5356c64ab35c12bd3b10ba32", "5356c64ab35c12bd3b10wa65"); //updateBuilder.removePinnedMessagesIds("5356c64ab35c12bd3b10ba32", "5356c64ab35c12bd3b10wa65"); ConnectycubeRestChatService.updateChatDialog(groupDialog, updateBuilder).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeChatDialog result, Bundle params) { } @Override public void onError(ResponseException responseException) { } }); ``` ## Remove dialog [Section titled “Remove dialog”](#remove-dialog) The following snippet is used to delete a conversation: * SDK v2 kotlin ```kotlin val dialogId = "5356c64ab35c12bd3b108a41" val forceDelete = false ConnectyCube.deleteDialog(dialogId, forceDelete, successCallback = { }, errorCallback = { }) ``` * SDK v1 kotlin (deprecated) ```kotlin val dialogId = "5356c64ab35c12bd3b108a41" val forceDelete = false ConnectycubeRestChatService.deleteDialog(dialogId, forceDelete) .performAsync(object : EntityCallback { override fun onSuccess(aVoid: Void?, bundle: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java String dialogId = "5356c64ab35c12bd3b108a41"; boolean forceDelete = false; ConnectycubeRestChatService.deleteDialog(dialogId, forceDelete).performAsync(new EntityCallback() { @Override public void onSuccess(Void aVoid, Bundle bundle) { } @Override public void onError(ResponseException error) { } }); ``` This request will remove this conversation for current user, but other users still will be able to chat there. **The** `forceDelete` **parameter is used to completely remove the dialog.** Only group chat owner can remove the group conversation for all users. You can also delete multiple conversations in a single request. ## Chat history [Section titled “Chat history”](#chat-history) Every chat conversation stores its chat history which you can retrieve: * SDK v2 kotlin ```kotlin val dialogId = "5356c64ab35c12bd3b108a41" val messageGetBuilder: GetMessagesParameters = GetMessagesParameters().also { it.limit = 100; it.markAsRead = false; it.sorter = RequestSorter("", "date_sent", "desc")} ConnectyCube.getMessages(dialogId, messageGetBuilder.getRequestParameters(), successCallback = { pagedMessagesResult -> }, errorCallback = { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val chatDialog = ConnectycubeChatDialog("5356c64ab35c12bd3b108a41") val messageGetBuilder = MessageGetBuilder().apply { limit = 100 // gt(Consts.MESSAGE_DATE_SENT, "1455098137") } ConnectycubeRestChatService.getDialogMessages(chatDialog, messageGetBuilder) .performAsync(object : EntityCallback> { override fun onSuccess(messages: ArrayList, bundle: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeChatDialog chatDialog = new ConnectycubeChatDialog("5356c64ab35c12bd3b108a41"); MessageGetBuilder messageGetBuilder = new MessageGetBuilder(); messageGetBuilder.setLimit(100); // messageGetBuilder.gt("date_sent", "1455098137"); ConnectycubeRestChatService.getDialogMessages(chatDialog, messageGetBuilder).performAsync(new EntityCallback>() { @Override public void onSuccess(ArrayList messages, Bundle bundle) { } @Override public void onError(ResponseException error) { } }); ``` If you want to retrieve chat messages that were sent after or before specific date time only, you can use `messageGetBuilder.gt("date_sent", "1455098137")` or `messageGetBuilder.lt("date_sent", "1455098137")` filter. This is useful if you implement pagination for loading messages in your app. > **Note** > > All retrieved chat messages will be marked as read after the request. If you decided not to mark chat messages as read, then add the following parameter to your request: `messageGetBuilder.markAsRead(false);` ## Send/Receive chat messages [Section titled “Send/Receive chat messages”](#sendreceive-chat-messages) ### Receive messages [Section titled “Receive messages”](#receive-messages) There is `IncomingMessagesManager` to listen for all incoming messages from all dialogs. * SDK v2 kotlin ```kotlin ConnectyCube.chat.addMessageListener(object: ConnectycubeMessageListener { override fun onError(message: ConnectycubeMessage, ex: Throwable) { } override fun onMessage(message: ConnectycubeMessage) { } }) ``` * SDK v1 kotlin (deprecated) ```kotlin val incomingMessagesManager = chatService.incomingMessagesManager incomingMessagesManager.addDialogMessageListener(object : ChatDialogMessageListener { override fun processMessage(dialogId: String, message: ConnectycubeChatMessage, senderId: Int) { } override fun processError(dialogId: String, exception: ChatException, message: ConnectycubeChatMessage, senderId: Int) { } }) ``` * SDK v1 java (deprecated) ```java IncomingMessagesManager incomingMessagesManager = chatService.getIncomingMessagesManager(); incomingMessagesManager.addDialogMessageListener(new ChatDialogMessageListener() { @Override public void processMessage(String dialogId, ConnectycubeChatMessage message, Integer senderId) { } @Override public void processError(String dialogId, ChatException exception, ConnectycubeChatMessage message, Integer senderId) { } }); ``` > Pay attention, messages from **group & public chat dialogs** will be received in this callback only after you join the dialogs. > Pay attention, before using dialog you need to init it for ConnectycubeChatService. Call this once you’ve got dialog - `chatDialog.initForChat(ConnectycubeChatService.getInstance())`. ### 1-1 chat [Section titled “1-1 chat”](#1-1-chat) * SDK v2 kotlin ```kotlin ConnectyCube.chat.addMessageListener(object: ConnectycubeMessageListener { override fun onError(message: ConnectycubeMessage, ex: Throwable) { } override fun onMessage(message: ConnectycubeMessage) { } }) ``` * SDK v1 kotlin (deprecated) ```kotlin val privateDialog = ... val chatMessage = ConnectycubeChatMessage().apply { body = "How are you today?" setSaveToHistory(true) } privateDialog.sendMessage(chatMessage) privateDialog.addMessageListener(object: ChatDialogMessageListener { override fun processMessage(dialogId: String, message: ConnectycubeChatMessage, senderId: Int ) { } override fun processError(dialogId: String, exception: ChatException, message: ConnectycubeChatMessage, senderId: Int? ) { } }) ``` * SDK v1 java (deprecated) ```java try { ConnectycubeChatDialog privateDialog = ...; ConnectycubeChatMessage chatMessage = new ConnectycubeChatMessage(); chatMessage.setBody("How are you today?"); chatMessage.setSaveToHistory(true); privateDialog.sendMessage(chatMessage); } catch (SmackException.NotConnectedException e) { } privateDialog.addMessageListener(new ChatDialogMessageListener() { @Override public void processMessage(String dialogId, ConnectycubeChatMessage message, Integer senderId) { } @Override public void processError(String dialogId, ChatException exception, ConnectycubeChatMessage message, Integer senderId) { } }); ``` ### Group/Public chat [Section titled “Group/Public chat”](#grouppublic-chat) Before you start chatting in a group/public conversation, you need to join it by calling `join` method: * SDK v1 kotlin (deprecated) ```kotlin val groupChatDialog = ... groupChatDialog.join(object : EntityCallback { override fun onSuccess(result: Void?, args: Bundle?) { } override fun onError(exception: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeChatDialog groupChatDialog = ...; groupChatDialog.join(new EntityCallback() { @Override public void onSuccess(Object o, Bundle bundle) { } @Override public void onError(ResponseException e) { } }); ``` Then you are able to send/receive messages: * SDK v2 kotlin ```kotlin val chatMessage = ConnectycubeMessage() chatMessage.dialogId = dialogId chatMessage.saveToHistory = true chatMessage.dateSent = System.currentTimeMillis() / 1000 chatMessage.markable = true if(dialog.type == ConnectycubeDialogType.PRIVATE) chatMessage.recipientId = dialog.getRecipientId() else chatMessage.type = when (dialog.type) { ConnectycubeDialogType.GROUP, ConnectycubeDialogType.PUBLIC -> ConnectycubeMessageType.Groupchat else -> ConnectycubeMessageType.Chat } chatMessage.body = "How are you today?" ConnectyCube.chat.sendMessage(chatMessage) ConnectyCube.chat.addMessageListener(object: ConnectycubeMessageListener { override fun onError(message: ConnectycubeMessage, ex: Throwable) { } override fun onMessage(message: ConnectycubeMessage) { } }) ``` * SDK v1 kotlin (deprecated) ```kotlin val chatMessage = ConnectycubeChatMessage().apply { body = "How are you today?" setSaveToHistory(true) } groupChatDialog.sendMessage(chatMessage) groupChatDialog.addMessageListener(object : ChatDialogMessageListener { override fun processMessage(dialogId: String, message: ConnectycubeChatMessage, senderId: Int ) { } override fun processError(dialogId: String, exception: ChatException, message: ConnectycubeChatMessage, senderId: Int ) { } }) ``` * SDK v1 java (deprecated) ```java try { ConnectycubeChatMessage chatMessage = new ConnectycubeChatMessage(); chatMessage.setBody("How are you today?"); chatMessage.setSaveToHistory(true); groupChatDialog.sendMessage(chatMessage); } catch (SmackException.NotConnectedException e) { } groupChatDialog.addMessageListener(new ChatDialogMessageListener() { @Override public void processMessage(String dialogId, ConnectycubeChatMessage message, Integer senderId) { } @Override public void processError(String dialogId, ChatException exception, ConnectycubeChatMessage message, Integer senderId) { } }); ``` When it’s done, you can leave the group conversation by calling `leave` method: * SDK v1 kotlin (deprecated) ```kotlin groupChatDialog.leave() ``` * SDK v1 java (deprecated) ```java try { groupChatDialog.leave(); groupChatDialog = null; } catch (XMPPException | SmackException.NotConnectedException e) { } ``` ## 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 `properties` field: ```dart val message = ConnectycubeMessage() message.properties["field_one"] = "value_one" message.properties["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: * SDK v1 kotlin (deprecated) ```kotlin val chatService = ConnectycubeChatService.getInstance() chatService.setUseStreamManagement(true) chatService.login(user) ``` * SDK v1 java (deprecated) ```java ConnectycubeChatService chatService = ConnectycubeChatService.getInstance(); chatService.setUseStreamManagement(true); chatService.login(user); ``` > Pay attention: you should enable Stream Management before logging into the chat. Stream Management is initialized only during chat login step. The Stream Management defines an extension for active management of a stream between client and server, including features for stanza acknowledgements. The following callback is used to track the status: * SDK v2 kotlin ```kotlin ConnectyCube.chat.addMessageSentListener(object: ConnectycubeMessageSentListener { override fun onMessageSent(message: ConnectycubeMessage) { } override fun onMessageSentFailed(message: ConnectycubeMessage) { } }) ``` * SDK v1 kotlin (deprecated) ```kotlin val messageSentListener = object : ChatDialogMessageSentListener { override fun processMessageSent(dialogId: String, message: ConnectycubeChatMessage ) { } override fun processMessageFailed(dialogId: String, message: ConnectycubeChatMessage ) { } } val chatDialog = ... chatDialog.addMessageSentListener(messageSentListener) ``` * SDK v1 java (deprecated) ```java ChatDialogMessageSentListener messageSentListener = new ChatDialogMessageSentListener() { @Override public void processMessageSent(String dialogId, ConnectycubeChatMessage message) { } @Override public void processMessageFailed(String dialogId, ConnectycubeChatMessage message) { } }; ConnectycubeChatDialog chatDialog = ...; chatDialog.addMessageSentListener(messageSentListener); ``` ## ‘Delivered’ status [Section titled “‘Delivered’ status”](#delivered-status) The following callback is used to track the ‘delivered’ status: * SDK v2 kotlin ```kotlin ConnectyCube.chat.addMessageStatusListener(object: ConnectycubeMessageStatusListener { override fun onMessageDelivered(message: ConnectycubeMessage) { } override fun onMessageRead(message: ConnectycubeMessage) { } }) ``` * SDK v1 kotlin (deprecated) ```kotlin // call it after chat login val messageStatusesManager: MessageStatusesManager = ConnectycubeChatService.getInstance().messageStatusesManager val messageStatusListener = object : MessageStatusListener { override fun processMessageDelivered(messageId: String, dialogId: String, userId: Int ) { } override fun processMessageRead(messageId: String, dialogId: String, userId: Int ) { } } messageStatusesManager.addMessageStatusListener(messageStatusListener) ``` * SDK v1 java (deprecated) ```java private MessageStatusesManager messageStatusesManager; private MessageStatusListener messageStatusListener; // call it after chat login messageStatusesManager = ConnectycubeChatService.getInstance().getMessageStatusesManager(); messageStatusListener = new MessageStatusListener() { @Override public void processMessageDelivered(String messageId, String dialogId, Integer userId) { } @Override public void processMessageRead(String messageId, String dialogId, Integer userId) { } }; messageStatusesManager.addMessageStatusListener(messageStatusListener); ``` The SDK sends the ‘delivered’ status automatically when the message is received by the recipient. This is controlled by `chatMessage.setMarkable(true)` parameter when you send a message. If `markable` is `false` or omitted, then you can send the delivered status manually via Chat: * SDK v2 kotlin ```kotlin val chatDialog = ... val message = ... ConnectyCube.chat.sendDeliveredStatus(message) ``` * SDK v1 kotlin (deprecated) ```kotlin val chatDialog = ... val message = ... chatDialog.deliverMessage(message) ``` * SDK v1 java (deprecated) ```java ConnectycubeChatMessage message = ...; try { chatDialog.deliverMessage(message); } catch (XMPPException | SmackException.NotConnectedException e) { } ``` and via REST * SDK v2 kotlin ```kotlin val updatedParams = UpdateMessageParameters().also { it.delivered = true } ConnectyCube.updateMessage(messageId, dialogId, updatedParams.getRequestParameters(), { }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val messageUpdateBuilder = MessageUpdateBuilder().apply { markDelivered() } ConnectycubeRestChatService.updateMessage(messageId, dialogId, messageUpdateBuilder) .performAsync(object : EntityCallback { override fun onSuccess(result: Void?, params: Bundle?) { } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java MessageUpdateBuilder messageUpdateBuilder = new MessageUpdateBuilder(); messageUpdateBuilder.markDelivered(); ConnectycubeRestChatService.updateMessage(messageId, dialogId, messageUpdateBuilder).performAsync(new EntityCallback() { @Override public void onSuccess(Void result, Bundle params) { } @Override public void onError(ResponseException responseException) { } }); ``` ## ’Read’ status [Section titled “’Read’ status”](#read-status) Send the ‘read’ status: * SDK v2 kotlin ```kotlin ConnectyCube.chat.sendReadStatus(message) ``` * SDK v1 kotlin (deprecated) ```kotlin chatDialog.readMessage(chatMessage) ``` * SDK v1 java (deprecated) ```java try { chatDialog.readMessage(chatMessage); } catch (XMPPException | SmackException.NotConnectedException e) { } ``` Receive the ‘read’ status callback: * SDK v2 kotlin ```kotlin ConnectyCube.chat.addMessageStatusListener(object: ConnectycubeMessageStatusListener { override fun onMessageDelivered(message: ConnectycubeMessage) { } override fun onMessageRead(message: ConnectycubeMessage) { } }) ``` * SDK v1 kotlin (deprecated) ```kotlin // call it after chat login val messageStatusesManager = ConnectycubeChatService.getInstance().messageStatusesManager val messageStatusListener = object : MessageStatusListener { override fun processMessageDelivered(messageId: String, dialogId: String, userId: Int ) { } override fun processMessageRead(messageId: String, dialogId: String, userId: Int ) { } } messageStatusesManager.addMessageStatusListener(messageStatusListener) ``` * SDK v1 java (deprecated) ```java private MessageStatusesManager messageStatusesManager; private MessageStatusListener messageStatusListener; // call it after chat login messageStatusesManager = ConnectycubeChatService.getInstance().getMessageStatusesManager(); messageStatusListener = new MessageStatusListener() { @Override public void processMessageDelivered(String messageId, String dialogId, Integer userId) { } @Override public void processMessageRead(String messageId, String dialogId, Integer userId) { } }; messageStatusesManager.addMessageStatusListener(messageStatusListener); ``` ## ‘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: * SDK v2 kotlin ```kotlin ConnectyCube.chat.sendIsTypingStatus(chatDialog) ... ConnectyCube.chat.sendStopTypingStatus(chatDialog) ``` * SDK v1 kotlin (deprecated) ```kotlin chatDialog.sendIsTypingNotification() ... chatDialog.sendStopTypingNotification() ``` * SDK v1 java (deprecated) ```java try { chatDialog.sendIsTypingNotification(); } catch (XMPPException | SmackException.NotConnectedException e) { e.printStackTrace(); } ... try { chatDialog.sendStopTypingNotification(); } catch (XMPPException | SmackException.NotConnectedException e) { e.printStackTrace(); } ``` Receive the ‘is typing’ status callback: * SDK v2 kotlin ```kotlin ConnectyCube.chat.addTypingStatusListener(object: ConnectycubeChatTypingListener { override fun onUserIsTyping(dialogId: String?, userId: Int) { } override fun onUserStopTyping(dialogId: String?, userId: Int) { } }) ``` * SDK v1 kotlin (deprecated) ```kotlin val typingListener = object : ChatDialogTypingListener { override fun processUserIsTyping(dialogId: String, senderId: Int) { } override fun processUserStopTyping(dialogId: String, senderId: Int) { } } chatDialog.addIsTypingListener(typingListener) ``` * SDK v1 java (deprecated) ```java ChatDialogTypingListener typingListener = new ChatDialogTypingListener() { @Override public void processUserIsTyping(String dialogId, Integer senderId) { } @Override public void processUserStopTyping(String dialogId, Integer senderId) { } }; chatDialog.addIsTypingListener(typingListener); ``` ## Edit Message [Section titled “Edit Message”](#edit-message) The following snippet is used to edit chat message: * SDK v1 kotlin (deprecated) ```kotlin chatDialog.editMessageWithId("5356c64ab35c12bd3b10wa64", "Updated message body", true) ``` * SDK v1 java (deprecated) ```java try { dialog.editMessageWithId("5356c64ab35c12bd3b10wa64", "Updated message body", true); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } ``` Other users will receive the ‘update’ status callback: * SDK v1 kotlin (deprecated) ```kotlin val messageUpdateListener = MessageUpdateListener { messageID, dialogId, newBody, isLastMessage -> } ConnectycubeChatService.getInstance().messageStatusesManager.addMessageUpdateListener(messageUpdateListener) ``` * SDK v1 java (deprecated) ```java MessageUpdateListener messageUpdateListener = new MessageUpdateListener() { @Override public void processMessageUpdated(String messageID, String dialogId, String newBody, boolean isLastMessage) { } }; ConnectycubeChatService.getInstance().getMessageStatusesManager().addMessageUpdateListener(messageUpdateListener); ``` ## Delete chat messages [Section titled “Delete chat messages”](#delete-chat-messages) The following snippet is used to remove chat message via REST: * SDK v2 kotlin ```kotlin val messagesIds = listOf("546cc3240eda8f2dd7ee2291", "546cc3230eda8f2dd7ee2292") ConnectyCube.deleteMessages(messagesIds, true, successCallback = { deleteResult -> }, errorCallback = { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val messagesIds = HashSet().apply { add("546cc3240eda8f2dd7ee2291") add("546cc3230eda8f2dd7ee2292") } val forceDelete = false ConnectycubeRestChatService.deleteMessages(messagesIds, forceDelete) .performAsync(object : EntityCallback { override fun onSuccess(aVoid: Void?, bundle: Bundle?) { } override fun onError(e: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java Set messagesIds = new HashSet() {{ add("546cc3240eda8f2dd7ee2291"); add("546cc3230eda8f2dd7ee2292"); }}; boolean forceDelete = false; ConnectycubeRestChatService.deleteMessages(messagesIds, forceDelete).performAsync(new EntityCallback() { @Override public void onSuccess(Void aVoid, Bundle bundle) { } @Override public void onError(ResponseException e) { } }); ``` This request will remove the messages from current user history only, without affecting the history of other users. **The** `forceDelete` **parameter is used to completely remove messages.** The following snippet is used to remove chat message in a real time: * SDK v1 kotlin (deprecated) ```kotlin chatDialog.removeMessageWithId("5356c64ab35c12bd3b10wa64") ``` * SDK v1 java (deprecated) ```java try { dialog.removeMessageWithId("5356c64ab35c12bd3b10wa64"); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } ``` Other users will receive the ‘delete’ status callback: * SDK v1 kotlin (deprecated) ```kotlin val messageDeleteListener = MessageDeleteListener { messageID, dialogId -> //actions after success deleting message } ConnectycubeChatService.getInstance().messageStatusesManager.addMessageDeleteListener(messageDeleteListener) ``` * SDK v1 java (deprecated) ```java MessageDeleteListener messageDeleteListener = new MessageDeleteListener() { @Override public void processMessageDeleted(String messageID, String dialogId) { //actions after success deleting message } }; ConnectycubeChatService.getInstance().getMessageStatusesManager().addMessageDeleteListener(messageDeleteListener); ``` ## Self-destroy message [Section titled “Self-destroy message”](#self-destroy-message) Self-destroy messages is used if you want to implement some sort of Secret Chat where messages are visible only for some limited amount of time. It’s your responsibility to setup a timer in your app and remove messages from the client side. Self-destroy messages are not stored in server history. * SDK v1 kotlin (deprecated) ```kotlin val chatMessage = ConnectycubeChatMessage().apply { body = "Self destroy message" destroyAfter = 10 } chatDialog.sendMessage(chatMessage) chatDialog.addMessageListener(object : ChatDialogMessageListener { override fun processMessage(dialogId: String, message: ConnectycubeChatMessage, senderId: Int ) { if (message.destroyAfter > 0) { // setup a timer } } override fun processError(dialogId: String, exception: ChatException, message: ConnectycubeChatMessage, senderId: Int ) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeChatMessage chatMessage = new ConnectycubeChatMessage(); chatMessage.setBody("Self destroy message"); chatMessage.setDestroyAfter(10); try { chatDialog.sendMessage(chatMessage); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } chatDialog.addMessageListener(new ChatDialogMessageListener() { @Override public void processMessage(String dialogId, ConnectycubeChatMessage message, Integer senderId) { if (message.getDestroyAfter() > 0) { // setup a timer } } }); ``` ## Attachments [Section titled “Attachments”](#attachments) ### Image/Video [Section titled “Image/Video”](#imagevideo) 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. * SDK v2 kotlin ```kotlin val messageAttachment = File("some_image.png") val fileIsPublic = false ConnectyCube.uploadFile(messageAttachment.path, fileIsPublic, successCallback = { cubeFile -> // create a message val chatMessage = ConnectycubeMessage() chatMessage.saveToHistory = true // attach a photo val attachment = ConnectycubeAttachment("photo") attachment.id = cubeFile.id.toString() chatMessage.attachments?.add(attachment) // send a chat message // ... }, errorCallback = { ex -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val messageAttachment = File("some_image.png") val fileIsPublic = false ConnectycubeStorage.uploadFileTask( messageAttachment, fileIsPublic ) { progress -> }.performAsync(object : EntityCallback { override fun onSuccess(storageFile: ConnectycubeFile, bundle: Bundle? ) { // create a message val chatMessage = ConnectycubeChatMessage() chatMessage.setSaveToHistory(true) // attach a photo val attachment = ConnectycubeAttachment("photo") attachment.id = storageFile.id.toString() chatMessage.addAttachment(attachment) // send a chat message // ... } override fun onError(e: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java File messageAttachment = new File("some_image.png"); Boolean fileIsPublic = false; ConnectycubeStorage.uploadFileTask(messageAttachment, fileIsPublic, new ConnectycubeProgressCallback() { @Override public void onProgressUpdate(int progressPercentages) { } }).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeFile storageFile, Bundle bundle) { // create a message ConnectycubeChatMessage chatMessage = new ConnectycubeChatMessage(); chatMessage.setSaveToHistory(true); // attach a photo ConnectycubeAttachment attachment = new ConnectycubeAttachment("photo"); attachment.setId(file.getId().toString()); chatMessage.addAttachment(attachment); // send a chat message // ... } @Override public void onError(ResponseException e) { } }); ``` 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. * SDK v2 kotlin ```kotlin // ConnectycubeMessageListener ... override fun onMessage(message: ConnectycubeMessage) { message.attachments?.forEach { attachment -> val privateAvatarUrl = getPrivateUrlForUID(attachment.url) } // process url } ``` * SDK v1 kotlin (deprecated) ```kotlin // ChatDialogMessageListener ... override fun processMessage(dialogId: String, chatMessage: ConnectycubeChatMessage, senderId: Int ) { for (attachment in chatMessage.attachments) { val fileId = attachment.id // download a file ConnectycubeStorage.downloadFile(fileId).performAsync(object : EntityCallback { override fun onSuccess(inputStream: InputStream, params: Bundle ) { // process file } override fun onError(errors: ResponseException) { } }) } } ``` * SDK v1 java (deprecated) ```java // ChatDialogMessageListener ... @Override public void processMessage(String dialogId, ConnectycubeChatMessage chatMessage, Integer senderId) { for (ConnectycubeAttachment attachment : chatMessage.getAttachments()){ String fileId = attachment.getId(); // download a file ConnectycubeStorage.downloadFile(fileId).performAsync(new EntityCallback(){ @Override public void onSuccess(InputStream inputStream, Bundle params) { // process file } @Override public void onError(ResponseException errors) { } }); } } ``` ### Contact [Section titled “Contact”](#contact) A contact profile can be send via chat attachments as well: * SDK v2 kotlin ```kotlin // create a message val chatMessage = ConnectycubeMessage(saveToHistory = true) // build a contact representation val jc = JsonObject().apply { add("phone", JsonPrimitive("180032323223")) add("name", JsonPrimitive("Samuel Johnson")) } // attach a contact val attachment = ConnectycubeAttachment("contact").apply { data = jc.toString() } chatMessage.attachments?.add(attachment) // send a chat message // ... ``` * SDK v1 kotlin (deprecated) ```kotlin // create a message val chatMessage = ConnectycubeChatMessage().apply { setSaveToHistory(true) } // build a contact representation val jc = JsonObject().apply { add("phone", JsonPrimitive("180032323223")) add("name", JsonPrimitive("Samuel Johnson")) } // attach a contact val attachment = ConnectycubeAttachment("contact").apply { data = jc.toString() } chatMessage.addAttachment(attachment) // send a chat message // ... ``` * SDK v1 java (deprecated) ```java // create a message ConnectycubeChatMessage chatMessage = new ConnectycubeChatMessage(); chatMessage.setSaveToHistory(true); // build a contact representation JsonObject jc = new JsonObject(); jc.add("phone", new JsonPrimitive("180032323223")); jc.add("name", new JsonPrimitive("Samuel Johnson")); // attach a contact ConnectycubeAttachment attachment = new ConnectycubeAttachment("contact"); attachment.setData(jc.toString()); chatMessage.addAttachment(attachment); // send a chat message // ... ``` On the receiver’s side, when you receive a message, you need to get a contact data from an attachment: - SDK v2 kotlin ```kotlin // ConnectycubeMessageListener override fun onMessage(message: ConnectycubeMessage) { for (attachment in message.attachments!!) { val data = attachment.data val obj = JsonParser().parse(data).asJsonObject val phone = obj.getAsJsonPrimitive("phone").asString val name = obj.getAsJsonPrimitive("name").asString } } ``` - SDK v1 kotlin (deprecated) ```kotlin // ChatDialogMessageListener ... override fun processMessage(dialogId: String, chatMessage: ConnectycubeChatMessage, senderId: Int ) { for (attachment in chatMessage.attachments) { val data = attachment.data val obj = JsonParser().parse(data).asJsonObject val phone = obj.getAsJsonPrimitive("phone").asString val name = obj.getAsJsonPrimitive("name").asString } } ``` - SDK v1 java (deprecated) ```java // ChatDialogMessageListener ... @Override public void processMessage(String dialogId, ConnectycubeChatMessage chatMessage, Integer senderId) { for (ConnectycubeAttachment attachment : chatMessage.getAttachments()){ String data = attachment.getData(); JsonObject obj = new JsonParser().parse(data).getAsJsonObject(); String phone = obj.getAsJsonPrimitive("phone").getAsString(); String name = obj.getAsJsonPrimitive("name").getAsString(); } } ``` ## 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 ```kotlin val messageId = "58e6a9c8a1834a3ea6001f15" val reaction = "🔥" ConnectyCube.addMessageReaction(messageId, reaction, { }, { error -> }) ``` Remove ```kotlin val messageId = "58e6a9c8a1834a3ea6001f15" val reaction = "👎" ConnectyCube.removeMessageReaction(messageId, reaction, { }, { error -> }) ``` Add/Remove ```kotlin val messageId = "58e6a9c8a1834a3ea6001f15" val reactionToAdd = "👎" val reactionToRemove = "🚀" ConnectyCube.updateMessageReaction(messageId, reactionToAdd, reactionToRemove, { }, { error -> }) ``` ### Listen reactions [Section titled “Listen reactions”](#listen-reactions) ```kotlin ConnectyCube.chat.addMessageReactionsListener(object : ConnectycubeMessageReactionsListener { override fun onMessageReaction(reaction: ConnectycubeReaction) { // var dialogId = reaction.dialogId // var messageId = reaction.messageId // var addReaction = reaction.addReaction // var removeReaction = reaction.removeReaction } }) ``` ### List message reactions [Section titled “List message reactions”](#list-message-reactions) User can list message reactions ```kotlin val messageId = "58e6a9c8a1834a3ea6001f15" ConnectyCube.getMessageReactions(messageId, { reactions -> // the result contains the map where key is the reaction and value is the list of users' ids who reacted with this reaction }, { error -> }) ``` Response example from `getMessageReactions(messageId)` - [see](/server/chat#response-22) ## Unread messages count [Section titled “Unread messages count”](#unread-messages-count) You can request total unread messages count and unread count for particular conversation: * SDK v2 kotlin ```kotlin val dialogsIds: MutableList = mutableListOf("546cc3240eda8f2dd7ee2291", "546cc3230eda8f2dd7ee2292") ConnectyCube.getUnreadMessagesCount(dialogsIds, successCallback = { result -> Log.i(TAG, "total unread messages: ${result["546cc3240eda8f2dd7ee2291"]}") Log.i(TAG, "total unread messages: ${result["546cc3230eda8f2dd7ee2292"]}") }, errorCallback = { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val dialogsIds: MutableSet = HashSet().apply { add("546cc3240eda8f2dd7ee2291") add("546cc3230eda8f2dd7ee2292") } val returnBundle = Bundle() ConnectycubeRestChatService.getTotalUnreadMessagesCount(dialogsIds, returnBundle) .performAsync(object : EntityCallback { override fun onSuccess(total: Int, params: Bundle) { Log.i(TAG, "total unread messages: $total") Log.i(TAG, "dialog Unread1: ${params?.getInt("546cc3240eda8f2dd7ee2291")}") Log.i(TAG, "dialog Unread2: ${params?.getInt("546cc3230eda8f2dd7ee2292")}") } override fun onError(e: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java Set dialogsIds = new HashSet(); dialogsIds.add("546cc3240eda8f2dd7ee2291"); dialogsIds.add("546cc3230eda8f2dd7ee2292"); ConnectycubeRestChatService.getTotalUnreadMessagesCount(dialogsIds, new Bundle()).performAsync(new EntityCallback() { @Override public void onSuccess(Integer total, Bundle params) { Log.i(TAG, "total unread messages: " + total); Log.i(TAG, "dialog Unread1: " + params.getInt("546cc3240eda8f2dd7ee2291")); Log.i(TAG, "dialog Unread2: " + params.getInt("546cc3230eda8f2dd7ee2292")); } @Override public void onError(ResponseException e) { } }); ``` ## Global search [Section titled “Global search”](#global-search) **Global search** feature was developed to simplify search of dialogs, messages and users at the same time. Similar functionality is used in most popular messengers and you can implement it in your app using Connectycube SDK. Just use request from snippet below. `SearchRequestBuilder` is **optional** parameter and it can be `null` if you don’t need additional configs for search request. * SDK v2 kotlin ```kotlin val searchText = "dialog name" // String or word. Should be longer than 4 symbols. Performs 'or' search. // For an exact search, you need to wrap the search phrase in quotes. val searchParams: GlobalSearchParams = GlobalSearchParams() //class-helper to simple config search request searchParams.dialogIds = dialogsIds // List of dialog ids. Max cam include 10 items. Optional parameter. searchParams.startDate = startDate // Closest date to now. Uses lte comparison. Optional parameter. searchParams.endDate = endDate // Shouldn't differ by more than 3 months from the start_date. Uses gte comparison. Optional parameter. searchParams.limit = 3 // Maximum number of items returned from the server in the search results. Max value - 100. Optional parameter. ConnectyCube.searchText(searchText, searchParams.getSearchParams(), successCallback = { searchResult -> val dialogs = searchResult.dialogs // found dialogs val messages = searchResult.messages // found messages val users = searchResult.users // found users }, errorCallback = { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val searchText // String or word. Should be longer than 4 symbols. Performs 'or' search. // For an exact search, you need to wrap the search phrase in quotes. val searchRequestBuilder = SearchRequestBuilder().apply { setDialogsIds(dialogsIds) // List of dialog ids. Max cam include 10 items. Optional parameter. setStartDate(startDate) // Closest date to now. Uses lte comparison. Optional parameter. setEndDate(endDate) // Shouldn't differ by more than 3 months from the start_date. Uses gte comparison. Optional parameter. limit = 3 // Maximum number of items returned from the server in the search results. Max value - 100. Optional parameter. } ConnectycubeRestChatService.searchByText(searchText, searchRequestBuilder).performAsync(object : EntityCallback { override fun onSuccess(result: SearchChatEntity, params: Bundle) { val dialogs = result.dialogs // found dialogs val messages = result.messages // found messages val users = result.users // found users } override fun onError(e: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java String searchText; // String or word. Should be longer than 4 symbols. Performs 'or' search. // For an exact search, you need to wrap the search phrase in quotes. SearchRequestBuilder searchRequestBuilder = new SearchRequestBuilder(); searchRequestBuilder.setDialogsIds(dialogsIds); // List of dialog ids. Max cam include 10 items. Optional parameter. searchRequestBuilder.setStartDate(startDate); // Closest date to now. Uses lte comparison. Optional parameter. searchRequestBuilder.setEndDate(endDate); // Shouldn't differ by more than 3 months from the start_date. Uses gte comparison. Optional parameter. searchRequestBuilder.setLimit(3); // Maximum number of items returned from the server in the search results. Max value - 100. Optional parameter. ConnectycubeRestChatService.searchByText(searchText, searchRequestBuilder).performAsync(new EntityCallback() { @Override public void onSuccess(SearchChatEntity result, Bundle params) { ArrayList dialogs = result.getDialogs(); // found dialogs ArrayList messages = result.getMessages(); // found messages ArrayList users = result.getUsers(); // found users } @Override public void onError(ResponseException responseException) { } }); ``` ## 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](/android/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 `properties` field of the instance of a `ConnectycubeMessage`. * SDK v2 kotlin ```kotlin val message = ConnectycubeMessage() message.properties["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. ## Chat notifications settings [Section titled “Chat notifications settings”](#chat-notifications-settings) ### 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. * SDK v2 kotlin ```kotlin val dialogId = "5356c64ab35c12bd3b108a41" val enabled = false //false - to disable push notification, true - to enable ConnectyCube.updateDialogNotificationsSettings(dialogId, enabled, { result -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val dialogId = "5356c64ab35c12bd3b108a41" val enabled = false //false - to disable push notification, true - to enable ConnectycubeRestChatService.updateDialogNotificationSending(dialogId, enabled).performAsync(object : EntityCallback { override fun onSuccess(result: Boolean, params: Bundle) { //if result == false - push notifications was disabled, otherwise - enabled } override fun onError(e: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java String dialogId = "5356c64ab35c12bd3b108a41"; boolean enabled = false; //false - to disable push notification, true - to enable ConnectycubeRestChatService.updateDialogNotificationSending(dialogId, enabled).performAsync(new EntityCallback() { @Override public void onSuccess(Boolean result, Bundle params) { //if result == false - push notifications was disabled, otherwise - enabled } @Override public void onError(ResponseException e) { } }); ``` ### 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. * SDK v2 kotlin ```kotlin val dialogId = "5356c64ab35c12bd3b108a41" ConnectyCube.getDialogNotificationsSettings(dialogId, { enabled -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val dialogId = "5356c64ab35c12bd3b108a41" ConnectycubeRestChatService.checkIsDialogNotificationEnabled(dialogId).performAsync(object : EntityCallback { override fun onSuccess(result: Boolean, params: Bundle) { //result == false - push notifications are disabled, //result == true - push notifications are enabled } override fun onError(e: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java String dialogId = "5356c64ab35c12bd3b108a41"; ConnectycubeRestChatService.checkIsDialogNotificationEnabled(dialogId).performAsync(new EntityCallback() { @Override public void onSuccess(Boolean result, Bundle params) { //result == false - push notifications are disabled, //result == true - push notifications are enabled } @Override public void onError(ResponseException e) { } }); ``` ## 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 notifications while still is connected to chat. For this particular case we have 2 handy methods: ‘enterInactiveState’ and ‘enterActiveState’: ```java ConnectycubeChatService.getInstance().enterInactiveState(); ``` ```java ConnectycubeChatService.getInstance().enterActiveState(); ``` The common use case for these APIs is to call ‘enterInactiveState’ when an app goes to background mode and to call ‘enterActiveState’ 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. * SDK v1 kotlin (deprecated) ```kotlin val userId = 12345 val seconds = ConnectycubeChatService.getInstance().getLastUserActivity(userId) // seconds - the difference in seconds from current time to last user activity in the chat or 0 if user is online. ``` * SDK v1 java (deprecated) ```java int userId = 12345; double seconds = ConnectycubeChatService.getInstance().getLastUserActivity(userId); // seconds - the difference in seconds from current time to last user activity in the chat or 0 if user is online. ``` ## System messages [Section titled “System messages”](#system-messages) There is a way to send system messages to other users about some events. System messages work on a separate channel and are not mixed with regular chat messages: > System messages are not stored on a server. It means messages will be delivered only to online users. * SDK v2 kotlin ```kotlin ConnectyCube.chat.addSystemMessageListener(object : ConnectycubeSystemMessageListener { override fun onMessage(message: ConnectycubeMessage) { } override fun onError(message: ConnectycubeMessage, ex: Throwable) { } }) ConnectyCube.chat.sendSystemMessage(message { recipientId = 58672 properties["param1"] = "value1" properties["param2"] = "value2" body = "some text" }) ``` * SDK v1 kotlin (deprecated) ```kotlin val systemMessagesManager = ConnectycubeChatService.getInstance().systemMessagesManager val systemMessageListener = object : SystemMessageListener { override fun processMessage(message: ConnectycubeChatMessage) { } override fun processError(exception: ChatException, message: ConnectycubeChatMessage ) { } } systemMessagesManager.addSystemMessageListener(systemMessageListener) val systemMessage = ConnectycubeChatMessage().apply { recipientId = 58672 setProperty("param1", "value1") setProperty("param2", "value2") body = "some text" } systemMessagesManager.sendSystemMessage(systemMessage) ``` * SDK v1 java (deprecated) ```java SystemMessagesManager systemMessagesManager = ConnectycubeChatService.getInstance().getSystemMessagesManager(); SystemMessageListener systemMessageListener = new SystemMessageListener() { @Override public void processMessage(ConnectycubeChatMessage message) { } @Override public void processError(ChatException exception, ConnectycubeChatMessage message) { } }; systemMessagesManager.addSystemMessageListener(systemMessageListener); ConnectycubeChatMessage systemMessage = new ConnectycubeChatMessage(); systemMessage.setRecipientId(58672); systemMessage.setProperty("param1", "value1"); systemMessage.setProperty("param2", "value2"); systemMessage.setBody("some text"); try { systemMessagesManager.sendSystemMessage(systemMessage); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } ``` ## 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  Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```kotlin val customObject: ConnectycubeCustomObject = ConnectycubeCustomObject("UserReports") customObject.fields = hashMapOf ( "reportedUserId" to 45, "reason" to "User is spamming with bad words", ) ConnectyCube.createCustomObject(customObject, { createdObject -> }, { ex -> }) ``` ### 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: ```kotlin val customObject: ConnectycubeCustomObject = ConnectycubeCustomObject("MessageReports") customObject.fields = hashMapOf ( "reportedMessageId" to "58e6a9c8a1834a3ea6001f15", "reason" to "The message contains phishing links", ) ConnectyCube.createCustomObject(customObject, { createdObject -> }, { ex -> }) ``` ### 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. **If no elements specified, then the list with given name will be deleted.** * SDK v1 kotlin (deprecated) ```kotlin val list = ConnectycubePrivacyList().apply { name = "myList" } val items = ArrayList() val item1 = ConnectycubePrivacyListItem().apply { isAllow = false type = ConnectycubePrivacyListItem.Type.USER_ID valueForType = 3678.toString() isMutualBlock = true } items.add(item1) list.items = items val privacyListsManager = chatService.privacyListsManager privacyListsManager.createPrivacyList(list) ``` * SDK v1 java (deprecated) ```java ConnectycubePrivacyList list = new ConnectycubePrivacyList(); list.setName("myList"); ArrayList items = new ArrayList(); ConnectycubePrivacyListItem item1 = new ConnectycubePrivacyListItem(); item1.setAllow(false); item1.setType(ConnectycubePrivacyListItem.Type.USER_ID); item1.setValueForType(String.valueOf(3678)); item1.setMutualBlock(true); items.add(item1); list.setItems(items); PrivacyListsManager privacyListsManager = chatService.getPrivacyListsManager(); try { privacyListsManager.createPrivacyList(list); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } catch (XMPPException.XMPPErrorException e) { e.printStackTrace(); } catch (SmackException.NoResponseException e) { e.printStackTrace(); } ``` The `ConnectycubePrivacyListItem` class takes 4 arguments: * **type** - use **USER\_ID** to block a user in 1-1 chat or **GROUP\_USER\_ID** to block in a group chat. * **valueForType** - ID of a user to apply an action * **allow** - can be true/false. * **mutualBlock** - can be true/false - to block user’s message in both directions or not. > 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: * SDK v1 kotlin (deprecated) ```kotlin privacyListsManager.applyPrivacyList("myList") ``` * SDK v1 java (deprecated) ```java try { privacyListsManager.applyPrivacyList("myList"); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } catch (XMPPException.XMPPErrorException e) { e.printStackTrace(); } catch (SmackException.NoResponseException e) { e.printStackTrace(); } ``` #### Update privacy list [Section titled “Update privacy list”](#update-privacy-list) There are some rules you should follow to update a privacy list: * Include all of the desired items (not a “delta”). * If you want to update or set new privacy list instead of current one, you should decline current default list first. - SDK v1 kotlin (deprecated) ```kotlin // Deactivate active list privacyListsManager.declinePrivacyList() // Create new list // ... // Activate again active list privacyListsManager.applyPrivacyList("myList") ``` - SDK v1 java (deprecated) ```java // Deactivate active list try { privacyListsManager.declinePrivacyList(); } catch (SmackException|XMPPException.XMPPErrorException e) { } // Create new list // ... // Activate again active list try { privacyListsManager.applyPrivacyList("myList"); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } catch (XMPPException.XMPPErrorException e) { e.printStackTrace(); } catch (SmackException.NoResponseException e) { e.printStackTrace(); } ``` #### Retrieve privacy lists [Section titled “Retrieve privacy lists”](#retrieve-privacy-lists) To get a list of all your privacy lists use the following request: * SDK v1 kotlin (deprecated) ```kotlin val privacyListsManager = ConnectycubeChatService.getInstance().privacyListsManager val lists = privacyListsManager.privacyLists ``` * SDK v1 java (deprecated) ```java PrivacyListsManager privacyListsManager = ConnectycubeChatService.getInstance().getPrivacyListsManager() List lists = null; try { lists = privacyListsManager.getPrivacyLists(); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } catch (XMPPException.XMPPErrorException e) { e.printStackTrace(); } catch (SmackException.NoResponseException e) { e.printStackTrace(); } ``` #### 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: * SDK v1 kotlin (deprecated) ```kotlin var list = privacyListsManager.getPrivacyList("myList") ``` * SDK v1 java (deprecated) ```java ConnectycubePrivacyList list = null; try { list = privacyListsManager.getPrivacyList("myList"); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } catch (XMPPException.XMPPErrorException e) { e.printStackTrace(); } catch (SmackException.NoResponseException e) { e.printStackTrace(); } ``` #### Remove privacy list [Section titled “Remove privacy list”](#remove-privacy-list) > Note: Before deleting privacy list you should decline it. * SDK v1 kotlin (deprecated) ```kotlin privacyListsManager.declinePrivacyList() privacyListsManager.deletePrivacyList("myList") ``` * SDK v1 java (deprecated) ```java try { privacyListsManager.declinePrivacyList(); privacyListsManager.deletePrivacyList("myList"); } catch (SmackException.NotConnectedException e) { e.printStackTrace(); } catch (XMPPException.XMPPErrorException e) { e.printStackTrace(); } catch (SmackException.NoResponseException e) { e.printStackTrace(); } ``` #### 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: * SDK v1 kotlin (deprecated) ```kotlin val chatMessage = ConnectycubeChatMessage().apply { body = "How is going on?" } val chatDialog = ... chatDialog.sendMessage(chatMessage) ... chatDialog.addMessageListener(object : ChatDialogMessageListener { override fun processMessage(dialogId: String, message: ConnectycubeChatMessage, senderId: Int ) {} override fun processError(dialogId: String, exception: ChatException, message: ConnectycubeChatMessage, senderId: Int ) { Log.e(TAG, "processError: " + exception.localizedMessage) } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeChatMessage chatMessage = new ConnectycubeChatMessage(); chatMessage.setBody("How is going on?"); ConnectycubeChatDialog chatDialog = ...; chatDialog.sendMessage(chatMessage); ... privateDialog.addMessageListener(new ChatDialogMessageListener() { @Override public void processMessage(String dialogId, ConnectycubeChatMessage message, Integer senderId) { } @Override public void processError(String dialogId, ChatException exception, ConnectycubeChatMessage message, Integer senderId) { log("processError: " + exception.getLocalizedMessage()); } }); ``` ```log Log output: processError: Service not available. ``` ## 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: * SDK v2 kotlin ```kotlin //coming soon ```
# Video Calling
> Empower your Android applications with our 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. ## Connect VideoChat SDK [Section titled “Connect VideoChat SDK”](#connect-videochat-sdk) To include video chat capabilities into your app you need to include the relevant dependencies in **build.gradle** project file (only for V1): #### SDK v1 (deprecated) [Section titled “SDK v1 (deprecated)”](#sdk-v1-deprecated) ```groovy dependencies { implementation "com.connectycube:connectycube-android-sdk-videochat:x.x.x" } ``` ## Preparations [Section titled “Preparations”](#preparations) ### Permissions [Section titled “Permissions”](#permissions) The video chat module requires camera, microphone, internet and storage permissions. Make sure you add relevant permissions to your app manifest: ```xml ``` You can get more info on how to work with app permissions at [Android Permissions Overview](https://developer.android.com/guide/topics/permissions/overview) ### Add signaling manager [Section titled “Add signaling manager”](#add-signaling-manager) [ConnectyCube Chat API](/android/messaging) is used as a signaling transport for Video Calling API, so in order to start using Video Calling API you need to [connect to Chat](/android/messaging#connect-to-chat). To be able to receive incoming video chat calls, you need to add WebRTC signaling to `RTCClient`: * SDK v2 kotlin ```kotlin //no need to add signaling manually ``` * SDK v1 kotlin (deprecated) ```kotlin ConnectycubeChatService.getInstance().videoChatWebRTCSignalingManager?.addSignalingManagerListener(object: VideoChatSignalingManagerListener { override fun signalingCreated(signaling: Signaling, createdLocally: Boolean) { if (!createdLocally) { RTCClient.getInstance(context).addSignaling(signaling as WebRTCSignaling) } } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeChatService.getInstance().getVideoChatWebRTCSignalingManager().addSignalingManagerListener(new VideoChatSignalingManagerListener() { @Override public void signalingCreated(Signaling signaling, boolean createdLocally) { if (!createdLocally) { RTCClient.getInstance(this).addSignaling((WebRTCSignaling) signaling); } } }); ``` ### Prepare an Activity [Section titled “Prepare an Activity”](#prepare-an-activity) To be able to receive callbacks about current `RTCSession` instance state, about video tracks (local and remotes) and session’s peer connections states\ you must implement appropriate interfaces by calling the following methods on `RTCSession` instance: * SDK v2 kotlin ```kotlin fun addSessionStateCallbacksListener(callback: RTCSessionStateCallback) fun addVideoTrackCallbacksListener(callback: VideoTracksCallback) ``` * SDK v1 kotlin (deprecated) ```kotlin fun addSessionCallbacksListener(callback: RTCSessionConnectionCallbacks) fun addVideoTrackCallbacksListener(callback: RTCClientVideoTracksCallback) ``` * SDK v1 java (deprecated) ```java public void addSessionCallbacksListener(RTCSessionConnectionCallbacks callback) public void addVideoTrackCallbacksListener(RTCClientVideoTracksCallback callback) ``` and also the following method on `RTCClient` or `P2PCalls` instance: * SDK v2 kotlin ```kotlin fun addSessionCallbacksListener(callback: RTCSessionEventsCallback) ``` * SDK v1 kotlin (deprecated) ```kotlin fun addSessionCallbacksListener(callback: RTCClientSessionCallbacks) ``` * SDK v1 java (deprecated) ```java public void addSessionCallbacksListener(RTCClientSessionCallbacks callback) ``` ### Setup views [Section titled “Setup views”](#setup-views) Set up your layout views for remote and local video tracks: * SDK v2 kotlin ```xml ``` * SDK v1 kotlin (deprecated) / SDK v1 java (deprecated) ```xml ``` ```xml ``` `RTCSurfaceView` allows using several views on screen layout and to overlap each other which is a good feature for group video calls. `RTCSurfaceView` is a surface view (it extends `org.webrtc.SurfaceViewRenderer` class) that renderers video track. It has its own lifecycle for rendering. It uses `init()` method for preparing to render and `release()` to release resource when video track does not exist any more. `RTCSurfaceView` is automatically initialized after the surface is created - in `surfaceCreated()` method callback. You can manually initialize `RTCSurfaceView` using `Egl` context getting from `RTCClient`. Use this method only when Activity is alive and GL resources exist: * SDK v2 kotlin ```kotlin val surfaceView: RTCSurfaceView = ... val eglContext = EglBaseContext.getEglContext() surfaceView.init(eglContext.eglBaseContext, null) ``` * SDK v1 kotlin (deprecated) ```kotlin val surfaceView: RTCSurfaceView = ... val eglContext = RTCClient.getInstance(context).eglContext surfaceView.init(eglContext.eglBaseContext, null) ``` * SDK v1 java (deprecated) ```java RTCSurfaceView surfaceView = ...; EglBase eglContext = RTCClient.getInstance(getContext()).getEglContext(); surfaceView.init(eglContext.getEglBaseContext(), null); ``` Method `release()` should be called when video track is no more valid, for example, when you receive `onConnectionClosedForUser()` callback from `RTCSession` or when `RTCSession` is going to close. But you should call `release()` method before `Activity` is destroyed and while `EGLContext` is still valid. If you don’t call this method, the GL resources might leak. Here is the `RTCSurfaceView` interface: * SDK v2 kotlin ```kotlin RTCSurfaceView.init(EglBase.Context, RendererCommon.RendererEvents) //Initialize this view using webrtc Egl context, It is allowed to call init() to reinitialize the view after a previous init()/release() cycle. RTCSurfaceView.release() // releases all related GL resources RTCSurfaceView.setScalingType(RendererCommon.ScalingType) //Set how the video will fill the allowed layout area RTCSurfaceView.setMirror(Boolean) //Set if the video stream should be mirrored or not. RTCSurfaceView.requestLayout() // Request to invalidate view when something has changed ``` * SDK v1 kotlin (deprecated) ```kotlin RTCSurfaceView.init(EglBase.Context, RendererCommon.RendererEvents) //Initialize this view using webrtc Egl context, It is allowed to call init() to reinitialize the view after a previous init()/release() cycle. RTCSurfaceView.release() // releases all related GL resources RTCSurfaceView.setScalingType(RendererCommon.ScalingType) //Set how the video will fill the allowed layout area RTCSurfaceView.setMirror(Boolean) //Set if the video stream should be mirrored or not. RTCSurfaceView.requestLayout() // Request to invalidate view when something has changed ``` * SDK v1 java (deprecated) ```java RTCSurfaceView.init(EglBase.Context, RendererCommon.RendererEvents);//Initialize this view using webrtc Egl context, It is allowed to call init() to reinitialize the view after a previous init()/release() cycle. RTCSurfaceView.release(); // releases all related GL resources RTCSurfaceView.setScalingType(scalingType); //Set how the video will fill the allowed layout area RTCSurfaceView.setMirror(mirror); //Set if the video stream should be mirrored or not. RTCSurfaceView.requestLayout(); // Request to invalidate view when something has changed ``` To render received video track from an opponent use the following snippet: * SDK v2 kotlin ```kotlin fun fillVideoView(userId: Int, videoView: RTCSurfaceView, videoTrack: ConnectycubeVideoTrack ) { videoTrack.addSink(videoView.videoSink) } ``` * SDK v1 kotlin (deprecated) ```kotlin fun fillVideoView(userId: Int, videoView: RTCSurfaceView, videoTrack: RTCVideoTrack ) { videoTrack.addRenderer(videoView) } ``` * SDK v1 java (deprecated) ```java private void fillVideoView(int userId, RTCSurfaceView videoView, RTCVideoTrack videoTrack) { videoTrack.addRenderer(new VideoRenderer(videoView)); } ``` ### Notify RTCClient you are ready for processing calls [Section titled “Notify RTCClient you are ready for processing calls”](#notify-rtcclient-you-are-ready-for-processing-calls) As soon as your app is ready for calls processing and activity exists, use the following snippet in activity class: * SDK v2 kotlin ```kotlin //do nothing ``` * SDK v1 kotlin (deprecated) ```java RTCClient.getInstance(context).prepareToProcessCalls() ``` * SDK v1 java (deprecated) ```java RTCClient.getInstance(this).prepareToProcessCalls(); ``` > **Note**: Pay attention: if you forget to add signaling manager, you will not be able to process calls. ## Initiate a call [Section titled “Initiate a call”](#initiate-a-call) To call users you should create a session and start a call: * SDK v2 kotlin ```kotlin val opponents: MutableList = ArrayList() opponents.add(21) // User can pass an additional info along with the call request val userInfo: HashMap = HashMap() userInfo["key1"] = "value1" //Init session val session = P2PCalls.createSession(opponents, CallType.VIDEO) session.startCall(userInfo) ``` * SDK v1 kotlin (deprecated) ```kotlin val opponents: MutableList = ArrayList() opponents.add(21) // User can pass an additional info along with the call request val userInfo: MutableMap = HashMap() userInfo["key1"] = "value1" //Init session val session = RTCClient.getInstance(this) .createNewSessionWithOpponents(opponents, RTCTypes.ConferenceType.CONFERENCE_TYPE_VIDEO) session.startCall(userInfo) ``` * SDK v1 java (deprecated) ```java List opponents = new ArrayList(); opponents.add(21); // User can pass an additional info along with the call request Map userInfo = new HashMap<>(); userInfo.put("key1", "value1"); //Init session RTCSession session = RTCClient.getInstance(this).createNewSessionWithOpponents(opponents, CONFERENCE_TYPE_VIDEO); session.startCall(userInfo); ``` After this, your opponents will receive a call request callback `onReceiveNewSession` via `RTCClientSessionCallbacks` (read below). ## Track session callbacks [Section titled “Track session callbacks”](#track-session-callbacks) For managing all session’s states you need to implement interface `RTCClientSessionCallbacks`. * SDK v2 kotlin ```kotlin // implement interface RTCCallSessionCallback P2PCalls.addSessionCallbacksListener(this) P2PCalls.removeSessionCallbacksListener(this) ``` * SDK v1 kotlin (deprecated) ```kotlin RTCClient.getInstance(this).addSessionCallbacksListener(this) RTCClient.getInstance(this).removeSessionsCallbacksListener(this) ``` * SDK v1 java (deprecated) ```java RTCClient.getInstance(this).addSessionCallbacksListener(this); RTCClient.getInstance(this).removeSessionsCallbacksListener(this); ``` Once you called `RTCClient.getInstance(this).prepareToProcessCalls()` method and added an instance of class, that implements `RTCClientSessionCallbacks`, to `RTCClient`, via method `RTCClient.getInstance(this).addSessionCallbacksListener(listener)`, you should start receiving sessions callbacks. The interface of `RTCClientSessionCallbacks` is the following: * SDK v2 kotlin ```kotlin /** * Called each time when new session request is received. */ fun onReceiveNewSession(session: P2PSession) /\*\* - Called in case when user didn't answer within timer expiration period \*/ override fun onUserNotAnswer(session: P2PSession, opponentId: Int) {} /\*\* - Called in case when opponent has rejected your call \*/ override fun onCallRejectByUser((session: P2PSession, opponentId: Int, userInfo: Map?) {} /\*\* - Called in case when opponent has accepted your call \*/ override fun onCallAcceptByUser(session: P2PSession, opponentId: Int, userInfo: Map?) {} /\*\* - Called in case when opponent hung up \*/ override fun onReceiveHangUpFromUser(session: P2PSession, opponentId: Int, userInfo: Map?) {} /\*\* - Called in case when user didn't make any actions on received session \*/ override fun onUserNoActions(session: P2PSession, userId: Int) {} /\*\* - Called in case when session will close \*/ override fun onSessionStartClose(session: P2PSession) {} /\*\* - Called when session is closed. \*/ override fun onSessionClosed(session: P2PSession) {} ``` * SDK v1 kotlin (deprecated) ```kotlin /** * Called each time when new session request is received. */ override fun onReceiveNewSession(session: RTCSession) {} /** * Called in case when user didn't answer within timer expiration period */ override fun onUserNotAnswer(session: RTCSession, userId: Int) {} /** * Called in case when opponent has rejected your call */ override fun onCallRejectByUser(session: RTCSession, userId: Int, userInfo: MutableMap?) {} /** * Called in case when opponent has accepted your call */ override fun onCallAcceptByUser(session: RTCSession, userId: Int, userInfo: MutableMap?) {} /** * Called in case when opponent hung up */ override fun onReceiveHangUpFromUser(session: RTCSession, userId: Int, userInfo: MutableMap?) {} /** * Called in case when user didn't make any actions on received session */ override fun onUserNoActions(session: RTCSession, userId: Int) {} /** * Called in case when session will close */ override fun onSessionStartClose(session: RTCSession) {} /** * Called when session is closed. */ override fun onSessionClosed(session: RTCSession?) {} ``` * SDK v1 java (deprecated) ```java /** * Called each time when new session request is received. */ void onReceiveNewSession(RTCSession session); /\*\* - Called in case when user didn't answer within timer expiration period \*/ void onUserNotAnswer(RTCSession session, Integer userID); /\*\* - Called in case when opponent has rejected your call \*/ void onCallRejectByUser(RTCSession session, Integer userID, Map userInfo); /\*\* - Called in case when opponent has accepted your call \*/ void onCallAcceptByUser(RTCSession session, Integer userID, Map userInfo); /\*\* - Called in case when opponent hung up \*/ void onReceiveHangUpFromUser(RTCSession session, Integer userID, Map userInfo); /\*\* - Called in case when user didn't make any actions on received session \*/ void onUserNoActions(RTCSession session, Integer userID); /\*\* - Called in case when session will close \*/ void onSessionStartClose(RTCSession session); /\*\* - Called when session is closed. \*/ void onSessionClosed(RTCSession session); ``` ## Accept a call [Section titled “Accept a call”](#accept-a-call) You will receive all incoming call requests in `RTCClientSessionCallbacks.onReceiveNewSession(session)` callback. There are a few ways how to proceed: * accept incoming call; * reject incoming call. To accept the call request use the following code snippet: * SDK v2 kotlin ```kotlin // RTCCallSessionCallback override fun onReceiveNewSession(session: P2PSession) { // obtain received user info // val userInfo = session.getUserInfo() // set your user info if needed val userInfo = HashMap() userInfo["key1"] = "value1" // Accept the incoming call session.acceptCall(userInfo) } ``` * SDK v1 kotlin (deprecated) ```kotlin // RTCClientSessionCallbacks override fun onReceiveNewSession(session: RTCSession) { // obtain received user info val userInfo = session.userInfo // set your user info if needed val userInfo = HashMap() userInfo["key1"] = "value1" // Accept the incoming call session.acceptCall(userInfo) } ``` * SDK v1 java (deprecated) ```java // RTCClientSessionCallbacks public void onReceiveNewSession(RTCSession session){ // obtain received user info Map userInfo = session.getUserInfo(); // set your user info if needed Map userInfo = new HashMap<>; userInfo.put("key1", "value1"); // Accept the incoming call session.acceptCall(userInfo); } ``` After this, your opponent will receive an accept callback: * SDK v2 kotlin ```kotlin // RTCSessionEventsCallback override fun onCallAcceptByUser(session: P2PSession, opponentId: Int, userInfo: Map?) { } ``` * SDK v1 kotlin (deprecated) ```kotlin // RTCClientSessionCallbacks override fun onCallAcceptByUser(session: RTCSession, userID: Int, userInfo: Map? ) { } ``` * SDK v1 java (deprecated) ```java // RTCClientSessionCallbacks public void onCallAcceptByUser(RTCSession session, Integer userID, Map userInfo){ } ``` ## Render video stream to view [Section titled “Render video stream to view”](#render-video-stream-to-view) For managing video tracks you need to implement `RTCClientVideoTracksCallbacks` interface: * SDK v2 kotlin ```kotlin p2pSession.addVideoTrackCallbacksListener(this) p2pSession.removeVideoTrackCallbacksListener(this) ``` * SDK v1 kotlin (deprecated) ```kotlin rtcSession.addVideoTrackCallbacksListener(this) rtcSession.removeVideoTrackCallbacksListener(this) ``` * SDK v1 java (deprecated) ```java rtcSession.addVideoTrackCallbacksListener(this); rtcSession.removeVideoTrackCallbacksListener(this); ``` - SDK v2 kotlin ```kotlin /** * Called when local video track is received */ override fun onLocalVideoTrackReceive(session: P2PSession, videoTrack: ConnectycubeVideoTrack ) {} /\*\* - Called when remote video track is received \*/ override fun onRemoteVideoTrackReceive(session: P2PSession, videoTrack: ConnectycubeVideoTrack, userId: Int ) {} ``` - SDK v1 kotlin (deprecated) ```kotlin /** * Called when local video track is received */ override fun onLocalVideoTrackReceive(session: RTCSession, localVideoTrack: RTCVideoTrack ) {} /** * Called when remote video track is received */ override fun onRemoteVideoTrackReceive(session: RTCSession, remoteVideoTrack: RTCVideoTrack, userID: Int ) {} ``` - SDK v1 java (deprecated) ```java /** * Called when local video track is received */ void onLocalVideoTrackReceive(RTCSession session, RTCVideoTrack localVideoTrack); /\*\* - Called when remote video track is received \*/ void onRemoteVideoTrackReceive(RTCSession session, RTCVideoTrack remoteVideoTrack, Integer userID); ``` Once you’ve got an access to video track, you can render them to some view in your app UI: * SDK v2 kotlin ```kotlin private fun fillVideoView(userId: Int, videoView: RTCSurfaceView, videoTrack: ConnectycubeVideoTrack, remoteRenderer: Boolean ) { videoTrack.addSink(videoView.videoSink) updateVideoView(videoView, !remoteRenderer, ScalingType.SCALE_ASPECT_FILL) } private fun updateVideoView(surfaceView: RTCSurfaceView, mirror: Boolean, scalingType: ScalingType ) { surfaceView.setScalingType(scalingType) surfaceView.setMirror(mirror) surfaceView.requestLayout() } ``` * SDK v1 kotlin (deprecated) ```kotlin private fun fillVideoView(userId: Int, videoView: RTCSurfaceView, videoTrack: ConnectycubeVideoTrack, remoteRenderer: Boolean ) { videoTrack.addRenderer(videoView) updateVideoView(videoView, !remoteRenderer, ScalingType.SCALE_ASPECT_FILL) } private fun updateVideoView(surfaceView: RTCSurfaceView, mirror: Boolean, scalingType: ScalingType ) { surfaceView.setScalingType(scalingType) surfaceView.setMirror(mirror) surfaceView.requestLayout() } ``` * SDK v1 java (deprecated) ```java private void fillVideoView(int userId, RTCSurfaceView videoView, RTCVideoTrack videoTrack, boolean remoteRenderer) { videoTrack.addRenderer(new VideoRenderer(videoView)); updateVideoView(videoView, !remoteRenderer, RendererCommon.ScalingType.SCALE_ASPECT_FILL); } private void updateVideoView(RTCSurfaceView surfaceView, boolean mirror, RendererCommon.ScalingType scalingType){ surfaceView.setScalingType(scalingType); surfaceView.setMirror(mirror); surfaceView.requestLayout(); } ``` ## Obtain audio tracks [Section titled “Obtain audio tracks”](#obtain-audio-tracks) To get an access to audio tracks you need to implement `RTCClientAudioTracksCallback` interface: * SDK v2 kotlin ```kotlin p2pSession.addAudioTrackCallbacksListener(this) p2pSession.removeAudioTrackCallbacksListener(this) ``` * SDK v1 kotlin (deprecated) ```kotlin rtcSession.addAudioTrackCallbacksListener(this) rtcSession.removeAudioTrackCallbacksListener(this) ``` * SDK v1 java (deprecated) ```java rtcSession.addAudioTrackCallbacksListener(this); rtcSession.removeAudioTrackCallbacksListener(this); ``` - SDK v2 kotlin ```kotlin /** * Called when local audio track is received */ override fun onLocalAudioTrackReceive(session: P2PSession, audioTrack: ConnectycubeAudioTrack) {} /\*\* - Called when remote audio track is received \*/ override fun onRemoteAudioTrackReceive(session: P2PSession, audioTrack: ConnectycubeAudioTrack, userId: Int ) {} ``` - SDK v1 kotlin (deprecated) ```kotlin /** * Called when local audio track is received */ override fun onLocalAudioTrackReceive(session: RTCSession, audioTrack: RTCAudioTrack) {} /** * Called when remote audio track is received */ override fun onRemoteAudioTrackReceive(session: RTCSession, audioTrack: RTCAudioTrack, userID: Int ) {} ``` - SDK v1 java (deprecated) ```java /** * Called when local audio track is received */ void onLocalAudioTrackReceive(RTCSession session, RTCAudioTrack audioTrack); /\*\* - Called when remote audio track is received \*/ void onRemoteAudioTrackReceive(RTCSession session, RTCAudioTrack audioTrack, Integer userID); ``` Then you can use these audio tracks to mute/unmute audio. Read more below. ## 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](/android/push-notifications) regarding how to integrate Push Notifications in your app. ## Reject a call [Section titled “Reject a call”](#reject-a-call) To reject a call request just use the following method: * SDK v2 kotlin ```kotlin // RTCCallSessionCallback override fun onReceiveNewSession(session: P2PSession) { // obtain received user info // val userInfo = session.userInfo // set your user info if needed val userInfo = HashMap() userInfo["key1"] = "value1" // Rejecting the incoming call session.rejectCall(userInfo) } ``` * SDK v1 kotlin (deprecated) ```kotlin // RTCClientSessionCallbacks override fun onReceiveNewSession(session: RTCSession) { // obtain received user info val userInfo = session.userInfo // set your user info if needed val userInfo = HashMap() userInfo["key1"] = "value1" // Rejecting the incoming call session.rejectCall(userInfo) } ``` * SDK v1 java (deprecated) ```java // RTCClientSessionCallbacks public void onReceiveNewSession(RTCSession session){ // obtain received user info Map userInfo = session.getUserInfo(); // set your user info if needed Map userInfo = new HashMap; userInfo.put("key1", "value1"); // Rejecting the incoming call session.rejectCall(userInfo); } ``` After this, your opponent will receive a reject callback: * SDK v2 kotlin ```kotlin // RTCSessionEventsCallback override fun onCallRejectByUser(session: P2PSession, opponentId: Int, userInfo: Map? ) {} ``` * SDK v1 kotlin (deprecated) ```kotlin // RTCClientSessionCallbacks override fun onCallRejectByUser(session: RTCSession, userID: Int, userInfo: Map? ) {} ``` * SDK v1 java (deprecated) ```java // RTCClientSessionCallbacks public void onCallRejectByUser(RTCSession session, Integer userID, Map userInfo){ } ``` ## End a call [Section titled “End a call”](#end-a-call) To end a call use the following snippet: * SDK v2 kotlin ```kotlin // set your user info if needed val userInfo = HashMap() userInfo["key1"] = "value1" session.hangUp(userInfo) ``` * SDK v1 kotlin (deprecated) ```kotlin // set your user info if needed val userInfo = HashMap() userInfo["key1"] = "value1" session.hangUp(userInfo) ``` * SDK v1 java (deprecated) ```java // set your user info if needed Map userInfo = new HashMap; userInfo.put("key1", "value1"); session.hangUp(userInfo); ``` After this, your opponent will receive a hang up callback: * SDK v2 kotlin ```kotlin override fun onReceiveHangUpFromUser(session: P2PSession, opponentId: Int, userInfo: Map?) {} ``` * SDK v1 kotlin (deprecated) ```kotlin override fun onReceiveHangUpFromUser(session: RTCSession, userID: Int, userInfo: Map?) {} ``` * SDK v1 java (deprecated) ```java public void onReceiveHangUpFromUser(RTCSession session, Integer userID, Map userInfo){ } ``` ## Release resource [Section titled “Release resource”](#release-resource) When you don’t want to receive and process video calls anymore - you need to destroy `RTCClient`: * SDK v2 kotlin ```kotlin EglBaseContext.release() P2PCalls.destroy() //P2PCalls.register() to init ``` * SDK v1 kotlin (deprecated) ```kotlin RTCClient.getInstance(this).destroy() ``` * SDK v1 java (deprecated) ```java RTCClient.getInstance(this).destroy(); ``` This method unregisters `RTCClient` from receiving any video chat events, clear session callbacks and closes existing signaling channels. ## Monitor connection state [Section titled “Monitor connection state”](#monitor-connection-state) To monitor the states of your peer connections (users) you need to implement `RTCSessionStateCallback` interface: * SDK v2 kotlin ```kotlin // RTCSessionStateCallback p2pSession.addSessionStateCallbacksListener(this) p2pSession.removeSessionStateCallbacksListener(this) ``` * SDK v1 kotlin (deprecated) ```kotlin rtcSession.addSessionCallbacksListener(this) rtcSession.removeSessionCallbacksListener(this) ``` * SDK v1 java (deprecated) ```java rtcSession.addSessionCallbacksListener(this); rtcSession.removeSessionCallbacksListener(this); ``` - SDK v2 kotlin ```kotlin /** * Called in case when connection with the opponent is established */ override fun onConnectedToUser(session: P2PSession, userId: Int){} /\*\* - Called in case when connection is closed \*/ override fun onConnectionClosedForUser(session: P2PSession, userId: Int) {} /\*\* - Called in case when the opponent is disconnected \*/ override fun onDisconnectedFromUser(session: P2PSession, userId: Int) {} /\*\* - Called in case when session state has changed \*/ override fun onStateChanged(session: P2PSession, state: BaseSession.RTCSessionState) {} ``` - SDK v1 kotlin (deprecated) ```kotlin /** * Called in case when connection with the opponent is established */ override fun onConnectedToUser(session: RTCSession, userID: Int){} /** * Called in case when connection is closed */ override fun onConnectionClosedForUser(session: RTCSession, userID: Int) {} /** * Called in case when the opponent is disconnected */ override fun onDisconnectedFromUser(session: RTCSession, userID: Int) {} /** * Called in case when connection establishment process is started */ override fun onStartConnectToUser(session: RTCSession, userID: Int) {} /** * Called in case when the opponent is disconnected by timeout */ override fun onDisconnectedTimeoutFromUser(session: RTCSession, userID: Int) {} /** * Called in case when connection has failed with the opponent */ override fun onConnectionFailedWithUser(session: RTCSession, userID: Int) {} ``` - SDK v1 java (deprecated) ```java /** * Called in case when connection with the opponent is established */ void onConnectedToUser(RTCSession session, Integer userID); /\*\* - Called in case when connection is closed \*/ void onConnectionClosedForUser(RTCSession session, Integer userID); /\*\* - Called in case when the opponent is disconnected \*/ void onDisconnectedFromUser(RTCSession session, Integer userID); /\*\* - Called in case when connection establishment process is started \*/ void onStartConnectToUser(RTCSession session, Integer userID); /\*\* - Called in case when the opponent is disconnected by timeout \*/ void onDisconnectedTimeoutFromUser(RTCSession session, Integer userID); /\*\* - Called in case when connection has failed with the opponent \*/ void onConnectionFailedWithUser(RTCSession session, Integer userID); ``` ## 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 `RTCSessionStateCallback` callback with `peer connections state` changing to `onDisconnectedFromUser` and then again to `onConnectedToUser`. 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). ```kotlin code snippet with ice restart (coming soon) ``` ## Mute audio [Section titled “Mute audio”](#mute-audio) * SDK v2 kotlin ```kotlin val localAudioTrack: ConnectycubeAudioTrack? = session.mediaStreamManager.localAudioTrack // mute localAudioTrack.enabled = false // unmute localAudioTrack.enabled = true // is muted? val isEnabled = localAudioTrack.enabled ``` * SDK v1 kotlin (deprecated) ```kotlin val localAudioTrack: ConnectycubeAudioTrack? = session.mediaStreamManager.localAudioTrack // mute localAudioTrack.enabled = false // unmute localAudioTrack.enabled = true // is muted? val isEnabled = localAudioTrack.enabled ``` * SDK v1 java (deprecated) ```java RTCAudioTrack localAudioTrack = currentSession.getMediaStreamManager().getLocalAudioTrack(); // mute localAudioTrack.setEnabled(false); // unmute localAudioTrack.setEnabled(true); // is muted? boolean isEnabled = localAudioTrack.enabled(); ``` `getMediaStreamManager()` method returns an instance of `RTCMediaStreamManager`. > **Note**: Pay attention, `RTCMediaStreamManager` is attached to `RTCSession` lifecycle. According to `RTCSession` lifecycle, you should use `RTCMediaStreamManager` only when `RTCSession` is active. ## Mute video [Section titled “Mute video”](#mute-video) * SDK v2 kotlin ```kotlin val localVideoTrack: ConnectycubeVideoTrack? = session.mediaStreamManager.localVideoTrack // mute localVideoTrack.enabled = false // unmute localVideoTrack.enabled = true // is muted? val isEnabled = localVideoTrack.enabled ``` * SDK v1 kotlin (deprecated) ```kotlin val localVideoTrack: RTCVideoTrack = currentSession.getMediaStreamManager().getLocalVideoTrack() // mute localVideoTrack.setEnabled(false) // mute localVideoTrack.setEnabled(true) // is muted? val isEnabled = localVideoTrack.enabled() ``` * SDK v1 java (deprecated) ```java RTCVideoTrack localVideoTrack = currentSession.getMediaStreamManager().getLocalVideoTrack(); // mute localVideoTrack.setEnabled(false); // mute localVideoTrack.setEnabled(true); // is muted? boolean isEnabled = localVideoTrack.enabled(); ``` ## Switch camera [Section titled “Switch camera”](#switch-camera) You can switch the video camera during a call (default is front camera): * SDK v2 kotlin ```kotlin val videoCapturer = session.mediaStreamManager.videoCapturer videoCapturer.switchCamera(cameraSwitchHandler) ``` * SDK v1 kotlin (deprecated) ```kotlin val videoCapturer = currentSession.getMediaStreamManager().getVideoCapturer() as RTCCameraVideoCapturer videoCapturer.switchCamera(cameraSwitchHandler) ``` * SDK v1 java (deprecated) ```java RTCCameraVideoCapturer videoCapturer = (RTCCameraVideoCapturer)currentSession.getMediaStreamManager().getVideoCapturer(); videoCapturer.switchCamera(cameraSwitchHandler); ``` ## Screen sharing [Section titled “Screen sharing”](#screen-sharing) Screen sharing allows you to share information from your application to all of your opponents. It gives you an ability to promote your product, share a screen with formulas to students, distribute podcasts, share video/audio/photo moments of your life in real-time all over the world. > **Note**: Pay attention! Screen sharing feature works only on devices with Android 5 (LollyPop) and newer. To simplify using this feature we prepared special `RTCScreenCapturer` class. To implement this feature in your application you should do **3** simple steps: ##### 1. Request projection permission from user: [Section titled “1. Request projection permission from user:”](#1-request-projection-permission-from-user) * SDK v2 kotlin ```kotlin //Coming soon ``` * SDK v1 kotlin (deprecated) ```kotlin RTCScreenCapturer.requestPermissions(this) ``` * SDK v1 java (deprecated) ```java RTCScreenCapturer.requestPermissions(this); ``` ##### 2. Listen to granted permission inside Activity (or Fragment): [Section titled “2. Listen to granted permission inside Activity (or Fragment):”](#2-listen-to-granted-permission-inside-activity-or-fragment) * SDK v2 kotlin ```kotlin //Coming soon ``` * SDK v1 kotlin (deprecated) ```kotlin override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { ... if (requestCode == RTCScreenCapturer.REQUEST_MEDIA_PROJECTION) { if (resultCode == Activity.RESULT_OK) { startScreenSharing(data!!) Log.i(TAG, "Starting screen capture") } else { Toast.makeText(applicationContext, "You cannot continue without an access to screen", Toast.LENGTH_SHORT).show() } } } ``` * SDK v1 java (deprecated) ```java @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { ... if (requestCode == RTCScreenCapturer.REQUEST_MEDIA_PROJECTION) { if (resultCode == Activity.RESULT_OK) { startScreenSharing(data); Log.i(TAG, "Starting screen capture"); } else { Toast.makeText(getApplicationContext(), "You cannot continue without an access to screen", Toast.LENGTH_SHORT).show(); } } } ``` ##### 3. Set `RTCScreenCapturer` as current video capturer: [Section titled “3. Set RTCScreenCapturer as current video capturer:”](#3-set-rtcscreencapturer-as-current-video-capturer) * SDK v2 kotlin ```kotlin //Coming soon ``` * SDK v1 kotlin (deprecated) ```kotlin rtcSession.mediaStreamManager.videoCapturer = RTCScreenCapturer(data, null) ``` * SDK v1 java (deprecated) ```java rtcSession.getMediaStreamManager().setVideoCapturer(new RTCScreenCapturer(data, null)); ``` > **Note**: To create instance of `RTCScreenCapturer` need use `data` from **permission request** ## Configure general settings [Section titled “Configure general settings”](#configure-general-settings) `RTCConfig` provides an interface to customize some SDK video chat settings: * SDK v2 kotlin ```kotlin /** * add to list ice servers. */ WebRTCConfig.iceServerList.add(ConnectycubeIceServer(uri, userName, password)) //Coming soon //DialingTimeInterval, AnswerTimeInterval ``` * SDK v1 kotlin (deprecated) ```kotlin /** * Set dialing time interval * Default value is 5 sec */ fun setDialingTimeInterval(dialingTimeInterval: Long) {} /** * Set answer time interval * Default value is 60 sec */ fun setAnswerTimeInterval(answerTimeInterval: Long) {} /** * Set max connections in conference * Default value is 10 */ fun setMaxOpponentsCount(maxOpponentsCount: Int) {} /** * Set max allowed time to repair a connection after it was lost. * Default value is 10 sec */ fun setDisconnectTime(disconnectTime: Int) {} /** * Set list of ice servers. */ fun setIceServerList(iceServerList: List) {} ``` * SDK v1 java (deprecated) ```java /** * Set dialing time interval * Default value is 5 sec */ public static void setDialingTimeInterval(long dialingTimeInterval); /\*\* - Set answer time interval - Default value is 60 sec \*/ public static void setAnswerTimeInterval(long answerTimeInterval); /\*\* - Set max connections in conference - Default value is 10 \*/ public static void setMaxOpponentsCount(Integer maxOpponentsCount); /\*\* - Set max allowed time to repair a connection after it was lost. - Default value is 10 sec \*/ public static void setDisconnectTime(Integer disconnectTime); /\*\* - Set list of ice servers. \*/ public static void setIceServerList(List iceServerList); ``` For example, you can customize a list of ICE servers that SDK uses. WebRTC engine will choose the TURN relay with the lowest round-trip time. Thus, setting multiple TURN servers allows your application to scale-up in terms of bandwidth and number of users: * SDK v2 kotlin ```kotlin WebRTCConfig.iceServerList.add( ConnectycubeIceServer( "turn:numb.default.com", "default@default.com", "default@default.com" ) ) ``` * SDK v1 kotlin (deprecated) ```kotlin val iceServerList: MutableList = LinkedList() iceServerList.add( IceServer( "turn:numb.default.com", "default@default.com", "default@default.com" ) ) RTCConfig.setIceServerList(iceServerList) ``` * SDK v1 java (deprecated) ```java List iceServerList = new LinkedList<>(); iceServerList.add(new PeerConnection.IceServer("turn:numb.default.com", "default@default.com", "default@default.com")); RTCConfig.setIceServerList(iceServerList); ``` ## Configure media settings [Section titled “Configure media settings”](#configure-media-settings) You can use `RTCMediaConfig` class instance to configure a various list of media settings like video/audio codecs, bitrate, fps etc: * SDK v2 kotlin ```kotlin //WebRTCMediaConfig var audioCodec: AudioCodec = AudioCodec.ISAC var videoCodec: VideoCodec? = null var videoWidth = 0 var videoHeight = 0 var videoFps = 0 var audioStartBitrate = 0 var videoStartBitrate = 0 var videoHWAcceleration = false var useOpenSLES = false //Allow OpenSL ES audio if device supports it var useBuildInAEC = true //Enable built-in AEC if device supports var audioProcessingEnabled = true //Enabling/Disabling audio processing - added for audio performance ``` * SDK v1 kotlin (deprecated) ```kotlin fun setAudioCodec(audioCodec: RTCMediaConfig.AudioCodec) {} fun setVideoCodec(videoCodec: VideoCodec) {} fun setVideoWidth(videoWidth: Int) {} fun setVideoHeight(videoHeight: Int) {} fun setVideoFps(videoFps: Int) {} fun setVideoStartBitrate(videoStartBitrate: Int) {} fun setAudioStartBitrate(audioStartBitrate: Int) {} fun setVideoHWAcceleration(videoHWAcceleration: Boolean) {} fun setUseBuildInAEC(useBuildInAEC: Boolean) {} // Enable built-in AEC if device supports it fun setUseOpenSLES(useOpenSLES: Boolean) {} //Allow OpenSL ES audio if device supports it fun setAudioProcessingEnabled(audioProcessingEnabled: Boolean) {} //Enabling/Disabling audio processing - added for audio performance. ``` * SDK v1 java (deprecated) ```java public static void setAudioCodec(AudioCodec audioCodec); public static void setVideoCodec(VideoCodec videoCodec); public static void setVideoWidth(int videoWidth); public static void setVideoHeight(int videoHeight); public static void setVideoFps(int videoFps); public static void setVideoStartBitrate(int videoStartBitrate); public static void setAudioStartBitrate(int audioStartBitrate); public static void setVideoHWAcceleration(boolean videoHWAcceleration); public static void setUseBuildInAEC(boolean useBuildInAEC); // Enable built-in AEC if device supports it public static void setUseOpenSLES(boolean useOpenSLES); //Allow OpenSL ES audio if device supports it public static void setAudioProcessingEnabled(boolean audioProcessingEnabled); //Enabling/Disabling audio processing - added for audio performance. ``` ## WebRTC Stats reporting [Section titled “WebRTC Stats reporting”](#webrtc-stats-reporting) Stats reporting is an insanely powerful tool which can help to debug a call if there are any problems with it (e.g. lags, missing audio/video etc.). To enable stats report you should first set stats reporting frequency using `RTCConfig` method below: * SDK v2 kotlin ```kotlin //Coming soon //ConnectycubeStatsReport ``` * SDK v1 kotlin (deprecated) ```kotlin RTCConfig.setStatsReportInterval(5) // receive stats report every 5 seconds ``` * SDK v1 java (deprecated) ```java RTCConfig.setStatsReportInterval(5); // receive stats report every 5 seconds ``` Now you will be able to receive a client delegate callback and perform operations with `RTCStatsReport` instance for the current period of time: * SDK v2 kotlin ```kotlin //Coming soon //ConnectycubeStatsReport ``` * SDK v1 kotlin (deprecated) ```kotlin val rtcSession: RTCSession = ... rtcSession.addStatsReportCallback { rtcStatsReport, userId -> Log.i(TAG, "Full report = $rtcStatsReport") Log.i(TAG, "Report by user = $userId") } ``` * SDK v1 java (deprecated) ```java RTCSession rtcSession = ...; rtcSession.addStatsReportCallback(new RTCStatsReportCallback() { @Override public void onStatsReportUpdate(RTCStatsReport rtcStatsReport, Integer userId) { Log.i(TAG, "Full report = " + rtcStatsReport); Log.i(TAG, "Report by user = " + userId); } }); ``` You can also use stats reporting to see who is currently talking in a group call. You must use `audioReceiveOutputLevel` for that. Take a look to the `RTCStatsReport` to see all of the other stats properties that might be useful for you. ## 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](/android/videocalling-conference). ## Call recording [Section titled “Call recording”](#call-recording) Coming soon
# Video Conferencing
> Discover the simplicity of integrating conference video calling into your Android 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) ## Connect SDK [Section titled “Connect SDK”](#connect-sdk) For using Video Conferencing feature you should add a dependency (only for V1): #### SDK v1 kotlin (deprecated) [Section titled “SDK v1 kotlin (deprecated)”](#sdk-v1-kotlin-deprecated) ```groovy implementation "com.connectycube:connectycube-android-sdk-videochat-conference:$sdkVersion" ``` ## Create meeting [Section titled “Create meeting”](#create-meeting) In order to have a conference call, a meeting object has to be created. ```kotlin val now = System.currentTimeMillis() / 1000 val meeting = ConnectycubeMeeting( name = "My meeting", startDate = now, endDate = now + 60 * 60, attendees = listOf(ConnectycubeAttendee(123, "superman@mail.com"), ConnectycubeAttendee(124, "superman2@mail.com")), chat = false, record = false, public = true, scheduled = false, // notify = true, //notify feature is available starting from the [Advanced plan](https://connectycube.com/pricing/) // notifyBefore = ConnectycubeNotifyBefore(ConnectycubeMeetingMetric.HOURS, 1) //notify feature is available starting from the [Advanced plan](https://connectycube.com/pricing/) ) createMeeting(meeting, { createdMeeting -> val confRoomId = createdMeeting.id }, { error -> }) ``` * `name` - the meeting name. * As for `attendees` - either ConnectyCube users ids or external emails can be provided. * `start_date` - the date when meeting starts. * `end_date` - the date when meeting ends. * Pass `withChat = 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. ## ConferenceSession [Section titled “ConferenceSession”](#conferencesession) * SDK v2 kotlin ```kotlin // Create session with Video or Audio type conference val conferenceType = CallType.VIDEO ConnectyCube.conferenceCalls.createSession(userId, conferenceType, object: ConferenceCallback { override fun onSuccess(result: ConferenceSession) { } override fun onError(ex: WsException) { } }) ``` * SDK v1 kotlin (deprecated) ```kotlin val client: ConferenceClient = ConferenceClient.getInstance(applicationContext) // Create session with Video or Audio type conference val conferenceType = ConferenceType.CONFERENCE_TYPE_VIDEO client.createSession(userId, conferenceType, object : ConferenceEntityCallback { override fun onSuccess(session: ConferenceSession?) { } override fun onError(exception: WsException?) { } }) ``` * SDK v1 java (deprecated) ```java ConferenceClient client = ConferenceClient.getInstance(getApplicationContext()); // Create session with Video or Audio type conference RTCTypes.ConferenceType conferenceType = RTCTypes.ConferenceType.CONFERENCE_TYPE_VIDEO; client.createSession(userID, conferenceType, new ConferenceEntityCallback() { @Override public void onSuccess(ConferenceSession session) { } }); ``` `ConferenceClient` (`ConferenceCalls` v2) instance is a client model responsible for managing conference session. `ConferenceClient` has a `setAutoSubscribeAfterJoin` (`autoSubscribeAfterJoin` v2) option, which means your client will be subscribing to all online publishers after join to some room. `ConferenceSession` is a session within certain video room, managing all current processes. ## Events [Section titled “Events”](#events) In order to have an ability to receive callbacks about current `ConferenceSession` instance state and conference events, you should implement appropriate interfaces: Implement `RTCSessionStateCallback` for tracking connection stat: * SDK v2 kotlin ```kotlin val sessionStateCallback = object: RTCSessionStateCallback { /** * Called when session state is changed */ override fun onStateChanged(session: ConferenceSession, state: BaseSession.RTCSessionState) {} /** * Called in case when opponent disconnected */ override fun onDisconnectedFromUser(session: ConferenceSession, userId: Int) {} /** * Called in case when connection with opponent is established */ override fun onConnectedToUser(session: ConferenceSession, userId: Int) {} /** * Called in case when connection closed with certain user. */ override fun onConnectionClosedForUser(session: ConferenceSession, userId: Int) {} } currentSession.addSessionStateCallbacksListener(sessionStateCallback) //currentSession.removeSessionStateCallbacksListener(sessionStateCallback) ``` * SDK v1 kotlin (deprecated) ```kotlin val sessionStateCallback = object: RTCSessionStateCallback { /** * Called when session state is changed */ override fun onStateChanged(session: ConferenceSession, state: BaseSession.RTCSessionState) {} /** * Called in case when opponent disconnected */ override fun onDisconnectedFromUser(session: ConferenceSession, userID: Int) {} /** * Called in case when connection with opponent is established */ override fun onConnectedToUser(session: ConferenceSession, userID: Int) {} /** * Called in case when connection closed with certain user. */ override fun onConnectionClosedForUser(session: ConferenceSession, userID: Int) {} } currentSession.addSessionCallbacksListener(sessionStateCallback) //currentSession.removeSessionCallbacksListener(sessionStateCallback) ``` * SDK v1 java (deprecated) ```java RTCSessionStateCallback sessionStateCallback = new RTCSessionStateCallback(){ /** * Called when session state is changed */ void onStateChanged(ConferenceSession session, BaseSession.RTCSessionState state); /** * Called in case when connection with opponent is established */ void onConnectedToUser(ConferenceSession session, Integer userID); /** * Called in case when opponent disconnected */ void onDisconnectedFromUser(ConferenceSession session, Integer userID); /** * Called in case when connection closed with certain user. */ void onConnectionClosedForUser(ConferenceSession session, Integer userID); } currentSession.addSessionCallbacksListener(sessionStateCallback); //currentSession.removeSessionCallbacksListener(sessionStateCallback); ``` Implement `ConferenceSessionCallbacks` for tracking conference events: * SDK v2 kotlin ```kotlin val conferenceSessionCallbacks: ConferenceSessionCallbacks = object : ConferenceSessionCallbacks { /** * Called when some publisher (user) joined to the video room */ override fun onPublishersReceived(publishers: List?) {} /** * Called when some publisher left room */ override fun onPublisherLeft(userId: Int?) {} /** * Called when media - audio or video type, is received */ override fun onMediaReceived(type: String?, success: Boolean ) {} /** * Called when slowLink is received. SlowLink with uplink=true means you notified several missing packets from server, while uplink=false means server is not receiving all your packets. */ override fun onSlowLinkReceived(uplink: Boolean, lost: Int) {} /** * Called when received errors from server */ override fun onError(ex: WsException?) {} /** * Called when ConferenceSession is closed */ override fun onSessionClosed(session: ConferenceSession) {} } currentSession.addConferenceSessionListener(conferenceSessionCallbacks) //currentSession.removeConferenceSessionListener(conferenceSessionCallbacks) ``` * SDK v1 kotlin (deprecated) ```kotlin val conferenceSessionCallbacks: ConferenceSessionCallbacks = object : ConferenceSessionCallbacks { /** * Called when some publisher (user) joined to the video room */ override fun onPublishersReceived(arrayList: ArrayList) {} /** * Called when some publisher left room */ override fun onPublisherLeft(integer: Int) {} /** * Called when media - audio or video type, is received */ override fun onMediaReceived(type: String, success: Boolean ) {} /** * Called when slowLink is received. SlowLink with uplink=true means you notified several missing packets from server, while uplink=false means server is not receiving all your packets. */ override fun onSlowLinkReceived(b: Boolean, i: Int) {} /** * Called when received errors from server */ override fun onError(e: WsException) {} /** * Called when ConferenceSession is closed */ override fun onSessionClosed(conferenceSession: ConferenceSession) {} } currentSession.addConferenceSessionListener(conferenceSessionCallbacks) //currentSession.removeConferenceSessionListener(conferenceSessionCallbacks) ``` * SDK v1 java (deprecated) ```java ConferenceSessionCallbacks conferenceSessionCallbacks = new ConferenceSessionCallbacks() { /** * Called when some publisher (user) joined to the video room */ @Override public void onPublishersReceived(ArrayList arrayList) { } /** * Called when some publisher left room */ @Override public void onPublisherLeft(Integer integer) { } /** * Called when media - audio or video type, is received */ @Override public void onMediaReceived(String type, boolean success) { } /** * Called when slowLink is received. SlowLink with uplink=true means you notified several missing packets from server, while uplink=false means server is not receiving all your packets. */ @Override public void onSlowLinkReceived(boolean b, int i) { } /** * Called when received errors from server */ @Override public void onError(WsException e) { } /** * Called when ConferenceSession is closed */ @Override public void onSessionClosed(ConferenceSession conferenceSession) { } }; currentSession.addConferenceSessionListener(conferenceSessionCallbacks); //currentSession.removeConferenceSessionListener(conferenceSessionCallbacks); ``` ## Video and Audio tracks [Section titled “Video and Audio tracks”](#video-and-audio-tracks) For obtaining video and audio tracks implement interface * SDK v2 kotlin ```kotlin VideoTracksCallback AudioTracksCallback ``` * SDK v1 kotlin (deprecated) ```kotlin RTCClientVideoTracksCallback RTCClientAudioTracksCallback ``` * SDK v1 java (deprecated) ```kotlin RTCClientVideoTracksCallback RTCClientAudioTracksCallback ``` For setting video track for ConferenceSession - the `ConferenceSurfaceView` (`RTCSurfaceView` v2) class is provided. ## Join video room [Section titled “Join video room”](#join-video-room) You can join room as a listener or as a publisher. As listener you subscribe only to the publishers, not giving own video and audio streams. * SDK v2 kotlin ```kotlin val conferenceRole = if (asListenerRole) ConferenceRole.LISTENER else ConferenceRole.PUBLISHER currentSession.joinDialog(dialogId, conferenceRole, object: ConferenceCallback > { ... }) ``` * SDK v1 kotlin (deprecated) ```kotlin val conferenceRole = if (asListenerRole) ConferenceRole.LISTENER else ConferenceRole.PUBLISHER currentSession.joinDialog(dialogID, conferenceRole, object: ConferenceEntityCallback > { ... }) ``` * SDK v1 java (deprecated) ```java ConferenceRole conferenceRole = asListenerRole ? ConferenceRole.LISTENER : ConferenceRole.PUBLISHER; currentSession.joinDialog(dialogID, conferenceRole, new ConferenceEntityCallback> { ... }); ``` ## List online participants [Section titled “List online participants”](#list-online-participants) To list online users in a room: ```kotlin currentSession.getOnlineParticipants(object: ConferenceCallback