codex-overleaf-link 2.4.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/README.md +272 -314
  2. package/README.zh-CN.md +287 -329
  3. package/extension/bootstrap/background.js +47 -10
  4. package/extension/bootstrap/manifest.template.json +9 -4
  5. package/extension/bootstrap/popup.html +10 -16
  6. package/extension/bootstrap/update.js +25 -2
  7. package/extension/runtime-manifest.json +3 -1
  8. package/extension/src/background.js +28 -3
  9. package/extension/src/backgroundSharedSessions.js +380 -0
  10. package/extension/src/backgroundUpdateCoordinator.js +442 -379
  11. package/extension/src/content/activeTurnControl.js +8 -0
  12. package/extension/src/content/assetTransferBroker.js +186 -12
  13. package/extension/src/content/changeHistoryView.js +175 -0
  14. package/extension/src/content/composerAttachments.js +24 -22
  15. package/extension/src/content/composerPanel.js +23 -8
  16. package/extension/src/content/contentRuntime.js +507 -507
  17. package/extension/src/content/diagnosticsController.js +1 -2
  18. package/extension/src/content/diagnosticsExport.js +222 -0
  19. package/extension/src/content/diagnosticsPanel.js +48 -19
  20. package/extension/src/content/diffReviewPanel.js +8 -5
  21. package/extension/src/content/generated/content.bundle.js +8330 -1349
  22. package/extension/src/content/generated/content.bundle.meta.json +22 -3
  23. package/extension/src/content/legacyGlobalRegistry.js +19 -1
  24. package/extension/src/content/localSkillsPanel.js +42 -7
  25. package/extension/src/content/markdownDomRenderer.js +9 -4
  26. package/extension/src/content/modelPicker.js +1 -1
  27. package/extension/src/content/pageBridgeClient.js +18 -3
  28. package/extension/src/content/panelMaintenance.js +48 -121
  29. package/extension/src/content/panelRenderer.js +4 -0
  30. package/extension/src/content/pendingInputView.js +109 -76
  31. package/extension/src/content/providerSettingsCoordinator.js +17 -6
  32. package/extension/src/content/providerSettingsDialog.js +661 -108
  33. package/extension/src/content/recentProjects.js +10 -1
  34. package/extension/src/content/runActivitySummary.js +631 -0
  35. package/extension/src/content/runController.js +3 -1
  36. package/extension/src/content/runFailureNotice.js +14 -1
  37. package/extension/src/content/runGuidanceController.js +4 -3
  38. package/extension/src/content/runPresence.js +306 -0
  39. package/extension/src/content/runQueueScheduler.js +2 -1
  40. package/extension/src/content/runResultActions.js +123 -3
  41. package/extension/src/content/runTimelineView.js +186 -154
  42. package/extension/src/content/selectionContextView.js +300 -0
  43. package/extension/src/content/sessionPersistence.js +6 -1
  44. package/extension/src/content/settingsPanel.js +77 -52
  45. package/extension/src/content/settingsWorkbench.js +374 -0
  46. package/extension/src/content/sharedSessionViewSync.js +72 -0
  47. package/extension/src/content/subagentActivityView.js +195 -0
  48. package/extension/src/content/trackedChangeCaptureController.js +7 -3
  49. package/extension/src/content/updateNotice.js +121 -50
  50. package/extension/src/content/writebackController.js +257 -0
  51. package/extension/src/content/writebackOrchestrator.js +146 -141
  52. package/extension/src/content/writebackPlan.js +231 -0
  53. package/extension/src/content/writebackRecovery.js +122 -0
  54. package/extension/src/content/writingStyleSettings.js +576 -0
  55. package/extension/src/content/writtenChangesView.js +152 -0
  56. package/extension/src/page/binaryAssetUploader.js +97 -19
  57. package/extension/src/page/overleafProjectSnapshot.js +1 -0
  58. package/extension/src/page/referenceProjects.js +148 -0
  59. package/extension/src/page/saveState.js +14 -2
  60. package/extension/src/page/snapshotRouter.js +44 -13
  61. package/extension/src/page/textCoordinates.js +102 -0
  62. package/extension/src/page/textFileCreator.js +350 -59
  63. package/extension/src/page/trackedChangeCapture.js +42 -56
  64. package/extension/src/page/trackedChangeOwnership.js +198 -0
  65. package/extension/src/page/trackedChangeReplay.js +164 -0
  66. package/extension/src/page/trackedChangesLifecycle.js +135 -149
  67. package/extension/src/page/treeOperations.js +26 -2
  68. package/extension/src/page/writebackReceiptJournal.js +112 -0
  69. package/extension/src/page/writebackRouter.js +175 -135
  70. package/extension/src/pageBridge.js +56 -101
  71. package/extension/src/popup.js +203 -59
  72. package/extension/src/shared/agentTranscript.js +18 -4
  73. package/extension/src/shared/auditRecords.js +71 -0
  74. package/extension/src/shared/changeHistoryModel.js +94 -0
  75. package/extension/src/shared/compatibility.js +1 -1
  76. package/extension/src/shared/compileAdapter.js +42 -0
  77. package/extension/src/shared/failureReasons.js +20 -0
  78. package/extension/src/shared/globalPreferences.js +1 -1
  79. package/extension/src/shared/i18n.js +93 -25
  80. package/extension/src/shared/managedUpdateProjection.js +6 -3
  81. package/extension/src/shared/pageRpcContract.js +7 -1
  82. package/extension/src/shared/providerProfiles.js +109 -0
  83. package/extension/src/shared/runActivityModel.js +361 -0
  84. package/extension/src/shared/runExecutionSnapshot.js +8 -2
  85. package/extension/src/shared/runExecutionSnapshotCodec.js +16 -4
  86. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +6 -3
  87. package/extension/src/shared/selectionContext.js +28 -0
  88. package/extension/src/shared/sessionState.js +97 -6
  89. package/extension/src/shared/sharedSessionBridge.js +288 -0
  90. package/extension/src/shared/storageDb.js +22 -55
  91. package/extension/src/shared/storageMigration.js +3 -0
  92. package/extension/src/shared/storageRunActions.js +10 -4
  93. package/extension/src/shared/storageValuePruning.js +40 -0
  94. package/extension/src/shared/subagentActivityModel.js +174 -0
  95. package/extension/src/shared/undoOperations.js +87 -0
  96. package/extension/src/shared/updateConsent.js +37 -8
  97. package/extension/src/shared/writebackIntent.js +62 -0
  98. package/extension/src/shared/writebackSettlement.js +29 -10
  99. package/extension/src/shared/writingStyle.js +33 -0
  100. package/extension/styles/panel.css +1972 -384
  101. package/extension/styles/popup.css +110 -0
  102. package/native-host/src/anthropicBridgeRequest.js +2 -1
  103. package/native-host/src/codexModelRecovery.js +107 -0
  104. package/native-host/src/codexPromptAssembly.js +2 -0
  105. package/native-host/src/codexProviderLaunch.js +3 -3
  106. package/native-host/src/codexRuntimeIdentity.js +1 -0
  107. package/native-host/src/codexSessionRunner.js +106 -140
  108. package/native-host/src/codexSessionTiming.js +3 -0
  109. package/native-host/src/managedInstall.js +5 -4
  110. package/native-host/src/nativeEnvironment.js +70 -0
  111. package/native-host/src/nativeQuotas.js +15 -8
  112. package/native-host/src/overleafWorkspaceRules.js +16 -0
  113. package/native-host/src/processStopBarrier.js +80 -0
  114. package/native-host/src/providerModelCatalog.js +4 -3
  115. package/native-host/src/providerProfile.js +13 -1
  116. package/native-host/src/providerReasoning.js +9 -5
  117. package/native-host/src/providerStore.js +4 -0
  118. package/native-host/src/releaseTransport.js +14 -5
  119. package/native-host/src/selectionScope.js +58 -0
  120. package/native-host/src/skills/parallel-subagents/SKILL.md +31 -3
  121. package/native-host/src/skills/writing-style-builder/SKILL.md +146 -0
  122. package/native-host/src/subagentBroker.js +102 -98
  123. package/native-host/src/subagentTelemetry.js +316 -0
  124. package/native-host/src/subagentWorkspace.js +103 -0
  125. package/native-host/src/subagentWorkspacePath.js +31 -0
  126. package/native-host/src/taskRunnerRuntime.js +55 -5
  127. package/native-host/src/turnAttachments.js +113 -0
  128. package/native-host/src/updateManager.js +122 -12
  129. package/native-host/src/updateNetworkControl.js +75 -0
  130. package/native-host/src/updateTrust.js +21 -2
  131. package/native-host/src/vendor/pdfjs/LICENSE +177 -0
  132. package/native-host/src/vendor/pdfjs/SOURCE.txt +3 -0
  133. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  134. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  135. package/native-host/src/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
  136. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  137. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  138. package/native-host/src/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
  139. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  140. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  141. package/native-host/src/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  142. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  143. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  144. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  145. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  146. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  147. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  148. package/native-host/src/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
  149. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  150. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  151. package/native-host/src/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
  152. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  153. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  154. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  155. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  156. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  157. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  158. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  159. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  160. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  161. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  162. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  163. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  164. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  165. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  166. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  167. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  168. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  169. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  170. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  171. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  172. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  173. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  174. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  175. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  176. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  177. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  178. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  179. package/native-host/src/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
  180. package/native-host/src/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
  181. package/native-host/src/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  182. package/native-host/src/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  183. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  184. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  185. package/native-host/src/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  186. package/native-host/src/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  187. package/native-host/src/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  188. package/native-host/src/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  189. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  190. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  191. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  192. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  193. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  194. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  195. package/native-host/src/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
  196. package/native-host/src/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
  197. package/native-host/src/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
  198. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  199. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  200. package/native-host/src/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
  201. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  202. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  203. package/native-host/src/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
  204. package/native-host/src/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
  205. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  206. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  207. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  208. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  209. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  210. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  211. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  212. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  213. package/native-host/src/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
  214. package/native-host/src/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
  215. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  216. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  217. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  218. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  219. package/native-host/src/vendor/pdfjs/cmaps/H.bcmap +0 -0
  220. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  221. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  222. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  223. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  224. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  225. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  226. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  227. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  228. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  229. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  230. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  231. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  232. package/native-host/src/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
  233. package/native-host/src/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
  234. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  235. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  236. package/native-host/src/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
  237. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  238. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  239. package/native-host/src/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
  240. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  241. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  242. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  243. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  244. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  245. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  246. package/native-host/src/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
  247. package/native-host/src/vendor/pdfjs/cmaps/LICENSE +36 -0
  248. package/native-host/src/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
  249. package/native-host/src/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
  250. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  251. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  252. package/native-host/src/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
  253. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  254. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  255. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  256. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  257. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  258. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  259. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  260. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  261. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  262. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  263. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  264. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  265. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  266. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  267. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  268. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  269. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  270. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  271. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  272. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  273. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  274. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  275. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  276. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  277. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  278. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  279. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  280. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  281. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  282. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  283. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  284. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  285. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  286. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  287. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  288. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  289. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  290. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  291. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  292. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  293. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  294. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  295. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  296. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  297. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  298. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  299. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  300. package/native-host/src/vendor/pdfjs/cmaps/V.bcmap +0 -0
  301. package/native-host/src/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  302. package/native-host/src/vendor/pdfjs/pdf.mjs +26740 -0
  303. package/native-host/src/vendor/pdfjs/pdf.worker.mjs +62201 -0
  304. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  305. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  306. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  307. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  308. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  309. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  310. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  311. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  312. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  313. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  314. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  315. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
  316. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  317. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  318. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  319. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  320. package/native-host/src/writingStyleEvidence.js +102 -0
  321. package/native-host/src/writingStylePdfWorker.js +35 -0
  322. package/native-host/src/writingStyleProfile.js +165 -0
  323. package/native-host/src/writingStyleRuntime.js +447 -0
  324. package/package.json +1 -1
