forked from mattermost/mattermost-webapp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
206 lines (170 loc) · 7.24 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import regeneratorRuntime from 'regenerator-runtime';
import {Client4} from 'mattermost-redux/client';
import store from 'stores/redux_store.jsx';
import {ActionTypes} from 'utils/constants.jsx';
import {getSiteURL} from 'utils/url';
import PluginRegistry from 'plugins/registry';
import {unregisterAllPluginWebSocketEvents, unregisterPluginReconnectHandler} from 'actions/websocket_actions.jsx';
import {unregisterPluginTranslationsSource} from 'actions/views/root';
import {unregisterAdminConsolePlugin} from 'actions/admin_actions';
import {removeWebappPlugin} from './actions';
// Plugins may have been compiled with the regenerator runtime. Ensure this remains available
// as a global export even though the webapp does not depend on same.
window.regeneratorRuntime = regeneratorRuntime;
// plugins records all active web app plugins by id.
window.plugins = {};
// registerPlugin, on the global window object, should be invoked by a plugin's web app bundle as
// it is loaded.
//
// During the beta, plugins manipulated the global window.plugins data structure directly. This
// remains possible, but is officially deprecated and may be removed in a future release.
function registerPlugin(id, plugin) {
window.plugins[id] = plugin;
}
window.registerPlugin = registerPlugin;
// initializePlugins queries the server for all enabled plugins and loads each in turn.
export async function initializePlugins() {
if (store.getState().entities.general.config.PluginsEnabled !== 'true') {
return;
}
const {data, error} = await getPlugins()(store.dispatch);
if (error) {
console.error(error); //eslint-disable-line no-console
return;
}
if (data == null || data.length === 0) {
return;
}
await Promise.all(data.map((m) => {
return loadPlugin(m).catch((loadErr) => {
console.error(loadErr.message); //eslint-disable-line no-console
});
}));
}
// getPlugins queries the server for all enabled plugins
export function getPlugins() {
return async (dispatch) => {
let plugins;
try {
plugins = await Client4.getWebappPlugins();
} catch (error) {
return {error};
}
dispatch({type: ActionTypes.RECEIVED_WEBAPP_PLUGINS, data: plugins});
return {data: plugins};
};
}
// loadedPlugins tracks which plugins have been added as script tags to the page
const loadedPlugins = {};
// describePlugin takes a manifest and spits out a string suitable for console.log messages.
const describePlugin = (manifest) => (
'plugin ' + manifest.id + ', version ' + manifest.version
);
// loadPlugin fetches the web app bundle described by the given manifest, waits for the bundle to
// load, and then ensures the plugin has been initialized.
export function loadPlugin(manifest) {
return new Promise((resolve, reject) => {
// Don't load it again if previously loaded
const oldManifest = loadedPlugins[manifest.id];
if (oldManifest && oldManifest.webapp.bundle_path === manifest.webapp.bundle_path) {
resolve();
return;
}
if (oldManifest) {
// upgrading, perform cleanup
store.dispatch(removeWebappPlugin(manifest));
}
function onLoad() {
initializePlugin(manifest);
console.log('Loaded ' + describePlugin(manifest)); //eslint-disable-line no-console
resolve();
}
function onError() {
reject(new Error('Unable to load bundle for ' + describePlugin(manifest)));
}
// Backwards compatibility for old plugins
let bundlePath = manifest.webapp.bundle_path;
if (bundlePath.includes('/static/') && !bundlePath.includes('/static/plugins/')) {
bundlePath = bundlePath.replace('/static/', '/static/plugins/');
}
console.log('Loading ' + describePlugin(manifest)); //eslint-disable-line no-console
const script = document.createElement('script');
script.id = 'plugin_' + manifest.id;
script.type = 'text/javascript';
script.src = getSiteURL() + bundlePath;
script.onload = onLoad;
script.onerror = onError;
document.getElementsByTagName('head')[0].appendChild(script);
loadedPlugins[manifest.id] = manifest;
});
}
// initializePlugin creates a registry specific to the plugin and invokes any initialize function
// on the registered plugin class.
function initializePlugin(manifest) {
// Initialize the plugin
const plugin = window.plugins[manifest.id];
const registry = new PluginRegistry(manifest.id);
if (plugin && plugin.initialize) {
plugin.initialize(registry, store);
}
}
// removePlugin triggers any uninitialize callback on the registered plugin, unregisters any
// event handlers, and removes the plugin script from the DOM entirely. The plugin is responsible
// for removing any of its registered components.
export function removePlugin(manifest) {
if (!loadedPlugins[manifest.id]) {
return;
}
console.log('Removing ' + describePlugin(manifest)); //eslint-disable-line no-console
delete loadedPlugins[manifest.id];
store.dispatch(removeWebappPlugin(manifest));
const plugin = window.plugins[manifest.id];
if (plugin && plugin.uninitialize) {
plugin.uninitialize();
// Support the deprecated deinitialize callback from the plugins beta.
} else if (plugin && plugin.deinitialize) {
plugin.deinitialize();
}
unregisterAllPluginWebSocketEvents(manifest.id);
unregisterPluginReconnectHandler(manifest.id);
store.dispatch(unregisterAdminConsolePlugin(manifest.id));
unregisterPluginTranslationsSource(manifest.id);
const script = document.getElementById('plugin_' + manifest.id);
if (!script) {
return;
}
script.parentNode.removeChild(script);
console.log('Removed ' + describePlugin(manifest)); //eslint-disable-line no-console
}
// loadPluginsIfNecessary synchronizes the current state of loaded plugins with that of the server,
// loading any newly added plugins and unloading any removed ones.
export async function loadPluginsIfNecessary() {
if (store.getState().entities.general.config.PluginsEnabled !== 'true') {
return;
}
const oldManifests = store.getState().plugins.plugins;
const {error} = await getPlugins()(store.dispatch);
if (error) {
console.error(error); //eslint-disable-line no-console
return;
}
const newManifests = store.getState().plugins.plugins;
// Get new plugins and update existing plugins if version changed
Object.values(newManifests).forEach((newManifest) => {
const oldManifest = oldManifests[newManifest.id];
if (!oldManifest || oldManifest.version !== newManifest.version) {
loadPlugin(newManifest).catch((loadErr) => {
console.error(loadErr.message); //eslint-disable-line no-console
});
}
});
// Remove old plugins
Object.keys(oldManifests).forEach((id) => {
if (!newManifests.hasOwnProperty(id)) {
const oldManifest = oldManifests[id];
removePlugin(oldManifest);
}
});
}