update.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import {
  2. i18n, request, compareVersion, sendCmd,
  3. } from '#/common';
  4. import { CMD_SCRIPT_UPDATE } from '#/common/consts';
  5. import { getScriptById, getScripts, parseScript } from './db';
  6. import { parseMeta } from './script';
  7. import { getOption, setOption } from './options';
  8. import { commands, notify } from './message';
  9. Object.assign(commands, {
  10. /** @return {Promise<true?>} */
  11. CheckUpdate(id) {
  12. return checkUpdate(getScriptById(id));
  13. },
  14. /** @return {Promise<boolean>} */
  15. async CheckUpdateAll() {
  16. setOption('lastUpdate', Date.now());
  17. const toUpdate = getScripts().filter(item => item.config.shouldUpdate);
  18. const results = await Promise.all(toUpdate.map(checkUpdate));
  19. return results.includes(true);
  20. },
  21. });
  22. const processes = {};
  23. const NO_HTTP_CACHE = {
  24. 'Cache-Control': 'no-cache, no-store, must-revalidate',
  25. };
  26. const OPTIONS = {
  27. meta: {
  28. headers: { ...NO_HTTP_CACHE, Accept: 'text/x-userscript-meta' },
  29. },
  30. script: {
  31. headers: NO_HTTP_CACHE,
  32. },
  33. };
  34. // resolves to true if successfully updated
  35. export default function checkUpdate(script) {
  36. const { id } = script.props;
  37. const promise = processes[id] || (processes[id] = doCheckUpdate(script));
  38. return promise;
  39. }
  40. async function doCheckUpdate(script) {
  41. const { id } = script.props;
  42. try {
  43. const { update } = await parseScript({
  44. id,
  45. code: await downloadUpdate(script),
  46. update: { checking: false },
  47. });
  48. if (getOption('notifyUpdates')) {
  49. notify({
  50. title: i18n('titleScriptUpdated'),
  51. body: i18n('msgScriptUpdated', [update.meta.name || i18n('labelNoName')]),
  52. });
  53. }
  54. return true;
  55. } catch (error) {
  56. if (process.env.DEBUG) console.error(error);
  57. } finally {
  58. delete processes[id];
  59. }
  60. }
  61. async function downloadUpdate(script) {
  62. const downloadURL = (
  63. script.custom.downloadURL
  64. || script.meta.downloadURL
  65. || script.custom.lastInstallURL
  66. );
  67. const updateURL = (
  68. script.custom.updateURL
  69. || script.meta.updateURL
  70. || downloadURL
  71. );
  72. if (!updateURL) throw false;
  73. let checkingMeta = true;
  74. const update = {};
  75. const result = { update, where: { id: script.props.id } };
  76. announce(i18n('msgCheckingForUpdate'));
  77. try {
  78. const { data } = await request(updateURL, OPTIONS.meta);
  79. const meta = parseMeta(data);
  80. if (compareVersion(script.meta.version, meta.version) >= 0) {
  81. announce(i18n('msgNoUpdate'), { checking: false });
  82. } else if (!downloadURL) {
  83. announce(i18n('msgNewVersion'), { checking: false });
  84. } else {
  85. announce(i18n('msgUpdating'));
  86. checkingMeta = false;
  87. return (await request(downloadURL, OPTIONS.script)).data;
  88. }
  89. } catch (error) {
  90. announce(
  91. checkingMeta ? i18n('msgErrorFetchingUpdateInfo') : i18n('msgErrorFetchingScript'),
  92. { error },
  93. );
  94. }
  95. throw update.error;
  96. function announce(message, { error, checking = !error } = {}) {
  97. Object.assign(update, {
  98. message,
  99. checking,
  100. // `null` is sendable in Chrome unlike `undefined`
  101. error: error?.url || error || null,
  102. });
  103. sendCmd(CMD_SCRIPT_UPDATE, result);
  104. }
  105. }