main.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. "use strict";
  2. const ipcMain = require("electron").ipcMain;
  3. const { BrowserWindow } = require('electron');
  4. const path = require('path');
  5. const fs = require('fs');
  6. const rimraf = require('rimraf');
  7. const imIconHelper = require("./core/imIconHelper");
  8. const { readJson } = require('./core/utils');
  9. const http = require("./core/http");
  10. const { tr } = require('./core/translate');
  11. let panelShown = false;
  12. let newVersionChecked = false;
  13. module.exports = {
  14. async load() {
  15. Editor.Ipc.sendToPanel("im-plugin", "im-plugin:force-close");
  16. this.onEditorReady = this.onEditorReady.bind(this);
  17. this.onUserLogin = this.onUserLogin.bind(this);
  18. this.onUserLogout = this.onUserLogout.bind(this);
  19. this.translate = this.translate.bind(this);
  20. this.getEntryUrl = this.getEntryUrl.bind(this);
  21. this.onDownloadNewVersionSucceed = this.onDownloadNewVersionSucceed.bind(
  22. this
  23. );
  24. this.onDownloadNewVersionFailed = this.onDownloadNewVersionFailed.bind(
  25. this
  26. );
  27. this.onInstallNewVersionSucceed = this.onInstallNewVersionSucceed.bind(
  28. this
  29. );
  30. this.onInstallNewVersionFailed = this.onInstallNewVersionFailed.bind(this);
  31. // 注:热更新后,不会触发editor:ready,故这里我们才分出来一个initialSetup。
  32. this.initialSetup();
  33. ipcMain.on("editor:ready", this.onEditorReady);
  34. Editor.User.on("login", this.onUserLogin);
  35. Editor.User.on("logout", this.onUserLogout);
  36. ipcMain.on("im-plugin:translate", this.translate);
  37. ipcMain.on("im-plugin:get-entry-url", this.getEntryUrl);
  38. ipcMain.on("store:download-zip-succeed", this.onDownloadNewVersionSucceed);
  39. ipcMain.on("store:download-zip-failed", this.onDownloadNewVersionFailed);
  40. ipcMain.on("store:install-zip-succeed", this.onInstallNewVersionSucceed);
  41. ipcMain.on("store:install-zip-failed", this.onInstallNewVersionFailed);
  42. },
  43. unload() {
  44. ipcMain.off("editor:ready", this.onEditorReady);
  45. Editor.User.removeListener("login", this.onUserLogin);
  46. Editor.User.removeListener("logout", this.onUserLogout);
  47. ipcMain.off("im-plugin:translate", this.translate);
  48. ipcMain.off("im-plugin:get-entry-url", this.getEntryUrl);
  49. ipcMain.off("store:download-zip-succeed", this.onDownloadNewVersionSucceed);
  50. ipcMain.off("store:download-zip-failed", this.onDownloadNewVersionFailed);
  51. ipcMain.off("store:install-zip-succeed", this.onInstallNewVersionSucceed);
  52. ipcMain.off("store:install-zip-failed", this.onInstallNewVersionFailed);
  53. imIconHelper.clearUpdateIconTimer();
  54. },
  55. async initialSetup() {
  56. if (!(await Editor.User.isLoggedIn())) {
  57. return;
  58. }
  59. await this.updateImSettings();
  60. imIconHelper.setupUpdateIconTimer();
  61. },
  62. async onEditorReady() {
  63. if (!(await Editor.User.isLoggedIn())) {
  64. return;
  65. }
  66. // 可能由于之前用户未登录,initialSetup没有更新imSettings
  67. // 这里需要检查并更新下。
  68. if (!imIconHelper.getImSettings()) {
  69. await this.updateImSettings();
  70. }
  71. this.promptForUpdateIfNewVersionExists();
  72. // initialSetup可能太早更新图标,直接被无视了,这里重新设置Timer。
  73. imIconHelper.clearUpdateIconTimer();
  74. imIconHelper.setupUpdateIconTimer();
  75. },
  76. async onUserLogin() {
  77. await this.updateImSettings();
  78. imIconHelper.setupUpdateIconTimer();
  79. this.promptForUpdateIfNewVersionExists();
  80. },
  81. onUserLogout() {
  82. Editor.Ipc.sendToPanel("im-plugin", "im-plugin:force-close");
  83. imIconHelper.clearUpdateIconTimer();
  84. },
  85. async updateImSettings() {
  86. // 强制init,避免重新登录导致session改变的问题。
  87. await http.init(true);
  88. const imSettings = await http.getIMSettings();
  89. imIconHelper.updateImSettings(imSettings);
  90. },
  91. async promptForUpdateIfNewVersionExists() {
  92. if (newVersionChecked) {
  93. return;
  94. }
  95. newVersionChecked = true;
  96. const imSettings = imIconHelper.getImSettings();
  97. const newVersion = imSettings.plugin_version;
  98. if (!newVersion || !newVersion.match(/^(\d|\.)+$/)) {
  99. // 版本只做一些粗略的检查,只检查是否只包含数字和点。
  100. return;
  101. }
  102. const packageJson = readJson(path.join(__dirname, "./package.json"));
  103. const oldVersion = packageJson.version;
  104. if (newVersion > oldVersion) {
  105. const newVersionMsg = tr("new-version-found").replace("_", newVersion);
  106. const result = await Editor.Dialog.messageBox({
  107. type: "info",
  108. title: tr("info-title"),
  109. buttons: [tr("ok"), tr("cancel")],
  110. defaultId: 0,
  111. cancelId: 1,
  112. message: newVersionMsg,
  113. });
  114. if (result === 0) {
  115. this.updateToNewVersion();
  116. }
  117. }
  118. },
  119. updateToNewVersion() {
  120. Editor.Ipc.sendToMain(
  121. "editor:package-query-info",
  122. "store",
  123. (err, storePackageInfo) => {
  124. if (err) {
  125. return;
  126. }
  127. const storeVersion = storePackageInfo.info.version;
  128. if (storeVersion >= "2.0.1") {
  129. const imSettings = imIconHelper.getImSettings();
  130. Editor.Ipc.sendToMain(
  131. "store:download-zip",
  132. imSettings.plugin_url,
  133. `im-plugin_${imSettings.plugin_version}.zip`
  134. );
  135. } else {
  136. try {
  137. // 打开不存在的Panel会有异常,这里忽略可能的异常。
  138. Editor.Panel.open("store");
  139. } catch (_) {}
  140. }
  141. }
  142. );
  143. },
  144. async onDownloadNewVersionSucceed(event, url, fileName, zipPath) {
  145. const imSettings = imIconHelper.getImSettings();
  146. if (url !== imSettings.plugin_url) {
  147. return;
  148. }
  149. this.newVersionZip = zipPath;
  150. try {
  151. this.cleanRequireCaches(__dirname);
  152. await this.removeDir(__dirname);
  153. Editor.Ipc.sendToMain("store:install-zip", this.newVersionZip);
  154. } catch (e) {
  155. Editor.warn(e);
  156. Editor.Dialog.messageBox({
  157. type: "warning",
  158. title: tr("warn-title"),
  159. message: tr("remove-current-version-failed"),
  160. });
  161. }
  162. },
  163. onDownloadNewVersionFailed(event, url, fileName) {
  164. const imSettings = imIconHelper.getImSettings();
  165. if (url !== imSettings.plugin_url) {
  166. return;
  167. }
  168. Editor.Dialog.messageBox({
  169. type: "warning",
  170. title: tr("warn-title"),
  171. message: tr("download-new-version-failed"),
  172. });
  173. },
  174. onInstallNewVersionSucceed(event, zipPath) {
  175. // 新版本插件会接收到这个消息,这里this.newVersionZip已经没了,不太好做精确验证。
  176. // 简单验证下zip名称中是否包含im-plugin。
  177. if (zipPath.indexOf("im-plugin") === -1) {
  178. return;
  179. }
  180. Editor.Dialog.messageBox({
  181. type: "info",
  182. title: tr("info-title"),
  183. message: tr("install-new-version-succeed"),
  184. });
  185. },
  186. onInstallNewVersionFailed(event, zipPath) {
  187. if (zipPath !== this.newVersionZip) {
  188. return;
  189. }
  190. Editor.Dialog.messageBox({
  191. type: "warning",
  192. title: tr("warn-title"),
  193. message: tr("install-new-version-failed"),
  194. });
  195. },
  196. translate(event, key) {
  197. event.returnValue = tr(key);
  198. },
  199. getEntryUrl(event) {
  200. event.returnValue = imIconHelper.getImSettings().entry_url;
  201. },
  202. cleanRequireCaches(...args) {
  203. const targetPath = path.join(...args);
  204. if (!fs.existsSync(path)) {
  205. return;
  206. }
  207. if (targetPath.endsWith(".js")) {
  208. return delete require.cache[require.resolve(targetPath)];
  209. }
  210. if (!fs.statSync(path).isDirectory()) {
  211. return;
  212. }
  213. recursive(
  214. path,
  215. (filePath) => {
  216. if (filePath.endsWith(".js") && existsSync(filePath)) {
  217. delete require.cache[require.resolve(filePath)];
  218. }
  219. },
  220. (file) => {
  221. if (path.basename(file) === "node_modules") {
  222. return false;
  223. }
  224. if (path.basename(file).startsWith(".")) {
  225. return false;
  226. }
  227. return true;
  228. }
  229. );
  230. function recursive(dir, handle, filter) {
  231. function step(filePath) {
  232. if (!existsSync(filePath)) {
  233. return;
  234. }
  235. if (filter ? filter(filePath) : true) {
  236. handle(filePath);
  237. if (fs.statSync(filePath).isDirectory()) {
  238. fs.readdirSync(filePath).forEach((file) =>
  239. step(path.join(filePath, file))
  240. );
  241. }
  242. }
  243. }
  244. step(dir);
  245. }
  246. },
  247. removeDir(dirPath) {
  248. return new Promise((resolve, reject) => {
  249. rimraf(dirPath, (err) => {
  250. if (!err) {
  251. resolve(null);
  252. } else {
  253. reject(err);
  254. }
  255. });
  256. });
  257. },
  258. messages: {
  259. async open(event) {
  260. imIconHelper.clearUpdateIconTimer();
  261. imIconHelper.setImPanelBlurred(false);
  262. if (!panelShown) {
  263. await this.updateImSettings(); // 更新下是否是VIP的信息
  264. }
  265. imIconHelper.updateIcon(0, true);
  266. Editor.Panel.open("im-plugin");
  267. if (!panelShown) {
  268. panelShown = true;
  269. }
  270. },
  271. "panel-ready"(event) {
  272. const win = BrowserWindow.fromWebContents(event.sender);
  273. const eventNames = [
  274. "blur",
  275. "focus",
  276. "show",
  277. "hide",
  278. "minimize",
  279. "restore",
  280. ];
  281. for (const eventName of eventNames) {
  282. win.on(eventName, () => {
  283. event.sender.send("im-plugin:panel-win-state-changed", eventName);
  284. });
  285. }
  286. win.on("blur", () => {
  287. imIconHelper.setImPanelBlurred(true);
  288. });
  289. win.on("focus", () => {
  290. imIconHelper.updateIcon(0, true);
  291. imIconHelper.setImPanelBlurred(false);
  292. });
  293. },
  294. close(event) {
  295. // 如果Panel隐藏了,需要show之后才能关闭,否则会出现在下次面板显示时才瞬间关闭的情况。
  296. BrowserWindow.fromWebContents(event.sender).show();
  297. imIconHelper.setImPanelBlurred(true);
  298. Editor.Panel.close("im-plugin");
  299. },
  300. hide(event) {
  301. imIconHelper.setImPanelBlurred(true);
  302. BrowserWindow.fromWebContents(event.sender).hide();
  303. },
  304. "update-icon"(event, newMsgCount) {
  305. imIconHelper.updateIcon(newMsgCount);
  306. },
  307. },
  308. };