Repository: mattermost/mattermost-plugin-calls
Pull request #1011: [MM-63889] Add backend version check to enable DC locking
Author: login: streamer45
Labels: name: 3: Reviews Complete
description: All reviewers have approved the pull request
Created: 2025-04-24T21:11:41Z | Merged: 2025-04-28T15:04:10Z

#### Summary

We expose RTCD versioning info on top of the existing plugin version/build pair. We then use this information to detect whether we can safely use the new DC locking functionality.

A test plan for both backward and forward compatibility is attached to the JIRA ticket.

#### Ticket Link

https://mattermost.atlassian.net/browse/MM-63889




Diff:
diff --git a/server/api.go b/server/api.go
index a2353e3a2..6c0784681 100644
--- a/server/api.go
+++ b/server/api.go
@@ -26,10 +26,16 @@ import (
 const requestBodyMaxSizeBytes = 1024 * 1024 // 1MB
 
 func (p *Plugin) handleGetVersion(w http.ResponseWriter, _ *http.Request) {
-	info := map[string]interface{}{
-		"version": manifest.Version,
-		"build":   buildHash,
+	p.mut.RLock()
+	defer p.mut.RUnlock()
+
+	info := public.VersionInfo{
+		Version:     manifest.Version,
+		Build:       buildHash,
+		RTCDVersion: p.rtcdVersionInfo.BuildVersion,
+		RTCDBuild:   p.rtcdVersionInfo.BuildHash,
 	}
+
 	w.Header().Set("Content-Type", "application/json")
 	if err := json.NewEncoder(w).Encode(info); err != nil {
 		p.LogError(err.Error())
diff --git a/server/plugin.go b/server/plugin.go
index 360738873..b582cbff6 100644
--- a/server/plugin.go
+++ b/server/plugin.go
@@ -19,6 +19,7 @@ import (
 	"github.com/mattermost/mattermost-plugin-calls/server/interfaces"
 	"github.com/mattermost/mattermost-plugin-calls/server/telemetry"
 
+	rtcd "github.com/mattermost/rtcd/service"
 	"github.com/mattermost/rtcd/service/rtc"
 
 	"github.com/mattermost/mattermost/server/public/model"
@@ -55,8 +56,9 @@ type Plugin struct {
 	clusterEvCh chan model.PluginClusterEvent
 	sessions    map[string]*session
 
-	rtcServer   *rtc.Server
-	rtcdManager *rtcdClientManager
+	rtcServer       *rtc.Server
+	rtcdManager     *rtcdClientManager
+	rtcdVersionInfo rtcd.VersionInfo
 
 	jobService *jobService
 
diff --git a/server/public/version.go b/server/public/version.go
new file mode 100644
index 000000000..61bdccee1
--- /dev/null
+++ b/server/public/version.go
@@ -0,0 +1,11 @@
+// Copyright (c) 2020-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package public
+
+type VersionInfo struct {
+	Version     string `json:"version"`
+	Build       string `json:"build"`
+	RTCDVersion string `json:"rtcd_version,omitempty"`
+	RTCDBuild   string `json:"rtcd_build,omitempty"`
+}
diff --git a/server/rtcd.go b/server/rtcd.go
index 10734b168..4d8f5a81f 100644
--- a/server/rtcd.go
+++ b/server/rtcd.go
@@ -343,6 +343,10 @@ func (m *rtcdClientManager) versionCheck(client *rtcd.Client) error {
 		return fmt.Errorf("failed to get rtcd version info: %w", err)
 	}
 
+	m.ctx.mut.Lock()
+	m.ctx.rtcdVersionInfo = info
+	m.ctx.mut.Unlock()
+
 	minRTCDVersion, ok := manifest.Props["min_rtcd_version"].(string)
 	if !ok {
 		return fmt.Errorf("failed to get min_rtcd_version from manifest")
@@ -408,6 +412,10 @@ func (m *rtcdClientManager) newRTCDClient(rtcdURL, host string, dialFn rtcd.Dial
 
 		m.ctx.LogDebug("reconnection successful, replacing client")
 
+		if versionErr := m.versionCheck(client); versionErr != nil {
+			m.ctx.LogError("version compatibility check failed", "err", versionErr.Error())
+		}
+
 		if err = m.removeHost(host); err != nil {
 			m.ctx.LogError("failed to remove rtcd client: %w", err)
 		}
diff --git a/standalone/package-lock.json b/standalone/package-lock.json
index f4091e0f4..7126975f2 100644
--- a/standalone/package-lock.json
+++ b/standalone/package-lock.json
@@ -5,7 +5,7 @@
   "packages": {
     "": {
       "dependencies": {
-        "@mattermost/calls-common": "github:mattermost/calls-common#fe9b2e74328facc46c2d9e3729ec3a9704d7c618",
+        "@mattermost/calls-common": "github:mattermost/calls-common#02b04117fcec88f158d3d9ba62546d8d942ed647",
         "@mattermost/compass-icons": "0.1.31",
         "@msgpack/msgpack": "2.7.1",
         "bootstrap": "3.4.1",
@@ -3089,11 +3089,12 @@
     },
     "node_modules/@mattermost/calls-common": {
       "version": "0.27.2",
-      "resolved": "git+ssh://git@github.com/mattermost/calls-common.git#fe9b2e74328facc46c2d9e3729ec3a9704d7c618",
-      "integrity": "sha512-Wj2fclEcPVWYtX7WdrMOaLHXS2AP3y2x8Py9FqHyhoNNY9yUg+ihocUKDKpRwqfu1qM/R02Y26d/g6Mc4+yddg==",
+      "resolved": "git+ssh://git@github.com/mattermost/calls-common.git#02b04117fcec88f158d3d9ba62546d8d942ed647",
+      "integrity": "sha512-jHlw8b8k0LrOyAzh/6cSnTg3GHL3yJEZhtNhjvvfEnmxso2CzproEX7juzATtMy0Z1NXOLgdBrn/M0naPxJqgA==",
       "dependencies": {
         "@msgpack/msgpack": "3.0.0-beta2",
-        "fflate": "0.8.2"
+        "fflate": "0.8.2",
+        "semver": "7.7.1"
       }
     },
     "node_modules/@mattermost/calls-common/node_modules/@msgpack/msgpack": {
@@ -3104,6 +3105,18 @@
         "node": ">= 14"
       }
     },
+    "node_modules/@mattermost/calls-common/node_modules/semver": {
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+      "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/@mattermost/client": {
       "resolved": "../webapp/mattermost-webapp/webapp/platform/client",
       "link": true
diff --git a/standalone/package.json b/standalone/package.json
index 1777708e2..444a326b6 100644
--- a/standalone/package.json
+++ b/standalone/package.json
@@ -18,7 +18,7 @@
     "extract": "formatjs extract 'src/**/*.{ts,tsx}' --ignore 'src/**/*.d.ts' --out-file i18n/temp.json --id-interpolation-pattern '[sha512:contenthash:base64:6]' && formatjs compile 'i18n/temp.json' --out-file i18n/en.json && rm i18n/temp.json"
   },
   "dependencies": {
-    "@mattermost/calls-common": "github:mattermost/calls-common#fe9b2e74328facc46c2d9e3729ec3a9704d7c618",
+    "@mattermost/calls-common": "github:mattermost/calls-common#02b04117fcec88f158d3d9ba62546d8d942ed647",
     "@mattermost/compass-icons": "0.1.31",
     "@msgpack/msgpack": "2.7.1",
     "bootstrap": "3.4.1",
diff --git a/standalone/src/init.ts b/standalone/src/init.ts
index 48cbe270d..f8d183668 100644
--- a/standalone/src/init.ts
+++ b/standalone/src/init.ts
@@ -31,6 +31,7 @@ import {
     UserVoiceOnOffData,
     WebsocketEventData,
 } from '@mattermost/calls-common/lib/types';
+import {hasDCSignalingLockSupport} from '@mattermost/calls-common/lib/utils';
 import {WebSocketMessage} from '@mattermost/client/websocket';
 import type {DesktopAPI} from '@mattermost/desktop-api';
 import {setServerVersion} from 'mattermost-redux/actions/general';
@@ -40,7 +41,7 @@ import {getConfig} from 'mattermost-redux/selectors/entities/general';
 import {getTheme, Theme} from 'mattermost-redux/selectors/entities/preferences';
 import configureStore from 'mattermost-redux/store';
 import {ActionFuncAsync} from 'mattermost-redux/types/actions';
-import {getCallActive, getCallsConfig, localSessionClose, setClientConnecting} from 'plugin/actions';
+import {getCallActive, getCallsConfig, getCallsVersionInfo, localSessionClose, setClientConnecting} from 'plugin/actions';
 import CallsClient from 'plugin/client';
 import {
     logDebug,
@@ -49,7 +50,7 @@ import {
 import {pluginId} from 'plugin/manifest';
 import reducer from 'plugin/reducers';
 import RestClient from 'plugin/rest_client';
-import {callsConfig, iceServers, needsTURNCredentials} from 'plugin/selectors';
+import {callsConfig, callsVersionInfo, iceServers, needsTURNCredentials} from 'plugin/selectors';
 import {DesktopNotificationArgs, Store, WebAppUtils} from 'plugin/types/mattermost-webapp';
 import {
     getPluginPath,
@@ -213,6 +214,7 @@ export default async function init(cfg: InitConfig) {
     try {
         [, active] = await Promise.all([
             store.dispatch(getCallsConfig()),
+            store.dispatch(getCallsVersionInfo()),
             getCallActive(channelID),
         ]);
     } catch (err) {
@@ -236,6 +238,7 @@ export default async function init(cfg: InitConfig) {
         simulcast: callsConfig(store.getState()).EnableSimulcast,
         enableAV1: callsConfig(store.getState()).EnableAV1,
         dcSignaling: callsConfig(store.getState()).EnableDCSignaling,
+        dcLocking: hasDCSignalingLockSupport(callsVersionInfo(store.getState())),
     };
 
     connectCall(joinData, clientConfig, (ev) => {
diff --git a/webapp/package-lock.json b/webapp/package-lock.json
index 0e1ab407b..05d0f3967 100644
--- a/webapp/package-lock.json
+++ b/webapp/package-lock.json
@@ -7,7 +7,7 @@
       "hasInstallScript": true,
       "dependencies": {
         "@floating-ui/react": "0.26.12",
-        "@mattermost/calls-common": "github:mattermost/calls-common#fe9b2e74328facc46c2d9e3729ec3a9704d7c618",
+        "@mattermost/calls-common": "github:mattermost/calls-common#02b04117fcec88f158d3d9ba62546d8d942ed647",
         "@msgpack/msgpack": "2.7.1",
         "@redux-devtools/extension": "3.2.3",
         "core-js": "3.26.1",
@@ -8240,11 +8240,12 @@
     },
     "node_modules/@mattermost/calls-common": {
       "version": "0.27.2",
-      "resolved": "git+ssh://git@github.com/mattermost/calls-common.git#fe9b2e74328facc46c2d9e3729ec3a9704d7c618",
-      "integrity": "sha512-Wj2fclEcPVWYtX7WdrMOaLHXS2AP3y2x8Py9FqHyhoNNY9yUg+ihocUKDKpRwqfu1qM/R02Y26d/g6Mc4+yddg==",
+      "resolved": "git+ssh://git@github.com/mattermost/calls-common.git#02b04117fcec88f158d3d9ba62546d8d942ed647",
+      "integrity": "sha512-jHlw8b8k0LrOyAzh/6cSnTg3GHL3yJEZhtNhjvvfEnmxso2CzproEX7juzATtMy0Z1NXOLgdBrn/M0naPxJqgA==",
       "dependencies": {
         "@msgpack/msgpack": "3.0.0-beta2",
-        "fflate": "0.8.2"
+        "fflate": "0.8.2",
+        "semver": "7.7.1"
       }
     },
     "node_modules/@mattermost/calls-common/node_modules/@msgpack/msgpack": {
@@ -8255,6 +8256,18 @@
         "node": ">= 14"
       }
     },
+    "node_modules/@mattermost/calls-common/node_modules/semver": {
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+      "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/@mattermost/client": {
       "resolved": "mattermost-webapp/webapp/platform/client",
       "link": true
diff --git a/webapp/package.json b/webapp/package.json
index 66ae294ad..cc40170b2 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -20,7 +20,7 @@
   },
   "dependencies": {
     "@floating-ui/react": "0.26.12",
-    "@mattermost/calls-common": "github:mattermost/calls-common#fe9b2e74328facc46c2d9e3729ec3a9704d7c618",
+    "@mattermost/calls-common": "github:mattermost/calls-common#02b04117fcec88f158d3d9ba62546d8d942ed647",
     "@msgpack/msgpack": "2.7.1",
     "@redux-devtools/extension": "3.2.3",
     "core-js": "3.26.1",
diff --git a/webapp/src/action_types.ts b/webapp/src/action_types.ts
index cd612ff24..8cd109201 100644
--- a/webapp/src/action_types.ts
+++ b/webapp/src/action_types.ts
@@ -46,6 +46,7 @@ export const DID_NOTIFY_FOR_CALL = pluginId + '_did_notify_for_call';
 export const CALL_END = pluginId + '_call_end';
 
 export const RECEIVED_CALLS_CONFIG = pluginId + '_received_calls_config';
+export const RECEIVED_CALLS_VERSION_INFO = pluginId + '_received_calls_version_info';
 export const RECORDINGS_ENABLED = pluginId + '_recordings_enabled';
 export const TRANSCRIPTIONS_ENABLED = pluginId + '_transcriptions_enabled';
 export const LIVE_CAPTIONS_ENABLED = pluginId + '_live_captions_enabled';
diff --git a/webapp/src/actions.ts b/webapp/src/actions.ts
index 9ba9c072d..7dae6dd5f 100644
--- a/webapp/src/actions.ts
+++ b/webapp/src/actions.ts
@@ -2,7 +2,7 @@
 // See LICENSE.txt for license information.
 
 /* eslint-disable max-lines */
-import {CallsConfig, CallState} from '@mattermost/calls-common/lib/types';
+import {CallsConfig, CallState, CallsVersionInfo} from '@mattermost/calls-common/lib/types';
 import {ClientError} from '@mattermost/client';
 import {Channel} from '@mattermost/types/channels';
 import {UserTypes} from 'mattermost-redux/action_types';
@@ -65,6 +65,7 @@ import {
     LIVE_CAPTIONS_ENABLED,
     LOCAL_SESSION_CLOSE,
     RECEIVED_CALLS_CONFIG,
+    RECEIVED_CALLS_VERSION_INFO,
     RECORDINGS_ENABLED,
     REMOVE_INCOMING_CALL,
     RINGING_FOR_CALL,
@@ -128,6 +129,16 @@ export const getCallsConfig = (): ActionFuncAsync<CallsConfig> => {
     });
 };
 
+export const getCallsVersionInfo = (): ActionFuncAsync<CallsVersionInfo> => {
+    return bindClientFunc({
+        clientFunc: () => RestClient.fetch<CallsVersionInfo>(
+            `${getPluginPath()}/version`,
+            {method: 'get'},
+        ),
+        onSuccess: [RECEIVED_CALLS_VERSION_INFO],
+    });
+};
+
 export const getCallActive = async (channelID: string) => {
     try {
         const res = await RestClient.fetch<{ active: boolean }>(
diff --git a/webapp/src/client.ts b/webapp/src/client.ts
index 8f328132b..1e992f0e6 100644
--- a/webapp/src/client.ts
+++ b/webapp/src/client.ts
@@ -348,6 +348,7 @@ export default class CallsClient extends EventEmitter {
                 },
                 simulcast: this.config.simulcast,
                 dcSignaling: this.config.dcSignaling,
+                dcLocking: this.config.dcLocking,
             });
 
             this.peer = peer;
diff --git a/webapp/src/index.tsx b/webapp/src/index.tsx
index 749dfb68a..121f0acf6 100644
--- a/webapp/src/index.tsx
+++ b/webapp/src/index.tsx
@@ -3,6 +3,7 @@
 
 /* eslint-disable max-lines */
 import {CallChannelState} from '@mattermost/calls-common/lib/types';
+import {hasDCSignalingLockSupport} from '@mattermost/calls-common/lib/utils';
 import WebSocketClient from '@mattermost/client/websocket';
 import type {DesktopAPI} from '@mattermost/desktop-api';
 import {PluginAnalyticsRow} from '@mattermost/types/admin';
@@ -25,6 +26,7 @@ import {
     displayFreeTrial,
     getCallsConfig,
     getCallsStats,
+    getCallsVersionInfo,
     incomingCallOnChannel,
     loadProfilesByIdsIfMissing,
     localSessionClose,
@@ -132,6 +134,7 @@ import {
     callsExplicitlyDisabled,
     callsExplicitlyEnabled,
     callStartAtForCallInChannel,
+    callsVersionInfo,
     channelHasCall,
     channelIDForCurrentCall,
     defaultEnabled,
@@ -665,6 +668,7 @@ export default class Plugin {
                     simulcast: callsConfig(state).EnableSimulcast,
                     enableAV1: callsConfig(state).EnableAV1,
                     dcSignaling: callsConfig(state).EnableDCSignaling,
+                    dcLocking: hasDCSignalingLockSupport(callsVersionInfo(state)),
                 });
                 window.currentCallData = CurrentCallDataDefault;
 
@@ -944,7 +948,7 @@ export default class Plugin {
 
             unsubscribeActivateListener();
 
-            await store.dispatch(getCallsConfig());
+            await Promise.all([store.dispatch(getCallsConfig()), store.dispatch(getCallsVersionInfo())]);
 
             // We don't care about fetching other calls states in pop out.
             // Current call state will be requested over websocket
diff --git a/webapp/src/reducers.ts b/webapp/src/reducers.ts
index b307ca175..b86ee6c8a 100644
--- a/webapp/src/reducers.ts
+++ b/webapp/src/reducers.ts
@@ -2,7 +2,7 @@
 // See LICENSE.txt for license information.
 
 /* eslint-disable max-lines */
-import {CallJobState, CallsConfig, LiveCaption, Reaction, UserSessionState} from '@mattermost/calls-common/lib/types';
+import {CallJobState, CallsConfig, CallsVersionInfo, LiveCaption, Reaction, UserSessionState} from '@mattermost/calls-common/lib/types';
 import {combineReducers} from 'redux';
 import {MAX_NUM_REACTIONS_IN_REACTION_STREAM} from 'src/constants';
 import {
@@ -41,6 +41,7 @@ import {
     LOCAL_SESSION_CLOSE,
     RECEIVED_CALLS_CONFIG,
     RECEIVED_CALLS_USER_PREFERENCES,
+    RECEIVED_CALLS_VERSION_INFO,
     RECEIVED_CHANNEL_STATE,
     RECORDINGS_ENABLED,
     REMOVE_INCOMING_CALL,
@@ -783,6 +784,15 @@ const callsConfig = (state = CallsConfigDefault, action: { type: string, data: C
     }
 };
 
+const callsVersionInfo = (state = {}, action: { type: string, data: CallsVersionInfo }) => {
+    switch (action.type) {
+    case RECEIVED_CALLS_VERSION_INFO:
+        return action.data;
+    default:
+        return state;
+    }
+};
+
 const rtcdEnabled = (state = false, action: {type: string, data: boolean}) => {
     switch (action.type) {
     case RTCD_ENABLED:
@@ -1004,6 +1014,7 @@ export default combineReducers({
     switchCallModal,
     screenSourceModal,
     callsConfig,
+    callsVersionInfo,
     rtcdEnabled,
     callsUserPreferences,
     recordings,
diff --git a/webapp/src/selectors.ts b/webapp/src/selectors.ts
index 43e953d4c..6738dad09 100644
--- a/webapp/src/selectors.ts
+++ b/webapp/src/selectors.ts
@@ -1,7 +1,7 @@
 // Copyright (c) 2020-present Mattermost, Inc. All Rights Reserved.
 // See LICENSE.txt for license information.
 
-import {CallsConfig, Reaction, UserSessionState} from '@mattermost/calls-common/lib/types';
+import {CallsConfig, CallsVersionInfo, Reaction, UserSessionState} from '@mattermost/calls-common/lib/types';
 import {Channel} from '@mattermost/types/channels';
 import {GlobalState} from '@mattermost/types/store';
 import {Team} from '@mattermost/types/teams';
@@ -634,3 +634,6 @@ export const screenSourceModal = (state: GlobalState) => {
 export const clientConnecting = (state: GlobalState) => {
     return pluginState(state).clientConnecting;
 };
+
+export const callsVersionInfo = (state: GlobalState): CallsVersionInfo =>
+    pluginState(state).callsVersionInfo;
diff --git a/webapp/src/types/types.ts b/webapp/src/types/types.ts
index fdaa56b76..1b3520d50 100644
--- a/webapp/src/types/types.ts
+++ b/webapp/src/types/types.ts
@@ -37,6 +37,7 @@ export type CallsClientConfig = {
     simulcast?: boolean;
     enableAV1: boolean;
     dcSignaling: boolean;
+    dcLocking: boolean;
 }
 
 export type AudioDevices = {


Reviews:
user: login: cpoile
body: Nice!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-24T21:57:21Z
user: login: streamer45
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T22:00:18Z
user: login: DHaussermann
body: Tested and passed

I have tested what I believe is every applicable scenario. 
A couple tests may be redundant as RTCD being enabled may superseded what  happens at the plugin level. But this seems like a safe list of scenarios based on details provided by Claudio.   
In all cases no issues joining a call and muting / unmuting from the mobile clients. Also no issue with viewing screen share. 

_Old and current RTCD with new Calls plugin_   
- Tested latest call 1.7.0+pr with RTCD v1.0.2 with new mobile app 
- Tested latest call 1.7.0+pr with RTCD v1.1.0 with new mobile app

_Older version of calls with old and latest RTCD_
- Tested Calls 1.5.2 with RTCD v1.0.2 and new mobile app
- Tested Calls 1.5.2 with RTCD v1.1.0 and new mobile app

_Older version of calls and new with no RTCD_
- Tested Call 1.5.2 with no RTCD and new Mobile PR apps
- Tested latest call 1.7.0+pr with no RTCD and new Mobile PR apps

_Old mobile clients work with newest_   
- Tested Older mobile version (2.25) against Calls 1.7.0+pr - no RTCD
- Tested Older mobile version (2.25) against Calls 1.7.0+pr with newest RTCD V1.1.0

**Notes**:
1. In all cases "_new mobile app_" refers to the pr builds https://github.com/mattermost/mattermost-mobile/pull/8799 and **not** `2.27..0`
2. When Mobile apps were tested - I tested both Android and iOS 

@streamer45 if you see anything missing please ping and I'm happy to revisit. Thaks for all the setup help on this 🎉  

state: APPROVED
author_association: NONE
submitted_at: 2025-04-25T20:38:40Z

Review comments:
diff_hunk: @@ -37,6 +37,7 @@ export type CallsClientConfig = {
     simulcast?: boolean;
     enableAV1: boolean;
     dcSignaling: boolean;
+    dcLocking: boolean;
 }
path: webapp/src/types/types.ts
user: login: cpoile
body: I think we talked about this, but it's been awhile I guess -- shouldn't this be moved over to calls-common?  Maybe that was a TODO...?
created_at: 2025-04-24T21:57:02Z
author_association: MEMBER
diff_hunk: @@ -37,6 +37,7 @@ export type CallsClientConfig = {
     simulcast?: boolean;
     enableAV1: boolean;
     dcSignaling: boolean;
+    dcLocking: boolean;
 }
path: webapp/src/types/types.ts
user: login: streamer45
body: @cpoile This is not RTCPeer, it's the wrapper, which at this point is only used on this side.
created_at: 2025-04-24T22:00:18Z
author_association: CONTRIBUTOR

PR comments:
user: login: mm-prodsec-bot
created_at: 2025-04-24T21:13:00Z
body: ![](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg)

### :tada: **Snyk checks have passed. No issues have been found so far.**

:white_check_mark: **security/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/mattermost-core/pr-checks/d8a7969d-d9c5-48bd-84bb-fb6f8f8c23c7?refs=2c5ede4374486c3ee311563af670e12b5a86cbe1&source=prChecksComment)

:white_check_mark: **license/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/mattermost-core/pr-checks/d8a7969d-d9c5-48bd-84bb-fb6f8f8c23c7/license?refs=2c5ede4374486c3ee311563af670e12b5a86cbe1&source=prChecksComment)



author_association: NONE
user: login: DHaussermann
created_at: 2025-04-25T04:44:51Z
body: @streamer45 I don't have mobile builds yet but I did start looking and the webapp cases
in the ticket.
- _Older plugin version (pre 1.7.0) against newest RTCD_  - This seemed to work fine  when exercising mute/unmute and screen share
- _Newest plugin version (i.e. with changes fixing this ticket) against older RTCD (pre 1.1.0)_ - This is not working for me.  
When I run the Calls build from this PR and RTCD v1.0.2 I can't start a call. The option in the UI becomes non-responsive. I see nothing of interest in the server logs. So I looked at the dev console and there is a relevant error.
<img width="598" alt="image" src="https://github.com/user-attachments/assets/ceb97495-fd7e-4b5b-8a44-2f277d3147aa" />

In case this was just some issue where the front end was out of date, I made sure my local was on `master` and rebuild the front end but the behavior did not change. Can you reproduce this? ☝️ or advise on if I may be hitting some type of setup issue.  
author_association: NONE
user: login: streamer45
created_at: 2025-04-25T13:59:41Z
body: @DHaussermann It looks like the client still thinks it's running against a newer version. A couple of things may be happening here:

- You haven't reloaded the page after switching RTCD version. We don't broadcast new versions dynamically. I didn't think the complexity was worth it for an edge case (e.g. upgrading/downgrading RTCD without restarting the plugin).
- You are running a dev RTCD version.
- There's a bug :p

The source of truth for deciding whether or not to use the new logic is the output of `/plugins/com.mattermost.calls/version` HTTP handler. Could you share that?
author_association: CONTRIBUTOR
user: login: DHaussermann
created_at: 2025-04-25T14:10:31Z
body: Hi @streamer45, 
- The Calls version info shows `{"version":"1.7.0+999b4a04","build":"999b4a04e1122d9b2f5315021b945503002d3c50"}`
- > "You are running a dev RTCD version."

Yes. I suppose I must be running a dev version. All I did to get this going was to checkout the `v1.0.2` from source and type `make go-run` to get it up and going. 
This is the only way I've ever setup RTCD locally. Since I'm on Mac OS, I'm not sure the pipeline accommodates me so, I use my own Go installation.
Can you provide high level guidance for how I would get a a release version running on Mac? Or another option may be to run the desired version of RTCD elsewhere and I'd be able to point my local to it. (Or I guess spin-up a `/cloud` server).  
     

author_association: NONE
user: login: streamer45
created_at: 2025-04-25T20:41:21Z
body: Amazing work @DHaussermann , thank you for covering everything and more :raised_hands: 
author_association: CONTRIBUTOR