Home Reference Source

cables_dev/cables_electron/src_client/standalone.js

  1. import { Logger } from "cables-shared-client";
  2. import ElectronEditor from "./electron_editor.js";
  3. import electronCommands from "./cmd_electron.js";
  4.  
  5. /**
  6. * frontend class for cables standalone
  7. * initializes the ui, starts the editor and adds functions custom to this platform
  8. */
  9. export default class CablesStandalone
  10. {
  11. constructor()
  12. {
  13. this._electron = window.nodeRequire("electron");
  14. this._importSync = window.nodeRequire("import-sync");
  15.  
  16. window.ipcRenderer = this._electron.ipcRenderer; // needed to have ipcRenderer in electron_editor.js
  17. this._settings = this._electron.ipcRenderer.sendSync("platformSettings") || {};
  18. this._usersettings = this._settings.userSettings;
  19. delete this._settings.userSettings;
  20. this._config = this._electron.ipcRenderer.sendSync("cablesConfig") || {};
  21. this.editorIframe = null;
  22.  
  23. this._startUpLogItems = this._electron.ipcRenderer.sendSync("getStartupLog") || [];
  24.  
  25. if (!this._config.isPackaged) window.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
  26.  
  27. this._loadedModules = {};
  28. }
  29.  
  30. /**
  31. * the `gui` object of the current editor, if initialized
  32. *
  33. * @type {Gui|null}
  34. */
  35. get gui()
  36. {
  37. return this.editorWindow ? this.editorWindow.gui : null;
  38. }
  39.  
  40. /**
  41. * the current editor window, if initialized
  42. *
  43. * @type {{}|null}
  44. */
  45. get editorWindow()
  46. {
  47. return this.editorIframe.contentWindow;
  48. }
  49.  
  50. /**
  51. * the CABLES core instance of the current editor window, if initialized
  52. *
  53. * @type {{}|null}
  54. */
  55. get CABLES()
  56. {
  57. return this.editorWindow ? this.editorWindow.CABLES : null;
  58. }
  59.  
  60. /**
  61. * initialize the editor, wait for core and ui to be ready, add
  62. * custom functionality
  63. */
  64. init()
  65. {
  66. this.editorIframe = document.getElementById("editorIframe");
  67. let src = this._config.uiIndexHtml + window.location.search;
  68. if (window.location.hash)
  69. {
  70. src += window.location.hash;
  71. }
  72. this.editorIframe.src = src;
  73. this.editorIframe.onload = () =>
  74. {
  75. if (this.editorWindow)
  76. {
  77. const waitForAce = this.editorWindow.waitForAce;
  78. this.editorWindow.waitForAce = () =>
  79. {
  80. this._log.info("loading", this._settings.patchFile);
  81.  
  82. this._incrementStartup();
  83. this._logStartup("checking/installing op dependencies...");
  84. this._electron.ipcRenderer.invoke("talkerMessage", "installProjectDependencies").then((npmResult) =>
  85. {
  86. this.editorWindow.CABLESUILOADER.cfg.patchConfig.onError = (...args) =>
  87. {
  88. // npm runtime error...
  89. if (args && args[0] === "core_patch" && args[2] && args[2].message && args[2].message.includes("was compiled against a different Node.js version"))
  90. {
  91. const dirParts = args[2].message.split("/");
  92. const opNameIndex = dirParts.findIndex((part) => { return part.startsWith("Ops."); });
  93. const opName = dirParts[opNameIndex];
  94. const packageName = dirParts[opNameIndex + 2];
  95. const onClick = "CABLES.CMD.STANDALONE.openOpDir('', '" + opName + "');";
  96.  
  97. const msg = "try running this <a onclick=\"" + onClick + "\" > in the op dir</a>:";
  98. this._log.error(msg);
  99. this._log.error("`npm --prefix ./ install " + packageName + "`");
  100. this._log.error("`npx \"@electron/rebuild\" -v " + process.versions.electron);
  101. }
  102. };
  103. waitForAce();
  104.  
  105. if (npmResult.error && npmResult.data && npmResult.msg !== "UNSAVED_PROJECT")
  106. {
  107. npmResult.data.forEach((msg) =>
  108. {
  109. const opName = msg.opName ? " for " + msg.opName : "";
  110. this._log.error("failed dependency" + opName + ": " + msg.stderr);
  111. });
  112. }
  113. else if (npmResult.msg !== "EMPTY" && npmResult.msg !== "UNSAVED_PROJECT")
  114. {
  115. npmResult.data.forEach((result) =>
  116. {
  117. const npmText = result.stderr || result.stdout;
  118. this._logStartup(result.opName + ": " + npmText);
  119. });
  120. }
  121.  
  122.  
  123. if (this.gui)
  124. {
  125. this.gui.on("uiloaded", () =>
  126. {
  127. if (this.editor && this.editor.config && !this.editor.config.patchFile) this.gui.setStateUnsaved();
  128. });
  129. }
  130. });
  131. };
  132. if (this._settings.uiLoadStart) this.editorWindow.CABLESUILOADER.uiLoadStart -= this._settings.uiLoadStart;
  133. this._startUpLogItems.forEach((logEntry) =>
  134. {
  135. this._logStartup(logEntry.title);
  136. });
  137. if (this.editorWindow.loadjs)
  138. {
  139. this.editorWindow.loadjs.ready("cables_core", this._coreReady.bind(this));
  140. this.editorWindow.loadjs.ready("cablesuinew", this._uiReady.bind(this));
  141. }
  142. }
  143. };
  144.  
  145. window.addEventListener("message", (event) =>
  146. {
  147. if (event.data && event.data.type === "hashchange")
  148. {
  149. window.location.hash = event.data.data;
  150. }
  151. }, false);
  152.  
  153. window.addEventListener("hashchange", () =>
  154. {
  155. if (this.editorWindow)
  156. {
  157. this.editorWindow.postMessage({ "type": "hashchange", "data": window.location.hash }, "*");
  158. }
  159. }, false);
  160.  
  161. this.editor = new ElectronEditor({
  162. "config": {
  163. ...this._settings,
  164. "isTrustedPatch": true,
  165. "platformClass": "PlatformStandalone",
  166. "urlCables": "cables://",
  167. "urlSandbox": "cables://",
  168. "communityUrl": this._config.communityUrl,
  169. "user": this._settings.currentUser,
  170. "usersettings": { "settings": this._usersettings },
  171. "isDevEnv": !this._config.isPackaged,
  172. "env": this._config.env,
  173. "patchId": this._settings.patchId,
  174. "patchVersion": "",
  175. "socketcluster": {},
  176. "remoteClient": false,
  177. "buildInfo": this._settings.buildInfo,
  178. "patchConfig": {
  179. "allowEdit": true,
  180. "prefixAssetPath": this._settings.currentPatchDir,
  181. "assetPath": this._settings.paths.assetPath,
  182. "paths": this._settings.paths
  183. },
  184. }
  185. });
  186. }
  187.  
  188. _coreReady()
  189. {
  190. if (this.CABLES)
  191. {
  192. if (this.CABLES.Op)
  193. {
  194. const standAlone = this;
  195. this.CABLES.Op.prototype.require = function (moduleName)
  196. {
  197. return standAlone._opRequire(moduleName, this, standAlone);
  198. };
  199. }
  200. if (this.CABLES.Patch)
  201. {
  202. Object.defineProperty(this.CABLES.Patch.prototype, "patchDir", { "get": this._patchDir.bind(this) });
  203. }
  204. }
  205. }
  206.  
  207. _uiReady()
  208. {
  209. this.CABLES.UI.standaloneLogger = () =>
  210. {
  211. CABLES.UI = this.CABLES.UI;
  212. return new Logger("standalone");
  213. };
  214. this._log = this.CABLES.UI.standaloneLogger();
  215. if (this.CABLES)
  216. {
  217. const getOpsForFilename = this.CABLES.UI.getOpsForFilename;
  218. this.CABLES.UI.getOpsForFilename = (filename) =>
  219. {
  220. let defaultOps = getOpsForFilename(filename);
  221. if (defaultOps.length === 0)
  222. {
  223. defaultOps.push(this.CABLES.UI.DEFAULTOPNAMES.HttpRequest);
  224. const addOpCb = this.gui.corePatch().on("onOpAdd", (newOp) =>
  225. {
  226. const contentPort = newOp.getPortByName("Content", false);
  227. if (contentPort) contentPort.set("String");
  228. this.gui.corePatch().off(addOpCb);
  229. });
  230. }
  231. return defaultOps;
  232. };
  233. this.CABLES.CMD.STANDALONE = electronCommands.functions;
  234. this.CABLES.CMD.commands = this.CABLES.CMD.commands.concat(electronCommands.commands);
  235. Object.assign(this.CABLES.CMD.PATCH, electronCommands.functionOverrides.PATCH);
  236. Object.assign(this.CABLES.CMD.RENDERER, electronCommands.functionOverrides.RENDERER);
  237. const commandOverrides = electronCommands.commandOverrides;
  238. this.CABLES.CMD.commands.forEach((command) =>
  239. {
  240. const commandOverride = commandOverrides.find((override) => { return override.cmd === command.cmd; });
  241. if (commandOverride)
  242. {
  243. Object.assign(command, commandOverride);
  244. }
  245. });
  246. }
  247. }
  248.  
  249. _opRequire(moduleName, op, thisClass)
  250. {
  251. if (op) op.setUiError("oprequire", null);
  252. if (moduleName === "electron") return thisClass._electron;
  253. if (this._loadedModules[moduleName]) return this._loadedModules[moduleName];
  254.  
  255. try
  256. {
  257. // load module by directory name
  258. const modulePath = window.ipcRenderer.sendSync("getOpModuleDir", { "opName": op.objName || op._name, "opId": op.opId, "moduleName": moduleName });
  259. this._loadedModules[moduleName] = window.nodeRequire(modulePath);
  260. return this._loadedModules[moduleName];
  261. }
  262. catch (ePath)
  263. {
  264. try
  265. {
  266. // load module by resolved filename from package.json
  267. const moduleFile = window.ipcRenderer.sendSync("getOpModuleLocation", { "opName": op.objName || op._name, "opId": op.opId, "moduleName": moduleName });
  268. this._loadedModules[moduleName] = window.nodeRequire(moduleFile);
  269. return this._loadedModules[moduleName];
  270. }
  271. catch (eFile)
  272. {
  273. try
  274. {
  275. // load module by module name
  276. this._loadedModules[moduleName] = window.nodeRequire(moduleName);
  277. return this._loadedModules[moduleName];
  278. }
  279. catch (eName)
  280. {
  281. try
  282. {
  283. const moduleFile = window.ipcRenderer.sendSync("getOpModuleLocation", { "opName": op.objName || op._name, "opId": op.opId, "moduleName": moduleName, });
  284. this._loadedModules[moduleName] = this._importSync(moduleFile);
  285. return this._loadedModules[moduleName];
  286. }
  287. catch (eImport)
  288. {
  289. let errorMessage = "failed to load node module: " + moduleName + "\n\n";
  290. errorMessage += "require by import:\n" + eImport + "\n\n";
  291. errorMessage += "require by name:\n" + eName + "\n\n";
  292. errorMessage += "require by file:\n" + eFile + "\n\n";
  293. errorMessage += "require by path:\n" + ePath;
  294. if (op) op.setUiError("oprequire", errorMessage);
  295. this._log.error(errorMessage, eName, eFile, ePath);
  296. return { };
  297. }
  298. }
  299. }
  300. }
  301. }
  302.  
  303. _patchDir(...args)
  304. {
  305. return this._settings.currentPatchDir;
  306. }
  307.  
  308. _logStartup(title)
  309. {
  310. if (this.editorWindow && this.editorWindow.logStartup) this.editorWindow.logStartup(title);
  311. }
  312.  
  313. _incrementStartup()
  314. {
  315. if (this.editorWindow && this.editorWindow.logStartup) this.editorWindow.incrementStartup();
  316. }
  317. }