Skip to content

Commit

Permalink
[MM-53433] Use JobID to differentiate bot initiated sessions (#462)
Browse files Browse the repository at this point in the history
* Use ContextID to differentiate bot initiated sessions

* ContextID --> JobID

* Bump deps
  • Loading branch information
streamer45 authored Aug 31, 2023
1 parent fe6f2e0 commit 177eded
Show file tree
Hide file tree
Showing 12 changed files with 89 additions and 37 deletions.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ require (

require (
github.com/Masterminds/semver v1.5.0
github.com/mattermost/calls-offloader v0.3.0
github.com/mattermost/calls-recorder v0.4.0
github.com/mattermost/calls-offloader v0.3.2
github.com/mattermost/calls-recorder v0.4.2
github.com/mattermost/logr/v2 v2.0.16
github.com/mattermost/mattermost/server/public v0.0.0-20230613002302-62a3ee8adcb5
github.com/mattermost/mattermost/server/v8 v8.0.0-20230622213803-fece5d5dd276
Expand Down Expand Up @@ -92,7 +92,7 @@ require (
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/exp v0.0.0-20200908183739-ae8ad444f925 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.54.0 // indirect
Expand Down
11 changes: 6 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,10 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattermost/calls-offloader v0.3.0 h1:9ygAcP/HkKH805+8nxOYhhxH3Y8SXkS+i0p+LwWzju8=
github.com/mattermost/calls-offloader v0.3.0/go.mod h1:X7T6UBd3BCnHsOYCmkg4JH7FnyDI0qupQaGxntPChNY=
github.com/mattermost/calls-recorder v0.4.0 h1:SbfS8vmCt8Re6lTgL/PO+9LovqZJw88DmHQeDHfvzgI=
github.com/mattermost/calls-recorder v0.4.0/go.mod h1:NvaEbw9B8oV2znZESocJfJ01LSKR88Ta/XCqx+U4UWI=
github.com/mattermost/calls-offloader v0.3.2 h1:Rz9pwW7XeFNcZ+/xE5wL+XEn02DCGBwcDXyymJ9O6Qc=
github.com/mattermost/calls-offloader v0.3.2/go.mod h1:RGhTfUjmZ/xtZog2xwdmPZzx+w8X8J+CPerkesR/6P0=
github.com/mattermost/calls-recorder v0.4.2 h1:cxNbgNMinSJX5ZcMZzX25LqPV/mXaWAAHOXCL4Fw72k=
github.com/mattermost/calls-recorder v0.4.2/go.mod h1:i50s8P7Wj6XWWfRnXuRcnV5bKx/ckXVD3iQk/5mR8xA=
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8=
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34=
github.com/mattermost/ldap v3.0.4+incompatible h1:SOeNnz+JNR+foQ3yHkYqijb9MLPhXN2BZP/PdX23VDU=
Expand Down Expand Up @@ -790,8 +790,9 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
Expand Down
4 changes: 2 additions & 2 deletions plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@
},
"props": {
"min_rtcd_version": "v0.10.1",
"min_offloader_version": "v0.3.0",
"calls_recorder_version": "v0.4.0"
"min_offloader_version": "v0.3.2",
"calls_recorder_version": "v0.4.2"
}
}
3 changes: 2 additions & 1 deletion server/job_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ func (s *jobService) UpdateJobRunner(runner string) error {
})
}

