-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpluginhookscheduler.js
135 lines (120 loc) · 5.26 KB
/
pluginhookscheduler.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
'use strict';
/**********************************************************************
* Copyright (C) 2025 BitCtrl Systems GmbH
*
* pluginhookscheduler.js
*
* @author Daniel Hammerschmidt <[email protected]>
* @author Daniel Hammerschmidt <[email protected]>
* @version 0.0.2
*********************************************************************/
const { sep: PATH_SEP } = require('node:path');
function nop() {}
// ["Immediately Invoked Function Expression" with arguments first](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)
function IIFE(/* ...args, */ iife) { return (iife = arguments[arguments.length - 1]), iife.apply(this, Array.prototype.slice.call(arguments, 0, arguments.length - 1)); };
const { mcConfig, mcPackageJson } = IIFE(function() {
const mcPath = require.main.path;
const mcPackageJson = require(mcPath + '/package.json');
const mcDataPathRel = (mcPath.replaceAll(PATH_SEP, '/').endsWith('/node_modules/meshcentral') ? '/..' : '') + '/../meshcentral-data';
const mcConfig = require(mcPath + mcDataPathRel + '/config.json');
return { mcConfig, mcPackageJson };
});
function getPluginShortName(dirname) {
return dirname.split(PATH_SEP).pop();
}
// get settings.plugins.pluginsettings[PLUGIN_SHORT_NAME] in module scope (before module.exports is called)
function getPluginConfig(pluginShortName, defaultConfigGenerator) {
const pluginsConfig = mcConfig.settings.plugins;
const pluginSettings = pluginsConfig.pluginsettings ?? (pluginsConfig.pluginsettings = {});
const config = pluginSettings[pluginShortName] ?? (pluginSettings[pluginShortName] = defaultConfigGenerator());
return config;
}
function requirePluginHooks(hooks) {
for (let hook of hooks) {
try { pluginConfig.backendhooks[hook].length; } catch (error) { error.message = `Enable ${hooks.join(', ')}! ${error.message}`; throw error; }
}
}
const PLUGIN_SHORT_NAME = getPluginShortName(__dirname);
const pluginConfig = getPluginConfig(PLUGIN_SHORT_NAME, () => ({
backendhooks: [],
webuihooks: [],
}));
// don't try-catch, check mesherrors.txt
pluginConfig.backendhooks = Object.assign({}, Object.fromEntries(pluginConfig.backendhooks));
const boundHooks = new Map();
function PluginHandler_firstHookInvocation(hookName) {
const hooks = new Set();
const plugins = new Map(Object.entries(this.plugins).filter(([_, plugin]) => (typeof plugin[hookName] === 'function')));
const schedule = (pluginConfig.backendhooks[hookName] ?? pluginConfig.backendhooks['*'] ?? [])
.filter((name) => {
if (name[0] === '#') { return false; }
if (plugins.has(name)) { return true; }
console.warn(`Scheduled plugin "${name}" is not installed or has no handler for "${hookName}".`);
return false;
})
.map((name) => ([name, plugins.get(name)]));
for (const [name, obj] of schedule) {
const hook = obj[hookName].bind(obj);
hook.pluginName = name;
hooks.add(hook);
plugins.delete(name);
};
for (const [name, obj] of plugins) {
const hook = obj[hookName].bind(obj);
hook.pluginName = name;
hooks.add(hook);
}
// called once, don't store
if (!(hookName === 'server_startup' || hookName === 'hook_setupHttpHandlers')) {
boundHooks.set(hookName, hooks);
}
return hooks;
}
function wrap_PluginHandler_callHook(hookName, ...args) {
const hooks = boundHooks.get(hookName) ?? PluginHandler_firstHookInvocation.call(this, hookName);
for (const boundHook of hooks) {
try {
boundHook(...args);
} catch (e) {
console.log("Error occurred while running plugin hook " + boundHook.pluginName + ':' + hookName, e);
}
}
}
let meshserver, webserver;
module.exports = {
[PLUGIN_SHORT_NAME]: function (pluginHandler) {
meshserver = pluginHandler.parent;
const hooks = new Set(Object.keys(pluginConfig.backendhooks).filter((name) => (name.startsWith('hook_'))));
function wrapFunctionCall(targetObject, targetFunctionName, hookNameAlias = targetFunctionName) {
function mkhook(name, fn) { return hooks.has(name) ? pluginHandler.callHook.bind(null, name) : fn; }
if (hooks.has('hook_before' + hookNameAlias) || hooks.has('hook_after' + targetFunctionName)) {
const before = mkhook('hook_before' + hookNameAlias, nop);
const target = targetObject[targetFunctionName];
const after = mkhook('hook_after' + hookNameAlias, nop);
targetObject[targetFunctionName] = function () {
before.apply(targetObject, arguments);
const result = target.apply(targetObject, arguments);
after.call(targetObject, result, ...arguments);
return result;
}
}
}
pluginHandler.callHook = wrap_PluginHandler_callHook.bind(pluginHandler);
pluginHandler.wrapFunctionCall = wrapFunctionCall;
return {
server_startup: function () {
webserver = meshserver.webserver;
wrapFunctionCall(webserver.meshAgentHandler, 'CreateMeshAgent');
wrapFunctionCall(webserver.meshRelayHandler, 'CreateMeshRelay');
wrapFunctionCall(webserver.meshRelayHandler, 'CreateLocalRelay');
wrapFunctionCall(webserver.meshUserHandler, 'CreateMeshUser');
wrapFunctionCall(meshserver, 'NotifyUserOfDeviceStateChange');
},
};
},
nop,
IIFE,
getPluginShortName,
getPluginConfig,
requirePluginHooks,
};