-
-
Notifications
You must be signed in to change notification settings - Fork 44
Expand file tree
/
Copy pathnode_helper.js
More file actions
131 lines (121 loc) · 4.63 KB
/
node_helper.js
File metadata and controls
131 lines (121 loc) · 4.63 KB
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
const NodeHelper = require("node_helper")
const path = require("node:path")
const fs = require("node:fs")
module.exports = NodeHelper.create({
start() {
// MM v2.36.0+ serializes functions natively via __mmFunction but loses closure context.
// This helper restores closure variables by extracting variable preamble from config.js
// and injecting it when reconstructing functions on the frontend.
// TODO: Remove when upstream MM adds closure variable support to function reviver.
this.functionConfigs = []
this.variablePreamble = ""
this.identifierFunctions = new Map()
this.loadFunctionConfigs()
},
/**
* Extract variable declarations from config.js (everything before "let config = {").
* These variables are needed as closure context for callback functions
* that reference them (eventTransformer, eventFilter, etc.).
*
* Example:
* let myVar = {key: "value"}
* const helpers = { ... }
* let config = { modules: [...] }
*
* Returns the preamble: "let myVar = {key: \"value\"}\nconst helpers = { ... }"
*/
extractVariablePreamble() {
try {
const configPath = path.resolve(
global.root_path,
process.env.MM_CONFIG_FILE || "config/config.js"
)
const configContent = fs.readFileSync(configPath, "utf-8")
// Find where the config object starts (let/var/const config = {)
const configMatch = configContent.match(/(let|var|const)\s+config\s*=/)
if (!configMatch) {
return ""
}
const configStartIndex = configMatch.index
// Get everything before the config declaration (the variable preamble)
const preamble = configContent.substring(0, configStartIndex).trim()
return preamble
} catch (error) {
console.warn(
`[${this.name}] Could not extract variable preamble:`,
error.message
)
return ""
}
},
/**
* Read config.js on the server side where functions are preserved.
* Extract function properties from each MMM-CalendarExt3 instance
* and convert them to strings for sending to the frontend.
*
* Background: Since MagicMirror v2.35.0, config.js is no longer loaded
* as a script tag in the browser. Instead, the browser fetches the config
* via JSON (/config endpoint), which strips all function properties.
* This node_helper restores those functions by reading the original
* config.js file server-side (respecting MM_CONFIG_FILE) and sending
* the function source code to the frontend for reconstruction.
*
* Also extracts the variable preamble so that closure variables
* (e.g., myVariable in eventTransformer) are available to the functions.
*/
loadFunctionConfigs() {
const functionKeys = [
"preProcessor",
"eventTransformer",
"eventFilter",
"eventSorter",
"manipulateDateCell",
"customHeader",
"eventPayload",
"weatherPayload"
]
// Extract variable preamble once (used for all module instances)
this.variablePreamble = this.extractVariablePreamble()
try {
const configPath = path.resolve(
global.root_path,
process.env.MM_CONFIG_FILE || "config/config.js"
)
delete require.cache[require.resolve(configPath)]
const userConfig = require(configPath)
for (const mod of userConfig.modules || []) {
if (mod.module !== this.name || !mod.config) continue
const fnStrings = {}
for (const key of functionKeys) {
if (typeof mod.config[key] === "function") {
fnStrings[key] = mod.config[key].toString()
}
}
this.functionConfigs.push(fnStrings)
}
} catch (error) {
console.warn(
`[${this.name}] Could not load config functions:`,
error.message
)
}
},
socketNotificationReceived(notification, payload) {
if (notification === "CX3_REGISTER") {
const { identifier } = payload
// On first registration of a given identifier, assign the next functionConfig in order
// (MagicMirror initializes modules sequentially, preserving config.modules order).
// Subsequent registrations from the same identifier (reconnects, multi-browser) reuse
// the cached functions so they always get the correct config.
if (!this.identifierFunctions.has(identifier)) {
const index = this.identifierFunctions.size
this.identifierFunctions.set(identifier, this.functionConfigs[index] || {})
}
this.sendSocketNotification("CX3_FUNCTIONS_RESTORED", {
identifier,
variablePreamble: this.variablePreamble,
functions: this.identifierFunctions.get(identifier)
})
}
}
})