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. ![Dialog Extensions fields configuration example](/_astro/dialog_custom_params.CrGT0s8Z_1XWSCw.webp) When create a dialog, the `extensions` field object must contain allowed fields only. Others fields will be ignored. The values will be casted to string. ```javascript const params = { type: 2, name: "Friday party", occupants_ids: [29085, 29086, 29087], description: "lets dance the night away", extensions: {location: "Sun bar"}, }; ConnectyCube.chat.dialog .create(params) .then((dialog) => {}) .catch((error) => {}); ``` When remove custom field in Admin panel, this field will be removed in all dialogs respectively. These parameters also can be used as a filter for retrieving dialogs. ### Chat permissions [Section titled “Chat permissions”](#chat-permissions) Chat could have different permissions to managa data access. This is managed via `permissions` field. At the moment, only one permission available - `allow_preview` - which allows to retrieve dialog’s messages for user who is not a member of dialog. This is useful when implement feature like Channels where a user can open chat and preview messages w/o joining it. ## List dialogs [Section titled “List dialogs”](#list-dialogs) It’s common to request all your dialogs on every app login: ```javascript const filters = {}; ConnectyCube.chat.dialog .list(filters) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.list(filters)` - [see](/server/chat#response) More filters available [here](/server/chat#retrieve-chat-dialogs) If you want to retrieve only dialogs updated after some specific date time, you can use `updated_at[gt]` filter. This is useful if you cache dialogs somehow and do not want to obtain the whole list of your dialogs on every app start. ## Update dialog [Section titled “Update dialog”](#update-dialog) User can update group chat name, photo or add/remove occupants: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const toUpdateParams = { name: "Crossfit2" }; ConnectyCube.chat.dialog .update(dialogId, toUpdateParams) .then((dialog) => {}) .catch((error) => {}); ``` ## Add/Remove occupants [Section titled “Add/Remove occupants”](#addremove-occupants) To add more occupants use `push_all` operator. To remove yourself from the dialog use `pull_all` operator: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const toUpdateParams = { push_all: { occupants_ids: [97, 789] } }; ConnectyCube.chat.dialog .update(dialogId, toUpdateParams) .then((dialog) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.update(dialogId, toUpdateParams)` - [see](/server/chat#response-2) > **Note** > > Only group chat owner can remove other users from group chat. ## Remove dialog [Section titled “Remove dialog”](#remove-dialog) The following snippet is used to delete a dialog: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; // const dialogIds = ['5356c64ab35c12bd3b108a41', ..., '5356c64ab35c12bd3b108a84'] ConnectyCube.chat.dialog.delete(dialogId).catch((error) => {}); ``` This request will remove this dialog for current user, but other users still will be able to chat there. Only group chat owner can remove the group dialog for all users. You can also delete multiple dialogs in a single request. ## Clear dialog history [Section titled “Clear dialog history”](#clear-dialog-history) The following snippet is used to clear dialog history by ID: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog.clearHistory(dialogId).catch((error) => {}); ``` This request will clear all messages in the dialog for current user, but not for other users. ## Subscribe to dialog [Section titled “Subscribe to dialog”](#subscribe-to-dialog) In order to be able to chat in public dialog, you need to subscribe to it: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog .subscribe(dialogId) .then((dialog) => {}) .catch((error) => {}); ``` It’s also possible to subscribe to group chat dialog. Response example from `ConnectyCube.chat.dialog.subscribe(dialogId)` - [see](/server/chat#response-5) ## Unsubscribe from dialog [Section titled “Unsubscribe from dialog”](#unsubscribe-from-dialog) ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog.unsubscribe(dialogId).catch((error) => {}); ``` ## Retrieve public dialog occupants [Section titled “Retrieve public dialog occupants”](#retrieve-public-dialog-occupants) A public chat dialog can have many occupants. There is a separated API to retrieve a list of public dialog occupants: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const params = { limit: 100, skip: 0, }; ConnectyCube.chat.dialog .getPublicOccupants(dialogId, params) .then((result) => { // result.items }) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.getPublicOccupants(dialogId, params)`: ```json { "items": [ { "id": 51941, "full_name": "Dacia Kail", "email": "dacia_k@domain.com", "login": "Dacia", "phone": "+6110797757", "website": null, "created_at": "2018-12-06T09:16:26Z", "updated_at": "2018-12-06T09:16:26Z", "last_request_at": null, "external_user_id": 52691165, "facebook_id": "91234409", "twitter_id": "83510562734", "blob_id": null, "custom_data": null, "avatar": null, "user_tags": null }, { "id": 51946, "full_name": "Gabrielle Corcoran", "email": "gabrielle.corcoran@domain.com", "login": "gabby", "phone": "+6192622155", "website": "http://gabby.com", "created_at": "2018-12-06T09:29:57Z", "updated_at": "2018-12-06T09:29:57Z", "last_request_at": null, "external_user_id": null, "facebook_id": "95610574", "twitter_id": null, "blob_id": null, "custom_data": "Responsible for signing documents", "avatar": null, "user_tags": "vip,accountant" } ... ] } ``` ## Add / Remove admins [Section titled “Add / Remove admins”](#add--remove-admins) Options to add or remove admins from the dialog can be done by Super admin (dialog’s creator) only. Options are supported in group chat, public or broadcast. Up to 5 admins can be added to chat. ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const adminsUsersIds = [45, 89]; ConnectyCube.chat.dialog .addAdmins(dialogId, adminsUsersIds) .then((dialog) => {}) .catch((error) => {}); ``` ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const adminsUsersIds = [45, 89]; ConnectyCube.chat.dialog .removeAdmins(dialogId, adminsUsersIds) .then((dialog) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.addAdmins(dialogId, adminsUsersIds)`/`ConnectyCube.chat.dialog.removeAdmins(dialogId, adminsUsersIds)` - [see](/server/chat#response-7) ## Update notifications settings [Section titled “Update notifications settings”](#update-notifications-settings) A user can turn on/off push notifications for offline messages in a dialog. By default push notification are turned ON, so offline user receives push notifications for new messages in a chat. ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const enabled = false; ConnectyCube.chat.dialog .updateNotificationsSettings(dialogId, enabled) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.updateNotificationsSettings(dialogId, enabled)` - [see](/server/chat#response-8) ## Get notifications settings [Section titled “Get notifications settings”](#get-notifications-settings) Check a status of notifications setting - either it is ON or OFF for a particular chat. Available responses: 1 - enabled, 0 - disabled. ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog .getNotificationsSettings(dialogId) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.getNotificationsSettings(dialogId)` - [see](/server/chat#response-9) ## Chat history [Section titled “Chat history”](#chat-history) Every chat dialog stores its chat history which you can retrieve: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const params = { chat_dialog_id: dialogId, sort_desc: "date_sent", limit: 100, skip: 0, }; ConnectyCube.chat.message .list(params) .then((messages) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.message.list(params)` - [see](/server/chat#response-10) If you want to retrieve chat messages that were sent after or before specific date time only, you can use `date_sent[gt]` or `date_sent[lt]` filter. This is useful if you implement pagination for loading messages in your app. ## Send/Receive chat messages [Section titled “Send/Receive chat messages”](#sendreceive-chat-messages) ### 1-1 chat [Section titled “1-1 chat”](#1-1-chat) ```javascript const dialog = ...; const opponentId = 78; const message = { type: dialog.type === 3 ? 'chat' : 'groupchat', body: "How are you today?", extension: { save_to_history: 1, dialog_id: dialog._id } }; message.id = ConnectyCube.chat.send(opponentId, message); // ... ConnectyCube.chat.onMessageListener = onMessage; function onMessage(userId, message) { console.log('[ConnectyCube.chat.onMessageListener] callback:', userId, message) } ``` ### Group chat [Section titled “Group chat”](#group-chat) > The group chat join is not a required step anymore. You can send/receive chat messages in a group chat w/o joining it. Before you start chatting in a group dialog, you need to join it by calling `join` function: ```javascript const dialog = ...; ConnectyCube.chat.muc.join(dialog._id).catch(error => {}); ``` Then you are able to send/receive messages: ```javascript const message = { type: dialog.type === 3 ? "chat" : "groupchat", body: "How are you today?", extension: { save_to_history: 1, dialog_id: dialog._id, } }; message.id = ConnectyCube.chat.send(dialog._id, message); // ... ConnectyCube.chat.onMessageListener = onMessage; function onMessage(userId, message) { console.log("[ConnectyCube.chat.onMessageListener] callback:", userId, message); } ``` When it’s done you can leave the group dialog by calling `leave` function: ```javascript ConnectyCube.chat.muc.leave(dialog._id).catch((error) => {}); ``` ## Message metadata [Section titled “Message metadata”](#message-metadata) A chat message can have custom sub-fields to store additional information that can be linked to the particular chat message. When create a message, the custom data can be attached via `extension` field: ```javascript const message = { ... extension: { field_one: "value_one", field_two: "value_two" } }; ``` ## ‘Sent’ status [Section titled “‘Sent’ status”](#sent-status) There is a ‘sent’ status to ensure that message is delivered to the server. In order to use the feature you need to enable it when you pass config in `ConnectyCube.init`: ```javascript chat: { streamManagement: { enable: true; } } ``` The following callback is used to track it: ```javascript ConnectyCube.chat.onSentMessageCallback = function (messageLost, messageSent) {}; ``` ## ‘Delivered’ status [Section titled “‘Delivered’ status”](#delivered-status) The following callback is used to track the ‘delivered’ status: ```javascript ConnectyCube.chat.onDeliveredStatusListener = function (messageId, dialogId, userId) { console.log("[ConnectyCube.chat.onDeliveredStatusListener] callback:", messageId, dialogId, userId); }; ``` The SDK sends the ‘delivered’ status automatically when the message is received by the recipient. This is controlled by `markable: 1` parameter when you send a message. If `markable` is `0` or omitted, then you can send the delivered status manually: ```javascript const params = { messageId: "557f1f22bcf86cd784439022", userId: 21, dialogId: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.sendDeliveredStatus(params); ``` ## ‘Read’ status [Section titled “‘Read’ status”](#read-status) Send the ‘read’ status: ```javascript const params = { messageId: "557f1f22bcf86cd784439022", userId: 21, dialogId: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.sendReadStatus(params); // ... ConnectyCube.chat.onReadStatusListener = function (messageId, dialogId, userId) { console.log("[ConnectyCube.chat.onReadStatusListener] callback:", messageId, dialogId, userId); }; ``` ## ‘Is typing’ status [Section titled “‘Is typing’ status”](#is-typing-status) The following ‘typing’ notifications are supported: * typing: The user is composing a message. The user is actively interacting with a message input interface specific to this chat session (e.g., by typing in the input area of a chat window) * stopped: The user had been composing but now has stopped. The user has been composing but has not interacted with the message input interface for a short period of time (e.g., 30 seconds) Send the ‘is typing’ status: ```javascript const opponentId = 78; // for 1-1 chats // const dialogJid = ..; // for group chat ConnectyCube.chat.sendIsTypingStatus(opponentId); ConnectyCube.chat.sendIsStopTypingStatus(opponentId); // ... ConnectyCube.chat.onMessageTypingListener = function (isTyping, userId, dialogId) { console.log("[ConnectyCube.chat.onMessageTypingListener] callback:", isTyping, userId, dialogId); }; ``` ## Attachments (photo / video) [Section titled “Attachments (photo / video)”](#attachments-photo--video) Chat attachments are supported with the cloud storage API. In order to send a chat attachment you need to upload the file to ConnectyCube cloud storage and obtain a link to the file (file UID). Then you need to include this UID into chat message and send it. ```javascript // for example, a file from HTML form input field const inputFile = $("input[type=file]")[0].files[0]; const fileParams = { name: inputFile.name, file: inputFile, type: inputFile.type, size: inputFile.size, public: false, }; const prepareMessageWithAttachmentAndSend = (file) => { const message = { type: dialog.type === 3 ? "chat" : "groupchat", body: "attachment", extension: { save_to_history: 1, dialog_id: dialog._id, attachments: [{ uid: file.uid, id: file.id, type: "photo" }], }, }; // send the message message.id = ConnectyCube.chat.send(dialog._id, message); }; ConnectyCube.storage .createAndUpload(fileParams) .then(prepareMessageWithAttachmentAndSend) .catch((error) => {}); ``` Response example from `ConnectyCube.storage.createAndUpload(fileParams)`: ```json { "account_id": 7, "app_id": 12, "blob_object_access": { "blob_id": 421517, "expires": "2020-10-06T15:51:38Z", "id": 421517, "object_access_type": "Write", "params": "https://s3.amazonaws.com/cb-shared-s3?Content-Type=text%2Fplain..." }, "blob_status": null, "content_type": "text/plain", "created_at": "2020-10-06T14:51:38Z", "id": 421517, "name": "awesome.txt", "public": false, "set_completed_at": null, "size": 11, "uid": "7cafb6030d3e4348ba49cab24c0cf10800", "updated_at": "2020-10-06T14:51:38Z" } ``` If you are running **Node.js** environment, the following code snippet can be used to access a file: ```javascript const fs = require("fs"); const imagePath = __dirname + "/dog.jpg"; let fileParams; fs.stat(imagePath, (error, stats) => { fs.readFile(srcIMG, (error, data) => { if (error) { throw error; } else { fileParams = { file: data, name: "image.jpg", type: "image/jpeg", size: stats.size, }; // upload // ... } }); }); ``` The same flow is supported on the receiver’s side. When you receive a message, you need to get the file UID and then download the file from the cloud storage. ```javascript ConnectyCube.chat.onMessageListener = (userId, message) => { if (message.extension.hasOwnProperty("attachments")) { if (message.extension.attachments.length > 0) { const fileUID = message.extension.attachments[0].uid; const fileUrl = ConnectyCube.storage.privateUrl(fileUID); const imageHTML = "photo"; // insert the imageHTML as HTML template } } }; ``` In a case you want remove a shared attachment from server: ```javascript ConnectyCube.storage .delete(file.id) .then(() => {}) .catch((error) => {}); ``` ## Attachments (location) [Section titled “Attachments (location)”](#attachments-location) Sharing location attachments is nothing but sending 2 numbers: **latitude** and **longitude**. These values can be accessed using any JS library available in npm registry. ```javascript const latitude = "64.7964274"; const longitude = "-23.7391878"; const message = { type: dialog.type === 3 ? "chat" : "groupchat", body: "attachment", extension: { save_to_history: 1, dialog_id: dialog._id, attachments: [{ latitude, longitude, type: "place" }], }, }; // send the message message.id = ConnectyCube.chat.send(dialog._id, message); ``` On the receiver’s side the location attachment can be accessed the following way: ```javascript ConnectyCube.chat.onMessageListener = (userId, message) => { if (message.extension.hasOwnProperty("attachments")) { if (message.extension.attachments.length > 0) { const attachment = message.extension.attachments[0]; const latitude = attachment.latitude; const longitude = attachment.longitude; // and now display the map // ... } } }; ``` ## Edit message [Section titled “Edit message”](#edit-message) Use the following code snippet to edit a message (correct message body). Other user(s) will receive the ‘edit’ message info via callback: ```javascript ConnectyCube.chat.editMessage({ to: 123, // either a user id if this is 1-1 chat or a chat dialog id dialogId: "52e6a9c8a18f3a3ea6001f18", body: "corrected message body", originMessageId: "58e6a9c8a1834a3ea6001f15", // origin message id to edit last: false // pass 'true' if edit last (the newest) message in history }) ... ConnectyCube.chat.onMessageUpdateListener = (messageId, isLast, updatedBody, dialogId, userId) => { } ``` Also, you can update a message via HTTP API: ```javascript // const messageIds = ""; // to update all const messageIds = ["55fd42369575c12c2e234c64", "55fd42369575c12c2e234c68"].join(","); // or one - "55fd42369575c12c2e234c64" const params = { read: 1, // mark message as read delivered: 1, // mark message as delivered message: "corrected message body", // update message body chat_dialog_id: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.message .update(messageIds, params) .then(() => {}) .catch((error) => {}); ``` ## Message reactions [Section titled “Message reactions”](#message-reactions) ### Add/Remove reactions [Section titled “Add/Remove reactions”](#addremove-reactions) User can add/remove message reactions and listen message reaction events Add ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' const reaction = '🔥' ConnectyCube.chat.message.addReaction(messageId, reaction) .then(() => {}) .catch(err => {}) ``` Remove ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' const reaction = '👎' ConnectyCube.chat.message.removeReaction(messageId, reaction) .then(() => {}) .catch(err => {}) ``` Add/Remove ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' const reactionToAdd = '👎' const reactionToRemove = '🚀' ConnectyCube.chat.message.updateReaction(messageId, reactionToAdd, reactionToRemove) .then(() => {}) .catch(err => {}) ``` ### Listen reactions [Section titled “Listen reactions”](#listen-reactions) ```javascript ConnectyCube.chat.onMessageReactionsListener = (messageId, userId, dialogId, addReaction, removeReaction) => { } ``` ### List message reactions [Section titled “List message reactions”](#list-message-reactions) User can list message reactions ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' ConnectyCube.chat.message.listReactions(messageId) .then(reactions => reactions) .catch(err => {}) ``` Response example from `ConnectyCube.chat.message.listReactions(messageId)` - [see](/server/chat#response-19) ## Delete messages [Section titled “Delete messages”](#delete-messages) Use the following code snippet to delete a message. Other user(s) will receive the ‘delete’ message info via callback: ```javascript ConnectyCube.chat.deleteMessage({ to: 123, // either a user id if this is 1-1 chat or a chat dialog id dialogId: "52e6a9c8a18f3a3ea6001f18", messageId: "58e6a9c8a1834a3ea6001f15" // message id to delete }) ... ConnectyCube.chat.onMessageDeleteListener = (messageId, dialogId, userId) => { } ``` If you want to delete a message for yourself only, use the following API: ```javascript const messageIds = ["55fd42369575c12c2e234c64", "55fd42369575c12c2e234c68"].join(","); const params = {}; ConnectyCube.chat.message .delete(messageIds, params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.message.delete(messageIds)` - [see](/server/chat#response-14) This request will remove the messages from current user history only, without affecting the history of other users. ## Unread messages count [Section titled “Unread messages count”](#unread-messages-count) You can request total unread messages count and unread count for particular dialog: ```javascript const params = { dialogs_ids: ["5356c64ab35c12bd3b108a41"] }; ConnectyCube.chat.message .unreadCount(params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.message.unreadCount(params)` - [see](/server/chat#response-11) ## Mark as read all chat messages [Section titled “Mark as read all chat messages”](#mark-as-read-all-chat-messages) The following snippet is used to mark all messages as read on a backend for dialog ID: ```javascript const messageIds = ""; // use "" to update all const params = { read: 1, chat_dialog_id: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.message .update("", params) .then(() => {}) .catch((error) => {}); ``` ## Search [Section titled “Search”](#search) The following API is used to search for messages and chat dialogs: ```javascript const params = { /* ... */ }; ConnectyCube.chat .search(params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.search(params)` - [see](/server/chat#response-15) Please refer to [Global search parameters](/server/chat#global-search) for more info on how to form search params. ## Chat alerts [Section titled “Chat alerts”](#chat-alerts) When you send a chat message and the recipient/recipients is offline, then automatic push notification will be fired. In order to receive push notifications you need to subscribe for it. Please refer to [Push Notifications](/js/push-notifications) guide. To configure push template which users receive - go to [Dashboard Console, Chat Alerts page](https://admin.connectycube.com/) Also, here is a way to avoid automatically sending push notifications to offline recipient/recipients. For it add the `silent` parameter with value `1` to the `extension` of the message. ```javascript const message = { ... extension: { ... silent: 1, }, }; ``` After sending such a message, the server won’t create the push notification for offline recipient/recipients. > **Note** > > Currently push notifications are supported on mobile environment only. ## Mark a client as Active/Inactive [Section titled “Mark a client as Active/Inactive”](#mark-a-client-as-activeinactive) When you send a chat message and the recipient/recipients is offline, then automatic push notification will be fired. Sometimes a client app can be in a background mode, but still online. In this case it’s useful to let server know that a user wants to receive push noificattions while still is connected to chat. For this particular case we have 2 handy methods: ‘markInactive’ and ‘markActive’: ```javascript ConnectyCube.chat.markInactive(); ``` ```javascript ConnectyCube.chat.markActive(); ``` The common use case for these APIs is to call ‘markInactive’ when an app goes to background mode and to call ‘markActive’ when an app goes to foreground mode. ## Get last activity [Section titled “Get last activity”](#get-last-activity) There is a way to get an info when a user was active last time, in seconds. This is a modern approach for messengers apps, e.g. to display this info on a Contacts screen or on a User Profile screen. ```javascript const userId = 123234; ConnectyCube.chat .getLastUserActivity(userId) .then((result) => { const userId = result.userId; const seconds = result.seconds; // 'userId' was 'seconds' ago }) .catch((error) => {}); ``` ## Last activity subscription [Section titled “Last activity subscription”](#last-activity-subscription) Listen to user last activity status via subscription. ```javascript ConnectyCube.chat.subscribeToUserLastActivityStatus(userId); ConnectyCube.chat.unsubscribeFromUserLastActivityStatus(userId); ConnectyCube.chat.onLastUserActivityListener = (userId, seconds) => {}; ``` ## System messages [Section titled “System messages”](#system-messages) In a case you want to send a non text message data, e.g. some meta data about chat, some events or so - there is a system notifications API to do so: ```javascript const userId = 123234; const msg = { body: "dialog/UPDATE_DIALOG", extension: { photo_uid: "7cafb6030d3e4348ba49cab24c0cf10800", name: "Our photos", }, }; ConnectyCube.chat.sendSystemMessage(userId, msg); ConnectyCube.chat.onSystemMessageListener = function (msg) {}; ``` ## Moderation [Section titled “Moderation”](#moderation) The moderation capabilities help maintain a safe and respectful chat environment. We have options that allow users to report inappropriate content and manage their personal block lists, giving them more control over their experience. ### Report user [Section titled “Report user”](#report-user) For user reporting to work, it requires the following: 1. Go to [ConnectyCube Daashboard](https://admin.connectycube.com/) 2. select your Application 3. Navigate to **Custom** module via left sidebar 4. Create new table called **UserReports** with the following fields: * **reportedUserId** - integer * **reason** - string ![Chat widget: report table in ConnectyCube dashboard](/images/chat_widget/chat-widget-report-table.png) Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```javascript const reportedUserId = 45 const reason = "User is spamming with bad words" await ConnectyCube.data.create('UserReports', { reportedUserId, reason }); ``` ### Report message [Section titled “Report message”](#report-message) For message reporting to work, the same approach to user reporting above could be used. You need to create new table called **MessageReports** with the following fields: * **reportedMessageId** - integer * **reason** - string Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```javascript const reportedMessageId = "58e6a9c8a1834a3ea6001f15" const reason = "The message contains phishing links" await ConnectyCube.data.create('MessageReports', { reportedMessageId, reason }); ``` ### Block user [Section titled “Block user”](#block-user) Block list (aka Privacy list) allows enabling or disabling communication with other users. You can create, modify, or delete privacy lists, define a default list. > The user can have multiple privacy lists, but only one can be active. #### Create privacy list [Section titled “Create privacy list”](#create-privacy-list) A privacy list must have at least one element in order to be created. You can choose a type of blocked logic. There are 2 types: * Block in one way. When you blocked a user, but you can send messages to him. * Block in two ways. When you blocked a user and you also can’t send messages to him. ```javascript const users = [ { user_id: 34, action: "deny" }, { user_id: 48, action: "deny", mutualBlock: true }, // it means you can't write to user { user_id: 18, action: "allow" }, ]; const list = { name: "myList", items: users }; ConnectyCube.chat.privacylist.create(list).catch((error) => {}); ``` > In order to be used the privacy list should be not only set, but also activated(set as default). #### Activate privacy list [Section titled “Activate privacy list”](#activate-privacy-list) In order to activate rules from a privacy list you should set it as default: ```javascript const listName = "myList"; ConnectyCube.chat.privacylist.setAsDefault(listName).catch((error) => {}); ``` #### Update privacy list [Section titled “Update privacy list”](#update-privacy-list) There is a rule you should follow to update a privacy list: * If you want to update or set new privacy list instead of current one, you should decline current default list first. ```javascript const listName = "myList"; const list = { name: listName, items: [{ user_id: 34, action: "allow" }], }; ConnectyCube.chat.privacylist .setAsDefault(null) .then(() => ConnectyCube.chat.privacylist.update(list)) .then(() => ConnectyCube.chat.privacylist.setAsDefault(listName)) .catch((error) => {}); ``` #### Retrieve privacy list names [Section titled “Retrieve privacy list names”](#retrieve-privacy-list-names) To get a list of all your privacy lists’ names use the following request: ```javascript let names; ConnectyCube.chat.privacylist .getNames() .then((response) => (names = response.names)) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.privacylist.getNames()`: ```json { "active": null, "default": null, "names": ["myList", "blockedusers"] } ``` #### Retrieve privacy list with name [Section titled “Retrieve privacy list with name”](#retrieve-privacy-list-with-name) To get the privacy list by name you should use the following method: ```javascript const listName = "myList"; let name, items; ConnectyCube.chat.privacylist.getList(listName).then((response) => { name = response.name; items = response.items; }); ``` Response example from `ConnectyCube.chat.privacylist.getList(listName)`: ```json { "name": "myList", "items": [ { "user_id": 34, "action": "deny" }, { "user_id": 48, "action": "deny", "mutualBlock": true }, { "user_id": 18, "action": "allow" } ] } ``` #### Remove privacy list [Section titled “Remove privacy list”](#remove-privacy-list) To delete a list you can call a method below or you can edit a list and set items to `nil`. ```javascript const listName = "myList"; ConnectyCube.chat.privacylist.delete(listName).catch((error) => {}); ``` #### Blocked user attempts to communicate with user [Section titled “Blocked user attempts to communicate with user”](#blocked-user-attempts-to-communicate-with-user) Blocked users will be receiving an error when trying to chat with a user in a 1-1 chat and will be receiving nothing in a group chat: ```javascript ConnectyCube.chat.onMessageErrorListener = function (messageId, error) {}; ``` ## Ping server [Section titled “Ping server”](#ping-server) Sometimes, it can be cases where TCP connection to Chat server can go down without the application layer knowing about it. To check that chat connection is still alive or to keep it to be alive there is a ping method: ```javascript const PING_TIMEOUT = 3000; // default is 5000 ms ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT) .then(() => { // Chat connection is alive }).catch((error) => { // No connection with chat server // Let's try to re-connect }) ``` ### Pause chat connection when ping fails [Section titled “Pause chat connection when ping fails”](#pause-chat-connection-when-ping-fails) Temporarily stop the chat connection if the server does not respond to the ping request. This allows the client to attempt automatic reconnection as soon as the chat connection becomes available again: ```javascript const PING_TIMEOUT = 1000; try { await ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT) } catch (error) { ConnectyCube.chat.terminate() } ``` ### Handle browser offline event with ping check [Section titled “Handle browser offline event with ping check”](#handle-browser-offline-event-with-ping-check) Listen for the browser’s `offline` event and try to ping the chat server if the event fires: ```javascript const PING_TIMEOUT = 3000; window.addEventListener("offline", () => { try { await ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT); } catch (error) { ConnectyCube.chat.terminate(); } }); ``` ### Maintain chat connection with periodic pings (VPN-friendly) [Section titled “Maintain chat connection with periodic pings (VPN-friendly)”](#maintain-chat-connection-with-periodic-pings-vpn-friendly) Regularly send pings to check if the chat connection is still alive. If a ping fails, send another ping after a short delay to handle cases where the first failure was temporary; terminate the connection if the second ping also fails: ```javascript const PING_INTERVAL = 40000; const PING_TIMEOUT = 1000; const PING_DELAY = 3000; let pingInterval = null; const startPingWithInterval = () => { pingInterval = setInterval(async () => { try { await ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT); } catch (error) { setTimeout(async () => { try { await ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT); } catch (error) { ConnectyCube.chat.terminate(); } }, PING_DELAY); } }, PING_INTERVAL); } const stopPingWithInterval = () => { if (pingInterval) { clearInterval(pingInterval); pingInterval = null; } } ``` # Video Calling > Empower your Web applications with our JavaScript Video Calling P2P API. Enable secure and immersive peer-to-peer video calls for enhanced user experience ConnectyCube **Video Calling P2P API** is built on top of [WebRTC](https://webrtc.org/) protocol and based on top of [WebRTC Mesh](https://webrtcglossary.com/mesh/) architecture. Max people per P2P call is 4. > To get a difference between **P2P calling** and **Conference calling** please read our [ConnectyCube Calling API comparison](https://connectycube.com/2020/04/15/connectycube-calling-api-comparison/) blog page. ## Preparations [Section titled “Preparations”](#preparations) [ConnectyCube Chat API](/js/messaging) is used as a signaling transport for Video Calling API, so in order to start using Video Calling API you need to [connect user to Chat](/js/messaging#connect-to-chat). ## Create video session [Section titled “Create video session”](#create-video-session) In order to use Video Calling API you need to create a call session object - choose your opponents with whom you will have a call and a type of session (VIDEO or AUDIO): > **Note** > > The `calleesIds` array must contain the opponents ids only and exclude current user id. ```javascript const calleesIds = [56, 76, 34]; // User's ids const sessionType = ConnectyCube.videochat.CallType.VIDEO; // AUDIO is also possible const additionalOptions = {}; const session = ConnectyCube.videochat.createNewSession(calleesIds, sessionType, additionalOptions); ``` > **Note**: In a case of low bandwidth network, you can try to limit the call bandwidth cap to get better quality vs stability results. It can be done by passing `const additionalOptions = {bandwidth: 256};` or 128 value. ## Access local media stream [Section titled “Access local media stream”](#access-local-media-stream) In order to have a video chat session you need to get an access to the user’s devices (webcam / microphone): ```javascript const mediaParams = { audio: true, video: true }; session .getUserMedia(mediaParams) .then((localStream) => {}) .catch((error) => {}); ``` This method lets the browser ask the user for permission to use devices. You should allow this dialog to access the stream. Otherwise, the browser can’t obtain access and will throw an error for `getUserMedia` callback function. For more information about possible audio/video constraints, here is a good code sample from WebRTC team how to work with getUserMedia constraints: ## Call quality [Section titled “Call quality”](#call-quality) **Limit bandwidth** Despite WebRTC engine uses automatic quality adjustement based on available Internet bandwidth, sometimes it’s better to set the max available bandwidth cap which will result in a better and smoother user experience. For example, if you know you have a bad internet connection, you can limit the max available bandwidth to e.g. 256 Kbit/s. This can be done either when initiate a call (see below ‘Initiate a call’ API documentation): ```javascript session.call({maxBandwidth: 256}); // default is 0 - unlimited ``` which will result in limiting the max vailable bandwidth for ALL participants or/and during a call: ```javascript session.setMaxBandwidth(512); // set 0 to remove the limit ``` which will result in limiting the max available bandwidth for current user only. **HD video quality** If HD video quality is required - the following audio/video constraints are required to pass: ```javascript {video: { width: 1280, height: 720 }, audio: true} ``` More info about all possible is are available here ## Attach local media stream [Section titled “Attach local media stream”](#attach-local-media-stream) Then you should attach your local media stream to HTML video element. For **Web-like environments**, including Cordova - use the following method: ```javascript session.attachMediaStream("myVideoElementId", localStream, { muted: true, mirror: true, }); ``` ## Initiate a call [Section titled “Initiate a call”](#initiate-a-call) ```javascript const extension = {}; session.call(extension, (error) => {}); ``` The extension is used to pass any extra parameters in the request to your opponents. After this, your opponents will receive a callback call: ```javascript ConnectyCube.videochat.onCallListener = function (session, extension) {}; ``` Or if your opponents are offline or did not answer the call request: ```javascript ConnectyCube.videochat.onUserNotAnswerListener = function (session, userId) {}; ``` ## Accept a call [Section titled “Accept a call”](#accept-a-call) To accept a call the following code snippet is used: ```javascript ConnectyCube.videochat.onCallListener = function (session, extension) { // Here we need to show a dialog with 2 buttons - Accept & Reject. // By accepting -> run the following code: // // 1. await session.getUserMedia (...) // // 2. Accept call request: const extension = {}; session.accept(extension); }; ``` After this, you will get a confirmation in the following callback: ```javascript ConnectyCube.videochat.onAcceptCallListener = function (session, userId, extension) {}; ``` Also, both the caller and opponents will get a special callback with the remote stream: ```javascript ConnectyCube.videochat.onRemoteStreamListener = function (session, userID, remoteStream) { // attach the remote stream to DOM element session.attachMediaStream("remoteOpponentVideoElementId", remoteStream); }; ``` From this point, you and your opponents should start seeing each other. ## Receive a call in background [Section titled “Receive a call in background”](#receive-a-call-in-background) For mobile apps, it can be a situation when an opponent’s user app is either in closed (killed) or background (inactive) state. In this case, to be able to still receive a call request, you can use Push Notifications. The flow should be as follows: * a call initiator should send a push notification along with a call request * when an opponent’s app is killed or in background state - an opponent will receive a push notification about an incoming call, and will be able to accept/reject the call. If accepted or pressed on a push notification - an app will be opened, a user should auto login and connect to chat and then will be able to join an incoming call. Please refer to Push Notifications API guides regarding how to integrate Push Notifications in your app: * [Push Notifications API guide: JS](/js/push-notifications) For even better integration - CallKit and VoIP push notifications can be used. Please check `CallKit and VoIP push notifications` section on each platform Push Notifications API guide page. ## Reject a call [Section titled “Reject a call”](#reject-a-call) ```javascript const extension = {}; session.reject(extension); ``` After this, the caller will get a confirmation in the following callback: ```javascript ConnectyCube.videochat.onRejectCallListener = function (session, userId, extension) {}; ``` Sometimes, it could a situation when you received a call request and want to reject, but the call session object has not arrived yet. It could be in a case when you integrated CallKit to receive call requests while an app is in background/killed state. To do a reject in this case, the following snippet can be used: ```javascript const params = { sessionID: callId, recipientId: callInitiatorID, platform: 'web' }; await ConnectyCube.videochat.callRejectRequest(params); ``` ## End a call [Section titled “End a call”](#end-a-call) ```javascript const extension = {}; session.stop(extension); ``` After this, the opponents will get a confirmation in the following callback: ```javascript ConnectyCube.videochat.onStopCallListener = function (session, userId, extension) {}; ``` ## Mute audio [Section titled “Mute audio”](#mute-audio) ```javascript session.mute("audio"); session.unmute("audio"); ``` ## Mute video [Section titled “Mute video”](#mute-video) ```javascript session.mute("video"); session.unmute("video"); ``` ## Switch video cameras [Section titled “Switch video cameras”](#switch-video-cameras) First of all you need to obtain all your device’s available cameras: ```javascript let deviceInfo, deviceId, deviceLabel; ConnectyCube.videochat .getMediaDevices("videoinput") .then((devices) => { if (devices.length) { // here is a list of all available cameras for (let i = 0; i !== devices.length; ++i) { deviceInfo = devices[i]; deviceId = deviceInfo.deviceId; deviceLabel = deviceInfo.label; } } }) .catch((error) => {}); ``` Then you can choose some `deviceId` and switch the video stream to exact this device: ```javascript const constraints = { video: deviceId }; session .switchMediaTracks(constraints) .then((stream) => {}) .catch((error) => {}); ``` ## Switch audio output [Section titled “Switch audio output”](#switch-audio-output) For switching audio - use the same above flow for switching camera. Just replace a `videoinput` to `audioinput` in `getMediaDevices` and `video` to `audio` in `constraints`. ## Screen Sharing [Section titled “Screen Sharing”](#screen-sharing) Request a desktop stream by calling `getDisplayMedia`: ```javascript const constraints = { video: { width: 1280, height: 720, frameRate: { ideal: 10, max: 15 }, }, audio: true, }; session .getDisplayMedia(constraints) .then((localDesktopStream) => {}) .catch((error) => {}); ``` More info about what else options can be passed can be found here If the local stream already exists, the next call to getUserMedia or getDisplayMedia will update the tracks in the stream and preserve the track’s enabled state for the audio track. ## Group video calls [Section titled “Group video calls”](#group-video-calls) Because of [Mesh architecture](https://webrtcglossary.com/mesh/) we use for multipoint where every participant sends and receives its media to all other participants, current solution supports group calls with up to 4 people. Also ConnectyCube provides an alternative solution for up to 12 people - [Multiparty Video Conferencing API](/js/videocalling-conference). ## Monitor connection state [Section titled “Monitor connection state”](#monitor-connection-state) There is a callback function to track the session connection state: ```javascript ConnectyCube.videochat.onSessionConnectionStateChangedListener = ( session, userID, connectionState ) => { }; ``` The possible values of connectionState are those of an enum of type `ConnectyCube.videochat.SessionConnectionState`: * ConnectyCube.videochat.SessionConnectionState.UNDEFINED * ConnectyCube.videochat.SessionConnectionState.CONNECTING * ConnectyCube.videochat.SessionConnectionState.CONNECTED * ConnectyCube.videochat.SessionConnectionState.DISCONNECTED * ConnectyCube.videochat.SessionConnectionState.FAILED * ConnectyCube.videochat.SessionConnectionState.CLOSED * ConnectyCube.videochat.SessionConnectionState.COMPLETED ## Tackling Network changes [Section titled “Tackling Network changes”](#tackling-network-changes) If a user’s network environment changes (e.g., switching from Wi-Fi to mobile data), the existing call connection might no longer be valid. Normally, in a case of short network interruptions, the ConnectyCube SDK will automatically restore the call so you can see via `onSessionConnectionStateChangedListener` callback with `connectionState` changing to `DISCONNECTED` and then again to `CONNECTED`. But not all cases are the same, and in some of them the connection needs to be **manually** refreshed due to various issues like NAT or firewall behavior changes or even longer network environment changes, e.g. when a user is offline for more than 30 seconds. This is where ICE restart helps to re-establish the connection to find a new network path for communication. The correct and recommended way for an application to handle all such ‘bad’ cases is to trigger an ICE restart when the connection state goes to either `FAILED` or `DISCONNECTED` for an extended period of time (e.g. > 30 seconds). Following is the preliminary code snippet regarding how to work with ICE restart: * Firstly, we have to disable the call termination logic after the network is disconnected for > 30 seconds by increasing the `videochat.disconnectTimeInterval` value to e.g. 5 mins. ```plaintext const appConfig = { videochat: { disconnectTimeInterval: 300, } }; ConnectyCube.init(credentials, appConfig); ``` * Secondly, define a variable which can track the Internet connection state: ```plaintext let isOnline = window.navigator.onLine; window.onoffline = () => { isOnline = false; }; window.ononline = () => { isOnline = true; }; ``` * Thirdly, define a function that will perform ICE restart: ```plaintext async maybeDoIceRestart(session, userID) { try { // firstly let's check if we are still connected to chat await ConnectyCube.chat.pingWithTimeout(); // do ICE restart if (session.canInitiateIceRestart(userID)) { session.iceRestart(userID); } } catch (error) { console.error(error.message); // chat ping request has timed out, // so need to reconnect to Chat await ConnectyCube.chat.disconnect(); await ConnectyCube.chat.connect({ userId: currentUser.id, password: currentUser.password, }) // do ICE restart if (session.canInitiateIceRestart(userID)) { session.iceRestart(userID); } } } ``` * Fourthly, define a `ConnectyCube.videochat.onSessionConnectionStateChangedListener` callback and try to perform ICE restart if not automatic call restoration happened after 30 seconds: ```plaintext iceRestartTimeout = null; needIceRestartForUsersIds = []; ConnectyCube.videochat.onSessionConnectionStateChangedListener = ( session, userID, connectionState ) => { console.log( "[onSessionConnectionStateChangedListener]", userID, connectionState ); const { DISCONNECTED, FAILED, CONNECTED, CLOSED } = ConnectyCube.videochat.SessionConnectionState; if (connectionState === DISCONNECTED || connectionState === FAILED) { iceRestartTimeout = setTimeout(() => { // Connection not restored within 30 seconds, trying ICE restart... if (isOnline) { maybeDoIceRestart(session, userID); } else { // Skip ICE restart, no Internet connection this.needIceRestartForUsersIds.push(userID); } }, 30000); } else if (connectionState === CONNECTED) { clearTimeout(iceRestartTimeout); iceRestartTimeout = null; needIceRestartForUsersIds = []; } else if (connectionState === CLOSED) { needIceRestartForUsersIds = []; } }; ``` * Finally, in a case a user got working Internet connection later, do ICE restart there: ```plaintext window.ononline = () => { if (!isOnline) { if (session && needIceRestartForUsersIds.length > 0) { for (let userID of needIceRestartForUsersIds) { maybeDoIceRestart(session, userID); } } } isOnline = true; }; ``` After these changes the call connection should be restored to working state again. ## Configuration [Section titled “Configuration”](#configuration) There are various calling related configs that can be changed. ### alwaysRelayCalls [Section titled “alwaysRelayCalls”](#alwaysrelaycalls) The `alwaysRelayCalls` config sets the WebRTC `RTCConfiguration.iceTransportPolicy` [config](/js/#default-configuration). Setting it to `true` means the calling media will be routed through TURN server all the time, and not directly P2P between users even if the network path allows it: ```javascript const appConfig = { videochat: { alwaysRelayCalls: true, }, }; ``` ## Recording [Section titled “Recording”](#recording) For the recording feature implementation you can use [MediaStream Recording API](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API) The recorder accepts a media stream and record it to file. Both local and remote streams can be recorded and saved to file. There is also a good article about client-side recording implementation ## Continue calling in background [Section titled “Continue calling in background”](#continue-calling-in-background) If you are developing dedicated apps for iOS and Android - it’s required to apply additional configure for the app to continue playing calling audio when it goes into the background. On iOS: there is no way to continue a video call in background because of some OS restrictions. What is supported there is to continue with voice calling while an app is in background. Basically, the recommended to achieve this is to switch off device camera when an app goes to background and then switch camera on back when an app goes to foreground. Furthermore, even voice background call are blocked by default on iOS. To unblock - you need to setup proper background mode capabilities in your project. Please find the [Enabling Background Audio link](https://developer.apple.com/documentation/avfoundation/media_playback_and_selection/creating_a_basic_video_player_ios_and_tvos/enabling_background_audio) with more information how to do it properly. Generally speaking, you need to enable `Voice over IP` and `Remote notifications` capabilities: ![Setup Xcode VOIP capabilities](/_astro/voip_capabilities.DFieoPnY_15jdKd.webp) For Android, we also recommend to implement the same camera switch flow when go to background and then return to foreground. # Video Conferencing > Discover the simplicity of integrating conference video calling into your Web app with our easy-to-use API. Empower users to connect from anywhere. ConnectyCube **Multiparty Video Conferencing API** is built on top of [WebRTC](https://webrtc.org/) protocol and based on top of [WebRTC SFU](https://webrtcglossary.com/sfu/) architecture. Max people per Conference call is 12. Video Conferencing is available starting from [Advanced plan](https://connectycube.com/pricing/). > To get a difference between **P2P calling** and **Conference calling** please read our [ConnectyCube Calling API comparison](https://connectycube.com/2020/04/15/connectycube-calling-api-comparison/) blog page. ## Features supported [Section titled “Features supported”](#features-supported) * Video/Audio Conference with up to 12 people * Join-Rejoin video room functionality (like Skype) * Guest rooms * Mute/Unmute audio/video streams * Display bitrate * Switch video input device (camera) * Switch audio input device (microphone) ## Preparations [Section titled “Preparations”](#preparations) ### Connect adapter.js [Section titled “Connect adapter.js”](#connect-adapterjs) Make sure you connected the latest version of [adapter.js](https://github.com/webrtc/adapter/tree/master/release) Note If you use Chrome browser and emulate iOS device - you may get “The adapter.js addTrack polyfill only supports a single stream which is associated with the specified track” error. That’s a known issue with adapter.js where it wrongly parses the user agent. Either use real iOS device or Safari browser to test iOS behaviour. ### Set up config [Section titled “Set up config”](#set-up-config) In order to start working with Multiparty Video Conferencing API you need to initialize a client: ```javascript const credentials = { appId: ..., authKey: "...", } const MULTIPARTY_SERVER_ENDPOINT = 'wss://...:8989'; const appConfig = { debug: { mode: 1 }, conference: { server: MULTIPARTY_SERVER_ENDPOINT }, } ConnectyCube.init(credentials, appConfig) ``` > Default multiparty server endpoint is wss\://janus.connectycube.com:8989 ([see](/js/#default-configuration)). ## Create meeting [Section titled “Create meeting”](#create-meeting) In order to have a conference call, a meeting object has to be created. ```javascript const params = { name: "My meeting", start_date: timestamp, end_date: timestamp attendees: [ {id: 123, email: "..."}, {id: 124, email: "..."} ], record: false, chat: false }; ConnectyCube.meeting.create(params) .then(meeting => { const confRoomId = meeting._id; }) .catch(error => { }); ``` * As for `attendees` - either ConnectyCube users ids or external emails can be provided. * If you want to schedule a meeting - pass `start_date` and `end_date`. * Pass `chat: true` if you want to have a chat connected to meeting. * Pass `record: true` if you want to have a meeting call recorded. Read more about Recording feature Once meeting is created, you can use `meeting._id` as a conf room identifier in the below requests when join a call. ## Create call session [Section titled “Create call session”](#create-call-session) Once a meeting is created/retrieved, you can create a conf call session. Normally, each browser tab will need a single session with the server: ```javascript const session = ConnectyCube.videochatconference.createNewSession(); ``` Once a session is created, you can interact with a Video Conferencing API. ## Access local media stream [Section titled “Access local media stream”](#access-local-media-stream) In order to have a video chat session you need to get an access to the user’s devices (webcam / microphone): ```javascript const mediaParams = { audio: true, video: true, }; session .getUserMedia(mediaParams) .then((localStream) => {}) .catch((error) => {}); ``` This method lets the browser ask the user for permission to use devices. You should allow this dialog to access the stream. Otherwise, the browser can’t obtain access and will throw an error for `getUserMedia` callback function. For more information about possible audio/video constraints, here is a good code sample from WebRTC team how to work with getUserMedia constraints: ### HD video quality [Section titled “HD video quality”](#hd-video-quality) If HD video quality is required - the following audio/video constraints are required to pass: ```javascript {video: { width: 1280, height: 720 }, audio: true} ``` More info about all possible is are available here ## Attach local media stream [Section titled “Attach local media stream”](#attach-local-media-stream) Then you should attach your local media stream to HTML video element. For **Web-like environments**, including Cordova - use the following method: ```javascript session.attachMediaStream("myVideoElementId", localStream, { muted: true, mirror: true, }); ``` For **ReactNative environment** - use the following method: ```javascript import {RTCView} from 'react-native-connectycube'; // pass a local or remote stream to the RTCView component ... ... ``` ## Join room [Section titled “Join room”](#join-room) To jump into video chat with users you should join it: ```javascript session .join(confRoomId, userId, userDisplayName) .then(() => {}) .catch((error) => {}); ``` To check current joined video room use the following property: ```javascript const confRoomId = session.currentRoomId; ``` ## Join as listener [Section titled “Join as listener”](#join-as-listener) It can be a requirement where it needs to join a room as listener only, w/o publishing own media stream. It can be useful for a case with a teacher and many students where normally students join a call as listeners and only a teacher publishes own media stream: ```javascript session .joinAsListener(confRoomId, userId, userDisplayName) .then(() => {}) .catch((error) => {}); ``` ## List online participants [Section titled “List online participants”](#list-online-participants) To list online users in a room: ```javascript session .listOfOnlineParticipants() .then((participants) => {}) .catch((error) => {}); ``` ## Events [Section titled “Events”](#events) There are 6 events you can listen for: ```javascript ConnectyCube.videochatconference.onParticipantJoinedListener = ( session, userId, userDisplayName, isExistingParticipant ) => {}; ConnectyCube.videochatconference.onParticipantLeftListener = (session, userId) => {}; ConnectyCube.videochatconference.onRemoteStreamListener = (session, userId, stream) => {}; ConnectyCube.videochatconference.onSlowLinkListener = (session, userId, uplink, nacks) => {}; ConnectyCube.videochatconference.onRemoteConnectionStateChangedListener = (session, userId, iceState) => {}; ConnectyCube.videochatconference.onSessionConnectionStateChangedListener = (session, iceState) => {}; ConnectyCube.videochatconference.onErrorListener = (session, error) => {}; ``` ## Mute/Unmute audio [Section titled “Mute/Unmute audio”](#muteunmute-audio) You can mute/unmute your own audio: ```javascript // mute session.muteAudio(); // unmute session.unmuteAudio(); //check mute state session.isAudioMuted(); // true/false ``` ## Mute/Unmute video [Section titled “Mute/Unmute video”](#muteunmute-video) You can mute/unmute your own video: ```javascript // mute session.muteVideo(); // unmute session.unmuteVideo(); //check mute state session.isVideoMuted(); // true/false ``` ## List of devices [Section titled “List of devices”](#list-of-devices) ```javascript // get all devices ConnectyCube.videochatconference .getMediaDevices() .then((allDevices) => {}) .catch((error) => {}); // only video devices ConnectyCube.videochatconference .getMediaDevices(ConnectyCube.videochatconference.DEVICE_INPUT_TYPES.VIDEO) .then((videoDevices) => {}) .catch((error) => {}); // only audio devices ConnectyCube.videochatconference .getMediaDevices(ConnectyCube.videochatconference.DEVICE_INPUT_TYPES.AUDIO) .then((audioDevices) => {}) .catch((error) => {}); ``` ## Switch video(camera)/audio(microphone) input device [Section titled “Switch video(camera)/audio(microphone) input device”](#switch-videocameraaudiomicrophone-input-device) ```javascript const deviceId = "..."; // switch video session .switchMediaTracks({ video: deviceId }) .then((updatedLocaStream) => {}) // you can reattach local stream .catch((error) => {}); // switch audio session .switchMediaTracks({ audio: deviceId }) .then((updatedLocaStream) => {}) // you can reattach local stream .catch((error) => {}); ``` ## Screen Sharing [Section titled “Screen Sharing”](#screen-sharing) Request a desktop stream by calling `getDisplayMedia`: ```javascript const constraints = { video: { width: 1280, height: 720, frameRate: { ideal: 10, max: 15 }, }, audio: true, }; session .getDisplayMedia(constraints) .then((localDesktopStream) => {}) .catch((error) => {}); ``` More info about what else options can be passed can be found here If the local stream already exists, the next call to getUserMedia or getDisplayMedia will update the tracks in the stream and preserve the track’s enabled state for the audio track. ## Get remote user bitrate [Section titled “Get remote user bitrate”](#get-remote-user-bitrate) ```javascript const bitrate = session.getRemoteUserBitrate(userId); ``` ## Get remote user mic level [Section titled “Get remote user mic level”](#get-remote-user-mic-level) ```javascript const micLevel = session.getRemoteUserVolume(userId); ``` ## Leave room and destroy conf session [Section titled “Leave room and destroy conf session”](#leave-room-and-destroy-conf-session) To leave current joined video room: ```javascript session .leave() .then(() => {}) .catch((error) => {}); ``` ## Retrieve meetings [Section titled “Retrieve meetings”](#retrieve-meetings) Retrieve a meeting by id: ```javascript const params = { _id: meetingId, }; ConnectyCube.meeting .get(params) .then((meeting) => {}) .catch((error) => {}); ``` Retrieve a list of meetings: ```javascript const params = { limit: 5, offset: 0 }; ConnectyCube.meeting .get(params) .then((meetings) => {}) .catch((error) => {}); ``` ## Edit meeting [Section titled “Edit meeting”](#edit-meeting) A meeting creator can edit a meeting: ```javascript const params = { name, start_date, end_date }; ConnectyCube.meeting .update(meetingId, params) .then((meeting) => {}) .catch((error) => {}); ``` ## Delete meeting [Section titled “Delete meeting”](#delete-meeting) A meeting creator can delete a meeting: ```javascript ConnectyCube.meeting .delete(meetingId) .then(() => {}) .catch((error) => {}); ``` ## Recording [Section titled “Recording”](#recording) Server-side recording is available. Read more about Recording feature ### Retrieve recordings with download url [Section titled “Retrieve recordings with download url”](#retrieve-recordings-with-download-url) ```javascript ConnectyCube.meeting .getRecordings(meetingId) .then(() => {}) .catch((error) => {}); ``` ## Continue calling in background [Section titled “Continue calling in background”](#continue-calling-in-background) If you are developing dedicated apps for iOS and Android - it’s required to apply additional configure for the app to continue playing calling audio when it goes into the background. On iOS: there is no way to continue a video call in background because of some OS restrictions. What is supported there is to continue with voice calling while an app is in background. Basically, the recommended to achieve this is to switch off device camera when an app goes to background and then switch camera on back when an app goes to foreground. Furthermore, even voice background call are blocked by default on iOS. To unblock - you need to setup proper background mode capabilities in your project. Please find the [Enabling Background Audio link](https://developer.apple.com/documentation/avfoundation/media_playback_and_selection/creating_a_basic_video_player_ios_and_tvos/enabling_background_audio) with more information how to do it properly. Generally speaking, you need to enable `Voice over IP` and `Remote notifications` capabilities: ![Setup Xcode VOIP capabilities](/_astro/voip_capabilities.DFieoPnY_15jdKd.webp) For Android, we also recommend to implement the same camera switch flow when go to background and then return to foreground. # Address Book > Effortlessly upload, sync, and access ConnectyCube users from your phone contacts in your Web app with Address Book API. Address Book API provides an interface to work with phone address book, upload it to server and retrieve already registered ConnectyCube users from your address book. With conjunction of [User authentication via phone number](/js/authentication-and-users#authentication-via-phone-number) you can easily organise a state of the art logic in your App where you can easily start chatting with your phone contacts, without adding them manually as friends - the same what you can see in WhatsApp, Telegram, Facebook Messenger and Viber. ## Upload Address Book [Section titled “Upload Address Book”](#upload-address-book) First of all you need to upload your Address Book to the backend. It’s a normal practice to do a full upload for the 1st time and then upload diffs on future app logins. ```javascript const CONTACTS = [ { name: "Gordie Kann", phone: "1879108395", }, { name: "Wildon Gilleon", phone: "2759108396", }, { name: "Gaston Center", phone: "3759108396", }, ]; const options = {}; // const options = {'force': 1, 'udid': 'XXX'}; ConnectyCube.addressbook .uploadAddressBook(CONTACTS, options) .then(() => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.addressbook.uploadAddressBook(params)` - [see](/server/address_book#response) * You also can edit an existing contact by providing a new name for it. * You also can upload more contacts, not just all in one request - they will be added to your address book on the backend. If you want to override the whole address book on the backend - just provide `force: 1` option. * You also can remove a contact by setting `contact.destroy = 1;` * A device UDID is used in cases where user has 2 or more devices and contacts sync is off. Otherwise - user has a single global address book. ## Retrieve Address Book [Section titled “Retrieve Address Book”](#retrieve-address-book) If you want you can retrieve your uploaded address book: ```javascript ConnectyCube.addressbook .get() .then((result) => {}) .catch((error) => {}); ``` or with UDID: ```javascript const UDID = "XXX"; ConnectyCube.addressbook .get(UDID) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.addressbook.get()` - [see](/server/address_book#response-1) ## Retrieve Registered Users [Section titled “Retrieve Registered Users”](#retrieve-registered-users) Using this request you can easily retrieve the ConnectyCube users - you phone Address Book contacts that already registered in your app, so you can start communicate with these users right away: ```javascript ConnectyCube.addressbook .getRegisteredUsers() .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.addressbook.getRegisteredUsers()` - [see](/server/address_book#response-2) If isCompact = true - server will return only id and phone fields of User. Otherwise - all User’s fields will be returned. ## Push notification on new contact joined [Section titled “Push notification on new contact joined”](#push-notification-on-new-contact-joined) There is a way to get a push notification when some contact from your Address Book registered in an app. You can enable this feature at [ConnectyCube Dashboard](https://admin.connectycube.com), Users module, Settings tab: ![Setup push notification on new contact joined](/_astro/setup_push_notification_on_new_contact_joined.DTG1vj8m_1gVrvB.webp) # 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 = "photo"; ``` ## 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. ![Create class scheme](/_astro/create_scheme.BZpQA4pv_Z1tQkMW.webp ":size=400") 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 ![Create class scheme](/_astro/created_scheme.DuEkinE2_2l5wmH.webp) 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. ![Edit permissions levels](/_astro/edit_class_permissions.DwZG2uer_2451o4.webp ":size=400") ### 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: ![Set permissions for record](/_astro/edit_record_permissions.YjI8FGha_Zb7hOq.webp ":size=400") 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** ![Adding Project in Firebase](/_astro/firebase_add_project.Dq7GHe8f_maP4o.webp) > **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**. ![Add a project 1 of 3](/_astro/firebase_add_project_2_1.CTNFPmFI_1qpQUj.webp ":size=400%") ![Add a project 2 of 3](/_astro/firebase_add_project_2_2.xX7Ff4sg_Z1D8se6.webp ":size=400%") 4. Configure Google Analytics for your project and click **Create project**. ![Add a project 3 of 3](/_astro/firebase_add_project_2_3._yPP5HiQ_Z1ctbYe.webp ":size=400%") Then click Continue. ![Click Continue](/_astro/firebase_add_project_4.Cus9Uz0-_Z2oLn44.webp) 5. Select platform for which you need Firebase ![Select platform](/_astro/firebase_add_project_platforms.CynsV-4f_Z1OKfii.webp) If you already have an app in your project, go to project Overview page and click Add another app link: ![Add Firebase to your Web app](/_astro/firebase_add_to_web_project.ChfY2N1I_YTAHo.webp) ## 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: ![Add Firebase to Web](/_astro/firebase_add_firebase_to_web.Cq4Oet5x_176CMz.webp) > **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: ![Enable phone authentication in Firebase](/_astro/firebase_authentication_phone.DOqJbuvO_Ziw2EF.webp) 2. Enable **Phone** number sign-in method: ![Enable phone authentication in Firebase](/_astro/firebase_authentication_phone_2.hEGZ-tgZ_1zWjJu.webp) 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: ![Authorized domains](/_astro/firebase_web_add_domain.CDBBAQXq_2rmNRy.webp) Then enter your domain name on the pop-up window that appears and click **Add**: ![Authorized domains](/_astro/firebase_web_add_domain_2.CYgr1TgH_Z1vMnX5.webp) 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**: ![Get your Project ID](/_astro/firebase_project_id.DbPbXrrM_ZLtFkp.webp) 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. ![User logged in](/_astro/firebase_user_logged_in.DqybzeAt_Z1rKYfj.webp) 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. ![Web Push Notifications](/_astro/web_push_notifications.BjEJ9byd_Z1CweCg.webp) ## 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. ![Whiteboard demo](/_astro/whiteboard_1024x504.by1QVA4x_1yUk0p.webp) 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. ![Dialog Extensions fields configuration example](/_astro/dialog_custom_params.CrGT0s8Z_1XWSCw.webp) When create a dialog, the `extensions` field object must contain allowed fields only. Others fields will be ignored. The values will be casted to string. ```javascript const params = { type: 2, name: "Friday party", occupants_ids: [29085, 29086, 29087], description: "lets dance the night away", extensions: {location: "Sun bar"}, }; ConnectyCube.chat.dialog .create(params) .then((dialog) => {}) .catch((error) => {}); ``` When remove custom field in Admin panel, this field will be removed in all dialogs respectively. These parameters also can be used as a filter for retrieving dialogs. ### Chat permissions [Section titled “Chat permissions”](#chat-permissions) Chat could have different permissions to managa data access. This is managed via `permissions` field. At the moment, only one permission available - `allow_preview` - which allows to retrieve dialog’s messages for user who is not a member of dialog. This is useful when implement feature like Channels where a user can open chat and preview messages w/o joining it. ## List dialogs [Section titled “List dialogs”](#list-dialogs) It’s common to request all your dialogs on every app login: ```javascript const filters = {}; ConnectyCube.chat.dialog .list(filters) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.list(filters)` - [see](/server/chat#response) More filters available [here](/server/chat#retrieve-chat-dialogs) If you want to retrieve only dialogs updated after some specific date time, you can use `updated_at[gt]` filter. This is useful if you cache dialogs somehow and do not want to obtain the whole list of your dialogs on every app start. ## Update dialog [Section titled “Update dialog”](#update-dialog) User can update group chat name, photo or add/remove occupants: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const toUpdateParams = { name: "Crossfit2" }; ConnectyCube.chat.dialog .update(dialogId, toUpdateParams) .then((dialog) => {}) .catch((error) => {}); ``` ## Add/Remove occupants [Section titled “Add/Remove occupants”](#addremove-occupants) To add more occupants use `push_all` operator. To remove yourself from the dialog use `pull_all` operator: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const toUpdateParams = { push_all: { occupants_ids: [97, 789] } }; ConnectyCube.chat.dialog .update(dialogId, toUpdateParams) .then((dialog) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.update(dialogId, toUpdateParams)` - [see](/server/chat#response-2) > **Note** > > Only group chat owner can remove other users from group chat. ## Remove dialog [Section titled “Remove dialog”](#remove-dialog) The following snippet is used to delete a dialog: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; // const dialogIds = ['5356c64ab35c12bd3b108a41', ..., '5356c64ab35c12bd3b108a84'] ConnectyCube.chat.dialog.delete(dialogId).catch((error) => {}); ``` This request will remove this dialog for current user, but other users still will be able to chat there. Only group chat owner can remove the group dialog for all users. You can also delete multiple dialogs in a single request. ## Clear dialog history [Section titled “Clear dialog history”](#clear-dialog-history) The following snippet is used to clear dialog history by ID: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog.clearHistory(dialogId).catch((error) => {}); ``` This request will clear all messages in the dialog for current user, but not for other users. ## Subscribe to dialog [Section titled “Subscribe to dialog”](#subscribe-to-dialog) In order to be able to chat in public dialog, you need to subscribe to it: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog .subscribe(dialogId) .then((dialog) => {}) .catch((error) => {}); ``` It’s also possible to subscribe to group chat dialog. Response example from `ConnectyCube.chat.dialog.subscribe(dialogId)` - [see](/server/chat#response-5) ## Unsubscribe from dialog [Section titled “Unsubscribe from dialog”](#unsubscribe-from-dialog) ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog.unsubscribe(dialogId).catch((error) => {}); ``` ## Retrieve public dialog occupants [Section titled “Retrieve public dialog occupants”](#retrieve-public-dialog-occupants) A public chat dialog can have many occupants. There is a separated API to retrieve a list of public dialog occupants: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; 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 = "photo"; // insert the imageHTML as HTML template } } }; ``` In a case you want remove a shared attachment from server: ```javascript ConnectyCube.storage .delete(file.id) .then(() => {}) .catch((error) => {}); ``` ## Attachments (location) [Section titled “Attachments (location)”](#attachments-location) Sharing location attachments is nothing but sending 2 numbers: **latitude** and **longitude**. These values can be accessed using any JS library available in npm registry. ```javascript const latitude = "64.7964274"; const longitude = "-23.7391878"; const message = { type: dialog.type === 3 ? "chat" : "groupchat", body: "attachment", extension: { save_to_history: 1, dialog_id: dialog._id, attachments: [{ latitude, longitude, type: "place" }], }, }; // send the message message.id = ConnectyCube.chat.send(dialog._id, message); ``` On the receiver’s side the location attachment can be accessed the following way: ```javascript ConnectyCube.chat.onMessageListener = (userId, message) => { if (message.extension.hasOwnProperty("attachments")) { if (message.extension.attachments.length > 0) { const attachment = message.extension.attachments[0]; const latitude = attachment.latitude; const longitude = attachment.longitude; // and now display the map // ... } } }; ``` ## Edit message [Section titled “Edit message”](#edit-message) Use the following code snippet to edit a message (correct message body). Other user(s) will receive the ‘edit’ message info via callback: ```javascript ConnectyCube.chat.editMessage({ to: 123, // either a user id if this is 1-1 chat or a chat dialog id dialogId: "52e6a9c8a18f3a3ea6001f18", body: "corrected message body", originMessageId: "58e6a9c8a1834a3ea6001f15", // origin message id to edit last: false // pass 'true' if edit last (the newest) message in history }) ... ConnectyCube.chat.onMessageUpdateListener = (messageId, isLast, updatedBody, dialogId, userId) => { } ``` Also, you can update a message via HTTP API: ```javascript // const messageIds = ""; // to update all const messageIds = ["55fd42369575c12c2e234c64", "55fd42369575c12c2e234c68"].join(","); // or one - "55fd42369575c12c2e234c64" const params = { read: 1, // mark message as read delivered: 1, // mark message as delivered message: "corrected message body", // update message body chat_dialog_id: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.message .update(messageIds, params) .then(() => {}) .catch((error) => {}); ``` ## Message reactions [Section titled “Message reactions”](#message-reactions) ### Add/Remove reactions [Section titled “Add/Remove reactions”](#addremove-reactions) User can add/remove message reactions and listen message reaction events Add ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' const reaction = '🔥' ConnectyCube.chat.message.addReaction(messageId, reaction) .then(() => {}) .catch(err => {}) ``` Remove ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' const reaction = '👎' ConnectyCube.chat.message.removeReaction(messageId, reaction) .then(() => {}) .catch(err => {}) ``` Add/Remove ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' const reactionToAdd = '👎' const reactionToRemove = '🚀' ConnectyCube.chat.message.updateReaction(messageId, reactionToAdd, reactionToRemove) .then(() => {}) .catch(err => {}) ``` ### Listen reactions [Section titled “Listen reactions”](#listen-reactions) ```javascript ConnectyCube.chat.onMessageReactionsListener = (messageId, userId, dialogId, addReaction, removeReaction) => { } ``` ### List message reactions [Section titled “List message reactions”](#list-message-reactions) User can list message reactions ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' ConnectyCube.chat.message.listReactions(messageId) .then(reactions => reactions) .catch(err => {}) ``` Response example from `ConnectyCube.chat.message.listReactions(messageId)` - [see](/server/chat#response-19) ## Delete messages [Section titled “Delete messages”](#delete-messages) Use the following code snippet to delete a message. Other user(s) will receive the ‘delete’ message info via callback: ```javascript ConnectyCube.chat.deleteMessage({ to: 123, // either a user id if this is 1-1 chat or a chat dialog id dialogId: "52e6a9c8a18f3a3ea6001f18", messageId: "58e6a9c8a1834a3ea6001f15" // message id to delete }) ... ConnectyCube.chat.onMessageDeleteListener = (messageId, dialogId, userId) => { } ``` If you want to delete a message for yourself only, use the following API: ```javascript const messageIds = ["55fd42369575c12c2e234c64", "55fd42369575c12c2e234c68"].join(","); const params = {}; ConnectyCube.chat.message .delete(messageIds, params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.message.delete(messageIds)` - [see](/server/chat#response-14) This request will remove the messages from current user history only, without affecting the history of other users. ## Unread messages count [Section titled “Unread messages count”](#unread-messages-count) You can request total unread messages count and unread count for particular dialog: ```javascript const params = { dialogs_ids: ["5356c64ab35c12bd3b108a41"] }; ConnectyCube.chat.message .unreadCount(params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.message.unreadCount(params)` - [see](/server/chat#response-11) ## Mark as read all chat messages [Section titled “Mark as read all chat messages”](#mark-as-read-all-chat-messages) The following snippet is used to mark all messages as read on a backend for dialog ID: ```javascript const messageIds = ""; // use "" to update all const params = { read: 1, chat_dialog_id: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.message .update("", params) .then(() => {}) .catch((error) => {}); ``` ## Search [Section titled “Search”](#search) The following API is used to search for messages and chat dialogs: ```javascript const params = { /* ... */ }; ConnectyCube.chat .search(params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.search(params)` - [see](/server/chat#response-15) Please refer to [Global search parameters](/server/chat#global-search) for more info on how to form search params. ## Chat alerts [Section titled “Chat alerts”](#chat-alerts) When you send a chat message and the recipient/recipients is offline, then automatic push notification will be fired. In order to receive push notifications you need to subscribe for it. Please refer to [Push Notifications](/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 ![Chat widget: report table in ConnectyCube dashboard](/images/chat_widget/chat-widget-report-table.png) Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```javascript const reportedUserId = 45 const reason = "User is spamming with bad words" await ConnectyCube.data.create('UserReports', { reportedUserId, reason }); ``` ### Report message [Section titled “Report message”](#report-message) For message reporting to work, the same approach to user reporting above could be used. You need to create new table called **MessageReports** with the following fields: * **reportedMessageId** - integer * **reason** - string Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```javascript const reportedMessageId = "58e6a9c8a1834a3ea6001f15" const reason = "The message contains phishing links" await ConnectyCube.data.create('MessageReports', { reportedMessageId, reason }); ``` ### Block user [Section titled “Block user”](#block-user) Block list (aka Privacy list) allows enabling or disabling communication with other users. You can create, modify, or delete privacy lists, define a default list. > The user can have multiple privacy lists, but only one can be active. #### Create privacy list [Section titled “Create privacy list”](#create-privacy-list) A privacy list must have at least one element in order to be created. You can choose a type of blocked logic. There are 2 types: * Block in one way. When you blocked a user, but you can send messages to him. * Block in two ways. When you blocked a user and you also can’t send messages to him. ```javascript const users = [ { user_id: 34, action: "deny" }, { user_id: 48, action: "deny", mutualBlock: true }, // it means you can't write to user { user_id: 18, action: "allow" }, ]; const list = { name: "myList", items: users }; ConnectyCube.chat.privacylist.create(list).catch((error) => {}); ``` > In order to be used the privacy list should be not only set, but also activated(set as default). #### Activate privacy list [Section titled “Activate privacy list”](#activate-privacy-list) In order to activate rules from a privacy list you should set it as default: ```javascript const listName = "myList"; ConnectyCube.chat.privacylist.setAsDefault(listName).catch((error) => {}); ``` #### Update privacy list [Section titled “Update privacy list”](#update-privacy-list) There is a rule you should follow to update a privacy list: * If you want to update or set new privacy list instead of current one, you should decline current default list first. ```javascript const listName = "myList"; const list = { name: listName, items: [{ user_id: 34, action: "allow" }], }; ConnectyCube.chat.privacylist .setAsDefault(null) .then(() => ConnectyCube.chat.privacylist.update(list)) .then(() => ConnectyCube.chat.privacylist.setAsDefault(listName)) .catch((error) => {}); ``` #### Retrieve privacy list names [Section titled “Retrieve privacy list names”](#retrieve-privacy-list-names) To get a list of all your privacy lists’ names use the following request: ```javascript let names; ConnectyCube.chat.privacylist .getNames() .then((response) => (names = response.names)) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.privacylist.getNames()`: ```json { "active": null, "default": null, "names": ["myList", "blockedusers"] } ``` #### Retrieve privacy list with name [Section titled “Retrieve privacy list with name”](#retrieve-privacy-list-with-name) To get the privacy list by name you should use the following method: ```javascript const listName = "myList"; let name, items; ConnectyCube.chat.privacylist.getList(listName).then((response) => { name = response.name; items = response.items; }); ``` Response example from `ConnectyCube.chat.privacylist.getList(listName)`: ```json { "name": "myList", "items": [ { "user_id": 34, "action": "deny" }, { "user_id": 48, "action": "deny", "mutualBlock": true }, { "user_id": 18, "action": "allow" } ] } ``` #### Remove privacy list [Section titled “Remove privacy list”](#remove-privacy-list) To delete a list you can call a method below or you can edit a list and set items to `nil`. ```javascript const listName = "myList"; ConnectyCube.chat.privacylist.delete(listName).catch((error) => {}); ``` #### Blocked user attempts to communicate with user [Section titled “Blocked user attempts to communicate with user”](#blocked-user-attempts-to-communicate-with-user) Blocked users will be receiving an error when trying to chat with a user in a 1-1 chat and will be receiving nothing in a group chat: ```javascript ConnectyCube.chat.onMessageErrorListener = function (messageId, error) {}; ``` ## Ping server [Section titled “Ping server”](#ping-server) Sometimes, it can be cases where TCP connection to Chat server can go down without the application layer knowing about it. To check that chat connection is still alive or to keep it to be alive there is a ping method: ```javascript const PING_TIMEOUT = 3000; // default is 5000 ms ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT) .then(() => { // Chat connection is alive }).catch((error) => { // No connection with chat server // Let's try to re-connect }) ``` ### Pause chat connection when ping fails [Section titled “Pause chat connection when ping fails”](#pause-chat-connection-when-ping-fails) Temporarily stop the chat connection if the server does not respond to the ping request. This allows the client to attempt automatic reconnection as soon as the chat connection becomes available again: ```javascript const PING_TIMEOUT = 1000; try { await ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT) } catch (error) { ConnectyCube.chat.terminate() } ``` ### Handle 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: ![Setup Xcode VOIP capabilities](/_astro/voip_capabilities.DFieoPnY_15jdKd.webp) \*\*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: ![Setup Xcode VOIP capabilities](/_astro/voip_capabilities.DFieoPnY_15jdKd.webp) \*\*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: ![Setup push notification on new contact joined](/_astro/setup_push_notification_on_new_contact_joined.DTG1vj8m_1gVrvB.webp) # 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 = "photo"; ``` ## 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. ![Create class scheme](/_astro/create_scheme.BZpQA4pv_Z1tQkMW.webp ":size=400") 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 ![Create class scheme](/_astro/created_scheme.DuEkinE2_2l5wmH.webp) 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. ![Edit permissions levels](/_astro/edit_class_permissions.DwZG2uer_2451o4.webp ":size=400") ### 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: ![Set permissions for record](/_astro/edit_record_permissions.YjI8FGha_Zb7hOq.webp ":size=400") 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. ![Upload APNS certificate in ConnectyCube dashboard](/_astro/ios-upload-push-certificate._J9x_biU_x01ix.webp) 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. ![Setup Xcode capabilities](/_astro/setup_capabilities.DDONTS8C_1EzgLI.webp) ### 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: ![Find your FCM service account key](/_astro/fcm_account_key_settings.DIN_1KuB_Z1tPxdW.webp) 2. Select and configure **Manage Keys** option: ![Find your FCM server key](/_astro/fcm_account_key_manage.B_QpQr4j_ZIsMmz.webp) 3. Select **ADD KEY**, **Create new key**: ![Find your FCM server key](/_astro/fcm_account_key_manage_create.DzJUbiXU_Z17xFHF.webp) 4. Select **Key type** (json recommended) and create: ![Find your FCM server key](/_astro/fcm_account_key_json.BHnYerBS_1Pcx5.webp) 5. Save it locally: ![Find your FCM server key](/_astro/fcm_account_key_key_saved.XyuT6mGT_ZRfxMx.webp) 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. ![Add your FCM server key to your Dashboard](/_astro/fcm_service_account_key2.Dd7Qkoql_Z25pJUu.webp) 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: ![Find your Sender ID](/_astro/fcm_service_account_key3.CFBXqwTK_EIg8N.webp) 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: ![Find your FCM server key](/_astro/fcm_server_key.BzK_4dQ__ZJghKo.webp) 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. ![Add your FCM server key to your Dashboard](/_astro/fcm_server_key_2.D-cbuUdg_fI4XM.webp) 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: ![Find your Sender ID](/_astro/fcm_service_account_key3.CFBXqwTK_EIg8N.webp) 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. ![Whiteboard demo](/_astro/whiteboard_1024x504.by1QVA4x_1yUk0p.webp) 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.client com.apple.security.device.audio-input com.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. ![Dialog Extensions fields configuration example](/_astro/dialog_custom_params.CrGT0s8Z_1XWSCw.webp) When create a dialog, the `extensions` field object must contain allowed fields only. Others fields will be ignored. The values will be casted to string. ```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 ![Chat widget: report table in ConnectyCube dashboard](/images/chat_widget/chat-widget-report-table.png) Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```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.client com.apple.security.device.audio-input com.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-groups group.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 RTCAppGroupIdentifier group.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 RTCScreenSharingExtension com.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 UIBackgroundModes remote-notification voip audio ``` 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.client com.apple.security.device.audio-input com.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-groups group.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 RTCAppGroupIdentifier group.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 RTCScreenSharingExtension com.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 UIBackgroundModes remote-notification voip audio ``` 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: ![Setup push notification on new contact joined](/_astro/setup_push_notification_on_new_contact_joined.DTG1vj8m_1gVrvB.webp) # 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. ![Create class scheme](/_astro/create_scheme.BZpQA4pv_Z1tQkMW.webp ":size=400") 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 ![Create class scheme](/_astro/created_scheme.DuEkinE2_2l5wmH.webp) 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. ![Edit permissions levels](/_astro/edit_class_permissions.DwZG2uer_2451o4.webp ":size=400") ### 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: ![Set permissions for record](/_astro/edit_record_permissions.YjI8FGha_Zb7hOq.webp ":size=400") 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** ![Adding Project in Firebase](/_astro/firebase_add_project.Dq7GHe8f_maP4o.webp) > **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**. ![Add a project 1 of 3](/_astro/firebase_add_project_2_1.CTNFPmFI_1qpQUj.webp ":size=400%") ![Add a project 2 of 3](/_astro/firebase_add_project_2_2.xX7Ff4sg_Z1D8se6.webp ":size=400%") 4. Configure Google Analytics for your project and click **Create project**. ![Add a project 3 of 3](/_astro/firebase_add_project_2_3._yPP5HiQ_Z1ctbYe.webp ":size=400%") Then click Continue. ![Click Continue](/_astro/firebase_add_project_4.Cus9Uz0-_Z2oLn44.webp) 5. Select platform for which you need Firebase ![Select platform](/_astro/firebase_add_project_platforms.CynsV-4f_Z1OKfii.webp) ## 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. ![User logged in](/_astro/firebase_user_logged_in.DqybzeAt_Z1rKYfj.webp) > 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**. ![User logged in](/_astro/firebase_email_auth_enable.B6gJaRmv_2nYeVY.webp) 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**. ![User logged in](/_astro/firebase_google_auth_enable.CgpcLBH0_Z15Rypx.webp) > 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): ![User logged in](/_astro/firebase_google_oauth_client_id.CXF4WxNY_Z1WDb8P.webp) 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. ![Upload APNS certificate in ConnectyCube dashboard](/_astro/ios-upload-push-certificate._J9x_biU_x01ix.webp) 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. ![Setup Xcode capabilities](/_astro/setup_capabilities.DDONTS8C_1EzgLI.webp) ### 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: ![Find your FCM service account key](/_astro/fcm_account_key_settings.DIN_1KuB_Z1tPxdW.webp) 2. Select and configure **Manage Keys** option: ![Find your FCM server key](/_astro/fcm_account_key_manage.B_QpQr4j_ZIsMmz.webp) 3. Select **ADD KEY**, **Create new key**: ![Find your FCM server key](/_astro/fcm_account_key_manage_create.DzJUbiXU_Z17xFHF.webp) 4. Select **Key type** (json recommended) and create: ![Find your FCM server key](/_astro/fcm_account_key_json.BHnYerBS_1Pcx5.webp) 5. Save it locally: ![Find your FCM server key](/_astro/fcm_account_key_key_saved.XyuT6mGT_ZRfxMx.webp) 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. ![Add your FCM server key to your Dashboard](/_astro/fcm_service_account_key2.Dd7Qkoql_Z25pJUu.webp) 7. Copy **Sender ID** value from your Firebase console **Cloud Messaging** section. You may require it later. ![Find your Sender ID](/_astro/fcm_service_account_key3.CFBXqwTK_EIg8N.webp) 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: ![Find your FCM server key](/_astro/fcm_server_key.BzK_4dQ__ZJghKo.webp) 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. ![Add your FCM server key to your Dashboard](/_astro/fcm_server_key_2.D-cbuUdg_fI4XM.webp) 3. Copy **Sender ID** value from your Firebase console **Cloud Messaging** section. You may require it later. ![Find your Sender ID](/_astro/fcm_service_account_key3.CFBXqwTK_EIg8N.webp) 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. ![Whiteboard demo](/_astro/whiteboard_1024x504.by1QVA4x_1yUk0p.webp) 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. ![Dialog Extensions fields configuration example](/_astro/dialog_custom_params.CrGT0s8Z_1XWSCw.webp) When create a dialog, the `extensions` field object must contain allowed fields only. Others fields will be ignored. The values will be casted to string. ```javascript const params = { type: 2, name: "Friday party", occupants_ids: [29085, 29086, 29087], description: "lets dance the night away", extensions: {location: "Sun bar"}, }; ConnectyCube.chat.dialog .create(params) .then((dialog) => {}) .catch((error) => {}); ``` When remove custom field in Admin panel, this field will be removed in all dialogs respectively. These parameters also can be used as a filter for retrieving dialogs. ### Chat permissions [Section titled “Chat permissions”](#chat-permissions) Chat could have different permissions to managa data access. This is managed via `permissions` field. At the moment, only one permission available - `allow_preview` - which allows to retrieve dialog’s messages for user who is not a member of dialog. This is useful when implement feature like Channels where a user can open chat and preview messages w/o joining it. ## List dialogs [Section titled “List dialogs”](#list-dialogs) It’s common to request all your dialogs on every app login: ```javascript const filters = {}; ConnectyCube.chat.dialog .list(filters) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.list(filters)` - [see](/server/chat#response) More filters available [here](/server/chat#retrieve-chat-dialogs) If you want to retrieve only dialogs updated after some specific date time, you can use `updated_at[gt]` filter. This is useful if you cache dialogs somehow and do not want to obtain the whole list of your dialogs on every app start. ## Update dialog [Section titled “Update dialog”](#update-dialog) User can update group chat name, photo or add/remove occupants: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const toUpdateParams = { name: "Crossfit2" }; ConnectyCube.chat.dialog .update(dialogId, toUpdateParams) .then((dialog) => {}) .catch((error) => {}); ``` ## Add/Remove occupants [Section titled “Add/Remove occupants”](#addremove-occupants) To add more occupants use `push_all` operator. To remove yourself from the dialog use `pull_all` operator: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const toUpdateParams = { push_all: { occupants_ids: [97, 789] } }; ConnectyCube.chat.dialog .update(dialogId, toUpdateParams) .then((dialog) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.update(dialogId, toUpdateParams)` - [see](/server/chat#response-2) > **Note** > > Only group chat owner can remove other users from group chat. ## Remove dialog [Section titled “Remove dialog”](#remove-dialog) The following snippet is used to delete a dialog: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; // const dialogIds = ['5356c64ab35c12bd3b108a41', ..., '5356c64ab35c12bd3b108a84'] ConnectyCube.chat.dialog.delete(dialogId).catch((error) => {}); ``` This request will remove this dialog for current user, but other users still will be able to chat there. Only group chat owner can remove the group dialog for all users. You can also delete multiple dialogs in a single request. ## 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 = "photo"; // insert the imageHTML as HTML template } } }; ``` In a case you want remove a shared attachment from server: ```javascript ConnectyCube.storage .delete(file.id) .then(() => {}) .catch((error) => {}); ``` ## Attachments (location) [Section titled “Attachments (location)”](#attachments-location) Sharing location attachments is nothing but sending 2 numbers: **latitude** and **longitude**. These values can be accessed using any JS library available in npm registry. ```javascript const latitude = "64.7964274"; const longitude = "-23.7391878"; const message = { type: dialog.type === 3 ? "chat" : "groupchat", body: "attachment", extension: { save_to_history: 1, dialog_id: dialog._id, attachments: [{ latitude, longitude, type: "place" }], }, }; // send the message message.id = ConnectyCube.chat.send(dialog._id, message); ``` On the receiver’s side the location attachment can be accessed the following way: ```javascript ConnectyCube.chat.onMessageListener = (userId, message) => { if (message.extension.hasOwnProperty("attachments")) { if (message.extension.attachments.length > 0) { const attachment = message.extension.attachments[0]; const latitude = attachment.latitude; const longitude = attachment.longitude; // and now display the map // ... } } }; ``` ## Edit message [Section titled “Edit message”](#edit-message) Use the following code snippet to edit a message (correct message body). Other user(s) will receive the ‘edit’ message info via callback: ```javascript ConnectyCube.chat.editMessage({ to: 123, // either a user id if this is 1-1 chat or a chat dialog id dialogId: "52e6a9c8a18f3a3ea6001f18", body: "corrected message body", originMessageId: "58e6a9c8a1834a3ea6001f15", // origin message id to edit last: false // pass 'true' if edit last (the newest) message in history }) ... ConnectyCube.chat.onMessageUpdateListener = (messageId, isLast, updatedBody, dialogId, userId) => { } ``` Also, you can update a message via HTTP API: ```javascript // const messageIds = ""; // to update all const messageIds = ["55fd42369575c12c2e234c64", "55fd42369575c12c2e234c68"].join(","); // or one - "55fd42369575c12c2e234c64" const params = { read: 1, // mark message as read delivered: 1, // mark message as delivered message: "corrected message body", // update message body chat_dialog_id: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.message .update(messageIds, params) .then(() => {}) .catch((error) => {}); ``` ## Message reactions [Section titled “Message reactions”](#message-reactions) ### Add/Remove reactions [Section titled “Add/Remove reactions”](#addremove-reactions) User can add/remove message reactions and listen message reaction events Add ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' const reaction = '🔥' ConnectyCube.chat.message.addReaction(messageId, reaction) .then(() => {}) .catch(err => {}) ``` Remove ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' const reaction = '👎' ConnectyCube.chat.message.removeReaction(messageId, reaction) .then(() => {}) .catch(err => {}) ``` Add/Remove ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' const reactionToAdd = '👎' const reactionToRemove = '🚀' ConnectyCube.chat.message.updateReaction(messageId, reactionToAdd, reactionToRemove) .then(() => {}) .catch(err => {}) ``` ### Listen reactions [Section titled “Listen reactions”](#listen-reactions) ```javascript ConnectyCube.chat.onMessageReactionsListener = (messageId, userId, dialogId, addReaction, removeReaction) => { } ``` ### List message reactions [Section titled “List message reactions”](#list-message-reactions) User can list message reactions ```javascript const messageId = '58e6a9c8a1834a3ea6001f15' ConnectyCube.chat.message.listReactions(messageId) .then(reactions => reactions) .catch(err => {}) ``` Response example from `ConnectyCube.chat.message.listReactions(messageId)` - [see](/server/chat#response-19) ## Delete messages [Section titled “Delete messages”](#delete-messages) Use the following code snippet to delete a message. Other user(s) will receive the ‘delete’ message info via callback: ```javascript ConnectyCube.chat.deleteMessage({ to: 123, // either a user id if this is 1-1 chat or a chat dialog id dialogId: "52e6a9c8a18f3a3ea6001f18", messageId: "58e6a9c8a1834a3ea6001f15" // message id to delete }) ... ConnectyCube.chat.onMessageDeleteListener = (messageId, dialogId, userId) => { } ``` If you want to delete a message for yourself only, use the following API: ```javascript const messageIds = ["55fd42369575c12c2e234c64", "55fd42369575c12c2e234c68"].join(","); const params = {}; ConnectyCube.chat.message .delete(messageIds, params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.message.delete(messageIds)` - [see](/server/chat#response-14) This request will remove the messages from current user history only, without affecting the history of other users. ## Unread messages count [Section titled “Unread messages count”](#unread-messages-count) You can request total unread messages count and unread count for particular dialog: ```javascript const params = { dialogs_ids: ["5356c64ab35c12bd3b108a41"] }; ConnectyCube.chat.message .unreadCount(params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.message.unreadCount(params)` - [see](/server/chat#response-11) ## Mark as read all chat messages [Section titled “Mark as read all chat messages”](#mark-as-read-all-chat-messages) The following snippet is used to mark all messages as read on a backend for dialog ID: ```javascript const messageIds = ""; // use "" to update all const params = { read: 1, chat_dialog_id: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.message .update("", params) .then(() => {}) .catch((error) => {}); ``` ## Search [Section titled “Search”](#search) The following API is used to search for messages and chat dialogs: ```javascript const params = { /* ... */ }; ConnectyCube.chat .search(params) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.search(params)` - [see](/server/chat#response-15) Please refer to [Global search parameters](/server/chat#global-search) for more info on how to form search params. ## Chat alerts [Section titled “Chat alerts”](#chat-alerts) When you send a chat message and the recipient/recipients is offline, then automatic push notification will be fired. In order to receive push notifications you need to subscribe for it. Please refer to [Push Notifications](/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 ![Chat widget: report table in ConnectyCube dashboard](/images/chat_widget/chat-widget-report-table.png) Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```javascript const reportedUserId = 45 const reason = "User is spamming with bad words" await ConnectyCube.data.create('UserReports', { reportedUserId, reason }); ``` ### Report message [Section titled “Report message”](#report-message) For message reporting to work, the same approach to user reporting above could be used. You need to create new table called **MessageReports** with the following fields: * **reportedMessageId** - integer * **reason** - string Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```javascript const reportedMessageId = "58e6a9c8a1834a3ea6001f15" const reason = "The message contains phishing links" await ConnectyCube.data.create('MessageReports', { reportedMessageId, reason }); ``` ### Block user [Section titled “Block user”](#block-user) Block list (aka Privacy list) allows enabling or disabling communication with other users. You can create, modify, or delete privacy lists, define a default list. > The user can have multiple privacy lists, but only one can be active. #### Create privacy list [Section titled “Create privacy list”](#create-privacy-list) A privacy list must have at least one element in order to be created. You can choose a type of blocked logic. There are 2 types: * Block in one way. When you blocked a user, but you can send messages to him. * Block in two ways. When you blocked a user and you also can’t send messages to him. ```javascript const users = [ { user_id: 34, action: "deny" }, { user_id: 48, action: "deny", mutualBlock: true }, // it means you can't write to user { user_id: 18, action: "allow" }, ]; const list = { name: "myList", items: users }; ConnectyCube.chat.privacylist.create(list).catch((error) => {}); ``` > In order to be used the privacy list should be not only set, but also activated(set as default). #### Activate privacy list [Section titled “Activate privacy list”](#activate-privacy-list) In order to activate rules from a privacy list you should set it as default: ```javascript const listName = "myList"; ConnectyCube.chat.privacylist.setAsDefault(listName).catch((error) => {}); ``` #### Update privacy list [Section titled “Update privacy list”](#update-privacy-list) There is a rule you should follow to update a privacy list: * If you want to update or set new privacy list instead of current one, you should decline current default list first. ```javascript const listName = "myList"; const list = { name: listName, items: [{ user_id: 34, action: "allow" }], }; ConnectyCube.chat.privacylist .setAsDefault(null) .then(() => ConnectyCube.chat.privacylist.update(list)) .then(() => ConnectyCube.chat.privacylist.setAsDefault(listName)) .catch((error) => {}); ``` #### Retrieve privacy list names [Section titled “Retrieve privacy list names”](#retrieve-privacy-list-names) To get a list of all your privacy lists’ names use the following request: ```javascript let names; ConnectyCube.chat.privacylist .getNames() .then((response) => (names = response.names)) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.privacylist.getNames()`: ```json { "active": null, "default": null, "names": ["myList", "blockedusers"] } ``` #### Retrieve privacy list with name [Section titled “Retrieve privacy list with name”](#retrieve-privacy-list-with-name) To get the privacy list by name you should use the following method: ```javascript const listName = "myList"; let name, items; ConnectyCube.chat.privacylist.getList(listName).then((response) => { name = response.name; items = response.items; }); ``` Response example from `ConnectyCube.chat.privacylist.getList(listName)`: ```json { "name": "myList", "items": [ { "user_id": 34, "action": "deny" }, { "user_id": 48, "action": "deny", "mutualBlock": true }, { "user_id": 18, "action": "allow" } ] } ``` #### Remove privacy list [Section titled “Remove privacy list”](#remove-privacy-list) To delete a list you can call a method below or you can edit a list and set items to `nil`. ```javascript const listName = "myList"; ConnectyCube.chat.privacylist.delete(listName).catch((error) => {}); ``` #### Blocked user attempts to communicate with user [Section titled “Blocked user attempts to communicate with user”](#blocked-user-attempts-to-communicate-with-user) Blocked users will be receiving an error when trying to chat with a user in a 1-1 chat and will be receiving nothing in a group chat: ```javascript ConnectyCube.chat.onMessageErrorListener = function (messageId, error) {}; ``` ## Ping server [Section titled “Ping server”](#ping-server) Sometimes, it can be cases where TCP connection to Chat server can go down without the application layer knowing about it. To check that chat connection is still alive or to keep it to be alive there is a ping method: ```javascript const PING_TIMEOUT = 3000; // default is 5000 ms ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT) .then(() => { // Chat connection is alive }).catch((error) => { // No connection with chat server // Let's try to re-connect }) ``` ### Pause chat connection when ping fails [Section titled “Pause chat connection when ping fails”](#pause-chat-connection-when-ping-fails) Temporarily stop the chat connection if the server does not respond to the ping request. This allows the client to attempt automatic reconnection as soon as the chat connection becomes available again: ```javascript const PING_TIMEOUT = 1000; try { await ConnectyCube.chat.pingWithTimeout(PING_TIMEOUT) } catch (error) { ConnectyCube.chat.terminate() } ``` ### Handle 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: ![Setup Xcode VOIP capabilities](/_astro/voip_capabilities.DFieoPnY_15jdKd.webp) For Android, we also recommend to implement the same camera switch flow when go to background and then return to foreground. # Video Conferencing > Discover the simplicity of integrating conference video calling into your 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: ![Setup Xcode VOIP capabilities](/_astro/voip_capabilities.DFieoPnY_15jdKd.webp) For Android, we also recommend to implement the same camera switch flow when go to background and then return to foreground. # Address Book > Effortlessly upload, sync, and access ConnectyCube users from your phone contacts in your 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: ![Setup push notification on new contact joined](/_astro/setup_push_notification_on_new_contact_joined.DTG1vj8m_1gVrvB.webp) # 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 = "photo"; ``` ## 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. ![Create class scheme](/_astro/create_scheme.BZpQA4pv_Z1tQkMW.webp ":size=400") 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 ![Create class scheme](/_astro/created_scheme.DuEkinE2_2l5wmH.webp) 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. ![Edit permissions levels](/_astro/edit_class_permissions.DwZG2uer_2451o4.webp ":size=400") ### 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: ![Set permissions for record](/_astro/edit_record_permissions.YjI8FGha_Zb7hOq.webp ":size=400") 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. ![Upload APNS certificate in ConnectyCube dashboard](/_astro/ios-upload-push-certificate._J9x_biU_x01ix.webp) 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. ![Setup Xcode capabilities](/_astro/setup_capabilities.DDONTS8C_1EzgLI.webp) ### 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: ![Find your FCM service account key](/_astro/fcm_account_key_settings.DIN_1KuB_Z1tPxdW.webp) 2. Select and configure **Manage Keys** option: ![Find your FCM server key](/_astro/fcm_account_key_manage.B_QpQr4j_ZIsMmz.webp) 3. Select **ADD KEY**, **Create new key**: ![Find your FCM server key](/_astro/fcm_account_key_manage_create.DzJUbiXU_Z17xFHF.webp) 4. Select **Key type** (json recommended) and create: ![Find your FCM server key](/_astro/fcm_account_key_json.BHnYerBS_1Pcx5.webp) 5. Save it locally: ![Find your FCM server key](/_astro/fcm_account_key_key_saved.XyuT6mGT_ZRfxMx.webp) 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. ![Add your FCM server key to your Dashboard](/_astro/fcm_service_account_key2.Dd7Qkoql_Z25pJUu.webp) 7. Copy **Sender ID** value from your Firebase console **Cloud Messaging** section. You may require it later. ![Find your Sender ID](/_astro/fcm_service_account_key3.CFBXqwTK_EIg8N.webp) 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: ![Find your FCM server key](/_astro/fcm_server_key.BzK_4dQ__ZJghKo.webp) 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. ![Add your FCM server key to your Dashboard](/_astro/fcm_server_key_2.D-cbuUdg_fI4XM.webp) 3. Copy **Sender ID** value from your Firebase console **Cloud Messaging** section. You may require it later. ![Find your Sender ID](/_astro/fcm_service_account_key3.CFBXqwTK_EIg8N.webp) 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. ![Whiteboard demo](/_astro/whiteboard_1024x504.by1QVA4x_1yUk0p.webp) 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 ![ConnectyCube dashboard: apps permissions page](/images/chat_widget/apps-permissions.png) ### 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: ![Chat widget: report user button](/images/chat_widget/chat-widget-report-user-button.png) 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 ![Chat widget: report table in ConnectyCube dashboard](/images/chat_widget/chat-widget-report-table.png) 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', }, }; } ``` ![AI - change message tone (choose)](/images/chat_widget/ai-message-tone-choose.png)\ ![AI - change message tone (result)](/images/chat_widget/ai-message-tone-result.png) #### 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', }, }; } ``` ![AI - get text summary](/images/chat_widget/ai-text-summary.png) ## 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 (
); } ``` * Vanilla JS ```js const openChatBtn = document.getElementById('openChatButton'); openChatBtn.addEventListener('click', () => { window.ConnectyCubeChatWidget.toggle() }); ``` * Angular ```ts @Component({ ... }) export class MyComponent { props = { ..., open: false, }; toggleVisible() { this.props = { ...this.props, open: !this.props.open }; } } ``` in template: ```html
``` ## 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. ![Dialog Extensions fields configuration example](/_astro/dialog_custom_params.CrGT0s8Z_1XWSCw.webp) When create a dialog, the `extensions` field object must contain allowed fields only. Others fields will be ignored. The values will be casted to string. * 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 ![Chat widget: report table in ConnectyCube dashboard](/images/chat_widget/chat-widget-report-table.png) Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```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>(){ override fun onSuccess(result: Map) { // the result contains the map where key is the userId and value is true if this user is publisher and false if listener } override fun onError(ex: WsException) {} }) ``` ## Subscribe/unsubscribe [Section titled “Subscribe/unsubscribe”](#subscribeunsubscribe) For subscribing to the active publisher: * SDK v2 kotlin ```kotlin currentSession.subscribeToPublisher(publisherUserId) ``` * SDK v1 kotlin (deprecated) ```kotlin currentSession.subscribeToPublisher(publisherUserId) ``` * SDK v1 java (deprecated) ```java currentSession.subscribeToPublisher(publisherUserId); ``` > **Note**: You should subscribe to publishers only when session state becomes **connected**. Use `onStateChanged` callback method to track session states. If you are a listener, then you can subscribe to publishers right after successful `joinDialog`. For unsubscribing from publisher: * SDK v2 kotlin ```kotlin currentSession.unsubscribeFromPublisher(publisherUserId) ``` * SDK v1 kotlin (deprecated) ```kotlin currentSession.unsubscribeFromPublisher(publisherUserId) ``` * SDK v1 java (deprecated) ```java currentSession.unsubscribeFromPublisher(publisherUserId); ``` ## Leave [Section titled “Leave”](#leave) To leave current room session: * SDK v2 kotlin ```kotlin currentSession.leave() ``` * SDK v1 kotlin (deprecated) ```kotlin currentSession.leave() ``` * SDK v1 java (deprecated) ```java currentSession.leave(); ``` ## Mute audio [Section titled “Mute audio”](#mute-audio) ```kotlin val localAudioTrack: ConnectycubeAudioTrack? = currentSession.mediaStreamManager.localAudioTrack // mute localAudioTrack.enabled = false // unmute localAudioTrack.enabled = true // is muted? val isEnabled = localAudioTrack.enabled ``` ## Mute video [Section titled “Mute video”](#mute-video) ```kotlin val localVideoTrack: ConnectycubeVideoTrack? = currentSession.mediaStreamManager.localVideoTrack // mute localVideoTrack.enabled = false // unmute localVideoTrack.enabled = true // is muted? val isEnabled = localVideoTrack.enabled ``` ## Switch video cameras [Section titled “Switch video cameras”](#switch-video-cameras) You can switch the video camera during a call (default is front camera): ```kotlin val cameraSwitchHandler: CameraVideoCapturer.CameraSwitchHandler = object: CameraVideoCapturer.CameraSwitchHandler { override fun onCameraSwitchDone(isFrontCamera: Boolean) { } override fun onCameraSwitchError(e: String?) { } } val videoCapturer = currentSession.mediaStreamManager.videoCapturer videoCapturer.switchCamera(cameraSwitchHandler) ``` ## 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 `WebRTCConfig` method below: ```kotlin //Coming soon //ConnectycubeStatsReport ``` ### 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 `ConnectycubeStatsReport` 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 `WebRTCConfig` as described above. Then create the instance of `ConnectycubeStatsReportsManager` and initialize it with the call session. ```kotlin //Coming soon //ConnectycubeStatsReportsManager ``` ## Recording [Section titled “Recording”](#recording) Server-side recording is available. Read more about Recording feature # Address Book > Effortlessly upload, sync, and access ConnectyCube users from your phone contacts in your Android 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](/android/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. * SDK v2 kotlin ```kotlin //val UDID = UUID.randomUUID().toString() val UDID: String? = null val force = true val contact = ConnectycubeContact(phone = "13656516112", name = "Bob Bobson") val contactsGlobal: ArrayList = arrayListOf(contact) ConnectyCube.uploadAddressBook(contactsGlobal, force, UDID, successCallback = {result -> }, errorCallback = { error ->}) ``` * SDK v1 kotlin (deprecated) ```kotlin //val UDID = Utils.generateDeviceId(context) val UDID: String? = null val force = true val contact = ConnectycubeAddressBookContact().apply { phone = "13656516112" name = "Bob Bobson" } val contactsGlobal: ArrayList = ArrayList() contactsGlobal.add(contact) ConnectycubeUsers.uploadAddressBook(contactsGlobal, UDID, force) .performAsync(object : EntityCallback { override fun onSuccess(result: ConnectycubeAddressBookResponse, params: Bundle? ) { } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java //String UDID = Utils.generateDeviceId(context); String UDID = null; boolean force = true; ArrayList contactsGlobal = new ArrayList<>(); ConnectycubeAddressBookContact contact = new ConnectycubeAddressBookContact(); contact.setPhone("13656516112"); contact.setName("Bob Bobson"); contactsGlobal.add(contact); ConnectycubeUsers.uploadAddressBook(contactsGlobal, UDID, force).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeAddressBookResponse result, Bundle params) { } @Override public void onError(ResponseException responseException) { } }); ``` - 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 `boolean force = true` - You also can remove a contact by setting `contact.setIsDestroy(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: * SDK v2 kotlin ```kotlin val UDID: String? = null ConnectyCube.getAddressBook(UDID, successCallback = {result -> }, errorCallback = { error ->}) ``` * SDK v1 kotlin (deprecated) ```kotlin val UDID: String? = null ConnectycubeUsers.getAddressBook(UDID) .performAsync(object : EntityCallback> { override fun onSuccess(uploadedContacts: ArrayList, params: Bundle ) { } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java String UDID = null; ConnectycubeUsers.getAddressBook(UDID).performAsync(new EntityCallback>() { @Override public void onSuccess(ArrayList uploadedContacts, Bundle params) { } @Override public void onError(ResponseException responseException) { } }); ``` ## 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: * SDK v2 kotlin ```kotlin val UDID: String? = null val isCompact = true ConnectyCube.getRegisteredUsersFromAddressBook(isCompact, UDID, successCallback = {result -> }, errorCallback = { error ->}) ``` * SDK v1 kotlin (deprecated) ```kotlin val UDID: String? = null val isCompact = true ConnectycubeUsers.getRegisteredUsersFromAddressBook(UDID, isCompact) .performAsync(object : EntityCallback> { override fun onSuccess(users: ArrayList, params: Bundle ) { } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java String UDID = null; boolean isCompact = true; ConnectycubeUsers.getRegisteredUsersFromAddressBook(UDID, isCompact).performAsync(new EntityCallback>() { @Override public void onSuccess(ArrayList users, Bundle params) { } @Override public void onError(ResponseException responseException) { } }); ``` If `isCompact = true` - server will return only id and phone fields of User. Otherwise - all User’s fields will be returned. ## Push notification on new contact joined [Section titled “Push notification on new contact joined”](#push-notification-on-new-contact-joined) There is a way to get a push notification when some contact from your Address Book registered in an app. You can enable this feature at [ConnectyCube Dashboard](https://admin.connectycube.com), Users module, Settings tab: ![Setup push notification on new contact joined](/_astro/setup_push_notification_on_new_contact_joined.DTG1vj8m_1gVrvB.webp) > **Note** > > this works only with *PRODUCTION* environment push subscription. ```plaintext ``` # Authentication and Users > Simplify user authentication in your Android app with our definitive API guide. Fortify your app's defenses and protect user data effectively. Every user needs 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: * SDK v2 kotlin ```kotlin val userToLogin = ConnectycubeUser(login = "marvin18", password = "supersecurepwd") ConnectyCube.signIn(userToLogin, { user -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val user = ConnectycubeUser().apply { login = "marvin18" password = "supersecurepwd" } ConnectycubeUsers.signIn(user).performAsync(object : EntityCallback { override fun onSuccess(user: ConnectycubeUser, args: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java final ConnectycubeUser user = new ConnectycubeUser(); user.setLogin("marvin18"); user.setPassword("supersecurepwd"); ConnectycubeUsers.signIn(user).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeUser user, Bundle args) { } @Override public void onError(ResponseException 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**. The whole guide on how to create Firebase project, connect Firebase SDK and implement authentication via phone number is available at our [Firebase Setup Guide](/android/firebase-setup-guide). Please follow it. ## 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](/android/authentication-and-users/#upgrade-session-token-user-login) is used. You just use existing login request params to pass your external user token: ```kotlin val userToLogin = ConnectycubeUser(login = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzNDU2Nzg5LCJuYW1lIjoiSm9zZXBoIn0.OpOSSw7e485LOP5PrzScxHb7SR6sAOMRckfFwi4rp7o") ConnectyCube.signIn(userToLogin, { user -> }, { 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](/android/messaging/#connect-to-chat-using-custom-authentication-providers). ## Session expiration [Section titled “Session expiration”](#session-expiration) Expiration time for session token is 2 hours after the last request to API is made. But you do not need to worry about it - with the automatic session token management it will be renewed automatically with next request to API. ## Destroy session token [Section titled “Destroy session token”](#destroy-session-token) To destroy a session use the following code: * SDK v2 kotlin ```kotlin ConnectyCube.destroySession({ }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin ConnectycubeAuth.deleteSession().performAsync(object : EntityCallback { override fun onSuccess(result: Void?, params: Bundle) { } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeAuth.deleteSession().performAsync(new EntityCallback() { @Override public void onSuccess(Void result, Bundle params) { } @Override public void onError(ResponseException responseException) { } }); ``` ## User signup [Section titled “User signup”](#user-signup) * SDK v2 kotlin ```kotlin val userTags = listOf("iphone", "apple") val userToSignUp = ConnectycubeUser(login = "marvin18", password = "supersecurepwd").apply { email = "awesomeman@gmail.com" fullName = "Marvin Simon" phone = "47802323143" website = "https://dozensofdreams.com" tags = userTags.joinToString(",") } ConnectyCube.signUp(userToSignUp, { user -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val userTags = StringifyArrayList().apply { add("iphone") add("apple") } val user = ConnectycubeUser("marvin18", "supersecurepwd").apply { login = "marvin18" password = "supersecurepwd" email = "awesomeman@gmail.com" fullName = "Marvin Simon" phone = "47802323143" website = "https://dozensofdreams.com" tags = userTags } ConnectycubeUsers.signUp(user).performAsync(object : EntityCallback { override fun onSuccess(user: ConnectycubeUser, args: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java final ConnectycubeUser user = new ConnectycubeUser("marvin18", "supersecurepwd"); user.setLogin("marvin18"); user.setPassword("supersecurepwd"); user.setEmail("awesomeman@gmail.com"); user.setFullName("Marvin Simon"); user.setPhone("47802323143"); user.setWebsite("https://dozensofdreams.com"); StringifyArrayList tags = new StringifyArrayList(); tags.add("iphone"); tags.add("apple"); user.setTags(tags); ConnectycubeUsers.signUp(user).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeUser user, Bundle args) { } @Override public void onError(ResponseException error) { } }); ``` Only login (or email) + password are required. Other fields are optional. ## User profile update [Section titled “User profile update”](#user-profile-update) * SDK v2 kotlin ```kotlin val userToUpdate = ConnectycubeUser(login = "marvin18", password = "supersecurepwd") ConnectyCube.updateUser(userToUpdate, { user -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val user = ConnectycubeUser().apply { login = "marvin18" fullName = "Marvin Simon" } ConnectycubeUsers.updateUser(user).performAsync(object : EntityCallback { override fun onSuccess(user: ConnectycubeUser, args: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeUser user = new ConnectycubeUser(); user.setLogin("marvin18"); user.setFullName("Marvin Simon"); ConnectycubeUsers.updateUser(user).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeUser user, Bundle args) { } @Override public void onError(ResponseException error) { } }); ``` If you want to change your password, you need to provide 2 parameters: `password` and `oldPassword`. An 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. * SDK v2 kotlin ```kotlin val imageFile: File = ... ConnectyCube.uploadFile(imageFile.path, successCallback = { cubeFile -> val jo = JsonObject() jo.add("avatar_uid", JsonPrimitive(cubeFile.uid)) user.customData = jo.toString() ConnectyCube.updateUser(user, { user -> }, { error -> }) }, errorCallback = { ex -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val imageFile: File = ... ConnectycubeStorage.uploadFileTask(imageFile, false, null).performAsync(object: EntityCallback { override fun onSuccess(connectycubeFile: ConnectycubeFile, params: Bundle) { val jo = JsonObject() jo.add("avatar_uid", JsonPrimitive(connectycubeFile.uid)) user.customData = jo.toString() ConnectycubeUsers.updateUser(user).performAsync(...) } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java File imageFile = ... ConnectycubeStorage.uploadFileTask(imageFile, false, null).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeFile connectycubeFile, Bundle params) { JsonObject jo = new JsonObject(); jo.add("avatar_uid", new JsonPrimitive(connectycubeFile.getUid())); user.setCustomData(jo.toString()); ConnectycubeUsers.updateUser(user).performAsync(...); } @Override public void onError(ResponseException responseException) { } }); ``` Now, other users can get you avatar: * SDK v2 kotlin ```kotlin val obj = JsonParser().parse(user.customData).asJsonObject val fileUID = obj.getAsJsonPrimitive("avatar_uid").asString val avatarUrl = getPrivateUrlForUID(fileUID) ``` * SDK v1 kotlin (deprecated) ```kotlin val obj = JsonParser().parse(user.customData).asJsonObject val fileUID = obj.getAsJsonPrimitive("avatar_uid").asString val avatarUrl = ConnectycubeFile.getPrivateUrlForUID(fileUID) ``` * SDK v1 java (deprecated) ```java JsonObject obj = new JsonParser().parse(user.getCustomData()).getAsJsonObject(); String fileUID = obj.getAsJsonPrimitive("avatar_uid").getAsString(); String avatarUrl = ConnectycubeFile.getPrivateUrlForUID(fileUID); ``` ## Password reset [Section titled “Password reset”](#password-reset) It’s possible to reset a password via email: * SDK v2 kotlin ```kotlin ConnectyCube.resetPassword("awesomeman@gmail.com", {}, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin ConnectycubeUsers.resetPassword("awesomeman@gmail.com").performAsync(object : EntityCallback { override fun onSuccess(result: Void?, params: Bundle) { } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeUsers.resetPassword("awesomeman@gmail.com").performAsync(new EntityCallback() { @Override public void onSuccess(Void result, Bundle params) { } @Override public void onError(ResponseException responseException) { } }); ``` ## Retrieve users [Section titled “Retrieve users”](#retrieve-users) ### Retrieve users by ID [Section titled “Retrieve users by ID”](#retrieve-users-by-id) * SDK v2 kotlin ```kotlin val usersIds = setOf(22, 23) ConnectyCube.getUsersByIds(usersIds, {usersResult -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val pagedRequestBuilder = PagedRequestBuilder().apply { page = 1 perPage = 50 } val usersIds: MutableList = ArrayList().apply { add(22) add(23) } val params = Bundle() ConnectycubeUsers.getUsersByIDs(usersIds, pagedRequestBuilder, params).performAsync(object : EntityCallback> { override fun onSuccess(users: ArrayList, args: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java PagedRequestBuilder pagedRequestBuilder = new PagedRequestBuilder(); pagedRequestBuilder.setPage(1); pagedRequestBuilder.setPerPage(50); List usersIds = new ArrayList<>(); usersIds.add(22); usersIds.add(23); Bundle params = new Bundle(); ConnectycubeUsers.getUsersByIDs(usersIds, pagedRequestBuilder, params).performAsync(new EntityCallback>() { @Override public void onSuccess(ArrayList users, Bundle args) { } @Override public void onError(ResponseException error) { } }); ``` ### Retrieve user by login [Section titled “Retrieve user by login”](#retrieve-user-by-login) * SDK v2 kotlin ```kotlin ConnectyCube.getUserByLogin("amigo", {user -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin ConnectycubeUsers.getUserByLogin("amigo").performAsync(object : EntityCallback { override fun onSuccess(user: ConnectycubeUser, args: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeUsers.getUserByLogin("amigo").performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeUser user, Bundle args) { } @Override public void onError(ResponseException error) { } }); ``` ### Retrieve user by email [Section titled “Retrieve user by email”](#retrieve-user-by-email) * SDK v2 kotlin ```kotlin ConnectyCube.getUserByEmail("amigo@gmail.com", {user -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin ConnectycubeUsers.getUserByEmail("amigo@gmail.com").performAsync(object : EntityCallback { override fun onSuccess(user: ConnectycubeUser, args: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeUsers.getUserByEmail("amigo@gmail.com").performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeUser user, Bundle args) { } @Override public void onError(ResponseException error) { } }); ``` ### Retrieve users by full name [Section titled “Retrieve users by full name”](#retrieve-users-by-full-name) * SDK v2 kotlin ```kotlin ConnectyCube.getUsersByFullName("Marvin Samuel", {usersResult -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val pagedRequestBuilder = PagedRequestBuilder().apply { page = 1 perPage = 50 } val params = Bundle() ConnectycubeUsers.getUsersByFullName("Marvin Samuel", pagedRequestBuilder, params) .performAsync(object : EntityCallback> { override fun onSuccess(users: ArrayList, args: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java PagedRequestBuilder pagedRequestBuilder = new PagedRequestBuilder(); pagedRequestBuilder.setPage(1); pagedRequestBuilder.setPerPage(50); Bundle params = new Bundle(); ConnectycubeUsers.getUsersByFullName("Marvin Samuel", pagedRequestBuilder, params).performAsync(new EntityCallback>() { @Override public void onSuccess(ArrayList users, Bundle args) { } @Override public void onError(ResponseException error) { } }); ``` ### Retrieve user by phone number [Section titled “Retrieve user by phone number”](#retrieve-user-by-phone-number) * SDK v2 kotlin ```kotlin ConnectyCube.getUserByPhoneNumber("+4427123314", {user -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val pagedRequestBuilder = PagedRequestBuilder().apply { page = 1 perPage = 50 } val usersPhones: ArrayList = ArrayList().apply { add("+4427123314") } val params = Bundle() ConnectycubeUsers.getUsersByPhoneNumbers(usersPhones, pagedRequestBuilder, params) .performAsync(object : EntityCallback> { override fun onSuccess(users: ArrayList, args: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java PagedRequestBuilder pagedRequestBuilder = new PagedRequestBuilder(); pagedRequestBuilder.setPage(1); pagedRequestBuilder.setPerPage(50); Bundle params = new Bundle(); ArrayList usersPhones = new ArrayList<>(); usersPhones.add("+4427123314"); ConnectycubeUsers.getUsersByPhoneNumbers(usersPhones, pagedRequestBuilder, params).performAsync(new EntityCallback>() { @Override public void onSuccess(ArrayList users, Bundle args) { } @Override public void onError(ResponseException error) { } }); ``` ### Retrieve user by external ID [Section titled “Retrieve user by external ID”](#retrieve-user-by-external-id) * SDK v1 kotlin (deprecated) ```kotlin ConnectycubeUsers.getUserByExternalId("3789").performAsync(object : EntityCallback { override fun onSuccess(users: ConnectycubeUser, args: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeUsers.getUserByExternalId("3789").performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeUser users, Bundle args) { } @Override public void onError(ResponseException error) { } }); ``` ### Retrieve users by tags [Section titled “Retrieve users by tags”](#retrieve-users-by-tags) * SDK v2 kotlin ```kotlin val userTags = setOf("iphone") ConnectyCube.getUsersByTags(userTags, {usersResult -> }, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val pagedRequestBuilder = PagedRequestBuilder().apply { page = 1 perPage = 50 } val userTags: ArrayList = ArrayList().apply { add("iphone") } val params = Bundle() ConnectycubeUsers.getUsersByTags(userTags, pagedRequestBuilder, params) .performAsync(object : EntityCallback> { override fun onSuccess(users: ArrayList, args: Bundle) { } override fun onError(error: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java PagedRequestBuilder pagedRequestBuilder = new PagedRequestBuilder(); pagedRequestBuilder.setPage(1); pagedRequestBuilder.setPerPage(50); ArrayList userTags = new ArrayList<>(); userTags.add("iphone"); Bundle params = new Bundle(); ConnectycubeUsers.getUsersByTags(userTags, pagedRequestBuilder, params).performAsync(new EntityCallback>() { @Override public void onSuccess(ArrayList users, Bundle args) { } @Override public void onError(ResponseException error) { } }); ``` ## Delete user [Section titled “Delete user”](#delete-user) A user can delete himself from the platform: * SDK v2 kotlin ```kotlin ConnectyCube.deleteUser({}, { error -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin ConnectycubeUsers.deleteUser().performAsync(object : EntityCallback { override fun onSuccess(result: Void?, params: Bundle) { } override fun onError(responseException: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubeUsers.deleteUser().performAsync(new EntityCallback() { @Override public void onSuccess(Void result, Bundle params) { } @Override public void onError(ResponseException responseException) { } }); ``` # Custom Data > Maximize your Android 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](/android/) 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`]); * Float (\[`Double`]); * Boolean (\[`Boolean`]); * String (\[`String`]); * Location (the array what consist of **two** `double`s); After adding all the required fields, click the **`CREATE CLASS`** button to save your scheme. ![Create class scheme](/_astro/create_scheme.BZpQA4pv_Z1tQkMW.webp ":size=400") 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 ![Create class scheme](/_astro/created_scheme.DuEkinE2_2l5wmH.webp) 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. ![Edit permissions levels](/_astro/edit_class_permissions.DwZG2uer_2451o4.webp ":size=400") ### 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: ![Set permissions for record](/_astro/edit_record_permissions.YjI8FGha_Zb7hOq.webp ":size=400") 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. ```kotlin val className = "call_history_item" val customObject: ConnectycubeCustomObject = ConnectycubeCustomObject(className) customObject.fields = hashMapOf ( "call_name" to "Group call", "call_participants" to intArrayOf(2325293, 563541, 563543), "call_start_time" to 1701789791673, "call_end_time" to 0, "call_duration" to 0, "call_state" to "accepted", "is_group_call" to true, "call_id" to "f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454", "user_id" to 2325293, "caller_location" to doubleArrayOf(50.004444, 36.234380) ) ConnectyCube.createCustomObject(customObject, { createdObject -> val objectFields = createdObject.fields // the fields of your object saved on the ConnectyCube server }, { ex -> }) ``` For example, you can use [kotlinx.serialization](https://kotlinlang.org/docs/serialization.html) for serialization and deserialization your objects. In this case, the code will appear as follows: ```kotlin val className = "call_history_item" val customObject: ConnectycubeCustomObject = ConnectycubeCustomObject(className) val callItem: CallItem // the instance of the class that has annotation `@Serializable` customObject.fields = HashMap(Json.encodeToJsonElement(callItem).jsonObject.toMap()) ConnectyCube.createCustomObject(customObject, { createdObject -> val callItem: CallItem = Json.decodeFromJsonElement(createdObject.fields.toJsonObject()) }, { ex -> }) ``` ### 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 `ConnectycubeCustomObject` you use to create the object. In this case, the request will look like this: ```kotlin val className = "call_history_item" val customObject = ConnectycubeCustomObject(className) val callItem: CallItem // the instance of the class that has annotation `@Serializable` customObject.fields = HashMap(Json.encodeToJsonElement(callItem).jsonObject.toMap()) val updatePermission = CustomObjectPermission(Level.OPEN_FOR_USERS_IDS, ids = listOf(232529, 563542, 563541)) val permissions = CustomObjectPermissions(updatePermission = updatePermission) customObject.permissions = permissions ConnectyCube.createCustomObject(customObject, { createdObject -> val permissions = createdObject.permissions }, { ex -> }) ``` ## Retrieve record by ID [Section titled “Retrieve record by ID”](#retrieve-record-by-id) Retrieve the record by specifying its identifier. ```kotlin val className = "call_history_item" val id = '656f407e29d6c5002fce89dc' ConnectyCube.getCustomObjectById(className, id, { record -> val customObject = record // the found record or null if requested record was not found }, { ex -> }) ``` ## Retrieve records by IDs [Section titled “Retrieve records by IDs”](#retrieve-records-by-ids) Retrieve records by specifying their identifiers. ```kotlin val className = "call_history_item" val ids = listOf("656f407e29d6c5002fce89dc", "5f985984ca8bf43530e81233") ConnectyCube.getCustomObjectsByIds(className, ids, { records -> val customObjects: List = records.items // the list of the found items }, { ex -> }) ``` ## 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 ```kotlin val className = "call_history_item" val params = mapOf("call_start_time[gt]" to 1701789791673) ConnectyCube.getCustomObjectsByClassName(className, params, { result -> val customObjects: List = result.items // the list of the found items }, { ex -> }) ``` ## 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. ```kotlin val className = "call_history_item" val id = "656f407e29d6c5002fce89dc" ConnectyCube.getCustomObjectPermissions(className, id, { permissions -> val recordPermissions = permissions // the permissions applied for a searched record }, { ex -> }) ``` ## Update record by ID [Section titled “Update record by ID”](#update-record-by-id) Update record data by specifying its ID. ```kotlin val className = "call_history_item" val id = "656f407e29d6c5002fce89dc" val params = mapOf("call_end_time" to 1701945033120) ConnectyCube.updateCustomObject(className, id, params, { updatedObject -> val updatedCustomObject = updatedObject }, { ex -> }) ``` ## 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 ```kotlin val className = "call_history_item" val params = mapOf( "search_criteria" to mapOf("user_id" to 1234567), "call_name" to "Deleted user" ) ConnectyCube.updateCustomObjectsByCriteria(className, params, { result -> val updatedItems = result.items // the list of updated items }, { ex -> }) ``` ## Delete record by ID [Section titled “Delete record by ID”](#delete-record-by-id) Delete a record from a class by record identifier. ```kotlin val className = "call_history_item" val id = "656f407e29d6c5002fce89dc" ConnectyCube.deleteCustomObjectById(className, id, { result -> // the record was successfully deleted }, { ex -> }) ``` ## 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). ```kotlin val className = "call_history_item" val ids = listOf("656f407e29d6c5002fce89dc", "5f998d3bca8bf4140543f79a") ConnectyCube.deleteCustomObjectsByIds(className, ids, { result -> val deletedItemsIds = result.successfullyDeleted?.ids // `ids` is the result with identifiers of the deleted records request }, { ex -> }) ``` ## 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 ```kotlin val className = "call_history_item" val params = mapOf("call_id[in]" to "f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454") ConnectyCube.deleteCustomObjectsByCriteria(className, params, { totalDeleted -> // `totalDeleted` is the count of deleted items }, { ex -> }) ``` ## 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 Android 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 project** ![Adding Project in Firebase](/_astro/firebase_add_project.Dq7GHe8f_maP4o.webp) > **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**. ![Add a project 1 of 3](/_astro/firebase_add_project_2_1.CTNFPmFI_1qpQUj.webp ":size=400%") ![Add a project 2 of 3](/_astro/firebase_add_project_2_2.xX7Ff4sg_Z1D8se6.webp ":size=400%") 4. Configure Google Analytics for your project and click **Create project**. ![Add a project 3 of 3](/_astro/firebase_add_project_2_3._yPP5HiQ_Z1ctbYe.webp ":size=400%") Then click Continue. ![Click Continue](/_astro/firebase_add_project_4.Cus9Uz0-_Z2oLn44.webp) 5. Select platform for which you need Firebase ![Select platform](/_astro/firebase_add_project_platforms.CynsV-4f_Z1OKfii.webp) 6. Fill in the fields on **Add Firebase to your Android App** screen and click **Register App** ![Add Firebase to your Android app](/_astro/firebase_android_add_to_app.DUbAqHJv_Z114frA.webp) ## Connect Firebase SDK [Section titled “Connect Firebase SDK”](#connect-firebase-sdk) For **Android** projects Firebase has the following **requirements** to be added successfully: * Android OS 4.0 or newer * Google Play Services 15.0.0 or higher * The latest version of Android Studio Here is a step by step guide how to connect Firebase SDK to your Android Project: 1. Download **Google-Services.json** config file ![Download config file](/_astro/firebase_android_add_to_app_config_file.B_sr0-5e_3gMda.webp) 2. Open **Project view** in Android Studio and upload **Google-Services.json** file you have just downloaded into the root directory of your Android app module. ![Upload config file to your project](/_astro/firebase_android_add_to_app_config_file_2.Vk-PvDNY_1QXGrS.webp) 3. **Add Firebase SDK** according to the instructions in your Firebase console ![Add Firebase SDK](/_astro/firebase_android_add_firebase_sdk.CdNjyODI_1YfcdV.webp) 4. **Add Google services plugin** to your **project build.gradle** file ![Add Google services plugin to project](/_astro/firebase_android_add_plugin.n-19GvoZ_Zdarlz.webp) 5. **Add Google services plugin** in the bottom of your **module build.gradle** file. ![Add Google services plugin to module](/_astro/firebase_android_add_plugin_2.D6XEHuoL_2q6gY1.webp) 6. Click **Sync Now** at the pop-up in your Android Studio ![Sync](/_astro/firebase_android_sync.0LS9GcRi_Z2uK4W6.webp) You are done now. ## 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 Android project: 1. Add Firebase authentication dependency to your **Module build.gradle** file: ![Add Firebase authentication dependency](/_astro/firebase_android_auth_dependency.B4tJo7h__1Bcrya.webp) 2. Sync your project as prompted by Android Studio: ![Sync your project](/_astro/firebase_android_sync_project.BxoxDGwl_Z1YRqPH.webp) 3. Find your app’s SHA-1 hash - check [Authenticating Your Client](https://developers.google.com/android/guides/client-auth) guide. 4. Add your app’s SHA-1 hash in your [Firebase console](https://console.firebase.google.com/) >> **Project settings** tab: ![Add your app's SHA-1 hash](/_astro/firebase_android_add_sha_hash.uqz31yJW_Z1NtaAg.webp) And then: ![Add your app's SHA-1 hash](/_astro/firebase_android_add_sha_hash_2.Dq7cQh9-_N04xQ.webp) 5. Go to **Firebase console >> Authentication >> Sign-in method** section: ![Enable phone authentication in Firebase](/_astro/firebase_authentication_phone.DOqJbuvO_Ziw2EF.webp) 6. Enable **Phone** number sign-in method: ![Enable phone authentication in Firebase](/_astro/firebase_authentication_phone_2.hEGZ-tgZ_1zWjJu.webp) 7. Add `PhoneAuthProvider.verifyPhoneNumber` method to request that Firebase verify the user’s phone number: ```java PhoneAuthProvider.getInstance().verifyPhoneNumber( phoneNumber, // Phone number to verify 60, // Timeout duration TimeUnit.SECONDS, // Unit of timeout this, // Activity (for callback binding) mCallbacks); // OnVerificationStateChangedCallbacks ``` ```kotlin PhoneAuthProvider.getInstance().verifyPhoneNumber( phoneNumber, // Phone number to verify 60, // Timeout duration TimeUnit.SECONDS, // Unit of timeout this, // Activity (for callback binding) mCallbacks) // OnVerificationStateChangedCallbacks ``` > **Note** > > `verifyPhoneNumber` method is reentrant: if you call it multiple times, such as in an activity’s `onStart` method, `verifyPhoneNumber` method will not send a second SMS unless the original request has timed out. > **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. 8. (Optional) To resume the phone number sign in process if your app closes before the user can sign in (if the user checks SMS app, for instance), after `verifyPhoneNumber` method is called, set a flag that indicates verification is in progress. Then, save the flag in your Activity’s `onSaveInstanceState` method and restore the flag in `onRestoreInstanceState`. In your Activity’s `onStart` method, check if verification is already in progress, and if it is not, call `verifyPhoneNumber` again. Be sure to clear the flag when verification completes or fails. Check this [guide](https://firebase.google.com/docs/auth/android/phone-auth#verification-callbacks) for more details. 9. `setLanguageCode` method on your Auth instance allows specifying the auth language and therefore localize SMS message sent by Firebase: ```java auth.setLanguageCode("fr"); // To apply the default app language instead of explicitly setting it. // auth.useAppLanguage(); ``` ```kotlin auth.setLanguageCode("fr") // To apply the default app language instead of explicitly setting it. // auth.useAppLanguage(); ``` 10. When you call `PhoneAuthProvider.verifyPhoneNumber` method, you must also provide an instance of `OnVerificationStateChangedCallbacks`, which contains implementations of the callback functions that handle the results of the request: ```java mCallbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() { @Override public void onVerificationCompleted(PhoneAuthCredential credential) { // This callback will be invoked in two situations: // 1 - Instant verification. In some cases the phone number can be instantly // verified without needing to send or enter a verification code. // 2 - Auto-retrieval. On some devices Google Play services can automatically // detect the incoming verification SMS and perform verification without // user action. Log.d(TAG, "onVerificationCompleted:" + credential); signInWithPhoneAuthCredential(credential); } @Override public void onVerificationFailed(FirebaseException e) { // This callback is invoked in an invalid request for verification is made, // for instance if the the phone number format is not valid. Log.w(TAG, "onVerificationFailed", e); if (e instanceof FirebaseAuthInvalidCredentialsException) { // Invalid request // ... } else if (e instanceof FirebaseTooManyRequestsException) { // The SMS quota for the project has been exceeded // ... } // Show a message and update the UI // ... } @Override public void onCodeSent(String verificationId, PhoneAuthProvider.ForceResendingToken token) { // The SMS verification code has been sent to the provided phone number, we // now need to ask the user to enter the code and then construct a credential // by combining the code with a verification ID. Log.d(TAG, "onCodeSent:" + verificationId); // Save verification ID and resending token so we can use them later mVerificationId = verificationId; mResendToken = token; // ... } }; ``` ```kotlin mCallbacks = object : OnVerificationStateChangedCallbacks() { override fun onVerificationCompleted(credential: PhoneAuthCredential) { // This callback will be invoked in two situations: // 1 - Instant verification. In some cases the phone number can be instantly // verified without needing to send or enter a verification code. // 2 - Auto-retrieval. On some devices Google Play services can automatically // detect the incoming verification SMS and perform verification without // user action. Log.d(TAG, "onVerificationCompleted:$credential") signInWithPhoneAuthCredential(credential) } override fun onVerificationFailed(e: FirebaseException) { // This callback is invoked in an invalid request for verification is made, // for instance if the the phone number format is not valid. Log.w(TAG, "onVerificationFailed", e) if (e is FirebaseAuthInvalidCredentialsException) { // Invalid request // ... } else if (e is FirebaseTooManyRequestsException) { // The SMS quota for the project has been exceeded // ... } // Show a message and update the UI // ... } override fun onCodeSent(verificationId: String, token: ForceResendingToken ) { // The SMS verification code has been sent to the provided phone number, we // now need to ask the user to enter the code and then construct a credential // by combining the code with a verification ID. Log.d(TAG, "onCodeSent:$verificationId") // Save verification ID and resending token so we can use them later mVerificationId = verificationId mResendToken = token // ... } } ``` 11. Create a `PhoneAuthCredential` object using the verification code and the verification ID that was passed to `onCodeSent` callback. You get a `PhoneAuthCredential` object when `onVerificationCompleted` is called. To create `PhoneAuthCredential` object, call `PhoneAuthProvider.getCredential`: ```java PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, code); ``` ```kotlin val credential = PhoneAuthProvider.getCredential(verificationId, code) ``` 12. Complete the sign-in flow by passing the `PhoneAuthCredential` object to `FirebaseAuth.signInWithCredential`: ```java private void signInWithPhoneAuthCredential(PhoneAuthCredential credential) { mAuth.signInWithCredential(credential) .addOnCompleteListener(this, new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { // Sign in success, update UI with the signed-in user's information Log.d(TAG, "signInWithCredential:success"); FirebaseUser user = task.getResult().getUser(); // ... } else { // Sign in failed, display a message and update the UI Log.w(TAG, "signInWithCredential:failure", task.getException()); if (task.getException() instanceof FirebaseAuthInvalidCredentialsException) { // The verification code entered was invalid } } } }); } ``` ```kotlin private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) { mAuth.signInWithCredential(credential) .addOnCompleteListener(this) { task -> if (task.isSuccessful) { // Sign in success, update UI with the signed-in user's information Log.d(TAG, "signInWithCredential:success") val user = task.result?.user // ... } else { // Sign in failed, display a message and update the UI Log.w(TAG, "signInWithCredential:failure", task.exception) if (task.exception is FirebaseAuthInvalidCredentialsException) { // The verification code entered was invalid } } } } ``` 13. Get Firebase `access_token` after SMS code verification as follows: ```java public Task getIdToken (boolean forceRefresh) ``` ```kotlin fun getIdToken(forceRefresh: Boolean): Task ``` *Example of the method implementation*: ```java FirebaseAuth.getInstance().getCurrentUser().getIdToken(forceRefresh); ``` ```kotlin FirebaseAuth.getInstance().currentUser?.getIdToken(forceRefresh) ``` 14. Add ConnectyCube user sign in to your project as follows: 1) Get your **Project ID** from your **Firebase console**: ![Get your Project ID](/_astro/firebase_project_id.DbPbXrrM_ZLtFkp.webp) 2. Pass your Firebase `project_id` and Firebase `access_token` parameters to `signInUsingFirebase` method: * SDK v2 ```kotlin //Coming soon ``` * SDK v1 ```java String projectId = "..."; String accessToken = "..."; ConnectycubeUsers.signInUsingFirebase(projectId, accessToken).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeUser user, Bundle args) { } @Override public void onError(ResponseException error) { } }); ``` ```kotlin val projectId = "..." val accessToken = "..." ConnectycubeUsers.signInUsingFirebase(projectId, accessToken) .performAsync(object : EntityCallback { override fun onSuccess(user: ConnectycubeUser, args: Bundle?) { } override fun onError(error: ResponseException) { } }) ``` 14. Try running a test. Once your user is logged in successfully, you will find him/her in your **Dashboard >> Your App >> Users** section. ![User logged in](/_astro/firebase_user_logged_in.DqybzeAt_Z1rKYfj.webp) 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 Android 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 user while he is not using your app actively. The following use cases can be covered additionally with push notifications: * send a chat message if recipient is offline (a push notification will be send automatically) * make a video call with offline opponents (need to send a push notification manually) * request to add a user to contact list (need to send a push notification manually) ## 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: ![Find your FCM service account key](/_astro/fcm_account_key_settings.DIN_1KuB_Z1tPxdW.webp) 2. Select and configure **Manage Keys** option: ![Find your FCM server key](/_astro/fcm_account_key_manage.B_QpQr4j_ZIsMmz.webp) 3. Select **ADD KEY**, **Create new key**: ![Find your FCM server key](/_astro/fcm_account_key_manage_create.DzJUbiXU_Z17xFHF.webp) 4. Select **Key type** (json recommended) and create: ![Find your FCM server key](/_astro/fcm_account_key_json.BHnYerBS_1Pcx5.webp) 5. Save it locally: ![Find your FCM server key](/_astro/fcm_account_key_key_saved.XyuT6mGT_ZRfxMx.webp) 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. ![Add your FCM server key to your Dashboard](/_astro/fcm_service_account_key2.Dd7Qkoql_Z25pJUu.webp) ## 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: ![Find your FCM server key](/_astro/fcm_server_key.BzK_4dQ__ZJghKo.webp) 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. ![Add your FCM server key to your Dashboard](/_astro/fcm_server_key_2.D-cbuUdg_fI4XM.webp) ## Prepare app dependencies [Section titled “Prepare app dependencies”](#prepare-app-dependencies) 1\. As part of enabling Firebase services in your Android application you need to add the **google-services plugin** to your Project **build.gradle** file: ```groovy dependencies { classpath 'com.google.gms:google-services:3.0.0' ... } ``` ![Add FCM dependency](/_astro/fcm_add_dependencies.zf-Op2Hy_1WtPYq.webp) > **Note**: If you use google play services in your project, use version 11.8.0 or higher (version we use in our SDK) as all com.google.android.gms libraries must use the exact same version specification. If you use version higher than 11.8.0, then just add explicit dependency: ```groovy compile "com.google.android.gms:play-services-gcm:17.0.0" compile "com.google.firebase:firebase-messaging:17.0.0" compile "com.google.firebase:firebase-core:17.0.0" ``` ![Add FCM dependency](/_astro/fcm_add_dependencies_2.C1WsHZeT_Z52G6D.webp) 2\. Include **gms** plugin in the bottom of your module **build.gradle** : ```groovy apply plugin: 'com.google.gms.google-services' ``` ![Include gms plugin](/_astro/firebase_android_add_plugin_2.D6XEHuoL_2q6gY1.webp) 3\. For **GCM** push type add an empty **current\_key** settings into your **google-services.json** file for your package name *(this paragraph relates to GCM. If you use FCM and already have some value in current\_key field, leave it as is)*: ```json "client": [ { "client_info": { "mobilesdk_app_id": "1:861750218637:android:c7299bc46191b2d7", "client_id": "android:com.your.app.package.id", "client_type": 1, "android_client_info": { "package_name": "com.your.app.package.id" } }, "api_key": [ { "current_key": "" } ], } ] ``` ![Add current\_key](/_astro/fcm_current_key.BJUPy1AB_12Cjrr.webp) ## Prepare app AndroidManifest (only v1) [Section titled “Prepare app AndroidManifest (only v1)”](#prepare-app-androidmanifest-only-v1) 1. Copy **Sender ID** value from your **Firebase console**: ![Find your Sender ID](/_astro/fcm_sender_id.Bhcx5o0__1EItYu.webp) 2. Edit your app **AndroidManifest.xml** file and add your Firebase **Sender ID** as well as notification type and environment there: To integrate automatic push subscription you just need set values in **AndroidManifest**: ```xml ``` * com.connectycube.pushnotifications.TYPE - can be FCM * com.connectycube.pushnotifications.SENDER\_ID - your sender id from google console (for ex.8617520217632) * com.connectycube.pushnotifications.ENVIRONMENT - can be DEVELOPMENT or PRODUCTION ![Add your Sender ID, notification type and environment](/_astro/fcm_manifest.DgScHQDs_16S5fo.webp) Then you need to setup **FcmPushListenerService** and **FcmPushInstanceIDService**: ```xml ``` ## Automatic push subscription (only v1) [Section titled “Automatic push subscription (only v1)”](#automatic-push-subscription-only-v1) ConnectyCube Android SDK provides automatic push subscription management. It means you don’t need bother how to get FCM device token, create push subscription and what to do with the received data. Thus, you can reduce your code and make it cleaner. Here are the requirements on how to integrate automatic push subscription feature to your app: ### Enable/Disable push subscription [Section titled “Enable/Disable push subscription”](#enabledisable-push-subscription) Here you can use global setting to enable or disable delivery of push notifications (means to set this parameter only one time): * SDK v1 kotlin (deprecated) ```kotlin ConnectycubeSettings.getInstance().isEnablePushNotification = false // default is 'true' val isEnabled = ConnectycubeSettings.getInstance().isEnablePushNotification ``` * SDK v1 java (deprecated) ```java ConnectycubeSettings.getInstance().setEnablePushNotification(false); // default is 'true' boolean isEnabled = ConnectycubeSettings.getInstance().isEnablePushNotification(); ``` ### Track subscription status (only v1) [Section titled “Track subscription status (only v1)”](#track-subscription-status-only-v1) To be aware about what is happening with your push subscription, whether you’re subscribed successfully or not, you can use the **SubscribeListener**. Just add **SubscribeListener** right after the **ConnectycubeSettings.getInstance().init()** code: * SDK v1 kotlin (deprecated) ```kotlin ConnectycubePushManager.getInstance() .addListener(object : ConnectycubePushManager.SubscribeListener { override fun onSubscriptionCreated() {} override fun onSubscriptionError(e: Exception, resultCode: Int) { Log.d(TAG, "onSubscriptionError$e") if (resultCode >= 0) { Log.d(TAG, "Google play service exception$resultCode") } } override fun onSubscriptionDeleted(success: Boolean) { } }) ``` * SDK v1 java (deprecated) ```java ConnectycubePushManager.getInstance().addListener(new ConnectycubePushManager.SubscribeListener() { @Override public void onSubscriptionCreated() { } @Override public void onSubscriptionError(final Exception e, int resultCode) { Log.d(TAG, "onSubscriptionError" + e); if (resultCode >= 0) { Log.d(TAG, "Google play service exception" + resultCode); } } @Override public void onSubscriptionDeleted(boolean success) { } }); ``` ## Manual push subscription [Section titled “Manual push subscription”](#manual-push-subscription) If you don’t want to use automatic push subscription feature (**only v1**), then do the following: * Set **SubscribePushStrategy.MANUAL** as the main strategy: - SDK v1 kotlin (deprecated) ```kotlin // default SubscribePushStrategy.ALWAYS ConnectycubeSettings.getInstance().subscribePushStrategy = SubscribePushStrategy.MANUAL ``` - SDK v1 java (deprecated) ```java // default SubscribePushStrategy.ALWAYS ConnectycubeSettings.getInstance().setSubscribePushStrategy(SubscribePushStrategy.MANUAL) ``` In this case you need to subscribe and unsubscribe manually using the following methods: * SDK v2 kotlin ```kotlin //create push subscription val subscriptionParameters = CreatePushSubscriptionParameters(environment = "development", channel = "gcm", udid = deviceId, platform = "android", pushToken = registrationID) ConnectyCube.createPushSubscription(subscriptionParameters.getRequestParameters(), { subscriptions -> }, { ex -> }) //delete push subscription ConnectyCube.deletePushSubscription(subscriptionId, {}, { ex -> }) ``` * SDK v1 kotlin (deprecated) ```kotlin val tokenRefreshed = false SubscribeService.subscribeToPushes(context, tokenRefreshed) SubscribeService.unSubscribeFromPushes(context) // this comes from 'InstanceIDListenerService' override fun onTokenRefresh() { val tokenRefreshed = true SubscribeService.subscribeToPushes(context, tokenRefreshed) } ``` * SDK v1 java (deprecated) ```java boolean tokenRefreshed = false; SubscribeService.subscribeToPushes(context, tokenRefreshed); SubscribeService.unSubscribeFromPushes(context); // this comes from 'InstanceIDListenerService' @Override public void onTokenRefresh() { boolean tokenRefreshed = true; SubscribeService.subscribeToPushes(context, tokenRefreshed); } ``` ## Send push notifications [Section titled “Send push notifications”](#send-push-notifications) You can manually initiate sending of push notifications 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: * SDK v2 kotlin ```kotlin val cubeEventParams = CreateEventParams() cubeEventParams.parameters["message"] = "Some text" cubeEventParams.notificationType = NotificationType.PUSH cubeEventParams.environment = ConnectycubeEnvironment.DEVELOPMENT cubeEventParams.eventType = PushEventType.ONE_SHOT cubeEventParams.usersIds.add(373737) val cubeEvent = cubeEventParams.getEventForRequest() ConnectyCube.createPushEvent(cubeEvent, successCallback = {result -> }, errorCallback = { error ->}) ``` * SDK v1 kotlin (deprecated) ```kotlin val userIds = StringifyArrayList().apply { add(21) add(22) } val event = ConnectycubeEvent().apply { this.userIds = userIds environment = ConnectycubeEnvironment.DEVELOPMENT notificationType = ConnectycubeNotificationType.PUSH } val json = JSONObject() json.put("message", "New bitcoin trends today") // custom parameters json.put("user_id", "56") json.put("dialog_id", "54123123123416234614263123") event.message = json.toString() ConnectycubePushNotifications.createEvent(event) .performAsync(object : EntityCallback { override fun onSuccess(event: ConnectycubeEvent, args: Bundle?) { // sent } override fun onError(errors: ResponseException) { } }) ``` * SDK v1 java (deprecated) ```java ArrayList recipientsUserIds = new StringifyArrayList(); userIds.add(21); userIds.add(22); ConnectycubeEvent event = new ConnectycubeEvent(); event.setUserIds(userIds); event.setEnvironment(ConnectycubeEnvironment.DEVELOPMENT); event.setNotificationType(ConnectycubeNotificationType.PUSH); JSONObject json = new JSONObject(); try { json.put("message", "New bitcoin trends today"); // custom parameters json.put("user_id", "56"); json.put("dialog_id", "54123123123416234614263123"); } catch (Exception e) { e.printStackTrace(); } event.setMessage(json.toString()); ConnectycubePushNotifications.createEvent(event).performAsync(new EntityCallback() { @Override public void onSuccess(ConnectycubeEvent event, Bundle args) { // sent } @Override public void onError(ResponseException errors) { } }); ``` ## Receive push notifications [Section titled “Receive push notifications”](#receive-push-notifications) To receive push notifications extend *FirebaseMessagingService* and register it in *AndroidManifest*: * SDK v2 kotlin ```kotlin class PushListenerService: FirebaseMessagingService() { override fun onNewToken(token: String) { Timber.d("Refreshed token: $token") if( ConnectycubeSessionManager.activeSession?.user?.id != 0) { //need to createPushSubscription() with new token } } override fun onMessageReceived(remoteMessage: RemoteMessage) { //need to process push notification Log.d(TAG, "From: ${remoteMessage.from}") if (remoteMessage.data.isNotEmpty()) { Log.d(TAG, "remoteMessage.data ${remoteMessage.data}") AppNotificationManager.getInstance().processPushNotification(this, remoteMessage.data) } } } // register service in AndroidManifest ``` or you need to register the **BroadcastReceiver** (**only v1**) : * SDK v1 kotlin (deprecated) ```kotlin val pushBroadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val message = intent.getStringExtra("message") val from = intent.getStringExtra("from") Log.i(TAG, "New push message: $message, from $from") } } LocalBroadcastManager.getInstance(context).registerReceiver( pushBroadcastReceiver, IntentFilter("new-push-event") ) ``` * SDK v1 java (deprecated) ```java private BroadcastReceiver pushBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String message = intent.getStringExtra("message"); String from = intent.getStringExtra("from"); Log.i(TAG, "New push message: " + message + ", from " + from); } }; LocalBroadcastManager.getInstance(this).registerReceiver(pushBroadcastReceiver, new IntentFilter("new-push-event")); ``` # Streaming > Leverage ConnectyCube's streaming feature for dynamic real-time interactions in Android app. Ideal for interactive sessions, such as teachers broadcasting to multiple students. ConnectyCube **Streaming 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. Streaming API is available starting from [Advanced plan](https://connectycube.com/pricing/). ## Features supported [Section titled “Features supported”](#features-supported) * Streaming/listening video/audio * Join-Rejoin stream functionality * Guest rooms * Mute/Unmute audio/video stream * Display bitrate * Switch video input device (camera) ## Code samples [Section titled “Code samples”](#code-samples) To be announced later. Please [contact us](https://connectycube.com/contact/) if you have any requirements. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) Streaming API is based on top of regular [Multiparty Video Conferencing API ](/android/videocalling-conference), so it’s good to learn its API as well. ## Connect SDK [Section titled “Connect SDK”](#connect-sdk) For using Streaming 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" ``` ## Streaming sessions [Section titled “Streaming sessions”](#streaming-sessions) There are two sessions for handling `streaming` process in a easy way: **ConferenceStreamingSession** - is a session within certain video room, managing the streaming current processes.\ **ConferenceWatchingSession** - is a session within certain video room, managing the watching current stream processes. ## Common Callbacks [Section titled “Common Callbacks”](#common-callbacks) In order to have an ability to receive callbacks about current `session` instance state and conference events, you should implement appropriate interfaces: Implement `RTCSessionStateCallback` for tracking connection state: * SDK v2 kotlin ```kotlin // Coming soon ``` * SDK v1 kotlin (deprecated) ```kotlin val sessionStateCallback = object: RTCSessionStateCallback { /** * Called when session state is changed */ override fun onStateChanged(session: ConferenceStreamBaseSession, state: BaseSession.RTCSessionState) {} /** * Called in case when opponent disconnected */ override fun onDisconnectedFromUser(session: ConferenceStreamBaseSession, userID: Int) {} /** * Called in case when connection with opponent is established */ override fun onConnectedToUser(session: ConferenceStreamBaseSession, userID: Int) {} /** * Called in case when connection closed with certain user. */ override fun onConnectionClosedForUser(session: ConferenceStreamBaseSession, 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(ConferenceStreamBaseSession session, BaseSession.RTCSessionState state); /** * Called in case when connection with opponent is established */ void onConnectedToUser(ConferenceStreamBaseSession session, Integer userID); /** * Called in case when opponent disconnected */ void onDisconnectedFromUser(ConferenceStreamBaseSession session, Integer userID); /** * Called in case when connection closed with certain user. */ void onConnectionClosedForUser(ConferenceStreamBaseSession session, Integer userID); } currentSession.addSessionCallbacksListener(sessionStateCallback); //currentSession.removeSessionCallbacksListener(sessionStateCallback); ``` Implement `ConferenceStateListener` for tracking conference events: * SDK v2 kotlin ```kotlin // Coming soon ``` * SDK v1 kotlin (deprecated) ```kotlin var stateListener: ConferenceStateListener = object : ConferenceStateListener { override fun onSlowLinkReceived(uplink: Boolean, nacks: Int) {} override fun onError(e: WsException) {} override fun onSessionClosed(conferenceBaseSession: ConferenceStreamBaseSession) {} } ``` * SDK v1 java (deprecated) ```java ConferenceStateListener stateListener = new ConferenceStateListener() { @Override public void onSlowLinkReceived(boolean uplink, int nacks) {} @Override public void onError(WsException e) { // e.g. handling exception for watchSession if (ex.getCause() != null && ConferenceWatchingSession.SUBSCRIBE_ERROR_CAUSE_NO_STREAM.equals(ex.getCause().getMessage())) { watchSession.stopWatching(); } } @Override public void onSessionClosed(ConferenceStreamBaseSession conferenceBaseSession) {} }; ``` ## Video and Audio tracks [Section titled “Video and Audio tracks”](#video-and-audio-tracks) For obtaining video and audio tracks implement interface `RTCClientVideoTracksCallback` and `RTCClientAudioTracksCallback`. For setting video track for ConferenceSession - the `ConferenceStreamSurfaceView` class is provided. ## ConferenceStreamClient [Section titled “ConferenceStreamClient”](#conferencestreamclient) `ConferenceStreamClient` instance is a client model responsible for managing conference streaming session. ## ConferenceStreamingSession [Section titled “ConferenceStreamingSession”](#conferencestreamingsession) * SDK v2 kotlin ```kotlin // Coming soon ``` * SDK v1 kotlin (deprecated) ```kotlin val streamSession = ConferenceStreamClient.getInstance(context).createStreamSession(currentUser.getId(), RTCTypes.ConferenceType.CONFERENCE_TYPE_VIDEO) ``` * SDK v1 java (deprecated) ```java ConferenceStreamingSession streamSession = ConferenceStreamClient.getInstance(context).createStreamSession(currentUser.getId(), RTCTypes.ConferenceType.CONFERENCE_TYPE_VIDEO); ``` **Callbacks** * SDK v2 kotlin ```kotlin // Coming soon ``` * SDK v1 kotlin (deprecated) ```kotlin var streamingListener: ConferenceStreamingSession.ConferenceStreamingListener = object : ConferenceStreamingSession.ConferenceStreamingListener { override fun onStartStreaming() {} override fun onStopStreaming() {} override fun onWatcherReceived(userId: Int) {} override fun onWatcherLeft(userId: Int) {} } streamSession.addEventListener(streamingListener); // streamSession.removeEventListener(streamingListener); ``` * SDK v1 java (deprecated) ```java ConferenceStreamingSession.ConferenceStreamingListener streamingListener = new ConferenceStreamingSession.ConferenceStreamingListener() { @Override public void onStartStreaming() { //your video/audio is successfully streaming } @Override public void onStopStreaming() { //your video/audio has stopped } @Override public void onWatcherReceived(Integer userId) { //some user has connected to your stream } @Override public void onWatcherLeft(Integer userId) { //some user has disconnected from your stream } }; streamSession.addEventListener(streamingListener); // streamSession.removeEventListener(streamingListener); ``` ## Start and stop streaming [Section titled “Start and stop streaming”](#start-and-stop-streaming) * SDK v2 kotlin ```kotlin // Coming soon ``` * SDK v1 kotlin (deprecated) ```kotlin streamSession.startStreaming(roomId) ... streamSession.stopStreaming() ``` * SDK v1 java (deprecated) ```java streamSession.startStreaming(roomId); ... streamSession.stopStreaming(); ``` ## ConferenceWatchingSession [Section titled “ConferenceWatchingSession”](#conferencewatchingsession) * SDK v2 kotlin ```kotlin // Coming soon ``` * SDK v1 kotlin (deprecated) ```kotlin val watchingSession = ConferenceStreamClient.getInstance(context).createWatchingSession(currentUser.getId()) ``` * SDK v1 java (deprecated) ```java ConferenceWatchingSession watchingSession = ConferenceStreamClient.getInstance(context).createWatchingSession(currentUser.getId()); ``` **Callbacks** * SDK v2 kotlin ```kotlin // Coming soon ``` * SDK v1 kotlin (deprecated) ```kotlin val watchingListener: ConferenceWatchingSession.ConferenceWatchingListener = object : ConferenceWatchingSession.ConferenceWatchingListener { override fun onStartWatching() {} override fun onStopWatching() {} override fun onPublishersReceived(userIds: ArrayList) {} override fun onPublisherLeft(userId: Int) {} } watchingSession.addEventListener(watchingListener); // watchingSession.removeEventListener(watchingListener); ``` * SDK v1 java (deprecated) ```java ConferenceWatchingSession.ConferenceWatchingListener watchingListener = new ConferenceWatchingSession.ConferenceWatchingListener() { @Override public void onStartWatching() { //your user successfully has become a watcher of the broadcasting stream } @Override public void onStopWatching() { //your user stopped watching the broadcasting stream } @Override public void onPublishersReceived(ArrayList userIds) { //while you are watching some stream, or ready to, you can get some streaming user, and (if you want) subscribe to it //watchingSession.subscribeToPublisher(userIds(0)); } @Override public void onPublisherLeft(Integer userId) { //your streaming user has left watchingSession.stopWatching(); } }; watchingSession.addEventListener(watchingListener); // watchingSession.removeEventListener(watchingListener); ``` ## Start and stop watching [Section titled “Start and stop watching”](#start-and-stop-watching) * SDK v2 kotlin ```kotlin // Coming soon ``` * SDK v1 kotlin (deprecated) ```kotlin watchingSession.startWatching(roomId) ... watchingSession.stopWatching() ``` * SDK v1 java (deprecated) ```java watchingSession.startWatching(roomId); ... watchingSession.stopWatching(); ``` # Send first chat message > Step-by-step guide to sending your first chat message using ConnectyCube iOS 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](/ios/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 connect ConnectyCube to your iOS app just add it into your `Podfile` which is a part of [CocoaPods](https://cocoapods.org/) tool: ```ruby platform :ios, '9.0' use_frameworks! target 'MyApp' do pod 'ConnectyCube' end ``` and then install packages from the command line: ```bash pod install ``` ### Import SDK [Section titled “Import SDK”](#import-sdk) Add the following import statement to start using all classes and methods. ```swift import 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): ```swift ConnectyCube().doInit(applicationId: APP_ID, authorizationKey: AUTH_KEY, connectycubeConfig: nil) ``` ## 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: ```swift let user: ConnectycubeUser = ConnectycubeUser() user.login = "marvin18" user.password = "supersecurepwd" ConnectyCube().createSession(user: user, successCallback: {(session) in }) { (error) in } ``` **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. ```swift let user = ConnectycubeUser() user.id = 2746 user.password = "supersecurepwd" ConnectyCube().chat.login(user: user, successCallback:{ }, errorCallback: { error in }, resource: ConnectycubeSettings().chatDefaultResource) ``` Use `ConnectycubeConnectionListener` to handle different connection states: ```swift class YourClass : NSObject { override init() { super.init() ConnectyCube().chat.addConnectionListener(listener: self) } } extension YourClass : ConnectycubeConnectionListener { func onConnected() { } func onDisconnected() { } } ``` ## 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. ```swift let dialog = ConnectycubeDialog() dialog.type = ConnectycubeDialogType.companion.PRIVATE dialog.occupantsIds = [34] // an ID of opponent ConnectyCube().createDialog(connectycubeDialog: dialog, successCallback: {(dialog) in }) { (error) in } ``` ## 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: ```swift let message = ConnectycubeMessage() message.body = "How are you today?" message.recipientId = 34 ConnectyCube().chat.sendMessage(msg: message, successCallback: { }) { (error) in } //MARK: ConnectycubeMessageListener ConnectyCube().chat.addMessageListener(listener: self) extension YourClass: ConnectycubeMessageListener { func onMessage(message: ConnectycubeMessage) { } func onError(message: ConnectycubeMessage, ex: KotlinThrowable) { } } ``` 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](/ios/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 iOS 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](/ios/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 connect ConnectyCube to your iOS app just add it into your `Podfile` which is a part of [CocoaPods](https://cocoapods.org/) tool: ```ruby platform :ios, '9.0' use_frameworks! target 'MyApp' do pod 'ConnectyCube' end ``` and then install packages from the command line: ```bash pod install ``` ### Import SDK [Section titled “Import SDK”](#import-sdk) Add the following import statement to start using all classes and methods. ```swift import 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): ```swift ConnectyCube().doInit(applicationId: APP_ID, authorizationKey: AUTH_KEY, connectycubeConfig: nil) ``` ## 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: ```swift let user: ConnectycubeUser = ConnectycubeUser() user.login = "marvin18" user.password = "supersecurepwd" ConnectyCube().createSession(user: user, successCallback: {(session) in }) { (error) in } ``` **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](/ios/messaging) is used as a signalling transport for Video Calling API: ```swift let user = ConnectycubeUser() user.id = 2746 user.password = "supersecurepwd" ConnectyCube().chat.login(user: user, successCallback:{ }, errorCallback: { error in }, resource: ConnectycubeSettings().chatDefaultResource) ``` ## Step 4: P2P calls initialization [Section titled “Step 4: P2P calls initialization”](#step-4-p2p-calls-initialization) Before any interaction with ConnectyCubeCalls you need to initialize it: ```swift ConnectyCube().p2pCalls.register() ``` ## Step 5: Initiate call [Section titled “Step 5: Initiate call”](#step-5-initiate-call) `Initiate Call` function sets up the foundational connection between the caller and the selected opponents: ```swift let opponentsIds = [3245, 2123, 3122].map{KotlinInt(value: $0)} let newSession = ConnectyCube().p2pCalls.createSession(userIds: opponentsIds, callType: CallType.video) // userInfo - the custom user information dictionary for the call. May be nil. let userInfo = ["key":"value"] as? KotlinMutableDictionary // optional newSession.startCall(userInfo: userInfo) ``` After this your opponents will receive one call request per 5 second for a duration of 45 seconds (you can configure these settings with `WebRTCConfig`: ```swift //MARK: RTCCallSessionCallback ConnectyCube().p2pCalls.addSessionCallbacksListener(callback: self) extension YourClass: RTCCallSessionCallback { func onReceiveNewSession(session: P2PSession) { if self.session != nil { // we already have a video/audio call session, so we reject another one // userInfo - the custom user information dictionary for the call from caller. May be nil. let userInfo = ["key":"value"] as? KotlinMutableDictionary // optional session.rejectCall(userInfo: userInfo) return } // saving session instance here self.session = session } ... } ``` `self.session` refers to the current session. Each particular audio - video call has a unique `sessionID`. This allows you to have more than one independent audio-video conferences. If you want to increase the call timeout, e.g. set to 60 seconds: ```swift WebRTCConfig().answerTimeInterval = 60 ``` > Default value is 60 seconds. In case opponent did not respond to your call within a specific timeout time, the callback listed below will be called: ```swift //MARK: RTCCallSessionCallback ConnectyCube().p2pCalls.addSessionCallbacksListener(callback: self) func onUserNotAnswer(session: P2PSession, opponentId: Int32) { } ``` ## Step 6: Accept a call [Section titled “Step 6: Accept a call”](#step-6-accept-a-call) In order to accept a call, use the `P2PSession` method below: ```swift // userInfo - the custom user information dictionary for the accept call. May be nil. let userInfo: KotlinMutableDictionary = ["key":"value"] // optional self.session?.acceptCall(userInfo: userInfo) ``` After this you will receive an **accept** signal: ```swift //MARK: RTCCallSessionCallback ConnectyCube().p2pCalls.addSessionCallbacksListener(callback: self) func onCallAcceptByUser(session: P2PSession, opponentId: Int32, userInfo: [String : Any]?) { } ``` 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](/ios/videocalling) * [Conference calling SDK documentation](/ios/videocalling-conference) # Chat > Integrate powerful chat functionality into your iOS 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 ```swift let user = ConnectycubeUser() user.id = 2746 user.password = "password" ConnectyCube().chat.login(user: user, successCallback:{ }, errorCallback: { error in }, resource: ConnectycubeSettings().chatDefaultResource) ``` * SDK v1 (deprecated) ```objectivec [CYBChat.instance connectWithUserID:2746 password:@"password" completion:^(NSError * _Nullable error) { }]; ``` ```swift Chat.instance.connect(withUserID: 2746, password: "password") { (error) in } ``` Use `CYBChatDelegate` v1 or `ConnectycubeConnectionListener` v2 to handle different connection states: * SDK v2 ```swift class YourClass : NSObject { override init() { super.init() ConnectyCube().chat.addConnectionListener(listener: self) } } extension YourClass : ConnectycubeConnectionListener { func onConnected() { } func onDisconnected() { } } ``` * SDK v1 (deprecated) ```objectivec @interface YourClass () @end @implementation YourClass - (instancetype)init { self = [super init]; if (self) { [CYBChat.instance addDelegate:self]; } return self; } //MARK: CYBChatDelegate - (void)chatDidConnect { } - (void)chatDidReconnect { } - (void)chatDidDisconnectWithError:(NSError *)error { } - (void)chatDidNotConnectWithError:(NSError *)error { } - (void)chatDidFailWithStreamError:(NSError *)error { } ``` ```swift class YourClass : NSObject { override init() { super.init() Chat.instance.addDelegate(self) } } //MARK: ChatDelegate extension YourClass : ChatDelegate { func chatDidConnect() { } func chatDidReconnect() { } func chatDidDisconnectWithError(_ error: Error) { } func chatDidNotConnectWithError(_ error: 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: * SDK v2 ```swift let user = ConnectycubeUser() user.id = 2746 user.password = ConnectycubeSessionManager().getToken() ConnectyCube().chat.login(user: user, successCallback:{ }, errorCallback: { error in }, resource: ConnectycubeSettings().chatDefaultResource) ``` * SDK v1 (deprecated) ```objectivec NSString *token = CYBSession.currentSession.sessionDetails.token; [CYBChat.instance connectWithUserID:2746 password:token completion:^(NSError * _Nullable error) { }]; ``` ```swift String token = Session.currentSession.sessionDetails.token Chat.instance.connect(withUserID: 2746, password: token) { (error) in } ``` ## Disconnect [Section titled “Disconnect”](#disconnect) * SDK v2 ```swift ConnectyCube().chat.logout(successCallback: { }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBChat.instance disconnectWithCompletionBlock:^(NSError * _Nullable error) { }]; ``` ```swift Chat.instance.disconnect { (error) in } ``` ## Reconnection [Section titled “Reconnection”](#reconnection) SDK reconnects automatically when connection to Chat server is lost. There is a way to disable it and then manage it manually: * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec CYBSettings.autoReconnectEnabled = NO; ``` ```swift Settings.autoReconnectEnabled = false ``` ## Chat in background [Section titled “Chat in background”](#chat-in-background) As iOS doesn’t provide ‘true’ background mode, we can’t have a persistent Chat connection while iOS application is in the background. The better way to handle [chat offline messages](#) correctly is to do: `Chat disconnect` when an app goes to background and do `Chat connect` when an app goes to the foreground. * SDK v2 ```swift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { ... func applicationWillTerminate(_ application: UIApplication) { ConnectyCube().chat.logout(successCallback: { }) } func applicationDidEnterBackground(\_ application: UIApplication) { ConnectyCube().chat.logout(successCallback: { }) } func applicationWillEnterForeground(\_ application: UIApplication) { ConnectyCube().chat.login(user: user, successCallback:{ }, errorCallback: { error in }, resource: ConnectycubeSettings().chatDefaultResource) } ``` * SDK v1 (deprecated) ```objectivec @implementation AppDelegate ... - (void)applicationWillTerminate:(UIApplication *)application { [CYBChat.instance disconnectWithCompletionBlock:^(NSError * _Nullable error) { }]; } - (void)applicationDidEnterBackground:(UIApplication *)application { [CYBChat.instance disconnectWithCompletionBlock:^(NSError * _Nullable error) { }]; } - (void)applicationWillEnterForeground:(UIApplication *)application { [CYBChat.instance connectWithUserID:2746 password:@"password" completion:^(NSError * _Nullable error) { }]; } ... ``` ```swift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { ... func applicationWillTerminate(_ application: UIApplication) { Chat.instance.disconnect { (error) in } } func applicationDidEnterBackground(_ application: UIApplication) { Chat.instance.disconnect { (error) in } } func applicationWillEnterForeground(_ application: UIApplication) { Chat.instance.connect(withUserID: 2746, password: "password") { (error) in } } ... ``` ## Dialogs [Section titled “Dialogs”](#dialogs) All chats between users are organized in dialogs. The are 4 types of chat dialogs: * 1-1 chat - a conversation between 2 users. * group chat - a conversation between specified list of users. * public group 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) * SDK v2 ```swift let dialog = ConnectycubeDialog() dialog.type = ConnectycubeDialogType.companion.PRIVATE dialog.occupantsIds = [34] // an ID of opponent ConnectyCube().createDialog(connectycubeDialog: dialog, successCallback: {(dialog) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec CYBChatDialog *dialog = [[CYBChatDialog alloc] initWithDialogID:nil type:CYBChatDialogTypePrivate]; dialog.occupantIDs = @[@34]; // an ID of opponent [CYBRequest createDialog:dialog successBlock:^(CYBChatDialog * _Nonnull dialog) { } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift let dialog = ChatDialog(dialogID: nil, type: .private) dialog.occupantIDs = [34] // an ID of opponent Request.createDialog(dialog, successBlock: { (dialog) in }) { (error) in } ``` ### Create group chat [Section titled “Create group chat”](#create-group-chat) * SDK v2 ```swift let dialog = ConnectycubeDialog() dialog.type = ConnectycubeDialogType.companion.GROUP dialog.name = "New group dialog" dialog.occupantsIds = [34, 45, 55] //dialog.photo = "..." //dialog.dialogDescription = "..." ConnectyCube().createDialog(connectycubeDialog: dialog, successCallback: {(dialog) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec CYBChatDialog *dialog = [[CYBChatDialog alloc] initWithDialogID:nil type:CYBChatDialogTypeGroup]; dialog.name = @"Group dialog name"; dialog.occupantIDs = @[@34, @45, @55]; // dialog.photo = @"..."; // dialog.dialogDescription = @"..."; [CYBRequest createDialog:dialog successBlock:^(CYBChatDialog * _Nonnull dialog) { } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift let dialog = ChatDialog(dialogID: nil, type: .group) dialog.name = "New group dialog" dialog.occupantIDs = [34, 45, 55] // dialog.photo = "..."; // dialog.dialogDescription = "..."; Request.createDialog(dialog, successBlock: { (dialog) in }) { (error) in } ``` ### 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. * SDK v2 ```swift let dialog = ConnectycubeDialog() dialog.type = ConnectycubeDialogType.companion.PUBLIC dialog.name = "Public dialog name" dialog.dialogDescription = "Public dialog description" //dialog.photo = "..." ConnectyCube().createDialog(connectycubeDialog: dialog, successCallback: {(dialog) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec CYBChatDialog *dialog = [[CYBChatDialog alloc] initWithDialogID:nil type:CYBChatDialogTypePublic]; dialog.name = @"Public dialog name"; dialog.dialogDescription = @"Public dialog description"; // dialog.photo = @"..."; [CYBRequest createDialog:dialog successBlock:^(CYBChatDialog * _Nonnull dialog) { } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift let dialog = ChatDialog(dialogID: nil, type: .public) dialog.name = "Public dialog name" dialog.dialogDescription = "Public dialog description" // dialog.photo = "..."; Request.createDialog(dialog, successBlock: { (dialog) in }) { (error) in } ``` With public dialog ID any user can subscribe to the public dialog via the following code: * SDK v2 ```swift ConnectyCube().subscribeToDialog(dialogId: "5b8d30d1ca8bf43f8b9df3d9", successCallback: {(dialog) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest subscribeToPublicDialogWithID:@"5b8d30d1ca8bf43f8b9df3d9" successBlock:^{ } errorBlock:^(NSError *error) { }]; ``` ```swift Request.subscribeToPublicDialog(withID: "5b8d30d1ca8bf43f8b9df3d9", successBlock: { }) { (error) in } ``` 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 ```swift ConnectyCube().unsubscribeFromDialog(dialogId: "5b8d30d1ca8bf43f8b9df3d9", successCallback: { }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest ubsubscribeFromPublicDialogWithID:@"5b8d30d1ca8bf43f8b9df3d9" successBlock:^{ } errorBlock:^(NSError *error) { }]; ``` ```swift Request.ubsubscribeFromPublicDialog(withID: "5b8d30d1ca8bf43f8b9df3d9", successBlock: { }) { (error) in } ``` ### Chat metadata [Section titled “Chat metadata”](#chat-metadata) A dialog can have up to 3 custom sub-fields to store additional information that can be linked to chat. To start using extensions, allowed fields should be added first. Go to [Admin panel](https://admin.connectycube.com) > Chat > Custom Fields and provide allowed custom fields. ![Dialog Extensions fields configuration example](/_astro/dialog_custom_params.CrGT0s8Z_1XWSCw.webp) When create a dialog, the `extensions` field object must contain allowed fields only. Others fields will be ignored. The values will be casted to string. * SDK v2 ```swift let dialog = ConnectycubeDialog() dialog.type = ConnectycubeDialogType.companion.GROUP dialog.name = "Friday party" dialog.occupantsIds = [29085, 29086, 29087] dialog.dialogDescription = "lets dance the night away" dialog.extensions = ["location": "Sun bar"] ConnectyCube().createDialog(connectycubeDialog: dialog, successCallback: {(dialog) in }) { (error) in } ``` 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 ```swift let params = ["limit": 50, "skip": 100] ConnectyCube().getDialogs(params: params, successCallback: { result in let dialogs: [ConnectycubeDialog] = result.items as! [ConnectycubeDialog] }, errorCallback: { error in }) ``` * SDK v1 (deprecated) ```objectivec [CYBRequest dialogsWithPaginator:[CYBPaginator limit:50 skip:100] extendedRequest:nil successBlock:^(NSArray * _Nonnull dialogs, NSSet * _Nonnull dialogsUsersIDs, CYBPaginator * _Nonnull paginator) { } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift Request.dialogs(with: Paginator.limit(50, skip: 100), extendedRequest: nil, successBlock: { (dialogs, usersIDs, paginator) in }) { (error) in } ``` It will return all your 1-1 dialogs, group dialog and also public dialogs your are subscribed to. ## 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 ```swift let paramsToUpdate = UpdateDialogParams() paramsToUpdate.newName = "New dialog name" paramsToUpdate.newDescription = "New dialog description" paramsToUpdate.newPhoto = "https://new_photo_url" // or it can be an ID to some file in Storage module let parameters = paramsToUpdate.getUpdateDialogParams() as! [String : Any] ConnectyCube().updateDialog(dialogId: "5356c64ab35c12bd3b108a41", params: parameters, successCallback: { dialog in }, errorCallback: { error in }) ``` * SDK v1 (deprecated) ```objectivec CYBUpdateChatDialogParameters *parameters = [[CYBUpdateChatDialogParameters alloc] init]; parameters.name = @"New dialog name"; parameters.dialogDescription = @"New dialog description"; parameters.photo = @"https://new_photo_url"; // or it can be an ID to some file in Storage module [CYBRequest updateDialogWithID:@"5356c64ab35c12bd3b108a41" updateParameters:parameters successBlock:^(CYBChatDialog * _Nonnull dialog) { } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift let parameters = UpdateChatDialogParameters() parameters.name = "New dialog name" parameters.dialogDescription = "New dialog description" parameters.photo = "https://new_photo_url" // or it can be an ID to some file in Storage module Request.updateDialog(withID: "5356c64ab35c12bd3b108a41", update: parameters, successBlock: { (updatedDialog) in }) { (error) in } ``` ## Add/Remove occupants [Section titled “Add/Remove occupants”](#addremove-occupants) You can add/remove occupants in group and public dialogs: * SDK v2 ```swift let paramsToUpdate = UpdateDialogParams() paramsToUpdate.addOccupantIds = [10056, 75432] //paramsToUpdate.deleteOccupantIds = [10023] let parameters = paramsToUpdate.getUpdateDialogParams() as! [String : Any] ConnectyCube().updateDialog(dialogId: "5356c64ab35c12bd3b108a41", params: parameters, successCallback: { dialog in }, errorCallback: { error in }) ``` * SDK v1 (deprecated) ```objectivec CYBUpdateChatDialogParameters *parameters = [[CYBUpdateChatDialogParameters alloc] init]; parameters.occupantsIDsToAdd = @[@10056, @75432]; //parameters.occupantsIDsToRemove = @[@10023]; [CYBRequest updateDialogWithID:@"5356c64ab35c12bd3b108a41" updateParameters:parameters successBlock:^(CYBChatDialog *dialog) { } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift let updateParameters = UpdateChatDialogParameters() updateParameters.occupantsIDsToAdd = [10056, 75432] //updateParameters.occupantsIDsToRemove = [10023] Request.updateDialog(withID: "5356c64ab35c12bd3b108a41", update: updateParameters, successBlock: { (updatedDialog) in }) { (error) in } ``` > **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 ```swift ConnectyCube().addRemoveAdmins(dialogId: "5356c64ab35c12bd3b108a41", toAddIds: [10056, 75432], toRemoveIds: nil, successCallback: { dialog in }, errorCallback: { error in }) ``` * SDK v1 (deprecated) ```objectivec [CYBRequest addAdminsToDialogWithID:@"5356c64ab35c12bd3b108a41" adminsUserIDs:@[@10056, @75432] successBlock:^{ } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift Request.addAdminsToDialog(withID: "5356c64ab35c12bd3b108a41", adminsUserIDs: [10056, 75432], successBlock: { }) { (error) in } ``` and remove: * SDK v2 ```swift ConnectyCube().addRemoveAdmins(dialogId: "5356c64ab35c12bd3b108a41", toAddIds: nil, toRemoveIds: [75435], successCallback: { dialog in }, errorCallback: { error in }) ``` * SDK v1 (deprecated) ```objectivec [CYBRequest removeAdminsFromDialogWithID:@"5356c64ab35c12bd3b108a41" adminsUserIDs:@[@75435] successBlock:^{ } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift Request.removeAdminsFromDialog(withID: "5356c64ab35c12bd3b108a41", adminsUserIDs: [75435], successBlock: { }) { (error) in } ``` ## 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 ```swift let paramsToUpdate = UpdateDialogParams() paramsToUpdate.addPinnedMsgIds = ["5356c64ab35c12bd3b10ba32", "5356c64ab35c12bd3b10wa65"] //paramsToUpdate.deletePinnedMsgIds = ["5356c64ab35c12bd3b10ba31", "5356c64ab35c12bd3b10wa64"] let parameters = paramsToUpdate.getUpdateDialogParams() as! [String : Any] ConnectyCube().updateDialog(dialogId: "5356c64ab35c12bd3b108a41", params: parameters, successCallback: { dialog in }, errorCallback: { error in }) ``` * SDK v1 (deprecated) ```objectivec CYBUpdateChatDialogParameters *parameters = [[CYBUpdateChatDialogParameters alloc] init]; parameters.pinnedMessagesIDsToAdd = @[@"5356c64ab35c12bd3b10ba32", @"5356c64ab35c12bd3b10wa65"]; //parameters.pinnedMessagesIDsToRemove = @[@"5356c64ab35c12bd3b10ba31", @"5356c64ab35c12bd3b10wa64"]; [CYBRequest updateDialogWithID:@"5356c64ab35c12bd3b108a41" updateParameters:parameters successBlock:^(CYBChatDialog * _Nonnull dialog) { } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift let updateParameters = UpdateChatDialogParameters() updateParameters.pinnedMessagesIDsToAdd = ["5356c64ab35c12bd3b10ba32", "5356c64ab35c12bd3b10wa65"] //updateParameters.pinnedMessagesIDsToRemove = ["5356c64ab35c12bd3b10ba31", "5356c64ab35c12bd3b10wa64"] Request.updateDialog(withID: "5356c64ab35c12bd3b108a41", update: updateParameters, successBlock: { (updatedDialog) in }) { (error) in } ``` ## Remove dialog [Section titled “Remove dialog”](#remove-dialog) * SDK v2 ```swift ConnectyCube().deleteDialogs(dialogsIds: ["5356c64ab35c12bd3b108a41", "d256c64ab35c12bd3b108bc5"], force: false, successCallback: {(result) in }) { (error) in } //or ConnectyCube().deleteDialog(dialogId: "5356c64ab35c12bd3b108a41", force: false, successCallback: { }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest deleteDialogsWithIDs:[NSSet setWithArray:@[@"5356c64ab35c12bd3b108a41", @"d256c64ab35c12bd3b108bc5"]] forAllUsers:NO successBlock:^(NSArray * _Nonnull deletedObjectsIDs, NSArray * _Nonnull notFoundObjectsIDs, NSArray * _Nonnull wrongPermissionsObjectsIDs) { } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift Request.deleteDialogs(withIDs: Set(["5356c64ab35c12bd3b108a41", "d256c64ab35c12bd3b108bc5"]), forAllUsers: false, successBlock: { (deletedObjectsIDs, notFoundObjectsIDs, wrongPermissionsObjectsIDs) in }) { (error) in } ``` This request will remove this conversation for current user, but other users still will be able to chat there. The `forAllUsers` or `force` 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 ```swift let params: GetMessagesParameters = GetMessagesParameters() params.sorter = RequestSorter(fieldType: "1455098137", fieldName: "date_sent", sortType: "gt") params.limit = 20 params.skip = 0 let parameters = params.getRequestParameters() as! [String : Any] ConnectyCube().getMessages(dialogId: "5356c64ab35c12bd3b108a41", params: parameters, successCallback: { result in let messages = result.items as! [ConnectycubeMessage] }, errorCallback: { error in }) ``` * SDK v1 (deprecated) ```objectivec [CYBRequest messagesWithDialogID:@"5356c64ab35c12bd3b108a41" extendedRequest:@{@"date_sent[gt]" : @"1455098137"} paginator:[CYBPaginator limit:20 skip:0] successBlock:^(NSArray * _Nonnull messages, CYBPaginator * _Nonnull paginator) { } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift Request .messages(withDialogID: "5356c64ab35c12bd3b108a41", extendedRequest: ["date_sent[gt]":"1455098137"], paginator: Paginator.limit(20, skip: 0), successBlock: { (messages, paginator) in }) { (error) in } ``` > **Note** > > All retrieved chat messages will be marked as read after the request. If you decide not to mark chat messages as read, then add the following parameter to your extendedRequest - `@{@"mark_as_read" : @"0"};` or `markAsRead = false` ## Send/Receive chat messages [Section titled “Send/Receive chat messages”](#sendreceive-chat-messages) ### 1-1 Chat [Section titled “1-1 Chat”](#1-1-chat) * SDK v2 ```swift let message = ConnectycubeMessage() message.body = "How are you today?" message.dialogId = "5356c64ab35c12bd3b108a41" message.recipientId = 10056 ConnectyCube().chat.sendMessage(msg: message, successCallback: { }) { (error) in } //MARK: ConnectycubeMessageListener ConnectyCube().chat.addMessageListener(listener: self) extension YourClass: ConnectycubeMessageListener { func onMessage(message: ConnectycubeMessage) { } func onError(message: ConnectycubeMessage, ex: KotlinThrowable) { } } ``` * SDK v1 (deprecated) ```objectivec CYBChatMessage *message = [[CYBChatMessage alloc] init]; message.text = @"How are you today?"; CYBChatDialog *privateDialog = ...; [privateDialog sendMessage:message completionBlock:^(NSError * _Nullable error) { }]; //MARK: CYBChatDelegate - (void)chatDidReceiveMessage:(CYBChatMessage *)message { } ``` ```swift let message = ChatMessage() message.text = "How are you today?" let privateDialog = ... privateDialog.send(message) { (error) in } //MARK: CYBChatDelegate func chatDidReceive(_ message: ChatMessage) { } ``` ### Group/Public chat [Section titled “Group/Public chat”](#grouppublic-chat) Before you start chatting in a group/public conversation, you need to join it. When joined - you can send/receive messages in a real time. * SDK v2 ```swift //not supported, no need to join ``` * SDK v1 (deprecated) ```objectivec CYBChatDialog *groupDialog = ...; [groupDialog joinWithCompletionBlock:^(NSError * _Nullable error) { }]; ``` ```swift let groupDialog = ... groupDialog.join { (error) in } ``` Then you are able to send/receive messages: * SDK v2 ```swift let message = ConnectycubeMessage() message.body = "How are you today?" message.dialogId = "5356c64ab35c12bd3b108a41" message.type = ConnectycubeMessageType.groupchat ConnectyCube().chat.sendMessage(msg: message, successCallback: { }) { (error) in } //MARK: ConnectycubeMessageListener ConnectyCube().chat.addMessageListener(listener: self) extension YourClass: ConnectycubeMessageListener { func onMessage(message: ConnectycubeMessage) { } func onError(message: ConnectycubeMessage, ex: KotlinThrowable) { } } ``` * SDK v1 (deprecated) ```objectivec CYBChatMessage *message = [[CYBChatMessage alloc] init]; message.text = @"How are you today?"; CYBChatDialog *groupDialog = ...; [groupDialog sendMessage:message completionBlock:^(NSError * _Nullable error) { }]; //MARK: CYBChatDelegate - (void)chatRoomDidReceiveMessage:(CYBChatMessage *)message fromDialogID:(NSString *)dialogID { } ``` ```swift let message = ChatMessage() message.text = "How are you today?" let groupDialog = ... groupDialog.send(message) { (error) in } //MARK: ChatDelegate func chatRoomDidReceive(_ message: ChatMessage, fromDialogID dialogID: String) { } ``` When it’s done, you can leave the group conversation: * SDK v2 ```swift //not supported, no need to leave ``` * SDK v1 (deprecated) ```objectivec CYBChatDialog *groupDialog = ...; [groupDialog leaveWithCompletionBlock:^(NSError * _Nullable error) { }]; ``` ```swift let groupDialog = ... groupDialog.leave { (error) in } ``` ## 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 let 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. The `completionBlock` is used to track the status: * SDK v2 ```swift //MARK: ConnectycubeMessageSentListener ConnectyCube().chat.addMessageSentListener(listener: self) extension YourClass: ConnectycubeMessageSentListener { func onMessageSent(message: ConnectycubeMessage) { } func onMessageSentFailed(message: ConnectycubeMessage) { } } ``` * SDK v1 (deprecated) ```objectivec CYBChatDialog *chatDialog = ...; [chatDialog sendMessage:message completionBlock:^(NSError * _Nullable error) { if(!error) { //Status - 'sent' } }]; ``` ```swift chatDialog.send(message) { (error) in if error == nil { //Status - 'sent' } } ``` ## ‘Delivered’ status [Section titled “‘Delivered’ status”](#delivered-status) By default, SDK sends ‘delivered’ status automatically when the message is received by the recipient. This is controlled by `message.markable` parameter when you send a message. * SDK v2 ```swift let message = ConnectycubeMessage() message.recipientId = 10056 message.markable = true message.body = "How are you today?" ConnectyCube().chat.sendMessage(msg: message, successCallback: { }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec CYBChatMessage *message = [CYBChatMessage new]; message.markable = YES; message.text = @"How are you today?"; [chatDialog sendMessage:message completionBlock:^(NSError * _Nullable error) { }]; ``` ```swift let message = ChatMessage() message.markable = true message.text = "How are you today?" chatDialog.send(message) { (error) in } ``` If markable is false or omitted, you can send ‘delivered’ status manually via `Chat`: * SDK v2 ```swift ConnectyCube().chat.sendDeliveredStatus(msg: message, successCallback: { }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBChat.instance markAsDelivered:message completion:^(NSError * _Nullable error) { }]; ``` ```swift Chat.instance.mark(asDelivered: message) { (error) in } ``` and via `REST` * SDK v2 ```swift let updatedParams = UpdateMessageParameters() updatedParams.delivered = true ConnectyCube().updateMessage(messageId: "5b23aa4f5d0b0be0900041aa", dialogId: "5b23a9f38b518248d4fd7625", params: updatedParams.getRequestParameters(), successCallback: { }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest markMessagesAsDelivered:[NSSet setWithArray:@[@"5b23aa4f5d0b0be0900041aa"]] dialogID:@"5b23a9f38b518248d4fd7625" successBlock:^{ } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift Request.markMessages(asDelivered: Set(["5b23aa4f5d0b0be0900041aa"]), dialogID: "5b23a9f38b518248d4fd7625", successBlock: { }) { (error) in } ``` The following method of a delegate is used to track ‘delivered’ status: * SDK v2 ```swift //MARK: ConnectycubeMessageStatusListener ConnectyCube().chat.addMessageStatusListener(listener: self) extension YourClass: ConnectycubeMessageStatusListener { func onMessageDelivered(messageId: String, dialogId: String, userId: Int32) { } func onMessageRead(messageId: String, dialogId: String, userId: Int32) { } } ``` * SDK v1 (deprecated) ```objectivec ... [CYBChat.instance addDelegate:self]; ... //MARK: CYBChatDelegate - (void)chatDidDeliverMessageWithID:(NSString *)messageID dialogID:(NSString *)dialogID toUserID:(NSUInteger)userID { } ``` ```swift ... Chat.instance.addDelegate(self) ... //MARK: ChatDelegate func chatDidDeliverMessage(withID messageID: String, dialogID: String, toUserID userID: UInt) { } ``` ## ‘Read’ status [Section titled “‘Read’ status”](#read-status) Send ‘read’ status: * SDK v2 ```swift ConnectyCube().chat.sendReadStatus(msg: message, successCallback: { }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBChat.instance readMessage:message completion:^(NSError * _Nullable error) { } ``` ```swift Chat.instance.read(message) { (error) in } ``` The following method of a delegate is used to track ‘read’ status: * SDK v2 ```swift //MARK: ConnectycubeMessageStatusListener ConnectyCube().chat.addMessageStatusListener(listener: self) extension YourClass: ConnectycubeMessageStatusListener { func onMessageDelivered(messageId: String, dialogId: String, userId: Int32) { } func onMessageRead(messageId: String, dialogId: String, userId: Int32) { } } ``` * SDK v1 (deprecated) ```objectivec //MARK: CYBChatDelegate - (void)chatDidReadMessageWithID:(NSString *)messageID dialogID:(NSString *)dialogID readerID:(NSUInteger)readerID { } ``` ```swift //MARK: ChatDelegate func chatDidReadMessage(withID messageID: String, dialogID: String, readerID: UInt) { } ``` ## ‘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 controller) * 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 ‘is typing’ status: * SDK v2 ```swift ConnectyCube().chat.sendIsTypingStatus(dialog: dialog) ``` * SDK v1 (deprecated) ```objectivec [chatDialog sendUserIsTyping]; ``` ```swift chatDialog.sendUserIsTyping() ``` Send ‘stop typing’ status: * SDK v2 ```swift ConnectyCube().chat.sendStopTypingStatus(dialog: dialog) ``` * SDK v1 (deprecated) ```objectivec [chatDialog sendUserStoppedTyping]; ``` ```swift chatDialog.sendUserStopTyping() ``` The following block (closure) is used to track ‘is typing’ status: * SDK v2 ```swift //MARK: ConnectycubeChatTypingListener ConnectyCube().chat.addTypingStatusListener(listener: self) extension YourClass: ConnectycubeChatTypingListener { func onUserIsTyping(dialogId: String?, userId: Int32) { } func onUserStopTyping(dialogId: String?, userId: Int32) { } } ``` * SDK v1 (deprecated) ```objectivec chatDialog.onUserIsTyping = ^(NSUInteger userID) { }; ``` ```swift chatDialog.onUserIsTyping = { (userID: UInt) in } ``` The following block (closure) is used to track ‘stopped typing’ status: * SDK v2 ```swift //MARK: ConnectycubeChatTypingListener ConnectyCube().chat.addTypingStatusListener(listener: self) extension YourClass: ConnectycubeChatTypingListener { func onUserIsTyping(dialogId: String?, userId: Int32) { } func onUserStopTyping(dialogId: String?, userId: Int32) { } } ``` * SDK v1 (deprecated) ```objectivec chatDialog.onUserStoppedTyping = ^(NSUInteger userID) { }; ``` ```swift chatDialog.onUserStoppedTyping = { (userID: UInt) in } ``` ## Edit Message [Section titled “Edit Message”](#edit-message) The following snippet is used to edit chat message: * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec CYBChatDialog *dialog = ... [dialog editMessageWithID:@"5356c64ab35c12bd3b10wa64" text:@"New message text" last:YES completion:^(NSError * _Nullable error) { }]; //MARK: CYBChatDelegate - (void)chatDidReceiveMessage:(CYBChatMessage \*)message { //Handle edited message if (message.edited) { } } ``` ```swift let dialog = ... dialog.editMessage(withID: "5356c64ab35c12bd3b10wa64", text: "New message text", last: true) { (error) in } //MARK: CYBChatDelegate func chatDidReceive(_ message: ChatMessage) { //Handle edited message if (message.edited) { } } ``` ## Delete chat messages [Section titled “Delete chat messages”](#delete-chat-messages) The following snippet is used to remove chat message via REST: * SDK v2 ```swift ConnectyCube().deleteMessages(messagesIds: ["5b23aa4f5d0b0be0900041aa", "bc23aa4f5d0b0be0900041ad"], force: false, successCallback: {(result) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest deleteMessagesWithIDs:[NSSet setWithArray:@[@"5b23aa4f5d0b0be0900041aa", @"bc23aa4f5d0b0be0900041ad"]] forAllUsers:NO successBlock:^{ } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift Request.deleteMessages(withIDs: Set(["5b23aa4f5d0b0be0900041aa", "bc23aa4f5d0b0be0900041ad"]), forAllUsers: false, successBlock: { }) { (error) in } ``` This request will remove the messages from current user history only, without affecting the history of other users. **The forAllUsers parameter is used to completely remove messages.** The following snippet is used to remove chat message in a real time: * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec CYBChatDialog *dialog = ... [dialog removeMessageWithID:@"5356c64ab35c12bd3b10wa64" completion:^(NSError * _Nullable error) { }]; //MARK: CYBChatDelegate - (void)chatDidReceiveMessage:(CYBChatMessage \*)message { //Handle removed message if (message.removed) { } } ``` ```swift let dialog = ... dialog.removeMessage(withID: "5356c64ab35c12bd3b10wa64") { (error) in } //MARK: CYBChatDelegate func chatDidReceive(_ message: ChatMessage) { //Handle removed message if (message.removed) { } } ``` ## 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 v2 ```swift //not supported ``` * SDK v1 (deprecated) ```objectivec CYBChatMessage *message = [[CYBChatMessage alloc] init]; message.text = @"Self destroy message"; message.destroyAfterInterval = 10 // in seconds CYBChatDialog _privateDialog = ...; [privateDialog sendMessage:message completionBlock:^(NSError _ \_Nullable error) { }]; //MARK: CYBChatDelegate - (void)chatDidReceiveMessage:(CYBChatMessage \*)message { //Handle destroyAfterInterval if (message.destroyAfterInterval > 0) { // setup a timer } } ``` ```swift let message = ChatMessage() message.text = "Self destroy message" message.destroyAfterInterval = 10 // in seconds let privateDialog = ... privateDialog.send(message) { (error) in } //MARK: CYBChatDelegate func chatDidReceive(_ message: ChatMessage) { //Handle destroyAfterInterval if (message.destroyAfterInterval > 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 file UID. Then you need to include this UID into chat message and send it. * SDK v2 ```swift let imageFilePath = "" ConnectyCube().uploadFile(filePath: imageFilePath, public: false, successCallback: {(cubeFile) in // create a message let message = ConnectycubeMessage() message.saveToHistory = true message.recipientId = 10056 // attach a photo let attachment = ConnectycubeAttachment() attachment.type = "photo" attachment.id = String(cubeFile.id) message.attachments?.add(attachment) // send a chat message ConnectyCube().chat.sendMessage(msg: message) }, errorCallback: { (error) in }, progress: { (progress) in }) ``` * SDK v1 (deprecated) ```objectivec NSURL *fileUrl = [NSURL fileURLWithPath:@"file_path"]; //Local file url [CYBRequest uploadFileWithUrl:fileUrl fileName:@"image.png" contentType:@"image/png" isPublic:NO progressBlock:^(float progress) { //Update UI with upload progress } successBlock:^(CYBBlob * _Nonnull blob) { //Create attachment CYBChatAttachment *attachment = [[CYBChatAttachment alloc] init]; attachment.ID = blob.UID; attachment.type = @"image/png"; //Create message CYBChatMessage *message = [[CYBChatMessage alloc] init]; message.text = @"Image attachment"; //Set attachment message.attachments = @[attachment]; //Send message with attachment [chatDialog sendMessage:message completionBlock:^(NSError * _Nullable error) { }]; } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift let url = URL(fileURLWithPath:"file_path") Request.uploadFile(with: url, fileName: "image.png", contentType: "image/png", isPublic: true, progressBlock: { (progress) in //Update UI with upload progress }, successBlock: { (blob) in let attachment = ChatAttachment() attachment.type = "image/png" attachment.id = blob.uid //Create message let message = ChatMessage() message.text = "Image Attachment" //Set attachment message.attachments = [attachment] //Send message with attachment chatDialog.send(message, completionBlock: { (error) in }) }) { (error) in } ``` The same flow is supported on the receiver’s side. When you receive a message, you need to get the file UID and then you can build a file URL: * SDK v2 ```swift // ConnectycubeMessageListener func onMessage(message: ConnectycubeMessage) { if let attachment = message.attachments { attachment.forEach { (attachment) in if let uid = (attachment as! ConnectycubeAttachment).id { let privateAvatarUrl = ConnectycubeFileKt.getPrivateUrlForUID(uid: uid) } } } } ``` * SDK v1 (deprecated) ```objectivec - (void)chatDidReceiveMessage:(CYBChatMessage *)message { [message.attachments enumerateObjectsUsingBlock:^(CYBChatAttachment * _Nonnull attachment, NSUInteger idx, BOOL * _Nonnull stop) { NSString *fileURL = [CYBBlob privateUrlForFileUID:attachment.ID] }]; } ``` ```swift func chatDidReceive(_ message: ChatMessage) { if let attachment = message.attachments { attachment.forEach { (attachment) in if let uid = attachment.id { let fileURL = CYBBlob.privateUrlForFileUID(uid) } } } } ``` ### Contact [Section titled “Contact”](#contact) A contact profile can be send via chat attachments as well: * SDK v2 ```swift let customData = ["phone" : "180032323223", "name" : "Samuel Johnson"] if let theJSONData = try? JSONSerialization.data(withJSONObject: customData, options: .prettyPrinted) { let data = String(data: theJSONData, encoding: .utf8) let attachment = ConnectycubeAttachment() attachment.data = data //Create message let message = ConnectycubeMessage() message.body = "Contact Attachment" message.attachments = [attachment] // send a chat message // ... } ``` * SDK v1 (deprecated) ```objectivec CYBChatAttachment *attachment = [[CYBChatAttachment alloc] init]; attachment[@"phone"] = @"180032323223" attachment[@"name"] = @"Samuel Johnson" // attach a contact CYBChatMessage *message = [[CYBChatMessage alloc] init]; message.text = @"Contact attachment"; message.attachments = @[attachment]; // send a chat message // ... ``` ```swift let attachment = ChatAttachment() attachment["phone"] = "180032323223" attachment["name"] = "Samuel Johnson" //Create message let message = ChatMessage() message.text = "Contact Attachment" message.attachments = [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 ```swift // ConnectycubeMessageListener func onMessage(message: ConnectycubeMessage) { if let attachment = message.attachments { attachment.forEach { (attachment) in if let data = (attachment as! ConnectycubeAttachment).data?.data(using: .utf8) { if let contactData = try? JSONSerialization.jsonObject(with: data, options: []) as! [String : String] { let phone = contactData["phone"] let name = contactData["name"] } } } } } ``` * SDK v1 (deprecated) ```objectivec - (void)chatDidReceiveMessage:(CYBChatMessage *)message { [message.attachments enumerateObjectsUsingBlock:^(CYBChatAttachment * _Nonnull attachment, NSUInteger idx, BOOL * _Nonnull stop) { NSData *jsonData = [attachment.data dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *contactData = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:nil]; NSString *phone = contactData[@"phone"]; NSString *name = contactData[@"name"]; }]; } ``` ```swift func chatDidReceive(_ message: ChatMessage) { if let attachment = message.attachments { attachment.forEach { (attachment) in if let data = attachment.data?.data(using: .utf8) { if let contactData = try? JSONSerialization.jsonObject(with: data, options: []) as! [String : String] { let phone = contactData["phone"] let name = contactData["name"] } } } } } ``` ## 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 ```swift let messageId = "58e6a9c8a1834a3ea6001f15" let reaction = "🔥" ConnectyCube().addMessageReaction(messageId: messageId, reaction: reaction, successCallback: { }, errorCallback: {error in }) ``` Remove ```swift let messageId = "58e6a9c8a1834a3ea6001f15" let reaction = "👎" ConnectyCube().removeMessageReaction(messageId: messageId, reaction: reaction, successCallback: { }, errorCallback: {error in }) ``` Add/Remove ```swift let messageId = "58e6a9c8a1834a3ea6001f15" let reactionToAdd = "👎" let reactionToRemove = "🚀" ConnectyCube().updateMessageReaction(messageId: messageId, reactionToAdd: reactionToAdd, reactionToRemove: reactionToRemove, successCallback: { }, errorCallback: {error in }) ``` ### Listen reactions [Section titled “Listen reactions”](#listen-reactions) ```swift //MARK: ConnectycubeMessageReactionsListener ConnectyCube().chat.addMessageReactionsListener(listener: self) extension YourClass: ConnectycubeMessageReactionsListener { func onMessageReaction(reaction: ConnectycubeReaction) { // let dialogId = reaction.dialogId // let messageId = reaction.messageId // let addReaction = reaction.addReaction // let removeReaction = reaction.removeReaction } } ``` ### List message reactions [Section titled “List message reactions”](#list-message-reactions) User can list message reactions ```swift let messageId = "58e6a9c8a1834a3ea6001f15" ConnectyCube().getMessageReactions(messageId: messageId, successCallback: { reactions in // the result contains the dictionary where key is the reaction and value is the list of users' ids who reacted with this reaction }, errorCallback: {error in }) ``` 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 ```swift ConnectyCube().getUnreadMessagesCount(dialogsIds: ["8b23aa4f5d0b0be0900041aa","1c23aa4f5d0b0be0900041ad"], successCallback: {(result) in NSLog("total unread messages= " + result["8b23aa4f5d0b0be0900041aa"]!.description) NSLog("total unread messages= " + result["1c23aa4f5d0b0be0900041ad"]!.description) }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest totalUnreadMessageCountForDialogsWithIDs:[NSSet setWithArray:@[@"8b23aa4f5d0b0be0900041aa", @"1c23aa4f5d0b0be0900041ad"]] successBlock:^(NSUInteger count, NSDictionary * _Nonnull dialogs) { } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift Request.totalUnreadMessageCountForDialogs(withIDs: Set(["8b23aa4f5d0b0be0900041aa","1c23aa4f5d0b0be0900041ad"]), successBlock: { (count, dialogs) in }) { (error) in } ``` ## 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 (only v2). * SDK v2 ```swift let 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. let dialogIds = ["8b23aa4f5d0b0be0900041aa","1c23aa4f5d0b0be0900041ad"] let searchParams: GlobalSearchParams = GlobalSearchParams() //class-helper to simple config search request searchParams.dialogIds = dialogIds // 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: searchText, params: searchParams.getSearchParams() as? [String : Any], successCallback: {(result) in let dialogs = result.dialogs // found dialogs let messages = result.messages // found messages let users = result.users // found users }) { (error) in } ``` ## 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 ```swift let 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 ```swift let dialogId = "8b23aa4f5d0b0be0900041aa" let enabled = false //false - to disable push notification, true - to enable ConnectyCube().updateDialogNotificationsSettings(dialogId: dialogId, enable: enabled, successCallback: {(result) in NSLog("notification is enabled= " + result.description) }) { (error) in } ``` ### 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 ```swift ConnectyCube().getDialogNotificationsSettings(dialogId: "8b23aa4f5d0b0be0900041aa", successCallback: {(result) in NSLog("notification is enabled= " + result.description) }) { (error) in } ``` ## 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 v2 ```swift //coming soon ``` ## 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 ```swift //MARK: ConnectycubeSystemMessageListener ConnectyCube().chat.addSystemMessageListener(listener: self) extension YourClass: ConnectycubeMessageSentListener { func onMessage(message: ConnectycubeMessage) { } func onError(message: ConnectycubeMessage, ex: KotlinThrowable) { } } let message = ConnectycubeMessage() message.recipientId = 58672 message.properties["param1"] = "value1" message.properties["param2"] = "value2" message.body = "some text" ConnectyCube().chat.sendSystemMessage(msg: message) ``` ## Moderation [Section titled “Moderation”](#moderation) The moderation capabilities help maintain a safe and respectful chat environment. We have options that allow users to report inappropriate content and manage their personal block lists, giving them more control over their experience. ### Report user [Section titled “Report user”](#report-user) For user reporting to work, it requires the following: 1. Go to [ConnectyCube Daashboard](https://admin.connectycube.com/) 2. select your Application 3. Navigate to **Custom** module via left sidebar 4. Create new table called **UserReports** with the following fields: * **reportedUserId** - integer * **reason** - string ![Chat widget: report table in ConnectyCube dashboard](/images/chat_widget/chat-widget-report-table.png) Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```swift let customObject = ConnectycubeCustomObject(className: "UserReports") customObject.fields = [ "reportedUserId": 45, "reason": "User is spamming with bad words", ] ConnectyCube().createCustomObject(customObject: customObject, successCallback:{ createdObject in }, errorCallback: { error in }) ``` ### 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: ```swift let customObject = ConnectycubeCustomObject(className: "MessageReports") customObject.fields = [ "reportedMessageId": "58e6a9c8a1834a3ea6001f15", "reason": "The message contains phishing links", ] ConnectyCube().createCustomObject(customObject: customObject, successCallback:{ createdObject in }, errorCallback: { error in }) ``` ### 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 v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec NSUInteger userID = 34; CYBPrivacyItem *privateChatPrivacyItem = [[CYBPrivacyItem alloc] initWithPrivacyType:CYBPrivacyTypeUserID userID:userID allow:NO]; privateChatPrivacyItem.mutualBlock = YES; CYBPrivacyItem *groupChatPrivacyItem = [[CYBPrivacyItem alloc] initWithPrivacyType:CYBPrivacyTypeGroupUserID userID:userID allow:NO]; CYBPrivacyList *privacyList = [[CYBPrivacyList alloc] initWithName:@"PrivacyList" items:@[privateChatPrivacyItem,groupChatPrivacyItem]]; //Setting privacy list [CYBChat.instance setPrivacyList:privacyList]; ``` ```swift let userID: UInt = 34 let privateChatPrivacyItem = PrivacyItem.init(privacyType: .userID, userID: userID, allow: false) privateChatPrivacyItem.mutualBlock = true let groupChatPrivacyItem = PrivacyItem.init(privacyType: .groupUserID, userID: userID, allow: false) let privacyList = PrivacyList.init(name: "PrivacyList", items: [privateChatPrivacyItem, groupChatPrivacyItem]) //Setting privacy list Chat.instance.setPrivacyList(privacyList) ``` If the privacy list is set successfully, the `CYBChat` instance will call its delegate’s `chatDidSetPrivacyListWithName`: method: * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec //MARK: CYBChatDelegate - (void)chatDidSetPrivacyListWithName:(NSString *)name { } ``` ```swift //MARK: ChatDelegate func chatDidSetPrivacyList(withName name: String) { } ``` In case of error the `CYBChat` instance will call its delegate’s `chatDidNotSetPrivacyListWithName:error:` method: * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec //MARK: CYBChatDelegate - (void)chatDidNotSetPrivacyListWithName:(NSString *)name error:(NSError *)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: * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec [CYBChat.instance setDefaultPrivacyListWithName:@"PrivacyList"]; ``` If the privacy list is activated (set as default) successfully, the `CYBChat` instance will call its delegate’s `chatDidSetDefaultPrivacyListWithName:` method: * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec //MARK: CYBChatDelegate - (void)chatDidSetDefaultPrivacyListWithName:(NSString *)name { } ``` Otherwise the `CYBChat` instance will call its delegate’s `chatDidNotSetDefaultPrivacyListWithName:error:` method: * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec //MARK: CYBChatDelegate - (void)chatDidNotSetDefaultPrivacyListWithName:(NSString *)name error:(NSError *)error { } ``` ```swift //MARK: ChatDelegate func chatDidNotSetDefaultPrivacyList(withName name: String, error: Error) { } ``` #### 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 v2 ```swift //coming soon ``` - SDK v1 (deprecated) ```objectivec //Deactivating privacy list before update [CYBChat.instance setDefaultPrivacyListWithName:nil]; //Some updates here //.... //Activating privacy list [CYBChat.instance setDefaultPrivacyListWithName:@"PrivacyList"]; ``` ```swift //Deactivating privacy list before update Chat.instance.setDefaultPrivacyListWithName(nil) //Some updates here //.... //Activating privacy list Chat.instance.setDefaultPrivacyListWithName("PrivacyList") ``` #### 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: * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec [CYBChat.instance retrievePrivacyListNames]; ``` ```swift Chat.instance.retrievePrivacyListNames() ``` If the privacy list names are retrieved successfully, the `CYBChat` instance will call its delegate’s `didReceivePrivacyListNames:` method: * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec - (void)chatDidReceivePrivacyListNames:(NSArray *)listNames { } ``` ```swift func chatDidReceivePrivacyListNames(_ listNames: [String]) { } ``` Otherwise the `CYBChat` instance will call its delegate’s `didNotReceivePrivacyListNamesDueToError:` method: * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec - (void)chatDidNotReceivePrivacyListNamesDueToError:(NSError *)error { } ``` ```swift func chatDidNotReceivePrivacyListNamesDue(toError error: Error) { } ``` #### 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 v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectiec [CYBChat.instance retrievePrivacyListWithName:@"PrivacyList"]; ``` ```swift Chat.instance.retrievePrivacyList(withName: "PrivacyList") ``` If the privacy list is retrieved successfully, the `CYBChat` instance will call its delegate’s `chatDidReceivePrivacyList:` method: * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec - (void)chatDidReceivePrivacyList:(CYBPrivacyList *)privacyList { } ``` ```swift func chatDidReceive(_ privacyList: PrivacyList) { } ``` Otherwise the `CYBChat` instance will call its delegate’s `chatDidNotReceivePrivacyListWithName:error:` method * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec - (void)chatDidNotReceivePrivacyListWithName:(NSString *)name error:(NSError *)error { } ``` ```swift func chatDidNotReceivePrivacyList(withName name: String, error: Error) { } ``` #### 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`. * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec [CYBChat.instance removePrivacyListWithName:@"PrivacyList"]; ``` ```swift Chat.instance.removePrivacyList(withName: "PrivacyList") ``` If the privacy list is removed successfully, the `CYBChat` instance will call its delegate’s `chatDidRemovedPrivacyListWithName:` method * SDK v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objectivec - (void)chatDidRemovedPrivacyListWithName:(NSString *)name { } ``` ```swift func chatDidRemovedPrivacyList(withName name: String) { } ``` #### 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 v2 ```swift //coming soon ``` * SDK v1 (deprecated) ```objective-c chatDialog.onBlockedMessage = ^(NSError * _Nullable error) { }; ``` ```swift chatDialog.onBlockedMessage = { (error) in } ``` ## 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 ```swift //coming soon ``` # Video Calling > Empower your iOS 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. ## Installation [Section titled “Installation”](#installation) ### Installation with CocoaPods [Section titled “Installation with CocoaPods”](#installation-with-cocoapods) [CocoaPods](https://cocoapods.org/pods/ConnectyCubeCalls) is a dependency manager for Objective-C and Swift, which automates and simplifies the process of using 3rd-party frameworks or libraries like ConnectyCubeCalls in your projects. You can follow their [getting started guide](https://guides.cocoapods.org/using/getting-started.html) if you don’t have CocoaPods installed. Copy and paste the following lines into your podfile: * SDK v2 ```bash pod 'ConnectyCube' ``` * SDK v1 (deprecated) ```bash pod 'ConnectyCube' pod 'ConnectyCubeCalls' ``` Now you can install the dependencies in your project: ```bash $ pod install ... ``` From now on, be sure to always open the generated Xcode workspace (.xcworkspace) instead of the project file when building your project. ### Importing framework [Section titled “Importing framework”](#importing-framework) At this point, everything is ready for you to start using ConnectyCube and ConnectyCubeCalls frameworks. Just import the frameworks wherever you need to use them: * SDK v2 ```swift import ConnectyCube ``` * SDK v1 (deprecated) ```objectivec #import #import ``` ```swift import ConnectyCube import ConnectyCubeCalls ``` ### Run script phase for archiving [Section titled “Run script phase for archiving”](#run-script-phase-for-archiving) Add a “Run Script Phase” to build phases of your project. Paste the following snippet into the script: ```bash bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/ConnectyCubeCalls.framework/strip-framework.sh" ``` This fixes the [known Apple bug](http://www.openradar.me/radar?id=6409498411401216), that does not allow to publish archives to the App store with dynamic frameworks that contain simulator platforms. Script is designed to work only for archiving. ### Swift namespacing [Section titled “Swift namespacing”](#swift-namespacing) ConnectyCubeCalls framework supports simple swift names, which means that instead of, for example, `CYBCallSession` you can just type `CallSession`. Sometimes it might get you an error: `__someclass__ is ambiguous for type lookup in this context`, which means that there is another class with the same swift name, and they are conflicting (swift does not understand what class do you want in the current context to be): ![Swift ambiguous name](/_astro/ios_videocalling_swift_ambiguous_name.fa_IQ-nr_2jC8uw.webp) In this case you must specify a namespace of that specific class with `.`, which has the same name as framework and will be: * SDK v2 ```swift ConnectyCube.P2PSession ``` * SDK v1 (deprecated) ```swift ConnectyCubeCalls.CallSession ``` ## Preparations [Section titled “Preparations”](#preparations) [ConnectyCube Chat API](/ios/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](/ios/messaging#connect-to-chat). ## Initialization [Section titled “Initialization”](#initialization) Before any interaction with ConnectyCubeCalls you need to initialize it once using the method below: * SDK v2 ```swift ConnectyCube().p2pCalls.register() ``` * SDK v1 (deprecated) ```objectivec [CYBCallClient initializeRTC]; ``` ```swift CallClient.initializeRTC() ``` ### Logging [Section titled “Logging”](#logging) Logging is a powerful tool to see the exact flow of the ConnectyCubeCalls framework and analyze its decisions. By enabling logs you will be able to debug most issues, or perhaps help us analyze your problems. Basic logs are enabled by default. To enable verbose logs use the method below: * SDK v2 ```swift ConnectycubeSettings().isDebugEnabled = true ``` * SDK v1 (deprecated) ```objectivec [CYBCallConfig setLogLevel:CYBCallLogLevelVerboseWithWebRTC]; ``` ```swift CallConfig.setLogLevel(.verboseWithWebRTC) ``` Here are all possible log levels to use: * `CYBCallLogLevelNothing`: turns off all logging * `CYBCallLogLevelVerbose`: basic logs from our framework (enabled by default) * `CYBCallLogLevelVerboseWithWebRTC`: verbose logs from our framework including all internal webrtc logging (might be helpful to debug some complicated problems with calls) To get more info about active call you can also [enable stats reporting](#webrtc-stats-reporting). ### Background mode [Section titled “Background mode”](#background-mode) You can use our SDK in the background mode as well, however this requires you to add a specific app permissions. Under the app build settings, open the Capabilities tab. In this tab, turn on Background Modes and set the Audio, AirPlay and Picture in Picture checkbox to set the audio background mode. ![Background modes](/_astro/ios_videocalling_backgtound_modes.BAsMDJpV_pQ06r.webp) If everything is correctly configured, iOS provides an indicator that your app is running in the background with an active audio session. This is seen as a red background of the status bar, as well as an additional bar indicating the name of the app holding the active audio session — in this case, your app. ## Client delegate [Section titled “Client delegate”](#client-delegate) In order to operate and receive calls you need to setup client delegate. Your class must conform to `RTCCallSessionCallback` (`CYBCallClientDelegate` v1 deprecated) protocol. Use the method below to subscribe: * SDK v2 ```swift ConnectyCube().p2pCalls.addSessionCallbacksListener(callback: self) ``` * SDK v1 (deprecated) ```objectivec [[CYBCallClient instance] addDelegate:self]; ``` ```swift CallClient.instance().add(self) ``` ## Initiate a call [Section titled “Initiate a call”](#initiate-a-call) * SDK v2 ```swift let opponentsIds = [3245, 2123, 3122].map{KotlinInt(value: $0)} let newSession = ConnectyCube().p2pCalls.createSession(userIds: opponentsIds, callType: CallType.video) // userInfo - the custom user information dictionary for the call. May be nil. let userInfo = ["key":"value"] as? KotlinMutableDictionary // optional newSession.startCall(userInfo: userInfo) ``` * SDK v1 (deprecated) In order to perform a call, use `CYBCallClient` and `CYBCallSession` methods below: ```objectivec // 2123, 2123, 3122 - opponent's NSArray *opponentsIDs = @[@3245, @2123, @3122]; CYBCallSession *newSession = [[CYBCallClient instance] createNewSessionWithOpponents:opponentsIDs withConferenceType:CYBCallConferenceTypeVideo]; // userInfo - the custom user information dictionary for the call. May be nil. NSDictionary *userInfo = @{ @"key" : @"value" }; // optional [newSession startCall:userInfo]; ``` ```swift // 2123, 2123, 3122 - opponent's let opponentsIDs = [3245, 2123, 3122] let newSession = CallClient.instance().createNewSession(withOpponents: ids! as [NSNumber], with: .video) // userInfo - the custom user information dictionary for the call. May be nil. let userInfo = ["key":"value"] // optional newSession.startCall(userInfo) ``` After this your opponents will receive one call request per 5 second for a duration of 45 seconds (you can configure these settings with `WebRTCConfig` (`CYBCallConfig` v1 deprecated): * SDK v2 ```swift //MARK: RTCCallSessionCallback ConnectyCube().p2pCalls.addSessionCallbacksListener(callback: self) extension YourClass: RTCCallSessionCallback { func onReceiveNewSession(session: P2PSession) { if self.session != nil { // we already have a video/audio call session, so we reject another one // userInfo - the custom user information dictionary for the call from caller. May be nil. let userInfo = ["key":"value"] as? KotlinMutableDictionary // optional session.rejectCall(userInfo: userInfo) return } // saving session instance here self.session = session } ... } ``` * SDK v1 (deprecated) ```objectivec - (void)didReceiveNewSession:(CYBCallSession *)session userInfo:(NSDictionary *)userInfo { if (self.session) { // we already have a video/audio call session, so we reject another one // userInfo - the custom user information dictionary for the call from caller. May be nil. NSDictionary *userInfo = @{ @"key" : @"value" }; // optional [session rejectCall:userInfo]; return; } // saving session instance here self.session = session; } ``` ```swift func didReceiveNewSession(_ session: CallSession, userInfo: [String : String]? = nil) { if self.session != nil { // we already have a video/audio call session, so we reject another one // userInfo - the custom user information dictionary for the call from caller. May be nil. let userInfo = ["key":"value"] // optional session.rejectCall(userInfo) return } // saving session instance here self.session = session } ``` `self.session` refers to the current session. Each particular audio - video call has a unique `sessionID`. This allows you to have more than one independent audio-video conferences. If you want to increase the call timeout, e.g. set to 60 seconds: * SDK v2 ```swift WebRTCConfig().answerTimeInterval = 60 ``` * SDK v1 (deprecated) ```objectivec [CYBCallConfig setAnswerTimeInterval:60]; ``` ```swift CallConfig.setAnswerTimeInterval(60) ``` > Default value is 60 seconds. In case opponent did not respond to your call within a specific timeout time, the callback listed below will be called: * SDK v2 ```swift //MARK: RTCCallSessionCallback ConnectyCube().p2pCalls.addSessionCallbacksListener(callback: self) func onUserNotAnswer(session: P2PSession, opponentId: Int32) { } ``` * SDK v1 (deprecated) ```objectivec - (void)session:(CYBCallSession *)session userDidNotRespond:(NSNumber *)userID { } ``` ```swift func session(_ session: CallSession, userDidNotRespond userID: NSNumber) { } ``` ## Accept a call [Section titled “Accept a call”](#accept-a-call) In order to accept a call, use the `P2PSession` method below: * SDK v2 ```swift // userInfo - the custom user information dictionary for the accept call. May be nil. let userInfo: KotlinMutableDictionary = ["key":"value"] // optional self.session?.acceptCall(userInfo: userInfo) ``` * SDK v1 (deprecated) In order to accept a call, use the `CYBCallSession` method below: ```objectivec // userInfo - the custom user information dictionary for the accept call. May be nil. NSDictionary *userInfo = @{ @"key" : @"value" }; // optional [self.session acceptCall:userInfo]; ``` ```swift // userInfo - the custom user information dictionary for the accept call. May be nil. let userInfo = ["key":"value"] // optional self.session?.acceptCall(userInfo) } ``` After this your opponent will receive an **accept** signal: * SDK v2 ```swift //MARK: RTCCallSessionCallback ConnectyCube().p2pCalls.addSessionCallbacksListener(callback: self) func onCallAcceptByUser(session: P2PSession, opponentId: Int32, userInfo: [String : Any]?) { } ``` * SDK v1 (deprecated) ```objectivec - (void)session:(CYBCallSession *)session acceptedByUser:(NSNumber *)userID userInfo:(NSDictionary *)userInfo { } ``` ```swift func session(_ session: CallSession, acceptedByUser userID: NSNumber, userInfo: [String : String]? = nil) { } ``` ## Reject a call [Section titled “Reject a call”](#reject-a-call) * SDK v2 In order to reject a call, use the `P2PSession` method below: ```swift // userInfo - the custom user information dictionary for the reject call. May be nil. let userInfo: KotlinMutableDictionary = ["key":"value"] // optional self.session?.rejectCall(userInfo: userInfo) // and release session instance self.session = nil ``` * SDK v1 (deprecated) In order to reject a call, use the `CYBCallSession` method below: ```objectivec // userInfo - the custom user information dictionary for the reject call. May be nil. NSDictionary *userInfo = @{ @"key" : @"value" }; // optional [self.session rejectCall:userInfo]; // and release session instance self.session = nil; ``` ```swift // userInfo - the custom user information dictionary for the reject call. May be nil. let userInfo = ["key":"value"] // optional self.session?.rejectCall(userInfo) // and release session instance self.session = nil ``` After this your opponent will receive a **reject** signal: * SDK v2 ```swift //MARK: RTCCallSessionCallback ConnectyCube().p2pCalls.addSessionCallbacksListener(callback: self) func onCallRejectByUser(session: P2PSession, opponentId: Int32, userInfo: [String : Any]?) { } ``` * SDK v1 (deprecated) ```objectivec - (void)session:(CYBCallSession *)session rejectedByUser:(NSNumber *)userID userInfo:(NSDictionary *)userInfo { NSLog(@"Rejected by user %@", userID); } ``` ```swift func session(_ session: CallSession, rejectedByUser userID: NSNumber, userInfo: [String : String]? = nil) { print("Rejected by user \(userID)") } ``` ## End a call [Section titled “End a call”](#end-a-call) * SDK v2 In order to end a call, use the `P2PSession` method below: ```swift // userInfo - the custom user information dictionary for the reject call. May be nil. let userInfo: KotlinMutableDictionary = ["key":"value"] // optional self.session?.hangUp(userInfo: userInfo) // and release session instance self.session = nil ``` * SDK v1 (deprecated) In order to end a call, use the `CYBCallSession` method below: ```objectivec // userInfo - the custom user information dictionary for the reject call. May be nil. NSDictionary *userInfo = @{ @"key" : @"value" }; // optional [self.session hangUp:userInfo]; // and release session instance self.session = nil; ``` ```swift // userInfo - the custom user information dictionary for the reject call. May be nil. let userInfo = ["key":"value"] // optional self.session?.hangUp(userInfo) // and release session instance self.session = nil ``` After this your opponent will receive a **hangup** signal: * SDK v2 ```swift //MARK: RTCCallSessionCallback ConnectyCube().p2pCalls.addSessionCallbacksListener(callback: self) func onReceiveHangUpFromUser(session: P2PSession, opponentId: Int32, userInfo: [String : Any]?) { } ``` * SDK v1 (deprecated) ```objectivec - (void)session:(CYBCallSession *)session hungUpByUser:(NSNumber *)userID userInfo:(NSDictionary *)userInfo { } ``` ```swift func session(_ session: CallSession, hungUpByUser userID: NSNumber, userInfo: [String : String]? = nil) { } ``` ## Connection life cycle [Section titled “Connection life cycle”](#connection-life-cycle) All starts when you have received new session and accepted the call. * SDK v2 ```swift //not supported ``` * SDK v1 (deprecated) The first thing you will get is `startedConnectingToUser` callback: ```objectivec - (void)session:(__kindof CYBCallBaseSession *)session startedConnectingToUser:(NSNumber *)userID { } ``` ```swift func session(_ session: CallBaseSession, startedConnectingToUser userID: NSNumber) { } ``` After that webrtc will perform all operations that needed to connect both users internally, and you will either get `onConnectedToUser `or `onDisconnectedFromUser `if connection failed to connect for some reason: * SDK v2 ```swift //MARK: RTCSessionStateCallback ConnectyCube().p2pCalls.addSessionStateCallbacksListener(callback: self) func onConnectedToUser(session: BaseSession, userId: Int32) { } func onDisconnectedFromUser(session: BaseSession, userId: Int32) { } ``` * SDK v1 (deprecated) After that webrtc will perform all operations that needed to connect both users internally, and you will either get `connectedToUser` or `connectionFailedForUser` (you will also receive this callback before `connectionClosedForUser` if connection failed during active call) if connection failed to connect for some reason: ```objectivec - (void)session:(__kindof CYBCallBaseSession *)session connectedToUser:(NSNumber *)userID { } - (void)session:(__kindof CYBCallBaseSession *)session connectionFailedForUser:(NSNumber *)userID { } ``` ```swift func session(_ session: CallBaseSession, connectedToUser userID: NSNumber) { } func session(_ session: CallBaseSession, connectionFailedForUser userID: NSNumber) { } ``` - SDK v2 When you or your opponent close the call, you will receive `onDisconnectedFromUser` callback first, and then `onConnectionClosedForUser` when connection is fully closed: ```swift //MARK: RTCSessionStateCallback ConnectyCube().p2pCalls.addSessionStateCallbacksListener(callback: self) func onDisconnectedFromUser(session: BaseSession, userId: Int32) { } func onConnectionClosedForUser(session: BaseSession, userId: Int32) { } ``` - SDK v1 (deprecated) When you or your opponent close the call, you will receive `disconnectedFromUser` callback first, and then `connectionClosedForUser` when connection is fully closed: ```objectivec - (void)session:(__kindof CYBCallBaseSession *)session disconnectedFromUser:(NSNumber *)userID { } - (void)session:(__kindof CYBCallBaseSession *)session connectionClosedForUser:(NSNumber *)userID { } ``` ```swift func session(_ session: CallBaseSession, disconnectedFromUser userID: NSNumber) { } func session(_ session: CallBaseSession, connectionClosedForUser userID: NSNumber) { } ``` ## Session states [Section titled “Session states”](#session-states) * SDK v2 Each session has its own state. You can always access current state by simply calling the `P2PSession` property: ```swift let sessionState = self.session?.state ``` * SDK v1 (deprecated) Each session has its own state. You can always access current state by simply calling the `CYBCallSession` property: ```objectivec CYBCallSessionState sessionState = self.session.state; ``` ```swift let sessionState = self.session.state ``` You can also receive a live time callbacks on session changing its own state: * SDK v2 ```swift //MARK: RTCSessionStateCallback ConnectyCube().p2pCalls.addSessionStateCallbacksListener(callback: self) func onStateChanged(session: BaseSession, state: BaseSessionRTCSessionState) { } ``` Here are all possible states that can occur: * `RTC_SESSION_NEW`: session was successfully created and ready for the next step * `RTC_SESSION_PENDING`: session is in pending state for other actions to occur * `RTC_SESSION_CONNECTING`: session is in progress of establishing connection * `RTC_SESSION_CONNECTED`: session was successfully established * `RTC_SESSION_GOING_TO_CLOSE`: session is going to close * `RTC_SESSION_CLOSED`: session was closed * SDK v1 (deprecated) ```objectivec - (void)session:(CYBCallSession *)session didChangeState:(CYBCallSessionState)state { } ``` ```swift func session(_ session: CallBaseSession, didChange state: CallSessionState) { } ``` Here are all possible states that can occur: * `CYBCallSessionStateNew`: session was successfully created and ready for the next step * `CYBCallSessionStatePending`: session is in pending state for other actions to occur * `CYBCallSessionStateConnecting`: session is in progress of establishing connection * `CYBCallSessionStateConnected`: session was successfully established * `CYBCallSessionStateClosed`: session was closed ## Monitor connection state [Section titled “Monitor connection state”](#monitor-connection-state) * SDK v2 Use session state to know connection state. You can always access current state by simply calling the `P2PSession` property: ```swift let sessionState = self.session?.state ``` * SDK v1 (deprecated) Each user connection has its own state. By default you can access that state by calling this method from `CYBCallSession`: ```objectivec NSNumber *userID = @(20450); // user with ID 20450 CYBCallConnectionState connectionState = [self.session connectionStateForUser:userID]; ``` ```swift let userID = 20450 as NSNumber // user with ID 20450 let connectionState = self.session.connectionState(forUser: userID) ``` There is also a callback about connection state being changed in the live time: ```objectivec - (void)session:(CYBCallSession *)session didChangeConnectionState:(CYBCallConnectionState)state forUser:(NSNumber *)userID { } ``` ```swift func session(_ session: CallSession, didChange state: CallConnectionState, forUser userID: NSNumber) { } ``` Here are all possible connection states that can occur: * `CYBCallConnectionUnknown`: connection state is unknown; this can occur when none of the other states are fit for the current situation * `CYBCallConnectionNew`: connection was created and ready for the next step * `CYBCallConnectionPending`: connection is in pending state for other actions to occur * `CYBCallConnectionConnecting`: one or more of the ICE transports are currently in the process of establishing a connection * `CYBCallConnectionChecking`: the ICE agent has been given one or more remote candidates and is checking pairs of local and remote candidates against one another to try to find a compatible match, but has not yet found a pair which will allow the peer connection to be made; it’s possible that gathering of candidates is also still underway * `CYBCallConnectionConnected`: connection was performed successfully * `CYBCallConnectionDisconnected`: disconnected, but not closed; can still be reconnected * `CYBCallConnectionClosed`: connection was closed * `CYBCallConnectionCount`: ICE connection reached max numbers * `CYBCallConnectionStateDisconnectTimeout`: connection was disconnected by timeout * `CYBCallConnectionStateNoAnswer`: connection did not receive answer from the opponent user * `CYBCallConnectionStateRejected`: connection was rejected by the opponent user * `CYBCallConnectionStateHangUp`: connection was hanged up by the opponent user * `CYBCallConnectionStateFailed`: one or more of the ICE transports on the connection is in the failed state; this can occur on the different circumstances, e.g. bad network etc. ## 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). ```swift code snippet with ice restart (coming soon) ``` ## Show local video [Section titled “Show local video”](#show-local-video) In order to show your local video track from camera you should create `UIView` on storyboard and then use the following code: * SDK v2 ```swift // your view controller interface code //MARK: VideoTracksCallback ConnectyCube().p2pCalls.addVideoTrackCallbacksListener(callback: self) var localVideo = RTCMTLVideoView() override func viewDidLoad() { setupVideo(localVideo) //... } private func setupVideo(\_ videoView: RTCMTLVideoView) { insertSubview(videoView, at: 0) videoView.translatesAutoresizingMaskIntoConstraints = false videoView.videoContentMode = .scaleAspectFill //... } func onLocalVideoTrackReceive(session: BaseSession, videoTrack: ConnectycubeVideoTrack) { videoTrack.addSink(videoSink: VideoSink(renderer: localVideo)) } ``` * SDK v1 (deprecated) In order to show your local video track from camera you should create `UIView` on storyboard and then use the following code: ```objectivec // your view controller interface code @interface CallController() @property (weak, nonatomic) IBOutlet UIView *localVideoView; // your video view to render local camera video stream @property (strong, nonatomic) CYBCallCameraCapture *videoCapture; @property (strong, nonatomic) CYBCallSession *session; @end @implementation CallController - (void)viewDidLoad { [super viewDidLoad]; [[CYBCallClient instance] addDelegate:self]; CYBCallVideoFormat *videoFormat = [[CYBCallVideoFormat alloc] init]; videoFormat.frameRate = 30; videoFormat.pixelFormat = CYBCallPixelFormat420f; videoFormat.width = 640; videoFormat.height = 480; // CYBCallCameraCapture class used to capture frames using AVFoundation APIs self.videoCapture = [[CYBCallCameraCapture alloc] initWithVideoFormat:videoFormat position:AVCaptureDevicePositionFront]; // or AVCaptureDevicePositionBack // add video capture to session's local media stream self.session.localMediaStream.videoTrack.videoCapture = self.videoCapture; self.videoCapture.previewLayer.frame = self.localVideoView.bounds; [self.videoCapture startSession]; [self.localVideoView.layer insertSublayer:self.videoCapture.previewLayer atIndex:0]; // start call } // ... @end ``` ```swift // your view controller interface code import Foundation class CallController: UIViewController, CallClientDelegate { @IBOutlet weak var localVideoView : UIView! // your video view to render local camera video stream var videoCapture: CallCameraCapture? var session: CallSession? override func viewDidLoad() { CallClient.instance().addDelegate(self) let videoFormat = VideoFormat.init() videoFormat.frameRate = 30 videoFormat.pixelFormat = .format420f videoFormat.width = 640 videoFormat.height = 480 // CYBCallCameraCapture class used to capture frames using AVFoundation APIs self.videoCapture = CallCameraCapture.init(videoFormat: videoFormat, position: .front) // add video capture to session's local media stream self.session?.localMediaStream.videoTrack.videoCapture = self.videoCapture self.videoCapture?.previewLayer.frame = self.localVideoView.bounds self.videoCapture?.startSession() self.localVideoView.layer.insertSublayer(self.videoCapture!.previewLayer, atIndex: 0) // start call } //... } ``` ## Show remote video [Section titled “Show remote video”](#show-remote-video) * SDK v2 ```swift // your view controller interface code //MARK: VideoTracksCallback ConnectyCube().p2pCalls.addVideoTrackCallbacksListener(callback: self) var remoteVideo = RTCMTLVideoView() private func setupVideo(\_ videoView: RTCMTLVideoView) { insertSubview(videoView, at: 0) videoView.translatesAutoresizingMaskIntoConstraints = false videoView.videoContentMode = .scaleAspectFill //... } func onRemoteVideoTrackReceive(session: BaseSession, videoTrack: ConnectycubeVideoTrack, userId: Int32) { { setupVideo(remoteVideo) videoTrack.addSink(videoSink: VideoSink(renderer: remoteVideo)) } ``` * SDK v1 (deprecated) In order to show video views with streams which you have received from your opponents you should create `CYBCallRemoteVideoView` views on storyboard and then use the following code: ```objectivec - (void)session:(CYBCallSession *)session receivedRemoteVideoTrack:(CYBCallVideoTrack *)videoTrack fromUser:(NSNumber *)userID { // we suppose you have created UIView and set it's class to CYBCallRemoteVideoView class // also we suggest you to set view mode to UIViewContentModeScaleAspectFit or // UIViewContentModeScaleAspectFill [self.opponentVideoView setVideoTrack:videoTrack]; } ``` ```swift func session(_ session: CallBaseSession, receivedRemoteVideoTrack videoTrack: CallVideoTrack, fromUser userID: NSNumber) { // we suppose you have created UIView and set it's class to RemoteVideoView class // also we suggest you to set view mode to UIViewContentModeScaleAspectFit or // UIViewContentModeScaleAspectFill self.opponentVideoView.setVideoTrack(videoTrack) } ``` - SDK v2 You can always get remote video tracks for a specific user ID in the call using these `P2PSession` methods (assuming that they are existent): ```swift let remoteVideoTrack = session?.mediaStreamManager?.videoTracks[24450] as! ConnectycubeVideoTrack // video track for user 24450 ``` - SDK v1 (deprecated) You can always get remote video tracks for a specific user ID in the call using these `CYBCallSession` methods (assuming that they are existent): ```objectivec CYBCallVideoTrack *remoteVideoTrack = [self.session remoteVideoTrackWithUserID:@(24450)]; // video track for user 24450 ``` ```swift let remoteVideoTrack = self.session?.remoteVideoTrack(withUserID: 24450) // video track for user 24450 ``` ## Mute audio [Section titled “Mute audio”](#mute-audio) You can disable/enable audio during a call: * SDK v2 ```swift self.session?.mediaStreamManager?.localAudioTrack?.enabled = !(self.session?.mediaStreamManager?.localAudioTrack!.enabled)! ``` * SDK v1 (deprecated) ```objectivec self.session.localMediaStream.audioTrack.enabled ^= 1; ``` ```swift self.session?.localMediaStream.audioTrack.enabled = !self.session?.localMediaStream.audioTrack.enabled ``` ## Mute remote audio [Section titled “Mute remote audio”](#mute-remote-audio) * SDK v2 You can always get remote audio tracks for a specific user ID in the call using these `P2PSession` methods (assuming that they are existent): ```swift let remoteAudioTrack = self.session?.mediaStreamManager?.audioTracks[24450] as! ConnectycubeAudioTrack// audio track for user 24450 ``` * SDK v1 (deprecated) You can always get remote audio tracks for a specific user ID in the call using these `CYBCallSession` methods (assuming that they are existent): ```objectivec CYBCallAudioTrack *remoteAudioTrack = [self.session remoteAudioTrackWithUserID:@(24450)]; // audio track for user 24450 ``` ```swift let remoteAudioTrack = self.session?.remoteAudioTrack(withUserID: 24450) // audio track for user 24450 ``` You can also mute remote media tracks on your side, by changing value of **enabled** property for a specific remote media track: * SDK v2 ```swift remoteAudioTrack.enabled = false ``` * SDK v1 (deprecated) ```objectivec remoteAudioTrack.enabled = NO; ``` ```swift remoteAudioTrack.enabled = false ``` `CYBCallAudioTrack` class (that represents remote audio track for a specific user) supports audio data sink through `CYBCallAudioTrackSinkInterface` protocol. In order to access audio data in real time, simply subscribe to sink interface using methods: ```objectivec /** * Add sink. * * @param sink class instance that conforms to CYBCallAudioTrackSinkInterface protocol * * @see CYBCallAudioTrackSinkInterface */ - (void)addSink:(id)sink; /** * Remove sink. * * @param sink class instance that conforms to CYBCallAudioTrackSinkInterface protocol * * @see CYBCallAudioTrackSinkInterface */ - (void)removeSink:(id)sink; ``` Now handle protocol method to access audio data: ```objectivec - (void)audioTrack:(CYBCallAudioTrack *)audioTrack didSinkAudioBufferList:(const AudioBufferList *)audioBufferList audioStreamDescription:(const AudioStreamBasicDescription)audioStreamDescription numberOfFrames:(size_t)numberOfFrames time:(CMTime)time { } ``` ```swift func audioTrack(_ audioTrack: CallAudioTrack, didSinkAudioBufferList audioBufferList: UnsafePointer, audioStreamDescription: AudioStreamBasicDescription, numberOfFrames: Int, time: CMTime) { } ``` > ***Note*** > > This interface provides AudioBufferList with audio data, AudioStreamBasicDescription description of audio data, a number of frames in current packet, and current media time that conforms to each packet. ## Mute video [Section titled “Mute video”](#mute-video) You can disable/enable video during a call: * SDK v2 ```swift self.session?.mediaStreamManager?.localVideoTrack?.enabled = !(self.session?.mediaStreamManager?.localVideoTrack!.enabled)! ``` * SDK v1 (deprecated) ```objectivec self.session.localMediaStream.videoTrack.enabled ^= 1; ``` ```swift self.session?.localMediaStream.videoTrack.enabled = !self.session?.localMediaStream.videoTrack.enabled ``` > ***Note*** > > Due to [webrtc restrictions](https://www.w3.org/TR/webrtc/) black frames will be placed into stream content if video is disabled. ## Switch camera [Section titled “Switch camera”](#switch-camera) You can switch the video capture position during a call (default: front camera): * SDK v2 ```swift self.session?.mediaStreamManager?.videoCapturer?.switchCamera() ``` * SDK v1 (deprecated) ```objectivec // to change some time after, for example, at the moment of call AVCaptureDevicePosition position = self.videoCapture.position; AVCaptureDevicePosition newPosition = position == AVCaptureDevicePositionBack ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack; // check whether videoCapture has or has not camera position // for example, some iPods do not have front camera if ([self.videoCapture hasCameraForPosition:newPosition]) { self.videoCapture.position = newPosition; } ``` ```swift // to change some time after, for example, at the moment of call let position = self.videoCapture?.position let newPosition = position == AVCaptureDevice.Position.Front ? AVCaptureDevice.Position.Back : AVCaptureDevice.Position.Front // check whether videoCapture has or has not camera position // for example, some iPods do not have front camera if self.videoCapture?.hasCameraForPosition(newPosition) { self.videoCapture?.position = newPosition } ``` ## Audio session management [Section titled “Audio session management”](#audio-session-management) * SDK v2 ```swift //not supported ``` * SDK v1 (deprecated) ConnectyCubeCalls has its own audio session management which you need to use. It’s located in `CYBCallAudioSession` class. This class represented as singleton and you can always access shared session by calling `instance` method: ```objectivec CYBCallAudioSession *audioSession = [CYBCallAudioSession instance]; ``` ```swift let audioSession = CallAudioSession.instance() ``` See `CYBCallAudioSession` class header for more information. ### Initialization and deinitialization [Section titled “Initialization and deinitialization”](#initialization-and-deinitialization) * SDK v2 ```swift //not supported ``` * SDK v1 (deprecated) You must initialize audio session before every call: ```objectivec [[CYBCallAudioSession instance] initializeWithConfigurationBlock:^(CYBCallAudioSessionConfiguration *configuration) { // adding blutetooth support configuration.categoryOptions |= AVAudioSessionCategoryOptionAllowBluetooth; configuration.categoryOptions |= AVAudioSessionCategoryOptionAllowBluetoothA2DP; // adding airplay support configuration.categoryOptions |= AVAudioSessionCategoryOptionAllowAirPlay; configuration.mode = AVAudioSessionModeVideoChat; // setting mode to video chat to enable airplay audio and speaker only for video call }]; ``` ```swift CallAudioSession.instance().initialize { (configuration: CallAudioSessionConfiguration) -> () in var options = configuration.categoryOptions // adding blutetooth support options = options.union(AVAudioSessionCategoryOptions.allowBluetoothA2DP) // adding airplay support options = options.union(AVAudioSessionCategoryOptions.allowAirPlay) configuration.categoryOptions = options configuration.mode = AVAudioSessionModeVideoChat // setting mode to video chat to enable airplay audio and speaker only for video call } ``` And deinitialize it after the call ends: ```objectivec [[CYBCallAudioSession instance] deinitialize]; ``` ```swift CallAudioSession.instance().deinitialize() ``` ### Audio output [Section titled “Audio output”](#audio-output) * SDK v2 ```swift func switchSpeaker(_ sender: UIButton) { let isCurrentSpeaker: Bool = !AVAudioSession.sharedInstance().currentRoute.outputs.filter{$0.portType == AVAudioSession.Port.builtInSpeaker }.isEmpty let port = isCurrentSpeaker ? AVAudioSession.PortOverride.none: AVAudioSession.PortOverride.speaker do { try AVAudioSession.sharedInstance().overrideOutputAudioPort(port) } catch let error as NSError { print("audioSession error: \(error.localizedDescription)") } } ``` * SDK v1 (deprecated) You can output audio either from receiver (unless you set mode `AVAudioSessionModeVideoChat`) or speaker: ```objectivec CYBCallAudioSession *audioSession = [CYBCallAudioSession instance]; // setting audio through receiver audioSession.currentAudioDevice = CYBCallAudioDeviceReceiver; // setting audio through speaker audioSession.currentAudioDevice = CYBCallAudioDeviceSpeaker; ``` ```swift let audioSession = CallAudioSession.instance() // setting audio through receiver audioSession.currentAudioDevice = .receiver // setting audio through speaker audioSession.currentAudioDevice = .speaker ``` ## 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*** > > Due to Apple iOS restrictions screen sharing feature works only within an app you are using it in. With iOS 11 Apple has introduced a new way to capture your in-app screen using [ReplayKit’s RPScreenRecorder](https://developer.apple.com/documentation/replaykit/rpscreenrecorder) class. This is the most optimal way to share screen and requires minimum resources as this is handled by iOS itself. * SDK v2 ```swift // Coming soon ``` * SDK v1 (deprecated) ```objectivec self.screenCapture = [[CYBCallVideoCapture alloc] init]; [RPScreenRecorder.sharedRecorder startCaptureWithHandler:^(CMSampleBufferRef \_Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * \_Nullable error) { switch (bufferType) { case RPSampleBufferTypeVideo: { CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CYBCallVideoFrame *frame = [[CYBCallVideoFrame alloc] initWithPixelBuffer:pixelBuffer videoRotation:CYBCallVideoRotation_0]; [self.screenCapture adaptOutputFormatToWidth:(NSUInteger)[UIScreen mainScreen].bounds.size.width height:(NSUInteger)[UIScreen mainScreen].bounds.size.height fps:30]; [self.screenCapture sendVideoFrame:frame]; break; } default: break; } } completionHandler:^(NSError \* \_Nullable error) { NSLog(@"error: %@", error); }]; self.session.localMediaStream.videoTrack.videoCapture = self.screenCapture; ``` ```swift self.screenCapture = CallVideoCapture() RPScreenRecorder.shared().startCapture(handler: { (sampleBuffer, type, error) in switch type { case .video : let source = CMSampleBufferGetImageBuffer(sampleBuffer) let frame = CallVideoFrame(pixelBuffer: source, videoRotation: ._0) self.screenCapture.adaptOutputFormat(toWidth: UInt(UIScreen.main.bounds.width), height: UInt(UIScreen.main.bounds.height), fps: 30) self.screenCapture.send(frame) break default: break } }) { (error) in if (error != nil) { print(error) } } self.session?.localMediaStream.videoTrack.videoCapture = self.screenCapture ``` **self.screenCapture** should be a property of `CYBCallVideoCapture` class type. > ***Note*** > > 30 fps is a maximum webrtc can go, even though `RPScreenRecorder` supports 60, you must set it to 30 or lower. ## 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.). * SDK v2 ```swift // Coming soon ``` * SDK v1 (deprecated) To enable stats report you should first set stats reporting frequency using `CYBCallConfig` method below: ```objectivec [CYBCallConfig setStatsReportTimeInterval:5]; // receive stats report every 5 seconds ``` ```swift CallConfig.setStatsReportTimeInterval(5) // receive stats report every 5 seconds ``` Now you will be able to receive a client delegate callback and perform operations with `CYBCallStatsReport` instance for the current period of time: ```objectivec - (void)session:(CYBCallBaseSession *)session updatedStatsReport:(CYBCallStatsReport *)report forUserID:(NSNumber *)userID { NSLog(@"%@", [report statsString]); } ``` ```swift func session(_ session: CallBaseSession, updatedStatsReport report: CallStatsReport, forUserID userID: NSNumber) { print(report.statsString()) } ``` By calling `statsString` you will receive a generic report string, which will contain the most useful data to debug a call, example: ```logos CN 565ms | local->local/udp | (s)248Kbps | (r)869Kbps VS (input) 640x480@30fps | (sent) 640x480@30fps VS (enc) 279Kbps/260Kbps | (sent) 200Kbps/292Kbps | 8ms | H264 AvgQP (past 30 encoded frames) = 36 VR (recv) 640x480@26fps | (decoded)27 | (output)27fps | 827Kbps/0bps | 4ms AS 38Kbps | opus AR 37Kbps | opus | 168ms | (expandrate)0.190002 Packets lost: VS 17 | VR 0 | AS 3 | AR 0 ``` > ***Note*** > > *CN* - connection info, *VS* - video sent, *VR* - video received, *AvgQP* - average quantization parameter (only valid for video; it is calculated as a fraction of the current delta sum over the current delta of encoded frames; low value corresponds with good quality; the range of the value per frame is defined by the codec being used), *AS* - audio sent, *AR* - audio received. You can also use stats reporting to see who is currently talking in a group call. You must use `audioReceivedOutputLevel` for that. Take a look to the `CYBCallStatsReport` header file to see all of the other stats properties that might be useful for you. ## Receive a call in background (CallKit) [Section titled “Receive a call in background (CallKit)”](#receive-a-call-in-background-callkit) 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](/ios/push-notifications) regarding how to integrate Push Notifications in your app. ### Apple CallKit using VOIP push notifications [Section titled “Apple CallKit using VOIP push notifications”](#apple-callkit-using-voip-push-notifications) ConnectyCubeCalls fully supports [Apple CallKit](https://developer.apple.com/documentation/callkit). In this block, we will guide you through the most important things you need to know when integrating CallKit into your application (besides those Apple has already provided in the link above). #### Project preparations [Section titled “Project preparations”](#project-preparations) In your Xcode project, make sure that your app supports Voice over IP services. For that open your Info.plist and make sure you have a specific line in Required background modes array: ![VOIP background mode](/_astro/ios_videocalling_voip_background_mode.CiqzDkLm_Z2dRyjT.webp) Now you are ready to integrate CallKit methods using [Apple’s guide here](https://developer.apple.com/documentation/callkit). * SDK v2 ```swift //not supported ``` * SDK v1 (deprecated) #### Managing audio session [Section titled “Managing audio session”](#managing-audio-session) CallKit requires you to manage Audio session by yourself. Use CYBCallAudioSession for that task. See [Audio session management](#audio-session-management). #### Initializing audio session [Section titled “Initializing audio session”](#initializing-audio-session) You must initialize audio session every time before you call `-[CXProvider reportNewIncomingCallWithUUID:update:completion:]` method, which shows incoming call screen. Before initializing audio session, set `useManualAudio` property value to `YES`. This will not activate webrtc audio before iOS allows us to. We will be activating audio manually later. See [Audio session initialization](#initialization-and-deinitialization) for more information. #### Managing audio session activations and deinitializing it [Section titled “Managing audio session activations and deinitializing it”](#managing-audio-session-activations-and-deinitializing-it) `CXProviderDelegate` has 2 delegate methods that you must conform to. These are `-[CXProviderDelegate provider:didActivateAudioSession:]` and `-[CXProviderDelegate provider:didDeactivateAudioSession:]`. Using `CYBCallAudioSessionActivationDelegateprotocol` of `CYBCallAudioSession` class, you need to notify that session was activated outside of it. `-[CXProviderDelegate provider:didActivateAudioSession:]` is also a delegate where we need to activate our audio manually. Set `audioEnabledproperty` of `CYBCallAudioSession` class in here, to enable webrtc audio as iOS have pushed audio session priority of our app to the top. ```objectivec - (void)provider:(CXProvider *)__unused provider didActivateAudioSession:(AVAudioSession *)audioSession { NSLog(@"[CallKitManager] Activated audio session."); CYBCallAudioSession *callAudioSession = [CYBCallAudioSession instance]; [callAudioSession audioSessionDidActivate:audioSession]; // enabling audio now callAudioSession.audioEnabled = YES; } ``` ```swift func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { print("[CallKitManager] Activated audio session.") let callAudioSession = CallAudioSession.instance() callAudioSession.audioSessionDidActivate(audioSession) // enabling audio now callAudioSession.isAudioEnabled = true } ``` Deinitialize audio session every time `CXProvider` deactivates it in `-[CXProviderDelegate provider:didDeactivateAudioSession:]` delegate. Deinitializing audio session earlier would lead to issues with the audio session. ```objectivec - (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession { NSLog(@"[CallKitManager] Dectivated audio session."); CYBCallAudioSession *callAudioSession = [CYBCallAudioSession instance]; [callAudioSession audioSessionDidDeactivate:audioSession]; // deinitializing audio session after iOS deactivated it for us if (callAudioSession.isInitialized) { NSLog(@"Deinitializing session in CallKit callback."); [callAudioSession deinitialize]; } } ``` ```swift func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { print("[CallKitManager] Dectivated audio session.") let callAudioSession = CallAudioSession.instance() callAudioSession.audioSessionDidDeactivate(audioSession) // deinitializing audio session after iOS deactivated it for us if (callAudioSession.isInitialized) { print("Deinitializing session in CallKit callback."); callAudioSession.deinitialize() } } ``` If you also have deinitialization code of `CYBCallAudioSession` somewhere else in your app, you can check and ignore it with `-[CYBCallAudioSessionActivationDelegate audioSessionIsActivatedOutside:]` method. By this, you will know for sure that **CallKit** is in charge of your audio session. Don’t forget to restore `CYBCallAudioSession` properties to default values in `-[CXProviderDelegate provider:performEndCallAction:]`: ```objectivec - (void)provider:(CXProvider *)__unused provider performEndCallAction:(CXEndCallAction *)action { CYBCallAudioSession *audioSession = [CYBCallAudioSession instance]; audioSession.audioEnabled = NO; audioSession.useManualAudio = NO; [action fulfillWithDateEnded:[NSDate date]]; } ``` ```swift func provider(_ provider: CXProvider, perform action: CXEndCallAction) { let audioSession = CallAudioSession.instance() audioSession.isAudioEnabled = false audioSession.useManualAudio = false action.fulfill(withDateEnded: Date()) } ``` ## 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](/ios/videocalling-conference). ## Recording [Section titled “Recording”](#recording) * SDK v2 ```swift //not supported ``` * SDK v1 (deprecated) `CYBCallRecorder` class handles calls recording. You cannot allocate it by yourself, but it is stored in each instance of `CYBCallSession` by the property named recorder if the requirements conform. Otherwise, recorder property value will be nil. ### Recorder requirements [Section titled “Recorder requirements”](#recorder-requirements) * Device must not be in a low-performance category. To check whether your device is in low performance category use `UIDevice+CYBCallPerformance` category property `CYBCall_lowPerformance`. * Only 1 to 1 audio and video calls are supported for now. ### Usage [Section titled “Usage”](#usage) Once you have created new rtc session, you can start recorder by accessing recorder property in session instance. Call start method and input desired file url: ```objectivec NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentPath = [searchPaths firstObject]; NSString *filePath = [NSString stringWithFormat:@"%@/file_%f.mp4", documentPath, [NSDate date].timeIntervalSince1970]; [self.session.recorder startRecordWithFileURL:[NSURL fileURLWithPath:filePath]]; ``` ```swift let searchPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) let documentPath = searchPaths.first let filePath = String(format: "%@/file_%f.mp4", documentPath!, Date().timeIntervalSince1970) self.session?.recorder?.startRecord(withFileURL: URL(fileURLWithPath: filePath)) ``` You can configure output file video settings and video orientation using these methods of `CYBCallRecorder` class: ```objectivec /** Set video recording format. @param width video width @param height video height @param bitrate video bitrate @param fps video fps @note You can only set this params while recording is not in active state (e.g. haven't started yet). @remark Default values are 640x480 with 636528 bitrate 30 fps @see https://www.dr-lex.be/info-stuff/videocalc.html for bitrate calculation */ - (void)setVideoRecordingWidth:(NSUInteger)width height:(NSUInteger)height bitrate:(NSUInteger)bitrate fps:(NSUInteger)fps; /** Set video recording orientation. @param videoRotation video rotation @note You can only set this params while recording is not in active state (e.g. haven't started yet). Cannot be changed mid record. @remark Default is 0 degrees, e.g. landscape orientation. */ - (void)setVideoRecordingRotation:(CYBCallVideoRotation)videoRotation; ``` Once the call is finished or whenever you want before that you need to simply call stop method: ```objectivec [self.session.recorder stopRecord:^(NSURL *file) { }]; ``` ```swift self.session?.recorder?.stopRecord({ (url) in }) ``` > ***Note*** > > Stop method is asynchronous and will take some time to finalize record file. Once the completion block is called, recording file should be ready by expected url unless some error happens. In order to handle any recorder errors, simply subscribe to delegate of `CYBCallRecorder` and handle this method: ```objectivec - (void)recorder:(CYBCallRecorder *)recorder didFailWithError:(NSError *)error { } ``` ```swift func recorder(_ recorder: CallRecorder, didFailWithError error: Error) { } ``` ## Settings and configuration [Section titled “Settings and configuration”](#settings-and-configuration) You can change different settings for your calls using `WebRTCConfig` (`CYBCallConfig` v1) class. All of them are listed below: ### Answer time interval [Section titled “Answer time interval”](#answer-time-interval) * SDK v2 If an opponent did not answer you within dialing time interval, then `onUserNotAnswer:` and then `onConnectionClosedForUser:` delegate methods will be called. > Default value: 60 seconds > > Minimum value: 10 seconds ```swift WebRTCConfig().answerTimeInterval = 45 ``` * SDK v1 (deprecated) If an opponent did not answer you within dialing time interval, then `userDidNotRespond:` and then `connectionClosedForUser:` delegate methods will be called. > Default value: 45 seconds > > Minimum value: 10 seconds ```objectivec [CYBCallConfig setAnswerTimeInterval:45]; ``` ```swift CallConfig.setAnswerTimeInterval(45) ``` ### Dialing time interval [Section titled “Dialing time interval”](#dialing-time-interval) Indicates how often we send notifications to your opponents about your call. > Default value: 5 seconds > > Minimum value: 3 seconds * SDK v2 ```swift WebRTCConfig().dialingTimeInterval = 5 ``` * SDK v1 (deprecated) ```objectivec [CYBCallConfig setDialingTimeInterval:5]; ``` ```swift CallConfig.setDialingTimeInterval(5) ``` ### DTLS (Datagram Transport Layer Security) [Section titled “DTLS (Datagram Transport Layer Security)”](#dtls-datagram-transport-layer-security) Datagram Transport Layer Security (DTLS) is used to provide communications privacy for datagram protocols. This fosters a secure signaling channel that cannot be tampered with. In other words, no eavesdropping or message forgery can occur on a DTLS encrypted connection. > DTLS is enabled by default. * SDK v2 ```swift // DTLS is enabled by default ``` * SDK v1 (deprecated) ```objectivec [CYBCallConfig setDTLSEnabled:YES]; ``` ```swift CallConfig.setDTLSEnabled(true) ``` ### Custom ICE servers [Section titled “Custom ICE servers”](#custom-ice-servers) You can customize a list of ICE servers. By default ConnectyCubeCalls will use internal ICE servers which is usually enough, but you can always set your own. > Q: How does WebRTC select which TURN server to use if multiple options are given? > > A: During the connectivity checking phase, WebRTC 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 ```swift let username = "turn_login" let password = "turn_password" let serverStun = ConnectycubeIceServer(uri: "stun:stun.randomserver.example") let serverTurn = ConnectycubeIceServer(uri: "turn:turn.randomserver.example", userName: username, password: password) WebRTCConfig().iceServerList.add(serverStun) WebRTCConfig().iceServerList.add(serverTurn) ``` * SDK v1 (deprecated) ```objectivec NSString *userName = @"turn_login"; NSString *password = @"turn_password"; NSArray *urls = @[ @"stun:stun.randomserver.example", @"turn:turn.randomserver.example", ]; CYBCallICEServer *server = [CYBCallICEServer serverWithURLs:urls username:userName password:password]; [CYBCallConfig setICEServers:@[server]]; ``` ```swift let username = "turn_login" let password = "turn_password" let urls = [ "stun:stun.randomserver.example", "turn:turn.randomserver.example", ] let server = CallICEServer.init(urls: urls, username: username, password: password) CallConfig.setICEServers([server!]) ``` ### Video codecs [Section titled “Video codecs”](#video-codecs) You can choose video codecs from available values: * SDK v2 * `WebRTCMediaConfig.VideoCodec.vp8`: VP8 video codec - `WebRTCMediaConfig.VideoCodec.vp9`: VP9 video codec - `WebRTCMediaConfig.VideoCodec.h264`: h264 high video codec * SDK v1 (deprecated) * `CYBCallVideoCodecVP8`: VP8 video codec - `CYBCallVideoCodecH264Baseline`: h264 baseline video codec - `CYBCallVideoCodecH264High`: h264 high video codec **VP8** is software supported video codec on Apple devices, which means it is the most demanding among all available ones. **VP9** is an improved version of VP8. It has better compression performance, improved rate control, and more efficient coding tools, which allow for higher-quality video at lower bitrates. **H264** is hardware supported video codec, which means that it is the most optimal one for use when performing video codec, using hardware acceleration you can always gurantee the best performance when encoding and decoding video frames. There are two options available: * **baseline**: the most suited one for video calls as it has low cost (default value) * **high**: mainly suited for broadcast to ensure you have the best picture possible. Takes more resources to encode/decode for the same resolution you set - SDK v2 ```swift WebRTCMediaConfig().videoCodec = .h264 ``` - SDK v1 (deprecated) ```objectivec [CYBCallConfig mediaStreamConfiguration].videoCodec = CYBCallVideoCodecH264Baseline; ``` ```swift CallConfig.mediaStreamConfiguration().videoCodec = .h264Baseline ``` > ***Note*** > > This will set your preferred codec, as webrtc will always choose the most suitable one for both sides in call through negotiations. ### Video quality [Section titled “Video quality”](#video-quality) Video quality depends on hardware you use. iPhone 4s will not handle FullHD rendering, but iPhone 6+ will. It also depends on network you use and how many connections you have. For multi-calls set lower video quality. For 1 to 1 calls you can set higher quality. * SDK v2 ```swift WebRTCMediaConfig().videoWidth = WebRTCMediaConfig.VideoQuality.hdVideo.width WebRTCMediaConfig().videoHeight = WebRTCMediaConfig.VideoQuality.hdVideo.height ``` * SDK v1 (deprecated) You can use our `CYBCallCameraCapture` `formatsWithPosition` method in order to get all supported formats for current device: ```objectivec /** * Retrieve available array of CYBCallVideoFormat instances for given camera position. * * @param position requested camera position * * @return Array of possible CYBCallVideoFormat video formats for requested position */ + (NSArray *)formatsWithPosition:(AVCaptureDevicePosition)position; ``` WebRTC has auto scaling of video resolution and quality to keep network connection active. To get best quality and performance you should use h264-baseline codec as your preferred one. 1. If some opponent user in call does not support h264, then automatically VP8 will be used. 2. If both caller and callee have h264 support, then h264 will be used. ### Audio codecs [Section titled “Audio codecs”](#audio-codecs) You can choose audio codecs from available values: * SDK v2 * `WebRTCMediaConfig.AudioCodec.opus` * `WebRTCMediaConfig.AudioCodec.isac` > Default value: WebRTCMediaConfig.AudioCodec.isac ```swift WebRTCMediaConfig().audioCodec = .opus ``` * SDK v1 (deprecated) * `CYBCallAudioCodecOpus` * `CYBCallAudioCodecISAC` * `CYBCallAudioCodeciLBC` > Default value: CYBCallAudioCodecOpus ```objectivec [CYBCallConfig mediaStreamConfiguration].audioCodec = CYBCallAudioCodecOpus; ``` ```swift CallConfig.mediaStreamConfiguration().audioCodec = .codecOpus ``` #### Opus [Section titled “Opus”](#opus) In the latest versions of Firefox and Chrome this codec is used by default for encoding audio streams. This codec is relatively new (released in 2012). It implements lossy audio compression. Opus can be used for both low and high bitrates. Supported bitrate: constant and variable, from 6 kbit/s to 510 kbit/s Supported sampling rates: from 8 kHz to 48 kHz. If you develop a Calls application that is supposed to work with high-quality audio, the only choice on audio codecs is OPUS. OPUS has the best quality, but it also requires a good internet connection. #### iSAC [Section titled “iSAC”](#isac) This codec was developed specially for VoIP applications and streaming audio. Supported bitrates: adaptive and variable. From 10 kbit/s to 52 kbit/s. Supported sampling rates: 32 kHz. Good choice for the voice data, but not nearly as good as OPUS. #### iLBC [Section titled “iLBC”](#ilbc) This audio codec is well-known, it was released in 2004, and became part of the WebRTC project in 2011 when Google acquired Global IP Solutions (the company that developed iLIBC). When you have very bad channels and low bandwidth, you definitely should try iLBC — it should be strong on such cases. Supported bitrates: fixed bitrate. 15.2 kbit/s or 13.33 kbit/s Supported sampling rate: 8 kHz. > ***Note*** > > When you have a strong reliable and good internet connection, then use OPUS. If you use Calls on 3g networks, use iSAC. If you still have problems, try iLBC. ### Bandwidth cap [Section titled “Bandwidth cap”](#bandwidth-cap) In a case of low bandwidth network, you can try to limit the call bandwidth cap to get better quality vs stability results: * SDK v2 ```swift WebRTCMediaConfig().audioStartBitrate = 50 WebRTCMediaConfig().videoStartBitrate = 256 ``` * SDK v1 (deprecated) ```objectivec // [CYBCallConfig mediaStreamConfiguration].audioBandwidth = 50; [CYBCallConfig mediaStreamConfiguration].videoBandwidth = 256; ``` ```swift // CallConfig.mediaStreamConfiguration().audioBandwidth = 50 CallConfig.mediaStreamConfiguration().videoBandwidth = 256 ``` # Multiparty Video Conferencing feature overview > Discover the simplicity of integrating conference video calling into your iOS 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) ## Config [Section titled “Config”](#config) **ConferenceConfig** class introduces new setting for Conference - conference endpoint. * SDK v2 ```swift ConferenceConfig.companion.url = "" ``` * SDK v1 (deprecated) **CYBCallConfig** class introduces new setting for Conference - conference endpoint. To set a specific conference endpoint use this method. ```objectivec + (void)setConferenceEndpoint:(NSString *)conferenceEndpoint; ``` > ***Note*** > > Endpoint should be a correct ConnectyCube Conference server endpoint. Use this method to get a current conference endpoint (default is nil): * SDK v2 ```swift ConferenceConfig.companion.url ``` * SDK v1 (deprecated) ```objectivec + (NSString *)conferenceEndpoint; ``` ## Create meeting [Section titled “Create meeting”](#create-meeting) In order to have a conference call, a meeting object has to be created. ```swift let now = Int64(Date().timeIntervalSince1970) / 1000 let meeting = ConnectycubeMeeting() meeting.name = "My meeting" meeting.startDate = now meeting.endDate = now + 60 * 60 meeting.attendees = [ConnectycubeAttendee(id: 123, email: "superman@mail.com"), ConnectycubeAttendee(id: 124, email: "superman2@mail.com")] meeting.record = false meeting.chat = false meeting.public_ = true meeting.scheduled = false // meeting.notify = true //notify feature is available starting from the [Advanced plan](https://connectycube.com/pricing/) // meeting.notifyBefore = ConnectycubeNotifyBefore(metric: ConnectycubeMeetingMetric.hours, value: 1) //notify feature is available starting from the [Advanced plan](https://connectycube.com/pricing/) ConnectyCube().createMeeting(meeting: meeting, successCallback: { createdMeeting in let confRoomId = createdMeeting.id }, errorCallback: { error in }) ``` * `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 `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. ## Conference client [Section titled “Conference client”](#conference-client) Conference module has its own client which is described in current part. ### Events [Section titled “Events”](#events) Conference client delegate is inherited from base client delegate and has all of its protocol methods implemented as well. #### Base client delegate protocol methods [Section titled “Base client delegate protocol methods”](#base-client-delegate-protocol-methods) All protocol methods below have their own explanation inlined and are optional. * SDK v2 ```swift /** * Called when session state has been changed. * * @param session P2PSession instance * @param state session state * */ func onStateChanged(session: BaseSession, state: BaseSessionRTCSessionState) {} /** * Called in case when connection with opponent is established * * @param session P2PSession instance * @param userId ID of opponent */ func onConnectedToUser(session: BaseSession, userId: Int32) {} /** * * Called in case when connection failed with user * * @param session P2PSession instance * @param userId ID of opponent */ func onDisconnectedFromUser(session: BaseSession, userId: Int32) {} /** * * Called in case when connection is closed for user * * @param session P2PSession * @param userId ID of opponent */ func onConnectionClosedForUser(session: BaseSession, userId: Int32) {} /** * * Called when received remote video track from user * * @param videoTrack ConnectycubeVideoTrack instance * @param userId ID of user */ func onRemoteVideoTrackReceive(session: BaseSession, videoTrack: ConnectycubeVideoTrack, userId: Int32) {} /** * * Called when received remote audio track from user * * @param videoTrack ConnectycubeAudioTrack instance * @param userId ID of user */ func onRemoteAudioTrackReceive(session: BaseSession, audioTrack: ConnectycubeAudioTrack, userId: Int32) {} ``` * SDK v1 (deprecated) ```objectivec /** * Called by timeout with updated stats report for user ID. * * @param session CYBCallSession instance * @param report CYBCallStatsReport instance * @param userID user ID * * @remark Configure time interval with [CYBCallConfig setStatsReportTimeInterval:timeInterval]. */ - (void)session:(__kindof CYBCallBaseSession *)session updatedStatsReport:(CYBCallStatsReport *)report forUserID:(NSNumber *)userID; /** * Called when session state has been changed. * * @param session CYBCallSession instance * @param state session state * * @discussion Use this to track a session state. As SDK 2.3 introduced states for session, you can now manage your own states based on this. */ - (void)session:(__kindof CYBCallBaseSession *)session didChangeState:(CYBCallSessionState)state; /** * Called when received remote audio track from user. * * @param audioTrack CYBCallAudioTrack instance * @param userID ID of user */ - (void)session:(__kindof CYBCallBaseSession *)session receivedRemoteAudioTrack:(CYBCallAudioTrack *)audioTrack fromUser:(NSNumber *)userID; /** * Called when received remote video track from user. * * @param videoTrack CYBCallVideoTrack instance * @param userID ID of user */ - (void)session:(__kindof CYBCallBaseSession *)session receivedRemoteVideoTrack:(CYBCallVideoTrack *)videoTrack fromUser:(NSNumber *)userID; /** * Called when connection is closed for user. * * @param session CYBCallSession instance * @param userID ID of user */ - (void)session:(__kindof CYBCallBaseSession *)session connectionClosedForUser:(NSNumber *)userID; /** * Called when connection is initiated with user. * * @param session CYBCallSession instance * @param userID ID of user */ - (void)session:(__kindof CYBCallBaseSession *)session startedConnectingToUser:(NSNumber *)userID; /** * Called when connection is established with user. * * @param session CYBCallSession instance * @param userID ID of user */ - (void)session:(__kindof CYBCallBaseSession *)session connectedToUser:(NSNumber *)userID; /** * Called when disconnected from user. * * @param session CYBCallSession instance * @param userID ID of user */ - (void)session:(__kindof CYBCallBaseSession *)session disconnectedFromUser:(NSNumber *)userID; /** * Called when connection failed with user. * * @param session CYBCallSession instance * @param userID ID of user */ - (void)session:(__kindof CYBCallBaseSession *)session connectionFailedForUser:(NSNumber *)userID; /** * Called when session connection state changed for a specific user. * * @param session CYBCallSession instance * @param state state - @see CYBCallConnectionState * @param userID ID of user */ - (void)session:(__kindof CYBCallBaseSession *)session didChangeConnectionState:(CYBCallConnectionState)state forUser:(NSNumber *)userID; ``` #### Conference client delegate protocol methods [Section titled “Conference client delegate protocol methods”](#conference-client-delegate-protocol-methods) All protocol methods below are conference client specific, optional to be implemented and have their own explanation inlined. * SDK v2 ```swift /** * Called when some publisher (user) joined to the video room */ func onPublisherLeft(userId: KotlinInt?) {} /** *Called when some publisher left room */ func onPublishersReceived(publishers: [KotlinInt]?) {} /** *Called when media - audio or video type, is received */ func onMediaReceived(type: String?, success\_ success: Bool) {} /** * 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. */ func onSlowLinkReceived(uplink: Bool, lost: Int32) {} /** * Called when received errors from server */ func onError(ex\_ ex: WsException?) {} /** * Called when ConferenceSession is closed */ func onSessionClosed(session: ConferenceBaseSession) {} ``` * SDK v1 (deprecated) ```objectivec /** * Called when session was created on server. * * @param session CYBCallConferenceSession instance * * @discussion When this method is called, session instance that was already created by CYBCallConferenceClient * will be assigned valid session ID from server. * * @see CYBCallConferenceSession, CYBCallConferenceClient */ - (void)didCreateNewSession:(CYBCallConferenceSession *)session; /** * Called when join to session is performed and acknowledged by server. * * @param session CYBCallConferenceSession instance * @param chatDialogID chat dialog ID * @param publishersList array of user IDs, that are currently publishers * * @see CYBCallConferenceSession */ - (void)session:(CYBCallConferenceSession *)session didJoinChatDialogWithID:(NSString *)chatDialogID publishersList:(NSArray *)publishersList; /** * Called when new publisher did join. * * @param session CYBCallConferenceSession instance * @param userID new publisher user ID * * @see CYBCallConferenceSession */ - (void)session:(CYBCallConferenceSession *)session didReceiveNewPublisherWithUserID:(NSNumber *)userID; /** * Called when publisher did leave. * * @param session CYBCallConferenceSession instance * @param userID publisher that left user ID * * @see CYBCallConferenceSession */ - (void)session:(CYBCallConferenceSession *)session publisherDidLeaveWithUserID:(NSNumber *)userID; /** * Called when session did receive error from server. * * @param session CYBCallConferenceSession instance * @param error received error from server * * @note Error doesn't necessarily means that session is closed. Can be just a minor error that can be fixed/ignored. * * @see CYBCallConferenceSession */ - (void)session:(CYBCallConferenceSession *)session didReceiveError:(NSError *)error; /** * Called when slowlink was received. * * @param session CYBCallConferenceSession instance * @param uplink whether the issue is uplink or not * @param nacks number of nacks * * @discussion this callback is triggered when serber reports trouble either sending or receiving media on the * specified connection, typically as a consequence of too many NACKs received from/sent to the user in the last * second: for instance, a slowLink with uplink=true means you notified several missing packets from server, * while uplink=false means server is not receiving all your packets. * * @note useful to figure out when there are problems on the media path (e.g., excessive loss), in order to * possibly react accordingly (e.g., decrease the bitrate if most of our packets are getting lost). * * @see CYBCallConferenceSession */ - (void)session:(CYBCallConferenceSession *)session didReceiveSlowlinkWithUplink:(BOOL)uplink nacks:(NSNumber *)nacks; /** * Called when media receiving state was changed on server. * * @param session CYBCallConferenceSession instance * @param mediaType media type * @param receiving whether media is receiving by server * * @see CYBCallConferenceSession, CYBCallConferenceMediaType */ - (void)session:(CYBCallConferenceSession *)session didChangeMediaStateWithType:(CYBCallConferenceMediaType)mediaType receiving:(BOOL)receiving; /** * Session did initiate close request. * * @param session CYBCallConferenceSession instance * * @discussion 'sessionDidClose:withTimeout:' will be called after server will close session with callback * * @see CYBCallConferenceSession */ - (void)sessionWillClose:(CYBCallConferenceSession *)session; /** * Called when session was closed completely on server. * * @param session CYBCallConferenceSession instance * @param timeout whether session was closed due to timeout on server * * @see CYBCallConferenceSession */ - (void)sessionDidClose:(CYBCallConferenceSession *)session withTimeout:(BOOL)timeout; ``` ### Conference client interface [Section titled “Conference client interface”](#conference-client-interface) * SDK v2 **ConferenceCalls** is a singleton based class which is used to create and operate with conference sessions. ```swift //MARK: ConferenceCallback let conferenceType = CallType.video ConnectyCube().conferenceCalls.createSession(userId: userId, callType: conferenceType, callback: self) extension YourClass: ConferenceCallback { func onSuccess(result: Any?) { let conferenceSession = result as! ConferenceSession } func onError(ex: WsException) { } } ``` * SDK v1 (deprecated) **CYBCallConferenceClient** is a singleton based class which is used to create and operate with conference sessions. It has observer (delegates) manager, which can be activated/deactivated with two simple methods: ```objectivec /** * Add delegate to the observers list. * * @param delegate delegate that conforms to CYBCallConferenceClientDelegate protocol * * @see CYBCallConferenceClientDelegate */ - (void)addDelegate:(id)delegate; /** * Remove delegate from the observers list. * * @param delegate delegate that conforms to CYBCallConferenceClientDelegate protocol * * @see CYBCallConferenceClientDelegate */ - (void)removeDelegate:(id)delegate; ``` Delegate should conform to **CYBCallConferenceClientDelegate** protocol, which is inherited from base client delegate. In order to create new conference session you should use method below: ```objectivec /** * Send create session request. * * @note Returns session without ID. When session will be created on server * ID will be assigned and session will be returned in 'didCreateNewSession:' callback. * * @see CYBCallConferenceClientDelegate * * @param chatDialogID chat dialog ID */ - (CYBCallConferenceSession *)createSessionWithChatDialogID:(NSString *)chatDialogID; ``` It will create session locally first, without session ID, until server will perform a `didCreateNewSession:` callback in **CYBCallConferenceClientDelegate** protocol, where session ID will be assigned and session will receive its **CYBCallSessionStateNew** state. After that you can join or leave (destroy) it, etc. Conference session is explained in the following paragraph. ## 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. ```swift class ConferenceCallbackJoinDialog: ConferenceCallback { func onSuccess(result: Any?) { let userIds: [Int] = result as! [Int] } func onError(ex: WsException) { } } let conferenceRole = ConferenceRole.publisher // or ConferenceRole.listener conferenceSession.joinDialog(dialogId: dialogId, conferenceRole: conferenceRole, callback: ConferenceCallbackJoinDialog()) ``` ## List online participants [Section titled “List online participants”](#list-online-participants) To list online users in a room: ```swift class CallbackOnlineParticipants: ConferenceCallback { override func onSuccess(result: NSDictionary?) { // the result contains the dictionary where key is the userId and value is true if this user is publisher and false if listener } override func onError(ex: WsException) {} } conferenceSession.getOnlineParticipants(callback: CallbackOnlineParticipants()) ``` ## Conference session [Section titled “Conference session”](#conference-session) * SDK v2 **ConferenceSession** is inherited from base session class, and has all of its basics, such as `state`, `currentUserId`, `localMediaStream`, ability to get remote audio and video tracks for a specific user IDs. You can subscribe and unsubscribe from publishers using methods below. ```swift conferenceSession.subscribeToPublisher(publisherId: publisherId) conferenceSession.unsubscribeFromPublisher(publisherId: publisherId) ``` And in order to close/leave session you can perform the following method: ```swift conferenceSession.leave() ``` > ***Note*** > > You do not need to be joined as publisher in order to perform subscription based operations in session. * SDK v1 (deprecated) **CYBCallConferenceSession** is inherited from base session class, and has all of its basics, such as `state`, `currentUserID`, `localMediaStream`, ability to get remote audio and video tracks for a specific user IDs: ```objectivec /** * Remote audio track with opponent user ID. * * @param userID opponent user ID * * @return CYBCallAudioTrack audio track instance */ - (CYBCallAudioTrack *)remoteAudioTrackWithUserID:(NSNumber *)userID; /** * Remote video track with opponent user ID. * * @param userID opponent user ID * * @return CYBCallVideoTrack video track instance */ - (CYBCallVideoTrack *)remoteVideoTrackWithUserID:(NSNumber *)userID; ``` and ability to get a connection state for a specific user ID if his connection is opened: ```objectivec /** * Connection state for opponent user ID. * * @param userID opponent user ID * * @return CYBCallConnectionState connection state for opponent user ID */ - (CYBCallConnectionState)connectionStateForUser:(NSNumber *)userID; ``` See **CYBCallBaseSession** class for more inline documentation. As for conference specific methods, conference session ID is **NSNumber**. Each conference session is tied to a specific ConnectyCube dialog ID (**NSString**). It also has a publishers list property. But publisher list will be only valid if you perform join to that session as publisher using method below: ```objectivec /** * Perform join room as publisher. * * @discussion 'session:didJoinChatDialogWithID:publishersList:' will be called upon successful join. * * @see CYBCallConferenceClientDelegate */ - (void)joinAsPublisher; ``` This method joins session and will publish your feed (make you an active publisher in room). Everyone in room will be able to subscribe and receive your feed. > ***Note*** > > Only can be used when session has a valid session ID, e.g. is created on server and notified to you with `didCreateNewSession:` callback from **CYBCallConferenceClientDelegate** protocol. You can subscribe and unsubscribe from publishers using methods below. > ***Note*** > > You do not need to be joined as publisher in order to perform subscription based operations in session. ```objectivec /** * Subscribe to publisher's with user ID feed. * * @param userID active publisher's user ID * * @discussion If you want to receive publishers feeds, you need to subscribe to them. * * @note User must be an active publisher. */ - (void)subscribeToUserWithID:(NSNumber *)userID; /** * Unsubscribe from publisher's with user ID feed. * * @param userID active publisher's user ID * * @discussion Do not need to be used when publisher did leave room, in that case unsibscribing will be performing automatically. Use if you need to unsubscribe from active publisher's feed. * * @note User must be an active publisher. */ - (void)unsubscribeFromUserWithID:(NSNumber *)userID; ``` > ***Note*** > > These methods can also be used only when session has a valid session ID, e.g. is created on server and notified to you with `didCreateNewSession:` callback from **CYBCallConferenceClientDelegate** protocol. And in order to close/leave session you can perform the following method: ```objectivec /** * Leave chat room and close session. * * @discussion 'sessionWillClose:' will be called when all connection are closed, 'sessionDidClose:withTimeout:' will be called when session will be successfully closed by server. */ - (void)leave; ``` > ***Note*** > > This method can be called in any state of the session and will always close it no matter what. ## Mute audio [Section titled “Mute audio”](#mute-audio) You can disable/enable audio during a call: ```swift conferenceSession?.mediaStreamManager?.localAudioTrack?.enabled = !(conferenceSession?.mediaStreamManager?.localAudioTrack!.enabled)! ``` ## Mute video [Section titled “Mute video”](#mute-video) You can disable/enable video during a call: ```swift conferenceSession?.mediaStreamManager?.localVideoTrack?.enabled = !(conferenceSession?.mediaStreamManager?.localVideoTrack!.enabled)! ``` ## Switch video cameras [Section titled “Switch video cameras”](#switch-video-cameras) You can switch the video capture position during a call (default: front camera): ```swift conferenceSession?.mediaStreamManager?.videoCapturer?.switchCamera() ``` ## 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 `WebRTCConfig` method below: ```swift //Coming soon //ConnectycubeStatsReport ``` ### 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 `ConnectycubeStatsReport` 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 `WebRTCConfig` as described above. Then create the instance of `ConnectycubeStatsReportsManager` and initialize it with the call session. ```swift //Coming soon //ConnectycubeStatsReportsManager ``` ## Recording [Section titled “Recording”](#recording) Server-side recording is available. Read more about Recording feature ## Examples and implementations [Section titled “Examples and implementations”](#examples-and-implementations) **sample-videochat-conference-objc** is a great example of our ConnectyCubeCalls Conference module, classes to look at: **CallViewController**. # Address Book > Effortlessly upload, sync, and access ConnectyCube users from your phone contacts in your iOS 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](/ios/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. * SDK v2 ```swift var addressBook = [ConnectycubeContact]() let contact = ConnectycubeContact(name: "Jony Ive", phone: "1-800-300-2000", destroy: false)// set true to remove a contact addressBook.append(contact) //let udid = UUID().uuidString ConnectyCube().uploadAddressBook(contacts: addressBook, force: false, udid: nil, successCallback: { (updates) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec NSMutableOrderedSet *addressBook = [[NSMutableOrderedSet alloc] init]; CYBAddressBookContact *contact = [[CYBAddressBookContact alloc] init]; contact.name = @"Jony Ive"; contact.phone = @"1-800-300-2000"; [addressBook addObject:contact]; [CYBRequest uploadAddressBookWithUdid:nil addressBook:addressBook force:false successBlock:^(CYBAddressBookUpdates * _Nonnull updates) { } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift let addressBook = NSMutableOrderedSet() let contact = AddressBookContact() contact.name = "Jony Ive" contact.phone = "1-800-300-2000" addressBook.add(contact) Request.uploadAddressBook(withUdid: nil, addressBook: addressBook, force: false, successBlock: { (updates) in }) { (error) in } ``` - 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`. - You also can remove a contact by setting `contact.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: * SDK v2 ```swift ConnectyCube().getAddressBook(udid: nil, successCallback: { (contacts) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest addressBookWithUdid:nil successBlock:^(NSArray * _Nonnull contacts) { } errorBlock:^(NSError \* \_Nonnull error) { }]; ``` ```swift Request.addressBook(withUdid: nil, successBlock: { (contacts) in }) { (error) in } ``` ## Retrieving Registered Users [Section titled “Retrieving Registered Users”](#retrieving-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: * SDK v2 ```swift ConnectyCube().getRegisteredUsersFromAddressBook(compact: false, udid: nil, successCallback: {(users) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest registeredUsersFromAddressBookWithUdid:nil isCompact:false successBlock:^(NSArray * _Nonnull users) { } errorBlock:^(NSError * _Nonnull error) { }]; ``` ```swift Request.registeredUsersFromAddressBook(withUdid: nil, isCompact: false, successBlock: { (users) in }) { (error) in } ``` 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, Address Book Settings tab: ![Setup push notification on new contact joined](/_astro/setup_push_notification_on_new_contact_joined.DTG1vj8m_1gVrvB.webp) ```plaintext ``` # Authentication and Users > Simplify user authentication in your iOS 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: * SDK v2 ```swift ConnectyCube().signIn(user: userToLogin, successCallback: {(user) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest logInWithUserLogin:@"userLogin" password:@"userPassword" successBlock:^(CYBUser *user) { } errorBlock:^(NSError *error) { }]; ``` ```swift Request.logIn(withUserLogin: "userLogin", password: "userPassword", successBlock: { (user) in }) { (error) in } ``` ## Authentication via phone number [Section titled “Authentication via phone number”](#authentication-via-phone-number) Sign In with phone number is supported with **Firebase integration**. The whole guide on how to create Firebase project, connect Firebase SDK and implement authentication via phone number is available at our [Firebase Setup Guide](/ios/firebase-setup-guide). Please follow it. ## 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](/ios/authentication-and-users/#upgrade-session-token-user-login) is used. You just use existing login request params to pass your external user token: ```swift let user: ConnectycubeUser = ConnectycubeUser() user.login = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzNDU2Nzg5LCJuYW1lIjoiSm9zZXBoIn0.OpOSSw7e485LOP5PrzScxHb7SR6sAOMRckfFwi4rp7o" ConnectyCube().signIn(user: userToLogin, successCallback: {(user) in }) { (error) in } ``` 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](/ios/messaging/#connect-to-chat-using-custom-authentication-providers). ## Session expiration [Section titled “Session expiration”](#session-expiration) Expiration time for session token is 2 hours after the last request to API is made. But you do not need to worry about it - with the automatic session token management it will be renewed automatically with next request to API (only v1 for now). ## Destroy session token [Section titled “Destroy session token”](#destroy-session-token) To destroy a session use the following code: * SDK v2 ```swift ConnectyCube().destroySession(successCallback: { }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest destroySessionWithSuccessBlock:^() { } errorBlock:^(NSError *error) { }]; ``` ```swift Request.destroySession(successBlock: { }) { (error) in } ``` ## User signup [Section titled “User signup”](#user-signup) * SDK v2 ```swift let user: ConnectycubeUser = ConnectycubeUser() user.login = "marvin18" user.password = "supersecurepwd" user.email = "awesomeman@gmail.com" user.fullName = "Marvin Simon" user.phone = "47802323143" user.website = "https://dozensofdreams.com" user.tags = "iphone, apple" ConnectyCube().signUp(user: user, successCallback: {(user) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec CYBUser *user = [CYBUser user]; user.login = @"marvin18"; user.password = @"supersecurepwd"; user.email = @"awesomeman@gmail.com"; user.fullName = @"Marvin Simon"; user.phone = @"47802323143"; user.website = @"https://dozensofdreams.com"; user.tags = @[@"iphone", @"apple"]; [CYBRequest signUp:user successBlock:^(CYBUser *user) { } errorBlock:^(NSError *error) { }]; ``` ```swift let user = User() user.login = "marvin18" user.password = "supersecurepwd" user.email = "awesomeman@gmail.com" user.fullName = "Marvin Simon" user.phone = "47802323143" user.website = "https://dozensofdreams.com" user.tags = ["iphone", "apple"] Request.signUp(user, successBlock: { (user) in }) { (error) in } ``` Only login (or email) + password are required. Other fields are optional. ## User profile update [Section titled “User profile update”](#user-profile-update) * SDK v2 ```swift let user: ConnectycubeUser = ConnectycubeUser() user.login = "marvin18" user.fullName = "Marvin Simon" ConnectyCube().updateUser(user: user, successCallback: {(user) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec CYBUpdateUserParameters *updateParameters = [CYBUpdateUserParameters new]; updateParameters.login = @"marvin18"; updateParameters.fullName = @"Marvin Simon"; [CYBRequest updateCurrentUser:updateParameters successBlock:^(CYBUser *user) { } errorBlock:^(NSError *error) { }]; ``` ```swift let updateParameters = UpdateUserParameters() updateParameters.login = "marvin18" updateParameters.fullName = "Marvin Simon" Request.updateCurrentUser(updateParameters, successBlock: { (user) in }) { (error) in } ``` If you want to change your password, you need to provide 2 parameters: `password` and `oldPassword`. An 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 the user. * SDK v2 ```swift let imageFilePath = "" ConnectyCube().uploadFile(filePath: imageFilePath, public: false, successCallback: {(cubeFile) in let customData = ["avatar_uid" : cubeFile.uid] if let theJSONData = try? JSONSerialization.data(withJSONObject: customData, options: .prettyPrinted) { user.customData = String(data: theJSONData, encoding: .utf8) } ConnectyCube().updateUser(user: user, successCallback: {(user) in }) { (error) in } }, errorCallback: { (error) in }, progress: { (progress) in }) ``` * SDK v1 (deprecated) ```objectivec UIImage *image = ... NSData *fileData = UIImagePNGRepresentation(image); [CYBRequest uploadFileWithData:fileData fileName:@"FileName" contentType:@"image/png" isPublic:false progressBlock:^(float progress) { } successBlock:^(CYBBlob *blob) { NSDictionary *customData = @{@"avatar_uid" : blob.UID}; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:customData options:NSJSONWritingPrettyPrinted error:nil]; NSString *jsonCustomData = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; CYBUpdateUserParameters *parameters = [[CYBUpdateUserParameters alloc] init]; parameters.customData = jsonCustomData; [CYBRequest updateCurrentUser:parameters successBlock:^(CYBUser *user) { } errorBlock:^(NSError *error) { }]; } errorBlock:^(NSError *error) { }]; ``` ```swift let image = ... let data = UIImagePNGRepresentation(image!) Request.uploadFile(with: data!, fileName: "FileName", contentType: "image/png", isPublic: false, progressBlock: { (progress) in }, successBlock: { (blob) in let parameters = UpdateUserParameters() let customData = ["avatar_uid" : blob.uid] if let theJSONData = try? JSONSerialization.data(withJSONObject: customData, options: .prettyPrinted) { parameters.customData = String(data: theJSONData, encoding: .utf8) } Request.updateCurrentUser(parameters, successBlock: { (user) in }, errorBlock: { (error) in }) }) { (error) in } ``` Now, other users can get your avatar: * SDK v2 ```swift let user = ... if let data = user.customData?.data(using: .utf8) { if let customData = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : String] { let avatarUID = customData["avatar_uid"] let privateAvatarUrl = ConnectycubeFileKt.getPrivateUrlForUID(uid: avatarUID) } } ``` * SDK v1 (deprecated) ```objectivec CYBUser *user = ... NSData *jsonData = [user.customData dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *customData = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:nil]; NSString *avatarUDID = customData[@"avatar_uid"]; NSString *privateUrl = [CYBBlob privateUrlForFileUID:avatarUDID]; ``` ```swift let user = ... if let data = user.customData?.data(using: .utf8) { if let customData = try? JSONSerialization.jsonObject(with: data, options: []) as! [String : String] { let avatarUID = customData["avatar_uid"] let privateAvatarUrl = Blob.privateUrl(forFileUID: avatarUID) } } ``` ## Password reset [Section titled “Password reset”](#password-reset) It’s possible to reset a password via email: * SDK v2 ```swift ConnectyCube().resetPassword(email: "user@mail.com", successCallback: { }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest resetUserPasswordWithEmail:@"user@mail.com" successBlock:^() { } errorBlock:^(NSError *error) { }]; ``` ```swift Request.resetUserPassword(withEmail: "user@mail.com", successBlock: { }) { (error) in } ``` 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 users by ID [Section titled “Retrieve users by ID”](#retrieve-users-by-id) * SDK v2 ```swift let paginator = RequestPagination(page: 0, itemsPerPage: 10) let sorter = RequestSorter(fieldName: "created_at", sortType: OrderType.shared.ASC) ConnectyCube().getUsersByIds(ids: [21, 22], pagination: paginator, sorter: sorter, successCallback: {(result) in let users = result.items }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec CYBPaginator *paginator = [CYBPaginator limit:10 skip:0]; [CYBRequest usersWithIDs:@[@"21",@"22"] paginator:paginator successBlock:^(CYBPaginator *paginator, NSArray *users) { } errorBlock:^(NSError *error) { }]; ``` ```swift let paginator = Paginator(limit:10, skip: 0) Request.users(withIDs: ["21","22"], paginator: paginator, successBlock: { (paginator, users) in }) { (error) in } ``` ### Retrieve user by login [Section titled “Retrieve user by login”](#retrieve-user-by-login) * SDK v2 ```swift ConnectyCube().getUserByLogin(login: "amigo", successCallback: {(user) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest userWithLogin:@"amigo" successBlock:^(CYBUser *user) { } errorBlock:^(NSError *error) { }]; ``` ```swift Request.user(withLogin: "amigo", successBlock: { (user) in }) { (error) in } ``` ### Retrieve user by email [Section titled “Retrieve user by email”](#retrieve-user-by-email) * SDK v2 ```swift ConnectyCube().getUserByEmail(email: "amigo@mail.com", successCallback: {(user) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest userWithEmail:@"amigo@mail.com" successBlock:^(CYBUser *user) { } errorBlock:^(NSError *error) { }]; ``` ```swift Request.user(withEmail: "amigo@mail.com", successBlock: { (user) in }) { (error) in } ``` ### Retrieve users by full name [Section titled “Retrieve users by full name”](#retrieve-users-by-full-name) * SDK v2 ```swift let paginator = RequestPagination(page: 0, itemsPerPage: 10) let sorter = RequestSorter(fieldName: "created_at", sortType: OrderType.shared.ASC) ConnectyCube().getUsersByFullName(fullName: "Marvin", pagination: paginator, sorter: sorter, successCallback: {(result) in let users = result.items }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec CYBPaginator *paginator = [CYBPaginator limit:10 skip:0]; [CYBRequest usersWithFullName:@"Marvin" paginator:paginator successBlock:^(CYBPaginator *paginator, NSArray *users) { } errorBlock:^(NSError *error) { }]; ``` ```swift let paginator = Paginator(limit:10, skip: 0) Request.users(withFullName: "Marvin", paginator: paginator, successBlock: { (paginator, users) in }) { (error) in } ``` ### Retrieve user by phone number [Section titled “Retrieve user by phone number”](#retrieve-user-by-phone-number) * SDK v2 ```swift ConnectyCube().getUserByPhoneNumber(phone: "+4427123314", successCallback: {(user) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec CYBPaginator *paginator = [CYBPaginator limit:10 skip:0]; [CYBRequest usersWithPhoneNumbers:@[@"+4427123314"] paginator:paginator successBlock:^(CYBPaginator *paginator, NSArray *users) { } errorBlock:^(NSError *error) { }]; ``` ```swift let paginator = Paginator(limit:10, skip: 0) Request.users(withPhoneNumbers: ["+4427123314"], paginator: paginator, successBlock: { (paginator, users) in }) { (error) in } ``` ### Retrieve user by external ID [Section titled “Retrieve user by external ID”](#retrieve-user-by-external-id) * SDK v2 ```swift ConnectyCube().getUserByExternalId(id: "3789", successCallback: {(user) in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest userWithExternalID:3789 successBlock:^(CYBUser *user) { } errorBlock:^(NSError *error) { }]; ``` ```swift Request.user(withExternalID: 3789, successBlock: { (user) in }) { (error) in } ``` ### Retrieve users by tags [Section titled “Retrieve users by tags”](#retrieve-users-by-tags) * SDK v2 ```swift let paginator = RequestPagination(page: 0, itemsPerPage: 10) let sorter = RequestSorter(fieldName: "created_at", sortType: OrderType.shared.ASC) ConnectyCube().getUsersByTags(tags: ["iphone"], pagination: paginator, sorter: sorter, successCallback: {(result) in let users = result.items }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec CYBPaginator *paginator = [CYBPaginator limit:10 skip:0]; [CYBRequest usersWithTags:@[@"iphone"] paginator:paginator successBlock:^(CYBPaginator *paginator, NSArray *users) { } errorBlock:^(NSError *error) { }]; ``` ```swift let paginator = Paginator(limit:10, skip: 0) Request.users(withTags: ["iphone"], paginator: paginator, successBlock: { (paginator, users) in }) { (error) in } ``` ## Delete user [Section titled “Delete user”](#delete-user) A user can delete himself from the platform: * SDK v2 ```swift ConnectyCube().deleteUser(successCallback: { }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec [CYBRequest deleteCurrentUserWithSuccessBlock:^() { } errorBlock:^(NSError *error) { }]; ``` ```swift Request.deleteCurrentUser(successBlock: { }) { (error) in } ``` # Custom Data > Maximize your iOS 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](/android/) 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`]); * Float (\[`Double`]); * Boolean (\[`Boolean`]); * String (\[`String`]); * Location (the array what consist of **two** `double`s); After adding all the required fields, click the **`CREATE CLASS`** button to save your scheme. ![Create class scheme](/_astro/create_scheme.BZpQA4pv_Z1tQkMW.webp ":size=400") 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 ![Create class scheme](/_astro/created_scheme.DuEkinE2_2l5wmH.webp) 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. ![Edit permissions levels](/_astro/edit_class_permissions.DwZG2uer_2451o4.webp ":size=400") ### 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: ![Set permissions for record](/_astro/edit_record_permissions.YjI8FGha_Zb7hOq.webp ":size=400") 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. ```swift let className = "call_history_item" let customObject = ConnectycubeCustomObject(className: className) customObject.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] ] ConnectyCube().createCustomObject(customObject: customObject, successCallback:{ createdObject in let objectFields = createdObject.fields // the fields of your object saved on the ConnectyCube server }, errorCallback: { error in }) ``` For example, you can use [JSONSerialization](https://developer.apple.com/documentation/foundation/jsonserialization) for serialization and deserialization your objects. In this case, the code will appear as follows: ```swift let className = "call_history_item" let customObject = ConnectycubeCustomObject(className: className) let callItem: CallItem // the instance of the class that extends `Codable` let data = try? JSONEncoder().encode(callItem) customObject.fields = try! JSONSerialization.jsonObject(with: data!, options: []) as! KotlinMutableDictionary ConnectyCube().createCustomObject(customObject: customObject, successCallback:{ createdObject in let callItem: CallItem = try! JSONDecoder().decode(CallItem.self, from: JSONSerialization.data(withJSONObject: createdObject.fields)) }, errorCallback: { error in }) ``` ### 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 `ConnectycubeCustomObject` you use to create the object. In this case, the request will look like this: ```swift let className = "call_history_item" let customObject = ConnectycubeCustomObject(className: className) let callItem: CallItem // the instance of the class that has annotation `@Serializable` let data = try? JSONEncoder().encode(callItem) customObject.fields = try! JSONSerialization.jsonObject(with: data!, options: []) as! KotlinMutableDictionary let updatePermission = CustomObjectPermission(level: Level.companion.OPEN_FOR_USERS_IDS, ids: [232529, 563542, 563541], tags: nil) let permissions = CustomObjectPermissions(readPermission: nil, updatePermission: updatePermission, deletePermission: nil) customObject.permissions = permissions ConnectyCube().createCustomObject(customObject: customObject, successCallback:{ createdObject in let permissions = createdObject.permissions }, errorCallback: { error in }) ``` ## Retrieve record by ID [Section titled “Retrieve record by ID”](#retrieve-record-by-id) Retrieve the record by specifying its identifier. ```swift let className = "call_history_item" let id = "656f407e29d6c5002fce89dc" ConnectyCube().getCustomObjectById(className: className, id: id, successCallback:{ record in let customObject = record // the found record or nil if requested record was not found }, errorCallback: { error in }) ``` ## Retrieve records by IDs [Section titled “Retrieve records by IDs”](#retrieve-records-by-ids) Retrieve records by specifying their identifiers. ```swift let className = "call_history_item" let ids = ["656f407e29d6c5002fce89dc", "5f985984ca8bf43530e81233"] ConnectyCube().getCustomObjectsByIds(className: className, ids: ids, successCallback:{ records in let customObjects = records.items // the list of the found items }, errorCallback: { error in }) ``` ## 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 ```swift let className = "call_history_item" let params = ["call_start_time[gt]" : 1701789791673] ConnectyCube().getCustomObjectsByClassName(className: className, params: params, successCallback:{ result in let customObjects = result.items // the list of the found items }, errorCallback: { error in }) ``` ## 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. ```swift let className = "call_history_item" let id = "656f407e29d6c5002fce89dc" ConnectyCube().getCustomObjectPermissions(className: className, id: id, successCallback:{ permissions in let recordPermissions = permissions // the permissions applied for a searched record }, errorCallback: { error in }) ``` ## Update record by ID [Section titled “Update record by ID”](#update-record-by-id) Update record data by specifying its ID. ```swift let className = "call_history_item" let id = "656f407e29d6c5002fce89dc" let params = ["call_end_time" : 1701945033120] ConnectyCube().updateCustomObject(className: className, id: id, params: params, successCallback:{ updatedObject in let updatedCustomObject = updatedObject }, errorCallback: { error in }) ``` ## 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 ```swift let className = "call_history_item" let params = ["search_criteria" : ["user_id" : 1234567], "call_name" : "Deleted user"] as [String : Any] ConnectyCube().updateCustomObjectsByCriteria(className: className, params: params, successCallback:{ result in let updatedItems = result.items // the list of updated items }, errorCallback: { error in }) ``` ## Delete record by ID [Section titled “Delete record by ID”](#delete-record-by-id) Delete a record from a class by record identifier. ```swift let className = "call_history_item" let id = "656f407e29d6c5002fce89dc" ConnectyCube().deleteCustomObjectById(className: className, id: id, successCallback:{ result in // the record was successfully deleted }, errorCallback: { error in }) ``` ## 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). ```swift let className = "call_history_item" let ids = ["656f407e29d6c5002fce89dc", "5f998d3bca8bf4140543f79a"] ConnectyCube().deleteCustomObjectsByIds(className: className, ids: ids, successCallback:{ result in let deletedItemsIds = result.successfullyDeleted?.ids // `ids` is the result with identifiers of the deleted records request }, errorCallback: { error in }) ``` ## 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 ```swift let className = "call_history_item" let params = ["call_id[in]" : "f8c3de3d-1fea-4d7c-a8b0-29f63c4c3454"] ConnectyCube().deleteCustomObjectsByCriteria(className: className, params: params, successCallback:{ totalDeleted in // `totalDeleted` is the count of deleted items }, errorCallback: { error in }) ``` ## 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 iOS application, empowering to easily create feature-rich, scalable applications. You might need to use Firebase for Firebase authentication of users in your app via phone number. 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 project** ![Adding Project in Firebase](/_astro/firebase_add_project.Dq7GHe8f_maP4o.webp) > **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**. ![Add a project 1 of 3](/_astro/firebase_add_project_2_1.CTNFPmFI_1qpQUj.webp ":size=400%") ![Add a project 2 of 3](/_astro/firebase_add_project_2_2.xX7Ff4sg_Z1D8se6.webp ":size=400%") 4. Configure Google Analytics for your project and click **Create project**. ![Add a project 3 of 3](/_astro/firebase_add_project_2_3._yPP5HiQ_Z1ctbYe.webp ":size=400%") Then click Continue. ![Click Continue](/_astro/firebase_add_project_4.Cus9Uz0-_Z2oLn44.webp) 5. Select platform for which you need Firebase ![Select platform](/_astro/firebase_add_project_platforms.CynsV-4f_Z1OKfii.webp) 6. Fill in the fields on **Add Firebase to your iOS App** screen and click **Register App** ![Add Firebase to your iOS app](/_astro/firebase_add_to_ios_app.Ces2fgt9_Z2pISkR.webp) > **Note**: Please pay attention to add the **bundle ID** your app is using. It can be set only when you add an app to your Firebase project. ## Connect Firebase SDK [Section titled “Connect Firebase SDK”](#connect-firebase-sdk) For **iOS** projects Firebase has the following **requirements** to be added successfully: * Xcode 9.0 or later * An Xcode project for iOS 8 version or above * For Swift projects - Swift 3.0 or later * Your app’s bundle identifier * CocoaPods 1.4.0 or later * For Cloud Messaging: * A physical iOS device * Your Apple Developer account’s Apple Push Notification Authentication Key * In Xcode you need to enable Push Notifications in App > Capabilities Here is a step by step guide how to connect Firebase SDK to your iOS Project: 1. Download **GoogleService-Info.plist** config file. It can be also downloaded later if you need. 2. Add Firebase SDK A. With CocoaPods It is recommended using CocoaPods to install the libraries. CocoaPods can be installed according to this [guide](https://guides.cocoapods.org/using/getting-started.html#getting-started). You need to add the pods for the libraries you need to use. 1. If you don’t have an Xcode project yet, you need to create it. 2. Create a Podfile if you don’t have one. You can do that as follows: ```groovy $ cd your-project directory $ pod init ``` ````plaintext 3) Add the pods you are going to install. A Pod can be included in your Podfile as follows: ```groovy pod 'Firebase/Core' ```` > **Note**: The above pod should add the libraries needed to make Firebase work in your iOS app as well as Google Analytics for Firebase. The list of available pods with their specifications can be found [here](https://firebase.google.com/docs/ios/setup#available_pods). 4. Install the pods of your choice and open your-project.xcworkspace file to see the project in Xcode. ```groovy $ pod install $ open your-project.xcworkspace ``` 5. Download a **GoogleService-Info.plist** file from your Firebase account and add it to your app. > **Note**: Each bundle ID must be connected in Firebase. If you have several bundle IDs in your project, each of them can have its own GoogleService-Info.plist file. B. Without CocoaPods SDK frameworks can be imported directly without using CocoaPods as follows: 1. Download the [framework SDK zip](https://firebase.google.com/download/ios) (since the file is about 500MB in size, it may take some time). 2. Unzip it and find README file. 3. Add the [ObjC linker flag](https://developer.apple.com/library/content/qa/qa1490/_index.html) in your Other Linker Settings in your target’s build settings. 3) Add initialisation code: 1. Import the Firebase module in your `UIApplicationDelegate`: ```objectivec @import Firebase; ``` ```swift import Firebase ``` 2. Configure a [FirebaseApp](https://firebase.google.com/docs/reference/ios/firebasecore/api/reference/Classes/FIRApp) shared instance, typically in your application’s `application:didFinishLaunchingWithOptions:` method: ```objectivec // Use Firebase library to configure APIs [FIRApp configure]; ``` ```swift // Use Firebase library to configure APIs FirebaseApp.configure() ``` You are done now. ## 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 iOS project: 1. Include the following pods in your Podfile: ```groovy pod 'Firebase/Auth' ``` 2. Go to **Firebase console >> Authentication >> Sign-in method** section: ![Enable phone authentication in Firebase](/_astro/firebase_authentication_phone.DOqJbuvO_Ziw2EF.webp) 3. Enable **Phone** number sign-in method: ![Enable phone authentication in Firebase](/_astro/firebase_authentication_phone_2.hEGZ-tgZ_1zWjJu.webp) 4. Enable app verification For Firebase phone authentication to work Firebase must be able to verify that phone number sign-in requests are coming from your app. There are two options how to verify it: * **Silent APNs notifications** In order to enable APNS notifications for Firebase authentication follow these steps: 1. [Enable push notifications](http://help.apple.com/xcode/mac/current/#/devdfd3d04a1) for your project in Xcode 2. Upload your APNS certificate to Firebase. If you do not know how to create APNS certificate - please use our [How to generate APNS certificate guide](/ios/how-to-create-apns-certificate) * **reCAPTCHA verification** If sending silent push notifications fails (user has disabled background refresh or your app or you are testing on a simulator), Firebase uses reCAPTCHA verification to complete the phone log in. In order to enable reCAPTCHA verification follow these steps: 1. Add custom URL schemes to your project in Xcode: A. In your **GoogleService-Info.plist** configuration file find **REVERSED\_CLIENT\_ID** key and copy it. B. Go to your **project configuration >> Your Project name**, then select your app from the **Targets** section, choose **Info** tab and open **URL Types** section. C. Click **+** button and add your **REVERSED\_CLIENT\_ID** key to **URL Schemes** field. 2. (Optional) It is possible to customize the way your app presents `SFSafariViewController` or `UIWebView` when displaying the reCAPTCHA to the user. For that purpose you need to create a custom class that conforms to the `FIRAuthUIDelegate` protocol, and pass it to `verifyPhoneNumber:UIDelegate:completion:`. 5. Call `verifyPhoneNumber:UIDelegate:completion:` passing to it the user’s phone number to request that Firebase send an authentication code to the user’s phone by SMS: ```objectivec [[FIRPhoneAuthProvider provider] verifyPhoneNumber:phoneNumber UIDelegate:nil completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) { if (error) { [self showMessagePrompt:error.localizedDescription]; return; } // Sign in using the verificationID and the code sent to the user // ... }]; ``` ```swift PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationID, error) in if let error = error { self.showMessagePrompt(error.localizedDescription) return } // Sign in using the verificationID and the code sent to the user // ... } ``` > **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. 6. `languageCode` property on your Auth instance allows specifying the auth language and therefore localize SMS message sent by Firebase: ```objectivec // Change language code to french. [FIRAuth auth].languageCode = @"fr"; ``` ```swift // Change language code to french. Auth.auth().languageCode = "fr"; ``` 7. Save the **verification ID** and restore it when your app loads. This measure should help you to have a valid verification ID if your app closes before the user can sign in (if the user checks SMS app, for instance). A simple way is to save the verification ID with the `NSUserDefaults` object: ```objectivec NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setObject:verificationID forKey:@"authVerificationID"]; ``` ```swift UserDefaults.standard.set(verificationID, forKey: "authVerificationID") ``` Then, you can restore the saved value: ```objectivec NSString *verificationID = [defaults stringForKey:@"authVerificationID"]; ``` ```swift let verificationID = UserDefaults.standard.string(forKey: "authVerificationID") ``` 8. Create a `FIRPhoneAuthCredential` object using the **verification code** and the **verification ID**: ```objectivec FIRAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID verificationCode:verificationCode]; ``` ```swift let credential = PhoneAuthProvider.provider().credential( withVerificationID: verificationID, verificationCode: verificationCode) ``` 9. Sign in the user with the FIRPhoneAuthCredential object: ```objectivec [[FIRAuth auth] signInAndRetrieveDataWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (error) { // ... return; } // User successfully signed in. Get user data from the FIRUser object if (authResult == nil) { return; } FIRUser *user = authResult.user; // ... }]; ``` ```swift Auth.auth().signInAndRetrieveData(with: credential) { (authResult, error) in if let error = error { // ... return } // User is signed in // ... } ``` 10. Get Firebase `access_token` after SMS code verification as follows: ```objectivec [FIRUser getIDTokenWithCompletion:^(NSString * _Nullable token, NSError * _Nullable __unused error) { }]; ``` ```swift User.getIDToken { (tokenResult, error) in } ``` 11. Add ConnectyCube user sign in to your project as follows: 1) Get your **Project ID** from your **Firebase console** ![Get your Project ID](/_astro/firebase_project_id.DbPbXrrM_ZLtFkp.webp) 2. Pass your Firebase `project_id` and Firebase `access_token` parameters to `signInUsingFirebasePhone` (`logInWithFirebaseProjectID` v1 deprecated) method: * SDK v2 ```swift let projectId = "" let accessToken = "" ConnectyCube().signInUsingFirebasePhone(projectId: projectId, accessToken: accessToken, successCallback: { user in }) { (error) in } ``` * SDK v1 (deprecated) ```objectivec NSString *projectId = @"..."; NSString *accessToken = @"..."; [CYBRequest logInWithFirebaseProjectID:projectId accessToken:accessToken successBlock:^(CYBUser *user) { } errorBlock:^(NSError *error) { }]; ``` ```swift let projectId = "" let accessToken = "" Request.logIn(withFirebaseProjectID: projectId, accessToken: accessToken, successBlock: { (user) in }) { (error) in } ``` 12. Run your app to verify installation Once your user is logged in successfully, you will find him/her in your **Dashboard >> Your App >> Users** section. ![User logged in](/_astro/firebase_user_logged_in.DqybzeAt_Z1rKYfj.webp) 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) # How to generate APNS certificate > Step-by-step instruction of how to create APNS certificates for iOS push notifications. ## Create App ID [Section titled “Create App ID”](#create-app-id) Each iOS application that uses the APNs must have a unique application ID that uniquely identifies your app. The following steps describe how to create an Apple App ID for your application. > If you already have an App ID, you can skip these steps. 1. Once you have signed in to the [iPhone Developer Connection Portal](https://developer.apple.com/account/), click on **Identifiers** on **Certificates, IDs & Profiles.** column ![iPhone Developer Connection Portal](/_astro/ios_dev_portal_main.Cl2RAoT0_2ciKs1.webp) 2. On the next screen, select **App IDs** in the right dropdown and then click on the plus button ![iPhone Developer Connection Portal, Certificates, IDs \& Profiles](/_astro/ios_new_identifier.DODuJ0Fx_ZE5lln.webp) 3. On the next screen, select **App IDs** in the radio menu and then click on the **Continue** button ![iPhone Developer Connection Portal, Certificates, IDs \& Profiles](/_astro/ios_register_new_app_ids.m1wnQ3Yf_1oRDdX.webp) 4. On the next screen, select **App** and then click on the **Continue** button ![iPhone Developer Connection Portal, Certificates, IDs \& Profiles](/_astro/ios_register_new_identifier_type.CIDLkUuw_Z1V6TkK.webp) 5. Enter your app name for the **App ID Description** section **(1)**. In the **App ID Prefix** section, select the **Explicit** option and enter **Bundle Identifier(2)**. For example, our iOS application ID looks like **com.awesomecorp.messagingspace**: ![iPhone Developer Connection Portal, new App ID](/_astro/ios_dev_portal_new_app_id.CwcApvNv_ZlQHCt.webp) **Note:** Ensure you have created an App ID without a wildcard(make sure it doesn’t have an asterisk in it). Wildcard IDs cannot use the push notification service. 6. Once you have entered all of the information, press **Continue** button. You will now be presented with an overview of your new app ID. Click **Register** button to continue. ## Generate a Certificate Signing Request [Section titled “Generate a Certificate Signing Request”](#generate-a-certificate-signing-request) You need to generate a certificate signing request file so that you can use it to request an APNS SSL certificate later on. Follow the instructions below to create one: 1. Launch the **Keychain Access** application in your macOS: ![macOS Keychain access](/_astro/ios_keychain_access.BikdEG89_1WL9mL.webp) 2. Select **Keychain Access -> Certificate Assistant -> Request a Certificate From a Certificate Authority**: ![Request a Certificate From a Certificate Authority](/_astro/ios_keychain_request_csr.BKHMScyZ_ZS2lRy.webp) 3. Enter the required information and check the **‘Saved to disk’** option. Click Continue: ![Form a CSR request](/_astro/ios_keychain_certificate_information.cIHcj9PT_Z1fqk0L.webp) 4. Save the certificate request using the suggested name and click **Save**. Click **Done** in the next screen: ![Save CSR file on disk](/_astro/ios_save_csr.Cr_mbNVJ_Z1YhM0p.webp) ## Create universal APNS certificate [Section titled “Create universal APNS certificate”](#create-universal-apns-certificate) 1. On the **Certificates, IDs & Profiles** screen, select **Certificates** in the left sidebar and then select **App IDs** in the right dropdown and then click on the plus button: ![Create new certificate](/_astro/ios_create_new_cert.CK6bKr7__1dhnMF.webp) 2. The **Add iOS Certificate** screen will appear. Choose **Apple Push Notifications service SSL (Sandbox & Production)** and click **Continue**: ![Create new universal APNS certificate](/_astro/ios_create_universal_apns_cert.SmcKjZXo_ZkSewr.webp) Here we will create a single **universal** push certificate which is good for both Development and Production environments. 3. On the next screen choose an App ID you want to create a push certificate for. Then click **Continue**: ![Choose App ID for new push certificate](/_astro/ios_choose_appid_for_push_cert.CbgNCQ9B_Z29tnzR.webp) 4. Click **‘Choose File…’** button to locate **Certificate Request File** that you have saved earlier. Click **Continue**: ![Upload CSR file and generate push certificate](/_astro/ios_generate_push_certificate.BErvCpv8_ZyNNCM.webp) 5. Your APNS SSL Certificate will now be generated. Click **Download** and save it to your hard disk: ![Download](/_astro/ios_apns_certificate_is_ready.C8Hl4nGd_Z5UK3H.webp) Also, you can download your new certificate on App ID details page. **Identifiers** > **Select your app id** > **Capabilities** > **Push Notifications** > **Edit**: ![Download APNS certificate from App ID details page](/_astro/ios_download_apns_cert_from_appid_details_page.C7mLNON0_39OMb.webp) ## Create p12 file [Section titled “Create p12 file”](#create-p12-file) 1. Once your certificate has been created, open the file with **Keychain Access** app on your computer. If you are asked which keychain to add the certificate to, select **login** option. SSL certificate will be used by provider(ConnectyCube) application so that it can contact the APNs to send push notifications to your applications. 2. Still in **Keychain Access** app, select **Certificates** option in the bottom section of the left sidebar. You will now need to find the certificate you just made; it will have the name **Apple Push Services: < Bundle ID >** and paired with a private key. ![APNS certificate in Keychain Access](/_astro/ios_apns_certificate_in_keychain.CRMI0oEg_16YJhi.webp) 3. Right-click on this key and select **Export “Apple Push Services …”** option. In the popup that appears, you can call the file whatever you want, but make sure that **File Format** is set to **Personal Information Exchange (.p12).** When exporting the certificate, you can provide a password for the p12 file. That’s all - now you have a ready-to-go APNS certificate which can be used on both Development and Production environments of your iOS application. ## Upload certificate to Dashboard [Section titled “Upload certificate to Dashboard”](#upload-certificate-to-dashboard) To upload APNS certificate to ConnectyCube Dashboard you need to do the following: 1. Open your ConnectyCube Dashboard at [admin.connectycube.com](https://admin.connectycube.com) 2. Go to **Push notifications** module, **Credentials** page 3. Upload the newly created APNS certificate on **Apple Push Notification Service (APNS)** form. Choose certificate and enter password if needed. ![Upload APNS certificate in ConnectyCube dashboard](/_astro/ios-upload-push-certificate._J9x_biU_x01ix.webp) After this you will be able to start using push notifications in your iOS application. ### Links you might find useful: [Section titled “Links you might find useful:”](#links-you-might-find-useful) [How to submit an iOS app to the App Store: Step-by-step guide](/guides/uploading-ios-app-to-store)\ [How to upload app to the Google Play Store: Step-by-step guide](/guides/uploading-android-app-to-store) # How to generate APNS key > Step-by-step instruction of how to create APNS key for iOS push notifications. 1. Once you have signed in to the [iPhone Developer Connection Portal](https://developer.apple.com/account/), click on **Keys** at **Certificates, IDs & Profiles** column ![iPhone Developer Connection Portal](/_astro/ios_dev_credentials_column.ZHkmARIZ_Z1fVpMf.webp) 2. Press **+** to generate new key ![iPhone Developer Connection Portal, Certificates, IDs \& Profiles](/_astro/ios_dev_new_key.CqXjPL2A_Z2e9VoX.webp) 3. **Select Apple Push Notifications service (APNs)**, and enter a name for the key ![iOS generate new key name](/_astro/ios_dev_new_key_name.BZRyC9uw_Z28lOPD.webp). Select Continue and on the next page, select Register. 4. Download your new key and save it in a secure place. You can only download it once, so don’t lose it. Then, click Done and you will have a new key. ## Key Id and Team Id [Section titled “Key Id and Team Id”](#key-id-and-team-id) ![iOS Key Id](/_astro/ios_dev_apns_key_id.DO8ALGHs_Z1urET3.webp) ![iOS Team Id](/_astro/ios_dev_apns_key_team_id.DXnbFvHO_Z24SoOd.webp) ## Upload key to Dashboard [Section titled “Upload key to Dashboard”](#upload-key-to-dashboard) To upload APNS key to ConnectyCube Dashboard you need to do the following: 1. Open your ConnectyCube Dashboard at [admin.connectycube.com](https://admin.connectycube.com) 2. Go to **Push notifications** module 3. Switch to **Credentials** tab 4. Upload the newly created APNS key on **APNS Authentication Token (JWT)** form. 5. Enter `key id` and `team id` 6. Press **Upload** ### Links you might find usefull: [Section titled “Links you might find usefull:”](#links-you-might-find-usefull) [How to submit an iOS app to the App Store: Step-by-step guide](/guides/uploading-ios-app-to-store)\ [How to upload app to the Google Play Store: Step-by-step guide](/guides/uploading-android-app-to-store) # Push Notifications > Elevate your iOS 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 user while he is not using your app actively. The following use cases can be covered additionally with push notifications: * send a chat message if recipient is offline (a push notification will be sent automatically) * make a video call with offline opponents (need to send a push notification manually) * request to add a user to contact list (need to send a push notification manually) ## Create APNS certificate [Section titled “Create APNS certificate”](#create-apns-certificate) In order to start working with push notifications functionality you need to do some pre-steps: * Create APNS certificate. Each iOS application that uses Apple Push Notifications must have an APNS certificate. * Upload the APNS certificate to ConnectyCube Dashboard panel, Push Notifications module, Settings tab, Apple Push Notification Service (APNS) section. To get the information on how to create APNS certificates and upload them to Dashboard, please, refer to the [How to generate APNS certificate](/ios/how-to-create-apns-certificate) page. ## Subscribe [Section titled “Subscribe”](#subscribe) In order to start receiving push notifications you need to get the current device push token and subscribe with it on the backend as follows: > Note: You cannot test push notifications in the iOS simulator. You need a physical iOS device and an Apple developer’s account. * SDK v2 ```swift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { UIApplication.shared.registerForRemoteNotifications() return true } ... func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let deviceIdentifier: String = UIDevice.current.identifierForVendor!.uuidString let subscriptionParameters = CreatePushSubscriptionParameters(environment: "development", channel: NotificationsChannels.shared.APNS, udid: deviceIdentifier, platform: "ios", pushToken: deviceToken.base64EncodedString(), bundleIdentifier: nil) ConnectyCube().createPushSubscription(params: subscriptionParameters.getRequestParameters(), successCallback:{ subscriptions in let subscriptionId = subscriptions.first?.id }, errorCallback: { error in }) } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { print("Failed to register for notifications: \(error.localizedDescription)") } } ``` * SDK v1 (deprecated) ```objectivec @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[UIApplication sharedApplication] registerForRemoteNotifications]; return YES; } ... - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { NSString *deviceIdentifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; CYBSubscription *subscription = [CYBSubscription subscription]; subscription.notificationChannel = CYBNotificationChannelAPNS; subscription.deviceUDID = deviceIdentifier; subscription.deviceToken = deviceToken; [CYBRequest createSubscription:subscription successBlock:^(NSArray *subscriptions) { } errorBlock:^(NSError *error) { }]; } - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { NSLog(@"Failed to register for notifications:%@", error); } @end ``` ```swift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { UIApplication.shared.registerForRemoteNotifications() return true } ... func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let deviceIdentifier: String = UIDevice.current.identifierForVendor!.uuidString let subscription: Subscription! = Subscription() subscription.notificationChannel = NotificationChannel.APNS subscription.deviceUDID = deviceIdentifier subscription.deviceToken = deviceToken Request.createSubscription(subscription, successBlock: { (subscriptions) in }) { (error) in } } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { print("Failed to register for notifications: \(error.localizedDescription)") } } ``` > **Note**: The device push token can change, even on the same device and with the same user. Make sure to update subscription every time when you get a new device token. ## Subscribe for VoIP push notifications [Section titled “Subscribe for VoIP push notifications”](#subscribe-for-voip-push-notifications) Normally, the VoIP push notifications are used in applications with video/audio calling capabilities. In the past, a VoIP app had to maintain a persistent network connection with a server to receive incoming calls and other data. This meant writing complex code that sent periodic messages back and forth between the app and the server to keep a connection alive, even when the app wasn’t in use. This technique resulted in frequent device wakes that wasted energy. It also meant that if a user quit the VoIP app, calls from the server could no longer be received. Instead of persistent connections, developers should use the VoIP push notifications. VoIP push notifications are different than regular APNS notifications mainly in how they are setup in the iOS app. iOS SDK provides the [PushKit API](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/OptimizeVoIP.html) to implement VoIP push notifications. * SDK v2 ```swift import PushKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { self.voipRegistration() return true } // Register for VoIP notifications func voipRegistration() { let mainQueue = DispatchQueue.main // Create a push registry object let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue) // Set the registry's delegate to self voipRegistry.delegate = self // Set the push type to VoIP voipRegistry.desiredPushTypes = [PKPushType.voIP] } func pushRegistry(registry: PKPushRegistry!, didUpdatePushCredentials credentials: PKPushCredentials!, forType type: String!) { // Register VoIP push token (a property of PKPushCredentials) with server let deviceIdentifier: String = UIDevice.current.identifierForVendor!.uuidString let subscriptionParameters = CreatePushSubscriptionParameters(environment: "development", channel: NotificationsChannels.shared.APNS_VOIP, udid: deviceIdentifier, platform: "ios", pushToken: credentials.token.base64EncodedString(), bundleIdentifier: nil) ConnectyCube().createPushSubscription(params: subscriptionParameters.getRequestParameters(), successCallback:{ subscriptions in }, errorCallback: { error in }) } } ``` * SDK v1 (deprecated) ```objectivec #import @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication _)application didFinishLaunchingWithOptions:(NSDictionary _)launchOptions { [self voipRegistration]; return YES; } // Register for VoIP notifications - (void) voipRegistration { dispatch_queue_t mainQueue = dispatch_get_main_queue() // Create a push registry object PKPushRegistry \* voipRegistry = [[PKPushRegistry alloc] initWithQueue: mainQueue]; // Set the registry's delegate to self voipRegistry.delegate = self; // Set the push type to VoIP voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; } // MARK: - PKPushRegistryDelegate protocol - (void)pushRegistry:(PKPushRegistry _)registry didUpdatePushCredentials: (PKPushCredentials _)credentials forType:(NSString \*)type { // Register VoIP push token (a property of PKPushCredentials) with server NSString *deviceIdentifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; CYBSubscription *subscription = [CYBSubscription subscription]; subscription.notificationChannel = CYBNotificationChannelAPNSVOIP; subscription.deviceUDID = deviceIdentifier; subscription.deviceToken = credentials.token; [CYBRequest createSubscription:subscription successBlock:^(NSArray *subscriptions) { } errorBlock:^(NSError *error) { }]; } @end ``` ```swift import PushKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { self.voipRegistration() return true } // Register for VoIP notifications func voipRegistration { let mainQueue = dispatch_get_main_queue() // Create a push registry object let voipRegistry: PKPushRegistry = PKPushRegistry(mainQueue) // Set the registry's delegate to self voipRegistry.delegate = self // Set the push type to VoIP voipRegistry.desiredPushTypes = [PKPushTypeVoIP] } // MARK: - PKPushRegistryDelegate protocol func pushRegistry(registry: PKPushRegistry!, didUpdatePushCredentials credentials: PKPushCredentials!, forType type: String!) { // Register VoIP push token (a property of PKPushCredentials) with server let deviceIdentifier: String = UIDevice.current.identifierForVendor!.uuidString let subscription: Subscription! = Subscription() subscription.notificationChannel = NotificationChannel.APNSVOIP subscription.deviceUDID = deviceIdentifier subscription.deviceToken = credentials.token Request.createSubscription(subscription, successBlock: { (subscriptions) in }) { (error) in } } } ``` ## Send push notifications [Section titled “Send push notifications”](#send-push-notifications) You can manually initiate sending of push notifications to user/users on any event in your application. To do so you need to form push notification parameters (payload) and set the push recipients: * SDK v2 ```swift let cubeEventParams = CreateEventParams() cubeEventParams.notificationType = NotificationType.shared.PUSH cubeEventParams.usersIds.add(20) cubeEventParams.eventType = PushEventType.shared.ONE_SHOT var pushParameters = [String : String]() pushParameters["message"] = "Bitcoin trends" pushParameters["ios_badge"] = "2" pushParameters["ios_sound"] = "app_sound.wav" // custom params pushParameters["thread_likes"] = "24" pushParameters["thread_id"] = "678923" cubeEventParams.parameters = pushParameters as! KotlinMutableDictionary ConnectyCube().createPushEvent(event: cubeEventParams.getEventForRequest(), successCallback:{ result in }, errorCallback: { error in }) ``` * SDK v1 (deprecated) ```objectivec CYBEvent *event = [CYBEvent event]; event.notificationType = CYBNotificationTypePush; event.usersIDs = @"21,22"; event.type = CYBEventTypeOneShot; NSMutableDictionary \*pushParameters = [NSMutableDictionary dictionary]; pushParameters[@"message"] = @"Bitcoin trends"; pushParameters[@"ios_badge"] = @"2"; pushParameters[@"ios_sound"] = @"app_sound.wav"; // custom parameters pushParameters[@"thread_likes"] = @"24"; pushParameters[@"thread_id"] = @"678923"; NSError *error = nil; NSData *sendData = [NSJSONSerialization dataWithJSONObject:pushParameters options:NSJSONWritingPrettyPrinted error:&error]; NSString \*jsonString = [[NSString alloc] initWithData:sendData encoding:NSUTF8StringEncoding]; event.message = jsonString; [CYBRequest createEvent:event successBlock:^(NSArray \*events) { } errorBlock:^(NSError \*error) { }]; ``` ```swift let event = Event() event.notificationType = .push event.usersIDs = "20,21" event.type = .oneShot var pushParameters = [String : String]() pushParameters["message"] = "Bitcoin trends" pushParameters["ios_badge"] = "2" pushParameters["ios_sound"] = "app_sound.wav" // custom params pushParameters["thread_likes"] = "24" pushParameters["thread_id"] = "678923" if let jsonData = try? JSONSerialization.data(withJSONObject: pushParameters, options: .prettyPrinted) { let jsonString = String(bytes: jsonData, encoding: String.Encoding.utf8) event.message = jsonString Request.createEvent(event, successBlock: {(events) in }, errorBlock: {(error) in }) } ``` ## Receive push notifications [Section titled “Receive push notifications”](#receive-push-notifications) * SDK v2 ```swift // MARK: - AppDelegate func application(\_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) { } func application(\_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Swift.Void) { } ``` * SDK v1 (deprecated) ```objectivec // MARK: - AppDelegate - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { } - (void)application:(UIApplication *)__unused application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { } ``` ```swift // MARK: - AppDelegate func application(\_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) { } func application(\_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Swift.Void) { } ``` ## Receive VoIP push notifications [Section titled “Receive VoIP push notifications”](#receive-voip-push-notifications) * SDK v2 ```swift // MARK: - PKPushRegistryDelegate protocol // Handle incoming pushes func pushRegistry(registry: PKPushRegistry!, didReceiveIncomingPushWithPayload payload: PKPushPayload!, forType type: String!) { } ``` * SDK v1 (deprecated) ```objectivec // MARK: - PKPushRegistryDelegate protocol // Handle incoming pushes - (void)pushRegistry:(PKPushRegistry _)registry didReceiveIncomingPushWithPayload:(PKPushPayload _)payload forType:(NSString \*)type { } ``` ```swift // MARK: - PKPushRegistryDelegate protocol // Handle incoming pushes func pushRegistry(registry: PKPushRegistry!, didReceiveIncomingPushWithPayload payload: PKPushPayload!, forType type: String!) { } ``` ## Unsubscribe [Section titled “Unsubscribe”](#unsubscribe) The unregister operation allows you to stop receiving push notifications on a selected device: * SDK v2 ```swift UIApplication.shared.unregisterForRemoteNotifications() // Unregister from server // subscriptionId - subscription id from createPushSubscription() ConnectyCube().deletePushSubscription(subscriptionId: subscriptionId, successCallback:{ }, errorCallback: { error in }) ``` * SDK v1 (deprecated) ```objectivec [[UIApplication sharedApplication] unregisterForRemoteNotifications]; // Unregister from server NSString *deviceIdentifier = [UIDevice currentDevice].identifierForVendor.UUIDString; [CYBRequest unregisterSubscriptionForUniqueDeviceIdentifier:deviceIdentifier successBlock:^() { } errorBlock:^(NSError *error) { }]; ``` ```swift UIApplication.shared.unregisterForRemoteNotifications() // Unregister from server let deviceIdentifier = UIDevice.current.identifierForVendor!.uuidString Request.unregisterSubscription(forUniqueDeviceIdentifier: deviceIdentifier, successBlock: { }) { (error) in } ``` ## Unsubscribe VoIP push notifications [Section titled “Unsubscribe VoIP push notifications”](#unsubscribe-voip-push-notifications) * SDK v2 ```swift // MARK: - PKPushRegistryDelegate protocol func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { } ``` * SDK v1 (deprecated) ```objectivec // MARK: - PKPushRegistryDelegate protocol - (void)pushRegistry:(PKPushRegistry \*)registry didInvalidatePushTokenForType:(PKPushType)type; } ``` ```swift // MARK: - PKPushRegistryDelegate protocol func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { } ``` # Address Book API > Effortlessly upload, sync, and access ConnectyCube users from your phone contacts in your application with Address Book API. AddressBook API allows to store the address book contacts on server and notify that someone from the address book is registered in ConnectyCube. ## Upload address book [Section titled “Upload address book”](#upload-address-book) Upload contacts from the address book on server and / or update list of contacts if it was changed. With ‘Upload address book’ request, 3 different actions can be performed: * create contact(s) * updated contact(s) * delete contact(s) ###### Endpoint [Section titled “Endpoint”](#endpoint) ```plaintext POST https://api.connectycube.com/address_book ``` ###### Parameters [Section titled “Parameters”](#parameters) | Parameter | Data type | Description | | --------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | contacts | hash | Array of contacts hashes generated as ‘contact name’ and ‘phone’:* **name** - required only for create/update action. Validation: length up to 255 symbols* **phone** - required for all types of actions. Validation: min 10 and max 15 chars* **destroy** - remove contact from address book. Should be set to ‘1’ | | force | integer | Re-write the whole address book on server. Should be set to ‘1’ | | udid | string | User’s device identifier. If specified - all operations will be performed for the specified device only. If not specified - all operations will be performed across all devices. Validation: length up to 64 symbols | ###### Request example [Section titled “Request example”](#request-example) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"contacts": [{"name":"Halldor Kun ","phone":"9506728854"},{"name":"Lidija Halil","phone":"5309413409"}], "force": 1, "udid": "d6236da0-f941-11e8-8eb2-f2801f1b9fd1"}' \ https://api.connectycube.com/address_book ``` ###### Response [Section titled “Response”](#response) ```json { "deleted" : 2, "rejected" : { "1" : [ "Invalid fields set" ], "1" : [ "Length of 'phone' field should be min: 10 and max: 15." ], "3" : [ "Length of 'name' field should be min: 1 and max: 255." ] }, "created" : 25, "updated" : 5 } ``` ## Retrieve address book [Section titled “Retrieve address book”](#retrieve-address-book) Retrieve the contacts from uploaded address book. ###### Endpoint [Section titled “Endpoint”](#endpoint-1) ```plaintext GET https://api.connectycube.com/address_book ``` ###### Parameters [Section titled “Parameters”](#parameters-1) | Parameter | Description | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | udid | User’s device identifier. If specified - contacts from that device will be retrieved. If not specified - contacts from the global address book will be retrieved | ###### Request example [Section titled “Request example”](#request-example-1) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/address_book ``` ###### Response [Section titled “Response”](#response-1) ```json [ { "phone" : "61412675500", "name" : "Dacia McCombie" }, { "phone" : "61736404001", "name" : "Oliver Knox" }, { "phone" : "61338804529", "name" : "Taila Buckley" } ] ``` ## Retrieve already registered contacts/users [Section titled “Retrieve already registered contacts/users”](#retrieve-already-registered-contactsusers) Retrieve users who are in the address book and are registered in ConnectyCube. ###### Endpoint [Section titled “Endpoint”](#endpoint-2) ```plaintext GET https://api.connectycube.com/address_book/registered_users ``` ###### Parameters [Section titled “Parameters”](#parameters-2) | Parameter | Description | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | udid | User’s device identifier. If specified - only contacts from the device will be checked. If not specified - global address book will be checked | | compact | Defines information retrieved for each contact:* `compact`=1 - only user’s ID, phone and name (with which the user was saved in the Address book) will be retrieved* `compact`=0 - all fields from user’s profile will be returned | ###### Request example [Section titled “Request example”](#request-example-2) ```bash curl -X GET \ -H "CB-Token: " \ -d 'udid=A337E8A2-80AD-8ABA-9F5D-579EFF6BACAB&compact=1' \ https://api.connectycube.com/address_book/registered_users ``` ###### Response [Section titled “Response”](#response-2) ```json { "items": [ { "user": { "id": 212, "phone": "614126755001", "address_book_name": "Dacia McCombie" } }, { "user": { "id": 8923, "phone": "478902328938", "address_book_name": "Oliver Knox" } } ] } ``` # Authentication and Authorization API > Discover ConnectyCube's Authentication and Authorization API for secure access to functionalities. 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. **By default, session token is valid for 2 hours**. Any API request prolongs the token validity for another 2 hours. 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: ###### Endpoint [Section titled “Endpoint”](#endpoint) ```plaintext POST https://api.connectycube.com/session ``` ###### Parameters [Section titled “Parameters”](#parameters) | Parameter | Required | Description | | ------------------------------- | :-------------------------: | ------------------------------------------------------------------------------------ | | user\[login] | Yes\* | User’s login | | user\[email] | Yes\* | User’s email | | user\[password] | Yes | User’s Password | | provider | Optional | Possible values: **facebook**, **twitter**, **firebase\_phone**, **firebase\_email** | | keys\[token] | Optional | Social network provider’s access token | | keys\[secret] | Optional, for Twitter only | Social network provider’s access token secret | | firebase\_phone\[project\_id] | Optional, for Firebase only | Firebase project ID - the unique identifier for your Firebase project | | firebase\_phone\[access\_token] | Optional, for Firebase only | Firebase user’s ID token | | firebase\_email\[project\_id] | Optional, for Firebase only | Firebase project ID - the unique identifier for your Firebase project | | firebase\_email\[access\_token] | Optional, for Firebase only | Firebase user’s ID token | There are four available sets of data to specify when create a session with a user: * `login` and `password` * `email` and `password` * `provider` + `keys[token]` and `keys[secret]` - when sign up with Facebook or Twitter * `provider` + `firebase_phone[project_id]` and `firebase_phone[access_token]` - when sign up with a phone number * `provider` + `firebase_email[project_id]` and `firebase_email[access_token]` - when sign up with a email ###### Request example [Section titled “Request example”](#request-example) ```bash curl -X POST \ -H "Content-Type: application/json" \ -d '{"application_id": "1", "auth_key": "29WfrNWdvkhmX6V", "timestamp": "1544010993", "user":{"login": "john", "password": "11111111"}}' \ https://api.connectycube.com/session ``` ###### Response [Section titled “Response”](#response) ```json { "session": { "id": 111, "user_id": 111, "application_id": 1, "token": "83153a14fb2df777c2f866178902a4bb15000001", "ts": 1544010993, "created_at": "2018-12-05T11:58:02Z", "updated_at": "2018-12-05T11:58:02Z", "user": { "id": 81, "full_name": "John Smith", "email": "johnsmith@domain.com", "login": "john", "phone": "380665787842", "website": null, "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": "", "blob_id": null, "avatar": "", "user_tags": null } } } ``` **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). ## Create session with Guest User [Section titled “Create session with Guest User”](#create-session-with-guest-user) Session can be created with temporary guest user, user will be automatically created, session with guest user **valid for 1 day** after user will be automatically deleted.\ **NOTE:** Guest user can’t be authorized by login/email password ###### Parameters [Section titled “Parameters”](#parameters-1) | Parameter | Required | Description | | ----------------- | :------: | ------------------------------------------------- | | user\[guest] | No | Define creating session with temporary guest user | | user\[full\_name] | No | Set guest user **full\_name** | ###### Request example [Section titled “Request example”](#request-example-1) ```bash curl -X POST \ -H "Content-Type: application/json" \ -d '{"application_id": "1", "auth_key": "29WfrNWdvkhmX6V", "timestamp": "1678966390", "user":{"guest": "1", "full_name": "Olof Shodger"}}' \ https://api.connectycube.com/session ``` ###### Response [Section titled “Response”](#response-1) ```json { "session": { "application_id": 1, "token": "3E5CDBE3743E33DC820E012BF81BCD77ABFF", "created_at": "2023-03-16T11:33:10Z", "updated_at": "2023-03-16T11:33:10Z", "ts": 1678966390, "user_id": 900265, "id": 900265, "user": { "_id": "6412fe76d6600d1d3d67877d", "id": 900265, "created_at": "2023-03-16T11:33:10Z", "updated_at": "2023-03-16T11:33:10Z", "login": "guest_login_328A465366359BDDB984904D49EAD187B524", "full_name": "Olof Shodger", "is_guest": true, "last_request_at": null, "timezone": null, "email": null, "phone": "", "website": null, "twitter_id": null, "external_user_id": null, "facebook_id": null, "custom_data": null, "user_tags": null, "avatar": null, "external_id": null } } } ``` ## Get information about session [Section titled “Get information about session”](#get-information-about-session) Retriving information about the current (active) session from token specified as a header. ###### Endpoint [Section titled “Endpoint”](#endpoint-1) ```plaintext GET https://api.connectycube.com/session ``` ###### Request example [Section titled “Request example”](#request-example-2) ```bash curl -X GET \ -H "CB-Token: "\ https://api.connectycube.com/session ``` ###### Response [Section titled “Response”](#response-2) ```json { "session": { "id": 219606, "user_id": 47592, "application_id": 212, "token": "66e8aef2757404f3c7c2488f17ebdd8b8a0000d4", "ts": 1544083714, "created_at": "2018-12-06T08:08:35Z", "updated_at": "2018-12-06T08:08:35Z", "user": { "id": 47592, "full_name": "John Smith", "email": "johnsmith@gmail.com", "login": "johnsmith", "phone": null, "website": null, "created_at": "2018-11-23T09:42:36Z", "updated_at": "2018-12-06T08:08:35Z", "last_request_at": "2018-12-06T08:08:35Z", "external_user_id": null, "facebook_id": null, "twitter_id": null, "blob_id": null, "custom_data": null, "avatar": null, "user_tags": null } } } ``` ## Destroy session [Section titled “Destroy session”](#destroy-session) The request destroys all of the data associated with the current session. ###### Endpoint [Section titled “Endpoint”](#endpoint-2) ```plaintext DELETE https://api.connectycube.com/session ``` ###### Request example [Section titled “Request example”](#request-example-3) ```bash curl -X DELETE \ -H "CB-Token: "\ https://api.connectycube.com/session ``` ###### Response [Section titled “Response”](#response-3) ```plaintext Status: 200 ``` # Chat API > Integrate powerful chat functionality into your app effortlessly with our versatile Chat APIs. Enhance user communication and engagement. ConnectyCube Chat API consists of 2 parts: REST API and Real-Time (XMPP) API. This document describes the REST API part. To get an info about Real-Time (XMPP) API - please refer to [Real-Time (XMPP) API documentation page](/server/realtime_api). ## Features [Section titled “Features”](#features) Robust, quick, ‘keep-alive’ connection. Unlimited concurrent connections thanks to ConnectyCube auto-scalable AWS powered cloud chat servers infrastructure. * **1:1 chat** (private user to user chat) * **Group chat** (unlimited chat rooms) * **Incoming chat alerts** (push notifications) for offline users * **File attachments** - cloud hosted, so both users don’t have to be online to receive messages. Allow users to send photos, videos and other files. * **Voice chat / call** – enable 1:1 voice calls in the application * **Video chat / call** – enable video chats in the application * **NAT traversal** – our TURN server and optimization system take care of NAT traversal and traffic compression making sure your chat, voice and video traffic reaches all users across the networks with different configurations ## Chat dialog model [Section titled “Chat dialog model”](#chat-dialog-model) Chat Dialog model describes a conversation between users. **Chat model structure:** | Field name | Description | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | \_id | ID of dialog. Generated automatically by server after dialog is created | | user\_id | ID of dialog’s owner | | created\_at | Unix timestamp. Date & time when record was created. System creates this parameter automatically | | description | Dialog description | | updated\_at | Unix timestamp. Date & time when record was updated. System creates this parameter automatically | | type | Type of the dialog: type 1 - broadcast, type 2 - group chat, type 3 - private chat, type 4 - public chat | | name | Name of a group chat | | photo | Image of the chat. Can contain a link to a file in Connectycube storage, Custom Objects module or just a web link | | xmpp\_room\_jid | Jaber identifier of XMPP room for group chat to connect. Not applicable for private chat (type 3). Generated automatically by server after dialog is created | | occupants\_ids | List of users in the chat. Applicable for private and group chats only | | occupants\_count | Number of users in the public chat | | admins\_ids | List of users who have admin rights | | last\_message | Last message sent in the chat dialog | | last\_message\_id | ID of the last message in the chat dialog | | last\_message\_user\_id | ID of user who sent the last message in the chat dialog | | last\_message\_date\_sent | Date & time when the last message in the chat dialog was sent | | last\_message\_status | Last sent message state. Will be ‘null’ if last message is sent by other user (not you). Will be one of the ‘sent’, ‘delivered’, ‘read’ if last message is sent by you | | unread\_messages\_count | Number of unread messages in the dialog for current user | | pinned\_messages\_ids | IDs of messages pinned in the chat dialog | | extensions | Object of allows fields | | permissions | Object with permissions props (`allow_preview`) | ## Message model [Section titled “Message model”](#message-model) Chat Message model describes a chat message in a chat dialog. **Message model structure:** | Field name | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | \_id | Message ID. Generated automatically by server after message is created | | created\_at | Unix timestamp. Created by system automatically | | updated\_at | Unix timestamp. Created by system automatically and is updated when the message is updated or read | | chat\_dialog\_id | ID of the dialog the current chat message is related to. Generated automatically by server after message is created | | message | Message body | | date\_sent | Date when massage has been sent | | sender\_id | ID of a user who sent a message | | recipient\_id | ID of user who received a message | | read\_ids | List of IDs of users who read the messages | | delivered\_ids | List of users IDs a message was delivered to | | views\_count | Number of views of the messages. Available for broadcast (type 1) and public chats (type 1) | | attachments | List of attachments. Each attachment object contains 3 keys: type (audio / video / image), id (link to file in Connectycube storage), url (link to file in the Internet) | | Custom parameters | Chat message can contain any other user custom parameters (additional metadata) | | reactions | Reactions object with total and own reactions | ## Roles and privileges [Section titled “Roles and privileges”](#roles-and-privileges) There are different roles of users in a chat dialog: **Regular user** - user who can send and receive messages. **Admin** - advanced user with moderation privileges. **Super admin** - chat owner (creator) with all possible privileges. | Roles | Private chat | Group chat | Public chat | Broadcast | | ------------ | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | Regular user | * send/receive messages* delete own message (one side/both sides)* delete chat dialog for themselves | * send/receive messages* delete own message (one side/both sides)* delete chat dialog for themselves* add more users* change name, photo, description | * send/receive messages* delete own message (one side/both sides)* delete chat dialog for themselves (or unsubscribe) | * receive messages* delete chat dialog for themselves | | Admin | - | * delete any messages for everyone* pin messages* add/remove users* regular user privileges | * change name, description, photo* delete any messages for everyone* pin messages* block/unblock users* regular user privileges | * change name, description, photo* delete any messages for everyone* pin messages* block/unblock users | | Super admin | - | * add/remove admins* delete dialog for everyone* regular admin privileges | * add/remove admins* delete dialog for everyone* regular admin privileges | * add/remove admins* delete dialog for everyone | ## Retrieve chat dialogs [Section titled “Retrieve chat dialogs”](#retrieve-chat-dialogs) Retrieve all the dialogs associated with the current user. Server returns all dialogs where the current user’s ID is in the list of ‘occupants\_ids’ field (type 2, type 3) OR in the list of ‘occupants\_count’ (type 4). Server identifies user ID with a session token. If there is a **broadcast chat** in the app, this chat will be retrieved for all users. ###### Endpoint [Section titled “Endpoint”](#endpoint) ```bash GET https://api.connectycube.com/chat/Dialog ``` ###### Parameters [Section titled “Parameters”](#parameters) | Operator | Applied fields | Description | Value example | | ---------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- | | {field\_name} | \_id, type, name, last\_message\_date\_sent, created\_at, updated\_at, extensions\[friend\_id] | Retrieve dialogs with the exact data specified in the request | type=2 | | {field\_name}\[{search\_operator}] | \_id, type, name, last\_message\_date\_sent, created\_at, updated\_at, extensions\[friend\_id] | Retrieve dialogs with the exact data specified in the request with applying additional filters. Available filters: **lt** (Less Than operator), **lte** (Less Than or Equal to operator), **gt** (Greater Than operator), **gte** (Greater Than or Equal to operator), **ne** (Not Equal to operator), **in** (Contained IN array operator), **nin** (Not contained IN array), **all** (ALL contained IN array), **ctn** (Contains substring operator) | type\[in]=2,3 | | limit | standalone operator | Setting of limit for a number of search results to display. Default - 100 | limit=70 | | skip | standalone operator | Skip the defined number of records in the search results. By default all records are shown | skip=20 | | count | standalone operator | Count search results. Response will contain only count of records found. | count=1 | | sort\_desc/sort\_asc | last\_message\_date\_sent | Sorting of query result set in ascending / descending order | sort\_desc=date\_sent | ###### Request Example [Section titled “Request Example”](#request-example) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/chat/Dialog ``` ###### Response [Section titled “Response”](#response) ```json { "total_entries": 4, "skip": 0, "limit": 100, "items": [ { "_id": "5c094c0ce588ce6856f87422", "admins_ids": [29085], "created_at": "2018-12-06T16:19:24Z", "description": "Cute chat", "last_message": "nope?", "last_message_date_sent": 1544452627, "last_message_id": "5c0e7a13e588ce6b6f3ebf80", "last_message_user_id": 29086, "last_message_status": "sent", "name": "hello kitty", "occupants_ids": [ 29085, 29086, 29087 ], "photo": null, "pinned_messages_ids": ["5c0e7a13e588ce6b6f3ebf80"], "type": 2, "updated_at": "2018-12-10T14:37:07Z", "user_id": 29085, "unread_messages_count": 1, "xmpp_room_jid": "105_5c094c0ce588ce6856f87422@muc.chatstage.connectycube.com", "extensions": null, "permissions": null }, ... ] } ``` ## Create a dialog [Section titled “Create a dialog”](#create-a-dialog) There are four types of dialogs available to be created: * type=1 - **broadcast** - type of 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. Max users count - all chat users Roles: 1 Super Admin (chat creator) and up to 5 admins * type=2 - **group chat** - type of chat where one user creates chat with other users. More users can be added later. All messages in chat have read / delivered status Max users count - according to [Plans](https://connectycube.com/pricing/) Roles: 1 Super Admin (chat creator) and up to 5 admins * type=3 - **private chat** between 2 users. If user sends a chat message to some user and private dialog isn’t created - it will be created automatically with the first chat message. All messages in chat have read / delivered status Max users count - 2 Roles: two regular users * type=4 - **public chat** - type of chat where any user can subscribe via public link. Max users count - according to [Plans](https://connectycube.com/pricing/) Roles: 1 Super Admin (chat creator) and up to 5 admins ###### Endpoint [Section titled “Endpoint”](#endpoint-1) ```plaintext POST https://api.connectycube.com/chat/Dialog ``` ###### Parameters [Section titled “Parameters”](#parameters-1) | Parameter | Required | Description | | -------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | type | Yes | Type of new dialog. Possible values: **1** - broadcast, **2** - group chat, **3** - private 1-to-1 chat, **4** - public chat | | occupants\_ids | Yes, for group (type 2) and private (type 3) chats | List of users who are participants of the chat. The current user id will be added automatically. Example: `occupants_ids=45,78` | | admins\_ids | No | Chat admins. Example: `admins_ids=65,89` | | name | Yes, for broadcast (type 1), public (type 4) and group (type 2) chats | Name of a dialog. Ignored for private chats (type 3) | | photo | No | Dialog image. Ignored for private chats (type 3) | | description | No | Dialog description | | extensions | No | Object of allows fields | | permissions | No | Object with permissions props | ### Dialog permissions [Section titled “Dialog permissions”](#dialog-permissions) Dialog 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. ### Dialog metadata [Section titled “Dialog metadata”](#dialog-metadata) A dialog can have up to 3 custom sub-fields to store additional information that can be linked to chat. These parameters also can be used as a filter for retrieving dialogs. To start using extensions, allowed fields should be added first. Go to [Admin panel](https://admin.connectycube.com) > Chat > Custom Fields and provide allowed custom fields. ![Dialog Extensions fields configuration example](/_astro/dialog_custom_params.CrGT0s8Z_1XWSCw.webp) When create a dialog, the `extensions` field object must contain allowed fields only. Others fields will be ignored. The values will be casted to string. When remove custom field in Admin panel, this field will be removed in all dialogs respectively. ###### Create dialogs with extensions request example [Section titled “Create dialogs with extensions request example”](#create-dialogs-with-extensions-request-example) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"type":2, "name":"Night party", "occupants_ids": [29085,29087], "extensions": { "location": "Sun bar", "unknown_field": "will be ignored" }, "permissions": { "allow_preview": true }}' \ https://api.connectycube.com/chat/Dialog ``` ###### Response [Section titled “Response”](#response-1) ```json { "_id": "5c091060e588ce59fdf873dd", "admins_ids": [], "created_at": "2018-12-06T12:04:48Z", "description": null, "last_message": null, "last_message_date_sent": null, "last_message_id": null, "last_message_user_id": null, "last_message_status": null, "name": "Night party", "occupants_ids": [29085, 29087], "photo": null, "pinned_messages_ids": [], "type": 2, "updated_at": "2018-12-06T12:04:48Z", "user_id": 29085, "unread_messages_count": 0, "xmpp_room_jid": "105_5c091060e588ce59fdf873dd@muc.chatstage.connectycube.com", "extensions": { "location": "Sun bar", }, "permissions": { "allow_preview": true } } ``` ###### Update dialogs with extensions request example [Section titled “Update dialogs with extensions request example”](#update-dialogs-with-extensions-request-example) ```bash curl -X PUT \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{ "extensions": { "location": "Sun bar", "unknown_field": "will be ignored" }}' \ https://api.connectycube.com/chat/Dialog/5c091060e588ce59fdf873dd ``` ###### Response [Section titled “Response”](#response-2) ```json { "_id": "5c091060e588ce59fdf873dd", "admins_ids": [], "created_at": "2018-12-06T12:04:48Z", "description": null, "last_message": null, "last_message_date_sent": null, "last_message_id": null, "last_message_user_id": null, "last_message_status": null, "name": "Night party", "occupants_ids": [29085, 29087], "photo": null, "pinned_messages_ids": [], "type": 2, "updated_at": "2018-12-06T12:04:48Z", "user_id": 29085, "unread_messages_count": 0, "xmpp_room_jid": "105_5c091060e588ce59fdf873dd@muc.chatstage.connectycube.com", "extensions": { "location": "Sun bar", }, "permissions": { "allow_preview": true } } ``` To reset extensions params in dialogs just set `extensions` to `null` ###### Search dialogs by extensions request example [Section titled “Search dialogs by extensions request example”](#search-dialogs-by-extensions-request-example) ```bash curl -X GET \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d 'extensions[location]=Sunbar' \ https://api.connectycube.com/chat/Dialog ``` ###### Response [Section titled “Response”](#response-3) ```json { "total_entries": 1, "skip": 0, "limit": 100, "items": [ { "_id": "5c091060e588ce59fdf873dd", "admins_ids": [], "created_at": "2018-12-06T12:04:48Z", "description": null, "last_message": null, "last_message_date_sent": null, "last_message_id": null, "last_message_user_id": null, "last_message_status": null, "name": "Night party", "occupants_ids": [29085, 29087], "photo": null, "pinned_messages_ids": [], "type": 2, "updated_at": "2018-12-06T12:04:48Z", "user_id": 29085, "unread_messages_count": 0, "xmpp_room_jid": "105_5c091060e588ce59fdf873dd@muc.chatstage.connectycube.com", "extensions": { "location": "Sun bar", } } ] } ``` ## Update a dialog [Section titled “Update a dialog”](#update-a-dialog) Based on the roles and privileges, users with different roles can update different data. ###### Endpoint [Section titled “Endpoint”](#endpoint-2) ```plaintext PUT https://api.connectycube.com/chat/Dialog/{dialog_id} ``` ###### Parameters [Section titled “Parameters”](#parameters-2) | Parameter | Description | Value example | | --------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | occupants\_ids | Add / delete occupants in the dialog | * {“push\_all”: {“occupants\_ids”: \[“id\_1”,“id\_2”]}}* “pull\_all”: {“occupants\_ids”: \[“id\_1”,“id\_2”]} | | pinned\_messages\_ids | Make messages pinned / unpinned in the chat history | * {“push\_all”: {“pinned\_messages\_ids”: \[“pinned\_message\_id\_1”, “pinned\_message\_id\_2”]}}* {“pull\_all”: {“pinned\_messages\_ids”: \[“pinned\_message\_id\_1”, “pinned\_message\_id\_2”]}} | | admins\_ids | Add / delete admins in the dialog | * {“push\_all”: {“admins\_ids”: \[admin\_id\_1]}}* {“pull\_all”: {“admins\_ids”: \[admin\_id\_1]}} | | name | Name of the dialog | {“name”: “dialog\_name”} | | photo | Photo of the dialog | {“photo”: “image.jpg”} | | description | Description of the dialog | {“description”: “Chat for Java developers”} | | extensions | Object with allowed fields | {“extensions”: {“status”: “CatSitter”}} | | permissions | Permissions object | {“permissions”: {“allow\_preview”: false}} | ###### Request example [Section titled “Request example”](#request-example-1) ```bash curl -X PUT \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"name":"Crossfit2","push_all":{"occupants_ids":[29088],"pinned_messages_ids":["5c123fdce588ce064043f53a"]},"photo":"gym2.jpeg", "permissions": { "allow_preview": false }}' \ https://api.connectycube.com/chat/Dialog/5c123f75e588ce063e43f541 ``` ###### Response [Section titled “Response”](#response-4) ```json { "_id": "5c123f75e588ce063e43f541", "admins_ids": [], "created_at": "2018-12-13T11:16:05Z", "description": "strong man", "last_message": "cool", "last_message_date_sent": 1544699868, "last_message_id": "5c123fdce588ce064043f53a", "last_message_user_id": 29085, "last_message_status": "read", "name": "Crossfit2", "occupants_ids": [29085, 29086, 29087, 29088], "photo": "gym2.jpeg", "pinned_messages_ids": ["5c123fdce588ce064043f53a"], "type": 2, "updated_at": "2018-12-13T11:21:21Z", "user_id": 29085, "unread_messages_count": 0, "xmpp_room_jid": "105_5c123f75e588ce063e43f541@muc.chatstage.connectycube.com", "extensions": null, "permissions": { "allow_preview": false } } ``` ## Delete dialog [Section titled “Delete dialog”](#delete-dialog) Each user from ‘occupant\_ids’ can remove the dialog. Dialog will be removed for the current user only and will continue to be available for other users in the group. To remove a dialog completely, set `force=1` parameter in the DELETE request. Only **owner** (in private dialog `type=3` **owner** or **admin**) can do this. ### Delete single dialog [Section titled “Delete single dialog”](#delete-single-dialog) ###### Endpoint [Section titled “Endpoint”](#endpoint-3) ```plaintext DELETE https://api.connectycube.com/chat/Dialog/{dialog_id} ``` ###### Request example [Section titled “Request example”](#request-example-2) ```bash curl -X DELETE \ -H "CB-Token: " \ https://api.connectycube.com/chat/Dialog/5c091060e588ce59fdf873dc ``` ###### Response [Section titled “Response”](#response-5) ```json 200 OK ``` ### Delete several dialogs [Section titled “Delete several dialogs”](#delete-several-dialogs) ###### Endpoint [Section titled “Endpoint”](#endpoint-4) ```plaintext DELETE https://api.connectycube.com/chat/Dialog/{dialog1_id},{dialog2_id} ``` ###### Request example [Section titled “Request example”](#request-example-3) ```bash curl -X DELETE \ -H "CB-Token: " \ https://api.connectycube.com/chat/Dialog/23thnu15e4b077ddd43e7db7,499abe15e4b077ddd43e7b5h ``` ###### Response [Section titled “Response”](#response-6) ```json { "SuccessfullyDeleted": { "ids": ["23thnu15e4b077ddd43e7db7"] }, "NotFound": { "ids": ["499abe15e4b077ddd43e7b5h"] }, "WrongPermissions": { "ids": ["5e394e7bca8bf410bc8017b0"] } } ``` ## Clear dialog history [Section titled “Clear dialog history”](#clear-dialog-history) This request will clear all messages in the dialog for current user, but not for other users. To clear all messages for each user form dialog completely, set `force=1` parameter in the DELETE request. Only **owner** (in private dialog `type=3` **owner** or **admin**) can do this. ###### Endpoint [Section titled “Endpoint”](#endpoint-5) ```plaintext DELETE https://api.connectycube.com/chat/Dialog/clearHistory/{dialog_id} ``` ###### Request example [Section titled “Request example”](#request-example-4) ```bash curl -X DELETE \ -H "CB-Token: " \ https://api.connectycube.com/chat/Dialog/clearHistory/5c091060e588ce59fdf873dc ``` ###### Response [Section titled “Response”](#response-7) ```json 200 OK ``` ## Subscribe to public dialog [Section titled “Subscribe to public dialog”](#subscribe-to-public-dialog) Add user to the list of occupants. Applicable for public dialogs (type=4) only. ###### Endpoint [Section titled “Endpoint”](#endpoint-6) ```plaintext POST https://api.connectycube.com/chat/Dialog/{dialog_id}/subscribe ``` ###### Request example [Section titled “Request example”](#request-example-5) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ https://api.connectycube.com/chat/Dialog/5c091822e588ce6856f873de/subscribe ``` ###### Response [Section titled “Response”](#response-8) ```json { "_id": "5c091822e588ce6856f873de", "admins_ids": [], "created_at": "2018-12-06T12:37:54Z", "description": "good news", "last_message": null, "last_message_date_sent": null, "last_message_id": null, "last_message_user_id": null, "last_message_status": "read", "name": "Public chat", "occupants_count": 2, "occupants_ids": [], "photo": null, "pinned_messages_ids": [], "type": 4, "updated_at": "2018-12-06T12:40:03Z", "user_id": 28926, "unread_messages_count": null, "xmpp_room_jid": "105_5c091822e588ce6856f873de@muc.chatstage.connectycube.com", "extensions": null, "permissions": null } ``` ## Unsubsribe from public dialog [Section titled “Unsubsribe from public dialog”](#unsubsribe-from-public-dialog) Remove user from the list of occupants. Applicable for public dialogs (type=4) only. ###### Endpoint [Section titled “Endpoint”](#endpoint-7) ```plaintext DELETE https://api.connectycube.com/chat/dialog/{dialog_id}/subscribe ``` ###### Request example [Section titled “Request example”](#request-example-6) ```bash curl -X DELETE \ -H "CB-Token: " \ https://api.connectycube.com/chat/dialog/5c091822e588ce6856f873de/subscribe ``` ###### Response [Section titled “Response”](#response-9) ```json 200 OK ``` ## 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. ###### Endpoint [Section titled “Endpoint”](#endpoint-8) ```plaintext PUT https://api.connectycube.com/chat/Dialog/{dialog_id}/admins ``` ###### Parameters [Section titled “Parameters”](#parameters-3) | Parameter | Description | Value example | | -------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------- | | push\_all\[admins\_ids]\[] | Add admin(s) to the chat. Several admin IDs can be specified separated by comma | {“push\_all”: {“admins\_ids”:\[admin\_id\_1]}} | | pull\_all\[admins\_ids]\[] | Remove admin(s) from the chat. Several admin IDs can be specified separated by comma | {“pull\_all”:{“admins\_ids”: \[admin\_id\_1]}} | ###### Request example [Section titled “Request example”](#request-example-7) ```bash curl -X PUT \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"push_all": {"admins_ids": [29087]}}' \ https://api.connectycube.com/chat/Dialog/5c093376e588ce6856f873fe/admins ``` ###### Response [Section titled “Response”](#response-10) ```json { "_id": "5c093376e588ce6856f873fe", "admins_ids": [29087], "created_at": "2018-12-06T14:34:30Z", "description": "admins only", "last_message": null, "last_message_date_sent": null, "last_message_id": null, "last_message_user_id": null, "last_message_status": "delivered", "name": "Friday party", "occupants_ids": [29085, 29086, 29087], "photo": null, "pinned_messages_ids": [], "type": 2, "updated_at": "2018-12-06T14:42:25Z", "user_id": 29085, "unread_messages_count": null, "xmpp_room_jid": "105_5c093376e588ce6856f873fe@muc.chatstage.connectycube.com", "extensions": null, "permissions": null } ``` ## Update notifications settings [Section titled “Update notifications settings”](#update-notifications-settings) 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 if notifications setting wasn’t changed. ###### Endpoint [Section titled “Endpoint”](#endpoint-9) ```plaintext PUT https://api.connectycube.com/chat/Dialog/{dialog_id}/notifications ``` ###### Parameters [Section titled “Parameters”](#parameters-4) | Parameter | Value | Description | | --------- | ------ | ----------------------------- | | enabled | 0 or 1 | Enable/ Disable notifications | ###### Request example [Section titled “Request example”](#request-example-8) ```bash curl -X PUT \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"enabled":"0"}' https://api.connectycube.com/chat/Dialog/5c093376e588ce6856f873fe/notifications ``` ###### Response [Section titled “Response”](#response-11) ```json { "notifications": { "enabled": 0 } } ``` ## 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. ###### Endpoint [Section titled “Endpoint”](#endpoint-10) ```plaintext GET https://api.connectycube.com/chat/Dialog/{dialog_id}/notifications ``` ###### Request example [Section titled “Request example”](#request-example-9) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/chat/Dialog/5c093376e588ce6856f873fe/notifications ``` ###### Response [Section titled “Response”](#response-12) ```json { "notifications": { "enabled": 0 } } ``` ## 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. ###### Endpoint [Section titled “Endpoint”](#endpoint-11) ```plaintext GET https://api.connectycube.com/chat/Dialog/{dialog_id}/occupants ``` ###### Parameters [Section titled “Parameters”](#parameters-5) | Operator | Description | Value example | | -------- | ------------------------------------------------------------------------------------------ | ------------- | | limit | Setting of limit for a number of search results to display. Default - 100 | limit=70 | | skip | Skip the defined number of records in the search results. By default all records are shown | skip=20 | ###### Request example [Section titled “Request example”](#request-example-10) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/chat/Dialog/5c093376e588ce6856f873fe/occupants ``` ###### Response [Section titled “Response”](#response-13) ```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" } ... ] } ``` ###### Response [Section titled “Response”](#response-14) ```json { "notifications": { "enabled": 1 } } ``` ## Retrieve messages [Section titled “Retrieve messages”](#retrieve-messages) Retrieve all chat messages within a particular dialog. For **broadcast chat** (type=1) all users within the app can see messages from the dialog. To retrieve chat messages from **group chat** (type=2) and **privat chat** (type=3) user’s IDs should be in `occupants_ids` field, whereas for **public chat** (type=4) user’s IDs should belong to `occupants_count` field. Server will return dialog’s chat messages sorted ascending by `date_sent` field (default sort is applied and can be changed if required). **Note:** All retrieved chat messages will be marked as read after the request. ###### Endpoint [Section titled “Endpoint”](#endpoint-12) ```plaintext GET https://api.connectycube.com/chat/Message ``` ###### Parameters [Section titled “Parameters”](#parameters-6) | Operator | Applied fields | Description | Value example | | ---------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | | {field\_name} | \_id, message, date\_sent, sender\_id, recipient\_id, attachments.type, updated\_at | Retrieve messages with the exact data specified in the request | recipient\_id=546 | | {field\_name}\[{search\_operator}] | \_id, message, date\_sent, sender\_id, recipient\_id, attachments.type, updated\_at | Retrieve messages with the exact data specified in the request with applying additional filters. Available filters: **lt** (Less Than operator), **lte** (Less Than or Equal to operator), **gt** (Greater Than operator), **gte** (Greater Than or Equal to operator), **ne** (Not Equal to operator), **in** (Contained IN array operator), **nin** (Not contained IN array), **all** (ALL contained IN array), **ctn** (Contains substring operator) | sender\_id\[lt]=132 | | limit | standalone operator | Set limit for a number of search results. Default - 100 | limit=50 | | skip | standalone operator | Skip the defined number of records in the search results. By default all records are shown | skip=30 | | sort\_desc/sort\_asc | date\_sent | Sorting of query result set in ascending / descending order | sort\_asc=date\_sent | | mark\_as\_read | standalone operator | Do not mark the received messages as read. To do not mark retrieved messages as **read**, `mark_as_read` parameter should be set to 0 | mark\_as\_read=0 | | preview | standalone operator | You can retrieve last `20` message if you not chat member (`occupants_ids`), only for dialogs with `permissions` prop `allow_preview` = `true`, parameters `skip`, `limit`, `sort_desc`, `sort_asc` will be ignored | preview=1 | ###### Request example [Section titled “Request example”](#request-example-11) ```bash curl -X GET \ -H "CB-Token: " \ -d 'chat_dialog_id=5c093376e588ce6856f873fe' \ https://api.connectycube.com/chat/Message ``` ###### Response [Section titled “Response”](#response-15) ```json { "skip": 0, "limit": 1000, "items": [ { "_id": "5c094682e588ce59fff87424", "attachments": [], "chat_dialog_id": "5c093376e588ce6856f873fe", "created_at": "2018-12-06T15:55:46Z", "date_sent": 1544111746, "delivered_ids": [29085, 29086], "message": "What's the price of it?", "read_ids": [29085, 29086], "recipient_id": 0, "sender_id": 29085, "updated_at": "2018-12-06T16:05:21Z", "reactions": null, "views_count": 0, "read": 0 }, { "_id": "5c094687e588ce59fff87425", "attachments": [], "chat_dialog_id": "5c093376e588ce6856f873fe", "created_at": "2018-12-06T15:55:51Z", "date_sent": 1544111751, "delivered_ids": [29085, 29086], "message": "Hello Daniel, how things are going on?", "read_ids": [29085, 29086], "recipient_id": 0, "sender_id": 29085, "updated_at": "2018-12-06T16:05:21Z", "reactions": { "own": ["👍", "👌"], "total": { "👍": 2, "👌": 1, "🚧": 5 } }, "views_count": 0, "read": 0 } ] } ``` ## Retrieve unread messages count [Section titled “Retrieve unread messages count”](#retrieve-unread-messages-count) Retrieve the number of unread chat messages. The output is split across dialogs plus a total number value. ###### Endpoint [Section titled “Endpoint”](#endpoint-13) ```plaintext GET https://api.connectycube.com/chat/Message/unread ``` ###### Parameters [Section titled “Parameters”](#parameters-7) | Operator | Description | Value example | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | | chat\_dialog\_ids | List of dialogs to retrieve the number of unread messages. Applied only for **group chat** (type=2) and **privat chat** (type=3). You can skip the parameter - in this case only total number of unread messages will be returned. | chat\_dialog\_ids=id\_1,id\_2 | ###### Request example [Section titled “Request example”](#request-example-12) ```bash curl -X GET \ -H "CB-Token: " \ -d 'chat_dialog_ids=5c093376e588ce6856f873fe,76ok0l3e989c12a0e45432a7' \ https://api.connectycube.com/chat/Message/unread ``` ###### Response [Section titled “Response”](#response-16) ```json { "total": 5, "53aadc78535c127f15009b6c": 3, "76ok0l3e989c12a0e45432a7": 2 } ``` ## Create a message [Section titled “Create a message”](#create-a-message) Create a chat message to add it to the chat history. Created message won’t be delivered to the recipient(s) by Real-Time (XMPP) transport and will be just added to the chat history. To initiate a real sending to the chat - pass `send_to_chat=1` parameter. ###### Endpoint [Section titled “Endpoint”](#endpoint-14) ```plaintext POST https://api.connectycube.com/chat/Message ``` ###### Parameters [Section titled “Parameters”](#parameters-8) | Parameter | Required | Description | Value example | | ----------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | chat\_dialog\_id | yes, if the `recipient_id` key is not specified | ID of a dialog where a new message will be added | {“chat\_dialog\_id”: “5bec0186e588ce7ff1c0ad0a”} | | message | no | text of the message (CGI-escaped) | {“message”: “Hi! How are you doing?“} | | recipient\_id | yes, if the ‘chat\_dialog\_id’ key is not specified | ID of a recipient. Useful for private chats only (type 3) | {“recipient\_id”: “4377”} | | attachments\[n]\[type/id/url] | no | List of attachments. Each attachment object contains 3 keys: type (audio/video/image/…), id (link to file ID in Connectycube), url (link to file in the Internet) | {“attachments”: {“0”: {“type”: “image”, “id”: “654645”}, “1”: {“type”: “video”, “id”: “654646”}}} | | Custom parameters | no | Key-value parameters. Chat message can contain any other custom parameters | {“country”: “USA”} | | send\_to\_chat | no | Send message to chat. Should be set to 1 if message should be sent | {“send\_to\_chat”: “1”} | | markable | no | Mark message to be processed as read / delivered statuses on a client side | {“markable”: “1”} | ###### Request example [Section titled “Request example”](#request-example-13) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"chat_dialog_id": "5c094c0ce588ce6856f87422", "message": "where are you?", "recipient_id": 29086, "attachments": {"3": {"type": "image", "id": "3004"}, "7": {"type": "image", "id": "24504"}}}' \ https://api.connectycube.com/chat/Message ``` ###### Response [Section titled “Response”](#response-17) ```json { "_id": "5c094e30e588ce59fff87431", "attachments": [ { "type": "image", "id": "3004" }, { "type": "image", "id": "24504" } ], "chat_dialog_id": "5c094c0ce588ce6856f87422", "created_at": "2018-12-06T16:28:32Z", "date_sent": 1544113712, "delivered_ids": [29086], "message": "where are you?", "read_ids": [29086], "recipient_id": 29086, "sender_id": 29086, "updated_at": "2018-12-06T16:28:32Z", "views_count": 0, "read": 0 } ``` ## Update message [Section titled “Update message”](#update-message) Update of sent chat message. Message body and message status can be updated. It’s possible to mark all messages as read / delivered - just don’t pass a message id. ###### Endpoint [Section titled “Endpoint”](#endpoint-15) ```plaintext PUT https://api.connectycube.com/chat/Message/{message1_id},{message2_id} ``` ###### Parameters [Section titled “Parameters”](#parameters-9) | Field | Description | Example | | ---------------- | ------------------------------------ | ------------------------------------------------ | | chat\_dialog\_id | Dialog’s id which message is updated | {“chat\_dialog\_id”: “5b7411335bd08d0e5761bc96”} | | read | Mark message as read | {“read”:“1”} | | delivered | Mark message as delivered | {“delivered”: “1} | | message | Updated message body | {“message”: “Morning”} | ###### Request example [Section titled “Request example”](#request-example-14) ```bash curl -X PUT \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"read": "1", "chat_dialog_id": "5c094c0ce588ce6856f87422"}'\ https://api.connectycube.com/chat/Message/5c094e30e588ce59fff87431 ``` ###### Response [Section titled “Response”](#response-18) ```json 200 OK ``` ## Delete message [Section titled “Delete message”](#delete-message) Remove of a chat message for the current user in a specified chat dialog. There are some rules for removing messages depending the type of the dialog: * For **broadcast chat** (type=1) only Super admin and admins are able to remove messages from the dialog. General users are readers only. * For **group chat** (type=2) any user in the dialog’s `occupant_ids` is able to remove a message from the dialog. The message will only be removed for the current user - the message will still be viewable in the chat history for all other users in the dialog. To remove a message completely, `force=1` parameter should be set. Super admin and admins can remove all messages from the dialog, whereas general users are able to remove only own message for all users with `force=1` parameter, but other users messages can remove only for themselves. * For **private chat** (type=3) users are able remove their own messages from the history. Users can remove messages both, without affecting the history of other user, and, as well as completely remove a message. To remove message for both sides `force=1` parameter should be set for own messages. * For **public chat** (type=4) any user in the dialog’s `occupants_count` is able to remove a message from the dialog. The message will only be removed for the current user - the message will still be viewable in the chat history for all other users in the dialog. To remove a message completely, `force=1` parameter should be set. Super admin and admins can remove all messages from the dialog, whereas general users are able to remove only own message for all users with `force=1` parameter, but other users messages can remove only for themselves. Deleting a message with the `force=1` parameter is followed by sending a XMPP packet to the chat room with the request to immediately remove the message from other occupants history. ###### Endpoint [Section titled “Endpoint”](#endpoint-16) ```plaintext DELETE https://api.connectycube.com/chat/Message/{message1_id},{message2_id} ``` ###### Request example [Section titled “Request example”](#request-example-15) ```bash curl -X DELETE \ -H "CB-Token: " \ https://api.connectycube.com/chat/Message/5c094687e588ce59fff87425,5c094682e588ce59fff87424 ``` ###### Response [Section titled “Response”](#response-19) ```json { "SuccessfullyDeleted": { "ids": ["5c094687e588ce59fff87425"] }, "NotFound": { "ids": ["5c094682e588ce59fff87424"] }, "WrongPermissions": { "ids": ["5c094682e588ce59fff87423"] } } ``` ## Send system message [Section titled “Send system message”](#send-system-message) Create a system message to send a signal. System message will be delivered to the recipient(s) by Real-Time (XMPP) transport. ###### Endpoint [Section titled “Endpoint”](#endpoint-17) ```plaintext POST https://api.connectycube.com/chat/Message/system ``` ###### Parameters [Section titled “Parameters”](#parameters-10) | Field | Required | Description | Value example | | ------------- | -------- | ------------------------------------------- | -------------------------------------------------------------- | | recipientId | Yes | recipient user id | {“recipientId”: 29086} | | \[key string] | No | any key value pain (string : string/object) | {“userExt”: { “name”: “Smith” }, “nickname”: “Awesome\_smith”} | ###### Request example [Section titled “Request example”](#request-example-16) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"recipientId": 29086, "needToUpdatePhotosStories": "1", "friendId": "90675", "userExt": { "name": "Smith" }, "nickname": "Awesome_smith"}' \ https://api.connectycube.com/chat/Message/system ``` ###### Response [Section titled “Response”](#response-20) ```json { "messageId": "641307a86e010326b51594dc", "recipientId": 29086, "extensionParams": { "needToUpdatePhotosStories": "1", "friendId": "90675", "userExt": { "name": "Smith" }, "nickname": "Awesome_smith" } } ``` ## Message reactions [Section titled “Message reactions”](#message-reactions) User can add or remove reactions (string) and receive chat server message Message response reaction format | Field name | Description | | ---------- | --------------------------------------------------------------------------------- | | own | array of user reactions (session user) | | total | object where key is reaction type and value is count of reactions (including own) | ```json { ... "reactions": { "own": ["👌", "😐"], "total": { "👌": 5, "😐": 1 } } ... } ``` ### Add/Remove reactions [Section titled “Add/Remove reactions”](#addremove-reactions) User can has only one reaction type ###### Endpoint [Section titled “Endpoint”](#endpoint-18) ```plaintext PUT https://api.connectycube.com/chat/Message/{message_id}/reactions ``` ###### Request example [Section titled “Request example”](#request-example-17) ```bash curl -X PUT \ -H "CB-Token: " \ -d '{"add": "👍", "remove": "👎"}'\ https://api.connectycube.com/chat/Message/5c094687e588ce59fff87425/reactions ``` ###### Response [Section titled “Response”](#response-21) ```json 200 OK ``` xmpp message will be sent to chat participants ```xml 6304b6a59cf9db00200c1cf7 ``` ### List reactions [Section titled “List reactions”](#list-reactions) User get message reactions object where key is reactions type and value is array of user\_ids ###### Endpoint [Section titled “Endpoint”](#endpoint-19) ```plaintext GET https://api.connectycube.com/chat/Message/{message_id}/reactions ``` ###### Request example [Section titled “Request example”](#request-example-18) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/chat/Message/5c094687e588ce59fff87425/reactions ``` ###### Response [Section titled “Response”](#response-22) ```json { "👍": [67456, 5687], "👎": [67456, 5687, 9736], "🚀": [9736] } ``` ## Global search [Section titled “Global search”](#global-search) It helps to find some messages and chat dialogs. ###### Endpoint [Section titled “Endpoint”](#endpoint-20) ```plaintext GET https://api.connectycube.com/chat/search ``` ###### Parameters [Section titled “Parameters”](#parameters-11) | Field | Required | Description | Value example | | ----------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | | search\_text | Yes | String. Should be longer than 4 symbols. Searches for the sub-string against chat message’s ‘message’ field and dialog’s ‘name’ field. Case sensitive. | search\_text=“lorem” | | chat\_dialog\_ids | No | List of dialog ids separated by comas. Max cam include 10 items. | chat\_dialog\_ids=5c49cb855368b57a05dc5872 | | start\_date | No | Closest date to Time.now. Uses `lte` comparison. | start\_date=2016-03-23T17:00:42Z | | end\_date | Yes, if `start_date` passed | Shouldn’t differ by more than 3 months from the `start_date`. Uses `gte` comparison. | start\_date=2016-01-23T17:00:42Z | | limit | No | Maximum number of items returned from the server in the search results. Max value - 100 | limit=3 | ###### Request example [Section titled “Request example”](#request-example-19) ```bash curl -X GET \ -H "Content-Type: application/json" \ -H "CB-Token: " \ 'https://api.connectycube.com/chat/search?search_text=lorem&limit=3' ``` ###### Response [Section titled “Response”](#response-23) ```json { "results": { "dialogs": [ { "_id": "5c4867115368b52b3f5c6b16", "admins_ids": [], "created_at": "2019-01-23T13:07:29Z", "description": null, "last_message": null, "last_message_date_sent": null, "last_message_id": null, "last_message_user_id": null, "name": "lorem ipsum dolor", "occupants_ids": [23, 2172], "photo": null, "pinned_messages_ids": [], "type": 3, "updated_at": "2019-01-23T13:07:29Z", "user_id": 2172, "unread_messages_count": null, "xmpp_room_jid": null } ], "messages": [ { "_id": "5c49ccef5368b57a05dc5877", "attachments": [], "chat_dialog_id": "5c49cb855368b57a05dc5872", "created_at": "2019-01-24T14:34:23Z", "date_sent": 1548340463, "delivered_ids": [2172], "message": "Ipsa lorem tempore vitae earum sequi aut velit qui.", "read_ids": [2172], "recipient_id": 0, "sender_id": 2172, "updated_at": "2019-01-24T14:34:23Z", "views_count": 0, "read": 0 }, { "_id": "5c49cd815368b57a05dc5882", "attachments": [], "chat_dialog_id": "5c49cb855368b57a05dc5872", "created_at": "2019-01-24T14:36:49Z", "date_sent": 1548340609, "delivered_ids": [2172], "message": "Incidunt maxime sapiente cum est dolor fugit lorem.", "read_ids": [2172], "recipient_id": 0, "sender_id": 2172, "updated_at": "2019-01-24T14:36:49Z", "views_count": 0, "read": 0 } ], "users": [] } } ``` # Custom objects API > Maximize your app's potential with customizable data cloud storage solutions. Tailor data structures to match your application's unique requirements. **Custom Objects** module allows to build the own data structure or schema and provides a flexibility for those who needs to go beyond the standard solutions. Data schema is defined in ConnectyCube dashboard and is called as ‘class’. To start using Custom Objects module, data schema should be created first. Field types supported in Custom object module: * Integer * String * Float * Boolean * Array * File * Location (Array of \[, ]) * Date ## Create data schema [Section titled “Create data schema”](#create-data-schema) Data schema can be created in the ConnectyCube dashboard within ‘Custom’ module. Go to ‘Add’ options and select ‘Add new class’ from the list: ![Create data schema via dashboard](/_astro/empty_dashboard.DrIRvZVQ_gpv3v.webp) Input a Class name and identify at least one field to add in the Class by specifying data type: ![Create a new class](/_astro/add_new_class.RweiCLpE_ZPEdv7.webp) Click on ‘Create class’ to complete creation of data schema. Newly created class is now available and contains the following data: * **ID** - record indentifier 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 ![Created class](/_astro/created_class.0PE35Deg_ZwqaL1.webp) ## 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 available in the scheme (class) will have null values. ###### Endpoint [Section titled “Endpoint”](#endpoint) ```plaintext POST https://api.connectycube.com/data/{class_name} ``` ###### Parameters [Section titled “Parameters”](#parameters) Any custom parameter (field in the schema) can be used in the request to identify a new record. ###### Request example [Section titled “Request example”](#request-example) ```bash curl -X POST \ -H "CB-Token: " \ -H "Content-Type: application/json" \ -d '{"full_name": "Nadine Collier", "age": "41", "job": "accountant", "country_of_birth": "Germany"}' \ https://api.connectycube.com/data/profile ``` ###### Response [Section titled “Response”](#response) ```json { "_id":"5c09798aca8bf468ab8d2936", "_parent_id":null, "age":41, "country_of_birth":"Germany", "created_at":1544124810, "full_name":"Nadine Collier", "job":"accountant", "updated_at":1544124810, "user_id":47592, "permissions": { "read":{ "access":"open" }, "update":{ "access":"owner" }, "delete":{ "access":"owner" } } } ``` ## 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. ###### Endpoint [Section titled “Endpoint”](#endpoint-1) ```plaintext POST https://api.connectycube.com/data/{class_name}/multi ``` ###### Set parameters [Section titled “Set parameters”](#set-parameters) **Format:** `{"record": {"0": {"field_1": "value", "field_2": "value"}, "1": {"field_1": "value", "field_2": "value"}}}` **Note:** the first `record_number` is always **0**. ###### Request example [Section titled “Request example”](#request-example-1) ```bash curl -X POST \ -H "CB-Token: " \ -H "Content-Type: application/json" \ -d '{"record": {"0": {"age": "11"}, "1": {"age": "55"}}}' \ https://api.connectycube.com/data/profile/multi ``` ###### Response [Section titled “Response”](#response-1) ```json { "class_name":"profile", "items":[ { "_id":"5c098d1fca8bf4291e8d220b", "_parent_id":null, "age":11, "country_of_birth":null, "created_at":1544129823, "full_name":null, "job":null, "updated_at":1544129823, "user_id":47592, "permissions": { "read":{ "access":"open" }, "update":{ "access":"owner" }, "delete":{ "access":"owner" } } }, { "_id":"5c098d1fca8bf4291e8d220c", "_parent_id":null, "age":55, "country_of_birth":null, "created_at":1544129823, "full_name":null, "job":null, "updated_at":1544129823, "user_id":47592, "permissions": { "read":{ "access":"open" }, "update":{ "access":"owner" }, "delete":{ "access":"owner" } } } ] } ``` ## Record with permissions [Section titled “Record with permissions”](#record-with-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 on given 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 withinh the Custom objects module. ### Permissions scheme [Section titled “Permissions scheme”](#permissions-scheme) ConnectyCube permission scheme contains five permissions levels: * **Open** (open) - any user within application can access the record(s) in the class and is allowed to perform actions with a record * **Owner** (owner) - only Owner (user who created a record) is allowed to perform action with a record * **Not allowed** (not\_allowed) - no one (except the Account Administrator) can proceed with a chosen action * **Open for groups** (open\_for\_groups) - users which have a specified tag(s) will be included in the group which is allowed to perform actions with a record. Several 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 to work with 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 objects module Click on ‘Edit permission’ button to open permissions schema to edit. ![Edit permissions](/_astro/edit_permissions.QOHOGNn1_2poO2h.webp) Each listed action has a separate permission level to select. The exception is a ‘Create’ action that isn’t available for ‘Owner’ permission level. ![Edit permissions levels](/_astro/permissions_levels.DIjyJN_5_26KrsB.webp) ### 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 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 ConnecyCube dashboard only withing Custom oblects 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 while creating a class: * **Create:** Open * **Read:** Open * **Update:** Owner * **Delete:** Owner To enable applying Class permissions for any of actions type, ‘Use Class permissions’ check box should be ticked. It means that record permission schema (if any) won’t be affected on a record. **Record entity** **Record** is an entity within the Class in the Custom Objects module that can be created in ConnectyCube dashaboard 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 permissions on record’ button: ![Set permissions for record](/_astro/set_permissions.Cwv-EPw3_ZQStiK.webp) Define the permission level for each of available actions: ![Define the permission level for a record](/_astro/record_permissions.DU4XAwIe_Z1p7Gcu.webp) ## 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 `permissions[action_name][access]` parameter to ‘Create a record’ request. ###### Endpoint [Section titled “Endpoint”](#endpoint-2) ```plaintext POST https://api.connectycube.com/data/{class_name} ``` ###### Parameters [Section titled “Parameters”](#parameters-1) | Permissions | Syntax | Example | | ------------------ | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | Open / Owner | ”permissions”: {“action\_name”: {“access”: “owner”} | “permissions”: {“read”: {“access”: “owner”} | | Open for users IDs | ”permissions”: {“action\_name”: {“access”: “open\_for\_users\_ids”, “ids”: \[“{id\_1}”,“{id\_2}”]} | “permissions”: {“update”: {“access”: “open\_for\_users\_ids”, “ids”: \[“51941”,“51943”]}} | | Open for groups | ”permissions”: {“action\_name”: {“access”: “open\_for\_groups”, “groups”: \[“group\_1”,“group\_2”]}} | “permissions”: {“delete”: {“access”: “open\_for\_groups”, “groups”: \[“officers”,“assistants”]}} | ###### Request example [Section titled “Request example”](#request-example-2) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"full_name": "Jacelyn Millard", "age":"25", "country_of_birth": "India", "permissions": {"read": {"access":"owner"}, "update": {"access": "open_for_users_ids", "ids": ["51941","51943"]}, "delete": {"access": "open_for_groups", "groups": ["officers","assistants"]}}}' \ https://api.connectycube.com/data/profile ``` ###### Response [Section titled “Response”](#response-2) ```json { "_id":"5c0d6d8cca8bf4291e8d3d1b", "_parent_id":null, "age":25, "avatar":null, "country_of_birth":"India", "created_at":1544383884, "full_name":"Jacelyn Millard", "job":null, "updated_at":1544383884, "user_id":47592, "permissions": { "read": { "access":"owner" }, "update": { "access":"open_for_users_ids", "users_ids": [ "51941","51943" ] }, "delete": { "access":"open_for_groups", "users_groups": [ "officers","assistants" ] } } } ``` ## Retrieve records by IDs [Section titled “Retrieve records by IDs”](#retrieve-records-by-ids) Retrieve records by specifying their identifiers. ###### Endpoint [Section titled “Endpoint”](#endpoint-3) ```plaintext GET https://api.connectycube.com/data/{class_name}/{record_id1},{record_id2} ``` ###### Request example [Section titled “Request example”](#request-example-3) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/data/profile/5c09798aca8bf468ab8d2936,5c097c45ca8bf468ab8d294e ``` ###### Response [Section titled “Response”](#response-3) ```json { "class_name":"profile", "items":[ { "_id":"5c09798aca8bf468ab8d2936", "_parent_id":null, "age":41, "country_of_birth":"Germany", "created_at":1544124810, "full_name":"Nadine Collier", "job":"accountant", "updated_at":1544124810, "user_id":47592, "permissions": { "read":{ "access":"open" }, "update":{ "access":"owner" }, "delete":{ "access":"owner"} } }, { "_id":"5c097c45ca8bf468ab8d294e", "_parent_id":null, "age":25, "country_of_birth":"Sweden", "created_at":1544125509, "full_name":"Lacey Idec", "job":"secretary", "updated_at":1544125521, "user_id":47592, "permissions": { "read":{ "access":"open" }, "update":{ "access":"owner" }, "delete":{ "access":"owner" } } } ] } ``` ## 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 will be much faster than sorting by `created_at` field. ###### Endpoint [Section titled “Endpoint”](#endpoint-4) ```plaintext GET https://api.connectycube.com/data/{class_name} ``` ###### Options to apply [Section titled “Options to apply”](#options-to-apply) ###### 1. Sorting [Section titled “1. Sorting”](#1-sorting) | Parameter | Applicable to type | Description | | -------------------------------- | :----------------: | ----------------------------------------------------------------------------------------------------------------------------------------- | | {field\_name} | all types | Search records by the specified field | | {field\_name}\[search\_operator] | all types | Search records by the specified field with applying filters to filter the query result set | | sort\_asc | all types | Sorting the query result set in ascending order | | sort\_desc | all types | Sorting the query result set in descending order | | skip | integer | Skip N records in the query result set. By default, all found records are shown | | limit | integer | Set a limit for displaying the query results. Default and max values - 100. To show the last record only, set `limit=-1` in the request | | count | integer | Number of records returned in the query result set | | output\[include] | all types | Fields to include in the query result set | | output\[exclude] | all types | Fields to exclude from the query result set | | near | Location | Search records in a defined radius (in meters) starting from the current position. Format: {field\_name}\[near]=longitude,latitude;radius | Example: ```bash curl -X GET \ -H "CB-Token: " \ -d 'sort_asc=age' \ https://api.connectycube.com/data/profile ``` ###### 2. Search criterias [Section titled “2. Search criterias”](#2-search-criterias) **Format:** `{field_name}[operator]={value}` | Operator | Applicable to types | Description | Data to return | | :------: | --------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------ | | gt | * integer* float | greater than | Records where the specified parameter has values greater than specified in the request | | lt | * integer* float | less than | Records where the specified parameter has values less than specified in the request | | gte | * integer* float | greater than or equal to | Records where the specified parameter has values greater than or equal to the specified in the request | | lte | * integer* float | less than or equal to | Records where the specified parameter has values less than or equal to the specified in the request | | ne | * integer* float* string* boolean | not equal | Records where the specified parameter has values not equal to the specified in the request | | in | * integer* float* string | **in** array | Records with the specified values to found in the array | | nin | * integer* float* string | not **in** array | Specified records not included in the array | | all | * array | all contained **in** array | All records that correspond to parameters specified in the request | | or | * integer* float* string | one **or** another parameter | Records with one of the specified parameters | | ctn | * integer* float* string | contain | Returns all records where field contains adjusted substring | Example: ```bash curl -X GET \ -H "CB-Token: " \ -d 'age[gt]=28' \ https://api.connectycube.com/data/profile ``` ###### 3. Aggregation operators [Section titled “3. Aggregation operators”](#3-aggregation-operators) | Parameter | Applicable to types | Description | | ----------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | {field\_name}\[**calc**]={operator} | * integer* float | `avg`, `min`, `max`, `sum` should be used with `group_by` operator | | **group\_by**={field\_name} | * integer* float* string* boolean | `group_by` works similar to [SQL GROUP BY](https://en.wikipedia.org/wiki/SQL#Queries) operator, should be used with `calc` operator | Example: ```bash curl -X GET \ -H "CB-Token: " \ -d 'age[calc]=avg&group_by=country_of_birth' \ https://api.connectycube.com/data/profile ``` ###### Request example [Section titled “Request example”](#request-example-4) ```bash curl -X GET \ -H "CB-Token: " \ -d 'age[gt]=28&sort_desc=full_name' \ https://api.connectycube.com/data/profile ``` ###### Response [Section titled “Response”](#response-4) ```json { "class_name":"profile", "skip":0, "limit":100, "items": [ { "_id":"5c0d7458ca8bf43a5b8cf487", "_parent_id":"", "age":41, "avatar":null, "country_of_birth":"India", "created_at":1544385624, "full_name":"Zach Whitehouse", "job":"Operation officer", "updated_at":1544385624, "user_id":47592 }, { "_id":"5c0d4f95ca8bf4291e8d3c2d", "_parent_id":"", "age":50, "avatar":null, "country_of_birth":"USA", "created_at":1544376213, "full_name":"Nadine Collier", "job":null, "updated_at":1544381152, "user_id":47592 } ] } ``` ## Retrieve record by ID to view its permissions [Section titled “Retrieve record by ID to view its permissions”](#retrieve-record-by-id-to-view-its-permissions) To retrieve the records to view their permissions use the parameter ‘permissions’ and set it as true ‘permissions=1’. Use GET method. Note: record permissions are checking while request is processing. Only owner has an ability to view a record’s permissions. ###### Endpoint [Section titled “Endpoint”](#endpoint-5) ```plaintext GET https://api.connectycube.com/data/{class_name}/{record_id} ``` ###### Parameters [Section titled “Parameters”](#parameters-2) | Parameter | Description | | ----------- | -------------------------------------------------------------------------------------------------------------------- | | permissions | To retrieve the records to view their permissions use the parameter ‘permissions’ and set it as true `permissions=1` | ###### Request example [Section titled “Request example”](#request-example-5) ```bash curl -X GET \ -H "CB-Token: " \ -d 'permissions=1' \ https://api.connectycube.com/data/profile/5bf7da90ca8bf42dae037dea ``` ###### Response [Section titled “Response”](#response-5) ```json { "permissions": { "read": { "access":"owner" }, "update": { "access":"open_for_users_ids", "users_ids": [ "51941","51943" ] }, "delete": { "access":"open_for_groups", "users_groups": [ "officers","assistants" ] } }, "record_id":"5c0d6d8cca8bf4291e8d3d1b" } ``` ## Update record by ID [Section titled “Update record by ID”](#update-record-by-id) Update record data by specifying its ID. ###### Endpoint [Section titled “Endpoint”](#endpoint-6) ```plaintext PUT https://api.connectycube.com/data/{class_name}/{record_id} ``` ###### Parameters [Section titled “Parameters”](#parameters-3) | Parameter | Description | | ----------- | ---------------------------- | | field\_name | Name of the fields to update | ###### Special update operators [Section titled “Special update operators”](#special-update-operators) | Operator | Applicable to types | Description | Syntax | | -------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | inc | * integer* float | Increment a value in the field to specified one. Value can be either positive or negative (i.e. decrement operation) | “inc”: {“field\_name”: “value”} | | pull | arrays | Remove specified value from the array | ”pull”: {“field\_name”: “value”} | | pull with filter | arrays | Remove all found elements from array after applying a filter | ”pull”: {“field\_name”: {“operator”: “value”}} | | pull\_all | arrays | Remove all specified values from the array | ”pull\_all”: {“field\_name”: \[“value\_1”, “value\_2”]} | | pop | arrays | Remove the last element in the array. To remove the first element, set parameter to ‘-1‘ | “pop”: {“groups”: “1”} | | push | arrays | Append specified values to the array | ”push”: {“field\_name”: \[“value\_1”, “value\_2”]} | | add\_to\_set | arrays | Add a value to the array if this value is not in the array yet. Existing value will be skipped | ”add\_to\_set”: {“field\_name”: \[“value\_1”, “value\_2”]}}’ \\ | | Update array element by index operator | arrays | Update array element by specifying element index. The first one in the array is always ‘0‘ | “field\_name”: {“index\_1”: “value”, “index\_2”: “value”} | To nullify an existing value, specify “null” for application/x-www-/form-urlencoded and null for application/json content-type (depends on the format used in the request). For numeric fields (Integer & Float) there is a special increment operator - **inc** that increments or decrements a numeric field. ###### Request example [Section titled “Request example”](#request-example-6) ```bash curl -X PUT \ -H "CB-Token: " \ -H "Content-Type: application/json" \ -d '{"age": "22", "job": "technical director"}' \ https://api.connectycube.com/data/profile/5c097e8cca8bf4291e8d219f ``` ###### Response [Section titled “Response”](#response-6) ```json { "_id":"5c097e8cca8bf4291e8d219f", "_parent_id":null, "age":22, "country_of_birth":"Poland", "created_at":1544126092, "full_name":"Barret Campbell", "job":"technical director", "updated_at":1544128080, "user_id":47592, "permissions": { "read":{ "access":"open" }, "update":{ "access":"owner" }, "delete":{ "access":"owner" } } } ``` ## 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). ###### Endpoint [Section titled “Endpoint”](#endpoint-7) ```plaintext PUT https://api.connectycube.com/data/{class_name}/by_criteria ``` ###### Parameters [Section titled “Parameters”](#parameters-4) | Parameter | Description | Syntax | | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | | search\_criteria\[field\_name]\[operator] | `search_criteria` - required on the beginning, `field_name` - name of the fields to use for search, `operator` - search operator to find the records | ”search\_criteria”: {“field”: {“operator”: “value”}} | ###### Search criterias [Section titled “Search criterias”](#search-criterias) | Operator | Applicable to types | Description | Data to return | | :------: | --------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------ | | gt | * integer* float | greater than | Records where the specified parameter has values greater than specified in the request | | lt | * integer* float | less than | Records where the specified parameter has values less than specified in the request | | gte | * integer* float | greater than or equal to | Records where the specified parameter has values greater than or equal to the specified in the request | | lte | * integer* float | less than or equal to | Records where the specified parameter has values less than or equal to the specified in the request | | ne | * integer* float* string* boolean | not equal | Records where the specified parameter has values not equal to the specified in the request | | in | * integer* float* string | contained **in** array | Records with the specified values to found in the array | | nin | * integer* float* string | not contained **in** array | Records with the specified values that are not in the array | | all | array | all contained IN array | All records that correspond to parameters specified in the request | | or | * integer* float* string | one **or** another | Records found by one of the specified parameters | | ctn | * integer* float* string | contain | Return all records that contain a specified data | ###### Request example [Section titled “Request example”](#request-example-7) ```bash curl -X PUT \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"search_criteria": {"age":{"lt":30}}, "country_of_birth":"Iran"}' \ https://api.connectycube.com/data/profile/by_criteria ``` ###### Response [Section titled “Response”](#response-7) ```json { "class_name":"profile", "skip":0, "limit":100, "total_found":1, "items":[ { "_id":"5c09798aca8bf468ab8d2936", "_parent_id":null, "age":22, "country_of_birth":"Iran", "created_at":1544124810, "full_name":"Nadine Collier", "job":"accountant", "updated_at":1544367921, "user_id":47592, "permissions": { "read":{ "access":"open" }, "update":{ "access":"open" }, "delete":{ "access":"open" } } } ] } ``` ## Update multi records [Section titled “Update multi records”](#update-multi-records) Update several records within a class by specifying new values. ###### Endpoint [Section titled “Endpoint”](#endpoint-8) ```plaintext PUT https://api.connectycube.com/data/{class_name}/multi ``` ###### The format of query [Section titled “The format of query”](#the-format-of-query) ```json {"record": {"record_number": {"id": "value", "field_1": "value", "field_2":"value"}}} ``` where: * `record_number` - record number in the query. Numbering begins with ‘1’ * `field_name` - field in Custom Object to update ###### Request example [Section titled “Request example”](#request-example-8) ```bash curl -X PUT \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"record": {"1": {"id": "5c0d4f95ca8bf4291e8d3c2d", "country_of_birth": "USA", "age":"50"}, "2": {"id": "5c0d625aca8bf43a5b8cf3de", "country_of_birth": "Lithuania", "age":"28"}, "3": {"id": "5c0d625aca8bf43a5b8cf111", "country_of_birth": "Greece", "age":"35"}}}' \ https://api.connectycube.com/data/profile/multi ``` ###### Response [Section titled “Response”](#response-8) ```json { "class_name":"profile", "not_found": { "ids":["5c0d625aca8bf43a5b8cf111"] }, "items": [ { "_id":"5c0d4f95ca8bf4291e8d3c2d", "_parent_id":"", "age":50, "avatar":null, "country_of_birth":"USA", "created_at":1544376213, "full_name":"Nadine Collier", "job":null, "updated_at":1544381152, "user_id":47592, "permissions": { "read": { "access":"open" }, "update": { "access":"owner" }, "delete": { "access":"owner" } } }, { "_id":"5c0d625aca8bf43a5b8cf3de", "_parent_id":"", "age":28, "avatar":null, "country_of_birth":"Lithuania", "created_at":1544381018, "full_name":"Georgia Barny", "job":"Managing officer", "updated_at":1544381152, "user_id":47592, "permissions": { "read": { "access":"open" }, "update": { "access":"owner" }, "delete": { "access":"owner" } } } ] } ``` ## Delete record by ID [Section titled “Delete record by ID”](#delete-record-by-id) Delete a record from a class by record identifier. ###### Endpoint [Section titled “Endpoint”](#endpoint-9) ```plaintext DELETE https://api.connectycube.com/data/{class_name}/{id} ``` ###### Request example [Section titled “Request example”](#request-example-9) ```bash curl -X DELETE \ -H "CB-Token: " \ https://api.connectycube.com/data/profile/5c090e0fe588ce59fff873df ``` ###### Response [Section titled “Response”](#response-9) ```json 200 OK ``` ## 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 shown for that record(s). ###### Endpoint [Section titled “Endpoint”](#endpoint-10) ```plaintext DELETE https://api.connectycube.com/data/{class_name}/{id_1},{id_2},{id_3} ``` ###### Request example [Section titled “Request example”](#request-example-10) ```bash curl -X DELETE \ -H "CB-Token: " \ https://api.connectycube.com/data/profile/5bf7da90ca8bf42dae037dea,5bf7db7cca8bf45ff403449c,5bf7d621ca8bf45ff4034463 ``` ###### Response [Section titled “Response”](#response-10) ```json { "SuccessfullyDeleted": { "ids": [ "5c097c45ca8bf468ab8d294e" ] }, "WrongPermissions": { "ids": [ "5c097e8cca8bf4291e8d219f" ] }, "NotFound": { "ids": [ "55c09798aca8bf468ab8d2936" ] } } ``` ## 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. ###### Endpoint [Section titled “Endpoint”](#endpoint-11) ```plaintext DELETE https://api.connectycube.com/data/{class_name}/by_criteria ``` ###### Query format [Section titled “Query format”](#query-format) ```bash '{field_name}[operator]={value}' ``` ###### Search criterias [Section titled “Search criterias”](#search-criterias-1) | Operator | Applicable to types | Description | Data to return | | -------- | --------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------ | | gt | * integer* float | greater than | Records where the specified parameter has values greater than specified in the request | | lt | * integer* float | less than | Records where the specified parameter has values less than specified in the request | | gte | * integer* float | greater than or equal to | Records where the specified parameter has values greater than or equal to the specified in the request | | lte | * integer* float | less than or equal to | Records where the specified parameter has values less than or equal to the specified in the request | | ne | * integer* float* string* boolean | not equal | Records where the specified parameter has values not equal to the specified in the request | | in | * integer* float* string | **in** array | Users with the specified IDs | | nin | * integer* float* string | not **in** array | Records not specified in the array | | all | array | **all** contained **in** array | All records that correspond to parameters specified in the request | | or | * integer* float* string | one **or** another | Records with one of the specified parameters | | ctn | * integer* float* string | contain | Returns all records where field contains adjusted substring | ###### Request example [Section titled “Request example”](#request-example-11) ```bash curl -X DELETE \ -H "CB-Token: " \ -d 'age[gte]=41' \ https://api.connectycube.com/data/profile/by_criteria ``` ###### Response [Section titled “Response”](#response-11) ```json { "total_deleted": 2 } ``` ## Relations [Section titled “Relations”](#relations) Objects (records) in different classes can be linked with a `_parent_id` field. If record from the Class 1 is pointed with a record from Class 2 via its `_parent_id`, the `_parent_id` 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 `_parent_id` 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. # Email templates In ConnectyCube, email templates are designed to simplify user communication by allowing admins to customize and manage email notifications for their apps. These templates cover various user interactions, such as account confirmations, password resets, and notifications, ensuring consistent branding and messaging. Admins can easily update these templates in the admin panel to align with their specific needs, providing a personalized experience for users. ## ‘Welcome email’ template [Section titled “‘Welcome email’ template”](#welcome-email-template) **A welcome email** is the first message sent to new users after they sign up. It introduces them to the app, provides essential information, and guides them on how to get started. In ConnectyCube, the welcome email template can be customized by admins to match their brand, include helpful links or tutorials, and set the tone for future interactions. It’s a valuable opportunity to make a strong first impression and engage users from the start. ```html
Welcome and thanks for joining!

Congratulations! You've been registered on #{app.title} with email #{user.email}


Some greeting goes here

Best regards,
#{app.title} Team
Copyright © 2020 ConnectyCube. All rights reserved.
You are receiving this email because you have registered for ConnectyCube.
Unsubscribe
``` ## ‘Reset password’ template [Section titled “‘Reset password’ template”](#reset-password-template) The **Reset Password template** is an email sent to users who request to reset their password. It includes a secure link or instructions to create a new password, helping users quickly regain access to their accounts. Admins in ConnectyCube can customize this template to ensure consistent branding and provide a good assistance for users needing password assistance. ```html
Reset password

Please click the link to reset your #{app.title} password.

Your password will remain the same if no action is taken.

Best regards,
#{app.title} Team
Copyright © 2020 ConnectyCube. All rights reserved.
You are receiving this email because you have registered for ConnectyCube.
Unsubscribe
``` ## ‘Email confirmation’ template [Section titled “‘Email confirmation’ template”](#email-confirmation-template) The **Email Confirmatio** template is an email sent to users after they register, asking them to verify their email address. It includes a link or code to confirm their identity, helping to secure their account and activate access. In ConnectyCube, admins can customize this template to maintain consistent branding and guide users through the verification process smoothly. ```html
Welcome and thanks for joining!

Please confirm your registration by following the link


Best regards,
#{app.title} Team
Copyright © 2020 ConnectyCube. All rights reserved.
You are receiving this email because you have registered for ConnectyCube.
Unsubscribe
``` ## ‘Recording done’ template [Section titled “‘Recording done’ template”](#recording-done-template) The **Recording Done** template is an email notification sent to users when their audio or video recording has successfully completed. This email can include details such as a link to access or download the recording. In ConnectyCube, admins can customize this template to align with their branding and provide users with easy access to their recordings. ```html
Please use the link(s) below to have access to 'Meeting recording':
{{{ renderRecordLinks(recording.download_record_link) }}}
This links will expire in #{recording.link_expire_in_days} days
Copyright © 2020 ConnectyCube. All rights reserved.
You are receiving this email because you have registered for ConnectyCube.
Unsubscribe
``` ## ‘Meeting notification’ template [Section titled “‘Meeting notification’ template”](#meeting-notification-template) The **Schedule a Meeting** template is an email notification sent to users to confirm a new meeting appointment. It includes important details like the date, time, and meeting link, helping users prepare and join the meeting. In ConnectyCube, admins can customize this template to reflect their brand and ensure users receive clear, helpful information for upcoming meetings. ```html
Meeting Link:
!{renderMeetingUrl(app.meeting_url_template, meetingData.meeting._id)}
When:
#{meetingData.meeting.start_date}
Guests:
  • #{meetingData.host.email} - organizer
  • !{renderMeetingAttendees(meetingData.attendees)}
Copyright © 2020 ConnectyCube. All rights reserved.
You are receiving this email because you have registered for ConnectyCube.
Unsubscribe
``` # Meetings API > Easily schedule conference calls with Meetings API. Streamline your meetings and collaborate efficiently with attendees. **Meetings API** allows to schedule conference call with attendees. A meeting can have a chat dialog associated with it. A meeting call can be recorded on server and then a file can be downloaded via Web Admin panel. ## Meeting model [Section titled “Meeting model”](#meeting-model) | Parameter | Description | | ---------------- | ------------------------------------------------ | | \_id | Meeting identifier | | name | Meeting name | | start\_date | Date and time when meeting starts | | end\_date | Date and time when meeting ends | | attendees | Users who will be in meeting | | record | Whether a meeting should be recorded | | chat\_dialog\_id | Meeting chat identifier | | host\_id | Meeting creator user id | | created\_at | Meeting created date | | updated\_at | Meeting updated date | | public | Indicate meeting public or not | | scheduled | Indicate meeting scheduled or not | | notify | Send notification after meeting is created | | notify\_before | Timing before meeting start to send notification | | timezone | Meeting timezone settings | ## Create meeting [Section titled “Create meeting”](#create-meeting) ###### Endpoint [Section titled “Endpoint”](#endpoint) ```plaintext POST https://api.connectycube.com/meetings ``` ###### Parameters [Section titled “Parameters”](#parameters) | Parameter | Required | Description | Data type | Value example | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------- | ---------------- | --------------------------------------------- | | name | Yes | Meeting name | String | ”Friday weekly sync” | | start\_date | Yes | Date when meeting starts | Timestamp | 1613729948 | | end\_date | Yes | Date when meeting ends | Timestamp | 1613829948 | | attendees | Yes | Array of meeting participants. `id` is a ConnectyCube user id. `email` can be anything. `id` or `email` is required | Array of objects | `'{"id": 5243, "email": "example@mail.com"}'` | | chat | No | Whether a chat dialog should be created along with a meeting | Boolean | true | | record | No | Whether a meeting should be recorded | Boolean | false | | public | No | If public is true any user can retrieve this meeting by id | Boolean | true | | scheduled | No | Can be use as filter | Boolean | true | | notify | No | Send notification after meeting is created | Boolean | true | | notify\_before | No | Timing before meeting start to send notification | Object | `'{"metric": "minutes", "value": 10}'` | | timezone | No | Meeting timezone settings | Object | `'{"offset": 120, "name": "Europe/Riga"}'` | ###### notify\_before parameter object [Section titled “notify\_before parameter object”](#notify_before-parameter-object) | Parameter | Date type | Description | | --------- | ------------- | -------------------------------------------------------------------------------------------------------------- | | metric | string (enum) | `'minutes'`, `'hours'`, `'days'`, `'weeks'` | | value | int | `'minutes'` range `[0 - 55]`, `'hours'` range `[1 - 24]`, `'days'` range `[1 - 30]`, `'weeks'` range `[1 - 4]` | ###### timezone parameter object [Section titled “timezone parameter object”](#timezone-parameter-object) | Parameter | Date type | Description | | --------- | --------- | ------------------ | | offset | int | Minutes UTC offset | | name | string | Timezone Name | ###### Request example [Section titled “Request example”](#request-example) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"name": "Friday weekly sync", "start_date": 1613729948, "end_date": 1613731012, "attendees": [{"id": 245,"email": "email1@domen.com"}, {"id": 343}, {"email": "email2@domen.com"}], "record": false, "chat": true, "notify": true, "notify_before": {"metric": "minutes", "value": 10}, "timezone": {"offset": 120, "name": "Europe/Riga"} }' \ https://api.connectycube.com/meetings ``` ###### Response [Section titled “Response”](#response) ```json { "_id": "602f926aaa81493ce4c5788e", "name": "Friday weekly sync", "start_date": 1613729948, "end_date": 1613731012, "attendees": [ { "id": 245, "email": "email1@domen.com" }, { "id": 343 }, { "email": "email2@domen.com" } ], "record": false, "chat_dialog_id": "602f9ea20740a24ec238d9da", "created_at": "2021-02-19T10:26:50.013Z", "updated_at": "2021-02-19T10:26:50.013Z", "host_id": 811, "scheduled": false, "notify": true, "notify_before": { "metric": "minutes", "value": 10 }, "timezone": { "offset": 120, "name": "Europe/Riga" } } ``` ## Retrieve meetings [Section titled “Retrieve meetings”](#retrieve-meetings) ###### Endpoint [Section titled “Endpoint”](#endpoint-1) ```plaintext GET https://api.connectycube.com/meetings ``` ###### Parameters [Section titled “Parameters”](#parameters-1) | Parameter | Data types | Description | | ----------- | ----------------------------- | ------------------------------------------------------------------ | | \_id | string (ObjectId) | Specify to retrieve a specific meeting by id | | limit | number | Setting of limit for a number of results to display. Default - 100 | | offset | number | Skip the defined number of records in the results | | start\_date | number (timestamp in seconds) | Filter by start\_date field | | end\_date | number (timestamp in seconds) | Filter by end\_date field | | updated\_at | string (date) | Filter by updated\_at field | | created\_at | string (date) | Filter by created\_at field | | scheduled | boolean | Filter by scheduled field | Without `_id` parameter specified, a user will retrieve meetings in which a current user is host or attendee. ###### Request example [Section titled “Request example”](#request-example-1) ```bash curl -X GET \ -H "CB-Token: " \ -d 'start_date[gte]=1613729948&scheduled=true&created_at[gt]=2021-02-19T10:26:49.013Z' \ https://api.connectycube.com/meetings ``` ###### Response [Section titled “Response”](#response-1) ```json [ { "_id": "602f926aaa81493ce4c5788e", "name": "Friday weekly sync", "start_date": 1613729948, "end_date": 1613731012, "attendees": [ { "id": 245, "email": "email1@domen.com" }, { "id": 343 }, { "email": "email2@domen.com" } ], "record": null, "chat_dialog_id": null, "created_at": "2021-02-19T10:26:50.013Z", "updated_at": "2021-02-19T10:26:50.013Z", "host_id": 811, "scheduled": false, "notify": true, "notify_before": { "metric": "minutes", "value": 10 }, "timezone": { "offset": 120, "name": "Europe/Riga" } } ] ``` ## Update meeting [Section titled “Update meeting”](#update-meeting) A request to update meeting’s parameters. Only meeting creator (host) can update a meeting. ###### Endpoint [Section titled “Endpoint”](#endpoint-2) ```plaintext PUT https://api.connectycube.com/meetings/{meeting_id} ``` ###### Parameters [Section titled “Parameters”](#parameters-2) | Parameter | Required | Description | Data type | Value example | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------ | | name | Yes | Meeting name | String | ”New meeting name” | | start\_date | Yes | Date when meeting starts | Timestamp | 1613429948 | | end\_date | Yes | Date when meeting ends | Timestamp | 1613329948 | | attendees | Yes | Array of meeting participants. `id` is a ConnectyCube user id. `email` can be anything. `id` or `email` is required | Array of objects | `'{"id": 4123, "email": "example@mail.com"}'` | | record | No | Update from `false` to `true` start recording, from `true` to `false` stop recording | Boolean | true | | public | No | If public is true any user can retrieve this meeting by id | Boolean | true | | scheduled | No | Can be use as filter | Boolean | true | | notify | No | Send notification after meeting is created | Boolean | true | | notify\_before | No | Timing before meeting start to send notification | Object | `'{"metric": "hours", "value": 1}'` | | timezone | No | Meeting timezone settings | Object | `'{"offset": -300, "name": "America/New_York"}'` | ###### Request example [Section titled “Request example”](#request-example-2) ```bash curl -X PUT \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"name": "Another meeting name", "start_date": 1613959948, "end_date": 1613956048, "attendees": [{"id": 1445,"email": "email3@domen.com"}, {"id": 3456 "email": "email4@domen.com"}], "record":true, "chat":true, "public": true, "scheduled": true, "notify_before": { "metric": "hours", "value": 1 }, "timezone": { "offset": -300, "name": "America/New_York" } }' \ https://api.connectycube.com/meetings/602f926aaa81493ce4c5788e ``` ###### Response [Section titled “Response”](#response-2) ```json { "_id": "602f926aaa81493ce4c5788e", "name": "Another meeting name", "start_date": 1613959948, "end_date": 1613956048, "attendees": [ { "id": 1445, "email": "email3@domen.com" }, { "id": 3456 }, { "email": "email4@domen.com" } ], "record": true, "chat_dialog_id": null, "created_at": "2021-02-19T10:26:50.013Z", "updated_at": "2021-02-19T10:28:50.142Z", "host_id": 811, "public": true, "scheduled": true, "notify": true, "notify_before": { "metric": "hours", "value": 1 }, "timezone": { "offset": -300, "name": "America/New_York" } } ``` ###### Meeting notification updating [Section titled “Meeting notification updating”](#meeting-notification-updating) * If `"notify"` was updated from `'false'` to `'true'`, mails will be send to all attendees + host * If `"start_date"` was updated, mails will be sent to all attendees + host * If `"attendees"` was updated, mails will be sent only to added attendees ## Delete meeting [Section titled “Delete meeting”](#delete-meeting) Delete a meeting by ID. Only meeting creator (host) can delete a meeting. ###### Endpoint [Section titled “Endpoint”](#endpoint-3) ```plaintext DELETE https://api.connectycube.com/meetings/{meeting_id} ``` ###### Request example [Section titled “Request example”](#request-example-3) ```bash curl -X DELETE \ -H "CB-Token: " \ https://api.connectycube.com/meetings/602f926aaa81493ce4c5788e ``` ###### Response [Section titled “Response”](#response-3) ```plaintext 200 OK ``` ## Meeting Email Link Settings [Section titled “Meeting Email Link Settings”](#meeting-email-link-settings) You can set meeting url template in Admin App settings `MEETING URL TEMPLATE` `https://meeting.host.domain/conference/join/%s` where `%s` will be replaced by meeting \_id ![Meeting Url Template](/_astro/meeting_url_template.UFjs8Grc_ZtuyRT.webp) ![Meeting Email Lik](/_astro/meeting_notification_email.7Ps-xBWn_KgYGj.webp) ## Meeting recordings [Section titled “Meeting recordings”](#meeting-recordings) Retrieve meeting records with download url ### Recording model [Section titled “Recording model”](#recording-model) | Parameter | Description | | ------------------- | ------------------------------------------------------------------ | | \_id | Recording identifier | | room\_id | Meeting identifier | | participants\_ids | array of user ids in recording | | participants\_count | users count in recording | | duration | recording file duration in seconds | | size | recording file size in MB | | download\_url | url to download recording file form storage (expired after 1 hour) | | created\_at | recording created date | | updated\_at | recording updated date | ###### Endpoint [Section titled “Endpoint”](#endpoint-4) ```plaintext GET https://api.connectycube.com/meetings/recordings/{meeting_id} ``` ###### Request example [Section titled “Request example”](#request-example-4) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/meetings/recordings/602f926aaa81493ce4c5788e ``` ###### Response [Section titled “Response”](#response-4) ```json [ { "_id": "649061c8d0b83a4a6dc85d0c", "room_id": "602f926aaa81493ce4c5788e", "participants_ids": [ 78934, 894946 ], "updated_at": "2023-06-19T14:10:17Z", "created_at": "2023-06-19T14:10:16Z", "download_url": "https://cb-recordings.s3.amazonaws.com/videos/dHk9xJQkGjw4ZD8rpuPsVeiD?AWSAccessKeyId=XXXXXXXXXXXX&Expires=1687187417&Signature=uRGxX1mCqLFKoPpP3RJGS0hG5pc%3D&response-cache-control=max-age%3D604800", "participants_count": 2, "duration": 6.062, "size": 0.34 }, { "_id": "649061c8d0b83a4a6dc85d0d", "room_id": "602f926aaa81493ce4c5788e", "participants_ids": [ 32885, 894946 ], "updated_at": "2023-06-19T14:10:16Z", "created_at": "2023-06-19T14:10:16Z", "download_url": "https://cb-recordings.s3.amazonaws.com/videos/kUZumRHnumFTmdmZ5TKYOKWa?AWSAccessKeyId=XXXXXXXXXXXXXXXX&Expires=1687187417&Signature=RVxkOJ4psx92nL%2B%2FPzZ%2BD6BlH7Q%3D&response-cache-control=max-age%3D604800", "participants_count": 2, "duration": 8.671, "size": 0.57 } ] ``` ## Recording done webhook [Section titled “Recording done webhook”](#recording-done-webhook) You can enable and set webhook endpoint url in Meetings Settings ![Meeting Settings](/_astro/admin_meetings_settings.6r1M8cF0_iMNtt.webp) Once recording files are available server will send `POST` requires on webhook url, payload will be an array of recording files records with details along with a meeting and host user. NOTE: recording files are not available during meeting, only after meeting is finished ##### Request payload example [Section titled “Request payload example”](#request-payload-example) ```json { "meeting": { "_id": "64ba8ab043e599002b5bdbca", "name": "My meeting", "start_date": 1689946801, "end_date": 1689950401, "attendees": [ { "id": 6892473, "email": "example-sam@email.com" }, { "id": 6892477, "email": "example-smith@email.com" } ], "public": false, "scheduled": false, "record": false, "chat_dialog_id": "64ba8ab043e599002b5bdbc9", "created_at": "2023-07-21T13:40:00Z", "updated_at": "2023-07-21T13:40:21Z", "host_id": 6892473, "notify": false, "notify_before": null, "timezone": null }, "hostUser": { "_id": "64ba8aac43e599002b5bdbc8", "id": 6892473, "created_at": "2023-07-21T13:39:56Z", "updated_at": "2023-07-21T13:40:00Z", "last_request_at": "2023-07-21T13:40:00Z", "login": "resourceful-norris-75", "full_name": "Sam Fisher", "timezone": null, "email": "example-sam@email.com", "phone": null, "website": null, "twitter_id": null, "external_user_id": null, "facebook_id": null, "custom_data": null, "user_tags": null, "avatar": null, "external_id": null, "is_guest": null }, "callRecords": [ { "_id": "64ba8abeea1de683a37139fd", "room_id": "64ba8ab043e599002b5bdbca", "participants_count": 1, "participants_ids": [ 6892473 ], "file_uid": "619486cc-3cb5-4171-afe0-74c1f1805e9a_64ba8ab043e599002b5bdbca", "duration": 5.082, "size": 0.29, "updated_at": "2023-07-21T13:40:14Z", "created_at": "2023-07-21T13:40:14Z", "download_url": "https://cb-staging-recordings.s3.amazonaws.com/videos/619486cc-3cb5-4171-afe0-74c1f1805e9a_64ba8ab043e599002b5bdbca?AWSAccessKeyId=XXXXXXXXXXXXXXXX&Expires=1690551720&Signature=8%2BOxOEQga2MP%XXXXX%2B4%2BYHAVjvQE%3D&response-cache-control=max-age%3D604800" }, { "_id": "64ba8acdea1de683a37139fe", "room_id": "64ba8ab043e599002b5bdbca", "participants_count": 2, "participants_ids": [ 6892473, 6892477 ], "file_uid": "5dfdffd3-1ffc-41f5-8d3c-6425df91c4b5_64ba8ab043e599002b5bdbca", "duration": 8.862, "size": 0.52, "updated_at": "2023-07-21T13:40:29Z", "created_at": "2023-07-21T13:40:29Z", "download_url": "https://cb-staging-recordings.s3.amazonaws.com/videos/5dfdffd3-1ffc-41f5-8d3c-6425df91c4b5_64ba8ab043e599002b5bdbca?AWSAccessKeyId=XXXXXXXXXXXXXXXX&Expires=1690551720&Signature=XXXXXXXXXXXXXXXXXX%3D&response-cache-control=max-age%3D604800" } ] } ``` # Push notifications API > 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 are a communication channel built into every mobile device sold today. Push notifications allow apps to reach out to users with short messages that users may respond to. Users don’t have to be in the app or using their devices to receive them. ConnectyCube provides an API to subscribe users for push notifications and to initiate push events. To send a notification on user’s device, the following actions should be initially completed: On receiver’s side: 1. [Create session with a user](/server/auth#create-session-with-user-authorization) 2. [Subscribe user on push notifications](/server/push_notifications#create-subscription) On sender’s side: 1. [Create session with a user](/server/auth#create-session-with-user-authorization) 2. [Create event](/server/push_notifications#create-event) ## Create subscription [Section titled “Create subscription”](#create-subscription) ###### Endpoint [Section titled “Endpoint”](#endpoint) ```plaintext POST https://api.connectycube.com/subscriptions ``` ###### Parameters [Section titled “Parameters”](#parameters) | Parameter | Required for channel | Description | | ---------------------------------------------- | :------------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | notification\_channel | all | Declare which notification channels could be used to notify user about events:* apns* apns\_voip* gcm* web | | push\_token\[environment] | all | Determines application environments. It allows conveniently separate development and production environments. Allowed values:* development* production | | push\_token\[bundle\_identifier] | optional | A unique identifier for client’s application. In iOS, this is the Bundle Identifier. In Android - package id | | push\_token\[client\_identification\_sequence] | `gcm`, `apns`, `apns_voip` | Identifies client device in 3-rd party service like APNS, GCM/FCM. Initially retrieved from 3-rd service and should be send to ConnectyCube to let it send push notifications to the client | | push\_token\[web\_endpoint] | `web` | `endpoint` param in 3-rd party service like [web client subscribe](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription#example). | | push\_token\[web\_auth] | `web` | `keys.auth` param in 3-rd party service like [web client subscribe](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription#example). | | push\_token\[web\_p256dh] | `web` | `keys.p256dh` param in 3-rd party service like [web client subscribe](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription#example). | | device\[platform] | all | Platform of device, which is the source of application running. Allowed values:* ios* android* web | | device\[udid] | all | UDID (Unique Device identifier) of device, which is the source of application running. This must be any sequence which uniquely identifies a particular device. This is needed to support schema: 1 User - Multiple devices | ###### Request example [Section titled “Request example”](#request-example) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"notification_channel": "apns", "push_token": {"environment": "development", "client_identification_sequence": "98559c1f3028b3fa0ec811d058cef16be27fe2fc507ca243b3d4fab006bfc9b8"}, "device": {"platform": "ios", "udid": "BFE36393-24E4-7689-AF85-428F4A5F2ED9"}}' \ https://api.connectycube.com/subscriptions ``` ###### Response [Section titled “Response”](#response) ```json [ { "subscription": { "id": 50, "notification_channel": { "name": "apns" }, "device": { "udid": "BFE36393-24E4-7689-AF85-428F4A5F2ED9", "platform": { "name": "ios" } } } } ] ``` ## Retrieve subscriptions [Section titled “Retrieve subscriptions”](#retrieve-subscriptions) Retrieve subscriptions for current user. ```plaintext GET https://api.connectycube.com/subscriptions ``` ###### Request example [Section titled “Request example”](#request-example-1) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/subscriptions ``` ###### Response [Section titled “Response”](#response-1) ```json [ { "subscription": { "id": 55, "notification_channel": { "name": "apns" }, "device": { "udid": "7618C089-4C28-5214-6H3C-763AC0CA79FW", "platform": { "name": "ios" } } } }, { "subscription": { "id": 56, "notification_channel": { "name": "apns" }, "device": { "udid": "7618C089-4C28-5214-6H3C-763AC0CA79FW", "platform": { "name": "ios" } } } } ] ``` ## Remove subscription [Section titled “Remove subscription”](#remove-subscription) Remove a subscription by its identifier. ###### Endpoint [Section titled “Endpoint”](#endpoint-1) ```plaintext DELETE https://api.connectycube.com/subscriptions/{subscription_id} ``` ###### Request example [Section titled “Request example”](#request-example-2) ```bash curl -X DELETE \ -H "CB-Token: "\ https://api.connectycube.com/subscriptions/56 ``` ###### Response [Section titled “Response”](#response-2) ```plaintext 200 OK ``` ## Create event [Section titled “Create event”](#create-event) Create event to initiate a push notification delivery. ###### Endpoint [Section titled “Endpoint”](#endpoint-2) ```plaintext POST https://api.connectycube.com/events ``` ###### Parameter [Section titled “Parameter”](#parameter) | Parameter | Required | Type | Description | | ----------------------------- | :--------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | event\[notification\_type] | yes | enum | Type of notification. Allowed values:* push | | event\[push\_type] | yes | enum | Used only if **notification\_type == push**, ignored in other cases.* If not present - Notification will be delivered to all possible devices for specified users. Each platform has their own standard format.* If specified - Notification will be delivered to the specified platform only.Allowed values:* apns* apns\_voip* gcm | | event\[environment] | yes | enum | An environment of the notification. Allowed values:* development* production | | event\[event\_type] | no | enum | Allowed values:* **one\_shot** - to send immediately | | event\[message] | yes | * event\[push\_type] not present - should be Base64 encoded text.* event\[push\_type] specified - should be formatted as described in [Payload formation rules](/server/push_notifications#payload-formation-rules) | Push notification payload | | event\[user]\[ids] | optional\* | string | Users who will receive push notifications. Several IDs comma separated can be specified | | event\[external\_user]\[ids] | optional\* | string | Send push notifications to users specifying their external IDs. Several external IDs comma separated can be specified | | event\[user]\[tags]\[any] | optional\* | string | Send push notifications to users specifying their tag(s). Recipients must have at least one tag specified in the list of tags | | event\[user]\[tags]\[all] | optional\* | string | Send push notifications to users specifying their tag(s). Recipients must have **all tags** specified in list of tags | | event\[user]\[tags]\[exclude] | optional\* | string | Send push notifications to users who do not have the specified tags | \*\* *Only one of these parameters is required.*\* ###### Request example [Section titled “Request example”](#request-example-3) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"event": {"notification_type": "push", "environment": "development", "user": { "ids": "271"}, "message": "payload=ew0KICAgICJhcHMiIDogew0KICAgICAgICAiYWxlcnQiIDogew0KICAgICAgICAgICAgInRpdGxlIiA6ICJHYW1lIFJlcXVlc3QiLA0KICAgICAgICAgICAgImJvZHkiIDogIkJvYiB3YW50cyB0byBwbGF5IHBva2VyIg0KICAgICAgICB9LA0KICAgICAgICAiYmFkZ2UiIDogNQ0KICAgIH0sDQogICAgImFjbWUxIiA6ICJiYXIiDQp9", "push_type": "apns"}}' \ https://api.connectycube.com/events ``` ###### Response [Section titled “Response”](#response-3) ```json [{ "event": { "id": 8429, "event_type": "one_shot", "message": "payload=ew0KICAgICJhcHMiIDogew0KICAgICAgICAiYWxlcnQiIDogew0KICAgICAgICAgICAgInRpdGxlIiA6ICJHYW1lIFJlcXVlc3QiLA0KICAgICAgICAgICAgImJvZHkiIDogIkJvYiB3YW50cyB0byBwbGF5IHBva2VyIg0KICAgICAgICB9LA0KICAgICAgICAiYmFkZ2UiIDogNQ0KICAgIH0sDQogICAgImFjbWUxIiA6ICJiYXIiDQp9", "date": null, "period": null, "name": null, "occured_count": 0, "created_at": "2018-12-06T11:31:23Z", "updated_at": "2018-12-06T11:31:23Z", "end_date": null, "active": true, "application_id": 1, "user_id": 271, "kind": "API", "environment": "development", "tag_query": null, "notification_channel": { "name": "apns" } } }] ``` ## Payload formation rules [Section titled “Payload formation rules”](#payload-formation-rules) ### Universal Push Notifications [Section titled “Universal Push Notifications”](#universal-push-notifications) Universal push notifications will be delivered to all platforms. To send Universal push notification do not set `event[push_type]` parameter in the ‘Create event’ request. There are some standard parameters, which will be translated to particular platform parameters: * **message** - push text. Will be translated to `aps.alert.body` for iOS and to `data.message` for Android * **ios\_badge** - will be translated to `aps.badge` for iOS. Ignored for Android * **ios\_sound** - will be translated to `aps.sound` for iOS. Ignored for Android * **ios\_content\_available=1** - will be translated to `aps.content-available` for iOS. Ignored for Android * **ios\_mutable\_content=1** - will be translated to `aps.mutable-content` for iOS. Ignored for Android * **ios\_category** - will be translated to `aps.category` for iOS. Ignored for Android * **ios\_voip=1** - will initiate VoIP push notification for iOS if user has VoIP push subscription. Otherwise - iOS user will receive standard iOS push. For Android - it will be a standard push. * **ios\_push\_type** - will be translated to `apns-push-type` header for iOS. Allowed values (`alert`, `background`), default `alert`. Ignored for Android * **ios\_thread\_id** - will be translated to `aps.thread-id` for iOS. Ignored for Android * **expiration** - A UNIX epoch date expressed in seconds (UTC). This value identifies the date when the notification is no longer valid and can be discarded. In short, the value should equal ‘current timestamp’ + ‘ttl offset, in seconds’. Will be translated to `apns-expiration` for iOS and `time-to-live` or `ttl` for Android FCM * **android\_fcm\_notification** - will be translated to ‘notification.\*’ param on Android. Ignored for iOS. [Available fields](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#AndroidNotification) * **ios\_loc\_data** - object with notification localization settings for iOS. Ignored for Android\ Object example: ```json { "title_loc_key": "GREETING_NOTIFICATION", "title_loc_args": ["smith"], "body_loc_key": "CUBE_NOTIFICATION", "body_loc_args": ["hi there", "guest"] } ``` `title_loc_key` - will be translated to `aps.alert.title-loc-key`\ `title_loc_args` - will be translated to `aps.alert.title-loc-args`\ `body_loc_key` - will be translated to `aps.alert.loc-key`\ `body_loc_args` - will be translated to `aps.alert.loc-args`\ None of these parameters are required Other custom parameters can be used as well according to specific platform push format. **Example:** 1. Initial JSON message: ```json { "message": "Walking in a flowers garden", "ios_badge": 2, "ios_sound": "lounge.wav", "friend_id": 51, "ios_loc_data": { "title_loc_key": "GREETING_NOTIFICATION", "title_loc_args": ["Tom Silencer"], } } ``` 2. Base64-encoded: ```plaintext eyJtZXNzYWdlIjoiV2Fsa2luZyBpbiBhIGZsb3dlcnMgZ2FyZGVuIiwiaW9zX2JhZGdlIjoyLCJpb3Nfc291bmQiOiJsb3VuZ2Uud2F2IiwiZnJpZW5kX2lkIjo1MSwiaW9zX2xvY19kYXRhIjp7InRpdGxlX2xvY19rZXkiOiJHUkVFVElOR19OT1RJRklDQVRJT04iLCJ0aXRsZV9sb2NfYXJncyI6WyJUb20gU2lsZW5jZXIiXX19 ``` 3. Final message: ```plaintext event[message]=eyJtZXNzYWdlIjoiV2Fsa2luZyBpbiBhIGZsb3dlcnMgZ2FyZGVuIiwiaW9zX2JhZGdlIjoyLCJpb3Nfc291bmQiOiJsb3VuZ2Uud2F2IiwiZnJpZW5kX2lkIjo1MSwiaW9zX2xvY19kYXRhIjp7InRpdGxlX2xvY19rZXkiOiJHUkVFVElOR19OT1RJRklDQVRJT04iLCJ0aXRsZV9sb2NfYXJncyI6WyJUb20gU2lsZW5jZXIiXX19 ``` # Real-time(XMPP) API > Discover a Real-time API - quick and reliable solution which combines benefits of scalable cloud hosted XMPP chat server, auth and incoming IM / chat alerts. Real-time(XMPP) API is a quick and reliable solution which combines benefits of scalable cloud hosted XMPP chat server, seamless authorization and incoming IM / chat alerts. It’s robust, quick and auto-scalable AWS powered cloud servers infrastructure. It’s the best and most comprehensive solution so far to have your users communicate cross-platform. The following ConnectyCube modules are built on top of real-time(XMPP) API: * Chat module * Voice & Video calling module ConnectyCube real-time API is based on origin XMPP standards, but with slightly changes. ## XMPP features supported [Section titled “XMPP features supported”](#xmpp-features-supported) All standard XMPP libraries are supported (please check the list here . All standard XEPs and the following additional ones (below) are supported: * RFC-6120 - - Core: SSL/TLS stream encryption, SASL authentication, Resource binding etc * RFC-6121 - - Instant Messaging and Presence * XEP-0016 - - Privacy Lists * XEP-0203 - - Offline Message Storage * XEP-0280 - - Message Carbons * XEP-0198 - - Stream Management * XEP-0085 - - Chat State Notifications * XEP-0079 - - Advanced Message Processing * XEP-0045 - - Multi User Chat * XEP-0333 - - Chat Markers * XEP-0308 - - Last Message Correction * XEP-0012 - - Last Activity * XEP-0352 - - Client State Indication ## Endpoint [Section titled “Endpoint”](#endpoint) All XMPP API access is over TLS, and accessed via the **chat.connectycube.com:5223** domain. For Web applications it’s also possible to use BOSH/WebSockets endpoints (**** and **wss\://chat.connectycube.com:5291**). ## Authentication in Real-time API [Section titled “Authentication in Real-time API”](#authentication-in-real-time-api) In order to connect to Real-Time API, the following actions should be performed: * create a user in ConnectyCube dashboard [ConnectyCube dashboard](https://admin.connectycube.com) or via Users REST API. * authenticate user via Real-Time API. * login - when user is authenticated via Real-Time API, he receives a JID (Jabber ID) in the following format: ```plaintext {user_id}-{application_id}@chat.connectycube.com ``` ```plaintext * user’s password for XMPP connection depends on what type of user authentication was applied for this particular user: * Standard `login` + `password` authentication: password is the same * Facebook / Twitter / Custom identity authentication: password is the same as ConnectyCube session token ``` ## Handshake / Login Flow [Section titled “Handshake / Login Flow”](#handshake--login-flow) Use this documentation to understand what the typical stanzas used in the ConnectyCube XMPP handshake flow are. **SASL Authentication Handshake Begin** Client: ```xml ``` Server: ```xml ``` Server: ```xml PLAIN ANONYMOUS zlib ``` Client: ```xml MjY5MDQ1OTQtMjk2NTBAY2hhdC5xdWlja2Jsb3guY29tADI2OTA0NTk0LTI5NjUwAGpzX2phc21pbmUyMjI= ``` Server: ```xml ``` **SASL Authentication Handshake End** Client: ```xml ``` Server: ```xml ``` Server: ```xml zlib ``` **Bind Resource Begin** Client: ```xml ``` Server: ```xml 26904594-29650@chat.connectycube.com/1571722472-connectycube-2386480 ``` **Bind Resource End** **Initial Presence Begin** Client: ```xml ``` Server: ```xml ``` **Initial Presence End** ## Server-side chat history [Section titled “Server-side chat history”](#server-side-chat-history) Chat introduces a very simple server-side chat history functionality that allows to moderate, restore messages on a new device via REST, download the chat massages. Connectycube is not able to read the messages - only the account owner has access to them. Messages with a parameter `save_to_history` set to 1 will be saved to chat history. ## Chat message format [Section titled “Chat message format”](#chat-message-format) A message’s **type** attribute can be **chat** or **groupchat**: * **chat** is used for 1-1 messages. * **groupchat** is used for group chats. **1-1 message:** ```xml This is 1-1 message! 1 1409146118 ``` **group chat message:** ```xml This is group chat message! 1 1409146118 ``` By default, messages are not stored on the back-end. To store messages on server, **save\_to\_history** parameter should be added to XMPP messages. There’s also a **date\_sent** field in the extra parameters. It is optional - if it wasn’t supplied, server will automatically add it when it receives the message. In case if the failed message should be re-sent, ‘date\_sent’ field can be used to specify the correct time. Parameter uses a unix timestamp format. **silent message:** ```xml This is message with prevented push notification! 1 1 1409146118 ``` By default, back-end create push notification (chat alert) for offline recipients. You can prevent creating push notification by **silent** parameter set to `1`. **receive message format:** ```xml Nice to met you! 1 1409146118 54789d40535c12b1a5001172 ``` Referring to the response above, server automatically adds a **dialog\_id** extra parameter, so a recipient knows which chat dialog this message is related to. ## Chat message statuses [Section titled “Chat message statuses”](#chat-message-statuses) Adding statuses to the chat allows to monitor when the opponent is typing, when message is delivered on the device and read. #### 1. ‘Is typing’ status [Section titled “1. ‘Is typing’ status”](#1-is-typing-status) \`is typing’ status can be added in 1-1 or group chats. **composing:** ```xml ``` **paused:** ```xml ``` #### 2. ‘Delivered’ status [Section titled “2. ‘Delivered’ status”](#2-delivered-status) ‘delivered’ status is available for 1-1 chat only, so you send ‘delivered’ status directly to users. ```xml 55e6a9c8a28f9a3ea6001f15 ``` #### 3. ‘Read’ status [Section titled “3. ‘Read’ status”](#3-read-status) ‘read’ status is available for 1-1 chat only, so you send ‘read’ status directly to users. ```xml 55e6a9c8a28f9a3ea6001f15 ``` ## Delete message [Section titled “Delete message”](#delete-message) ```xml 52e6a9c8a18f3a3ea6001f18 ``` ## Edit message [Section titled “Edit message”](#edit-message) ```xml How are you today? 52e6a9c8a18f3a3ea6001f18 ``` ## Self-destroy message [Section titled “Self-destroy message”](#self-destroy-message) ```xml How are you today? 1509146118 ``` ## File attachments [Section titled “File attachments”](#file-attachments) To send attachments as messages in chat, attachment should be included into **extraParams** part of a message. Each attachment contains the following attributes: * **type** - type of the attachment. Recommended types: image, video, audio, location. * **id** (optional)- a link to a file, for example ID of a file in Content or Custom Objects modules * **url** (optional) - an url to file in the Internet * **data** (optional) - an attachment’s data. For example, used for location attachments to pass latitude & longitude. * **size** (optional) - file size in bytes * **width** (optional) - if attachment is an image - an image’s width * **height** (optional) - if attachment is an image - an image’s height * **duration** (optional) - if attachment is an audio/video - duration of an audio/video file * **content-type** (optional) - an attachment’s content type * **name** (optional) - an attachment’s name **format:** ```xml Nice to met you! 1 ``` ## System messages [Section titled “System messages”](#system-messages) There is a special channel for any system notifications. The end user can use it to split regular chat messages and other system events. All such messages should contain **extraParams.moduleIdentifier=SystemNotifications** and use **type=headline**. You can send system messages directly to user only (group chat is not supported). **format:** ```xml SystemNotifications value1 value2 ... ``` ## Stream management [Section titled “Stream management”](#stream-management) Stream management ([XEP-0198](http://xmpp.org/extensions/xep-0198.html)) defines an XMPP protocol extension for active management of a stream between two users, including features for stanza acknowledgements and stream resumption. This protocol aims to resolve the issue with lost messages in a case of very poor Internet connection. The basic concept behind stream management is that the initiating entity (a client) and the receiving entity (a server) can exchange “commands” for active management of the stream. The following stream management features are of particular interest because they are expected to improve network reliability and the end-user experience: * Stanza Acknowledgements — the ability to know if a stanza or series of stanzas has been received by one’s peer. * Stream Resumption — the ability to quickly resume a stream that has been terminated. ## Active/Inactive client state [Section titled “Active/Inactive client state”](#activeinactive-client-state) It is common for IM clients to be logged in and ‘online’ even while the user is not interacting with the application. This protocol allows the client to indicate to the server when the user is not actively using the client, allowing the server know that a user wants to receive push notifications while still is connected to chat. Client: ```xml ``` Server: ```xml ``` # Storage API > Manage and optimize data storage to enhance performance and scalability. Explore our documentation for simplified integration and powerful storage solutions. **Storage API** allows to store any kind of files in the ConnectyCube cloud. Mainly it is used for chat attachments, users avatars, group chat pictures. All files in the ConnectyCube are named as **blobs** - binary large object. It is a data type that can store binary objects or data. ## File model [Section titled “File model”](#file-model) | Parameter | Description | | ---------------------- | ---------------------------------------------------------------------------------------------------------------- | | id | FIle identifier | | created\_at | Date and time when file was created. System creates this parameter automatically | | updated\_at | Date and time when file was updated. System creates this parameter automatically | | name | File name, the lenth can be min 1 char and max 100 chars | | status | (**deprecated**) Status of file, can be ‘null’ or ‘complete’ | | public | File availability / access. Can be public or private. By default all created files are private | | uid | Unique identifier that points to an object in Amazon S3 service. Mainly used to download file content. | | content\_type | Type of file. Format: [mime type](https://en.wikipedia.org/wiki/Media_type) | | size | File size | | blob\_object\_access | Data that are used for upload or download file | | last\_read\_access\_ts | (**deprecated**) Last date and time when the file was accessed | | set\_completed\_at | (**deprecated**) Date and time when ‘Completed’ status was set for a file with ‘Declaring file uploaded’ request | ## File upload flow [Section titled “File upload flow”](#file-upload-flow) The complete file upload flow looks as follow: * [Create a file](/server/storage#create-a-file) * [Upload a file](/server/storage#upload-a-file) * [Declare file uploaded](/server/storage#declare-file-uploaded) ## Create a file [Section titled “Create a file”](#create-a-file) With a **create a file** request a new file can be created on server and used in the application. To create a new file at least two mandatory parameters `content_type` and `name` should be set to identify what file will be created. As a response, created file will receive `blob_object_access` parameter with a link on Amazon S3 service where this file will be stored. ###### Endpoint [Section titled “Endpoint”](#endpoint) ```plaintext POST https://api.connectycube.com/blobs ``` ###### Parameters [Section titled “Parameters”](#parameters) | Parameter | Required | Description | | -------------------- | -------- | ---------------------------------------------------------------------------------------------------------- | | blob\[content\_type] | Yes | Mime content type. This is a type of file to create, it can be image, video, audio, etc. | | blob\[name] | Yes | Name of a new file | | blob\[public] | No | File visibility. Can be public or private. By default all created files are private (`blob[public]=false`) | ###### Request example [Section titled “Request example”](#request-example) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"blob": {"content_type": "image/jpeg", "name": "cat.jpeg"}}' \ https://api.connectycube.com/blobs ``` ###### Response [Section titled “Response”](#response) ```json { "blob": { "id": 46919, "uid": "763ef825ad8748239ed07def2c14555100", "content_type": "image/jpeg", "name": "cat.jpeg", "size": null, "created_at": "2018-12-10T10:26:01Z", "updated_at": "2018-12-10T10:26:01Z", "public": true, "blob_object_access": { "id": 46919, "blob_id": 46919, "expires": "2018-12-10T11:26:01Z", "object_access_type": "Write", "params": "https://s3.amazonaws.com/cb-shared-s3?Content-Type=image%2Fjpeg&Expires=Mon%2C%2010%20Dec%202018%2011%3A26%3A01%20GMT&acl=public-read&key=763ef825ad8748239ed07def2c14555100&policy=eyJleHBpcmF0aW9uIjoiMjAxOC0xMi0xMFQxMToyNjowMVoiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJjYi1zaGFyZWQtczMifSx7ImFjbCI6InB1YmxpYy1yZWFkIn0seyJDb250ZW50LVR5cGUiOiJpbWFnZS9qcGVnIn0seyJzdWNjZXNzX2FjdGlvbl9zdGF0dXMiOiIyMDEifSx7IkV4cGlyZXMiOiJNb24sIDEwIERlYyAyMDE4IDExOjI2OjAxIEdNVCJ9LHsia2V5IjoiNzYzZWY4MjVhZDg3NDgyMzllZDA3ZGVmMmMxNDU1NTEwMCJ9LHsieC1hbXotY3JlZGVudGlhbCI6IkFLSUFJRzNXUFQ3UkFMWU9aVzZRLzIwMTgxMjEwL3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QifSx7IngtYW16LWFsZ29yaXRobSI6IkFXUzQtSE1BQy1TSEEyNTYifSx7IngtYW16LWRhdGUiOiIyMDE4MTIxMFQxMDI2MDFaIn1dfQ%3D%3D&success_action_status=201&x-amz-algorithm=AWS4-HMAC-SHA256&x-amz-credential=AKIAIG3WPT7RALYOZW6Q%2F20181210%2Fus-east-1%2Fs3%2Faws4_request&x-amz-date=20181210T102601Z&x-amz-signature=718eabea1a52a1cf03f926310a22ad7f68daa5b2b66432abaed2a0f90e74952d" } } } ``` ## Upload a file [Section titled “Upload a file”](#upload-a-file) Upload a file with the parameters taken from `blob_object_access` field from **create a file** response. **Note:** The maximum size of the uploaded file is 100 Mb. ###### Endpoint [Section titled “Endpoint”](#endpoint-1) ```plaintext POST https://cb-shared-s3.s3.amazonaws.com ``` ###### Parameters [Section titled “Parameters”](#parameters-1) Use parameters and URL from `blob_object_access` received as a response for [Create a file](/server/storage#create-a-file) request. The complete list pf parameters can vary. | Parameter | Value | | ----------------------- | ---------------------------------------------------------------- | | Content-Type | image/jpg | | Expires | Fri, 23 Nov 2018 15.30.33 GMT | | acl | public-read | | key | c44be580f3294f1ca18056d62f75864500 | | policy | eyJleHBpcmF0aW9…OVoifV19 | | success\_action\_status | 201 | | x-amz-algorithm | AWS4-HMAC-SHA256 | | x-amz-credential | AKIAIG3WPT7RALYOZW6Q/20181123/us-east-1/s3/aws4\_request | | x-amz-date | 20181123T141009Z | | x-amz-signature | 6b76f67cc72313fd5c1f39a75af3ec99bc7363cc6db1adf7e9e0d73091c05299 | | file | *root to the* profile\_image.jpg | ###### Request example [Section titled “Request example”](#request-example-1) ```bash curl -X POST -F "Content-Type=image/jpeg" -F "Expires=Fri, 17 Dec 2018 23:30:00 GMT" -F "acl=public-read" -F "key=c44be580f3294f1ca18056d62f75864500" -F "policy=eyJleHBpcmF0aW9uIjoiMjAxNS0wOS0zMFQxMzZGQ4N2ZlNGIyOTlmZjQxZjYzNjMzYmY5YzEwMCJ9LHsieC1hbXotY3JlZGVudGlhbCI6IkFLSUFJWTdLRk0yM1hHWEo3UjdBLzIwMTUwOTMwL3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QifSx7IngtYW16LWFsZ29yaXRobSI6IkFXUzQtSE1BQy1TSEEyNTYifSx7IngtYW16LWRhd" -F "success_action_status=201" -F "x-amz-algorithm=AWS4-HMAC-SHA256" -F "x-amz-credential=AKIAIY7KFM23XGXJ7R7A/20150930/us-east-1/s3/aws4_request" -F "x-amz-date=20181217T141009Z" -F "x-amz-signature=6b76f67cc72313fd5c1f39a75af3ec99bc7363cc6db1adf7e9e0d73091c05299" -F "file=@profile_image.jpg" https://cb-shared-s3.s3.amazonaws.com ``` ###### Response [Section titled “Response”](#response-1) ```plaintext https://cb-shared-s3.s3.amazonaws.com/g6f92bcf84374e4fb8961537f7a7de908 cb-shared-s3 g6f92bcf84374e4fb8961537f7a7de908 "cv1aae3a4mkiob83bc7h1e21eb7e2m88" ``` ## Declare file uploaded [Section titled “Declare file uploaded”](#declare-file-uploaded) Declaring file as uploaded by set a ‘Complete’ status for the uploaded file. ###### Endpoint [Section titled “Endpoint”](#endpoint-2) ```plaintext PUT https://api.connectycube.com/blobs/{blob_id}/complete ``` ###### Parameters [Section titled “Parameters”](#parameters-2) | Parameter | Required | Description | | ----------- | -------- | ----------------------------------- | | blob\[size] | Yes | Size of the uploaded file, in bytes | ###### Request example [Section titled “Request example”](#request-example-2) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"blob": {"size": "347"}}' \ https://api.connectycube.com/blobs/111/complete ``` ###### Response [Section titled “Response”](#response-2) ```plaintext 200 OK ``` ## Get information about file by ID [Section titled “Get information about file by ID”](#get-information-about-file-by-id) Retrieving of information about file by specifying its ID. ###### Endpoint [Section titled “Endpoint”](#endpoint-3) ```plaintext GET https://api.connectycube.com/blobs/{blob_id} ``` ###### Request example [Section titled “Request example”](#request-example-3) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/blobs/8049 ``` ###### Response [Section titled “Response”](#response-3) ```json { "blob": { "id": 8049, "uid": "9ad79a5b8bb1430fbc4bbf7be6215cb700", "content_type": "image/jpeg", "name": "Mixed_Pets.jpg", "size": 70980, "created_at": "2018-08-02T07:52:51Z", "updated_at": "2018-08-02T07:52:51Z", "public": true } } ``` ## Get files list (**deprecated**) [Section titled “Get files list (deprecated)”](#get-files-list-deprecated) Get list of files created by the current user. User ID is taken from the token specified in the request. ###### Endpoint [Section titled “Endpoint”](#endpoint-4) ```plaintext GET https://api.connectycube.com/blobs ``` ###### Parameters [Section titled “Parameters”](#parameters-3) | Parameter | Required | Description | | --------- | -------- | --------------------------------------------------------------------------------------------------------------------- | | page | No | Number of page to show. The first page is shown by default | | per\_page | No | Number of results to show per page. Default number of results per page - 10, maximum number of results per page - 100 | ###### Request example [Section titled “Request example”](#request-example-4) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/blobs ``` ###### Response [Section titled “Response”](#response-4) ```json { "current_page": 1, "per_page": 10, "total_entries": 1, "items": [ { "blob": { "id": 8049, "uid": "9ad79a5b8bb1430fbc4bbf7be6215cb700", "content_type": "image/jpeg", "name": "Pets.jpg", "size": 70980, "created_at": "2018-08-02T07:52:51Z", "updated_at": "2018-08-02T07:52:51Z", "blob_status": "complete", "set_completed_at": "2018-08-02T07:52:51Z", "public": true } } ] } ``` ## Download file by UID [Section titled “Download file by UID”](#download-file-by-uid) Download file (get file as a redirect to the S3 object) by its UID. `UID` is a parameter created by system automatically with a ‘create a file’ request. Only file set as complete can be downloaded. All public file (`blob[public]=true`) can be downloaded without a session token. ###### Endpoit [Section titled “Endpoit”](#endpoit) ```plaintext GET https://api.connectycube.com/blobs/{uid}/download ``` ###### Request example [Section titled “Request example”](#request-example-5) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/blobs/9ad79a5b8bb1430fbc4bbf7be6215cb744/download ``` ###### Response [Section titled “Response”](#response-5) ```plaintext 301 redirect to file ``` ## Get file object by UID [Section titled “Get file object by UID”](#get-file-object-by-uid) Retrieving of information about file by specifying its UID. ###### Endpoint [Section titled “Endpoint”](#endpoint-5) ```plaintext GET https://api.connectycube.com/blobs/{blob_uid}/object ``` ###### Parameters [Section titled “Parameters”](#parameters-4) | Parameter | Required | Description | | ------------- | -------- | -------------------------------- | | download\_url | No | include download url to response | ###### Request example [Section titled “Request example”](#request-example-6) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/blobs/789AFF0737033E93E179BD7491169D6043B0/object?download_url=1 ``` ###### Response [Section titled “Response”](#response-6) ```json { "blob": { "id": 8049, "uid": "789AFF0737033E93E179BD7491169D6043B0", "content_type": "image/jpeg", "name": "Mixed_Pets.jpg", "size": 70980, "created_at": "2018-08-02T07:52:51Z", "updated_at": "2018-08-02T07:52:51Z", "public": true, "blob_object_access": { "dowload_url": "https://cb-shared-s3.s3.amazonaws.com/789AFF0737033E93E179BD7491169D6043B0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIATFRX3OIRNHFSRZEEL%2F20221007%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221007T104904Z&X-Amz-Expires=604800&X-Amz-Signature=1eade6afb3e571c24131a15bf520058uffdv7393521e16e51de1c317c43908b1c5b52&X-Amz-SignedHeaders=host&response-cache-control=max-age%3D604800" } } } ``` ## Edit a file (**deprecated**) [Section titled “Edit a file (deprecated)”](#edit-a-file-deprecated) Update one or more parameters of the file. ###### Endpoint [Section titled “Endpoint”](#endpoint-6) ```plaintext PUT https://api.connectycube.com/blobs/{blob_id} ``` ###### Parameters [Section titled “Parameters”](#parameters-5) | Parameter | Required | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------- | | blob\[content\_type] | Optional | Type of file, format: [mime content type](https://en.wikipedia.org/wiki/Media_type) | | blob\[name] | Optional | File name with a length up to 100 chars | | blob\[new] | Optional | Set to ‘1’ if file content should be changed | ###### Request example [Section titled “Request example”](#request-example-7) ```bash curl -X PUT \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"blob": {"name": "My photo"}}' \ https://api.connectycube.com/blobs/111 ``` ###### Response [Section titled “Response”](#response-7) ```json { "blob": { "id": 8049, "uid": "9ad79a5b8bb1430fbc4bbf7be6215cb700", "content_type": "image/jpeg", "name": "My photo", "size": 70980, "created_at": "2018-08-02T07:52:51Z", "updated_at": "2018-12-10T10:21:18Z", "blob_status": "complete", "set_completed_at": "2018-08-02T07:52:51Z", "public": true } } ``` ## Delete a file [Section titled “Delete a file”](#delete-a-file) Delete a file from server by its ID. ###### Endpoint [Section titled “Endpoint”](#endpoint-7) ```plaintext DELETE https://api.connectycube.com/blobs/{blob_id} ``` ###### Request example [Section titled “Request example”](#request-example-8) ```bash curl -X DELETE \ -H "CB-Token: " \ https://api.connectycube.com/blobs/111 ``` ###### Response [Section titled “Response”](#response-8) ```plaintext 200 OK ``` # Users API > Unlock seamless user management with ConnectyCube's server API. Effortlessly create, manage, and authenticate users to elevate your app's functionality. **Users API** allows to work with users within the application and manage user’s information - add, update and/or remove. To work with user’s data, session with user authorisation should be created initially. ## Users model [Section titled “Users model”](#users-model) A Users model captures certain characteristics about an individual user or group of users registered in the application. | Field name | Description | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | id | User’s identifier on server | | login | User’s login on server. Usually login OR email is required to log in into the application | | password | User’s password sets for his user’s account. Required for log in process | | email | User’s email. Usually login OR email is required to log in into the application | | external\_user\_id | ID of User in external system (**Deprecated**) | | external\_id | ID of User in external system | | facebook\_id | ID of User in Facebook | | twitter\_id | ID of User in Twitter | | full\_name | User’s full name | | phone | User’s phone number | | website | User’s Website address | | custom\_data | User’s additional info (if any) | | user\_tags | Tag(s) added for User. Several tags comma separated can be added. Same tags can be used for any number of users | | avatar | An ID of the graphical representation of the user | | blob\_id | **Deprecated** - ‘avatar’ field should be used instead. ID of associated blob. Note: [blob should be created](/server/storage#create-a-file) before | | created\_at | Date and time when User was registered in the application. System creates this parameter automatically | | updated\_at | System creates / updates this parameter automatically when user’s data are updated | | last\_request\_at | Date and time where the latest request was sent. System creates this parameter automatically | | timezone | Minutes UTC offset | ## User Sign Up [Section titled “User Sign Up”](#user-sign-up) To have an ability to work with user’s data, user should be registered in the application. To register a new user in the application, at least login / email and password should be specified. Other parameters are optional and can be added later. ###### Endpoint [Section titled “Endpoint”](#endpoint) ```plaintext POST https://api.connectycube.com/users ``` ###### Headers [Section titled “Headers”](#headers) * **CB-AuthKey** - an Authorization Key from [Admin panel app’s Credentials page](https://admin.connectycube.com) ###### Parameters [Section titled “Parameters”](#parameters) | Parameter | Required | Description | | ------------------------- | :-----------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------- | | user\[login] | Yes | User’s login. Only login OR email is required | | user\[password] | Yes | User’s password | | user\[email] | Yes | User’s email. Only login OR email is required | | user\[external\_user\_id] | Optional (**Deprecated**) | ID of User in external system | | user\[external\_id] | Optional | ID of User in external system | | user\[facebook\_id] | Optional | ID of User in Facebook | | user\[twitter\_id] | Optional | ID of User in Twitter | | user\[full\_name] | Optional | Full name | | user\[phone] | Optional | Phone number | | user\[website] | Optional | Website address | | user\[custom\_data] | Optional | User’s additional info (if any) | | user\[timezone] | Optional | Minutes UTC offset | | user\[tag\_list] | Optional | Tags added for User. Several tags comma separated can be added. Same tags can be used for any number of users | | user\[avatar] | Optional | The graphical representation of the user | | user\[blob\_id] | Optional | **Deprecated** - ‘avatar’ field should be used instead. ID of associated blob. Note: [blob should be created](/server/storage#create-a-file) before | ###### Request example [Section titled “Request example”](#request-example) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-AuthKey: 29WfrNWdvkhmX6V" \ -d '{"user": {"login": "Dacia", "password": "petU4or!", "email": "dacia_k@domain.com", "facebook_id": "91234409", "twitter_id": "83510562734", "full_name": "Dacia Kail ", "phone": "+6110797757", "timezone": 180}}' \ https://api.connectycube.com/users ``` ###### Response [Section titled “Response”](#response) ```json { "user":{ "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":null, "external_id":null, "facebook_id":"91234409", "twitter_id":"83510562734", "blob_id":null, "custom_data":null, "avatar":null, "user_tags":null, "timezone": 180 } } ``` ## Retrieve Users V2 [Section titled “Retrieve Users V2”](#retrieve-users-v2) Retrieving users within the application. Query result set can be sorted or / and filtered with defining the number of results to shown per page. ###### Endpoint [Section titled “Endpoint”](#endpoint-1) ```plaintext GET https://api.connectycube.com/users/v2 ``` ###### Parameters [Section titled “Parameters”](#parameters-1) Optional parameters to use for data representative. **Pagination** | Parameter | Required | Description | Value example | | --------- | -------- | --------------------------------------------------------------- | ------------- | | offset | No | Number of users to skip before show. Default = 0 | `offset=50` | | limit | No | Number of results to show per request. Max = 100, default = 100 | `limit=20` | **Sort the query result set** | Type | Description | Example | | ---- | ----------- | ---------------------- | | asc | Ascending | `sort_asc=id` | | desc | Descending | `sort_desc=created_at` | **Available fields to filter** | Field | Type | Apply Type | | ----------------- | -------------------------- | ---------------------- | | id | integer | stand-alone | | login | string | stand-alone | | email | string | stand-alone | | full\_name | string | stand-alone | | phone | string | stand-alone | | external\_id | string | stand-alone | | facebook\_id | string | stand-alone | | twitter\_id | string | stand-alone | | user\_tags | array of strings | stand-alone/additional | | last\_request\_at | date (string or timestamp) | additional | | created\_at | date (string or timestamp) | additional | | updated\_at | date (string or timestamp) | additional | **Operators** | Operator | Description | Value example | Type | | ----------- | ------------------------------------ | ------------------------------------------- | ------- | | eq | equal | `id=51946` | primary | | in | list of data equal | `id[in][]=51946&id[in][]=51943` | primary | | start\_with | match string starting with substring | `full_name[start_with]=diver` | primary | | nin | list of data not equal | `user_tags[nin][]=vip` | exclude | | gt | greater than | `updated_at[gt]=2019-11-06T09:21:41Z` | compare | | lt | less than | `created_at[lt]=2019-11-06T09:21:41Z` | compare | | gte | greater than or equal | `last_request_at[gte]=2017-12-06T09:21:41Z` | compare | | lte | less than or equal | `last_request_at[lte]=2018-11-06T09:21:41Z` | compare | Query need to contain at least one `stand-alone` field with `primary` operator\ `stand-alone` field can’t be used with `compare` operators\ `start_with` operator search for a match at the beginning of the string, this operator automatic set `limit` = 5, minimum argument length for searching = 4 Examples of valid/invalid query: * `id=51946` - valid (`stand-alone` with `primary` operator) * `id[in][]=51946&id[in][]=51943&last_request_at[gt]=2018-12-06T09:21:41Z` - valid (`stand-alone` with `primary` operator and `additional` with `compare` operator) * `user_tags=guest` - valid (`stand-alone` with `primary` operator) * `user_tags=guest&created_at[lt]=1690886495` - valid (`stand-alone/additional` with `primary` operator and `additional` with `compare` operator) * `login=smith1&phone=6754987345566&user_tags[nin][]=vip&updated_at[lte]=2018-12-06T09:21:41Z` - valid (has `stand-alone` with `primary` operator) * `phone=6754987345566&last_request_at=2020-11-09T08:21:41Z` - valid (`stand-alone` with `primary` operator) * `full_name[start_with]=hunter&id[nin][]=68647` - valid (`stand-alone` with `primary` operator, `start_with` argument length = 6) * `login[nin][]=admin19` - invalid (`stand-alone` only with `exclude` operator) * `user_tags[nin][]=guest` - invalid (`stand-alone/additional` only with `exclude` operator) * `last_request_at=2017-07-06T11:21:41Z` - invalid (`additional` with `primary` operator) * `created_at[gte]=2019-11-06T09:21:41Z` - invalid (`additional` with `compare` operator) * `login[start_with]=vip` - invalid (`stand-alone` with `primary` operator, but `start_with` argument length = 3) Example: ```bash curl -X GET \ -H "CB-Token: " \ -d 'id[in][]=52671&id[in][]=51943&phone=5464579797975&last_request_at[gt]=2018-12-06T09:21:41Z&sort_desc=id&limit=10' \ https://api.connectycube.com/users/v2 ``` ###### Response [Section titled “Response”](#response-1) ```json { "limit":10, "skip":0, "total_entries":1, "items":[ { "id":52671, "full_name":"David Smith", "email":null, "login":"smithguest18", "phone":"5464579797975", "website":null, "created_at":"2018-12-06T09:16:26Z", "updated_at":"2018-12-06T09:16:26Z", "last_request_at":"2019-12-07T10:21:41Z", "external_user_id":null, "external_id":null, "facebook_id":null, "twitter_id":null, "blob_id":null, "custom_data":null, "avatar":null, "user_tags":null, "timezone": null } ] } ``` ## Retrieve Users V1 (**Deprecated**) [Section titled “Retrieve Users V1 (Deprecated)”](#retrieve-users-v1-deprecated) Retrieving users within the application. Query result set can be sorted or / and filtered with defining the number of results to shown per page. To enable this deprecated API go to `Overview` -> `Permissions`, and uncheck option `DISABLE LIST USERS API V1`. ![App Permissions](/_astro/permissions_tab.C2yQdnbz_1Lqz4w.webp) ![Permission option](/_astro/permissions_users_disable_retrieve.C6eTSv7__Z2bPRTE.webp) ###### Endpoint [Section titled “Endpoint”](#endpoint-2) ```plaintext GET https://api.connectycube.com/users ``` ###### Parameters [Section titled “Parameters”](#parameters-2) Optional parameters to use for data representative. **Pagination** | Parameter | Required | Description | | --------- | :------: | --------------------------------------------------------------------------------------------------------------------- | | page | No | Number of page to show. The first page is shown by default | | per\_page | No | Number of results to show per page. Default number of results per page - 10, maximum number of results per page - 100 | Example: ```bash curl -X GET \ -H "CB-Token: " \ -d "per_page=7" \ https://api.connectycube.com/users ``` **Filtering the query result set** * Parameter to use to filter data - filter\[] * Filter by - all fields (exclude ‘custom\_data’) are allowed to be used as a filter for results * Data types: * number * string * date - date format must be yyyy-mm-dd or yyyy.mm.dd) * **Request format:** `filter[]={data_type}+{field_to_filter}+[operator]+{value_to_filter}` | Operator | Description | Value example | Data to return | | -------- | --------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------ | | gt | greater than | filter\[gt]=number+id+gt+51496 | Users with IDs greater than 51496 | | lt | less than | filter\[lt]=number+id+lt+51946 | Users with IDs less than specified ID | | ge | greater than or equal | filter\[ge]=number+id+ge+51946 | Users with IDs greater than or equal to specified ID | | le | less than or equal | filter\[le]=number+id+le+51946 | Users with IDs less than or equal to specified ID | | eq | equal | filter\[eq]=number+id+eg+51946 | User with ID equal to specified ID | | ne | not equal | filter\[ne]=number+id+ne+51946 | Users with IDs not equal to specified ID | | between | between | filter\[between]=number+id+between+51941,51959 | List of users with IDs between the specified range. Range limits should be comma separated | | in | list of data | filter\[in]=number+id+in+51943,51946 | Users with the specified IDs | Example: ```bash curl -X GET \ -H "CB-Token: " \ -d 'filter[]=number+id+gt+51946' \ https://api.connectycube.com/users ``` **Sort the query result set** | Type of sort | Description | Value Example | | ------------ | -------------------------------------------------------------------- | ------------------------------------------------------------------------- | | asc | sort the query result set in an ascending order by any of the field | order=asc+{data\_type}+{field}. Example: order=asc+string+login | | desc | sort the query result set in an descending order by any of the field | order=desc+{data\_type}+{field}. Example: order=desc+integer+facebook\_id | Example: ```bash curl -X GET \ -H "CB-Token: " \ -d 'order=desc+string+full_name' \ https://api.connectycube.com/users ``` ###### Request example [Section titled “Request example”](#request-example-1) ```bash curl -X GET \ -H "CB-Token: " \ -d 'order=asc+string+login' \ https://api.connectycube.com/users ``` ###### Response [Section titled “Response”](#response-2) ```json { "current_page":1, "per_page":10, "total_entries":3, "items":[ { "user": { "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, "external_id":null, "facebook_id":"91234409", "twitter_id":"83510562734", "blob_id":null, "custom_data":null, "avatar":null, "user_tags":null, "timezone": null } }, { "user": { "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, "external_id":null, "facebook_id":"95610574", "twitter_id":null, "blob_id":null, "custom_data":"Responsible for signing documents", "avatar":null, "user_tags":"vip,accountant", "timezone": null } }, { "user": { "id":51943, "full_name":"Pallavi Purushottam", "email":"pavallip@domain.com", "login":"ppavalli", "phone":"+6138907507", "website":null, "created_at":"2018-12-06T09:21:41Z", "updated_at":"2018-12-06T09:21:41Z", "last_request_at":null, "external_user_id":null, "external_id":null, "facebook_id":null, "twitter_id":null, "blob_id":null, "custom_data":null, "avatar":null, "user_tags":"accountant", "timezone": null } }, ] } ``` **Note:** if ‘Allow to retrieve a list of users via API’ is un-ticked, retrieving users is forbidden, error is shown in the response: ```json { "errors":{ "base":["Users retrieving forbidden in admin panel, application settings. Setup it here https://admin.connectycube.com/apps/230/service/users/settings"] } } ``` ### Show User by identifier (**Deprecated**) [Section titled “Show User by identifier (Deprecated)”](#show-user-by-identifier-deprecated) Retrieve information about User stored in the data base by specifying user’s identifier. ###### Endpoint [Section titled “Endpoint”](#endpoint-3) ```plaintext GET https://api.connectycube.com/users/{user_id} ``` ###### Request example [Section titled “Request example”](#request-example-2) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/users/51946 ``` ###### Response [Section titled “Response”](#response-3) ```json { "user":{ "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", "timezone": null } } ``` ## Retrieve User by login (**Deprecated**) [Section titled “Retrieve User by login (Deprecated)”](#retrieve-user-by-login-deprecated) Retrieve information about User stored in the bata base by specifying user’s login. ###### Endpoint [Section titled “Endpoint”](#endpoint-4) ```plaintext GET https://api.connectycube.com/users/by_login ``` ###### Parameters [Section titled “Parameters”](#parameters-3) | Parameter | Required | Description | | --------- | :------: | ------------------------------------- | | login | Yes | User’s login to login into the system | ###### Request example [Section titled “Request example”](#request-example-3) ```bash curl -X GET \ -H "CB-Token: " \ -d 'login=Dacia' \ https://api.connectycube.com/users/by_login ``` ###### Response [Section titled “Response”](#response-4) ```json { "user":{ "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, "timezone": null } } ``` ### Retrieve Users by full name (**Deprecated**) [Section titled “Retrieve Users by full name (Deprecated)”](#retrieve-users-by-full-name-deprecated) Retrieve information about User stored in the data base by specifying user’s full name. ###### Endpoint [Section titled “Endpoint”](#endpoint-5) ```plaintext GET https://api.conectycube.com/users/by_full_name ``` ###### Parameters [Section titled “Parameters”](#parameters-4) | Parameter | Required | Description | | ---------- | :------: | --------------------------------------------------------------------------------------------------------------------- | | full\_name | Yes | Full name of user | | page | No | Number of page to show. The first page is shown by default | | per\_page | No | Number of results to show per page. Default number of results per page - 10, maximum number of results per page - 100 | ###### Request example [Section titled “Request example”](#request-example-4) ```bash curl -X GET \ -H "CB-Token: " \ -d 'full_name=Gabrielle%20Corcoran' \ https://api.connectycube.com/users/by_full_name ``` ###### Response [Section titled “Response”](#response-5) ```json { "current_page":1, "per_page":10, "total_entries":1, "items":[ { "user": { "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", "timezone": null } } ] } ``` ## Retrieve User by Facebook identifier (**Deprecated**) [Section titled “Retrieve User by Facebook identifier (Deprecated)”](#retrieve-user-by-facebook-identifier-deprecated) Retrieve information about User stored in the data base by specifying user’s Facebook identifier. If no Facebook ID is available for User, system will return 404 error. ###### Endpoint [Section titled “Endpoint”](#endpoint-6) ```plaintext GET https://api.connectycube.com/users/by_facebook_id ``` ###### Parameters [Section titled “Parameters”](#parameters-5) | Parameter | Required | Description | | ------------ | :------: | --------------------- | | facebook\_id | Yes | User’s ID on Facebook | ###### Request example [Section titled “Request example”](#request-example-5) ```bash curl -X GET \ -H "CB-Token: " \ -d 'facebook_id=95610574' \ https://api.connectycube.com/users/by_facebook_id ``` ###### Response [Section titled “Response”](#response-6) ```json { "user": { "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", "timezone": null } } ``` ## Retrieve User by Twitter identifier (**Deprecated**) [Section titled “Retrieve User by Twitter identifier (Deprecated)”](#retrieve-user-by-twitter-identifier-deprecated) Retrieve information about User stored in the data base by specifying user’s Twitter identifier. If no Twitter ID is available for User, system will return 404 error. ###### Endpoint [Section titled “Endpoint”](#endpoint-7) ```plaintext GET https://api.connectycube.com/users/by_twitter_id ``` ###### Parameters [Section titled “Parameters”](#parameters-6) | Parameter | Required | Description | | ----------- | :------: | -------------------- | | twitter\_id | Yes | User’s ID on Twitter | ###### Request example [Section titled “Request example”](#request-example-6) ```bash curl -X GET \ -H "CB-Token: " \ -d 'twitter_id=83510562734' \ https://api.connectycube.com/users/by_twitter_id ``` ###### Response [Section titled “Response”](#response-7) ```json { "user": { "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, "timezone": null } } ``` ## Retrieve User by email (**Deprecated**) [Section titled “Retrieve User by email (Deprecated)”](#retrieve-user-by-email-deprecated) Retrieve information about User stored in the data base by specifying user’s email. ###### Endpoint [Section titled “Endpoint”](#endpoint-8) ```plaintext GET https://api.connectycube.com/users/by_email ``` ###### Parameters [Section titled “Parameters”](#parameters-7) | Parameter | Required | Description | | --------- | -------- | -------------------- | | email | Yes | User’s email address | ###### Request example [Section titled “Request example”](#request-example-7) ```bash curl -X GET \ -H "CB-Token: " \ -d 'email=dacia_k@domain.com' \ https://api.connectycube.com/users/by_email ``` ###### Response [Section titled “Response”](#response-8) ```json { "user": { "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, "timezone": null } } ``` ## Retrieve Users by tags (**Deprecated**) [Section titled “Retrieve Users by tags (Deprecated)”](#retrieve-users-by-tags-deprecated) Retrieve information about User(s) stored in the data base by specifying one or more tags added for their profiles. At least one specified tag must match with User’s tags to retrieve User’s information. ###### Endpoint [Section titled “Endpoint”](#endpoint-9) ```plaintext GET https://api.connectycube.com/users/by_tags ``` ###### Parameters [Section titled “Parameters”](#parameters-8) | Param | Required | Description | | --------- | -------- | ------------------------------------------------------------------------------------------------------ | | tags | Yes | API User tag(s). The maximum number of tags per user: 5. Several tags comma separated can be specified | | page | No | Page number of the book of the results that you want to get. Default: 1 | | per\_page | No | The maximum number of results per page. Min: 1. Max: 100. Default: 10 | ###### Request example [Section titled “Request example”](#request-example-8) ```bash curl -X GET \ -H "CB-Token: " \ -d 'tags=accountant' \ https://api.connectycube.com/users/by_tags ``` ###### Response [Section titled “Response”](#response-9) ```json { "current_page":1, "per_page":10, "total_entries":2, "items":[ { "user": { "id":51943, "full_name":"Pallavi Purushottam", "email":"pavallip@domain.com", "login":"ppavalli", "phone":"+6138907507", "website":null, "created_at":"2018-12-06T09:21:41Z", "updated_at":"2018-12-06T09:21:41Z", "last_request_at":null, "external_user_id":null, "facebook_id":null, "twitter_id":null, "blob_id":null, "custom_data":null, "avatar":null, "user_tags":"accountant", "timezone": null } }, { "user": { "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", "timezone": null } } ] } ``` ## Retrieve User by external user id (**Deprecated**) [Section titled “Retrieve User by external user id (Deprecated)”](#retrieve-user-by-external-user-id-deprecated) Retrieve information about User stored in the data base by specifying user’s external User ID. ###### Endpoint [Section titled “Endpoint”](#endpoint-10) ```plaintext GET https://api.connectycube.com/users/external/{id} ``` ###### Request example [Section titled “Request example”](#request-example-9) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/users/external/52691165 ``` ###### Response [Section titled “Response”](#response-10) ```json { "user": { "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, "timezone": null } } ``` ## Update User by identifier [Section titled “Update User by identifier”](#update-user-by-identifier) Update User’s information stored in the data base by specifying user’s identifier. One or more parameters can be updated at once. System will return the updated data in the response. ###### Endpoint [Section titled “Endpoint”](#endpoint-11) ```plaintext PUT https://api.connectycube.com/users/{user_id} ``` ###### Parameters [Section titled “Parameters”](#parameters-9) | Parameter | Required | Description | | ------------------------- | :------: | ------------------------------------------------------------------ | | user\[login] | Optional | User login | | user\[password] | Optional | User password | | user\[old\_password] | Optional | Old user password. Required if new password provided | | user\[email] | Optional | User email | | user\[external\_user\_id] | Optional | ID of User in external system | | user\[facebook\_id] | Optional | ID of User in Facebook | | user\[twitter\_id] | Optional | ID of User in Twitter | | user\[full\_name] | Optional | Full name | | user\[phone] | Optional | Phone | | user\[website] | Optional | Website | | user\[custom\_data] | Optional | User’s additional info (if any) | | user\[tag\_list] | Optional | Tags | | user\[timezone] | Optional | Minutes UTC offset | | user\[avatar] | Optional | The graphical representation of the user | | user\[blob\_id] | Optional | **Deprecated** - ID of associated blob (for example, user’s photo) | ###### Request example [Section titled “Request example”](#request-example-10) ```bash curl -X PUT \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"user": {"email": "pallavi.purushottam@yahoo.com", "website": "pavalli.com.au"}}' \ https://api.connectycube.com/users/51943 ``` ###### Response [Section titled “Response”](#response-11) ```json { "user": { "id":51943, "full_name":"Pallavi Purushottam", "email":"pallavi.purushottam@yahoo.com", "login":"ppavalli", "phone":"+6138907507", "website":"http://pavalli.com.au", "created_at":"2018-12-06T09:21:41Z", "updated_at":"2018-12-06T11:50:16Z", "last_request_at":null, "external_user_id":null, "facebook_id":null, "twitter_id":null, "blob_id":null, "custom_data":null, "avatar":null, "user_tags":"accountant", "timezone": null } } ``` ## Delete User by identifier [Section titled “Delete User by identifier”](#delete-user-by-identifier) Delete User by user’s identifier. Users can delete their own accounts only. ###### Endpoint [Section titled “Endpoint”](#endpoint-12) ```plaintext DELETE https://api.connectycube.com/users/{user_id} ``` ###### Request example [Section titled “Request example”](#request-example-11) ```bash curl -X DELETE \ -H "CB-Token: " \ https://api.connectycube.com/users/51959 ``` ###### Response [Section titled “Response”](#response-12) ```json Status: 200 ``` ## Delete User by external user ID [Section titled “Delete User by external user ID”](#delete-user-by-external-user-id) Delete User by specifying an external user id. ###### Request [Section titled “Request”](#request) ```plaintext DELETE https://api.connectycube.com/users/external/{external_user_id} ``` ###### Request example [Section titled “Request example”](#request-example-12) ```bash curl -X DELETE \ -H "CB-Token: " \ https://api.connectycube.com/users/external/52691165 ``` ###### Response [Section titled “Response”](#response-13) ```json Status: 200 ``` ## Reset User password by email [Section titled “Reset User password by email”](#reset-user-password-by-email) Reseting User’s password by specifying an email. Reset link will be sent on the specified email. ###### Endpoint [Section titled “Endpoint”](#endpoint-13) ```plaintext GET https://api.connectycube.com/users/password/reset ``` ###### Parameters [Section titled “Parameters”](#parameters-10) | Parameter | Required | Description | | --------- | -------- | -------------------------------------------- | | email | Yes | User’s email available in the user’s profile | ###### Request example [Section titled “Request example”](#request-example-13) ```bash curl -X GET \ -H "CB-Token: " \ -d 'email=dacia_k@domain.com' \ https://api.connectycube.com/users/password/reset ``` ###### Response [Section titled “Response”](#response-14) ```json Status: 200 ``` User whose email was specified in the requests receives an email immidiately with a reset link: ![Reset password email](/_astro/reset_password_email.Bqp1UlGQ_ZWHBj0.webp) # Video Chat > Experience seamless video chat with our platform, facilitating productive and visual communication between users. Video chat or video calling is essentially streaming both audio and video inputs asynchronously between two or more end users. Video calling is a great way to have productive and visual communication between users. Typically both audio and video calling are used along with 1:1 / IM textual chat communication but there are use cases (such is in gaming or when walking / driving for example) where they are used on their own. ## How it works [Section titled “How it works”](#how-it-works) ConnectyCube SDK client library works with input sources (camera, microphone), codecs, compression and then the data is streamed peer-to-peer between end users. This way video calling doesn’t impact the server much, so the system is highly scalable. Server, however, enables the handshake between end users before streaming starts to take place, and also it resolves NAT traversal in case configuration of networks and firewalls between end users makes call impossible otherwise. This is done with the help of ConnectyCube STUN/TURN server. ## Technologies used [Section titled “Technologies used”](#technologies-used) Our latest video chat solution uses the open-source technology [WebRTC](https://webrtc.org/). It is intended for the organisation of streaming media data between browsers or other supporting it applications for peer-to-peer technology without any additional plugins. Get an overview of WebRTC from [the Google I/O presentation](https://www.youtube.com/watch?v=p2HzZkd2A40). [WebRTC FAQ](https://webrtc.github.io/webrtc-org/faq/) To achieve a real-time media communication, several transfer servers for data exchanges and a specific signaling mechanism are required. Their roles are: * Get network information such as IP addresses and ports and exchange this with other WebRTC clients (known as **peers**) to enable connection even through NATs and firewalls * Coordinate signaling communication to report errors and initiate or close sessions * Exchange information about media and client capability such as resolution and codecs * Communicate streaming audio, video or data The signaling in the WebRTC module is implemented over the **XMPP protocol** using [Chat API](/server/chat). Our module is a high-level solution around WebRTC technology. Read more about signaling in the next paragraph. ## P2P calling [Section titled “P2P calling”](#p2p-calling) P2P Video Chat is used mainly for 1-1 calls or for small group calls (up to 4 users). It’s based on [Mesh architecture](https://webrtcglossary.com/mesh/). ConnectyCube own signaling protocol is used. ### Signaling v1.0 [Section titled “Signaling v1.0”](#signaling-v10) Next signaling protocol is used in ConnectyCube WebRTC Video chat [iOS](/ios/videocalling) / [Android](/android/videocalling) / [Web](/js/videocalling) code samples. Developers also can use this protocol to build WebRTC library and video chat applications for other platforms. **Note:** All video chat signaling messages have `type="headline"` and an extra parameter `...`. Check these two values to detect the video chat signaling message. #### Initiate a call [Section titled “Initiate a call”](#initiate-a-call) Signal to initiate a call. ###### Format [Section titled “Format”](#format) ```xml WebRTCVideoChat call ... ... ... ... ... ... ... ... ... ``` ###### Parameters [Section titled “Parameters”](#parameters) | Parameter | Description | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | moduleIdentifier | Identifier of a module, holds **WebRTCVideoChat** | | signalType | Type of signal, holds **call** value | | sessionID | Unique id of current video chat session. Users have to use the same sessionID within particular call. Timestamp can be used as a session ID value | | callType | Type of call. Use `1` for video call, `2` for audio call | | sdp | Local session description, value of `RTCSessionDescription.sdp` property , obtained after `createOffer` call. More info | | platform | Type of platform:* iOS* Android* Web | | callerID | The id of a user who initiates a call. Used for group calls | | opponentsIDs | Array of users ids a caller initiates a call with. Used for group calls | | userInfo | Optional user info | Additional custom parameters can be added to a message to pass more information about a caller (like avatar, full\_name, etc.) #### Accept a call [Section titled “Accept a call”](#accept-a-call) Signal to accept an incoming call. ###### Format [Section titled “Format”](#format-1) ```xml WebRTCVideoChat accept ... ... ... ... ``` ###### Parameters [Section titled “Parameters”](#parameters-1) | Parameter | Description | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | moduleIdentifier | Identifier of a module, holds **WebRTCVideoChat** | | signalType | Type of signal, holds **accept** value | | sessionID | Unique ID of current video chat session. Users have to use the same sessionID within particular call. Timestamp can be used as a session ID value. | | sdp | Local session description, value of RTCSessionDescription.sdp property , obtained after ‘createOffer’ call. More info \[] () | | platform | Type of platform:* iOS* Android* Web | | userInfo | Optional user info | Additional custom parameters can be added to a message to pass more information about a caller (like avatar, full\_name, etc.) #### Reject incoming call [Section titled “Reject incoming call”](#reject-incoming-call) Signal to reject an incoming call. ###### Format [Section titled “Format”](#format-2) ```xml WebRTCVideoChat reject ... ... true ``` ###### Parameters [Section titled “Parameters”](#parameters-2) | Parameter | Description | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | moduleIdentifier | Identifier of a module, holds **WebRTCVideoChat** | | signalType | Type of signal, holds **reject** value | | sessionID | Unique id of current video chat session. Users have to use the same sessionID within particular call. Timestamp can be used as a session ID value. | | platform | Type of platform:* iOS* Android* Web | | userInfo | Optional user info | Additional custom parameters can be added to a message to pass more information about a caller (like avatar, full\_name, etc.) If a client doesn’t support WebRTC, he should send the auto reject request with `user_ info={not_supported: 1}` #### Reject incoming call by HTTP [Section titled “Reject incoming call by HTTP”](#reject-incoming-call-by-http) Signal to reject an incoming call by HTTP. ###### Endpoint [Section titled “Endpoint”](#endpoint) ```plaintext POST https://api.connectycube.com/calls/reject ``` ###### Parameters [Section titled “Parameters”](#parameters-3) | Parameter | Description | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | recipientId | Call participant Id | | sessionID | Unique id of current video chat session. Users have to use the same sessionID within particular call. Timestamp can be used as a session ID value. | | platform | Type of platform:* iOS* Android* Web | | userInfo | object with additional params | ###### Request example [Section titled “Request example”](#request-example) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"recipientId": 573, "sessionID": "56eddd33-b3dd-42ee-bf33-11ca1c03218a", "platform": "Web", "userInfo": { "busy": true, "fiend_id": 10 }}' \ https://api.connectycube.com/calls/reject ``` ###### Response [Section titled “Response”](#response) ```json 200 {} ``` #### Hang Up a call [Section titled “Hang Up a call”](#hang-up-a-call) Signal to finish a call. ###### Format [Section titled “Format”](#format-3) ```xml WebRTCVideoChat hangUp ... ... ``` ###### Parameters [Section titled “Parameters”](#parameters-4) | Parameter | Description | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | moduleIdentifier | Identifier of a module, holds **WebRTCVideoChat** | | signalType | Type of signal, holds **hangUp** value | | sessionID | Unique id of current video chat session. Users have to use the same sessionID within particular call. Timestamp can be used as a session ID value. | | platform | Type of platform:* iOS* Android* Web | | userInfo | Optional user info | Additional custom parameters can be added to a message to pass more information about a caller (like avatar, full\_name, etc.). #### ICE candidates [Section titled “ICE candidates”](#ice-candidates) Signal to send WebRTC ICE candidates. ###### Format [Section titled “Format”](#format-4) ```xml WebRTCVideoChat iceCandidates ... ... ... ... ... ... ... ... ``` ###### Parameters [Section titled “Parameters”](#parameters-5) | Parameter | Description | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | moduleIdentifier | Identifier of a module, holds **WebRTCVideoChat** | | signalType | Type of signal, holds **iceCandidates** value | | sessionID | Unique id of current video chat session. Users have to use the same session ID within particular call. Timestamp can be used as a session ID value. | | iceCandidates | An array of WebRTC ICE candidates. More info | #### Update call parameters [Section titled “Update call parameters”](#update-call-parameters) Signal to notify the opponent that some call parameters were updated. ###### Format [Section titled “Format”](#format-5) ```xml WebRTCVideoChat update ... ... ... ... ``` ###### Parameters [Section titled “Parameters”](#parameters-6) | Parameter | Description | | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | moduleIdentifier | Identifier of a module, holds **WebRTCVideoChat** | | signalType | Type of signal, holds **update** value | | sessionID | Unique id of current video chat session. Users have to use the same sessionID within particular call. Timestamp can be used as a session ID value. | | Add all changed parameters to a message to pass these updates to an opponent. | | #### ICE restart [Section titled “ICE restart”](#ice-restart) Signal to restart ICE. ###### Format [Section titled “Format”](#format-6) ```xml WebRTCVideoChat iceRestart ... ... ``` ###### Parameters [Section titled “Parameters”](#parameters-7) | Parameter | Description | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | moduleIdentifier | Identifier of a module, holds **WebRTCVideoChat** | | signalType | Type of signal, holds **iceRestart** value | | sessionID | Unique id of current video chat session. Users have to use the same sessionID within particular call. Timestamp can be used as a session ID value | | sdp | Local session description, value of `RTCSessionDescription.sdp` property , obtained after `createOffer` call. More info | #### ICE restart accept [Section titled “ICE restart accept”](#ice-restart-accept) Signal to accept restart ICE. ###### Format [Section titled “Format”](#format-7) ```xml WebRTCVideoChat iceRestartAccept ... ... ``` ###### Parameters [Section titled “Parameters”](#parameters-8) | Parameter | Description | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | moduleIdentifier | Identifier of a module, holds **WebRTCVideoChat** | | signalType | Type of signal, holds **iceRestartAccept** value | | sessionID | Unique id of current video chat session. Users have to use the same sessionID within particular call. Timestamp can be used as a session ID value | | sdp | Local session description, value of `RTCSessionDescription.sdp` property , obtained after `createOffer` call. More info | ## Conference calling [Section titled “Conference calling”](#conference-calling) 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) ### SDKs supported [Section titled “SDKs supported”](#sdks-supported) * [JS/Web](/js/videocalling-conference) * [ReactNative](/reactnative/videocalling-conference) * [Cordova](/cordova/videocalling-conference) * [iOS](/ios/videocalling-conference) * [Android](/android/videocalling-conference) # Whiteboard API > Enable dynamic collaboration in chat with ConnectyCube Whiteboard API. Ideal for remote meetings, teaching environments, sales demos, real-time workflows. **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. Separated whiteboard server endpoint is used: ## Whiteboard model [Section titled “Whiteboard model”](#whiteboard-model) | Parameter | Description | | ---------------- | -------------------------------------------------------------- | | \_id | Whiteboard identifier | | name | Whiteboard name | | chat\_dialog\_id | A chat dialog identifier to which a whiteboard is conected to. | | user\_id | Whiteboard creator user id | | created\_at | Whiteboard created date. | | updated\_at | Whiteboard updated date. | ## Create whiteboard [Section titled “Create whiteboard”](#create-whiteboard) ###### Endpoint [Section titled “Endpoint”](#endpoint) ```plaintext POST https://api.connectycube.com/whiteboards ``` ###### Parameters [Section titled “Parameters”](#parameters) | Parameter | Required | Description | Value example | | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | | name | Yes | Whiteboard name | ”My whiteboard” | | chat\_dialog\_id | Yes | A chat dialog identifier to which a whiteboard is conected to. Creating whiteboard only available for Group/Private/Meeting chat dialogs. Max 3 whiteboards per chat | ”602f9ea20740a24ec238d9da” | Once whiteboard is created - a user can display it in app in WebView using the following url: ```plaintext https://whiteboard.connectycube.com?whiteboardid={_id}&username={any_desired_username}&title={name} ``` ###### Request example [Section titled “Request example”](#request-example) ```bash curl -X POST \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"name": "My whiteboard", "chat_dialog_id": "602f9ea20740a24ec238d9da"}' \ https://api.connectycube.com/whiteboards ``` ###### Response [Section titled “Response”](#response) ```json { "_id": "602faca5bb3c7a638f8c0805", "name": "My whiteboard", "chat_dialog_id": "602f9ea20740a24ec238d9da", "user_id": 8117, "created_at": "2021-02-19T12:18:45.867Z", "updated_at": "2021-02-19T12:18:45.867Z" } ``` ## Retrieve whiteboards [Section titled “Retrieve whiteboards”](#retrieve-whiteboards) ###### Endpoint [Section titled “Endpoint”](#endpoint-1) ```plaintext GET https://api.connectycube.com/whiteboards ``` ###### Parameters [Section titled “Parameters”](#parameters-1) | Parameter | Required | Description | | ---------------- | -------- | ------------------------------------------------------- | | chat\_dialog\_id | Yes | Retrieve whiteboards related to particular chat dialog. | ###### Request example [Section titled “Request example”](#request-example-1) ```bash curl -X GET \ -H "CB-Token: " \ https://api.connectycube.com/whiteboards?chat_dialog_id=602f9ea20740a24ec238d9da ``` ###### Response [Section titled “Response”](#response-1) ```json [ { "_id": "602faca5bb3c7a638f8c0805", "name": "My whiteboard", "chat_dialog_id": "602f9ea20740a24ec238d9da", "user_id": 8117, "created_at": "2021-02-19T12:18:45.867Z", "updated_at": "2021-02-19T12:18:45.867Z" } ] ``` ## Update whiteboards [Section titled “Update whiteboards”](#update-whiteboards) A request to update whiteboard’s parameters. Only creator can update a whiteboard. ###### Endpoint [Section titled “Endpoint”](#endpoint-2) ```plaintext PUT https://api.connectycube.com/whiteboards/{whiteboard_id} ``` ###### Parameters [Section titled “Parameters”](#parameters-2) | Parameter | Required | Description | Data type | Value example | | --------- | -------- | --------------- | --------- | --------------------- | | name | Yes | Whiteboard name | string | ”New whiteboard name” | ###### Request example [Section titled “Request example”](#request-example-2) ```bash curl -X PUT \ -H "Content-Type: application/json" \ -H "CB-Token: " \ -d '{"name": "New whiteboard name"}' \ https://api.connectycube.com/whiteboards/602faca5bb3c7a638f8c0805 ``` ###### Response [Section titled “Response”](#response-2) ```json { "_id": "602faca5bb3c7a638f8c0805", "name": "New whiteboard name", "chat_dialog_id": "602f9ea20740a24ec238d9da", "user_id": 8117, "created_at": "2021-02-19T12:18:45.867Z", "updated_at": "2021-02-19T12:34:45.765Z" } ``` ## Delete whiteboard [Section titled “Delete whiteboard”](#delete-whiteboard) Delete a whiteboard by ID. Only creator can delete a whiteboard. ###### Endpoint [Section titled “Endpoint”](#endpoint-3) ```plaintext DELETE https://api.connectycube.com/whiteboards/{whiteboard_id} ``` ###### Request example [Section titled “Request example”](#request-example-3) ```bash curl -X DELETE \ -H "CB-Token: " \ https://api.connectycube.com/whiteboards/602faca5bb3c7a638f8c0805 ``` ###### Response [Section titled “Response”](#response-3) ```plaintext 200 OK ``` # Admin API > Explore a set of clearly defined methods of gathering administrative statistics for your app, performing administrative tasks. Admin API is a set of APIs to manage your app outside of user app. Available starting from **Advanced plan**. A special key called **API Key** needs to be used to access the Admin API. You can access (or generate/rotate) your application Administration API Credentials (API KEY) in [ConnectyCube Dashboard](https://admin.connectycube.com):. ![Administration API Key](/_astro/administration_api_key.CcMcUxCX_tySLN.webp) ## List of APIs [Section titled “List of APIs”](#list-of-apis) * [Statistics](/admin_api/statistics/) * [Chat](/admin_api/chat/) # Chat > Enabling admins to monitor and extract the total number of dialogs, messages and more. Gain valuable insights for effective management and optimization. ## Dialog Update [Section titled “Dialog Update”](#dialog-update) ###### Endpoint [Section titled “Endpoint”](#endpoint) ```plaintext PUT https://api.connectycube.com/admin/chat/{dialog_id} ``` ###### Parameters [Section titled “Parameters”](#parameters) | Parameter | Data type | Description | Value example | | --------- | --------- | -------------------------------- | ------------- | | user\_id | integer | User Id for parameter `is_muted` | 32242 | Other parameters same as for [Update Dialog API](/server/chat#update-a-dialog) ###### Request example [Section titled “Request example”](#request-example) ```bash curl -X PUT \ -H "CB-Administration-API-Key: " \ -d '{"user_id": 32242, "is_muted": true}' \ https://api.connectycube.com/admin/chat/620e2b1463ee2a1977b4ee65 ``` ###### Response [Section titled “Response”](#response) ```json { "_id": "620e2b1463ee2a1977b4ee65", "user_id": 32241, "created_at": "2022-02-17T11:01:40Z", "updated_at": "2022-02-17T11:01:49Z", "type": 3, "occupants_ids":[32241, 32242], "last_message": null, "last_message_date_sent": null, "last_message_id": null, "last_message_user_id": null, "last_message_status": null, "name": null, "description": null, "photo": null, "occupants_count": null, "xmpp_room_jid": null, "is_e2ee": null, "admins_ids":[], "is_muted": null, "unread_messages_count": null, "pinned_messages_ids": null, "deleted_ids":[], "silent_ids":[32242] } ``` ## Dialogs List [Section titled “Dialogs List”](#dialogs-list) ###### Endpoint [Section titled “Endpoint”](#endpoint-1) ```plaintext GET https://api.connectycube.com/admin/chat/list ``` ###### Parameters [Section titled “Parameters”](#parameters-1) | Parameter | Data type | Description | Value example | | -------------------- | ------------------- | -------------------------------------------------------------------------------- | ----------------------------------- | | updated\_at | integer (timestamp) | Filter dialogs by updated\_at field (operators `gt`, `lt`, `gte`, `lte`) | updated\_at\[gt]=1687886581 | | created\_at | integer (timestamp) | Filter dialogs by updated\_at field (operators `gt`, `lt`, `gte`, `lte`) | created\_at\[lte]=1687886581 | | \_id | string (ObjectId) | Filter dialogs by \_id field (operators `gt`, `lt`, `gte`, `lte`) | \_id\[gte]=620e2b1463ee2a1977b4ee65 | | limit | integer | Dialogs in one response (default 100, max 100) | limit=50 | | sort\_asc/sort\_desc | string | Set sort direction asc/desc (supported fields `created_at`, `updated_at`, `_id`) | sort\_desc=updated\_at | Other parameters same as for [Retrieve Dialog API](/server/chat#retrieve-chat-dialogs) ###### Request example [Section titled “Request example”](#request-example-1) ```bash curl -X GET \ -H "CB-Administration-API-Key: " \ -d 'updated_at[lt]=1687886581' \ https://api.connectycube.com/admin/chat/list ``` ###### Response [Section titled “Response”](#response-1) ```json { "total_entries": 1788, "skip": 0, "limit": 100, "items": [ { "_id": "620e2b1463ee2a1977b4ee65", "user_id": 32241, "created_at": "2022-02-17T11:01:40Z", "updated_at": "2022-02-17T11:01:49Z", "type": 3, "occupants_ids":[32241, 32242], "last_message": "Warning! People are coming", "last_message_date_sent": 1645095709, "last_message_id": "620e2b14e45f5b089d000008", "last_message_user_id": 32241, "last_message_status": "sent", "name": null, "description": null, "photo": null, "occupants_count": null, "xmpp_room_jid": null, "is_e2ee": null, "admins_ids":[], "is_muted": null, "unread_messages_count": null, "pinned_messages_ids": null, "deleted_ids":[], "silent_ids":[] } ... ] } ``` Response is similar to [Retrieve dialog API](/server/chat#response), but with additional fields | Parameter | Data type | Description | Value example | | ------------ | ----------------------------------- | ----------------------------------- | ---------------- | | silent\_ids | array of int (field may be missing) | Array of users ids who muted chat | `[32241, 32245]` | | deleted\_ids | array of int (field may be missing) | Array of users ids who deleted chat | `[32245]` | ###### NodeJS example [Section titled “NodeJS example”](#nodejs-example) To get list of all dialogs, use pagination by `_id` field: ```js import fetch from 'node-fetch' const url = 'https://api.connectycube.com/admin/chat/list' const headers = { 'CB-Administration-API-Key': 'XXXXXXXXXXXXXXXXX' } const limit = 100 let allDialogs = [] const firstResponse = await fetch(`${url}?limit=${limit}`, { headers }).then(response => response.json()) const { total_entries, items } = firstResponse console.log(total_entries) // 1167 allDialogs = allDialogs.concat(items) const totalBatchesCount = Math.ceil(total_entries / limit) for (let i = 1; i < totalBatchesCount; ++i) { const lastRetrievedDialog = allDialogs.at(-1) const lastId = lastRetrievedDialog['_id'] const firstResponse = await fetch(`${url}?limit=${limit}&_id[lt]=${lastId}`, { headers }).then(response => response.json()) const { items } = firstResponse allDialogs = allDialogs.concat(items) } console.log(allDialogs.length) // 1167 ``` ## Messages List [Section titled “Messages List”](#messages-list) ###### Endpoint [Section titled “Endpoint”](#endpoint-2) ```plaintext GET https://api.connectycube.com/admin/chat/messages/list ``` ###### Parameters [Section titled “Parameters”](#parameters-2) | Parameter | Data type | Description | Value example | | -------------------- | ------------------- | -------------------------------------------------------------------------------- | ----------------------------------------- | | updated\_at | integer (timestamp) | Filter messages by updated\_at field (operators `gt`, `lt`, `gte`, `lte`) | updated\_at\[gt]=1687886581 | | created\_at | integer (timestamp) | Filter messages by updated\_at field (operators `gt`, `lt`, `gte`, `lte`) | created\_at\[lte]=1687886581 | | \_id | string (ObjectId) | Filter messages by \_id field (operators `gt`, `lt`, `gte`, `lte`) | \_id\[gte]=63834bbecf1efb102da40f2c | | limit | integer | Messages in one response (default 100, max 100) | limit=50 | | chat\_dialog\_id | string (ObjectId) | Filter messages by chat\_dialog\_id field | chat\_dialog\_id=61721539107bd9002fdb84ed | | sort\_asc/sort\_desc | string | Set sort direction asc/desc (supported fields `created_at`, `updated_at`, `_id`) | sort\_desc=updated\_at | ###### Request example [Section titled “Request example”](#request-example-2) ```bash curl -X GET \ -H "CB-Administration-API-Key: " \ -d '_id[lte]=63834bbecf1efb102da40f2c' \ https://api.connectycube.com/admin/chat/messages/list ``` ###### Response [Section titled “Response”](#response-2) ```json { "total_entries": 663, "skip": 0, "limit": 100, "items": [ { "_id": "63834bbecf1efb102da40f2c", "chat_dialog_id": "61721539107bd9002fdb84ed", "message": "Warning! People are coming! Right Now. By Http", "date_sent": 1669548990, "sender_id": 4980089, "recipient_id": 4997495, "created_at": "2022-11-27T11:36:31Z", "updated_at": "2022-11-27T11:36:31Z", "read_ids": [4980089], "delivered_ids": [4980089], "userExtraData": null, "mission": "take over the planet", "name": "intel", "attachments": [ { "type": "image", "id": "3004" }, ], "reactions": null, "views_count": 0, "edited_at": null, "read": 0 } ... ] } ``` Response is similar to [Retrieve messages API](/server/chat#response-13), but with additional fields | Parameter | Data type | Description | Value example | | ----------------- | ----------------------------------- | -------------------------------------- | ------------- | | deleted\_for\_ids | array of int (field may be missing) | Array of users ids who deleted message | `[32245]` | ###### NodeJS example [Section titled “NodeJS example”](#nodejs-example-1) To get list of all messages, use pagination by `_id` field: ```js import fetch from 'node-fetch' const url = 'https://api.connectycube.com/admin/chat/messages/list' const headers = { 'CB-Administration-API-Key': 'XXXXXXXXXXXXXXXXX' } const limit = 100 let allMessages = [] const firstResponse = await fetch(`${url}?limit=${limit}`, { headers }).then(response => response.json()) const { total_entries, items } = firstResponse console.log(total_entries) // 663 allMessages = allMessages.concat(items) const totalBatchesCount = Math.ceil(total_entries / limit) for (let i = 1; i < totalBatchesCount; ++i) { const lastRetrievedMessage = allMessages.at(-1) const lastId = lastRetrievedMessage['_id'] const firstResponse = await fetch(`${url}?limit=${limit}&_id[lt]=${lastId}`, { headers }).then(response => response.json()) const { items } = firstResponse allMessages = allMessages.concat(items) } console.log(allMessages.length) // 663 ``` # Statistics API > Gain valuable insights into your app's popularity with statistics on daily and cumulative dialogs and messages. Enhance visibility to optimize app's performance. ## Messages sent total [Section titled “Messages sent total”](#messages-sent-total) ###### Endpoint [Section titled “Endpoint”](#endpoint) ```plaintext GET https://api.connectycube.com/admin/stats/messages/sent-total ``` ###### Parameters [Section titled “Parameters”](#parameters) | Parameter | Data type | Description | | ----------- | ------------------- | ------------------------------------------------- | | start\_date | integer (timestamp) | Message date sent after to include to statistics | | end\_date | integer (timestamp) | Message date sent before to include to statistics | ###### Request example [Section titled “Request example”](#request-example) ```bash curl -X GET \ -H "CB-Administration-API-Key: " \ -d 'start_date=1676894034&end_date=16768967370' \ https://api.connectycube.com/admin/stats/messages/sent-total ``` ###### Response [Section titled “Response”](#response) ```json { "total": 167 } ``` ## Messages sent per day [Section titled “Messages sent per day”](#messages-sent-per-day) ###### Endpoint [Section titled “Endpoint”](#endpoint-1) ```plaintext GET https://api.connectycube.com/admin/stats/messages/sent-per-day ``` ###### Parameters [Section titled “Parameters”](#parameters-1) | Parameter | Data type | Description | | ----------- | ------------------- | ------------------------------------------------- | | start\_date | integer (timestamp) | Message date sent after to include to statistics | | end\_date | integer (timestamp) | Message date sent before to include to statistics | | limit | integer | Days in one response (Default 100) | | offset | integer | Days offset in response (Default 0) | ###### Request example [Section titled “Request example”](#request-example-1) ```bash curl -X GET \ -H "CB-Administration-API-Key: " \ -d 'start_date=1676894034&end_date=16768967370' \ https://api.connectycube.com/admin/stats/messages/sent-per-day ``` ###### Response [Section titled “Response”](#response-1) ```json { "2023-02-20": 56, "2023-02-21": 78, "2023-02-26": 47, ... } ``` ## Dialogs created total [Section titled “Dialogs created total”](#dialogs-created-total) ###### Endpoint [Section titled “Endpoint”](#endpoint-2) ```plaintext GET https://api.connectycube.com/admin/stats/dialogs/created-total ``` ###### Parameters [Section titled “Parameters”](#parameters-2) | Parameter | Data type | Description | | ----------- | ------------------- | ----------------------------------------------- | | start\_date | integer (timestamp) | Dialogs created after to include to statistics | | end\_date | integer (timestamp) | Dialogs created before to include to statistics | | types | array (integer) | Dialogs types before to include to statistics | ###### Request example [Section titled “Request example”](#request-example-2) ```bash curl -X GET \ -H "CB-Administration-API-Key: " \ -d 'start_date=1676894057&end_date=16768967671&types[]=2&types[]=3' \ https://api.connectycube.com/admin/stats/dialogs/created-total ``` ###### Response [Section titled “Response”](#response-2) ```json { "total": 54 } ``` ## Dialogs created per day [Section titled “Dialogs created per day”](#dialogs-created-per-day) ###### Endpoint [Section titled “Endpoint”](#endpoint-3) ```plaintext GET https://api.connectycube.com/admin/stats/dialogs/created-per-day ``` ###### Parameters [Section titled “Parameters”](#parameters-3) | Parameter | Data type | Description | | ----------- | ------------------- | ----------------------------------------------- | | start\_date | integer (timestamp) | Dialogs created after to include to statistics | | end\_date | integer (timestamp) | Dialogs created before to include to statistics | | types | array (integer) | Dialogs types before to include to statistics | | limit | integer | Days in one response (Default 100) | | offset | integer | Days offset in response (Default 0) | ###### Request example [Section titled “Request example”](#request-example-3) ```bash curl -X GET \ -H "CB-Administration-API-Key: " \ -d 'start_date=1676894057&end_date=16768967671&types[]=2&types[]=4' \ https://api.connectycube.com/admin/stats/dialogs/created-per-day ``` ###### Response [Section titled “Response”](#response-3) ```json { "2023-02-20": 21, "2023-02-21": 15, ... } ``` # User API > Enabling admins to monitor and administrating users. ## Delete User [Section titled “Delete User”](#delete-user) ###### Endpoint [Section titled “Endpoint”](#endpoint) ```plaintext DELETE https://api.connectycube.com/admin/user/{user_id} ``` ###### Request example [Section titled “Request example”](#request-example) ```bash curl -X DELETE \ -H "CB-Administration-API-Key: " \ https://api.connectycube.com/admin/user/32242 ``` ###### Response [Section titled “Response”](#response) ```json { "success": true } ``` # Code samples > Explore easy-to-follow Android code snippets for implementing chat, video calling, and push notifications using ConnectyCube SDK With ConnectyCube code samples - learn how to implement 1-1 and group chat messaging, video calling, enable Push Notifications, authenticate your users via phone SMS verification, store and retrieve file attachments from the cloud and many more features. These code samples are simple enough that even novice developers will be able to understand them. ## Messenger Chat and Video Chat code sample [Section titled “Messenger Chat and Video Chat code sample”](#messenger-chat-and-video-chat-code-sample) > [Download source code, in Kotlin](https://github.com/ConnectyCube/android-messenger-app) `````` # Chat Bots Getting Started > A complete guide of how to build your own chat bot, improve bot’s intelligence and recommendation about chat bots hosting Bots are computer programs - third-party applications, that run inside ConnectyCube platform. Bots have almost same qualities as human: they have profile photos, names, they can send messages and upload files, and they can be added to and removed from group chats. Use chat bots to enable users to conversationally interact with your service or your product. Chat bots are controlled programmatically via [ConnectyCube Javascript SDK](/js/) ## Build your own Chat Bot [Section titled “Build your own Chat Bot”](#build-your-own-chat-bot) ### 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: Create Bot user [Section titled “Step 1: Create Bot user”](#step-1-create-bot-user) At ConnectyCube Dashboard, in **Users** module - create a new user to control your chat bot. Then save somewhere the user’s ID, login and password. We will need these values later. ### Step 2: Create Node.js application to control your bot [Section titled “Step 2: Create Node.js application to control your bot”](#step-2-create-nodejs-application-to-control-your-bot) Open terminal and type the following commands: ```bash mkdir my-chat-bot cd my-chat-bot npm init ``` This will ask you a bunch of questions, and then write a `package.json` file for you. More information on [npm init](https://docs.npmjs.com/cli/init). The main thing is that we have now a `package.json` file and can start develop our first chat bot. ### Step 3: Connect ConnectyCube SDK [Section titled “Step 3: Connect ConnectyCube SDK”](#step-3-connect-connectycube-sdk) In terminal type the following command: ```bash npm install connectycube --save # yarn add connectycube ``` ### Step 4: Create index.js file [Section titled “Step 4: Create index.js file”](#step-4-create-indexjs-file) In terminal type the following command: ```bash touch index.js ``` It will create the main entry point for your bot. Then also open `package.json` file and add command to run our bot: ```json "scripts": { "start": "node index.js" }, ``` Now open the `index.js` file and let’s write some logic. ### Step 5: Making heart beat of your bot [Section titled “Step 5: Making heart beat of your bot”](#step-5-making-heart-beat-of-your-bot) Open `index.js` file and write the following code: ```javascript const Connectycube = require("connectycube"); // Initialise SDK const APPLICATION_CREDENTIALS = { appId: 0, // put your ConnectyCube App Id authKey: "...", // put your ConnectyCube Auth Key }; ConnectyCube.init(APPLICATION_CREDENTIALS); // Connect to Real-Time Chat const BOT_USER_CREDENTIALS = { userId: 0, // put your Bot user id login: "...", // put your Bot user login password: "...", // put your Bot user password }; const onError = (error) => { console.log("Chat connect is failed", JSON.stringify(error)); process.exit(1); }; const onConnected = () => { console.log("Bot is up and running"); // Add chat messages listener Connectycube.chat.onMessageListener = onMessageListener; }; function onMessageListener(userId, msg) { // process 1-1 messages if (msg.type === "chat" && msg.body) { const answerMessage = { type: "chat", body: msg.body, // echo back original message extension: { save_to_history: 1, }, }; Connectycube.chat.send(userId, answerMessage); } } const userCredentials = { login: BOT_USER_CREDENTIALS.login, password: BOT_USER_CREDENTIALS.password }; ConnectyCube.createSession(userCredentials).then((session) => { Connectycube.chat.connect(BOT_USER_CREDENTIALS).then(onConnected).catch(onError); }).catch((error) => {}); process.on("exit", function () { console.log("Kill bot"); Connectycube.chat.disconnect(); }); ``` This is a simple bot that simply reply back with origin message. Nothing especial. But you got the idea. You just need to put in `APPLICATION_CREDENTIALS` variable your ConnectyCube Application credentials and in `BOT_USER_CREDENTIALS` variable - you bot user credentials. The complete source code of chat bot template is available ### Step 6: Run our bot [Section titled “Step 6: Run our bot”](#step-6-run-our-bot) In terminal type the following command: ```bash npm start ``` Now you can send a message to your bot and will receive a reply. ### Step 7: Improve bot’s intelligence - add AI [Section titled “Step 7: Improve bot’s intelligence - add AI”](#step-7-improve-bots-intelligence---add-ai) #### Create OpenAI API Key [Section titled “Create OpenAI API Key”](#create-openai-api-key) Usually, it’s not enough just to build simple bot which echoes your messages. It’s better to add some intelligence for you bot. Here is where ChatGPT OpenAI API comes to the rescue. After making an OpenAI account, you’ll need an API Key. You can get an [OpenAI API Key here](https://platform.openai.com/api-keys) by clicking on **+ Create new secret key**. ![Create OpenAI API Key](/images/guides/chat-bots/open-ai-create-api-key.png) Save that API key for later to use the OpenAI client library in your ConnectyCube Chat Bot. #### Connect OpenAI API SDK [Section titled “Connect OpenAI API SDK”](#connect-openai-api-sdk) Open **index.js** file and write the following code: ```javascript const { Configuration, OpenAIApi } = require("openai"); const configuration = new Configuration({ apiKey: }); const openai = new OpenAIApi(configuration); const response = await openai.completions.create({ model: "gpt-3.5-turbo-instruct", prompt: msg.body, temperature: 0.7, // Creative risks the engine takes when generating text. max_tokens: 3000, // Maximum completion length. max: 4000-prompt frequency_penalty: 0.7 // # between 0 and 1. The higher this value, the bigger the effort the model will make in not repeating itself. }); ``` And then modify the answer message object: ```javascript const answerMessage = { type: "chat", body: response.choices[0].text, extension: { save_to_history: 1, }, }; ``` This code imports **openai**, initializes a Configuration object. The function then calls the **openai.completions.create** function to use one of their language models to generate text based on **msg.body**. ### Step 8: Host your bot [Section titled “Step 8: Host your bot”](#step-8-host-your-bot) You can host your chat bot on any virtual server. If you do not have any in your mind - we will recommend the following: * [Render](https://render.com/docs/deploy-node-express-app) * [Digital Ocean](https://www.digitalocean.com/) * [Heroku](https://www.heroku.com/) ### What’s next [Section titled “What’s next”](#whats-next) From here you can do a 1-1 chat with your chat bot. Simply use [ConnectyCube.chat.send API](/js/messaging/#1-1-chat) from your mobile/web app to reach the bot. You also can add a bot to group chat, so the bot can listen for all conversation within group chat and react accordingly. To add a bot to group chat, use [ConnectyCube.chat.dialog.update API](/js/messaging/#addremove-occupants) and pass bot User Id in `push_all.occupants_ids` param. To send/receive messages in a group chat - refer to [Send/Receive chat messages - Group chat](/js/messaging/#group-chat) API documentation. # Medusa Plugin > Medusa Plugin to integrate Chat Widget for seller/buyer communication Medusa 2.0 commerce platform plugin to integrate Chat Widget for seller/buyer communication **With the plugin, store owners can:** * add live chat support to help customers in real-time * enable buyer-seller messaging in multi-vendor marketplaces * support group conversations or private threads * record and persist messages securely via ConnectyCube’s backend **Use cases:** * Customer Support: Enable users to ask questions and get support without leaving the site * Marketplace Messaging: Let buyers and vendors chat directly in a secure environment. ## How can I use it? [Section titled “How can I use it?”](#how-can-i-use-it) On storefront, once logged in and opened product page, there will be a Chat toggle button bottom right: ![Screenshot 2025-05-07 at 16 35 22](https://github.com/user-attachments/assets/af6acca9-6619-4d9f-b33a-ba9ccafcc03c) Once clicked, a chat with seller will be opened where you can ask any product’s related questions: ![Screenshot 2025-05-07 at 16 39 20](https://github.com/user-attachments/assets/17f613fc-0467-41f6-a333-c14d08d54f40) From Medusa dashboard there will be a new page called Chat, with the widget embedded, where all customers’ chats are displayed, so you as a merchant can reply: ![Screenshot 2025-05-07 at 16 38 13](https://github.com/user-attachments/assets/13cefe90-216b-46bb-94b3-ac754df4de74) ## Installation [Section titled “Installation”](#installation) ### Backend [Section titled “Backend”](#backend) Note If you don’t have a Medusa 2.0 application installed, follow the guide to setup one . 1. Add plugin to your Medusa 2.0 core app: ```plaintext yarn add @connectycube/chat-widget-medusa-plugin ``` 2. Create ConnectyCube account [https://connectycube.com/signup](https://connectycube.com/signup/) and application, obtain credentials ![Screenshot 2025-05-07 at 15 19 59](https://github.com/user-attachments/assets/77995af3-eb65-4559-8939-e3cc36104862) 3. Add the following variables to your `.env` file: ```plaintext VITE_BACKEND_URL=http://localhost:9000 VITE_CHAT_APP_ID= VITE_CHAT_AUTH_KEY= ``` * `VITE_BACKEND_URL` - The URL of your Medusa backend, required for custom admin components. * `VITE_CHAT_APP_ID` - This is essential for authenticating your application with the ConnectyCube platform and accessing its chat services. * `VITE_CHAT_AUTH_KEY` - This key is used to authorize your application and ensure secure communication with the ConnectyCube SDK. 4. Add the following code to your `medusa-config.ts` file: ```typescript module.exports = defineConfig({ admin: { vite: (config) => { config.define["__VITE_CHAT_APP_ID__"] = JSON.stringify(process.env.VITE_CHAT_APP_ID); config.define["__VITE_CHAT_AUTH_KEY__"] = JSON.stringify(process.env.VITE_CHAT_AUTH_KEY); return { optimizeDeps: { include: ["qs", "eventemitter3", "@xmpp/iq/callee", "@xmpp/resolve", "@xmpp/session-establishment", "@xmpp/client-core", "@xmpp/sasl-plain", "@xmpp/stream-features", "@xmpp/resource-binding", "@xmpp/reconnect", "@xmpp/middleware", "@xmpp/sasl-anonymous", "@xmpp/websocket", "@xmpp/iq/caller", "@xmpp/sasl"], // Will be merged with config that we use to run and build the dashboard. }, }; }, }, projectConfig: { ... }, plugins: [ { resolve: "@connectycube/chat-widget-medusa-plugin", options: {}, }, ], }) ``` This code connect plugin and helps to resolve an issue similar to . 5. Start the project: ```bash yarn dev ``` ### Storefront [Section titled “Storefront”](#storefront) Note If you don’t have a Medusa 2.0 storefront, follow the guide to setup one . 1. Add chat widget to your Storefront app: ```plaintext yarn add @connectycube/chat-widget ``` 2. Add the following variables to your `.env` file: ```plaintext NEXT_PUBLIC_CHAT_APP_ID= NEXT_PUBLIC_CHAT_AUTH_KEY= NEXT_PUBLIC_STORE_ID= NEXT_PUBLIC_STORE_NAME= ``` 3. Create `src/ChatWidget.tsx` component with the following content: ```typescript "use client" import React, { useEffect, useState } from "react" import ConnectyCubeChatWidget from "@connectycube/chat-widget/react19" import { StoreCustomer, StoreProduct } from "@medusajs/types" export interface ChatWidgetProps { customer: StoreCustomer | null product: StoreProduct chatPerProduct?: boolean } export default function ChatWidget({ customer, product, chatPerProduct, }: ChatWidgetProps) { const quickActions = { title: "Quick Actions", description: "Select an action from the options below or type a first message to start a conversation.", actions: [ "Hi, I'm interested in this product.", "Can you tell me more about the price and payment options?", "Is the product still available?", "Can I schedule a viewing?", ], } if (!customer) { return null } const [defaultChat, setDefaultChat] = useState(null) const [isOpen, setIsOpen] = useState(false) const onOpenCloseWidget = (isOpen: boolean) => { setIsOpen(isOpen) } const storeId = process.env.NEXT_PUBLIC_STORE_ID const storeName = process.env.NEXT_PUBLIC_STORE_NAME useEffect(() => { if (isOpen) { console.log("Widget is open:", isOpen) const defaultChatKey = chatPerProduct ? product.id : storeId const defaultChatName = chatPerProduct ? product.title : storeName setDefaultChat({ id: defaultChatKey, opponentUserId: storeId, type: "group", name: defaultChatName, }) } }, [isOpen]) return (
) } ``` 4. update `tsconfig.json`: ```typescript { "compilerOptions": { "module": "nodenext", "moduleResolution": "nodenext", ... } } ``` 5. update `storefront/src/app/[countryCode]/(main)/products/[handle]/page.tsx` to retrieve customer info and pass it to `ProductTemplate`: ```typescript const customer = await retrieveCustomer() return ( ) ``` 6. Finally, connect `ChatWidget` component on product details page, e.g. `src/modules/products/templates/index.tsx` ```typescript ``` ## Demo [Section titled “Demo”](#demo) The complete demo app (backend + storefront) available ## 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 or [file a GitHub issue](https://github.com/ConnectyCube/chat-widget-medusa-plugin/issues) # Vendure Plugin > Vendure Plugin to integrate Chat Widget for seller/buyer communication # Overview [Section titled “Overview”](#overview) Vendure commerce platform plugin to integrate Chat Widget for seller/buyer communication With the plugin, store owners can: * add live chat support to help customers in real-time * enable buyer-seller messaging in multi-vendor marketplaces * support group conversations or private threads * record and persist messages securely via ConnectyCube’s backend **Use cases:** * Customer Support: Enable users to ask questions and get support without leaving the site * Marketplace Messaging: Let buyers and vendors chat directly in a secure environment. ## How can I use it? [Section titled “How can I use it?”](#how-can-i-use-it) On storefront, once logged in and opened product page, there will be a Chat toggle button bottom right where customers can contact the merchant: ![Screenshot 2025-07-02 at 12 14 00](https://github.com/user-attachments/assets/d27d7761-0aff-4291-bc34-f27d4eedcb95) From Vendure dashboard there will be a new page called Chat, with the widget embedded, where all customers’ chats are displayed, so you as a merchant can reply: ![Screenshot 2025-07-02 at 12 13 44](https://github.com/user-attachments/assets/9dd3f4ac-b395-4052-a12e-787444a56ab1) ## Installation [Section titled “Installation”](#installation) ### Backend [Section titled “Backend”](#backend) 1. Add plugin to your Vendure app: ```plaintext yarn add @connectycube/vendure-plugin-chat-widget ``` 2. Create ConnectyCube account [https://connectycube.com/signup](https://connectycube.com/signup/) and application, obtain credentials: ![Screenshot 2025-06-04 at 10 36 59](https://github.com/user-attachments/assets/98862827-619a-4cfc-a847-2a982f562e90) Also, go to **Chat -> Custom Fields** and create a new custom field called `externalId`: ![Screenshot 2025-07-02 at 12 24 35](https://github.com/user-attachments/assets/868646d2-bdda-4634-aadd-629777cdf24e) 3. Add the following code to your `vendure-config.ts` file: ```typescript import { ChatWidgetPlugin } from '@connectycube/vendure-plugin-chat-widget'; plugins: [ ChatWidgetPlugin.init({ appId: ..., // ConnectyCube App Id authKey: "", // ConnectyCube Auth Key storeName: "", // A name of your store (any string, will be visible by buyer) storeId: "" // Some uniq identifier of your store (any uniq string) }), ]; ``` ### Storefront [Section titled “Storefront”](#storefront) 1. Add chat widget to your Storefront app. For example, let’s use [Remix storefront starter](https://github.com/vendure-ecommerce/storefront-remix-starter) as an example: ```plaintext yarn add @connectycube/chat-widget yarn connectycube patch-ssr # Apply SSR patches for Remix to work well ``` 2. Add the following variables to your `.env` file: ```plaintext CHAT_WIDGET_CONNECTYCUBE_APP_ID= CHAT_WIDGET_CONNECTYCUBE_AUTH_KEY=" CHAT_WIDGET_STORE_ID= CHAT_WIDGET_STORE_NAME= ``` 3. Create `app/components/ChatWidget.tsx` component with the following content: ```typescript import { useEffect, useState } from 'react'; import ConnectyCubeChatWidget from '@connectycube/chat-widget'; type StoreCustomer = { id: string; firstName: string; lastName: string; }; type StoreProduct = { id: string; title: string; }; export type ChatWidgetEnv = { CHAT_WIDGET_STORE_ID: string; CHAT_WIDGET_STORE_NAME: string; CHAT_WIDGET_CONNECTYCUBE_APP_ID: string; CHAT_WIDGET_CONNECTYCUBE_AUTH_KEY: string; }; export interface ChatWidgetProps { customer: StoreCustomer | null; product: StoreProduct; chatPerProduct?: boolean; env: ChatWidgetEnv; } export default function ChatWidget({ customer, product, chatPerProduct, env }: ChatWidgetProps) { const quickActions = { title: 'Quick Actions', description: 'Select an action from the options below or type a first message to start a conversation.', actions: [ "Hi, I'm interested in this product.", 'Can you tell me more about the price and payment options?', 'Is the product still available?', 'Can I schedule a viewing?', ], }; if (!customer) { return null; } const [defaultChat, setDefaultChat] = useState(null); const [isOpen, setIsOpen] = useState(false); const onOpenCloseWidget = (isOpen: boolean) => { setIsOpen(isOpen); }; const storeId = env.CHAT_WIDGET_STORE_ID; const storeName = env.CHAT_WIDGET_STORE_NAME; useEffect(() => { if (isOpen) { console.log('Widget is open:', isOpen); const defaultChatKey = chatPerProduct ? product.id : storeId; const defaultChatName = chatPerProduct ? product.title : storeName; setDefaultChat({ id: defaultChatKey, opponentUserId: storeId, type: 'group', name: defaultChatName, }); } }, [isOpen]); return (
); } ``` 4. update `remix.config.js`: ```typescript const commonConfig = { ... browserNodeBuiltinsPolyfill: { modules: { events: true } }, }; ``` 5. Finally, connect `ChatWidget` component on product details page, e.g. `app/routes/products.$slug.tsx` ```typescript import ChatWidget, { ChatWidgetEnv } from '~/components/ChatWidget'; ... export async function loader({ params, request }: DataFunctionArgs) { ... const { product } = await getProductBySlug(params.slug!, { request }); const activeCustomer = await getActiveCustomer({ request }); return json({ product: product!, activeCustomer, ENV: { CHAT_WIDGET_STORE_ID: process.env.CHAT_WIDGET_STORE_ID, CHAT_WIDGET_STORE_NAME: process.env.CHAT_WIDGET_STORE_NAME, CHAT_WIDGET_CONNECTYCUBE_APP_ID: process.env.CHAT_WIDGET_CONNECTYCUBE_APP_ID, CHAT_WIDGET_CONNECTYCUBE_AUTH_KEY: process.env.CHAT_WIDGET_CONNECTYCUBE_AUTH_KEY, }}) } export default function ProductSlug() { ... const { product, activeCustomer, ENV } = useLoaderData(); 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 or [file a GitHub issue](https://github.com/ConnectyCube/chat-widget-vendure-plugin/issues) # Code samples > Explore easy-to-follow Cordova code snippets for implementing chat, video calling, and push notifications using ConnectyCube SDK With ConnectyCube code samples - learn how to implement 1-1 and group chat messaging, video calling, enable Push Notifications, authenticate your users via phone SMS verification, store and retrieve file attachments from the cloud and many more features. These code samples are simple enough that even novice developers will be able to understand them. Cordova code samples are available at [GitHub repository](https://github.com/ConnectyCube/connectycube-cordova-samples) ## P2P Video Chat code sample [Section titled “P2P Video Chat code sample”](#p2p-video-chat-code-sample) > [Video Chat code sample for Cordova](https://github.com/ConnectyCube/connectycube-cordova-samples/tree/master/sample-videochat-cordova) `````` ## Conferencing Video Chat [Section titled “Conferencing Video Chat”](#conferencing-video-chat) > [Conferencing Video Chat code sample for Cordova](https://github.com/ConnectyCube/connectycube-cordova-samples/tree/master/sample-videochat-conf-cordova) `````` ## Chat code sample [Section titled “Chat code sample”](#chat-code-sample) Coming soon ## Do It Yourself [Section titled “Do It Yourself”](#do-it-yourself) If you want to build Cordova project code samples from scratch using existing Web code samples as a basis - follow the following link: > [DYI - Chat code sample for Cordova](https://github.com/ConnectyCube/connectycube-cordova-samples/blob/master/guides/code-samples-chat-cordova.md) > [DYI - Video Chat code sample for Cordova](https://github.com/ConnectyCube/connectycube-cordova-samples/blob/master/guides/code-samples-videochat-cordova.md) > [DYI - Conference Calling code sample for Cordova](https://github.com/ConnectyCube/connectycube-cordova-samples/blob/master/guides/code-samples-videochat-conf-cordova.md) # Code samples > Explore easy-to-follow Flutter code snippets for implementing chat, video calling, and push notifications using ConnectyCube SDK With ConnectyCube code samples - learn how to implement 1-1 and group chat messaging, video calling, enable Push Notifications, authenticate your users via phone SMS verification, store and retrieve file attachments from the cloud and many more features. These code samples are simple enough that even novice developers will be able to understand them. Flutter code samples are available at [GitHub repository](https://github.com/ConnectyCube/connectycube-flutter-samples) ## Chat code sample [Section titled “Chat code sample”](#chat-code-sample) [**Chat Sample Web App**](https://connectycube.github.io/connectycube-flutter-samples/chat_sample/build/web) > [Chat code sample for Flutter](https://github.com/ConnectyCube/connectycube-flutter-samples/tree/master/chat_sample) ```` `` ## P2P Calls code sample [Section titled “P2P Calls code sample”](#p2p-calls-code-sample) > [P2P Calls code sample for Flutter](https://github.com/ConnectyCube/connectycube-flutter-samples/tree/master/p2p_call_sample) `````` `` ## Conference Calls code sample [Section titled “Conference Calls code sample”](#conference-calls-code-sample) [**Conference calls Sample Web app**](https://connectycube.github.io/connectycube-flutter-samples/conf_call_sample/build/web) > [Conference Calls code sample for Flutter](https://github.com/ConnectyCube/connectycube-flutter-samples/tree/master/conf_call_sample) `````` `` # AI Assistants and LLMs > Explore and learn how to use AI assistants and LLMs effectively in your development apps with ConnectyCube. Here you’ll learn how you can use AI assistants and LLMs effectively in your apps development with ConnectyCube. ## AI Assistant in Documentation [Section titled “AI Assistant in Documentation”](#ai-assistant-in-documentation) Coming soon ## Plain Text Documentation [Section titled “Plain Text Documentation”](#plain-text-documentation) The ConnectyCube documentation is available in plain text format, which allows LLMs and AI tools to easily parse and understand the content. You can access the following plain text documentation files: * [llms.txt](https://developers.connectycube.com/llms.txt) - contains a short structure of links to important documentation pages. * [llms-small.txt](https://developers.connectycube.com/llms-small.txt) - a compact version of the documentation for ConnectyCube, with non-essential content removed. * [llms-full.txt](https://developers.connectycube.com/llms-full.txt) - contains the full documentation content, including all pages and sections. * **Markdown version of any page** - You can access the Markdown version of any documentation page by appending `/index.html.md` to the page URL. For example, the plain text content of the current page is available at . You can provide these files to your AI tools or LLM editors like [Cursor](https://docs.cursor.com/context/@-symbols/@-docs). This will help them understand the ConnectyCube documentation and provide better assistance when building customizations or answering questions. # External authentication via Custom Identity Provider (CIdP) > Integrate your user database with using Custom Identity Provider (CIdP). Effortlessly authenticate users externally and streamline database integration. **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. ## Login flow diagram [Section titled “Login flow diagram”](#login-flow-diagram) ![Custom Identity Provider login flow](/_astro/custom_idp_login_flow.Cm1uCcwH_Z1AxJCi.webp) Here are explanations to the diagram: 1. A user logins in your Backend and receives a token. 2. The user logins to ConnectyCube with data received from your Backend: ```plaintext POST https://api.connectycube.com/login login= password= ``` 3. ConnectyCube backend sends a request to your Backend to verify the user: ```plaintext GET https://yourserver.co/user/verify?token={token} ``` **Note**: This URL has to be configured in your ConnectyCube Dashboard (check the instructions below). 4. Get user verification confirmation from your Backend. If ConnectyCube server gets successful verification, a ConnectyCube User entity will be created (during first login) and ConnectyCube session token will be returned to access API. 5. Next step is login to Chat. Use **user\_id** and ConnectyCube session token retrieved at the previous stage instead of password to log in to Chat. ## Setup mapping [Section titled “Setup mapping”](#setup-mapping) In order to use Custom Identity Provider feature you need to configure it in your ConnectyCube Dashboard as follows: 1. Go to your **Dashboard > Your App > Overview > CIP** page and enable **Custom Identity Provider** feature: ![Open your app\'s Overview page in Dashboard](/_astro/custom_idp_enable.Bj0J-Rcr_Z1ziFhR.webp) 2. In the fields that appear **configure your API URL to verify user and parameters mapping settings** and click **Update** button: ![Configure your API URL to verify user and parameters mapping settings](/_astro/custom_idp_enable_3.B1Bt3kRj_Z2rwCGV.webp) ### Example of mapping [Section titled “Example of mapping”](#example-of-mapping) Let’s assume your API URL to verify users looks like following: ```plaintext GET https://yourserver.co/user/verify?token={token} ``` And it returns the following JSON response in case of successful verification: ```json {"user": {"id": 2345, "login": "simon371", "fill_name": "Simon Davis"}} ``` In this case you need to set the following mapping parameters in ConnectyCube Dashboard: * **API URL**: * **Request params**: ```plaintext {"token": "#{login}"} ``` * **Response params**: ```plaintext {"uid": "#{user.id}"} ``` After that you can login to ConnectyCube with the following login method: ```plaintext POST https://api.connectycube.com/login Params: login= ``` The **login** parameter will be translated into **token** parameter then. ## Starter Kit [Section titled “Starter Kit”](#starter-kit) There is a starter kit project to develop Custom identity provider service for ConnectyCube: ## Have any issues? [Section titled “Have any issues?”](#have-any-issues) Raise an issue # Security and Compliance > Explore our comprehensive security measures and privacy practices to understand how we protect your data and ensure a safe and secure experience for users. At ConnectyCube, we take the security and privacy of your data seriously. Our commitment to safeguarding your information is at the core of everything we do. Explore our comprehensive security measures and privacy practices below to understand how we protect your data and ensure a safe and secure experience for all users. ## Client side Security Measures [Section titled “Client side Security Measures”](#client-side-security-measures) * By default, the data is encrypted in transit via state of the art protocols: * **HTTPS** for server API communication * **TLS** for real time messaging * **SRTP, SCTP, DTLS** for calling * Session token - uniquely generated OAuth 2.0 session access token, per user, signed, is used to authenticate each API request. A session token provides temporary, secure access to app features. A session token is an opaque string that identifies a user and an application. It informs the API that the bearer of the token has been authorized to access the API and perform specific actions specified by the scope that has been granted. * Chat messages are stored in a plain way at the backend, hence search & navigation across chat history is possible. * Another level of encryption can be potentially added via E2EE. * Text data transfer between users is happening via secure **TLS** protocol * Voice/Video calling - a standard WebRTC encryption stack with secured media and data channels is used. Media data transfer between users is happening via secure **SRTP, SCTP, DTLS** protocols. * All the calls are E2EE by default. * Multi-factor Authentication - MFA increases security for your account. A user can enable MFA at app settings. A user will be required to enter a time-based one-time (TOTP) password generated by the authenticator app which will be used as an additional authentication layer. ## Server side Security Measures [Section titled “Server side Security Measures”](#server-side-security-measures) * **High Availability and Disaster Recovery:** * We implement redundant server architecture with failover mechanisms to ensure continuous availability of services. * Deploy geographically distributed data centers or cloud regions to mitigate the impact of localized outages or disasters. * Develop and test comprehensive disaster recovery plans and procedures to minimize downtime and facilitate rapid recovery in case of system failures or disasters. * **24/7 Uptime Monitoring:** * Utilize monitoring tools and services to continuously monitor server performance, availability, and uptime. * Set up automated alerts and notifications to promptly identify and respond to potential issues or disruptions. * Implement proactive monitoring and incident response procedures to maintain 24/7 uptime and minimize service disruptions. * **GDPR Compliance:** * Ensure compliance with the General Data Protection Regulation (GDPR) by implementing data protection measures and privacy controls. * Obtain explicit consent from users before collecting, processing, or storing their personal data. * Implement data encryption, pseudonymization, and anonymization techniques to protect the privacy and confidentiality of user information. * **HIPAA Compliance:** * Comply with the Health Insurance Portability and Accountability Act (HIPAA) regulations when handling protected health information (PHI). * Implement stringent security controls and safeguards to protect PHI from unauthorized access, disclosure, and misuse. * Conduct regular risk assessments and audits to ensure HIPAA compliance and mitigate potential security risks and vulnerabilities. * **Data Encryption in Rest and Transit:** * Encrypt sensitive data at rest using strong encryption algorithms and cryptographic techniques to protect it from unauthorized access. * Use secure communication protocols, such as TLS/SSL, to encrypt data transmitted between clients and servers, ensuring confidentiality and integrity during transit. * **On-Premise Considerations:** * Maintain full control and visibility over server infrastructure by hosting servers on-premises within your own data center or private cloud environment. * Implement physical security measures, such as access controls, surveillance cameras, and environmental controls, to protect server hardware and facilities from unauthorized access, theft, and environmental hazards. ## Privacy Practices [Section titled “Privacy Practices”](#privacy-practices) The following privacy practices are applied at ConnectyCube: * **Data Minimization**: We collect and process only the data necessary to provide our services and fulfill our contractual obligations. We adhere to the principle of data minimization to limit the collection, storage, and use of personal information to the extent required for legitimate business purposes. * **User Consent**: We obtain explicit consent from users before collecting, processing, or sharing their personal information. We provide clear and transparent information about our data practices, including the purposes of data processing, the types of data collected, and the rights of users regarding their data. * **Data Anonymization**: Where feasible, we anonymize or pseudonymize personal data to protect user privacy. By removing or encrypting personally identifiable information (PII), we reduce the risk of unauthorized disclosure and enhance the privacy of user data. * **Data Retention Policies**: We establish data retention policies and procedures to govern the storage and deletion of user data. We retain personal information only for as long as necessary to fulfill the purposes for which it was collected or as required by law, and we securely dispose of data when it is no longer needed. * **GDPR Compliance**: We comply with the General Data Protection Regulation (GDPR) and other applicable data protection laws and regulations. We respect the privacy rights of individuals and provide mechanisms for users to exercise their rights, including the right to access, rectify, and delete their personal data. > [Request additional consultancy regarding Security and Compliance in ConnectyCube](https://connectycube.com/contact/) ## More info [Section titled “More info”](#more-info) * [ConnectyCube Shares its Ways to GDPR Compliance](https://connectycube.com/2018/07/10/connectycube-shares-its-ways-to-gdpr-compliance/) * [Secure chat: implementing end-to-end encryption in Flutter chat apps](https://connectycube.com/2023/08/08/secure-chat-implementing-end-to-end-encryption-in-flutter-chat-apps/) * [Secure chat: implementing end-to-end encryption in Web and React Native chat apps](https://connectycube.com/2023/03/29/1584/) * [Introducing Advanced Features: Encrypted Address Book](https://connectycube.com/2024/01/12/introducing-advanced-features-encrypted-address-book/) # Sign the app for release to Google Play > Learn how to sign your Android App Bundle (AAB) for release on the Google Play Store. Includes step-by-step instructions with screenshots for easy guidance. Before publishing your app on the Google Play Store, it needs to be signed with a unique cryptographic key. Think of this as a “digital signature” that proves the app’s authenticity and guarantees that the app hasn’t been tampered with. The signing configuration links your app to this key, ensuring that future updates can be trusted and securely delivered to your users. This guide walks you through the process step by step. It consists of 2 steps only: * generate signing key. * sign your app with the generated key. ## Before we start: why we use AAB instead of APK [Section titled “Before we start: why we use AAB instead of APK”](#before-we-start-why-we-use-aab-instead-of-apk) Before diving into the steps, it’s important to understand why we work with **Android App Bundles (AAB)** instead of the traditional **APK** format. Starting August 2021, Google Play started requiring new apps to be published with the Android App Bundle. An **Android App Bundle** is a publishing format that includes all your app’s compiled code and resources, and defers APK generation and signing to Google Play. AABs are a better way of organizing app files and resources. Furthermore, developers would still need an APK build to test an Android app before it goes live. As the guide describes signing the app to be published on Google Play, AAB file type is chosen in the steps. ## Step 1: Generate an upload key and keystore [Section titled “Step 1: Generate an upload key and keystore”](#step-1-generate-an-upload-key-and-keystore) To generate an upload key, go to Android Studio and follow the steps below: 1. **Menu** bar -> **Build** section-> choose **Generate Signed App Bundle / APK**: ![Context menu](/_astro/generate_signed_app_bundle.BimMfXYM_Z1xpita.webp) 2. In the dialog that appears, select **Android App Bundle (AAB)** and click **Next**: ![Generate Signed APP Bundle or APK window](/_astro/select_android_app_bundle.BrotaiN-_Z1PR4XL.webp) 3. Below the field for **Key store path**, click **Create new**: ![Generate Signed APP Bundle or APK window](/_astro/create_new_key_store_path.DRuDS4Up_2fJzfO.webp) After this step the **New Key Store** window is opened: ![New Key Store window](/_astro/new_key_store.AgMsmRbb_Z2fqgh9.webp) 4. Fill out the required details in the **New Key Store** window: 1. **Key store**: 1. **Key store path**: choose a location for the keystore file and add a file name with the *.jks* extension. 2. **Password**: create and confirm a secure password for your keystore. 2. **Key**: 1. **Alias\***: enter a unique name to identify your key. 2. **Password\***: set and confirm a password for your key (ideally the same as your keystore password). 3. **Validity (years)\***: specify how long the key will remain valid. The key should be valid for at least **25 years** to ensure app updates can be signed throughout its lifespan. 4. **Certificate Information** (optional): provide your details (e.g., name, organization). This information is included in your certificate but won’t appear in your app. *// \* - required fields* ![Filled New Key Store window](/_astro/filled_new_key_store_window.CRk3EGcL_Z1wBGfT.webp) 5. After completing the form, click **OK**. On this stage the **‘Generate Signed App Bundle or APK’** window is opened. It means, you’ve now created an upload key and are ready to use it to sign your application. Leave the **Generate Signed Bundle** dialog opened, you will start to sign the application from this dialog in the **Step 2** below. ## Step 2: Signing app with the generated key [Section titled “Step 2: Signing app with the generated key”](#step-2-signing-app-with-the-generated-key) Follow these steps to sign your app: 1. Select a **module** from the drop down. > if you have the only module available for your project, it will be chosen by default and disabled in the field. 2. **Key store details** are filled automatically with the data from the previous step: **path**, **alias** and the **passwords** for both the key store and the key. 3. Click **Next** to open next dialog under **‘Generate Signed App Bundle or APK’** window with a build options: ![Generate Signed APP Bundle or APK window](/_astro/generate_signed_app_bundle_or_apk_window.diAlT-si_ZOXUx4.webp) 4. **Choose build options**: 1. Select a **destination folder** for the signed app. 2. Choose the **release build type** (‘debug’ is also available to be selected). 5. Click **Create**. ![Build selection window](/_astro/build_selection_window.HhMv0jXp_Z2jgfyR.webp) Once the build is complete, a pop-up notification will appear. Use the options in the notification to either **locate** or **analyze** your app: * **locate** -> this option opens the folder where your signed app file is saved. It allows you to quickly find the file on your computer for uploading to a distribution platform or sharing it. * **analyze** -> shows the bundle stats. ![Popup of successful bundle generation](/_astro/popup_of_successful_bundle_generation.Brnp_OX2_Z1Oxr6a.webp) So, now the app is ready to be uploaded to Google Play Store, this is probably one of the major steps to complete before going to the Developer’s console and proceed with the file uploading together with the app submission. Continue with the [How to upload app to the Google Play Store: Step-by-step guide](/guides/uploading-android-app-to-store) to go through the next steps of app submission and avoid any excess information of the process and to be concentrated only of the important things to do. ## Conclusion [Section titled “Conclusion”](#conclusion) Now that you’ve learned how to sign your Android app, why not take your app development to the next level? Simplify your app development process by using **ConnectyCube** - a platform that provides a ready-to-use backend for chat, video calls, push notifications, and more. With a **free ConnectyCube account**, you can kickstart your app journey faster and focus on delivering features to your users. Take the first step towards app development - [join ConnectyCube now and get started for free!](https://connectycube.com/signup/) Let ConnectyCube handle the backend for you! # How to upload app to the Google Play Store: Step-by-step guide > Learn how to upload app to the Google Play Store. Follow easy instructions to navigate the Play Console, set up your app, manage releases, and publish app. Releasing an app on the Google Play Store is essential for Android developers looking to connect with millions of users globally. The process includes several important stages, from getting your app ready and creating a developer account to submitting the app for approval. In this guide, we’ll guide you through each step to help ensure your app gets published smoothly. The process of submitting application to the Google Play Store itself isn’t difficult and consists of **5 main steps**: **Step 1:** Sign the release version of your application **Step 2:** Create an application in Google Developers account **Step 3:** Provide information about the app and set up app listing **Step 4:** Upload AAB file **Step 5:** Review and rollout Each step doesn’t take much time except for work on the application you will create the AAB file for. ## Step 1: Sign the release version of your application [Section titled “Step 1: Sign the release version of your application”](#step-1-sign-the-release-version-of-your-application) All Android apps must be digitally signed with a certificate before they can be installed. To distribute your Android app through the Google Play Store, it must be signed with a **release key**, which should be used for all future updates. Additionally, before uploading your AAB to Google Play, it needs to be signed with an **upload key**.\ When publishing your app to Google Play for the first time, you must also configure **Play App Signing.** To configure signing for an app that has not yet been published to Google Play, proceed with **generating an upload key** and **signing your app with that upload key**. The [Signing Your Applications](/guides/sign-app-for-release-to-google-store) page describes the topic in detail. ## Step 2: Create an application in Google Developers account [Section titled “Step 2: Create an application in Google Developers account”](#step-2-create-an-application-in-google-developers-account) Creating the application in Google Play is the first major step that initiates the publishing procedure itself. It means, the app will be existing in Google Play as a record and will be waiting for the AAB file to be uploaded. Better to say, it is a “shell” of your application that will be fulfilled with the build and all necessary information later. For creating an application, do the following: * log in to the [Google Play Console](https://play.google.com/console/). If you are new here - [sign up for a Play console developer account](https://play.google.com/apps/publish/signup), the one-time fee for the registration is $25 * On the **Home** page click **“Create app”** ![Click create app](/_astro/click_create_app.DmoH9ecv_1byYO.webp) * Enter the app name, select the default language, choose whether it’s an app or game, if the app is free or paid. * Make sure that your app complies with Google’s Developer Program policies and adheres to US export laws. Once you’ve reviewed and agreed to the terms and conditions, click **“Create App”** button ![Submit app creation](/_astro/submit_app_creation.vyhS55NL_Z21rtn8.webp) By clicking on the **“Create app”** button, the application is now created and the application dashboard is opened for the newly created application. The Dashboard shows you what you need to do to get your app up and running. This includes recommendations on how to manage, test, and promote your app. ## Step 3: Add information about the application [Section titled “Step 3: Add information about the application”](#step-3-add-information-about-the-application) On the Dashboard expand the “Provide information about your app and set up your store listing” section with the points to fill to complete the application information. This is the section that blocks adding the info in other sections when empty. First, you need to provide the information for the app and only after - proceed with other options available.\ Follow all the questionnaires to provide the full information about the application content, all points are required. ![Providing app information](/_astro/providing_app_info.BTCpDa93_Z1sBcg7.webp) When completed all the questions, your listing should look like this: ![Providing app info completeness](/_astro/providing_app_info_completeness.DOOJxhlP_Zj4yAt.webp) Next - Manage how your app is organised and presented: * **Prepare store listing** * choose the appropriate category and add tags. * provide an email address, website, and phone number for users to contact you. * title and description, * upload screenshots, a high-res icon and a feature graphic. * **Set up app content** * content rating, * privacy policy and * target audience * **Set up pricing and distribution** When all the fields are completed correctly, app becomes available on the top of the page. ![Dashboard](/_astro/dashboard.BFceE9nR_ZfC57D.webp) ## Step 4: Upload build - AAB [Section titled “Step 4: Upload build - AAB”](#step-4-upload-build---aab) Section to upload the build can be opened from the left side menu by selecting “Production” section OR from the Dashboard -> Create and publish a release ![Uploading AAB build](/_astro/uploading_aab_build.DUDPQgjG_ZaVfre.webp) Here you need to: 1. Click on the **“Create a new release”** button.\ If this is your first release for this app, follow the instructions to [configure Play App Signing](https://support.google.com/googleplay/android-developer/answer/9842756). 2. Upload your AAB file.\ **Note:** since August 2021 APKs have already been phased out for the most part and it is only able to submit new apps to Google Play as AABs. 3. Provide a **release name** and add **release notes** for users. 4. To save any changes you make to your release, select **Save as draft**. 5. When you’ve finished preparing your release, select **Next**. ![Create production release](/_astro/create_production_release.BPi4_p4b_1Lz7EU.webp) ## Step 5: Review and rollout [Section titled “Step 5: Review and rollout”](#step-5-review-and-rollout) Verify that all the information is correct, then click “**Review**” to check the app for issues and warnings. 1. Open Play Console and go to the [Releases Overview](https://play.google.com/console/developers/app/releases/overview) page. 2. Open the **Release details** for the release to roll out 3. In the “**Release overview**” section, select **View release dashboard**. 4. Select the **Releases** tab, then **Edit**. 5. Review your draft release, make any necessary additional changes, and select **Next**.\ \- You’ll be taken to the “**Preview and confirm**” screen, where you can make sure there aren’t any issues with your release before rolling it out to users.\ \- If you see the heading “**Errors summary**” at the top of the page, click **Show more** to review the details and resolve any problems. **Note:** You can’t publish your app until errors have been resolved. If you only have warnings, minor issues, or a combination of the two, then you can still publish your app 6. Select **Start rollout**.\ If you’re rolling out your app’s first release on production, clicking **Start rollout to production** will also publish your app to all Google Play users in the countries you selected. Once you’ve created a release, you’ll see the following information for the latest app release you rolled out to each track in a table under “**Latest releases**” on your **Releases overview** page. ![Releases overview](/_astro/releases_overview.DvI4SChp_ZxBznX.webp) After submitting, Google will review your app, which may take a few hours to a few days. You will be notified via email once your app is live on the Play Store. ## Conclusion [Section titled “Conclusion”](#conclusion) Uploading and submitting an app to the Google Play Store may seem complex, but by following these step-by-step instructions, you can navigate the process with confidence. From setting up a Google Developer account to preparing your app for release and configuring your store listing, each step plays a vital role in ensuring your app reaches a global audience. Remember to thoroughly test your app, comply with all of Google’s guidelines, and provide accurate and engaging information in your store listing. Once your app is published, monitor its performance through the Google Play Console and continue to update and improve it based on user feedback. With careful preparation and attention to detail, you can successfully launch your app and start building a strong presence on the Google Play Store. # How to submit an iOS app to the App Store: Step-by-step guide > Step-by-step guide to submit your iOS app to the App Store, covering app preparation, App Store Connect setup, Xcode builds, and the review process. Uploading the iOS application to AppStore might seem challenging for the first time, especially for people who never did that before. With a step-by-step guide and detailed instruction this process can be quite easy and clear to successfully release the first application. Before you start, make sure you have the following things ready for the further work: 1. Apple Developer Program Membership is active * If you are new, visit [](https://developer.apple.com/programs/enroll/) to enroll in the Apple developer program.\ **Price:** The Apple Developer Program is $99 USD per membership year 2. iOS certificates for publishing to the App Store are created: * **Bundle ID** - The Bundle ID is used to identify the app. * **Provisioning profile** - The provisioning profile saves the configuration of the apps for different devices and must be used together with the distribution certificate and bundle ID to sign the app. * **iOS Distribution Certificate** - Apple requires the app to be signed with a valid iOS distribution certificate. You can sign all your apps with a single certificate. * **Push Notification Certificate** - To configure the Push notifications, you will use the tool One Signal. This requests a certificate from Apple to identify the app to send the notifications to. This certificate will be created on the Apple Developer Program. The process of submitting application to the Apple App Store consists of **5 main steps**: **Step 1:** Create an App Store Connect record for the application\ **Step 2:** Configure application using Xcode\ **Step 3:** Archive and upload application to App Store Connect\ **Step 4:** Configure application’s metadata and further details in its App Store Connect record\ **Step 5:** Submit application for review ## Step 1: create an App Store Connect record for the application [Section titled “Step 1: create an App Store Connect record for the application”](#step-1-create-an-app-store-connect-record-for-the-application) Creating the record in App store Connect is the first major step that initiates the publishing procedure itself. It means, the app will be existing in the App store as a record and will be waiting for the build from the Xcode to be uploaded. Better to say, it is a “shell” of your application that will be fulfilled with the build and all necessary information later. To create a record for the application in the App Store Connect: 1. **Log in** to App Store connect:\ go to App Store Connect and log in with your Apple ID. Available sections become available right after login: ![App Store connect icons](/_astro/app_store_connect_icons._jDvXdhL_F1TY3.webp) 2. Create a **New App**: * Click on **“Apps”** * Click on the **”+”** button and select **“New App”**. ![Apps menu](/_astro/apps_menu.4YZcWt5j_Z1kjRHc.webp) * Fill in the required information. All fields are mandatory, you can’t change these details later, so be sure of what you enter: * **platform** - the platform where your application operates; * **app name** - the name of the application that will be displayed on the App Store; * **primary language** - the language of the application; * **bundle ID** - the unique identifier for your app on the App Store. This should match the one created when setting up iOS certificates and provisioning profiles. * **SKU** - the unique identifier for your app that isn’t visible to users. It should match the Bundle ID and can include letters, numbers, hyphens, periods, and underscores, starting with a letter or number * **user access** - set it if applicable. ![New App settings](/_astro/new_app_settings.CZIGJbGq_UjYCc.webp) **‘Create’** button becomes active when all the fields on the form are filled. Once the new application name and information from the forms are verified, the application is created and the window to input the distribution information for the created application is shown. ![Distribution information](/_astro/distribution_info.DVrVrjAf_ZWmThU.webp) **Note:** adding required information to submit application to App store is covered below in ‘Configure application’s information in App Store Connect’ section ## Step 2: Configure application in Xcode [Section titled “Step 2: Configure application in Xcode”](#step-2-configure-application-in-xcode) When the record for the application is ready in the App Store Connect, the build needs to be uploaded from the Xcode to the created record, so the application will be considered as an application ready to be submitted. For that, launch the Xcode and do the following: * In Xcode, select your project in the **Project Navigator**. * Select the **target** and then go to the **General** tab. * Update the **version** and **build numbers**. * Set the **deployment target**: ensure that the deployment target is set to the appropriate iOS version. ![Version and build number setting](/_astro/version-build_number_setting.CtyjS88n_1ImSSY.webp) Once the Version and Build Number are set, proceed with code signing: * Go to the **Signing & Capabilities** tab. * Ensure that **“Automatically manage signing”** is checked. * Select your **Apple Developer account**. ![Automatically manage signing.png](/_astro/automatically_manage_signing.DDeb9JN-_6qC2Y.webp) ## Step 3: Archive and upload application using Xcode [Section titled “Step 3: Archive and upload application using Xcode”](#step-3-archive-and-upload-application-using-xcode) In Xcode, an archive of your app is a packaged version that includes everything needed to distribute the app, such as the compiled code, resources, and debugging information. It’s more than just a simple build, it’s a complete snapshot of your app in a state that’s ready for distribution. You need an archive for submitting your app to the App Store because it ensures that your app is in the correct format, with all the necessary metadata and resources bundled together. The archive also allows Xcode to repackage your app based on your distribution settings, whether that’s for the App Store, TestFlight, or Ad Hoc distribution. This process ensures that your app meets all the necessary requirements and is properly signed before being submitted to the App Store for review and distribution. 1. Archive your app: * In Xcode, select the target and go to **Product** > **Archive** * After the archive is created, the **Organizer window** will open. ![Archive product](/_astro/archive_product._aPYdPCQ_Z1A8MJz.webp) 2. Validate and upload your app\ Before uploading, it’s essential to validate the archive to catch and resolve any potential issues. Once validated, you can upload your app to App Store Connect. * Select the archive in the **Organizer window** in Xcode * Click on the **“Distribute App”** button * Select **“App Store Connect”** as the distribution method * Choose **“Upload”** Follow the prompts to validate the archive and resolve any errors before proceeding and upload your application After this step the build is uploaded and appears under the **‘Builds’** section. It will be available to be selected during the submitting the app for review in Step 5. ## Step 4: configure application’s information in App Store Connect [Section titled “Step 4: configure application’s information in App Store Connect”](#step-4-configure-applications-information-in-app-store-connect) The final step before the final submit of application for review is to add all necessary information about the application in the appropriate sections in App Store Connect. The information required for submitting the application becomes available when clicking on the created application (at this stage the application has ‘Prepare for submission’ status): * **Enter app information** - enter the app’s information, including the description, keywords, and app review information. ![Enter app information](/_astro/enter_app_info.CoMXYe6K_1q7RBl.webp) * **Upload screenshots** - upload the screenshots for all required device sizes. This information is used for all platforms of the application. Any changes will be released with all next application versions. ![Upload screenshots](/_astro/upload_screenshots.Bg0aRFq9_Z1ljl89.webp) After publishing an app to the App Store it appears on the application’s product page, when users install the application, and will be used for web engine search results once the application is released. ## Step 5: Submit application for review [Section titled “Step 5: Submit application for review”](#step-5-submit-application-for-review) When you are sure all required fields are filled out and all necessary documents are uploaded, you are ready for submission. * Go to the **“iOS app”** section and click on your app that is in ‘Ready for submission’ status now * Scroll page to **‘Builds’** section -> pick the build you want to push for review * Scroll page down to **‘AppStore version release’** section -> select the appropriate submission options (e.g., Manual release or Automatic release after approval). ![Release submission](/_astro/release_submission.Bn3Hob0v_Z2uyA6K.webp) * Click the **“Add for Review”** button. ![Release submission](/_astro/add_for_review.BkA7gOYO_9Yjno.webp) * Confirm submitting Congrats! Now the app is in the **‘Wait for Approval’** status. Apple’s review process can take a few days to a week. You may receive feedback requiring changes. If Apple provides feedback, address the issues and resubmit the app. If you choose **manual release**, go back to App Store Connect once your app is approved and release it. If you choose **automatic release**, the app will be released as soon as it’s approved. ## Conclusion [Section titled “Conclusion”](#conclusion) Thorough preparation can ensure a smooth submission. Once your app is live, you’ll be ready to reach a global audience and provide them with a high-quality iOS experience. Remember, the journey doesn’t end here—continue to monitor user feedback and update your app to keep it at its best. # Code samples > Explore easy-to-follow iOS code snippets for implementing chat, video calling, and push notifications using ConnectyCube SDK With ConnectyCube code samples - learn how to implement 1-1 and group chat messaging, video calling, enable Push Notifications, authenticate your users via phone SMS verification, store and retrieve file attachments from the cloud and many more features. These code samples are simple enough that even novice developers will be able to understand them. iOS code samples are available at [GitHub repository](https://github.com/ConnectyCube/connectycube-ios-samples) ## Chat code sample [Section titled “Chat code sample”](#chat-code-sample) > [Download source code](https://github.com/ConnectyCube/connectycube-ios-samples/tree/master/SampleChat) `````` ## Video Chat code sample [Section titled “Video Chat code sample”](#video-chat-code-sample) > [Download source code](https://github.com/ConnectyCube/connectycube-ios-samples/tree/master/SampleVideoChat) `````` ## Multiparty Video Conferencing code sample [Section titled “Multiparty Video Conferencing code sample”](#multiparty-video-conferencing-code-sample) Coming soon. # Code samples > Explore easy-to-follow Web code snippets for implementing chat, video calling, and push notifications using ConnectyCube SDK With ConnectyCube code samples - learn how to implement 1-1 and group chat messaging, video calling, enable Push Notifications, authenticate your users via phone SMS verification, store and retrieve file attachments from the cloud and many more features. These code samples are simple enough that even novice developers will be able to understand them. ## Chat code sample [Section titled “Chat code sample”](#chat-code-sample) > [Chat sample for Browser (React)](https://github.com/ConnectyCube/connectycube-web-samples/tree/master/chat-react) ![Chat code sample demo React](/images/code_samples/javascript/js_codesample_chat_react1.png) > [Chat sample for Browser (Angular)](https://github.com/ConnectyCube/connectycube-web-samples/tree/master/chat-angular) ![Chat code sample demo Angular](/images/code_samples/javascript/js_codesample_chat_angular1.png) > [Chat code sample for React Native](https://github.com/ConnectyCube/connectycube-reactnative-samples/tree/master/RNChat) `````` > [Chat code sample for Cordova/PhoneGap - How To guide](/cordova/code-samples-chat-cordova) > [Chat code sample for NativeScript](https://github.com/ConnectyCube/connectycube-nativescript-samples/tree/master/sample-chat-nativescript-core) ```` ## P2P Calling code sample [Section titled “P2P Calling code sample”](#p2p-calling-code-sample) > [P2P Calling code sample for Web](https://github.com/ConnectyCube/connectycube-web-samples/tree/master/videochat) ![Video Chat code sample demo](/images/code_samples/javascript/js_codesample_videochat_active_call.png) > [P2P Calling code sample for Cordova/PhoneGap - How To guide](/cordova/code-samples-videochat-cordova) > [P2P Calling code sample for React Native](https://github.com/ConnectyCube/connectycube-reactnative-samples/tree/master/RNVideoChat) `````` * Video Chat code sample for NativeScript - coming soon ## Conferencing Calling code sample [Section titled “Conferencing Calling code sample”](#conferencing-calling-code-sample) > [Conference Calling code sample for Web (React)](https://github.com/ConnectyCube/connectycube-web-samples/tree/master/videochat-conf-react) ![Conference Calling code sample for Web (React)](/images/code_samples/javascript/js_codesample_conference_universal.png) > [Conference Calling code sample for Web (Angular)](https://github.com/ConnectyCube/connectycube-web-samples/tree/master/videochat-conf-angular) ![Conference Calling code sample for Web (Angular)](/images/code_samples/javascript/js_codesample_conference_universal.png) > [Conference Calling code sample for Web (vanilla JS)](https://github.com/ConnectyCube/connectycube-web-samples/tree/master/videochat-conf) ![Conference Calling code sample for Web](/images/code_samples/javascript/js_codesample_videochat_active_call.png) > [Conference Calling code sample for Cordova/PhoneGap - How To guide](/cordova/code-samples-videochat-conf-cordova) # Address Book > Effortlessly upload, sync, and access ConnectyCube users from your phone contacts in your NativeScript 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](/nativescript/authentication-and-users#authentication-via-phone-number) you can easily organise a state of the art logic in your App where you can easily start chatting with your phone contacts, without adding them manually as friends - the same what you can see in WhatsApp, Telegram, Facebook Messenger and Viber. ## Upload Address Book [Section titled “Upload Address Book”](#upload-address-book) First of all you need to upload your Address Book to the backend. It’s a normal practice to do a full upload for the 1st time and then upload diffs on future app logins. ```javascript const CONTACTS = [ { name: "Gordie Kann", phone: "1879108395", }, { name: "Wildon Gilleon", phone: "2759108396", }, { name: "Gaston Center", phone: "3759108396", }, ]; const options = {}; // const options = {'force': 1, 'udid': 'XXX'}; ConnectyCube.addressbook .uploadAddressBook(CONTACTS, options) .then(() => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.addressbook.uploadAddressBook(params)` - [see](/server/address_book#response) * You also can edit an existing contact by providing a new name for it. * You also can upload more contacts, not just all in one request - they will be added to your address book on the backend. If you want to override the whole address book on the backend - just provide `force: 1` option. * You also can remove a contact by setting `contact.destroy = 1;` * A device UDID is used in cases where user has 2 or more devices and contacts sync is off. Otherwise - user has a single global address book. ## Retrieve Address Book [Section titled “Retrieve Address Book”](#retrieve-address-book) If you want you can retrieve your uploaded address book: ```javascript ConnectyCube.addressbook .get() .then((result) => {}) .catch((error) => {}); ``` or with UDID: ```javascript const UDID = "XXX"; ConnectyCube.addressbook .get(UDID) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.addressbook.get()` - [see](/server/address_book#response-1) ## Retrieve Registered Users [Section titled “Retrieve Registered Users”](#retrieve-registered-users) Using this request you can easily retrieve the ConnectyCube users - you phone Address Book contacts that already registered in your app, so you can start communicate with these users right away: ```javascript ConnectyCube.addressbook .getRegisteredUsers() .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.addressbook.getRegisteredUsers()` - [see](/server/address_book#response-2) If isCompact = true - server will return only id and phone fields of User. Otherwise - all User’s fields will be returned. ## Push notification on new contact joined [Section titled “Push notification on new contact joined”](#push-notification-on-new-contact-joined) There is a way to get a push notification when some contact from your Address Book registered in an app. You can enable this feature at [ConnectyCube Dashboard](https://admin.connectycube.com), Users module, Settings tab: ![Setup push notification on new contact joined](/_astro/setup_push_notification_on_new_contact_joined.DTG1vj8m_1gVrvB.webp) # Authentication and Users > Simplify user authentication in your NativeScript 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](/nativescript/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](/nativescript/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 = "photo"; ``` ## 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 NativeScript 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. ![Create class scheme](/_astro/create_scheme.BZpQA4pv_Z1tQkMW.webp ":size=400") 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 ![Create class scheme](/_astro/created_scheme.DuEkinE2_2l5wmH.webp) 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. ![Edit permissions levels](/_astro/edit_class_permissions.DwZG2uer_2451o4.webp ":size=400") ### 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: ![Set permissions for record](/_astro/edit_record_permissions.YjI8FGha_Zb7hOq.webp ":size=400") 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. # Code samples > Explore easy-to-follow NativeScript code snippets for implementing chat, video calling, and push notifications using ConnectyCube SDK With ConnectyCube code samples - learn how to implement 1-1 and group chat messaging, video calling, enable Push Notifications, authenticate your users via phone SMS verification, store and retrieve file attachments from the cloud and many more features. These code samples are simple enough that even novice developers will be able to understand them. NativeScript code samples are available at [GitHub repository](https://github.com/ConnectyCube/connectycube-nativescript-samples) ## Chat code sample [Section titled “Chat code sample”](#chat-code-sample) > [Chat code sample for NativeScript](https://github.com/ConnectyCube/connectycube-nativescript-samples/tree/master/sample-chat-nativescript-core) `````` ## Video Chat code sample [Section titled “Video Chat code sample”](#video-chat-code-sample) Coming soon # Send first chat message > Step-by-step guide to sending your first chat message using ConnectyCube NativeScript 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](/nativescript/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 nativescript-connectycube --save ``` * yarn ```bash yarn add nativescript-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 'nativescript-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: "marvin18", password: "supersecurepwd" }; // const userCredentials = { email: 'cubeuser@gmail.com', password: 'awesomepwd' }; // 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 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 SDK documentation](/nativescript/messaging) to enrich your app and engage your users even further! # Chat > Integrate powerful chat functionality into your NativeScript app effortlessly with our versatile Chat APIs. Enhance user communication and engagement. ConnectyCube Chat API is built on top of Real-time(XMPP) protocol. In order to use it you need to setup real-time connection with ConnectyCube Chat server and use it to exchange data. By default Real-time Chat works over secure TLS connection. ## Connect to chat [Section titled “Connect to chat”](#connect-to-chat) ```javascript const userCredentials = { userId: 4448514, password: "awesomepwd", }; ConnectyCube.chat .connect(userCredentials) .then(() => { // connected }) .catch((error) => {}); ``` ### Connect to chat using custom authentication providers [Section titled “Connect to chat using custom authentication providers”](#connect-to-chat-using-custom-authentication-providers) In some cases we don’t have a user’s password, for example when login via: * Facebook * Twitter * Firebase phone authorization * Custom identity authentication * etc. In such cases ConnectyCube API provides possibility to use ConnectyCube session token as a password for chat connection: ```java // get current ConnectyCube session token and set as user's password const token = ConnectyCube.service.sdkInstance.session.token; const userCredentials = { userId: 4448514, password: token }; ``` ## Connection status [Section titled “Connection status”](#connection-status) The following snippet can be used to determine whether a user is connected to chat or not: ```javascript const isConnected = ConnectyCube.chat.isConnected; ``` ## Disconnect [Section titled “Disconnect”](#disconnect) ```javascript ConnectyCube.chat.disconnect(); ConnectyCube.chat.onDisconnectedListener = onDisconnectedListener; function onDisconnectedListener() {} ``` ## Reconnection [Section titled “Reconnection”](#reconnection) The SDK reconnects automatically when connection to Chat server is lost. The following 2 callbacks are used to track the state of connection: ```javascript ConnectyCube.chat.onDisconnectedListener = onDisconnectedListener; ConnectyCube.chat.onReconnectListener = onReconnectListener; function onDisconnectedListener() {} function onReconnectListener() {} ``` ## Dialogs [Section titled “Dialogs”](#dialogs) All chats between users are organized in dialogs. The are 4 types of dialogs: * 1-1 chat - a dialog between 2 users. * group chat - a dialog between specified list of users. * public group chat - an open dialog. Any user from your app can chat there. * broadcast - chat where a message is sent to all users within application at once. All the users from the application are able to join this group. Broadcast dialogs can be created only via Admin panel. You need to create a new dialog and then use it to chat with other users. You also can obtain a list of your existing dialogs. ## Create new dialog [Section titled “Create new dialog”](#create-new-dialog) ### Create 1-1 chat [Section titled “Create 1-1 chat”](#create-1-1-chat) You need to pass `type: 3` (1-1 chat) and an id of an opponent you want to create a chat with: ```javascript const params = { type: 3, occupants_ids: [56], }; ConnectyCube.chat.dialog .create(params) .then((dialog) => {}) .catch((error) => {}); ``` ### Create group chat [Section titled “Create group chat”](#create-group-chat) You need to pass `type: 2` and ids of opponents you want to create a chat with: ```javascript const params = { type: 2, name: "Friday party", occupants_ids: [29085, 29086, 29087], description: "lets dance the night away", photo: "party.jpg", }; ConnectyCube.chat.dialog .create(params) .then((dialog) => {}) .catch((error) => {}); ``` ### Create public group chat [Section titled “Create public group chat”](#create-public-group-chat) It’s possible to create a public group chat, so any user from you application can join it. There is no a list with occupants, this chat is just open for everybody. You need to pass `type: 4` and ids of opponents you want to create a chat with: ```javascript const params = { type: 4, name: "Blockchain trends", }; ConnectyCube.chat.dialog .create(params) .then((dialog) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.create(params)` - [see](/server/chat#response-1) ### Chat metadata [Section titled “Chat metadata”](#chat-metadata) A dialog can have up to 3 custom sub-fields to store additional information that can be linked to chat. To start using extensions, allowed fields should be added first. Go to [Admin panel](https://admin.connectycube.com) > Chat > Custom Fields and provide allowed custom fields. ![Dialog Extensions fields configuration example](/_astro/dialog_custom_params.CrGT0s8Z_1XWSCw.webp) When create a dialog, the `extensions` field object must contain allowed fields only. Others fields will be ignored. The values will be casted to string. ```javascript const params = { type: 2, name: "Friday party", occupants_ids: [29085, 29086, 29087], description: "lets dance the night away", extensions: {location: "Sun bar"}, }; ConnectyCube.chat.dialog .create(params) .then((dialog) => {}) .catch((error) => {}); ``` When remove custom field in Admin panel, this field will be removed in all dialogs respectively. These parameters also can be used as a filter for retrieving dialogs. ### Chat permissions [Section titled “Chat permissions”](#chat-permissions) Chat could have different permissions to managa data access. This is managed via `permissions` field. At the moment, only one permission available - `allow_preview` - which allows to retrieve dialog’s messages for user who is not a member of dialog. This is useful when implement feature like Channels where a user can open chat and preview messages w/o joining it. ## List dialogs [Section titled “List dialogs”](#list-dialogs) It’s common to request all your dialogs on every app login: ```javascript const filters = {}; ConnectyCube.chat.dialog .list(filters) .then((result) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.list(filters)` - [see](/server/chat#response) More filters available [here](/server/chat#retrieve-chat-dialogs) If you want to retrieve only dialogs updated after some specific date time, you can use `updated_at[gt]` filter. This is useful if you cache dialogs somehow and do not want to obtain the whole list of your dialogs on every app start. ## Update dialog [Section titled “Update dialog”](#update-dialog) User can update group chat name, photo or add/remove occupants: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const toUpdateParams = { name: "Crossfit2" }; ConnectyCube.chat.dialog .update(dialogId, toUpdateParams) .then((dialog) => {}) .catch((error) => {}); ``` ## Add/Remove occupants [Section titled “Add/Remove occupants”](#addremove-occupants) To add more occupants use `push_all` operator. To remove yourself from the dialog use `pull_all` operator: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; const toUpdateParams = { push_all: { occupants_ids: [97, 789] } }; ConnectyCube.chat.dialog .update(dialogId, toUpdateParams) .then((dialog) => {}) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.dialog.update(dialogId, toUpdateParams)` - [see](/server/chat#response-2) > **Note** > > Only group chat owner can remove other users from group chat. ## Remove dialog [Section titled “Remove dialog”](#remove-dialog) The following snippet is used to delete a dialog: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; // const dialogIds = ['5356c64ab35c12bd3b108a41', ..., '5356c64ab35c12bd3b108a84'] ConnectyCube.chat.dialog.delete(dialogId).catch((error) => {}); ``` This request will remove this dialog for current user, but other users still will be able to chat there. Only group chat owner can remove the group dialog for all users. You can also delete multiple dialogs in a single request. ## Clear dialog history [Section titled “Clear dialog history”](#clear-dialog-history) The following snippet is used to clear dialog history by ID: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog.clearHistory(dialogId).catch((error) => {}); ``` This request will clear all messages in the dialog for current user, but not for other users. ## Subscribe to dialog [Section titled “Subscribe to dialog”](#subscribe-to-dialog) In order to be able to chat in public dialog, you need to subscribe to it: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog .subscribe(dialogId) .then((dialog) => {}) .catch((error) => {}); ``` It’s also possible to subscribe to group chat dialog. Response example from `ConnectyCube.chat.dialog.subscribe(dialogId)` - [see](/server/chat#response-5) ## Unsubscribe from dialog [Section titled “Unsubscribe from dialog”](#unsubscribe-from-dialog) ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; ConnectyCube.chat.dialog.unsubscribe(dialogId).catch((error) => {}); ``` ## Retrieve public dialog occupants [Section titled “Retrieve public dialog occupants”](#retrieve-public-dialog-occupants) A public chat dialog can have many occupants. There is a separated API to retrieve a list of public dialog occupants: ```javascript const dialogId = "5356c64ab35c12bd3b108a41"; 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 = "photo"; // insert the imageHTML as HTML template } } }; ``` In a case you want remove a shared attachment from server: ```javascript ConnectyCube.storage .delete(file.id) .then(() => {}) .catch((error) => {}); ``` ## Attachments (location) [Section titled “Attachments (location)”](#attachments-location) Sharing location attachments is nothing but sending 2 numbers: **latitude** and **longitude**. These values can be accessed using any JS library available in npm registry. ```javascript const latitude = "64.7964274"; const longitude = "-23.7391878"; const message = { type: dialog.type === 3 ? "chat" : "groupchat", body: "attachment", extension: { save_to_history: 1, dialog_id: dialog._id, attachments: [{ latitude, longitude, type: "place" }], }, }; // send the message message.id = ConnectyCube.chat.send(dialog._id, message); ``` On the receiver’s side the location attachment can be accessed the following way: ```javascript ConnectyCube.chat.onMessageListener = (userId, message) => { if (message.extension.hasOwnProperty("attachments")) { if (message.extension.attachments.length > 0) { const attachment = message.extension.attachments[0]; const latitude = attachment.latitude; const longitude = attachment.longitude; // and now display the map // ... } } }; ``` ## Edit message [Section titled “Edit message”](#edit-message) Use the following code snippet to edit a message (correct message body). Other user(s) will receive the ‘edit’ message info via callback: ```javascript ConnectyCube.chat.editMessage({ to: 123, // either a user id if this is 1-1 chat or a chat dialog id dialogId: "52e6a9c8a18f3a3ea6001f18", body: "corrected message body", originMessageId: "58e6a9c8a1834a3ea6001f15", // origin message id to edit last: false // pass 'true' if edit last (the newest) message in history }) ... ConnectyCube.chat.onMessageUpdateListener = (messageId, isLast, updatedBody, dialogId, userId) => { } ``` Also, you can update a message via HTTP API: ```javascript // const messageIds = ""; // to update all const messageIds = ["55fd42369575c12c2e234c64", "55fd42369575c12c2e234c68"].join(","); // or one - "55fd42369575c12c2e234c64" const params = { read: 1, // mark message as read delivered: 1, // mark message as delivered message: "corrected message body", // update message body chat_dialog_id: "5356c64ab35c12bd3b108a41", }; ConnectyCube.chat.message .update(messageIds, params) .then(() => {}) .catch((error) => {}); ``` ## 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](/nativescript/push-notifications) guide. To configure push template which users receive - go to [Dashboard Console, Chat Alerts page](https://admin.connectycube.com/) Also, here is a way to avoid automatically sending push notifications to offline recipient/recipients. For it add the `silent` parameter with value `1` to the `extension` of the message. ```javascript const message = { ... extension: { ... silent: 1, }, }; ``` After sending such a message, the server won’t create the push notification for offline recipient/recipients. > **Note** > > Currently push notifications are supported on mobile environment only. ## Mark a client as Active/Inactive [Section titled “Mark a client as Active/Inactive”](#mark-a-client-as-activeinactive) When you send a chat message and the recipient/recipients is offline, then automatic push notification will be fired. Sometimes a client app can be in a background mode, but still online. In this case it’s useful to let server know that a user wants to receive push noificattions while still is connected to chat. For this particular case we have 2 handy methods: ‘markInactive’ and ‘markActive’: ```javascript ConnectyCube.chat.markInactive(); ``` ```javascript ConnectyCube.chat.markActive(); ``` The common use case for these APIs is to call ‘markInactive’ when an app goes to background mode and to call ‘markActive’ when an app goes to foreground mode. ## Get last activity [Section titled “Get last activity”](#get-last-activity) There is a way to get an info when a user was active last time, in seconds. This is a modern approach for messengers apps, e.g. to display this info on a Contacts screen or on a User Profile screen. ```javascript const userId = 123234; ConnectyCube.chat .getLastUserActivity(userId) .then((result) => { const userId = result.userId; const seconds = result.seconds; // 'userId' was 'seconds' ago }) .catch((error) => {}); ``` ## Last activity subscription [Section titled “Last activity subscription”](#last-activity-subscription) Listen to user last activity status via subscription. ```javascript ConnectyCube.chat.subscribeToUserLastActivityStatus(userId); ConnectyCube.chat.unsubscribeFromUserLastActivityStatus(userId); ConnectyCube.chat.onLastUserActivityListener = (userId, seconds) => {}; ``` ## System messages [Section titled “System messages”](#system-messages) In a case you want to send a non text message data, e.g. some meta data about chat, some events or so - there is a system notifications API to do so: ```javascript const userId = 123234; const msg = { body: "dialog/UPDATE_DIALOG", extension: { photo_uid: "7cafb6030d3e4348ba49cab24c0cf10800", name: "Our photos", }, }; ConnectyCube.chat.sendSystemMessage(userId, msg); ConnectyCube.chat.onSystemMessageListener = function (msg) {}; ``` ## Moderation [Section titled “Moderation”](#moderation) The moderation capabilities help maintain a safe and respectful chat environment. We have options that allow users to report inappropriate content and manage their personal block lists, giving them more control over their experience. ### Report user [Section titled “Report user”](#report-user) For user reporting to work, it requires the following: 1. Go to [ConnectyCube Daashboard](https://admin.connectycube.com/) 2. select your Application 3. Navigate to **Custom** module via left sidebar 4. Create new table called **UserReports** with the following fields: * **reportedUserId** - integer * **reason** - string ![Chat widget: report table in ConnectyCube dashboard](/images/chat_widget/chat-widget-report-table.png) Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```javascript const reportedUserId = 45 const reason = "User is spamming with bad words" await ConnectyCube.data.create('UserReports', { reportedUserId, reason }); ``` ### Report message [Section titled “Report message”](#report-message) For message reporting to work, the same approach to user reporting above could be used. You need to create new table called **MessageReports** with the following fields: * **reportedMessageId** - integer * **reason** - string Once the table is created, you can create a report with the following code snippet and then see all the reports in Dashboard: ```javascript const reportedMessageId = "58e6a9c8a1834a3ea6001f15" const reason = "The message contains phishing links" await ConnectyCube.data.create('MessageReports', { reportedMessageId, reason }); ``` ### Block user [Section titled “Block user”](#block-user) Block list (aka Privacy list) allows enabling or disabling communication with other users. You can create, modify, or delete privacy lists, define a default list. > The user can have multiple privacy lists, but only one can be active. #### Create privacy list [Section titled “Create privacy list”](#create-privacy-list) A privacy list must have at least one element in order to be created. You can choose a type of blocked logic. There are 2 types: * Block in one way. When you blocked a user, but you can send messages to him. * Block in two ways. When you blocked a user and you also can’t send messages to him. ```javascript const users = [ { user_id: 34, action: "deny" }, { user_id: 48, action: "deny", mutualBlock: true }, // it means you can't write to user { user_id: 18, action: "allow" }, ]; const list = { name: "myList", items: users }; ConnectyCube.chat.privacylist.create(list).catch((error) => {}); ``` > In order to be used the privacy list should be not only set, but also activated(set as default). #### Activate privacy list [Section titled “Activate privacy list”](#activate-privacy-list) In order to activate rules from a privacy list you should set it as default: ```javascript const listName = "myList"; ConnectyCube.chat.privacylist.setAsDefault(listName).catch((error) => {}); ``` #### Update privacy list [Section titled “Update privacy list”](#update-privacy-list) There is a rule you should follow to update a privacy list: * If you want to update or set new privacy list instead of current one, you should decline current default list first. ```javascript const listName = "myList"; const list = { name: listName, items: [{ user_id: 34, action: "allow" }], }; ConnectyCube.chat.privacylist .setAsDefault(null) .then(() => ConnectyCube.chat.privacylist.update(list)) .then(() => ConnectyCube.chat.privacylist.setAsDefault(listName)) .catch((error) => {}); ``` #### Retrieve privacy list names [Section titled “Retrieve privacy list names”](#retrieve-privacy-list-names) To get a list of all your privacy lists’ names use the following request: ```javascript let names; ConnectyCube.chat.privacylist .getNames() .then((response) => (names = response.names)) .catch((error) => {}); ``` Response example from `ConnectyCube.chat.privacylist.getNames()`: ```json { "active": null, "default": null, "names": ["myList", "blockedusers"] } ``` #### Retrieve privacy list with name [Section titled “Retrieve privacy list with name”](#retrieve-privacy-list-with-name) To get the privacy list by name you should use the following method: ```javascript const listName = "myList"; let name, items; ConnectyCube.chat.privacylist.getList(listName).then((response) => { name = response.name; items = response.items; }); ``` Response example from `ConnectyCube.chat.privacylist.getList(listName)`: ```json { "name": "myList", "items": [ { "user_id": 34, "action": "deny" }, { "user_id": 48, "action": "deny", "mutualBlock": true }, { "user_id": 18, "action": "allow" } ] } ``` #### Remove privacy list [Section titled “Remove privacy list”](#remove-privacy-list) To delete a list you can call a method below or you can edit a list and set items to `nil`. ```javascript const listName = "myList"; ConnectyCube.chat.privacylist.delete(listName).catch((error) => {}); ``` #### Blocked user attempts to communicate with user [Section titled “Blocked user attempts to communicate with user”](#blocked-user-attempts-to-communicate-with-user) Blocked users will be receiving an error when trying to chat with a user in a 1-1 chat and will be receiving nothing in a group chat: ```javascript ConnectyCube.chat.onMessageErrorListener = function (messageId, error) {}; ``` ## Ping server [Section titled “Ping server”](#ping-server) Sometimes, it can be cases where TCP connection to Chat server can go down without the application layer knowing about it. To check that chat connection is still alive or to keep it to be alive there is a ping method: ```javascript const msTimeout = 3000; ConnectyCube.chat.pingWithTimeout(msTimeout).then(() => { }).catch((e) => { // No connection with server // // Let's try to re-connect // ... }) ``` # Push Notifications > Elevate NativeScript 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. First of all we need to install [nativescript-plugin-firebase](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/blob/master/docs/NON_FIREBASE_MESSAGING.md) lib. Just follow [the guide](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/blob/master/docs/NON_FIREBASE_MESSAGING.md). 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. ![Upload APNS certificate in ConnectyCube dashboard](/_astro/ios-upload-push-certificate._J9x_biU_x01ix.webp) 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. ![Setup Xcode capabilities](/_astro/setup_capabilities.DDONTS8C_1EzgLI.webp) ### 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: ![Find your FCM service account key](/_astro/fcm_account_key_settings.DIN_1KuB_Z1tPxdW.webp) 2. Select and configure **Manage Keys** option: ![Find your FCM server key](/_astro/fcm_account_key_manage.B_QpQr4j_ZIsMmz.webp) 3. Select **ADD KEY**, **Create new key**: ![Find your FCM server key](/_astro/fcm_account_key_manage_create.DzJUbiXU_Z17xFHF.webp) 4. Select **Key type** (json recommended) and create: ![Find your FCM server key](/_astro/fcm_account_key_json.BHnYerBS_1Pcx5.webp) 5. Save it locally: ![Find your FCM server key](/_astro/fcm_account_key_key_saved.XyuT6mGT_ZRfxMx.webp) 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. ![Add your FCM server key to your Dashboard](/_astro/fcm_service_account_key2.Dd7Qkoql_Z25pJUu.webp) 7. Copy **Sender ID** value from your Firebase console **Cloud Messaging** section. You may require it later. ![Find your Sender ID](/_astro/fcm_service_account_key3.CFBXqwTK_EIg8N.webp) 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: ![Find your FCM server key](/_astro/fcm_server_key.BzK_4dQ__ZJghKo.webp) 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. ![Add your FCM server key to your Dashboard](/_astro/fcm_server_key_2.D-cbuUdg_fI4XM.webp) 3. Copy **Sender ID** value from your Firebase console **Cloud Messaging** section. You may require it later. ![Find your Sender ID](/_astro/fcm_service_account_key3.CFBXqwTK_EIg8N.webp) 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) ## Setup nativescript-plugin-firebase lib [Section titled “Setup nativescript-plugin-firebase lib”](#setup-nativescript-plugin-firebase-lib) Next step is to setup the **nativescript-plugin-firebase** lib: ```javascript import { messaging, Message } from "nativescript-plugin-firebase/messaging"; messaging.registerForPushNotifications({ onPushTokenReceivedCallback: (token: string): void => { console.log("Firebase plugin received a push token: " + token); }, onMessageReceivedCallback: (message: Message) => { console.log("Push message received: " + message.title); }, // Whether you want this plugin to automatically display the notifications or just notify the callback. Currently used on iOS only. Default true. showNotifications: true, // Whether you want this plugin to always handle the notifications when the app is in foreground. Currently used on iOS only. Default false. showNotificationsWhenInForeground: true }).then(() => console.log("Registered for push")); ``` ## 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"; ... onPushTokenReceivedCallback: (token: string): void => { console.log("TOKEN:", token); this.subscribeToPushNotification(token); } ... subscribeToPushNotification(deviceToken) { const deviceUDID = "..."; // unique device identifier const platform = "ios"; // "android" const env = 'development'; // 'production' const params = { // for iOS VoIP it should be 'apns_voip' notification_channel: platform === 'ios' ? 'apns' : 'gcm', device: { platform: platform, udid: deviceUDID }, push_token: { environment: env, 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 env = 'development'; // 'production' const pushParameters = { notification_type: "push", user: { ids: [21, 12] }, // recipients. environment: env, 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. ## 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 deviceUDID = "..."; // unique device identifier const platform = "ios"; // "android" const deleteSubscription = (subscriptions) => { let subscriptionIdToDelete; subscriptions.forEach((sbs) => { if (sbs.subscription.device.platform === platform && sbs.subscription.device.udid === deviceUDID) { 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 [NativeScript Call plugin](https://github.com/EddyVerbruggen/nativescript-call). For iOS, this will also require to integrate **VoIP push notifications** along with CallKit. 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. # Streaming > Leverage ConnectyCube's streaming feature for dynamic real-time interactions in NativeScript app. Ideal for interactive sessions, such as teachers broadcasting to multiple students. At this time, ConnectyCube SDK does not support Streaming for NativeScript. We are doing our best to make it happened ASAP. For now - please follow [ConnectyCube blog](https://connectycube.com/blog/) for further updates or in a case on any questions - please contact us at # Video Calling > Empower your NativeScript applications with our Video Calling P2P API. Enable secure and immersive peer-to-peer video calls for enhanced user experience At this time, ConnectyCube SDK does not support Video Calling for NativeScript. We are doing our best to make it happened ASAP. For now - please follow [ConnectyCube blog](https://connectycube.com/blog/) for further updates or in a case on any questions - please contact us at # Video Conferencing > Discover the simplicity of integrating conference video calling into your NativeScript app with our easy-to-use API. Empower users to connect from anywhere. At this time, ConnectyCube SDK does not support Multiparty Video Conferencing for NativeScript. We are doing our best to make it happened ASAP. For now - please follow [ConnectyCube blog](https://connectycube.com/blog/) for further updates or in a case on any questions - please contact us at # 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. ![Whiteboard demo](/_astro/whiteboard_1024x504.by1QVA4x_1yUk0p.webp) 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](/nativescript/) 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 => { }); ``` # Code samples > Explore easy-to-follow ReactNative code snippets for implementing chat, video calling, and push notifications using ConnectyCube SDK With ConnectyCube code samples - learn how to implement 1-1 and group chat messaging, video calling, enable Push Notifications, authenticate your users via phone SMS verification, store and retrieve file attachments from the cloud and many more features. These code samples are simple enough that even novice developers will be able to understand them. React Native code samples are available at [GitHub repository](https://github.com/ConnectyCube/connectycube-reactnative-samples) ## Chat code sample [Section titled “Chat code sample”](#chat-code-sample) > [Chat code sample for React Native](https://github.com/ConnectyCube/connectycube-reactnative-samples/tree/master/RNChat) `````` ## P2P calling code sample [Section titled “P2P calling code sample”](#p2p-calling-code-sample) > [Video Chat code sample for React Native](https://github.com/ConnectyCube/connectycube-reactnative-samples/tree/master/RNVideoChat) `````` ## Conference calling code sample [Section titled “Conference calling code sample”](#conference-calling-code-sample) > [Video Chat code sample for React Native](https://github.com/ConnectyCube/connectycube-reactnative-samples/tree/master/RNVideoChatConf) `````` # ConnectyCube Server API > Explore a set of clearly defined methods of communication among various components. ConnectyCube provides an easy API to build powerful applications. Welcome to ConnectyCube Server API - set of clearly defined methods of communication among various components. ConnectyCube provides an easy API to work with different modules of the application: * Users * Chats * Push notifications * Video calling * Storage * Address book * Custom data API documentation describes what services an API offers and how to use those services. ## REST API endpoint [Section titled “REST API endpoint”](#rest-api-endpoint) **Basic URL:** `https://api.connectycube.com` ## Headers [Section titled “Headers”](#headers) The only mandatory header required for all types of requests is ConnectyCube **Token** - a credential that can be used by an application and user to access an API. Follow [Create Session](/server/auth#create-session) request to obtain a session token. For POST and PUT requests also add **Content-Type** header to transfer data in JSON format. To set headers of the request, use the following example: ```bash -H "Content-Type: application/json" \ -H "CB-Token: " \ ``` ## Request [Section titled “Request”](#request) The Hypertext Transfer Protocol (HTTP) is designed to enable communications between clients and servers. HTTP works as a request-response protocol between a client and server. **Methods:** * **POST** - is used to send data to a server to create a resource * **GET** - is used to request data from a specified resource * **PUT** - is used to send data to a server to update a resource * **DELETE** - deletes the specified resource **Request body:** Depends on the request type, request body may contain parameters. Each separate request contains its own set of parameters available to be specified. For **POST** and **PUT** requests (in JSON format): ```bash -d '{"entity": {"field_1": "value", "field_2": "value"}}' \ ``` For **GET** request: ```bash -d 'per_page=7&page=2' \ ``` ## Response [Section titled “Response”](#response) Depends on a request, response can contain a defined set of information or returns the status code only. Available statuses: | Code | Status | Description | | ---- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 200 | OK | Response for successful HTTP requests. In a **GET** request, the response will contain an entity corresponding to the requested resource. In a **POST** request, the response will contain an entity describing or containing the result of the action | | 201 | Created | The request has been fulfilled, resulting in the creation of a new resource | | 202 | Accepted | The request has been accepted for processing, but the processing has not been completed. The request might or might not be eventually acted upon, and may be disallowed when processing occurs | | 400 | Bad request | The server cannot or will not process the request due to an apparent client error (e.g., malformed request syntax, size too large, invalid request message framing, or deceptive request routing) | | 401 | Unauthorized | Authorisation is requeired to process the request | | 403 | Forbidden | Server is refusing action. The user might not have the necessary permissions for a resource, or may need an account of some sort | | 404 | Not found | The requested resource could not be found | | 422 | Unprocessable Entity | The request was well-formed but was unable to be followed due to semantic errors | | 429 | Too Many Requests | The user has sent too many requests in a given amount of time | | 500 | Internal Server Error | Something has gone wrong on the web site’s server but the server could not be more specific on what the exact problem is | | 503 | Service Unavailable | The server is currently unavailable. Generally, this is a temporary state |