Source: extensions/sysinfo/sysinfo.js

  1. /**
  2. * StreamRoller Copyright 2023 "SilenusTA https://www.twitch.tv/olddepressedgamer"
  3. *
  4. * StreamRoller is an all in one streaming solution designed to give a single
  5. * 'second monitor' control page and allow easy integration for configuring
  6. * content (ie. tweets linked to chat, overlays triggered by messages, hue lights
  7. * controlled by donations etc)
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License as published
  11. * by the Free Software Foundation, either version 3 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU Affero General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License
  20. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  21. */
  22. /**
  23. * @extension Sysinfo
  24. * Provides information about the system(computer) that the extension is running on
  25. */
  26. // ============================================================================
  27. // IMPORTS/VARIABLES
  28. // ============================================================================
  29. import * as logger from "../../backend/data_center/modules/logger.js";
  30. import sr_api from "../../backend/data_center/public/streamroller-message-api.cjs";
  31. import si from 'systeminformation';
  32. import * as fs from "fs";
  33. import { dirname } from 'path';
  34. import { fileURLToPath } from 'url';
  35. const __dirname = dirname(fileURLToPath(import.meta.url));
  36. const localConfig = {
  37. OUR_CHANNEL: "SYSINFO_CHANNEL",
  38. EXTENSION_NAME: "sysinfo",
  39. SYSTEM_LOGGING_TAG: "[EXTENSION]",
  40. DataCenterSocket: null,
  41. MAX_CONNECTION_ATTEMPTS: 5,
  42. poll_handle_cpu_data: null,
  43. poll_handle_cpu_temperature: null,
  44. poll_handle_gpu_data: null,
  45. CPUDataTriggerUpdated: false,
  46. GPUDataTriggerUpdated: false,
  47. CPUTemperaturesTriggerUpdated: false
  48. };
  49. const default_serverConfig = {
  50. __version__: "0.1",
  51. // data used in the settings modal
  52. extensionname: localConfig.EXTENSION_NAME,
  53. channel: localConfig.OUR_CHANNEL,
  54. sysinfo_enabled: "off",
  55. sysinfo_console_log_enabled: "off",
  56. sysinfo_cpu_data_enabled: "off",
  57. sysinfo_cpu_data_poll_interval: "60",//default poll time if enabled
  58. sysinfo_cpu_temperature_enabled: "off",
  59. sysinfo_cpu_temperature_poll_interval: "10",
  60. sysinfo_gpu_data_enabled: "off",
  61. sysinfo_gpu_data_poll_interval: "60",//default poll time if enabled
  62. sysinfo_restore_defaults: "off",
  63. // data for the app
  64. temps_enabled: "off"
  65. };
  66. let serverConfig = structuredClone(default_serverConfig)
  67. const triggersandactions =
  68. {
  69. extensionname: serverConfig.extensionname,
  70. description: "SysInfo Extension can provide information about the system that StreamRoller is running on",
  71. version: "0.2",
  72. channel: serverConfig.channel,
  73. triggers:
  74. [
  75. {
  76. name: "sysInfoCPUData",
  77. displaytitle: "CPU Data",
  78. description: "Data about the CPU",
  79. messagetype: "trigger_sysinfoCPUData",
  80. parameters: {
  81. reference: "",// this will be auto or a value passed in by an action
  82. //fields will be added on the first call (values change for specific hardware)
  83. }
  84. },
  85. {
  86. name: "sysInfoCPUTemperatures",
  87. displaytitle: "CPU Temperatures",
  88. description: "CPU Temperatures if available (you may need to run StreamRoller as admin to allow windows to read this)",
  89. messagetype: "trigger_sysinfoCPUTemperatures",
  90. parameters: {
  91. reference: "",// this will be auto or a value passed in by an action
  92. //fields will be added on the first call (values change for specific hardware)
  93. }
  94. },
  95. {
  96. name: "sysInfoGPUData",
  97. displaytitle: "GPU Data",
  98. description: "GPU Data",
  99. messagetype: "trigger_sysInfoGPUData",
  100. parameters: {
  101. reference: "",// this will be auto or a value passed in by an action
  102. //fields will be added on the first call (values change for specific hardware)/
  103. }
  104. }
  105. ],
  106. actions:
  107. [
  108. {
  109. name: "sysInfoGetCPUData",
  110. displaytitle: "Get CPU Data",
  111. description: "Sends out a trigger with the CPU Data",
  112. messagetype: "action_sysInfoGetCPUData",
  113. parameters: { reference: "" }
  114. },
  115. {
  116. name: "sysInfoGetCPUTemperatures",
  117. displaytitle: "Get CPU Temperatures",
  118. description: "Sends out a trigger with the CPU Temperatures",
  119. messagetype: "action_sysInfoGetCPUTemperatures",
  120. parameters: { reference: "" }
  121. },
  122. {
  123. name: "sysInfoGetGPUData",
  124. displaytitle: "Get GPU Data",
  125. description: "Sends out a trigger with the GPU Data",
  126. messagetype: "action_sysInfoGetGPUData",
  127. parameters: { reference: "" }
  128. }
  129. ],
  130. }
  131. // ============================================================================
  132. // FUNCTION: initialise
  133. // ============================================================================
  134. /**
  135. * Starts the extension using the given data.
  136. * @param {object:Express} app
  137. * @param {string} host
  138. * @param {number} port
  139. * @param {number} heartbeat
  140. */
  141. function initialise (app, host, port, heartbeat)
  142. {
  143. // update our triggers with the systems fields
  144. getCPUData("initialise");
  145. getGPUData("initialise");
  146. getCPUTemperature("initialise");
  147. try
  148. {
  149. localConfig.DataCenterSocket = sr_api.setupConnection(onDataCenterMessage, onDataCenterConnect,
  150. onDataCenterDisconnect, host, port);
  151. } catch (err)
  152. {
  153. logger.err(localConfig.SYSTEM_LOGGING_TAG + localConfig.EXTENSION_NAME + ".initialise", "localConfig.DataCenterSocket connection failed:", err);
  154. }
  155. }
  156. // ============================================================================
  157. // FUNCTION: onDataCenterDisconnect
  158. // ============================================================================
  159. /**
  160. * Disconnection message sent from the server
  161. * @param {String} reason
  162. */
  163. function onDataCenterDisconnect (reason)
  164. {
  165. // do something here when disconnects happens if you want to handle them
  166. logger.log(localConfig.SYSTEM_LOGGING_TAG + localConfig.EXTENSION_NAME + ".onDataCenterDisconnect", reason);
  167. }
  168. // ============================================================================
  169. // FUNCTION: onDataCenterConnect
  170. // ============================================================================
  171. /**
  172. * Connection message handler
  173. * @param {*} socket
  174. */
  175. function onDataCenterConnect (socket)
  176. {
  177. logger.log(localConfig.SYSTEM_LOGGING_TAG + localConfig.EXTENSION_NAME + ".onDataCenterConnect", "Creating our channel");
  178. sr_api.sendMessage(localConfig.DataCenterSocket,
  179. sr_api.ServerPacket("RequestConfig", serverConfig.extensionname));
  180. sr_api.sendMessage(localConfig.DataCenterSocket,
  181. sr_api.ServerPacket("CreateChannel", localConfig.EXTENSION_NAME, localConfig.OUR_CHANNEL)
  182. );
  183. }
  184. // ============================================================================
  185. // FUNCTION: onDataCenterMessage
  186. // ============================================================================
  187. /**
  188. * receives message from the socket
  189. * @param {data} server_packet
  190. */
  191. function onDataCenterMessage (server_packet)
  192. {
  193. //logger.log(localConfig.SYSTEM_LOGGING_TAG + localConfig.EXTENSION_NAME + ".onDataCenterMessage", "message received ", server_packet);
  194. if (server_packet.type === "ConfigFile")
  195. {
  196. if (server_packet.data != "" && server_packet.to === serverConfig.extensionname)
  197. {
  198. if (server_packet.data.__version__ != default_serverConfig.__version__)
  199. {
  200. serverConfig = structuredClone(default_serverConfig);
  201. console.log("\x1b[31m" + serverConfig.extensionname + " ConfigFile Updated", "The config file has been Restored. Your settings may have changed" + "\x1b[0m");
  202. }
  203. else
  204. serverConfig = structuredClone(server_packet.data);
  205. SaveConfigToServer();
  206. UpdateMonitoring();
  207. }
  208. }
  209. else if (server_packet.type === "ExtensionMessage")
  210. {
  211. let extension_packet = server_packet.data;
  212. if (extension_packet.type === "RequestSettingsWidgetSmallCode")
  213. SendSettingsWidgetSmall(extension_packet.from);
  214. else if (extension_packet.type === "SettingsWidgetSmallData")
  215. {
  216. if (extension_packet.data.extensionname === serverConfig.extensionname)
  217. {
  218. if (extension_packet.data.sysinfo_restore_defaults == "on")
  219. {
  220. serverConfig = structuredClone(default_serverConfig);
  221. console.log("\x1b[31m" + serverConfig.extensionname + " ConfigFile Updated.", "The config file has been Restored. Your settings may have changed" + "\x1b[0m");
  222. }
  223. else
  224. {
  225. serverConfig.sysinfo_enabled = "off";
  226. serverConfig.sysinfo_console_log_enabled = "off";
  227. serverConfig.sysinfo_cpu_data_enabled = "off";
  228. serverConfig.sysinfo_cpu_temperature_enabled = "off";
  229. serverConfig.sysinfo_gpu_data_enabled = "off";
  230. for (const [key, value] of Object.entries(extension_packet.data))
  231. serverConfig[key] = value;
  232. }
  233. SaveConfigToServer();
  234. SendSettingsWidgetSmall("");
  235. UpdateMonitoring();
  236. }
  237. }
  238. else if (extension_packet.type === "SendTriggerAndActions")
  239. {
  240. sendTriggersAndActions(server_packet.from)
  241. }
  242. else if (extension_packet.type === "action_sysInfoGetCPUTemperatures")
  243. {
  244. console.log("action_sysInfoGetCPUTemperatures called with", extension_packet.data)
  245. getCPUTemperature(extension_packet.data.reference)
  246. }
  247. else if (extension_packet.type === "action_sysInfoGetCPUData")
  248. {
  249. console.log("action_sysInfoGetCPUData called with", extension_packet.data)
  250. getCPUData(extension_packet.data.reference)
  251. }
  252. else if (extension_packet.type === "action_sysInfoGetGPUData")
  253. {
  254. console.log("action_sysInfoGetGPUData called with", extension_packet.data)
  255. getGPUData(extension_packet.data.reference)
  256. }
  257. else
  258. logger.log(localConfig.SYSTEM_LOGGING_TAG + localConfig.EXTENSION_NAME + ".onDataCenterMessage", "received unhandled ExtensionMessage ", server_packet);
  259. }
  260. else if (server_packet.type === "ChannelData")
  261. {
  262. let extension_packet = server_packet.data;
  263. if (extension_packet.type === "HeartBeat")
  264. {
  265. //Just ignore messages we know we don't want to handle
  266. }
  267. else
  268. {
  269. logger.log(localConfig.SYSTEM_LOGGING_TAG + localConfig.EXTENSION_NAME + ".onDataCenterMessage", "received message from unhandled channel ", server_packet.dest_channel);
  270. }
  271. }
  272. else if (server_packet.type === "InvalidMessage")
  273. {
  274. logger.err(localConfig.SYSTEM_LOGGING_TAG + localConfig.EXTENSION_NAME + ".onDataCenterMessage",
  275. "InvalidMessage ", server_packet.data.error, server_packet);
  276. }
  277. else if (server_packet.type === "LoggingLevel")
  278. {
  279. logger.setLoggingLevel(server_packet.data)
  280. }
  281. else if (server_packet.type === "ChannelJoined"
  282. || server_packet.type === "ChannelCreated"
  283. || server_packet.type === "ChannelLeft"
  284. )
  285. {
  286. // just a blank handler for items we are not using to avoid message from the catchall
  287. }
  288. // ------------------------------------------------ unknown message type received -----------------------------------------------
  289. else
  290. logger.warn(localConfig.SYSTEM_LOGGING_TAG + localConfig.EXTENSION_NAME +
  291. ".onDataCenterMessage", "Unhandled message type", server_packet.type);
  292. }
  293. // ===========================================================================
  294. // FUNCTION: SendSettingsWidgetSmall
  295. // ===========================================================================
  296. /**
  297. * sends some modal code to be displayed on the admin page or somewhere else
  298. * this is done as part of the webpage request for modal message we get from
  299. * extension. It is a way of getting some user feedback via submitted forms
  300. * from a page that supports the modal system
  301. * @param {String} toextension
  302. */
  303. function SendSettingsWidgetSmall (toextension)
  304. {
  305. fs.readFile(__dirname + '/sysinfosettingswidgetsmall.html', function (err, filedata)
  306. {
  307. if (err)
  308. logger.err(localConfig.SYSTEM_LOGGING_TAG + localConfig.EXTENSION_NAME +
  309. ".SendSettingsWidgetSmall", "failed to load modal", err);
  310. else
  311. {
  312. let modalstring = filedata.toString();
  313. for (const [key, value] of Object.entries(serverConfig))
  314. {
  315. // checkboxes
  316. if (value === "on")
  317. modalstring = modalstring.replace(key + "checked", "checked");
  318. // replace text strings
  319. else if (typeof (value) == "string")
  320. modalstring = modalstring.replace(key + "text", value);
  321. }
  322. sr_api.sendMessage(localConfig.DataCenterSocket,
  323. sr_api.ServerPacket(
  324. "ExtensionMessage",
  325. localConfig.EXTENSION_NAME,
  326. sr_api.ExtensionPacket(
  327. "SettingsWidgetSmallCode",
  328. localConfig.EXTENSION_NAME,
  329. modalstring,
  330. "",
  331. toextension,
  332. localConfig.OUR_CHANNEL
  333. ),
  334. "",
  335. toextension
  336. ))
  337. }
  338. });
  339. }
  340. // ============================================================================
  341. // FUNCTION: SaveConfigToServer
  342. // ============================================================================
  343. /**
  344. * Sends our config to the server to be saved for next time we run
  345. */
  346. function SaveConfigToServer ()
  347. {
  348. sr_api.sendMessage(localConfig.DataCenterSocket, sr_api.ServerPacket
  349. ("SaveConfig",
  350. localConfig.EXTENSION_NAME,
  351. serverConfig))
  352. }
  353. // ============================================================================
  354. // FUNCTION: sendTriggersAndActions
  355. // ============================================================================
  356. /**
  357. * Sends the triggers and actions to the extension or broadcast if extension name is ""
  358. * @param {string} extension
  359. */
  360. function sendTriggersAndActions (extension)
  361. {
  362. if (extension != "")
  363. {
  364. sr_api.sendMessage(localConfig.DataCenterSocket,
  365. sr_api.ServerPacket("ExtensionMessage",
  366. serverConfig.extensionname,
  367. sr_api.ExtensionPacket(
  368. "TriggerAndActions",
  369. serverConfig.extensionname,
  370. triggersandactions,
  371. "",
  372. extension
  373. ),
  374. "",
  375. extension
  376. )
  377. )
  378. }
  379. else
  380. {
  381. sr_api.sendMessage(localConfig.DataCenterSocket,
  382. sr_api.ServerPacket("ExtensionMessage",
  383. serverConfig.extensionname,
  384. sr_api.ExtensionPacket(
  385. "TriggerAndActions",
  386. serverConfig.extensionname,
  387. triggersandactions,
  388. serverConfig.channel,
  389. ""
  390. ),
  391. serverConfig.channel,
  392. ""
  393. )
  394. )
  395. }
  396. }
  397. // ============================================================================
  398. // FUNCTION: SaveConfigToServer
  399. // ============================================================================
  400. /**
  401. * Sends our config to the server to be saved for next time we run
  402. */
  403. function UpdateMonitoring ()
  404. {
  405. if (serverConfig.sysinfo_enabled == "on")
  406. {
  407. if (serverConfig.sysinfo_cpu_data_enabled == "on")
  408. {
  409. // is there any reason to have a poll for this data?
  410. console.log("CPUData Polling TBD");
  411. //getCPUData()
  412. }
  413. else
  414. clearTimeout(localConfig.poll_handle_cpu_data);
  415. if (serverConfig.sysinfo_cpu_temperature_enabled == "on")
  416. getCPUTemperatureScheduler();
  417. else
  418. clearTimeout(localConfig.poll_handle_cpu_temperature);
  419. if (serverConfig.sysinfo_gpu_data_enabled == "on")
  420. getGPUDataScheduler();
  421. else
  422. clearTimeout(localConfig.poll_handle_gpu_data);
  423. }
  424. }
  425. // ============================================================================
  426. // FUNCTION: getCPUData
  427. // ============================================================================
  428. /**
  429. * Requests CPU data and sends out a trigger message to the system
  430. * @param {string} [reference="poll"]
  431. */
  432. function getCPUData (reference = "poll")
  433. {
  434. si.cpu()
  435. .then(data =>
  436. {
  437. // get the data in a format we can use in the triggers (can't access multidimensional arrays)
  438. let new_data = flattenObject(data);
  439. // update our triggers if we haven't done so already
  440. if (!localConfig.CPUDataTriggerUpdated)
  441. {
  442. updateTrigger("trigger_sysinfoCPUData", new_data);
  443. localConfig.CPUDataTriggerUpdated = true;
  444. }
  445. // add the users reference or the default if not provided
  446. new_data.reference = reference;
  447. if (serverConfig.sysinfo_console_log_enabled == "on")
  448. {
  449. console.log("CPU Data:", new_data)
  450. }
  451. // send the trigger with the new data
  452. sendTrigger("trigger_sysinfoCPUData", new_data)
  453. })
  454. .catch(error =>
  455. {
  456. console.log("##### SysInfo getCPUData Error ######");
  457. console.error(error)
  458. });
  459. }
  460. // ============================================================================
  461. // FUNCTION: getCPUTemperature
  462. // ============================================================================
  463. /**
  464. * Requests CPU Temperature data and sends out a trigger message to the system
  465. * @param {string} [reference="poll"]
  466. */
  467. function getCPUTemperature (reference = "poll")
  468. {
  469. // for an action use getCPUTemperature(reference); where reference is from the users action
  470. try
  471. {
  472. si.cpuTemperature()
  473. .then(data =>
  474. {
  475. // get the data in a format we can use in the triggers (can't access multidimensional arrays)
  476. let new_data = flattenObject(data);
  477. if (!localConfig.CPUTemperaturesTriggerUpdated)
  478. {
  479. updateTrigger("trigger_sysinfoCPUTemperatures", new_data)
  480. localConfig.CPUTemperaturesTriggerUpdated = true;
  481. }
  482. new_data.reference = reference;
  483. if (serverConfig.sysinfo_console_log_enabled == "on")
  484. {
  485. console.log("CPU Temperature:", new_data)
  486. }
  487. // send the trigger with the new data
  488. sendTrigger("trigger_sysinfoCPUTemperatures", new_data)
  489. })
  490. .catch(error =>
  491. {
  492. logger.err(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname + ".getCPUTemperature", "failed:", error);
  493. });
  494. }
  495. catch (err)
  496. {
  497. logger.err(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname + ".getCPUTemperature", "callback failed:", err.message);
  498. }
  499. // restart timer if we are poll enabled
  500. if (serverConfig.sysinfo_cpu_temperature_enabled == "on")
  501. getCPUTemperatureScheduler();
  502. }
  503. // ============================================================================
  504. // FUNCTION: getCPUTemperatureScheduler
  505. // ============================================================================
  506. /**
  507. * start the poll timer for the CPU temperature
  508. */
  509. function getCPUTemperatureScheduler ()
  510. {
  511. console.log("getCPUTemperatureScheduler")
  512. // if we have a poll timer value, setup the poll timer
  513. if (localConfig.poll_handle_cpu_temperature > 0)
  514. clearTimeout(localConfig.poll_handle_cpu_temperature);
  515. localConfig.poll_handle_cpu_temperature = setTimeout(getCPUTemperature, serverConfig.sysinfo_cpu_temperature_poll_interval * 1000)
  516. }
  517. // ============================================================================
  518. // FUNCTION: getGPUData
  519. // ============================================================================
  520. /**
  521. * Requests GPU data and sends out a trigger message to the system
  522. * @param {string} [reference="poll"]
  523. */
  524. function getGPUData (reference = "poll")
  525. {
  526. si.graphics()
  527. .then(data =>
  528. {
  529. let new_data = flattenObject(data);
  530. if (!localConfig.GPUDataTriggerUpdated)
  531. {
  532. updateTrigger("trigger_sysInfoGPUData", new_data);
  533. localConfig.GPUDataTriggerUpdated = true;
  534. }
  535. new_data.reference = reference;
  536. if (serverConfig.sysinfo_console_log_enabled == "on")
  537. {
  538. console.log("GPU Data:", new_data)
  539. }
  540. sendTrigger("trigger_sysInfoGPUData", new_data)
  541. })
  542. .catch(error =>
  543. {
  544. console.log("##### SysInfo getGPUData Error ######");
  545. console.error(error)
  546. });
  547. // restart timer if we are poll enabled
  548. if (serverConfig.sysinfo_gpu_data_enabled == "on")
  549. getGPUDataScheduler();
  550. }
  551. // ============================================================================
  552. // FUNCTION: getGPUDataScheduler
  553. // ============================================================================
  554. /**
  555. * start the poll timer for the GPU Data
  556. */
  557. function getGPUDataScheduler ()
  558. {
  559. console.log("getGPUDataScheduler")
  560. // if we have a poll timer value, setup the poll timer
  561. if (localConfig.poll_handle_gpu_data > 0)
  562. clearTimeout(localConfig.poll_handle_gpu_data);
  563. localConfig.poll_handle_gpu_data = setTimeout(getGPUData, serverConfig.sysinfo_gpu_data_poll_interval * 1000)
  564. }
  565. // ============================================================================
  566. // FUNCTION: sendTrigger
  567. // ============================================================================
  568. /**
  569. * Requests CPU Temperature data and sends out a trigger message to the system
  570. * @param {string} trigger_name
  571. * @param {object} trigger_data
  572. */
  573. function sendTrigger (trigger_name, trigger_data)
  574. {
  575. let trigger = findTriggerByMessageType(trigger_name);
  576. if (trigger)
  577. {
  578. trigger.parameters = trigger_data;
  579. //console.log("#### sendTrigger sending ####")
  580. //console.log(JSON.stringify(trigger, null, 2))
  581. sr_api.sendMessage(localConfig.DataCenterSocket,
  582. sr_api.ServerPacket("ChannelData",
  583. serverConfig.extensionname,
  584. sr_api.ExtensionPacket(
  585. trigger_name,
  586. serverConfig.extensionname,
  587. trigger,
  588. serverConfig.channel,
  589. ),
  590. serverConfig.channel)
  591. )
  592. }
  593. else
  594. {
  595. logger.err(localConfig.SYSTEM_LOGGING_TAG + localConfig.EXTENSION_NAME + ".sendTrigger:", "Failed to retrieve ", trigger_name);
  596. }
  597. }
  598. // ============================================================================
  599. // FUNCTION: findTriggerByMessageType
  600. // ============================================================================
  601. /**
  602. * Finds a trigger by name
  603. * @param {string} messagetype
  604. * @returns trigger
  605. */
  606. function findTriggerByMessageType (messagetype)
  607. {
  608. for (let i = 0; i < triggersandactions.triggers.length; i++)
  609. {
  610. if (triggersandactions.triggers[i].messagetype.toLowerCase() == messagetype.toLowerCase())
  611. return triggersandactions.triggers[i];
  612. }
  613. logger.err(localConfig.SYSTEM_LOGGING_TAG + serverConfig.extensionname +
  614. ".findTriggerByMessageType", "failed to find action", messagetype);
  615. }
  616. // ============================================================================
  617. // FUNCTION: flatten
  618. // ============================================================================
  619. /**
  620. * Flattens an array to move nested objects to the top level
  621. * @param {array} data
  622. * @returns 1D array
  623. */
  624. function flatten (data = [])
  625. {
  626. var ret = [];
  627. console.log("########## flatten ##########")
  628. console.log(data)
  629. //check if we have an Array. if not it is probably an object
  630. if (Array.isArray(data))
  631. {
  632. console.log("array passed")
  633. for (var i = 0; i < data.length; i++)
  634. {
  635. if (Array.isArray(data[i]))
  636. {
  637. ret = ret.concat(flatten(data[i]));
  638. } else
  639. {
  640. ret.push(data[i]);
  641. }
  642. }
  643. }
  644. else if (typeof data === 'object' &&
  645. !Array.isArray(data) &&
  646. data !== null
  647. )
  648. {
  649. console.log("got an object");
  650. }
  651. else
  652. {
  653. console.log("got a variable");
  654. }
  655. return ret;
  656. }
  657. // ============================================================================
  658. // FUNCTION: flatten
  659. // ============================================================================
  660. /**
  661. * Flattens an object to move nested objects to the top level
  662. * @param {object} data
  663. * @returns 1D object
  664. */
  665. function flattenObject (ob)
  666. {
  667. var toReturn = {};
  668. for (var i in ob)
  669. {
  670. if (!ob.hasOwnProperty(i)) continue;
  671. if ((typeof ob[i]) == 'object' && ob[i] !== null)
  672. {
  673. var flatObject = flattenObject(ob[i]);
  674. for (var x in flatObject)
  675. {
  676. if (!flatObject.hasOwnProperty(x)) continue;
  677. toReturn[i + '.' + x] = flatObject[x];
  678. }
  679. } else
  680. {
  681. toReturn[i] = ob[i];
  682. }
  683. }
  684. return toReturn;
  685. }
  686. // ============================================================================
  687. // FUNCTION: updateTrigger
  688. // ============================================================================
  689. /**
  690. * Updates the trigger parameter fields to what we get from the system (as these will be different on each PC)
  691. * @param {string} trigger
  692. * @param {data} ob
  693. */
  694. function updateTrigger (trigger, ob)
  695. {
  696. for (var triggers_i = 0; triggers_i < triggersandactions.triggers.length; triggers_i++)
  697. {
  698. // find this trigger
  699. if (triggersandactions.triggers[triggers_i].messagetype == trigger)
  700. {
  701. // add the fields to the trigger
  702. for (const property in ob)
  703. {
  704. triggersandactions.triggers[triggers_i].parameters[property] = ""
  705. }
  706. }
  707. }
  708. // send out the update so extensions have the new trigger fields
  709. sendTriggersAndActions("");
  710. localConfig.CPUDataTriggerUpdated = true
  711. }
  712. // ============================================================================
  713. // EXPORTS
  714. // ============================================================================
  715. export { initialise, triggersandactions };