diff --git a/server/channels/api4/plugin_test.go b/server/channels/api4/plugin_test.go index dbb72e714f..0c6b869f77 100644 --- a/server/channels/api4/plugin_test.go +++ b/server/channels/api4/plugin_test.go @@ -13,6 +13,7 @@ import ( "net/http" "net/http/httptest" "os" + "path" "path/filepath" "sort" "strings" @@ -25,6 +26,7 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" + "github.com/mattermost/mattermost/server/public/plugin/utils" "github.com/mattermost/mattermost/server/v8/channels/testlib" "github.com/mattermost/mattermost/server/v8/channels/utils/fileutils" ) @@ -1980,3 +1982,56 @@ func findClusterMessages(event model.ClusterEvent, msgs []*model.ClusterMessage) } return result } + +func TestPluginWebSocketSession(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + pluginID := "com.mattermost.websocket_session_test" + + // Compile plugin + testFolder, found := fileutils.FindDir("channels/app/plugin_api_tests") + require.True(t, found, "Cannot find tests folder") + fullPath := path.Join(testFolder, "manual.test_websocket_session", "main.go") + pluginCode, err := os.ReadFile(fullPath) + require.NoError(t, err) + require.NotEmpty(t, pluginCode) + pluginDir, err := filepath.Abs(*th.App.Config().PluginSettings.Directory) + require.NoError(t, err) + backend := filepath.Join(pluginDir, pluginID, "backend.exe") + utils.CompileGo(t, string(pluginCode), backend) + os.WriteFile(filepath.Join(pluginDir, pluginID, "plugin.json"), []byte(`{"id": "`+pluginID+`", "server": {"executable": "backend.exe"}}`), 0600) + + // Activate the plugin + manifest, activated, reterr := th.App.GetPluginsEnvironment().Activate(pluginID) + require.NoError(t, reterr) + require.NotNil(t, manifest) + require.True(t, activated) + + // Connect through WebSocket and send a message + reqURL := fmt.Sprintf("ws://localhost:%d", th.Server.ListenAddr.Port) + wsc, err := model.NewWebSocketClient4(reqURL, th.Client.AuthToken) + require.NoError(t, err) + require.NotNil(t, wsc) + wsc.Listen() + defer wsc.Close() + resp := <-wsc.ResponseChannel + require.Equal(t, resp.Status, model.StatusOk) + wsc.SendMessage("custom_action", map[string]any{"value": "test"}) + + // Get session for user + sessions, _, err := th.Client.GetSessions(context.Background(), th.BasicUser.Id, "") + require.NoError(t, err) + require.NotEmpty(t, sessions) + + // Verify the session has been set correctly. Check plugin code in + // channels/app/plugin_api_tests/manual.test_websocket_session + // + // Here the MessageWillBePosted hook is used purely as a way to + // communicate with the plugin side. + hooks, err := th.App.GetPluginsEnvironment().HooksForPlugin(pluginID) + require.NoError(t, err) + require.NotNil(t, hooks) + _, sessionID := hooks.MessageWillBePosted(nil, nil) + require.Equal(t, sessions[0].Id, sessionID) +} diff --git a/server/channels/app/platform/web_conn.go b/server/channels/app/platform/web_conn.go index bbb62205e3..d3c6778afa 100644 --- a/server/channels/app/platform/web_conn.go +++ b/server/channels/app/platform/web_conn.go @@ -444,6 +444,10 @@ func (wc *WebConn) readPump() { continue } + if session := wc.GetSession(); session != nil { + clonedReq.Session.Id = session.Id + } + wc.pluginPosted <- pluginWSPostedHook{wc.GetConnectionID(), wc.UserId, clonedReq} } } diff --git a/server/channels/app/plugin_api_tests/manual.test_websocket_session/main.go b/server/channels/app/plugin_api_tests/manual.test_websocket_session/main.go new file mode 100644 index 0000000000..b9a53cbc83 --- /dev/null +++ b/server/channels/app/plugin_api_tests/manual.test_websocket_session/main.go @@ -0,0 +1,28 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package main + +import ( + "github.com/mattermost/mattermost/server/public/model" + "github.com/mattermost/mattermost/server/public/plugin" +) + +type Plugin struct { + plugin.MattermostPlugin + sessionCh chan string +} + +func (p *Plugin) MessageWillBePosted(_ *plugin.Context, _ *model.Post) (*model.Post, string) { + return nil, <-p.sessionCh +} + +func (p *Plugin) WebSocketMessageHasBeenPosted(connID, userID string, req *model.WebSocketRequest) { + p.sessionCh <- req.Session.Id +} + +func main() { + plugin.ClientMain(&Plugin{ + sessionCh: make(chan string, 1), + }) +}