MM-48120/MM-48623 Patch plugin bundle react-dom on webapp extract (#21171)
Automatic Merge
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
43e26ccda2
Коммит
c3c81cb3d6
@@ -243,7 +243,15 @@ func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir s
|
|||||||
return New(ServerConnector(ch)).NewPluginAPI(c, manifest)
|
return New(ServerConnector(ch)).NewPluginAPI(c, manifest)
|
||||||
}
|
}
|
||||||
|
|
||||||
env, err := plugin.NewEnvironment(newAPIFunc, NewDriverImpl(ch.srv), pluginDir, webappPluginDir, ch.srv.Log(), ch.srv.GetMetrics())
|
env, err := plugin.NewEnvironment(
|
||||||
|
newAPIFunc,
|
||||||
|
NewDriverImpl(ch.srv),
|
||||||
|
pluginDir,
|
||||||
|
webappPluginDir,
|
||||||
|
*ch.cfgSvc.Config().ExperimentalSettings.PatchPluginsReactDOM,
|
||||||
|
ch.srv.Log(),
|
||||||
|
ch.srv.GetMetrics(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Error("Failed to start up plugins", mlog.Err(err))
|
mlog.Error("Failed to start up plugins", mlog.Err(err))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func setupMultiPluginAPITest(t *testing.T, pluginCodes []string, pluginManifests
|
|||||||
return app.NewPluginAPI(c, manifest)
|
return app.NewPluginAPI(c, manifest)
|
||||||
}
|
}
|
||||||
|
|
||||||
env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, app.Log(), nil)
|
env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, len(pluginCodes), len(pluginIDs))
|
require.Equal(t, len(pluginCodes), len(pluginIDs))
|
||||||
@@ -849,7 +849,7 @@ func TestPluginAPIGetPlugins(t *testing.T) {
|
|||||||
defer os.RemoveAll(pluginDir)
|
defer os.RemoveAll(pluginDir)
|
||||||
defer os.RemoveAll(webappPluginDir)
|
defer os.RemoveAll(webappPluginDir)
|
||||||
|
|
||||||
env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, th.App.Log(), nil)
|
env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pluginIDs := []string{"pluginid1", "pluginid2", "pluginid3"}
|
pluginIDs := []string{"pluginid1", "pluginid2", "pluginid3"}
|
||||||
@@ -937,7 +937,7 @@ func TestInstallPlugin(t *testing.T) {
|
|||||||
return app.NewPluginAPI(c, manifest)
|
return app.NewPluginAPI(c, manifest)
|
||||||
}
|
}
|
||||||
|
|
||||||
env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, app.Log(), nil)
|
env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
app.ch.SetPluginsEnvironment(env)
|
app.ch.SetPluginsEnvironment(env)
|
||||||
@@ -1632,7 +1632,7 @@ func TestAPIMetrics(t *testing.T) {
|
|||||||
defer os.RemoveAll(pluginDir)
|
defer os.RemoveAll(pluginDir)
|
||||||
defer os.RemoveAll(webappPluginDir)
|
defer os.RemoveAll(webappPluginDir)
|
||||||
|
|
||||||
env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, th.App.Log(), metricsMock)
|
env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
th.App.ch.SetPluginsEnvironment(env)
|
th.App.ch.SetPluginsEnvironment(env)
|
||||||
@@ -2079,7 +2079,7 @@ func TestRegisterCollectionAndTopic(t *testing.T) {
|
|||||||
return th.App.NewPluginAPI(th.Context, manifest)
|
return th.App.NewPluginAPI(th.Context, manifest)
|
||||||
}
|
}
|
||||||
|
|
||||||
env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.App.Srv()), pluginDir, webappPluginDir, th.App.Log(), nil)
|
env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.App.Srv()), pluginDir, webappPluginDir, false, th.App.Log(), nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
th.App.ch.SetPluginsEnvironment(env)
|
th.App.ch.SetPluginsEnvironment(env)
|
||||||
@@ -2179,7 +2179,7 @@ func TestPluginUploadsAPI(t *testing.T) {
|
|||||||
newPluginAPI := func(manifest *model.Manifest) plugin.API {
|
newPluginAPI := func(manifest *model.Manifest) plugin.API {
|
||||||
return th.App.NewPluginAPI(th.Context, manifest)
|
return th.App.NewPluginAPI(th.Context, manifest)
|
||||||
}
|
}
|
||||||
env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.App.Srv()), pluginDir, webappPluginDir, th.App.Log(), nil)
|
env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.App.Srv()), pluginDir, webappPluginDir, false, th.App.Log(), nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
th.App.ch.SetPluginsEnvironment(env)
|
th.App.ch.SetPluginsEnvironment(env)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, a
|
|||||||
webappPluginDir, err := os.MkdirTemp("", "")
|
webappPluginDir, err := os.MkdirTemp("", "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
env, err := plugin.NewEnvironment(apiFunc, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, app.Log(), nil)
|
env, err := plugin.NewEnvironment(apiFunc, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
app.ch.SetPluginsEnvironment(env)
|
app.ch.SetPluginsEnvironment(env)
|
||||||
@@ -1030,7 +1030,7 @@ func TestHookMetrics(t *testing.T) {
|
|||||||
defer os.RemoveAll(pluginDir)
|
defer os.RemoveAll(pluginDir)
|
||||||
defer os.RemoveAll(webappPluginDir)
|
defer os.RemoveAll(webappPluginDir)
|
||||||
|
|
||||||
env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, th.App.Log(), metricsMock)
|
env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
th.App.ch.SetPluginsEnvironment(env)
|
th.App.ch.SetPluginsEnvironment(env)
|
||||||
|
|||||||
@@ -968,6 +968,7 @@ type ExperimentalSettings struct {
|
|||||||
EnableSharedChannels *bool `access:"experimental_features"`
|
EnableSharedChannels *bool `access:"experimental_features"`
|
||||||
EnableRemoteClusterService *bool `access:"experimental_features"`
|
EnableRemoteClusterService *bool `access:"experimental_features"`
|
||||||
EnableAppBar *bool `access:"experimental_features"`
|
EnableAppBar *bool `access:"experimental_features"`
|
||||||
|
PatchPluginsReactDOM *bool `access:"experimental_features"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ExperimentalSettings) SetDefaults() {
|
func (s *ExperimentalSettings) SetDefaults() {
|
||||||
@@ -1002,6 +1003,10 @@ func (s *ExperimentalSettings) SetDefaults() {
|
|||||||
if s.EnableAppBar == nil {
|
if s.EnableAppBar == nil {
|
||||||
s.EnableAppBar = NewBool(false)
|
s.EnableAppBar = NewBool(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.PatchPluginsReactDOM == nil {
|
||||||
|
s.PatchPluginsReactDOM = NewBool(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnalyticsSettings struct {
|
type AnalyticsSettings struct {
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -57,15 +59,20 @@ type Environment struct {
|
|||||||
dbDriver Driver
|
dbDriver Driver
|
||||||
pluginDir string
|
pluginDir string
|
||||||
webappPluginDir string
|
webappPluginDir string
|
||||||
|
patchReactDOM bool
|
||||||
prepackagedPlugins []*PrepackagedPlugin
|
prepackagedPlugins []*PrepackagedPlugin
|
||||||
prepackagedPluginsLock sync.RWMutex
|
prepackagedPluginsLock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEnvironment(newAPIImpl apiImplCreatorFunc,
|
func NewEnvironment(
|
||||||
|
newAPIImpl apiImplCreatorFunc,
|
||||||
dbDriver Driver,
|
dbDriver Driver,
|
||||||
pluginDir string, webappPluginDir string,
|
pluginDir string,
|
||||||
|
webappPluginDir string,
|
||||||
|
patchReactDOM bool,
|
||||||
logger *mlog.Logger,
|
logger *mlog.Logger,
|
||||||
metrics einterfaces.MetricsInterface) (*Environment, error) {
|
metrics einterfaces.MetricsInterface,
|
||||||
|
) (*Environment, error) {
|
||||||
return &Environment{
|
return &Environment{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
metrics: metrics,
|
metrics: metrics,
|
||||||
@@ -73,6 +80,7 @@ func NewEnvironment(newAPIImpl apiImplCreatorFunc,
|
|||||||
dbDriver: dbDriver,
|
dbDriver: dbDriver,
|
||||||
pluginDir: pluginDir,
|
pluginDir: pluginDir,
|
||||||
webappPluginDir: webappPluginDir,
|
webappPluginDir: webappPluginDir,
|
||||||
|
patchReactDOM: patchReactDOM,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,6 +459,17 @@ func (env *Environment) UnpackWebappBundle(id string) (*model.Manifest, error) {
|
|||||||
return nil, errors.Wrapf(err, "unable to read webapp bundle: %v", id)
|
return nil, errors.Wrapf(err, "unable to read webapp bundle: %v", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if env.patchReactDOM {
|
||||||
|
newContents, changed := patchReactDOM(sourceBundleFileContents)
|
||||||
|
if changed {
|
||||||
|
sourceBundleFileContents = newContents
|
||||||
|
err = os.WriteFile(sourceBundleFilepath, sourceBundleFileContents, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "unable to overwrite webapp bundle: %v", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hash := fnv.New64a()
|
hash := fnv.New64a()
|
||||||
if _, err = hash.Write(sourceBundleFileContents); err != nil {
|
if _, err = hash.Write(sourceBundleFileContents); err != nil {
|
||||||
return nil, errors.Wrapf(err, "unable to generate hash for webapp bundle: %v", id)
|
return nil, errors.Wrapf(err, "unable to generate hash for webapp bundle: %v", id)
|
||||||
@@ -467,6 +486,52 @@ func (env *Environment) UnpackWebappBundle(id string) (*model.Manifest, error) {
|
|||||||
return manifest, nil
|
return manifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func patchReactDOM(initialBytes []byte) ([]byte, bool) {
|
||||||
|
if !bytes.Contains(initialBytes, []byte("react-dom.production.min.js")) {
|
||||||
|
return initialBytes, false
|
||||||
|
}
|
||||||
|
|
||||||
|
initial := string(initialBytes)
|
||||||
|
nameIndex := strings.Index(initial, "react-dom.production.min.js")
|
||||||
|
|
||||||
|
beginning := strings.LastIndex(initial[:nameIndex], "{")
|
||||||
|
var end int
|
||||||
|
|
||||||
|
argDefBeginning := strings.LastIndex(initial[:beginning], "function") + 9
|
||||||
|
argDefEnd := strings.LastIndex(initial[:beginning], ")") - 1
|
||||||
|
argsNames := strings.Split(initial[argDefBeginning:argDefEnd], ",")
|
||||||
|
if len(argsNames) != 3 {
|
||||||
|
return initialBytes, false
|
||||||
|
}
|
||||||
|
|
||||||
|
exportsArgName := strings.TrimSpace(argsNames[1])
|
||||||
|
|
||||||
|
numOpenBraces := 0
|
||||||
|
for i, c := range initial[beginning:] {
|
||||||
|
if end != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '}':
|
||||||
|
numOpenBraces--
|
||||||
|
|
||||||
|
if numOpenBraces == 0 {
|
||||||
|
end = beginning + i
|
||||||
|
}
|
||||||
|
case '{':
|
||||||
|
numOpenBraces++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beforePatch := initial[:end]
|
||||||
|
afterPatch := initial[end:]
|
||||||
|
|
||||||
|
patch := fmt.Sprintf("; Object.assign(%s, window.ReactDOM)", exportsArgName)
|
||||||
|
|
||||||
|
result := fmt.Sprintf("%s%s%s", beforePatch, patch, afterPatch)
|
||||||
|
return []byte(result), true
|
||||||
|
}
|
||||||
|
|
||||||
// HooksForPlugin returns the hooks API for the plugin with the given id.
|
// HooksForPlugin returns the hooks API for the plugin with the given id.
|
||||||
//
|
//
|
||||||
// Consider using RunMultiPluginHook instead.
|
// Consider using RunMultiPluginHook instead.
|
||||||
|
|||||||
@@ -734,6 +734,7 @@ func (ts *TelemetryService) trackConfig() {
|
|||||||
"enable_shared_channels": *cfg.ExperimentalSettings.EnableSharedChannels,
|
"enable_shared_channels": *cfg.ExperimentalSettings.EnableSharedChannels,
|
||||||
"enable_remote_cluster_service": *cfg.ExperimentalSettings.EnableRemoteClusterService && cfg.FeatureFlags.EnableRemoteClusterService,
|
"enable_remote_cluster_service": *cfg.ExperimentalSettings.EnableRemoteClusterService && cfg.FeatureFlags.EnableRemoteClusterService,
|
||||||
"enable_app_bar": *cfg.ExperimentalSettings.EnableAppBar,
|
"enable_app_bar": *cfg.ExperimentalSettings.EnableAppBar,
|
||||||
|
"patch_plugins_react_dom": *cfg.ExperimentalSettings.PatchPluginsReactDOM,
|
||||||
})
|
})
|
||||||
|
|
||||||
ts.SendTelemetry(TrackConfigAnalytics, map[string]any{
|
ts.SendTelemetry(TrackConfigAnalytics, map[string]any{
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ func initializeMocks(cfg *model.Config, cloudLicense bool) (*mocks.ServerIface,
|
|||||||
func(m *model.Manifest) plugin.API { return pluginsAPIMock },
|
func(m *model.Manifest) plugin.API { return pluginsAPIMock },
|
||||||
nil,
|
nil,
|
||||||
pluginDir, webappPluginDir,
|
pluginDir, webappPluginDir,
|
||||||
|
false,
|
||||||
logger,
|
logger,
|
||||||
nil)
|
nil)
|
||||||
serverIfaceMock.On("GetPluginsEnvironment").Return(pluginEnv, nil)
|
serverIfaceMock.On("GetPluginsEnvironment").Return(pluginEnv, nil)
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ func TestPublicFilesRequest(t *testing.T) {
|
|||||||
defer os.RemoveAll(pluginDir)
|
defer os.RemoveAll(pluginDir)
|
||||||
defer os.RemoveAll(webappPluginDir)
|
defer os.RemoveAll(webappPluginDir)
|
||||||
|
|
||||||
env, err := plugin.NewEnvironment(th.NewPluginAPI, app.NewDriverImpl(th.Server), pluginDir, webappPluginDir, th.App.Log(), nil)
|
env, err := plugin.NewEnvironment(th.NewPluginAPI, app.NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pluginID := "com.mattermost.sample"
|
pluginID := "com.mattermost.sample"
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user