Skip to content
Merged
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 @@ -86,6 +86,10 @@ class HeadersUtil {
}.sanitize()
}

/** Builds the X-Stream-Client header sent to the SFU, e.g. `stream-video-android-v1.30.0`. */
internal fun buildSfuSdkTrackingHeader(): String =
"stream-video-android-v${BuildConfig.STREAM_VIDEO_VERSION}"
Comment thread
PratimMallick marked this conversation as resolved.

private fun buildAppVersionForHeader() = (StreamVideo.instanceOrNull() as? StreamVideoClient)?.let { streamVideoImpl ->
"|app_version=" + (streamVideoImpl.appVersion ?: getAppVersionName())
} ?: ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.lifecycle.Lifecycle
import io.getstream.video.android.core.analytics.call.observer.SfuAnalytics
import io.getstream.video.android.core.api.SignalServerService
import io.getstream.video.android.core.call.utils.RetryableSignalingServiceDecorator
import io.getstream.video.android.core.header.HeadersUtil
import io.getstream.video.android.core.internal.network.NetworkStateProvider
import io.getstream.video.android.core.socket.common.token.ConstantTokenProvider
import io.getstream.video.android.core.socket.common.token.TokenRepository
Expand Down Expand Up @@ -53,8 +54,13 @@ internal class SfuConnectionModule(
// Internal logic
override val http: OkHttpClient = buildSfuOkHttpClient()

// Not on [http] — the socket built on it already sets its own X-Stream-Client.
private val signalHttp: OkHttpClient by lazy {
http.newBuilder().addInterceptor(SfuHeadersInterceptor(HeadersUtil())).build()
}

private val signalRetrofitClient: Retrofit by lazy {
Retrofit.Builder().client(http).addConverterFactory(WireConverterFactory.create())
Retrofit.Builder().client(signalHttp).addConverterFactory(WireConverterFactory.create())
.baseUrl("$apiUrl/").build()
}
private fun buildSfuOkHttpClient(): OkHttpClient {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.video.android.core.internal.module

import io.getstream.video.android.core.header.HeadersUtil
import okhttp3.Interceptor
import okhttp3.Response

internal class SfuHeadersInterceptor(private val headersUtil: HeadersUtil) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
.newBuilder()
.addHeader("X-Stream-Client", headersUtil.buildSfuSdkTrackingHeader())
.build()
return chain.proceed(request)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.video.android.core.internal.module

import com.google.common.truth.Truth.assertThat
import io.getstream.video.android.core.BuildConfig
import io.getstream.video.android.core.header.HeadersUtil
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import org.junit.Test

class SfuHeadersInterceptorTest {
Comment thread
aleksandar-apostolov marked this conversation as resolved.

private val twirpUrl = "https://sfu.example.com/twirp/signal.SignalServer/SendStats"

@Test
fun `identifies the SDK and its version on every twirp request`() {
val request = sendTwirpRequest()

assertThat(request.header("X-Stream-Client"))
.isEqualTo("stream-video-android-v${BuildConfig.STREAM_VIDEO_VERSION}")
}

@Test
fun `sends a single client header`() {
val request = sendTwirpRequest()

assertThat(request.headers("X-Stream-Client")).hasSize(1)
}

private fun sendTwirpRequest(): Request {
lateinit var sent: Request
val client = OkHttpClient.Builder()
.addInterceptor(SfuHeadersInterceptor(HeadersUtil()))
.addInterceptor(
Interceptor { chain ->
sent = chain.request()
Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("OK")
.body("".toResponseBody())
.build()
},
)
.build()

client.newCall(Request.Builder().url(twirpUrl).build()).execute().close()
return sent
}
}
Loading