Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class BaseWebSocketMessage(
@JsonField(name = ["id"])
override var id: String? = null,
@JsonField(name = ["type"])
var type: String? = null
) : Parcelable {
override var type: String? = null
) : BaseWebSocketMessageInterface, Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null)
constructor() : this(null, null)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Daniel Calviño Sánchez <[email protected]>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.models.json.websocket

/**
* Interface with the properties common to all websocket signaling messages.
*/
interface BaseWebSocketMessageInterface {
var id: String?
var type: String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class CallOverallWebSocketMessage(
@JsonField(name = ["id"])
override var id: String? = null,
@JsonField(name = ["type"])
var type: String? = null,
override var type: String? = null,
@JsonField(name = ["message"])
var callWebSocketMessage: CallWebSocketMessage? = null
) : Parcelable {
) : BaseWebSocketMessageInterface, Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
constructor() : this(null, null, null)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class ErrorOverallWebSocketMessage(
@JsonField(name = ["id"])
override var id: String? = null,
@JsonField(name = ["type"])
var type: String? = null,
override var type: String? = null,
@JsonField(name = ["error"])
var errorWebSocketMessage: ErrorWebSocketMessage? = null
) : Parcelable {
) : BaseWebSocketMessageInterface, Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
constructor() : this(null, null, null)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import java.util.HashMap
@JsonObject
@TypeParceler<Any, AnyParceler>
class EventOverallWebSocketMessage(
@JsonField(name = ["id"])
override var id: String? = null,
@JsonField(name = ["type"])
var type: String? = null,
override var type: String? = null,
@JsonField(name = ["event"])
var eventMap: HashMap<String, Any>? = null
) : Parcelable {
) : BaseWebSocketMessageInterface, Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
constructor() : this(null, null, null)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class HelloOverallWebSocketMessage(
@JsonField(name = ["id"])
override var id: String? = null,
@JsonField(name = ["type"])
var type: String? = null,
override var type: String? = null,
@JsonField(name = ["hello"])
var helloWebSocketMessage: HelloWebSocketMessage? = null
) : Parcelable {
) : BaseWebSocketMessageInterface, Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
constructor() : this(null, null, null)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class HelloResponseOverallWebSocketMessage(
@JsonField(name = ["id"])
override var id: String? = null,
@JsonField(name = ["type"])
var type: String? = null,
override var type: String? = null,
@JsonField(name = ["hello"])
var helloResponseWebSocketMessage: HelloResponseWebSocketMessage? = null
) : Parcelable {
) : BaseWebSocketMessageInterface, Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
constructor() : this(null, null, null)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class JoinedRoomOverallWebSocketMessage(
@JsonField(name = ["id"])
override var id: String? = null,
@JsonField(name = ["type"])
var type: String? = null,
override var type: String? = null,
@JsonField(name = ["room"])
var roomWebSocketMessage: RoomWebSocketMessage? = null
) : Parcelable {
) : BaseWebSocketMessageInterface, Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
constructor() : this(null, null, null)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import kotlinx.parcelize.Parcelize
@JsonObject
class RoomOverallWebSocketMessage(
@JsonField(name = ["type"])
var type: String? = null,
override var id: String? = null,
@JsonField(name = ["type"])
override var type: String? = null,
@JsonField(name = ["room"])
var roomWebSocketMessage: RoomWebSocketMessage? = null
) : Parcelable {
) : BaseWebSocketMessageInterface, Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
constructor() : this(null, null, null)
}
68 changes: 45 additions & 23 deletions app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.models.json.participants.Participant.ActorType
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage
import com.nextcloud.talk.models.json.signaling.settings.FederationSettings
import com.nextcloud.talk.models.json.websocket.BaseWebSocketMessageInterface
import com.nextcloud.talk.models.json.websocket.BaseWebSocketMessage
import com.nextcloud.talk.models.json.websocket.ByeWebSocketMessage
import com.nextcloud.talk.models.json.websocket.CallOverallWebSocketMessage
Expand Down Expand Up @@ -79,7 +80,9 @@ class WebSocketInstance internal constructor(
private var currentFederation: FederationSettings? = null
private var reconnecting = false
private val usersHashMap: HashMap<String?, Participant>
private var messagesQueue: MutableList<String> = ArrayList()
private var messagesQueue: MutableList<Pair<BaseWebSocketMessageInterface, ((String) -> Unit)?>> = ArrayList()
private var callbacks: MutableMap<String, (String) -> Unit> = HashMap()
private var lastCallbackId: Int = 1
private val signalingMessageReceiver = ExternalSignalingMessageReceiver()
val signalingMessageSender = ExternalSignalingMessageSender()

Expand Down Expand Up @@ -140,6 +143,7 @@ class WebSocketInstance internal constructor(

fun restartWebSocket() {
reconnecting = true
callbacks.clear()
Log.d(TAG, "restartWebSocket: $connectionUrl")
val request = Request.Builder().url(connectionUrl).build()
okHttpClient!!.newWebSocket(request, this)
Expand All @@ -149,12 +153,18 @@ class WebSocketInstance internal constructor(
if (webSocket === internalWebSocket) {
Log.d(TAG, "Receiving : $webSocket $text")
try {
val (messageType) = LoganSquare.parse(text, BaseWebSocketMessage::class.java)
val (id, messageType) = LoganSquare.parse(text, BaseWebSocketMessage::class.java)

if (callbacks.contains(id)) {
val callback = callbacks[id]!!
callbacks.remove(id)
callback(text)
}

if (messageType != null) {
when (messageType) {
"hello" -> processHelloMessage(webSocket, text)
"error" -> processErrorMessage(webSocket, text)
"room" -> processJoinedRoomMessage(text)
"event" -> processEventMessage(text)
"message" -> processMessage(text)
"bye" -> {
Expand All @@ -175,7 +185,7 @@ class WebSocketInstance internal constructor(

@Throws(IOException::class)
private fun processMessage(text: String) {
val (_, callWebSocketMessage) = LoganSquare.parse(text, CallOverallWebSocketMessage::class.java)
val (_, _, callWebSocketMessage) = LoganSquare.parse(text, CallOverallWebSocketMessage::class.java)
if (callWebSocketMessage != null) {
val ncSignalingMessage = callWebSocketMessage.ncSignalingMessage

Expand Down Expand Up @@ -284,7 +294,7 @@ class WebSocketInstance internal constructor(

@Throws(IOException::class)
private fun processJoinedRoomMessage(text: String) {
val (_, roomWebSocketMessage) = LoganSquare.parse(text, JoinedRoomOverallWebSocketMessage::class.java)
val (_, _, roomWebSocketMessage) = LoganSquare.parse(text, JoinedRoomOverallWebSocketMessage::class.java)
if (roomWebSocketMessage != null) {
currentRoomToken = roomWebSocketMessage.roomId
if (roomWebSocketMessage.roomPropertiesWebSocketMessage != null && !TextUtils.isEmpty(currentRoomToken)) {
Expand All @@ -296,7 +306,7 @@ class WebSocketInstance internal constructor(
@Throws(IOException::class)
private fun processErrorMessage(webSocket: WebSocket, text: String) {
Log.e(TAG, "Received error: $text")
val (_, message) = LoganSquare.parse(text, ErrorOverallWebSocketMessage::class.java)
val (_, _, message) = LoganSquare.parse(text, ErrorOverallWebSocketMessage::class.java)
if (message != null) {
if ("no_such_session" == message.code) {
Log.d(TAG, "WebSocket " + webSocket.hashCode() + " resumeID " + resumeId + " expired")
Expand All @@ -315,7 +325,7 @@ class WebSocketInstance internal constructor(
isConnected = true
reconnecting = false
val oldResumeId = resumeId
val (_, helloResponseWebSocketMessage1) = LoganSquare.parse(
val (_, _, helloResponseWebSocketMessage1) = LoganSquare.parse(
text,
HelloResponseOverallWebSocketMessage::class.java
)
Expand All @@ -325,7 +335,12 @@ class WebSocketInstance internal constructor(
hasMCU = helloResponseWebSocketMessage1.serverHasMCUSupport()
}
for (i in messagesQueue.indices) {
webSocket.send(messagesQueue[i])
// Safety check to ensure that it will not end in an endless loop
// trying to send the messages, queueing them, and then trying to
// send them again and again.
if (isConnected && !reconnecting) {
sendMessage(messagesQueue[i].first, messagesQueue[i].second)
}
}
messagesQueue = ArrayList()
val helloHashMap = HashMap<String, String?>()
Expand Down Expand Up @@ -379,14 +394,15 @@ class WebSocketInstance internal constructor(
Log.d(TAG, " roomToken: $roomToken")
Log.d(TAG, " session: $normalBackendSession")
try {
val message = LoganSquare.serialize(
webSocketConnectionHelper.getAssembledJoinOrLeaveRoomModel(roomToken, normalBackendSession, federation)
)
val message = webSocketConnectionHelper.getAssembledJoinOrLeaveRoomModel(roomToken, normalBackendSession, federation)
val processJoinedRoomMessageCallback = { text: String ->
processJoinedRoomMessage(text)
}
if (roomToken == "") {
Log.d(TAG, "sending 'leave room' via websocket")
currentNormalBackendSession = ""
currentFederation = null
sendMessage(message)
sendMessage(message, processJoinedRoomMessageCallback)
} else if (
roomToken == currentRoomToken &&
normalBackendSession == currentNormalBackendSession &&
Expand All @@ -399,7 +415,7 @@ class WebSocketInstance internal constructor(
Log.d(TAG, "Sending join room message via websocket")
currentNormalBackendSession = normalBackendSession
currentFederation = federation
sendMessage(message)
sendMessage(message, processJoinedRoomMessageCallback)
}
} catch (e: IOException) {
Log.e(TAG, "Failed to serialize signaling message", e)
Expand All @@ -408,27 +424,33 @@ class WebSocketInstance internal constructor(

private fun sendCallMessage(ncSignalingMessage: NCSignalingMessage) {
try {
val message = LoganSquare.serialize(
webSocketConnectionHelper.getAssembledCallMessageModel(ncSignalingMessage)
)
val message = webSocketConnectionHelper.getAssembledCallMessageModel(ncSignalingMessage)
sendMessage(message)
} catch (e: IOException) {
Log.e(TAG, "Failed to serialize signaling message", e)
}
}

private fun sendMessage(message: String) {
private fun sendMessage(message: BaseWebSocketMessageInterface, callback: ((String) -> Unit)? = null) {
if (!isConnected || reconnecting) {
messagesQueue.add(message)
messagesQueue.add(Pair(message, callback))

if (!reconnecting) {
restartWebSocket()
}
} else {
if (!internalWebSocket!!.send(message)) {
messagesQueue.add(message)
restartWebSocket()
}

return
}

if (callback != null) {
val callbackId = lastCallbackId++
callbacks[callbackId.toString()] = callback
message.id = callbackId.toString()
}

if (!internalWebSocket!!.send(LoganSquare.serialize(message))) {
messagesQueue.add(Pair(message, callback))
restartWebSocket()
}
}

Expand Down
Loading