@@ -24,14 +24,18 @@
24
24
  const OVERLEAF_MATCHES = [
25
25
  'https://www.overleaf.com/project',
26
26
  'https://overleaf.com/project',
27
+ 'https://cn.overleaf.com/project',
27
28
  'https://www.overleaf.com/project/*',
28
- 'https://overleaf.com/project/*'
29
+ 'https://overleaf.com/project/*',
30
+ 'https://cn.overleaf.com/project/*'
29
31
  ];
30
32
  const MESSAGE_TYPES = new Set([
31
33
  'codex-overleaf/consent-update-get-state',
32
34
  'codex-overleaf/consent-update-check',
33
35
  'codex-overleaf/consent-update-install',
34
36
  'codex-overleaf/consent-update-later',
37
+ 'codex-overleaf/consent-update-cancel',
38
+ 'codex-overleaf/consent-update-recover',
35
39
  'codex-overleaf/consent-update-dismiss',
36
40
  'codex-overleaf/consent-update-reload'
37
41
  ]);
@@ -47,6 +51,11 @@
47
51
  let runtimeStatusAt = 0;
48
52
  let runtimeStatusRead = null;
49
53
  let runtimeReloadMessage = '';
54
+ let operationGeneration = 0;
55
+ let activeOperation = null;
56
+ let cancellationPromise = null;
57
+ let recoveryPromise = null;
58
+ let stateWriteTail = Promise.resolve();
50
59
 
51
60
  function init(options = {}) {
52
61
  if (initialized || !policy || !revocation) return;
@@ -59,7 +68,10 @@
59
68
  sendResponse({ ok: false, error: { code: 'forbidden_sender', message: 'Update actions are limited to this extension and Overleaf project tabs.' } });
60
69
  return false;
61
70
  }
62
- enqueuePolicyAction(() => handleMessage(message))
71
+ const direct = ['codex-overleaf/consent-update-get-state',
72
+ 'codex-overleaf/consent-update-cancel', 'codex-overleaf/consent-update-later',
73
+ 'codex-overleaf/consent-update-recover'].includes(message.type);
74
+ (direct ? handleMessage(message) : enqueuePolicyAction(() => handleMessage(message)))
63
75
  .then(result => sendResponse({ ok: true, result }))
64
76
  .catch(error => sendResponse({ ok: false, error: safeError(error) }));
65
77
  return true;
@@ -70,7 +82,7 @@
70
82
  void enqueuePolicyAction(() => checkOnly({ manual: false })).catch(() => {});
71
83
  }
72
84
  if (alarm?.name === WATCHDOG_ALARM) {
73
- void enqueuePolicyAction(() => reconcileRecoveryState()).catch(() => {});
85
+ void reconcileRecoveryState().catch(() => {});
74
86
  }
75
87
  if (alarm?.name === IDLE_ALARM) {
76
88
  void enqueuePolicyAction(() => tryActivateStagedUpdate()).catch(() => {});
@@ -128,11 +140,16 @@
128
140
  switch (message.type) {
129
141
  case 'codex-overleaf/consent-update-get-state':
130
142
  return getView();
143
+ case 'codex-overleaf/consent-update-recover':
144
+ await reconcileRecoveryState();
145
+ return getView();
131
146
  case 'codex-overleaf/consent-update-check':
132
147
  return checkOnly({ manual: true });
133
148
  case 'codex-overleaf/consent-update-install':
134
149
  return installUpdate();
135
150
  case 'codex-overleaf/consent-update-later':
151
+ return snoozeManualInstall().then(view => view || postponeUpdate());
152
+ case 'codex-overleaf/consent-update-cancel':
136
153
  return postponeUpdate();
137
154
  case 'codex-overleaf/consent-update-dismiss':
138
155
  return dismissCompletedUpdate();
@@ -143,106 +160,83 @@
143
160
  }
144
161
  }