func (s *jobService) RunRecordingJob(callID, postID, authToken string) (string, error) {
func (s *jobService) RunRecordingJob(callID, postID, recordingID, authToken string) (string, error) {
cfg := s.ctx.getConfiguration()
if cfg == nil {
return "", fmt.Errorf("failed to get plugin configuration")
Expand All @@ -302,6 +302,7 @@ func (s *jobService) RunRecordingJob(callID, postID, authToken string) (string,
baseRecorderCfg.SiteURL = siteURL
baseRecorderCfg.CallID = callID
baseRecorderCfg.ThreadID = postID
baseRecorderCfg.RecordingID = recordingID
baseRecorderCfg.AuthToken = authToken

jobCfg := job.Config{
Expand Down
2 changes: 1 addition & 1 deletion server/recording_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (p *Plugin) handleRecordingStartAction(state *channelState, callID, userID
// We don't want to keep the lock while making the API call to the service since it
// could take a while to return. We lock again as soon as this returns.
p.unlockCall(callID)
recJobID, jobErr := p.getJobService().RunRecordingJob(callID, state.Call.PostID, p.botSession.Token)
recJobID, jobErr := p.getJobService().RunRecordingJob(callID, state.Call.PostID, recState.ID, p.botSession.Token)
state, err := p.lockCall(callID)
if err != nil {
res.Err = fmt.Errorf("failed to lock call: %w", err).Error()
Expand Down
5 changes: 4 additions & 1 deletion server/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func newUserSession(userID, channelID, connID string, rtc bool) *session {
}
}

func (p *Plugin) addUserSession(state *channelState, userID, connID, channelID string) (*channelState, error) {
func (p *Plugin) addUserSession(state *channelState, userID, connID, channelID, jobID string) (*channelState, error) {
state = state.Clone()

if state == nil {
Expand Down Expand Up @@ -121,6 +121,9 @@ func (p *Plugin) addUserSession(state *channelState, userID, connID, channelID s
// When the bot joins the call it means the recording has started.
if userID == p.getBotID() {
if state.Call.Recording != nil && state.Call.Recording.StartAt == 0 {
if state.Call.Recording.ID != jobID {
return nil, fmt.Errorf("invalid job ID for recording")
}
state.Call.Recording.StartAt = time.Now().UnixMilli()
state.Call.Recording.BotConnID = connID
} else if state.Call.Recording == nil || state.Call.Recording.StartAt > 0 {
Expand Down
40 changes: 34 additions & 6 deletions server/websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ const (
wsReconnectionTimeout = 10 * time.Second
)

type CallsClientJoinData struct {
ChannelID string
Title string
ThreadID string

// JobID is the id of the job tight to the bot connection to
// a call (e.g. recording, transcription). It's a parameter reserved to the
// Calls bot only.
JobID string
}

func (p *Plugin) publishWebSocketEvent(ev string, data map[string]interface{}, broadcast *model.WebsocketBroadcast) {
botID := p.getBotID()
// We don't want to expose to the client that the bot is in a call.
Expand Down Expand Up @@ -479,14 +490,20 @@ func (p *Plugin) handleLeave(us *session, userID, connID, channelID string) erro
return nil
}

func (p *Plugin) handleJoin(userID, connID, channelID, title, threadID string) (retErr error) {
func (p *Plugin) handleJoin(userID, connID string, joinData CallsClientJoinData) (retErr error) {
channelID := joinData.ChannelID
p.LogDebug("handleJoin", "userID", userID, "connID", connID, "channelID", channelID)

// We should go through only if the user has permissions to the requested channel
// or if the user is the Calls bot.
if !(p.isBot(userID) || p.API.HasPermissionToChannel(userID, channelID, model.PermissionCreatePost)) {
return fmt.Errorf("forbidden")
}

if userID == p.getBotID() && joinData.JobID == "" {
return fmt.Errorf("JobID should not be empty for bot connections")
}

channel, appErr := p.API.GetChannel(channelID)
if appErr != nil {
return appErr
Expand All @@ -495,8 +512,8 @@ func (p *Plugin) handleJoin(userID, connID, channelID, title, threadID string) (
return fmt.Errorf("cannot join call in archived channel")
}

if threadID != "" {
post, appErr := p.API.GetPost(threadID)
if joinData.ThreadID != "" {
post, appErr := p.API.GetPost(joinData.ThreadID)
if appErr != nil {
return appErr
}
Expand All @@ -519,7 +536,7 @@ func (p *Plugin) handleJoin(userID, connID, channelID, title, threadID string) (
return fmt.Errorf("failed to lock call: %w", err)
}

state, err := p.addUserSession(prevState, userID, connID, channel.Id)
state, err := p.addUserSession(prevState, userID, connID, channelID, joinData.JobID)
if err != nil {
p.unlockCall(channelID)
return fmt.Errorf("failed to add user session: %w", err)
Expand All @@ -541,7 +558,7 @@ func (p *Plugin) handleJoin(userID, connID, channelID, title, threadID string) (
)
}

postID, threadID, err := p.createCallStartedPost(state, userID, channelID, title, threadID)
postID, threadID, err := p.createCallStartedPost(state, userID, channelID, joinData.Title, joinData.ThreadID)
if err != nil {
p.LogError(err.Error())
}
Expand Down Expand Up @@ -794,8 +811,19 @@ func (p *Plugin) WebSocketMessageHasBeenPosted(connID, userID string, req *model
// it will be an empty string.
threadID, _ := req.Data["threadID"].(string)

// JobID is optional, so if it's not present,
// it will be an empty string.
jobID, _ := req.Data["jobID"].(string)

joinData := CallsClientJoinData{
channelID,
title,
threadID,
jobID,
}

go func() {
if err := p.handleJoin(userID, connID, channelID, title, threadID); err != nil {
if err := p.handleJoin(userID, connID, joinData); err != nil {
p.LogWarn(err.Error(), "userID", userID, "connID", connID, "channelID", channelID)
p.publishWebSocketEvent(wsEventError, map[string]interface{}{
"data": err.Error(),
Expand Down
5 changes: 5 additions & 0 deletions standalone/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export function getRootID() {
return params.get('root_id') || '';
}

export function getJobID() {
const params = new URLSearchParams(window.location.search);
return params.get('job_id') || '';
}

export function getToken() {
if (!window.location.hash) {
return '';
Expand Down
19 changes: 11 additions & 8 deletions standalone/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,14 @@ import {
handleUserDismissedNotification,
} from 'plugin/websocket_handlers';
import {Reducer} from 'redux';
import {CallActions, CurrentCallData, CurrentCallDataDefault, CallsClientConfig} from 'src/types/types';
import {CallActions, CurrentCallData, CurrentCallDataDefault, CallsClientConfig, CallsClientJoinData} from 'src/types/types';

import {
getCallID,
getCallTitle,
getToken,
getRootID,
getJobID,
} from './common';
import {applyTheme} from './theme_utils';

Expand All @@ -96,9 +97,7 @@ function setBasename() {
}

function connectCall(
channelID: string,
callTitle: string,
rootID: string,
joinData: CallsClientJoinData,
clientConfig: CallsClientConfig,
wsEventHandler: (ev: WebSocketMessage<WebsocketEventData>) => void,
closeCb?: (err?: Error) => void,
Expand All @@ -118,7 +117,7 @@ function connectCall(
}
});

window.callsClient.init(channelID, callTitle, rootID).then(() => {
window.callsClient.init(joinData).then(() => {
window.callsClient?.ws?.on('event', wsEventHandler);
}).catch((err: Error) => {
logErr(err);
Expand Down Expand Up @@ -236,8 +235,12 @@ export default async function init(cfg: InitConfig) {
return;
}

const callTitle = getCallTitle();
const rootID = getRootID();
const joinData = {
channelID,
title: getCallTitle(),
threadID: getRootID(),
jobID: getJobID(),
};

// Setting the base URL if present, in case MM is running under a subpath.
if (window.basename) {
Expand Down Expand Up @@ -289,7 +292,7 @@ export default async function init(cfg: InitConfig) {
simulcast: callsConfig(store.getState()).EnableSimulcast,
};

connectCall(channelID, callTitle, rootID, clientConfig, (ev) => {
connectCall(joinData, clientConfig, (ev) => {
switch (ev.event) {
case 'hello':
store.dispatch(setServerVersion((ev.data as HelloData).server_version));
Expand Down
14 changes: 5 additions & 9 deletions webapp/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {EventEmitter} from 'events';
// @ts-ignore
import {deflate} from 'pako/lib/deflate';

import {AudioDevices, CallsClientConfig, CallsClientStats, TrackInfo} from 'src/types/types';
import {AudioDevices, CallsClientConfig, CallsClientStats, TrackInfo, CallsClientJoinData} from 'src/types/types';

import {logErr, logDebug, logWarn, logInfo} from './log';
import {getScreenStream} from './utils';
Expand Down Expand Up @@ -154,8 +154,8 @@ export default class CallsClient extends EventEmitter {
}
}

public async init(channelID: string, title?: string, rootId?: string) {
this.channelID = channelID;
public async init(joinData: CallsClientJoinData) {
this.channelID = joinData.channelID;

if (!window.isSecureContext) {
throw insecureContextErr;
Expand Down Expand Up @@ -201,17 +201,13 @@ export default class CallsClient extends EventEmitter {
if (isReconnect) {
logDebug('ws reconnect, sending reconnect msg');
ws.send('reconnect', {
channelID,
channelID: joinData.channelID,
originalConnID,
prevConnID,
});
} else {
logDebug('ws open, sending join msg');
ws.send('join', {
channelID,
title,
threadID: rootId,
});
ws.send('join', joinData);
}
});

Expand Down
6 changes: 5 additions & 1 deletion webapp/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,11 @@ export default class Plugin {
});
});

window.callsClient.init(channelID, title, rootId).catch((err: Error) => {
window.callsClient.init({
channelID,
title,
threadID: rootId,
}).catch((err: Error) => {
logErr(err);
unmountCallWidget();
store.dispatch(displayCallErrorModal(channelID, err));
Expand Down
11 changes: 11 additions & 0 deletions webapp/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ export type ChannelState = {
enabled?: boolean;
}

export type CallsClientJoinData = {
channelID: string;
title?: string;
threadID?: string;

// Calls bot only
// jobID is the id of the job tight to the bot connection to
// a call (e.g. recording, transcription).
jobID?: string;
}

export type CallsClientConfig = {
wsURL: string;
authToken?: string;
Expand Down

0 comments on commit 177eded

Please sign in to comment.