{"id":27999342,"url":"https://github.com/voximplant/solutions-messaging","last_synced_at":"2025-05-08T22:57:56.723Z","repository":{"id":40858506,"uuid":"221198392","full_name":"voximplant/solutions-messaging","owner":"voximplant","description":"Voximplant IP messaging tutorial for web, iOS, and Android","archived":false,"fork":false,"pushed_at":"2023-03-02T16:21:33.000Z","size":21966,"stargazers_count":7,"open_issues_count":32,"forks_count":1,"subscribers_count":10,"default_branch":"draft-4","last_synced_at":"2025-05-08T22:57:45.883Z","etag":null,"topics":["android","demo","ios","tutorial","voximplant"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/voximplant.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-11-12T11:09:40.000Z","updated_at":"2025-04-24T14:32:37.000Z","dependencies_parsed_at":"2023-02-02T15:32:16.569Z","dependency_job_id":null,"html_url":"https://github.com/voximplant/solutions-messaging","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voximplant%2Fsolutions-messaging","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voximplant%2Fsolutions-messaging/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voximplant%2Fsolutions-messaging/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voximplant%2Fsolutions-messaging/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/voximplant","download_url":"https://codeload.github.com/voximplant/solutions-messaging/tar.gz/refs/heads/draft-4","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253160780,"owners_count":21863627,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","demo","ios","tutorial","voximplant"],"created_at":"2025-05-08T22:57:55.941Z","updated_at":"2025-05-08T22:57:56.707Z","avatar_url":"https://github.com/voximplant.png","language":"JavaScript","readme":"Messaging allows you to implement text communications within main Voximplant developer account: account users can log in via Voximplant SDKs and become participants in conversations. Follow this tutorial to learn how to create your own web and mobile messaging client based on our SDKs.\n\n## WHAT YOU NEED\n* Voximplant developer account. If you don’t have one, [sign up here](https://voximplant.com/sign-up/).\n* Voximplant application, JS scenario, rule, and two users. Those will be created during this tutorial.\n* Client for users to log in. We’ll use our demo clients for Web and iOS.\n* Backend server for storing users of a Voximplant application. \n\n## BACKEND SERVER\nIt’s for the better to make a request for all available users within a Voximplant application at each client’s start. In order to do so, the client has to request all users related to already created conversation by using your Voximplant account credentials.\nThis is where the backend server is in need: we don’t want to store private authorization on the client side since it’s totally insecure (chances are you don’t want it either), so we can delegate it to backend. We’ve implemented a backend server using PHP, check the full listing of it [here](https://github.com/voximplant/solutions-messaging/blob/master/server/index.php). You’re free to either use our solution or implement your own server using another programming language.\n\n## 1. VOXIMPLANT APPLICATION SETTINGS\nFirst, log in to your account here: https://manage.voximplant.com/auth. On the left menu, select **Applications**, click **New application** and create a **messaging** application. \nNext, you have to create at least two users for your application. Switch to the **Users** tab, click **Create user**, set username (e.g., user1) and password, then select the **Create another** checkbox and click **Create**. The same window for creating the second user will appear in which you should unselect **Create another** as we don’t need more users. We’ll need these users’ login-password pairs to authenticate in the clients.\n\n## 2. CLIENT\n### Connect to Voximplant and login\n\nInitialize your project depending on what type of client you are going to use. \n\nFirst, you have to make the login screen to work properly. The client has to know what credentials to use for authentication. \n\n\u003eWeb client\n\nOpen the **vox.service.ts** and use the following code for connection and authentication to the Voximplant cloud:\n\n```typescript\n// Create Voximplant Web SDK instance\n    VoxService.inst = VoxImplant.getInstance();\n\n    // Reconnect to the Voximplant cloud when disconnected\n    VoxService.inst.on(VoxImplant.Events.ConnectionClosed, () =\u003e {\n      log('Connection was closed');\n      this.connectToVoxCloud();\n    });\n\n    // Init Voximplant\n    VoxService.inst.init({\n      experiments: {\n        messagingV2: true,\n      },\n    })\n      .then(() =\u003e {\n        log('SDK initialized');\n        // Connect to the Voximplant cloud\n        this.connectToVoxCloud();\n      })\n      .catch(logError);\n\npublic onLogin(loginForm, accessToken) {\n    if (!accessToken) {\n      return VoxService.inst.login(loginForm.user, loginForm.password);\n    } else {\n      return VoxService.inst.loginWithToken(loginForm.user, accessToken);\n    }\n  }\n```\n----\n\u003eiOS client\n\nOpen the **AuthService.swift** and use the following code for connection and authentication to the Voximplant cloud:\n\n\n**(AppDelegate.swift)**\n```swift\nlet client = VIClient(delegateQueue: DispatchQueue.main)\nlet authService = AuthService(client: client)\n```\n\n**(AuthService.swift)**\n```swift\nfunc connect(_ completion: @escaping (Result\u003c(), Error\u003e) -\u003e Void) {\n    let state = client.clientState\n\n    if state == .disconnected || state  == .connecting {\n        client.connect()\n    } else {\n        completion(.success(()))\n    }\n}\n\nfunc login(\n    with user: String,\n    and password: String,\n    completion: @escaping (Result\u003cString, Error\u003e) -\u003e Void\n) {\n    connect() { result in\n        if case .failure(let error) = result {\n            completion(.failure(error))\n        }\n        if case .success = result {\n            self.client.login(withUser: user,\n                              password: password,\n                              success: { displayName, tokens in\n                                completion(.success(displayName)) },\n                              failure: { error in\n                                completion(.failure(error)) })\n        }\n    }\n}\n\nfunc login(\n    with user: String,\n    and token: String,\n    completion: @escaping (Result\u003cString, Error\u003e) -\u003e Void\n) {\n    connect() { result in\n        if case .failure(let error) = result {\n            completion(.failure(error))\n        }\n        if case .success = result {\n            self.client.login(withUser: user,\n                              token: token,\n                              success: { displayName, tokens in\n                                completion(.success(displayName)) },\n                              failure: { error in\n                                completion(.failure(error)) })\n        }\n    }\n}\n```\n\n----\n\u003eAndroid client\n\n**(MessagingApplication.kt)**\n```kotlin\nval client = Voximplant.getClientInstance(\n    Executors.newSingleThreadExecutor(),\n    applicationContext,\n    ClientConfig()\n)\nval clientManager = VoxClientManager(client)\n```\n\n**(VoxClientManager.kt)**\n```kotlin\nfun login(\n    username: String,\n    password: String\n) {\n    when (client.clientState) {\n        ClientState.DISCONNECTED -\u003e\n            try {\n                client.connect()\n            } catch (e: IllegalStateException) {\n                Log.e(APP_TAG, \"exception on connect $e\")\n            }\n\n        ClientState.CONNECTED -\u003e\n            client.login(username, password)\n\n        else -\u003e return\n    }\n}\n\nfun loginWithToken(\n    username: String,\n    token: String\n) {\n    when (client.clientState) {\n        ClientState.DISCONNECTED -\u003e\n            try {\n                client.connect()\n            } catch (e: IllegalStateException) {\n                Log.e(APP_TAG, \"exception on connect $e\")\n            }\n\n        ClientState.CONNECTED -\u003e\n            client.loginWithAccessToken(username, token)\n\n        else -\u003e return\n    }\n}\n\n// Login result will be sent via the IClientLoginListener methods;\n// e.g., if login is successfully completed,\n// onLoginSuccessful(displayName:authParams:)\n// will be called\n```\n\nAfter successful initialization the client renders the login screen where you specify credentials, click **Sign in** and after that the client can log in to the Messaging module.  \n\nWe suggest that this part of the code should do the following:\n- initiates a messaging instance\n- gets the current user info\n- gets all the conversation where the current user belong to\n- receive other users from backend\n- add listeners for events that will be triggering over WebSockets\n\n\n\u003eWeb client\n\nAll the methods related to communication with Messaging are comprised in the **messenger.service.ts** file. \n\n```typescript\npublic async init() {\n    // Get Voximplant Messenger instance\n    try {\n      MessengerService.messenger = VoxImplant.getMessenger();\n      log('Messenger v2', MessengerService.messenger);\n      log('VoxImplant.Messaging v2', VoxImplant.Messaging);\n    } catch (e)  {\n      // Most common error 'Not authorised', so redirect to login\n      logError(e);\n      await store.dispatch('auth/relogin');\n    }\n\n    // Get the current user data\n    const initialData = {\n      currentUser: {},\n      conversations: [],\n      users: [],\n    };\n\n    await MessengerService.messenger.getUser(MessengerService.messenger.getMe())\n      .then((evt) =\u003e {\n        logHelp('Current user data received', evt);\n        initialData.currentUser = evt.user;\n\n        return this.getCurrentConversations(evt.user.conversationsList);\n      })\n      .then((evts) =\u003e {\n        logHelp('Current user conversations received', evts);\n\n        initialData.conversations = evts.length ? evts.map((e) =\u003e e.conversation) : [];\n        return this.getAllUsers();\n      })\n      .then((evts) =\u003e {\n        logHelp('Conversation participants user info received', evts);\n        initialData.users = evts.map((e) =\u003e e.user);\n      })\n      .catch(logError);\n\n    this.addMessengerEventListeners();\n\n    /**\n     * You have to send user presence status periodically to notify the new coming users if you are online\n     * TODO You can implement invisible mode by sending setStatus(false)\n     */\n    const sendStatus = () =\u003e setTimeout(() =\u003e {\n      MessengerService.messenger.setStatus(true);\n      this.setStatusTimer = sendStatus();\n    }, TIME_NOTIFICATION);\n\n    this.setStatusTimer = sendStatus();\n\n    return initialData;\n  }\n```\n----\n\u003eiOS client:\n\n**(AppDelegate.swift)**\n```swift\nlet voximplantService =  VoximplantService(with: client.messenger)\n```\n**(VoximplantService.swift)**\n```swift\nprivate let messenger: VIMessenger\n\ninit(with messenger: VIMessenger) {\n    self.messenger = messenger\n    super.init()\n    self.messenger.addDelegate(self)\n}\n\n// Call requestUser(with:completion:) with messenger.me as a username\n// to get the VIUser instance for the current user\nfunc requestUser(\n    with username: String,\n    completion: @escaping (Result\u003cVIUser, Error\u003e) -\u003e Void\n) {\n    messenger.getUserByName(username, completion:\n        VIMessengerCompletion\u003cVIUserEvent\u003e (\n            success: { userEvent in\n                completion(.success(userEvent.user))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n\n// Get conversationList from the VIUser instance\n// of the current user (user.conversationList).\n// Call requestMultipleConversations(with:completion:) with a conversationList array\n// to get all the conversations where the current user belongs to\nfunc requestMultipleConversations(\n    with uuids: [String],\n    completion: @escaping (Result\u003c[VIConversation], Error\u003e) -\u003e Void\n) {\n    messenger.getConversations(uuids, completion:\n        VIMessengerCompletion\u003cNSArray\u003e (\n            success: { conversationEvents in\n                let conversations =\n                    (conversationEvents as! [VIConversationEvent])\n                        .map { $0.conversation }\n                completion(.success(conversations))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n\n// Get participants from each \n// VIConversation instance (conversation.participants).\n// Call requestUsers(with:completion:) with a participants ImIds array \n// to get all the participants of the conversation\nfunc requestUsers(\n    with imIDArray: [NSNumber],\n    completion: @escaping (Result\u003c[VIUser], Error\u003e) -\u003e Void\n) {\n    messenger.getUsersByIMId(imIDArray, completion:\n        VIMessengerCompletion\u003cNSArray\u003e (\n            success: { event in\n                let users = (event as! [VIUserEvent]).map { $0.user }\n                completion(.success(users))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n```\n\n----\n\n\u003eAndroid client:\n\n**(Repository.kt)**\n```kotlin\nprivate val remote = VoximplantService(Voximplant.getMessenger())\n```\n\n**(VoximplantService.kt)**\n```kotlin\nprivate val messenger: IMessenger\n\nconstructor(messenger: IMessenger) {\n    this.messenger = messenger\n    this.messenger.addMessengerListener(this)\n}\n\n// Call requestUser(username:completion:) with messenger.me as a username\n// to get the IUser instance for the current user\nfun requestUser(\n    username: String,\n    completion: (Result\u003cIUser\u003e) -\u003e Unit\n) {\n    messenger.getUser(\n        username,\n        object : IMessengerCompletionHandler\u003cIUserEvent\u003e {\n            override fun onSuccess(event: IUserEvent) {\n                completion(success(event.user))\n            }\n            override fun onError(event: IErrorEvent) {\n                completion(failure(buildError(event)))\n            }\n        }\n    )\n}\n\n// Get conversationList from the IUser instance\n// of the current user (user.conversationList).\n// Call requestMultipleConversations(uuids:completion:) with conversationList \n// to get all the conversations where the current user belongs to\nfun requestMultipleConversations(\n    uuids: List\u003cString\u003e,\n    completion: (Result\u003cList\u003cIConversation\u003e\u003e) -\u003e Unit\n) {\n    messenger.getConversations(uuids, object :\n        IMessengerCompletionHandler\u003cList\u003cIConversationEvent\u003e\u003e {\n        override fun onSuccess(conversationEvents: List\u003cIConversationEvent\u003e) {\n            completion(success(conversationEvents.map { it.conversation }))\n        }\n        override fun onError(errorEvent: IErrorEvent) {\n            completion(failure(buildError(errorEvent)))\n        }\n    })\n}\n\n// Get participants from each\n// IConversation instance (conversation.participants).\n// Call requestUsers(imIDs:completion:) with a participants ImIds array \n// to get all the participants of the conversation\nfun requestUsers(\n    imIDs: List\u003cLong\u003e,\n    completion: (Result\u003cList\u003cIUser\u003e\u003e) -\u003e Unit\n) {\n    messenger.getUsersByIMId(\n        imIDs,\n        object : IMessengerCompletionHandler\u003cList\u003cIUserEvent\u003e\u003e {\n            override fun onSuccess(userEvents: List\u003cIUserEvent\u003e) {\n                completion(success(userEvents.map { it.user }))\n            }\n            override fun onError(errorEvent: IErrorEvent) {\n                completion(failure(buildError(errorEvent)))\n            }\n        }\n    )\n}\n```\n\nFrom now on, your login screen allows users to authenticate in your client. \n\n### Retrieve conversations\nThe client can retrieve all conversations that your user belongs to via the **getConversations** method:\n\n\u003eWeb client (**messenger.service.ts**):\n```typescript\nprivate getCurrentConversations(conversationsList) {\n    return MessengerService.messenger.getConversations(conversationsList).catch((e) =\u003e {\n      logError('MessengerService.getCurrentConversations', e);\n      return [];\n    });\n  }\n```\n----\n\u003eiOS client (**VoximplantService.swift**):\n```swift\nfunc requestMultipleConversations(\n    with uuids: [String],\n    completion: @escaping (Result\u003c[VIConversation], Error\u003e) -\u003e Void\n) {\n    messenger.getConversations(uuids, completion:\n        VIMessengerCompletion\u003cNSArray\u003e (\n            success: { conversationEvents in\n                let conversations =\n                    (conversationEvents as! [VIConversationEvent])\n                        .map { $0.conversation }\n                completion(.success(conversations))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n```\n\n----\n\n\u003eAndroid client **(VoximplantService.kt)**:\n```kotlin\nfun requestMultipleConversations(\n    uuids: List\u003cString\u003e,\n    completion: (Result\u003cList\u003cIConversation\u003e\u003e) -\u003e Unit\n) {\n    messenger.getConversations(uuids, object :\n        IMessengerCompletionHandler\u003cList\u003cIConversationEvent\u003e\u003e {\n        override fun onSuccess(conversationEvents: List\u003cIConversationEvent\u003e) {\n            completion(success(conversationEvents.map { it.conversation }))\n        }\n        override fun onError(errorEvent: IErrorEvent) {\n            completion(failure(buildError(errorEvent)))\n        }\n    })\n}\n```\n## Create conversations\n\nVoximplant Messaging allows creating different types of conversations: regular (public and non-public) and direct ones, see the details here.  In these particular demo clients they are implemented as chat, direct and broadcast. To create a conversation, there is a **createConversation** method, whereas demo clients have appropriate wrappers for it.\n\n\u003e**Client-specific note**\n\u003e\n\u003ePermissions that are specified on creating a conversation become **default permissions** for this particular conversation; this means that all new participants will inherit them. If none of permissions is specified on creating a conversation, the following ones will be applied: **canRead**, **canWrite**, **canRemove**.\n\nHere is an example of how to create a chat where all members can write and see messages of each other:\n\n\u003eWeb client (**messenger.service.ts**)\n```typescript\nprivate createNewConversation(participants, title: string, direct:boolean, publicJoin:boolean, uber:boolean, customData:object) {\n    return MessengerService.messenger.createConversation(participants, title, direct, publicJoin, uber, customData);\n  }\n\n\npublic createChat(newChatData: NewChatData) {\n    const permissions: Permissions = {\n      canWrite: true,\n      canEdit: true,\n      canRemove: true,\n      canManageParticipants: true,\n      canEditAll: false,\n      canRemoveAll: false,\n    };\n\n    const participants = newChatData.usersId.map( (userId: number) =\u003e {\n      return {\n        userId,\n        ...permissions,\n      };\n    });\n\n    return this.createNewConversation(\n      participants,\n      newChatData.title,\n      false,\n      newChatData.isPublic,\n      newChatData.isUber,\n      {\n        type: 'chat',\n        image: newChatData.avatar,\n        description: newChatData.description,\n        permissions,\n      });\n  }\n```\n----\n\u003eiOS client (**VoximplantService.swift**):\n```swift\nfunc createConversation(\n    with title: String, and userImids: [NSNumber],\n    description: String, pictureName: String?,\n    isPublic: Bool, isUber: Bool, permissions: Permissions,\n    completion: @escaping (Result\u003cVIConversation, Error\u003e) -\u003e Void\n) {\n    let config = VIConversationConfig()\n    config.title = title\n    config.isDirect = false\n    config.isUber = isUber\n    config.isPublicJoin = isPublic\n    config.participants = userImIds\n        .map { self.builder.buildVIParticipant(with: $0, and: permissions) }\n    config.customData = builder\n        .buildCustomData(for: .chat, with: pictureName, and: description)\n    \n    messenger.createConversation(config, completion:\n        VIMessengerCompletion\u003cVIConversationEvent\u003e (\n            success: { conversationEvent in\n                completion(.success(conversationEvent.conversation))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n```\n\n----\n\u003eAndroid client (**VoximplantService.kt**):\n```kotlin\nfun createConversation(\n    title: String, userImIds: List\u003cLong\u003e, description: String,\n    pictureName: String?, isPublic: Boolean, isUber: Boolean,\n    permissions: Permissions, completion: (Result\u003cIConversationEvent\u003e) -\u003e Unit\n) {\n    val config = ConversationConfig.createBuilder()\n        .setTitle(title)\n        .setDirect(false)\n        .setUber(isUber)\n        .setPublicJoin(isPublic)\n        .setParticipants(\n            userImIds\n                .map { builder.buildParticipant(it, permissions) }\n        )\n        .setCustomData(\n            builder.buildCustomData(\n                ConversationType.CHAT, pictureName, description\n            )\n        )\n        .build()\n\n    messenger.createConversation(\n        config,\n        object : IMessengerCompletionHandler\u003cIConversationEvent\u003e {\n            override fun onSuccess(conversationEvent: IConversationEvent) {\n                completion(success(conversationEvent))\n            }\n            override fun onError(errorEvent: IErrorEvent) {\n                completion(failure(buildError(errorEvent)))\n            }\n        }\n    )\n}\n```\nPay attention that conversations are created on behalf of a user which is currently logged in on your client. That means this user becomes the owner and the very first admin of a newly created application with all possible permissions (see the details on permissions here).\n\nOnce a conversation is created, others can join it or be joined by administrators of the conversation.\n\n### Managing conversations\nBeing an administrator, your user can edit conversations and also leave them. It’s possible due to the **addParticipants**, **removeParticipants**, **addAdmins**, **removeAdmins**, **editPermissions** and **leaveConversation** methods. \n\nEditing includes changing of:\n- the title\n- users’ permissions\n- number of users (add/remove users)\n- custom data\n\nLet’s assume that the administrator wants to change all the mentioned aspects – this is how you can handle it by calling the appropriate methods.\n\nThe method to change the title and custom data. Custom data changing has a nuance: you can’t just pass changes of one field, you have to copy all other fields, otherwise, other fields will be deleted.\n\n\u003eWeb client (**actionConversations.ts**)\n```typescript\neditConversation: ({ getters }, newData) =\u003e {\n    if (newData.title \u0026\u0026 newData.title !== getters.currentConversation.title) {\n      getters.currentConversation.setTitle(newData.title)\n        .catch(logError);\n    }\n\n    if (newData.customData) {\n      getters.currentConversation.setCustomData({...getters.currentConversation.customData, ...newData.customData})\n        .catch(logError);\n    }\n  }\n```\n----\n\u003eiOS client (**VoximplantService.swift**):\n\n```swift\n// Update the VIConversation instance properties and\n// call update(conversation:completion:) to update this conversation;\n// for example:\n// conversation.title = “newTitle”\n// conversation.isPublicJoin = true\n// update(conversation: conversation) { result in\n//     // handle result\n// }\nfunc update(\n    conversation: VIConversation,\n    completion: @escaping (Result\u003cVIConversationEvent, Error\u003e) -\u003e Void\n) {\n    conversation.update(completion:\n        VIMessengerCompletion\u003cVIConversationEvent\u003e (\n            success: { conversationEvent in\n                completion(.success(conversationEvent))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n```\n\n----\n\u003eAndroid client (**VoximplantService.kt**):\n```kotlin\n// Update the IConversation instance properties and\n// call updateConversation(conversation:completion:) to update this conversation;\n// for example:\n// conversation.title = “newTitle”\n// conversation.isPublicJoin = true\n// updateConversation(conversation) { result -\u003e\n//     // handle result\n// }\nfun updateConversation(\n    conversation: IConversation,\n    completion: (Result\u003cIConversationEvent\u003e) -\u003e Unit\n) {\n    conversation.update(\n        object : IMessengerCompletionHandler\u003cIConversationEvent\u003e {\n            override fun onSuccess(conversationEvent: IConversationEvent) {\n                completion(success(conversationEvent))\n            }\n            override fun onError(errorEvent: IErrorEvent) {\n                completion(failure(buildError(errorEvent)))\n            }\n        }\n    )\n}\n```\n\nThe method to change some of the user’s permissions\n\n\u003eWeb client (**messenger.service.ts**)\n```typescript\npublic editPermissions(currentConversation: Conversation, permissions: Permissions, allUserIds: number[]) {\n    currentConversation.setCustomData({ ...currentConversation.customData, permissions });\n    return currentConversation.editParticipants(allUserIds.map((userId) =\u003e ({\n      userId,\n      ...permissions,\n    }))).catch(logError);\n  }\n```\n----\n\u003eiOS client (**VoximplantService.swift**):\n```swift\nfunc edit(\n    participants: [VIConversationParticipant],\n    in conversation: VIConversation,\n    completion: @escaping (Result\u003cVIConversationEvent, Error\u003e) -\u003e Void\n) {\n    conversation.editParticipants(participants, completion:\n        VIMessengerCompletion\u003cVIConversationEvent\u003e (\n            success: { conversationEvent in\n                completion(.success(conversationEvent))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n```\n----\n\u003eAndroid client (**VoximplantService.kt**):\n```kotlin\nfun editParticipants(\n    participants: List\u003cConversationParticipant\u003e,\n    conversation: IConversation,\n    completion: (Result\u003cIConversationEvent\u003e) -\u003e Unit\n) {\n    conversation.editParticipants(\n        participants,\n        object : IMessengerCompletionHandler\u003cIConversationEvent\u003e {\n            override fun onSuccess(conversationEvent: IConversationEvent) {\n                completion(success(conversationEvent))\n            }\n            override fun onError(errorEvent: IErrorEvent) {\n                completion(failure(buildError(errorEvent)))\n            }\n        }\n    )\n}\n```\n\nThe methods for adding and removing users\n\n\u003eWeb client (**messenger.service.ts**)\n\n```typescript\npublic addParticipants(currentConversation: Conversation, userIds: number[]) {\n    return currentConversation.addParticipants(userIds.map((userId) =\u003e ({\n      userId,\n      ...currentConversation.customData.permissions,\n    }))).catch(logError);\n  }\n\npublic removeParticipants(currentConversation: Conversation, userIds: number[]) {\n    return currentConversation.removeParticipants(userIds.map((userId) =\u003e ({userId}))).catch(logError);\n  }\n```\n----\n\u003eiOS client (**VoximplantService.swift**):\n```swift\nfunc add(\n    participants: [VIConversationParticipant],\n    to conversation: VIConversation,\n    completion: @escaping (Result\u003cVIConversationEvent, Error\u003e) -\u003e Void\n) {\n    conversation.addParticipants(participants, completion:\n        VIMessengerCompletion\u003cVIConversationEvent\u003e (\n            success: { conversationEvent in\n                completion(.success(conversationEvent))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n\nfunc remove(\n    participants: [VIConversationParticipant],\n    from conversation: VIConversation,\n    completion: @escaping (Result\u003cVIConversationEvent, Error\u003e) -\u003e Void\n) {\n    conversation.removeParticipants(participants, completion:\n        VIMessengerCompletion\u003cVIConversationEvent\u003e (\n            success: { conversationEvent in\n                completion(.success(conversationEvent))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n```\n----\n\u003eAndroid client (**VoximplantService.kt**):\n```kotlin\nfun addParticipants(\n    participants: List\u003cConversationParticipant\u003e,\n    conversation: IConversation,\n    completion: (Result\u003cIConversationEvent\u003e) -\u003e Unit\n) {\n    conversation.addParticipants(\n        participants,\n        object : IMessengerCompletionHandler\u003cIConversationEvent\u003e {\n            override fun onSuccess(conversationEvent: IConversationEvent) {\n                completion(success(conversationEvent))\n            }\n            override fun onError(errorEvent: IErrorEvent) {\n                completion(failure(buildError(errorEvent)))\n            }\n        }\n    )\n}\n\nfun removeParticipants(\n    participants: List\u003cConversationParticipant\u003e,\n    conversation: IConversation,\n    completion: (Result\u003cIConversationEvent\u003e) -\u003e Unit\n) {\n    conversation.removeParticipants(\n        participants,\n        object : IMessengerCompletionHandler\u003cIConversationEvent\u003e {\n            override fun onSuccess(conversationEvent: IConversationEvent) {\n                completion(success(conversationEvent))\n            }\n            override fun onError(errorEvent: IErrorEvent) {\n                completion(failure(buildError(errorEvent)))\n            }\n        }\n    )\n}\n```\n\nAnd this is the method that allows users to leave a conversation:\n\n\u003eWeb client (**messenger.service.ts**)\n```typescript\npublic leaveConversation(currentConversationUuid: string) {\n    MessengerService.messenger.leaveConversation(currentConversationUuid)\n      .catch(logError);\n  }\n```\n----\n\u003eiOS client (**VoximplantService.swift**):\n```swift\nfunc leaveConversation(\n    with UUID: String,\n    completion: @escaping (Result\u003cVIConversationEvent, Error\u003e) -\u003e Void\n) {\n    messenger.leaveConversation(UUID, completion:\n        VIMessengerCompletion\u003cVIConversationEvent\u003e (\n            success: { conversationEvent in\n                completion(.success(conversationEvent))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n```\n----\n\u003eAndroid client (**VoximplantService.kt**):\n```kotlin\nfun leaveConversation(\n    uuid: String,\n    completion: (Result\u003cIConversationEvent\u003e) -\u003e Unit\n) {\n    messenger.leaveConversation(\n        uuid,\n        object : IMessengerCompletionHandler\u003cIConversationEvent\u003e {\n            override fun onSuccess(conversationEvent: IConversationEvent) {\n                completion(success(conversationEvent))\n            }\n            override fun onError(errorEvent: IErrorEvent) {\n                completion(failure(buildError(errorEvent)))\n            }\n        }\n    )\n}\n```\n\nThe last method could come in handy if you need to remove the conversation: the owner can remove all other participants, then leave the conversation and that is how it would be deleted.\n\n### Send/receive messages\nThe following method is responsible for sending messages: **sendMessage**.\n\nTo receive messages, you have to handle the event on message sending. Sending messages works in the same, event-driven way.\n\n\n\u003eWeb client (**messenger.service.ts**)\n\nWe’ve added this on initialization step (**addMessengerEventListeners**).\nSee the listing of the **addMessengerEventListeners** function below:\n\n```typescript\nprivate addMessengerEventListeners() {\n    // Listen to other users presence status event\n    MessengerService.messenger.on(VoxImplant.Messaging.MessengerEvents.SetStatus, (e) =\u003e store.dispatch('conversations/onOnlineReceived', e));\n\n    // Listen to CreateConversation event called by this or another user\n    MessengerService.messenger.on(VoxImplant.Messaging.MessengerEvents.CreateConversation, (e) =\u003e store.dispatch('conversations/onConversationCreated', e));\n\n    // Listen to EditConversation event called by this or another user\n    MessengerService.messenger.on(VoxImplant.Messaging.MessengerEvents.EditConversation, (e) =\u003e store.dispatch('conversations/onConversationEdited', e));\n\n    // Listen to incoming messages\n    MessengerService.messenger.on(VoxImplant.Messaging.MessengerEvents.SendMessage, (e) =\u003e store.dispatch('conversations/onMessageSent', e));\n\n    // Listen to edited messages\n    MessengerService.messenger.on(VoxImplant.Messaging.MessengerEvents.EditMessage, (e) =\u003e store.dispatch('conversations/onMessageEdited', e));\n\n    // Listen to deleted messages\n    MessengerService.messenger.on(VoxImplant.Messaging.MessengerEvents.RemoveMessage, (e) =\u003e store.dispatch('conversations/onMessageDeleted', e));\n\n    // Listen to markAsRead message\n    MessengerService.messenger.on(VoxImplant.Messaging.MessengerEvents.Read, (e) =\u003e store.dispatch('conversations/onMessageMarkAsRead', e));\n\n    // Listen to typing event\n    MessengerService.messenger.on(VoxImplant.Messaging.MessengerEvents.Typing, (e) =\u003e store.dispatch('conversations/onNotifyTyping', e));\n  }\n```\n----\n\u003eiOS client (**VoximplantService.swift**):\n\nWe’ve added delegate on VoximplantService initialization step:\n```swift\nself.messenger.addDelegate(self)\n```\n\nSend messages and receive didSendMessage events:\n```swift\n// Note that if a completion block is specified in a method call,\n// the didSendMessage event won’t be triggered\nfunc sendMessage(\n    with text: String,\n    in conversation: VIConversation,\n    completion: @escaping (Result\u003cVIMessageEvent, Error\u003e) -\u003e Void\n) {\n    conversation.sendMessage(text, payload: nil, completion:\n        VIMessengerCompletion\u003cVIMessageEvent\u003e (\n            success: { messageEvent in\n                completion(.success(messageEvent))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n\nfunc messenger(_ messenger: VIMessenger,\n               didSendMessage event: VIMessageEvent) {\n    delegate?.didReceive(messageEvent: event)\n}\n```\n\n----\n\u003eAndroid client (**VoximplantService.kt**):\n\nWe’ve added listener on VoximplantService initialization step:\n```kotlin\nthis.messenger.addMessengerListener(this)\n```\nSend messages and receive onSendMessage events:\n```kotlin\n// Note that if a completion block is specified in a method call,\n// the onSendMessage event won’t be triggered\nfun sendMessage(\n    text: String,\n    conversation: IConversation,\n    completion: (Result\u003cIMessageEvent\u003e) -\u003e Unit\n) {\n    conversation.sendMessage(\n        text,\n        null,\n        object : IMessengerCompletionHandler\u003cIMessageEvent\u003e {\n            override fun onSuccess(messageEvent: IMessageEvent) {\n                completion(success(messageEvent))\n            }\n            override fun onError(errorEvent: IErrorEvent) {\n                completion(failure(buildError(errorEvent)))\n            }\n        }\n    )\n}\n\noverride fun onSendMessage(event: IMessageEvent) {\n    listener?.onMessageEvent(event)\n}\n```\n\n### Edit/remove messages\nTo track changes of other messages, you have to subscribe to the **EditMessage** / **didEditMessage** and **RemoveMessage** / **didRemoveMessage** events as has been shown above.\n\nTo edit your messages, use the appropriate method:\n\n\u003eWeb client (**actionMessages.ts**)\n```typescript\neditMessage: (context, newData) =\u003e {\n    newData.message.text = newData.newText;\n    MessengerService.get().updateMessage(newData.message)\n      .catch(logError);\n  },\n```\n----\n\u003eiOS client (**VoximplantService.swift**):\n\nEdit messages and receive didEditMessage events:\n```swift\n// Note that if a completion block is specified in a method call,\n// the didEditMessage event won’t be triggered:\nfunc edit(\n    message: VIMessage,\n    with text: String,\n    completion: @escaping (Result\u003cVIMessageEvent, Error\u003e) -\u003e Void\n) {\n    message.update(text, payload: nil, completion:\n        VIMessengerCompletion\u003cVIMessageEvent\u003e (\n            success: { messageEvent in\n                completion(.success(messageEvent))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n\nfunc messenger(_ messenger: VIMessenger,\n               didEditMessage event: VIMessageEvent) {\n    delegate?.didReceive(messageEvent: event)\n}\n```\n----\n\u003eAndroid client (**VoximplantService.kt**):\n\nEdit messages and receive onEditMessage events:\n```kotlin\n// Note that if a completion block is specified in a method call,\n// the onEditMessage event won’t be triggered:\nfun editMessage(\n    message: IMessage,\n    text: String,\n    completion: (Result\u003cIMessageEvent\u003e) -\u003e Unit\n) {\n    message.update(\n        text,\n        null,\n        object : IMessengerCompletionHandler\u003cIMessageEvent\u003e {\n            override fun onSuccess(messageEvent: IMessageEvent) {\n                completion(success(messageEvent))\n            }\n            override fun onError(errorEvent: IErrorEvent) {\n                completion(failure(buildError(errorEvent)))\n            }\n        }\n    )\n}\n\noverride fun onEditMessage(event: IMessageEvent) {\n    listener?.onMessageEvent(event)\n}\n```\n\nTo remove your messages, use the following method:\n\n\u003eWeb client (**actionMessages.ts**)\n\n```typescript\ndeleteMessage: (context, message) =\u003e {\n    MessengerService.get().removeMessage(message)\n      .catch(logError);\n  },\n```\n----\n\u003eiOS client (**VoximplantService.swift**):\n\nRemove messages and receive didRemoveMessage events:\n```swift\n// Note that if a completion block is specified in a method call,\n// the didRemoveMessage event won’t be triggered:\nfunc remove(\n    message: VIMessage,\n    completion: @escaping (Result\u003cVIMessageEvent, Error\u003e) -\u003e Void\n) {\n    message.remove(completion:\n        VIMessengerCompletion\u003cVIMessageEvent\u003e (\n            success: { messageEvent in\n                completion(.success(messageEvent))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n\nfunc messenger(_ messenger: VIMessenger,\n               didRemoveMessage event: VIMessageEvent) {\n    delegate?.didReceive(messageEvent: event)\n}\n```\n----\n\u003eAndroid client (**VoximplantService.kt**):\n\nRemove messages and receive onRemoveMessage events:\n```kotlin\n// Note that if a completion block is specified in a method call,\n// the onRemoveMessage event won’t be triggered:\nfun removeMessage(\n    message: IMessage,\n    completion: (Result\u003cIMessageEvent\u003e) -\u003e Unit\n) {\n    message.remove(\n        object : IMessengerCompletionHandler\u003cIMessageEvent\u003e {\n            override fun onSuccess(messageEvent: IMessageEvent) {\n                completion(success(messageEvent))\n            }\n            override fun onError(errorEvent: IErrorEvent) {\n                completion(failure(buildError(errorEvent)))\n            }\n        }\n    )\n}\n\noverride fun onRemoveMessage(event: IMessageEvent) {\n    listener?.onMessageEvent(event)\n}\n```\n\n### Retransmit events\nThere is a great variety of possible actions in a conversation and each of them triggers an appropriate event:\n- new user has been added\n- users’ permissions have been changed\n- conversation type has been changed\n- a new message has been sent to a conversation\n- and so on.\n\nIf a user is logged in to a client, each and every one of these events can be tracked by using listeners, but it won’t work if a user hasn’t been logged in. For example, the admin added a new participant to a conversation. There possibly could be a lot of other events between the adding itself and the moment when a newly added user will log in to messaging. How to ensure that the client with this user will retrieve all events that had triggered before the login? Retransmitting events is the answer.\n\nEach time when a user logs in, you have to retransmit events that triggered before – this is the way to keep users up to date. \n\nIn the Web SDK, there is a **retransmitEvents** method that returns maximum 100 events. \n\n\u003eWeb client (**messenger.service.ts**)\n\nIt accepts two required parameters, **eventFrom** and **lastEvent**, in order to specify the range of events. As each conversation object has the **lastSeq** field, it could be passed to the **lastEvent** parameter; **eventFrom** should be retrieved from the **seq** field of an event.\n\nImplement this code to enable retransmitting:\n```typescript\npublic retransmitMessageEvents(currentConversation: any, lastEvent?: number) {\n    lastEvent = lastEvent ? lastEvent : currentConversation.lastSeq;\n    const eventFrom = lastEvent - 100 \u003e 0 ? lastEvent - 100 : 1;\n    store.commit('conversations/updateLastEvent', eventFrom - 1);\n \n    return currentConversation.retransmitEvents(eventFrom, lastEvent)\n      .then(async (e) =\u003e {\n        // event handling\n      })\n      .catch(logError);\n  }\n```\n----\n\u003eiOS client (**VoximplantService.swift**):\n\nIn the iOS SDK, there are three methods, each of them returns maximum 100 events:\n**retransmitEvents\nretransmitEventsFrom\nretransmitEventsTo**\n\nIn example of **retransmitEvents**, it accepts two required parameters, **to** and **count**, in order to specify the range of events. The **to** value should be retrieved from the lastSequence property of a conversation and **count** is just a number of maximum of 100.\n\nImplement this code to enable retransmitting:\n\n```swift\nfunc requestMessengerEvents(\n    for conversation: VIConversation,\n    completion: @escaping (Result\u003c[VIMessengerEvent], Error\u003e) -\u003e Void\n) {\n    conversation.retransmitEvents(\n        to: conversation.lastSequence,\n        count: UInt(100),\n        completion: VIMessengerCompletion\u003cVIRetransmitEvent\u003e (\n            success: { retransmitEvents in\n                completion(.success(retransmitEvents.events))\n            },\n            failure: { errorEvent in\n                completion(.failure(NSError.buildError(from: errorEvent)))\n            }\n        )\n    )\n}\n```\n----\n\u003eAndroid client (**VoximplantService.kt**):\n\nIn the Android SDK, there are three methods, each of them returns maximum 100 events:\n**retransmitEvents\nretransmitEventsFrom\nretransmitEventsTo**\n\nIn example of **retransmitEventsTo**, it accepts two required parameters, **to** and **count**, in order to specify the range of events.\n\nImplement this code to enable retransmitting:\n\n```kotlin\nfun requestMessengerEvents(\n    conversation: IConversation,\n    numberOfEvents: Int,\n    sequence: Long,\n    completion: (Result\u003cList\u003cIMessengerEvent\u003e\u003e) -\u003e Unit\n) {\n    conversation.retransmitEventsTo(\n        sequence,\n        numberOfEvents,\n        object : IMessengerCompletionHandler\u003cIRetransmitEvent\u003e {\n            override fun onSuccess(retransmitEvents: IRetransmitEvent) {\n                completion(success(retransmitEvents.events))\n            }\n            override fun onError(errorEvent: IErrorEvent) {\n                completion(failure(buildError(errorEvent)))\n            }\n        }\n    )\n}\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoximplant%2Fsolutions-messaging","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvoximplant%2Fsolutions-messaging","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoximplant%2Fsolutions-messaging/lists"}