145
162
 
146
- async function checkOnly({ manual }) {
147
- const [currentState, consent] = await Promise.all([getUpdateState(), getConsentState()]);
148
- if (consent.authorizedVersion || policy.isExecutionState(currentState.state)) {
149
- return getView();
150
- }
151
- await setUpdateState({
152
- ...currentState,
153
- state: 'checking',
154
- initiatedBy: manual ? 'manual' : 'automatic',
155
- code: '',
156
- message: '',
157
- blocker: '',
158
- blockers: []
159
- });
160
-
163
+ async function checkOnly({ manual }, inheritedOperation) {
164
+ const operation = inheritedOperation || beginOperation();
161
165
  try {
162
- const result = await withTimeout(
163
- requestNative('update.check', {
164
- currentVersion: currentVersion(),
165
- etag: currentState.etag || ''
166
- }),
167
- CHECK_REQUEST_TIMEOUT_MS,
168
- codedError('update_check_timeout', 'The managed update check did not complete in time.')
169
- );
170
- const checkedAt = Date.now();
171
- if (result.available) {
172
- const nextConsent = result.latestVersion !== consent.snoozedVersion
173
- ? { ...consent, snoozedVersion: '', snoozedUntil: 0 }
174
- : consent;
175
- await setConsentState({
176
- ...nextConsent,
177
- lastPromptedVersion: result.latestVersion,
178
- lastPromptedAt: manual ? checkedAt : nextConsent.lastPromptedAt
179
- });
180
- await setUpdateState({
181
- ...currentState,
182
- state: 'update_available',
183
- managed: true,
184
- currentVersion: result.currentVersion || currentVersion(),
185
- latestVersion: result.latestVersion,
186
- etag: result.etag || currentState.etag || '',
187
- lastCheckedAt: checkedAt,
188
- postponeUntil: LEGACY_GUARD,
189
- code: '',
190
- message: ''
191
- });
192
- } else if (result.reason === 'not_modified' &&
193
- policy.compareStableVersions(currentState.latestVersion, currentVersion()) > 0) {
194
- await setUpdateState({
195
- ...currentState,
196
- state: 'update_available',
197
- lastCheckedAt: checkedAt,
198
- postponeUntil: LEGACY_GUARD,
199
- code: '',
200
- message: ''
201
- });
202
- } else {
203
- await setUpdateState({
204
- ...currentState,
205
- state: 'idle',
206
- currentVersion: result.currentVersion || currentVersion(),
207
- latestVersion: result.latestVersion || result.currentVersion || currentVersion(),
208
- etag: result.etag || currentState.etag || '',
209
- lastCheckedAt: checkedAt,
210
- postponeUntil: LEGACY_GUARD,
211
- transactionId: '',
212
- code: '',
213
- message: ''
214
- });
166
+ const [currentState, consent] = await Promise.all([getUpdateState(), getConsentState()]);
167
+ assertOperation(operation);
168
+ if (currentState.cancelRequested || currentState.recoveryPending
169
+ || consent.authorizedVersion || policy.isExecutionState(currentState.state)) return getView();
170
+ operation.phase = 'checking';
171
+ await setUpdateState({ ...currentState, state: 'checking', operationId: operation.id,
172
+ cancelRequested: false, initiatedBy: manual ? 'manual' : 'automatic',
173
+ code: '', message: '', blocker: '', blockers: [], postponeUntil: LEGACY_GUARD }, { operation });
174
+ try {
175
+ const result = await withTimeout(requestNative('update.check', {
176
+ operationId: operation.id, currentVersion: currentVersion(), etag: currentState.etag || ''
177
+ }), CHECK_REQUEST_TIMEOUT_MS, codedError('update_check_timeout', 'The managed update check did not complete in time.'));
178
+ assertOperation(operation);
179
+ const checkedAt = Date.now();
180
+ if (result.available || (result.reason === 'not_modified'
181
+ && policy.compareStableVersions(currentState.latestVersion, currentVersion()) > 0)) {
182
+ const latestVersion = result.latestVersion || currentState.latestVersion;
183
+ await setConsentState({ ...consent,
184
+ ...(latestVersion !== consent.snoozedVersion ? { snoozedVersion: '', snoozedUntil: 0 } : {}),
185
+ lastPromptedVersion: latestVersion,
186
+ lastPromptedAt: manual ? checkedAt : consent.lastPromptedAt
187
+ }, { operation });
188
+ await setUpdateState({ ...currentState, state: 'update_available', operationId: operation.id,
189
+ currentVersion: result.currentVersion || currentVersion(), latestVersion,
190
+ managed: true, etag: result.etag || currentState.etag || '', lastCheckedAt: checkedAt,
191
+ postponeUntil: LEGACY_GUARD, code: '', message: '', cancelRequested: false
192
+ }, { operation });
193
+ } else if (result.reason === 'manual_install_required'
194
+ && policy.compareStableVersions(result.latestVersion, currentVersion()) > 0) {
195
+ await setUpdateState({ ...currentState, state: 'manual_install_required', operationId: operation.id,
196
+ currentVersion: result.currentVersion || currentVersion(), latestVersion: result.latestVersion,
197
+ managed: true, etag: result.etag || currentState.etag || '', lastCheckedAt: checkedAt,
198
+ postponeUntil: LEGACY_GUARD, code: 'update_bootstrap_upgrade_required', message: '',
199
+ manualReason: result.manualInstall?.reason || {}, cancelRequested: false
200
+ }, { operation });
201
+ } else {
202
+ await setUpdateState({ ...currentState, state: 'idle', operationId: operation.id,
203
+ currentVersion: result.currentVersion || currentVersion(),
204
+ latestVersion: result.latestVersion || result.currentVersion || currentVersion(),
205
+ etag: result.etag || currentState.etag || '', lastCheckedAt: checkedAt,
206
+ postponeUntil: LEGACY_GUARD, transactionId: '', code: '', message: '', cancelRequested: false
207
+ }, { operation });
208
+ }
209
+ return getView();
210
+ } catch (error) {
211
+ if (!isCurrentOperation(operation)) return getView();
212
+ await requestNative('update.cancel', { operationId: operation.id }).catch(() => {});
213
+ assertOperation(operation);
214
+ await setUpdateState({ ...currentState, state: 'failed', operationId: operation.id,
215
+ initiatedBy: manual ? 'manual' : 'automatic', postponeUntil: LEGACY_GUARD,
216
+ code: safeCode(error), message: safeMessage(error)
217
+ }, { operation });
218
+ throw error;
215
219
  }
216
- return getView();
217
- } catch (error) {
218
- await setUpdateState({
219
- ...currentState,
220
- state: 'failed',
221
- initiatedBy: manual ? 'manual' : 'automatic',
222
- postponeUntil: LEGACY_GUARD,
223
- code: safeCode(error),
224
- message: safeMessage(error)
225
- });
226
- throw error;
220
+ } finally {
221
+ if (!inheritedOperation) finishOperation(operation);
227
222
  }
228
223
  }
229
224
 
225
+
230
226
  async function recoverInterruptedCheck() {
231
227
  const state = await getUpdateState();
232
- if (state.state !== 'checking') return;
233
- const nextState = policy.compareStableVersions(state.latestVersion, currentVersion()) > 0
234
- ? 'update_available'
235
- : 'idle';
236
- await setUpdateState({
237
- ...state,
238
- state: nextState,
239
- blocker: '',
240
- blockers: [],
241
- code: '',
242
- message: ''
228
+ if (state.state !== 'checking' || activeOperation || state.cancelRequested) return;
229
+ if (state.operationId) {
230
+ await postponeUpdate().catch(() => {});
231
+ return;
232
+ }
233
+ await setUpdateState({ ...state,
234
+ state: policy.compareStableVersions(state.latestVersion, currentVersion()) > 0 ? 'update_available' : 'idle',
235
+ blocker: '', blockers: [], code: '', message: ''
243
236
  });
244
237
  }
245
238
 
239
+
246
240
  async function dismissCompletedUpdate() {
247
241
  const state = await getUpdateState();
248
242
  if (state.state !== 'committed') return getView();
@@ -263,194 +257,177 @@
263
257
  }
264
258
 
265
259
  async function installUpdate() {
266
- let state = await getUpdateState();
267
- let consent = await getConsentState();
268
- const candidateStale = !state.lastCheckedAt || Date.now() - state.lastCheckedAt > CANDIDATE_MAX_AGE_MS;
269
- if (state.state !== 'update_available' || candidateStale) {
270
- await checkOnly({ manual: true });
260
+ const operation = beginOperation();
261
+ let state, consent, authorizationId = '';
262
+ try {
271
263
  state = await getUpdateState();
272
264
  consent = await getConsentState();
273
- }
274
- if (state.state !== 'update_available' ||
275
- policy.compareStableVersions(state.latestVersion, currentVersion()) <= 0) {
276
- throw codedError('update_candidate_missing', 'No newer signed stable update is available.');
277
- }
278
-
279
- const authorizationId = crypto.randomUUID();
280
- let authorizationIntent = revocation.prepareAuthorization(
281
- consent,
282
- authorizationId,
283
- state.latestVersion,
284
- Date.now()
285
- );
286
- try {
287
- authorizationIntent = await setConsentState(authorizationIntent);
265
+ assertOperation(operation);
266
+ if (state.cancelRequested || state.recoveryPending || policy.isExecutionState(state.state)) return getView();
267
+ const candidateStale = !state.lastCheckedAt || Date.now() - state.lastCheckedAt > CANDIDATE_MAX_AGE_MS;
268
+ if (state.state !== 'update_available' || candidateStale) {
269
+ await checkOnly({ manual: true }, operation);
270
+ assertOperation(operation);
271
+ state = await getUpdateState();
272
+ consent = await getConsentState();
273
+ assertOperation(operation);
274
+ }
275
+ // The fresh check found a release only a reinstall can apply; show that notice.
276
+ if (state.state === 'manual_install_required') return getView();
277
+ if (state.state !== 'update_available'
278
+ || policy.compareStableVersions(state.latestVersion, currentVersion()) <= 0) {
279
+ throw codedError('update_candidate_missing', 'No newer signed stable update is available.');
280
+ }
281
+ authorizationId = crypto.randomUUID();
282
+ operation.authorizationId = authorizationId;
283
+ operation.targetVersion = state.latestVersion;
284
+ const intent = revocation.prepareAuthorization(consent, authorizationId, state.latestVersion, Date.now());
285
+ await setConsentState(intent, { operation });
286
+ assertOperation(operation);
288
287
  await requestNative('update.authorize', {
289
- authorizationId,
290
- targetVersion: state.latestVersion,
291
- currentVersion: currentVersion()
288
+ operationId: operation.id, authorizationId, targetVersion: state.latestVersion, currentVersion: currentVersion()
292
289
  });
293
- await setConsentState(revocation.clear({
294
- ...authorizationIntent,
295
- snoozedVersion: '',
296
- snoozedUntil: 0,
297
- authorizedVersion: state.latestVersion,
298
- authorizationId,
299
- authorizedAt: Date.now()
300
- }));
301
- await setUpdateState({ ...state, postponeUntil: 0, code: '', message: '' });
302
- const executor = globalThis.CodexOverleafManagedUpdateExecutor;
303
- if (!executor || typeof executor.installAuthorizedUpdate !== 'function') {
304
- throw codedError(
305
- 'update_executor_unavailable',
306
- 'The managed update executor is unavailable.'
307
- );
308
- }
309
- await executor.installAuthorizedUpdate();
290
+ assertOperation(operation);
291
+ await setConsentState(revocation.clear({ ...intent, snoozedVersion: '', snoozedUntil: 0,
292
+ authorizedVersion: state.latestVersion, authorizationId, authorizedAt: Date.now()
293
+ }), { operation });
294
+ await setUpdateState({ ...state, operationId: operation.id, postponeUntil: LEGACY_GUARD,
295
+ cancelRequested: false, recoveryPending: false, code: '', message: ''
296
+ }, { operation });
297
+ // Replaceable runtime owns this transfer. The immutable Bootstrap keeps
298
+ // its health-confirmation role and cannot resume a postponed operation.
299
+ await stageAuthorizedUpdate(operation);
310
300
  return getView();
311
301
  } catch (error) {
312
- const observedConsent = await getConsentState().catch(() => consent);
313
- const pendingConsent = revocation.prepareAuthorization({
314
- ...observedConsent,
315
- authorizedVersion: state.latestVersion,
316
- authorizationId,
317
- authorizedAt: observedConsent.authorizedAt || Date.now()
318
- }, authorizationId, state.latestVersion, Date.now());
319
- await setConsentState(pendingConsent);
320
- try {
321
- await requestNative('update.revoke', {
322
- authorizationId: pendingConsent.revokingAuthorizationId,
323
- targetVersion: pendingConsent.revokingVersion,
324
- transactionId: pendingConsent.revokingTransactionId
325
- });
326
- await setConsentState(revocation.clear({
327
- ...pendingConsent,
328
- authorizedVersion: '',
329
- authorizationId: '',
330
- authorizedAt: 0
331
- }));
332
- } catch (revokeError) {
333
- if (error && typeof error === 'object') {
302
+ if (!isCurrentOperation(operation)) return getView();
303
+ if (authorizationId) {
304
+ const observed = await getConsentState();
305
+ assertOperation(operation);
306
+ const pending = revocation.prepareAuthorization({ ...observed,
307
+ authorizedVersion: state.latestVersion, authorizationId,
308
+ authorizedAt: observed.authorizedAt || Date.now()
309
+ }, authorizationId, state.latestVersion, Date.now());
310
+ await setConsentState(pending, { operation });
311
+ try {
312
+ await requestNative('update.revoke', {
313
+ operationId: operation.id, authorizationId, targetVersion: state.latestVersion
314
+ });
315
+ assertOperation(operation);
316
+ await setConsentState(revocation.clear({ ...pending,
317
+ authorizedVersion: '', authorizationId: '', authorizedAt: 0
318
+ }), { operation });
319
+ } catch (revokeError) {
320
+ if (!isCurrentOperation(operation)) return getView();
321
+ operation.revocationPending = true;
334
322
  error.revocationError = safeMessage(revokeError);
335
323
  }
336
324
  }
337
- const observed = await getUpdateState().catch(() => state);
338
- if (!['awaiting_health', 'committed', 'rolled_back'].includes(observed.state)) {
339
- await setUpdateState({
340
- ...observed,
341
- state: 'failed',
342
- initiatedBy: 'manual',
343
- blocker: '',
344
- blockers: [],
345
- code: safeCode(error),
346
- message: safeMessage(error)
347
- });
325
+ const observedState = await getUpdateState();
326
+ assertOperation(operation);
327
+ if (!['awaiting_health', 'committed', 'rolled_back'].includes(observedState.state)) {
328
+ await setUpdateState({ ...observedState, state: 'failed', initiatedBy: 'manual',
329
+ blocker: '', blockers: [], code: safeCode(error), message: safeMessage(error),
330
+ operationId: operation.id,
331
+ cancelRequested: operation.phase !== 'applying' && operation.revocationPending === true,
332
+ recoveryPending: operation.phase === 'applying'
333
+ }, { operation });
348
334
  }
349
- await armLegacyGuard();
350
335
  throw error;
351
- }
336
+ } finally { finishOperation(operation); }
352
337
  }
353
338
 
354
- async function stageAuthorizedUpdate() {
355
- const current = await getUpdateState();
356
- await setUpdateState({
357
- ...current,
358
- state: 'downloading',
359
- initiatedBy: 'manual',
360
- blocker: '',
361
- blockers: [],
362
- code: '',
363
- message: ''
364
- });
365
- const staged = await requestNative('update.stage');
366
- await setUpdateState({
367
- ...current,
368
- state: 'staged',
369
- initiatedBy: 'manual',
370
- latestVersion: staged.targetVersion,
371
- transactionId: staged.transactionId,
372
- stagedAt: Date.now(),
373
- blocker: '',
374
- blockers: [],
375
- code: '',
376
- message: ''
377
- });
378
- return tryActivateStagedUpdate();
339
+
340
+ async function stageAuthorizedUpdate(operation) {
341
+ operation.phase = 'downloading';
342
+ await setUpdateState({ state: 'downloading', operationId: operation.id,
343
+ initiatedBy: 'manual', blocker: '', blockers: [], code: '', message: '',
344
+ postponeUntil: LEGACY_GUARD
345
+ }, { operation });
346
+ assertOperation(operation);
347
+ const staged = await requestNative('update.stage', { operationId: operation.id });
348
+ assertOperation(operation);
349
+ await setUpdateState({ state: 'staged', operationId: operation.id, initiatedBy: 'manual',
350
+ latestVersion: staged.targetVersion, transactionId: staged.transactionId, stagedAt: Date.now(),
351
+ blocker: '', blockers: [], code: '', message: '', postponeUntil: LEGACY_GUARD
352
+ }, { operation });
353
+ return tryActivateStagedUpdate(operation);
379
354
  }
380
355
 
381
- function tryActivateStagedUpdate() {
382
- const executor = root.CodexOverleafManagedUpdateExecutor;
383
- if (typeof executor?.installAuthorizedUpdate === 'function') {
384
- if (activationPromise) return activationPromise;
385
- activationPromise = Promise.resolve(executor.installAuthorizedUpdate()).finally(() => {
386
- activationPromise = null;
387
- });
388
- return activationPromise;
389
- }
356
+
357
+ async function tryActivateStagedUpdate(inheritedOperation) {
390
358
  if (activationPromise) return activationPromise;
391
- activationPromise = tryActivateStagedUpdateCore().finally(() => {
359
+ const state = await getUpdateState();
360
+ if (state.cancelRequested || state.recoveryPending || cancellationPromise
361
+ || !['staged', 'waiting_for_idle'].includes(state.state)) return state;
362
+ const operation = inheritedOperation || beginOperation(state.operationId);
363
+ assertOperation(operation);
364
+ activationPromise = tryActivateStagedUpdateCore(operation).finally(() => {
392
365
  activationPromise = null;
366
+ if (!inheritedOperation) finishOperation(operation);
393
367
  });
394
368
  return activationPromise;
395
369
  }
396
370
 
397
- async function tryActivateStagedUpdateCore() {
371
+
372
+ async function tryActivateStagedUpdateCore(operation) {
398
373
  clearIdleRetryTimer();
399
374
  const state = await getUpdateState();
400
- if (!['staged', 'waiting_for_idle'].includes(state.state)) {
401
- await chrome.alarms?.clear?.(IDLE_ALARM).catch(() => {});
402
- return state;
403
- }
404
- const surfaceTabs = await chrome.tabs.query({ url: OVERLEAF_MATCHES }).catch(() => []);
375
+ assertOperation(operation);
376
+ if (state.cancelRequested || !['staged', 'waiting_for_idle'].includes(state.state)) return state;
377
+ operation.phase = 'waiting_for_idle';
378
+ const surfaceTabs = await chrome.tabs.query({ url: OVERLEAF_MATCHES });
405
379
  const editorTabs = surfaceTabs.filter(isUsableEditorTab);
406
- const reloadTabs = surfaceTabs
407
- .filter(tab => Number.isInteger(tab?.id) && !tab.discarded && tab.status !== 'unloaded')
408
- .map(tab => tab.id);
380
+ const reloadTabs = surfaceTabs.filter(tab => Number.isInteger(tab?.id) && !tab.discarded && tab.status !== 'unloaded').map(tab => tab.id);
409
381
  const probes = await Promise.all(editorTabs.map(tab => probeTabIdle(tab.id)));
410
382
  const nativeGate = await requestNative('update.canApply')
411
- .then(result => ({ ok: true, result }))
412
- .catch(error => ({ ok: false, error: safeError(error) }));
383
+ .then(result => ({ ok: true, result })).catch(error => ({ ok: false, error: safeError(error) }));
384
+ assertOperation(operation);
413
385
  const blockers = root.CodexOverleafUpdateStatus?.collectBlockers(probes, nativeGate) || ['busy'];
386
+ if (nativeBridge?.getPendingState?.().executionRequests > 0) blockers.push('background_execution_pending');
414
387
  if (blockers.length) {
415
- const waiting = await setUpdateState({
416
- ...state,
417
- state: 'waiting_for_idle',
418
- blocker: blockers[0],
419
- blockers
420
- });
421
- scheduleIdleRetry(blockers);
388
+ const waiting = await setUpdateState({ state: 'waiting_for_idle',
389
+ operationId: operation.id, blocker: blockers[0], blockers
390
+ }, { operation });
391
+ if (isCurrentOperation(operation)) scheduleIdleRetry(blockers);
422
392
  return waiting;
423
393
  }
424
-
394
+ operation.phase = 'applying';
425
395
  await chrome.alarms?.clear?.(IDLE_ALARM).catch(() => {});
396
+ assertOperation(operation);
426
397
  await chrome.storage.local.set({ [UPDATE_RELOAD_TABS_KEY]: reloadTabs });
427
- await setUpdateState({
428
- ...state,
429
- state: 'applying',
430
- blocker: '',
431
- blockers: [],
432
- code: '',
433
- message: ''
434
- });
435
- const activation = await requestNative('update.activate', {
436
- transactionId: state.transactionId
437
- });
438
- const transaction = await waitForActivatedTransaction(activation.transactionId || state.transactionId);
439
- if (transaction.state === 'rolled_back') {
440
- throw codedError(transaction.reasonCode || 'update_apply_failed', 'The managed update was rolled back during activation.');
398
+ await setUpdateState({ state: 'applying', operationId: operation.id,
399
+ blocker: '', blockers: [], code: '', message: ''
400
+ }, { operation });
401
+ assertOperation(operation);
402
+ // requestInternal rechecks the safe point and blocks new native work.
403
+ let applied;
404
+ try {
405
+ applied = await requestNative('update.apply', {
406
+ operationId: operation.id, transactionId: state.transactionId
407
+ });
408
+ } catch (error) {
409
+ assertOperation(operation);
410
+ if (!['update_not_idle', 'update_native_busy'].includes(error.code)) throw error;
411
+ operation.phase = 'waiting_for_idle';
412
+ const waiting = await setUpdateState({ state: 'waiting_for_idle',
413
+ blocker: 'busy', blockers: ['busy'], recoveryPending: false
414
+ }, { operation, observed: true });
415
+ scheduleIdleRetry(waiting.blockers);
416
+ return waiting;
441
417
  }
442
- await setUpdateState({
443
- ...state,
444
- state: 'awaiting_health',
445
- latestVersion: transaction.targetVersion,
446
- transactionId: transaction.id,
447
- code: '',
448
- message: ''
449
- });
418
+ assertOperation(operation);
419
+ if (applied.state !== 'awaiting_health' || applied.transactionId !== state.transactionId) {
420
+ throw codedError('update_apply_unconfirmed', 'Update activation did not return matching transaction evidence.');
421
+ }
422
+ await setUpdateState({ state: 'awaiting_health', operationId: operation.id,
423
+ latestVersion: applied.targetVersion, transactionId: applied.transactionId,
424
+ recoveryPending: false, code: '', message: ''
425
+ }, { operation });
450
426
  chrome.runtime.reload();
451
427
  return { state: 'awaiting_health' };
452
428
  }
453
429
 
430
+
454
431
  async function waitForActivatedTransaction(transactionId) {
455
432
  const deadline = Date.now() + ACTIVATION_TIMEOUT_MS;
456
433
  while (Date.now() <= deadline) {
@@ -486,7 +463,7 @@
486
463
  try {
487
464
  const url = new URL(tab.url || '');
488
465
  return url.protocol === 'https:' &&
489
- (url.hostname === 'www.overleaf.com' || url.hostname === 'overleaf.com') &&
466
+ (['overleaf.com', 'www.overleaf.com', 'cn.overleaf.com'].includes(url.hostname)) &&
490
467
  /^\/project\/[^/]+(?:\/|$)/.test(url.pathname);
491
468
  } catch (_error) {
492
469
  return false;
@@ -505,142 +482,177 @@
505
482
  }
506
483
  }
507
484
 
508
- async function postponeUpdate() {
485
+ // A reinstall notice has no download or authorization to cancel; Later only
486
+ // hides it for a day.
487
+ async function snoozeManualInstall() {
509
488
  const [state, consent] = await Promise.all([getUpdateState(), getConsentState()]);
510
- if (['applying', 'awaiting_health'].includes(state.state)) {
511
- throw codedError('update_revoke_too_late', 'The update is already being installed.');
512
- }
513
- let pendingConsent = consent;
514
- if (consent.authorizationId) {
515
- pendingConsent = revocation.begin(consent, state, Date.now());
516
- await setConsentState(pendingConsent);
517
- try {
518
- await requestNative('update.revoke', {
519
- authorizationId: pendingConsent.revokingAuthorizationId,
520
- targetVersion: pendingConsent.revokingVersion,
521
- transactionId: pendingConsent.revokingTransactionId
522
- });
523
- } catch (error) {
524
- if (error?.code === 'update_revoke_too_late') {
525
- await setConsentState(revocation.clear(pendingConsent));
526
- return getView();
527
- }
528
- throw error;
529
- }
530
- } else if (['staged', 'waiting_for_idle'].includes(state.state)) {
531
- throw codedError('update_consent_mismatch', 'The staged update has no matching runtime authorization.');
532
- }
533
-
534
- clearIdleRetryTimer();
535
- await chrome.alarms?.clear?.(IDLE_ALARM).catch(() => {});
536
- const completed = revocation.complete(state, pendingConsent, {
537
- now: Date.now(),
538
- snoozeMs: SNOOZE_MS,
539
- postponeUntil: LEGACY_GUARD
540
- });
541
- await setUpdateAndConsentState(completed.updateState, completed.consentState);
489
+ if (state.state !== 'manual_install_required') return null;
490
+ await setConsentState({ ...consent, snoozedVersion: state.latestVersion, snoozedUntil: Date.now() + SNOOZE_MS });
542
491
  return getView();
543
492
  }
544
493
 
545
- async function reconcileRecoveryState() {
546
- await reconcilePendingRevocation();
547
- return reconcileExpiredPhase();
494
+ function postponeUpdate() {
495
+ if (cancellationPromise) return cancellationPromise;
496
+ cancellationPromise = cancelUpdateCore().finally(() => { cancellationPromise = null; });
497
+ return cancellationPromise;
548
498
  }
549
499
 
550
- async function reconcilePendingRevocation() {
500
+ async function cancelUpdateCore() {
551
501
  const [state, consent] = await Promise.all([getUpdateState(), getConsentState()]);
552
- if (!revocation.hasPending(consent)) return state;
553
-
502
+ if (['applying', 'awaiting_health', 'rolling_back'].includes(state.state)
503
+ || activeOperation?.phase === 'applying' || state.recoveryPending) {
504
+ throw codedError('update_revoke_too_late', 'The update is being installed or recovered.');
505
+ }
506
+ const operation = activeOperation;
507
+ const generation = ++operationGeneration;
508
+ activeOperation = null;
509
+ clearIdleRetryTimer();
510
+ const operationId = operation?.id || state.operationId || '';
511
+ const authorizationId = consent.authorizationId || consent.revokingAuthorizationId || operation?.authorizationId || '';
512
+ const targetVersion = consent.authorizedVersion || consent.revokingVersion || operation?.targetVersion || state.latestVersion;
513
+ const pending = authorizationId ? revocation.begin({ ...consent, authorizationId,
514
+ authorizedVersion: targetVersion }, state, Date.now()) : consent;
515
+ await setUpdateAndConsentState({ ...state, operationId, cancelRequested: true,
516
+ postponeUntil: LEGACY_GUARD, code: '', message: ''
517
+ }, pending, { generation });
518
+ await chrome.alarms?.clear?.(IDLE_ALARM).catch(() => {});
554
519
  try {
555
- await requestNative('update.revoke', {
556
- authorizationId: consent.revokingAuthorizationId,
557
- targetVersion: consent.revokingVersion,
558
- transactionId: consent.revokingTransactionId
559
- });
560
- } catch (error) {
561
- if (error?.code === 'update_revoke_too_late') {
562
- await setConsentState(revocation.clear(consent));
563
- return state;
520
+ if (operationId) {
521
+ const result = await withTimeout(requestNative('update.cancel', { operationId }), 15000,
522
+ codedError('update_cancel_unconfirmed', 'Update cancellation has not been acknowledged yet.'));
523
+ if (result.state !== 'cancelled' || result.operationId !== operationId) {
524
+ throw codedError('update_cancel_unconfirmed', 'Update cancellation returned incomplete evidence.');
525
+ }
526
+ } else if (state.state === 'downloading') {
527
+ throw codedError('update_cancel_unconfirmed', 'This older download has no operation identity. Reload the extension to recover it.');
528
+ }
529
+ if (authorizationId) {
530
+ await withTimeout(requestNative('update.revoke', {
531
+ operationId, authorizationId, targetVersion
532
+ }), 15000, codedError('update_cancel_unconfirmed', 'Update authorization cleanup has not finished yet.'));
564
533
  }
565
- if (!['update_already_revoked', 'update_authorization_revoked'].includes(error?.code)) {
566
- return state;
534
+ const completed = revocation.complete(state, pending, { now: Date.now(),
535
+ snoozeMs: SNOOZE_MS, postponeUntil: LEGACY_GUARD });
536
+ await setUpdateAndConsentState({ ...completed.updateState,
537
+ state: policy.compareStableVersions(state.latestVersion, currentVersion()) > 0 ? 'update_available' : 'idle',
538
+ operationId: '', cancelRequested: false, recoveryPending: false,
539
+ code: 'update_cancelled', message: ''
540
+ }, completed.consentState, { generation, observed: true });
541
+ return getView();
542
+ } catch (error) {
543
+ if (error.code === 'update_revoke_too_late') {
544
+ await setUpdateAndConsentState({ cancelRequested: false, recoveryPending: true,
545
+ state: 'failed', code: 'update_recovery_pending', message: 'The update reached installation before cancellation. Checking its result.'
546
+ }, revocation.clear(pending), { generation, observed: true });
547
+ } else {
548
+ await setUpdateState({ cancelRequested: true, code: 'update_cancel_unconfirmed',
549
+ message: safeMessage(error)
550
+ }, { generation });
567
551
  }
552
+ throw error;
568
553
  }
554
+ }
569
555
 
570
- clearIdleRetryTimer();
571
- await chrome.alarms?.clear?.(IDLE_ALARM).catch(() => {});
572
- const completed = revocation.complete(state, consent, {
573
- now: Date.now(),
574
- snoozeMs: SNOOZE_MS,
575
- postponeUntil: LEGACY_GUARD
576
- });
577
- const settled = await setUpdateAndConsentState(
578
- completed.updateState,
579
- completed.consentState
580
- );
581
- return settled.updateState;
556
+
557
+ function reconcileRecoveryState() {
558
+ if (recoveryPromise) return recoveryPromise;
559
+ recoveryPromise = (async () => {
560
+ const state = await getUpdateState();
561
+ if (state.recoveryPending) return reconcileExpiredPhase();
562
+ if (state.cancelRequested) return postponeUpdate();
563
+ if (!activeOperation) await reconcilePendingRevocation();
564
+ return reconcileExpiredPhase();
565
+ })().finally(() => { recoveryPromise = null; });
566
+ return recoveryPromise;
567
+ }
568
+
569
+
570
+ async function reconcilePendingRevocation() {
571
+ const consent = await getConsentState();
572
+ if (!revocation.hasPending(consent) || activeOperation) return getUpdateState();
573
+ return postponeUpdate().then(view => view.state);
582
574
  }
583
575
 
576
+
584
577
  async function settleTerminalConsent() {
585
578
  const [state, consent] = await Promise.all([getUpdateState(), getConsentState()]);
586
- if (!policy.isTerminalState(state.state) || !consent.authorizationId) return;
587
- await setConsentState({
588
- ...consent,
589
- authorizedVersion: '',
590
- authorizationId: '',
591
- authorizedAt: 0
592
- });
579
+ if (!policy.isTerminalState(state.state) || state.cancelRequested || state.recoveryPending
580
+ || revocation.hasPending(consent) || !consent.authorizationId) return;
581
+ await setConsentState({ ...consent, authorizedVersion: '', authorizationId: '', authorizedAt: 0 },
582
+ { expectedAuthorizationId: consent.authorizationId });
593
583
  }
594
584
 
585
+
595
586
  async function reconcileExpiredPhase() {
596
587
  const state = await getUpdateState();
597
- if (!state.deadlineAt || Date.now() <= state.deadlineAt || policy.isTerminalState(state.state)) {
598
- return state;
588
+ if (!state.recoveryPending && (!state.deadlineAt || Date.now() <= state.deadlineAt
589
+ || policy.isTerminalState(state.state))) return state;
590
+ if (['checking', 'downloading'].includes(state.state)) {
591
+ await postponeUpdate();
592
+ return setUpdateState({ state: 'failed', cancelRequested: false,
593
+ code: 'update_phase_timeout', message: 'The update timed out and its download was stopped. Retry when the connection is available.'
594
+ }, { observed: true });
599
595
  }
600
- if (['applying', 'awaiting_health', 'rolling_back'].includes(state.state)) {
601
- const status = await requestNative('update.status').catch(() => null);
602
- const transaction = status?.transaction;
603
- if (transaction?.state === 'awaiting_health') {
604
- await requestNative('update.rollback', {
605
- transactionId: transaction.id,
606
- reasonCode: 'update_health_timeout'
607
- }).catch(() => null);
608
- return setUpdateState({
609
- ...state,
610
- state: 'failed',
611
- currentVersion: transaction.sourceVersion || state.currentVersion,
612
- code: 'update_health_timeout',
613
- message: 'The updated runtime did not complete its health check and was rolled back. Retry, or use the manual update command.'
614
- }, { observed: true });
615
- }
616
- if (transaction?.state === 'committed') {
617
- return setUpdateState({
618
- ...state,
619
- state: 'committed',
620
- currentVersion: transaction.targetVersion,
621
- latestVersion: transaction.targetVersion,
622
- code: '',
623
- message: ''
624
- }, { observed: true });
625
- }
626
- if (transaction?.state === 'rolled_back') {
627
- return setUpdateState({
628
- ...state,
629
- state: 'rolled_back',
630
- currentVersion: transaction.sourceVersion || state.currentVersion,
631
- code: transaction.reasonCode || 'update_rolled_back',
632
- message: 'The managed update was rolled back after activation did not complete.'
596
+ if (state.recoveryPending || ['applying', 'awaiting_health', 'rolling_back'].includes(state.state)) {
597
+ try {
598
+ let status = await withTimeout(requestNative('update.status'), 10000,
599
+ codedError('update_status_timeout', 'Update recovery status is unavailable.'));
600
+ let transaction = status.transaction;
601
+ if (state.transactionId && transaction?.id !== state.transactionId) {
602
+ throw codedError('update_transaction_mismatch', 'The update recovery transaction could not be matched.');
603
+ }
604
+ if (transaction?.state === 'staged' || (!transaction && status.installedAligned
605
+ && status.activeVersion === state.currentVersion)) {
606
+ if (state.operationId) await requestNative('update.cancel', { operationId: state.operationId });
607
+ const consent = await getConsentState();
608
+ const authorizationId = consent.authorizationId || consent.revokingAuthorizationId;
609
+ const targetVersion = consent.authorizedVersion || consent.revokingVersion || state.latestVersion;
610
+ if (authorizationId) await requestNative('update.revoke', { authorizationId, targetVersion });
611
+ await setConsentState(revocation.clear({ ...consent,
612
+ authorizedVersion: '', authorizationId: '', authorizedAt: 0
613
+ }));
614
+ return setUpdateState({ state: 'failed', recoveryPending: false, cancelRequested: false,
615
+ operationId: '', code: 'update_not_applied', message: 'The update was stopped before installation. Retry when ready.'
616
+ }, { observed: true });
617
+ }
618
+ if (transaction?.state === 'awaiting_health') {
619
+ const rolledBack = await requestNative('update.rollback', {
620
+ transactionId: transaction.id, expectedState: 'awaiting_health', reasonCode: 'update_health_timeout'
621
+ });
622
+ if (rolledBack.state !== 'rolled_back' || rolledBack.version !== transaction.sourceVersion) {
623
+ throw codedError('update_rollback_unconfirmed', 'Rollback returned incomplete evidence.');
624
+ }
625
+ status = await requestNative('update.status');
626
+ transaction = status.transaction;
627
+ }
628
+ if (transaction?.state === 'committed' && status.installedAligned
629
+ && status.activeVersion === transaction.targetVersion) {
630
+ return setUpdateState({ state: 'committed', currentVersion: transaction.targetVersion,
631
+ latestVersion: transaction.targetVersion, transactionId: transaction.id,
632
+ recoveryPending: false, cancelRequested: false, code: '', message: ''
633
+ }, { observed: true });
634
+ }
635
+ if (transaction?.state === 'rolled_back' && status.installedAligned
636
+ && status.activeVersion === transaction.sourceVersion
637
+ && (!state.transactionId || transaction.id === state.transactionId)) {
638
+ const restored = await setUpdateState({ state: 'rolled_back', currentVersion: transaction.sourceVersion,
639
+ transactionId: transaction.id, recoveryPending: false, cancelRequested: false,
640
+ code: transaction.reasonCode || 'update_rolled_back', message: 'The previous version has been restored.'
641
+ }, { observed: true });
642
+ setTimeout(() => chrome.runtime.reload(), 0);
643
+ return restored;
644
+ }
645
+ throw codedError('update_recovery_pending', 'The installed update result has not been confirmed.');
646
+ } catch (error) {
647
+ return setUpdateState({ state: 'failed', recoveryPending: true, cancelRequested: false,
648
+ code: safeCode(error), message: safeMessage(error)
633
649
  }, { observed: true });
634
650
  }
635
651
  }
636
- return setUpdateState({
637
- ...state,
638
- state: 'failed',
639
- code: 'update_phase_timeout',
640
- message: 'The managed update phase did not complete in time. Retry, or use the manual update command.'
641
- });
652
+ return state;
642
653
  }
643
654
 
655
+
644
656
  async function handleObservedStateChange() {
645
657
  await settleTerminalConsent();
646
658
  const [state, consent] = await Promise.all([getUpdateState(), getConsentState()]);
@@ -653,14 +665,14 @@
653
665
  async function armLegacyGuard() {
654
666
  const [state, consent] = await Promise.all([getUpdateState(), getConsentState()]);
655
667
  if (consent.authorizationId || state.postponeUntil === LEGACY_GUARD) return state;
656
- return setUpdateState({ ...state, postponeUntil: LEGACY_GUARD });
668
+ return setUpdateState({ postponeUntil: LEGACY_GUARD });
657
669
  }
658
670
 
659
671
  async function getView() {
660
672
  let [state, consent] = await Promise.all([getUpdateState(), getConsentState()]);
661
- const busy = state.state === 'checking' || policy.isExecutionState(state.state) || consent.authorizationId;
673
+ const busy = state.state === 'checking' || policy.isExecutionState(state.state) || consent.authorizationId || state.cancelRequested;
662
674
  const runtime = busy ? { state: 'transaction_active' } : await readRuntimeStatus();
663
- if (runtime.state === 'aligned' && ['failed', 'rolled_back'].includes(state.state) &&
675
+ if (!state.recoveryPending && !state.cancelRequested && runtime.state === 'aligned' && ['failed', 'rolled_back'].includes(state.state) &&
664
676
  policy.compareStableVersions(state.latestVersion || state.currentVersion, runtime.installedVersion) <= 0) {
665
677
  state = await setUpdateState({ ...state, state: 'idle', currentVersion: runtime.installedVersion,
666
678
  latestVersion: runtime.installedVersion, code: '', message: '', transactionId: '', blocker: '', blockers: [] });
@@ -769,17 +781,23 @@
769
781
  return policy.normalizeUpdateState(stored?.[UPDATE_STATE_KEY], currentVersion());
770
782
  }
771
783
 
772
- async function setUpdateState(value, options = {}) {
773
- const stored = await chrome.storage.local.get(UPDATE_STATE_KEY);
774
- const previous = policy.normalizeUpdateState(stored?.[UPDATE_STATE_KEY], currentVersion());
775
- const next = projectUpdateState(previous, value, options);
776
- await chrome.storage.local.set({ [UPDATE_STATE_KEY]: next });
777
- return next;
784
+ function setUpdateState(value, options = {}) {
785
+ return enqueueStateWrite(async () => {
786
+ assertWriteOwner(options);
787
+ const stored = await chrome.storage.local.get(UPDATE_STATE_KEY);
788
+ assertWriteOwner(options);
789
+ const previous = policy.normalizeUpdateState(stored?.[UPDATE_STATE_KEY], currentVersion());
790
+ const next = projectUpdateState(previous, value, options);
791
+ await chrome.storage.local.set({ [UPDATE_STATE_KEY]: next });
792
+ return next;
793
+ });
778
794
  }
779
795
 
796
+
780
797
  function projectUpdateState(previous, value, options = {}) {
781
798
  const now = Date.now();
782
799
  const candidate = {
800
+ ...previous,
783
801
  ...value,
784
802
  initiatedBy: value.initiatedBy || previous.initiatedBy
785
803
  };
@@ -805,24 +823,35 @@
805
823
  return policy.normalizeConsentState(stored?.[CONSENT_STATE_KEY]);
806
824
  }
807
825
 
808
- async function setConsentState(value) {
809
- const next = policy.normalizeConsentState(value);
810
- await chrome.storage.local.set({ [CONSENT_STATE_KEY]: next });
811
- return next;
826
+ function setConsentState(value, options = {}) {
827
+ return enqueueStateWrite(async () => {
828
+ assertWriteOwner(options);
829
+ if (options.expectedAuthorizationId) {
830
+ const current = await getConsentState();
831
+ if (current.authorizationId !== options.expectedAuthorizationId) return current;
832
+ }
833
+ assertWriteOwner(options);
834
+ const next = policy.normalizeConsentState(value);
835
+ await chrome.storage.local.set({ [CONSENT_STATE_KEY]: next });
836
+ return next;
837
+ });
812
838
  }
813
839
 
814
- async function setUpdateAndConsentState(updateValue, consentValue, options = {}) {
815
- const stored = await chrome.storage.local.get([UPDATE_STATE_KEY, CONSENT_STATE_KEY]);
816
- const previous = policy.normalizeUpdateState(stored?.[UPDATE_STATE_KEY], currentVersion());
817
- const updateState = projectUpdateState(previous, updateValue, options);
818
- const consentState = policy.normalizeConsentState(consentValue);
819
- await chrome.storage.local.set({
820
- [UPDATE_STATE_KEY]: updateState,
821
- [CONSENT_STATE_KEY]: consentState
840
+
841
+ function setUpdateAndConsentState(updateValue, consentValue, options = {}) {
842
+ return enqueueStateWrite(async () => {
843
+ assertWriteOwner(options);
844
+ const stored = await chrome.storage.local.get([UPDATE_STATE_KEY, CONSENT_STATE_KEY]);
845
+ assertWriteOwner(options);
846
+ const previous = policy.normalizeUpdateState(stored?.[UPDATE_STATE_KEY], currentVersion());
847
+ const updateState = projectUpdateState(previous, updateValue, options);
848
+ const consentState = policy.normalizeConsentState(consentValue);
849
+ await chrome.storage.local.set({ [UPDATE_STATE_KEY]: updateState, [CONSENT_STATE_KEY]: consentState });
850
+ return { updateState, consentState };
822
851
  });
823
- return { updateState, consentState };
824
852
  }
825
853
 
854
+
826
855
  async function requestNative(method, params = {}) {
827
856
  const response = await nativeBridge?.requestInternal?.({
828
857
  id: crypto.randomUUID(),
@@ -839,11 +868,45 @@
839
868
  }
840
869
 
841
870
  function enqueuePolicyAction(action) {
842
- const result = policyTail.then(action, action);
871
+ const generation = operationGeneration;
872
+ const guarded = () => generation !== operationGeneration || cancellationPromise ? getView() : action();
873
+ const result = policyTail.then(guarded, guarded);
843
874
  policyTail = result.catch(() => {});
844
875
  return result;
845
876
  }
846
877
 
878
+ function beginOperation(id) {
879
+ const operation = { id: id || crypto.randomUUID(), generation: ++operationGeneration, phase: 'checking' };
880
+ activeOperation = operation;
881
+ return operation;
882
+ }
883
+
884
+ function isCurrentOperation(operation) {
885
+ return activeOperation === operation && operation.generation === operationGeneration;
886
+ }
887
+
888
+ function assertOperation(operation) {
889
+ if (!isCurrentOperation(operation)) throw codedError('update_network_cancelled', 'The update request was cancelled.');
890
+ }
891
+
892
+ function finishOperation(operation) {
893
+ if (activeOperation === operation) activeOperation = null;
894
+ }
895
+
896
+ function assertWriteOwner(options) {
897
+ if (options.operation) assertOperation(options.operation);
898
+ if (options.generation !== undefined && options.generation !== operationGeneration) {
899
+ throw codedError('update_network_cancelled', 'The update request was superseded.');
900
+ }
901
+ }
902
+
903
+ function enqueueStateWrite(action) {
904
+ const result = stateWriteTail.then(action, action);
905
+ stateWriteTail = result.catch(() => {});
906
+ return result;
907
+ }
908
+
909
+
847
910
  function isAllowedSender(sender) {
848
911
  if (sender?.id !== chrome.runtime.id) return false;
849
912
  try {
@@ -853,7 +916,7 @@
853
916
  return url.pathname === '/bootstrap/popup.html';
854
917
  }
855
918
  return url.protocol === 'https:' &&
856
- (url.hostname === 'www.overleaf.com' || url.hostname === 'overleaf.com') &&
919
+ (['overleaf.com', 'www.overleaf.com', 'cn.overleaf.com'].includes(url.hostname)) &&
857
920
  (url.pathname === '/project' || url.pathname.startsWith('/project/'));
858
921
  } catch (_error) {
859
922
  return false;