codex-overleaf-link 2.3.5 → 2.4.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 (57) hide show
  1. package/README.md +63 -31
  2. package/README.zh-CN.md +543 -0
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/src/background.js +7 -0
  5. package/extension/src/backgroundUpdateCoordinator.js +88 -4
  6. package/extension/src/content/activeTurnControl.js +1 -0
  7. package/extension/src/content/assetTransferBroker.js +2 -0
  8. package/extension/src/content/contentRuntime.js +77 -57
  9. package/extension/src/content/contextTray.js +4 -0
  10. package/extension/src/content/diagnosticsController.js +17 -3
  11. package/extension/src/content/diagnosticsPanel.js +129 -5
  12. package/extension/src/content/generated/content.bundle.js +1422 -348
  13. package/extension/src/content/generated/content.bundle.meta.json +7 -3
  14. package/extension/src/content/legacyGlobalRegistry.js +3 -0
  15. package/extension/src/content/modelPicker.js +21 -9
  16. package/extension/src/content/modelPickerSupport.js +38 -1
  17. package/extension/src/content/nativeCompatibilityController.js +4 -1
  18. package/extension/src/content/otWarmMirror.js +126 -92
  19. package/extension/src/content/otWarmMirrorController.js +122 -1
  20. package/extension/src/content/pageBridgeClient.js +2 -0
  21. package/extension/src/content/panelMaintenance.js +26 -15
  22. package/extension/src/content/providerSettingsCoordinator.js +1 -0
  23. package/extension/src/content/runFailureNotice.js +104 -0
  24. package/extension/src/content/runResultActions.js +16 -1
  25. package/extension/src/content/runScrollLayout.js +124 -0
  26. package/extension/src/content/runTimelineView.js +37 -11
  27. package/extension/src/content/sessionForkController.js +20 -11
  28. package/extension/src/content/settingsPanel.js +28 -11
  29. package/extension/src/content/trackedChangeCaptureController.js +153 -0
  30. package/extension/src/content/updateNotice.js +24 -4
  31. package/extension/src/content/writebackOrchestrator.js +31 -0
  32. package/extension/src/page/binaryAssetUploader.js +101 -29
  33. package/extension/src/page/overleafRealtimeObserver.js +120 -25
  34. package/extension/src/page/textFileCreator.js +493 -0
  35. package/extension/src/page/trackedChangeCapture.js +342 -0
  36. package/extension/src/page/trackedChangesLifecycle.js +145 -149
  37. package/extension/src/page/treeOperations.js +30 -2
  38. package/extension/src/page/writebackRouter.js +94 -48
  39. package/extension/src/pageBridge.js +62 -25
  40. package/extension/src/shared/compatibility.js +1 -1
  41. package/extension/src/shared/i18n.js +77 -37
  42. package/extension/src/shared/managedUpdateProjection.js +1 -1
  43. package/extension/src/shared/otText.js +0 -1
  44. package/extension/src/shared/pageRpcContract.js +2 -1
  45. package/extension/src/shared/sessionState.js +23 -23
  46. package/extension/src/shared/sessionTitle.js +120 -0
  47. package/extension/src/shared/storageDb.js +10 -12
  48. package/extension/src/shared/storageRunActions.js +29 -1
  49. package/extension/src/shared/updateRuntimeIdentity.js +32 -0
  50. package/extension/src/shared/writebackSettlement.js +19 -5
  51. package/extension/styles/panel.css +128 -24
  52. package/native-host/src/mirrorWorkspace.js +20 -1
  53. package/native-host/src/releaseProxy.js +177 -0
  54. package/native-host/src/releaseTransport.js +154 -0
  55. package/native-host/src/taskRunnerRuntime.js +25 -0
  56. package/native-host/src/updateManager.js +40 -39
  57. package/package.json +5 -1
@@ -11,6 +11,8 @@ const {
11
11
  verifySignedReleaseManifest
12
12
  } = require('./updateTrust');
13
13
  const { extractVerifiedUpdateBundle } = require('./updateArchive');
14
+ const { requestRelease } = require('./releaseTransport');
15
+ const RUNNING_VERSION = require('../../package.json').version;
14
16
 
15
17
  const GITHUB_LATEST_URL = 'https://github.com/Ghqqqq/codex-overleaf-link/releases/latest';
16
18
  const GITHUB_RELEASE_DOWNLOAD_ROOT = 'https://github.com/Ghqqqq/codex-overleaf-link/releases/download';
@@ -175,11 +177,11 @@ function delay(milliseconds) {
175
177
  }
176
178
 
177
179
  async function checkForUpdate(context, params = {}, options = {}) {
178
- const fetchImpl = options.fetch || globalThis.fetch;
179
- if (typeof fetchImpl !== 'function') {
180
- throw updateError('update_network_unavailable', 'This Node runtime cannot check GitHub Releases.');
181
- }
180
+ const fetchImpl = options.fetch;
181
+ const network = { ...options.network, deadlineAt: Date.now() + 40000 };
182
182
  const currentVersion = normalizeCurrentVersion(params.currentVersion, context);
183
+ const authorized = readAuthorizedCandidate(context, currentVersion);
184
+ if (authorized) return authorized;
183
185
  const headers = {
184
186
  Accept: 'text/html,application/xhtml+xml',
185
187
  'User-Agent': 'codex-overleaf-link-updater'
@@ -187,11 +189,13 @@ async function checkForUpdate(context, params = {}, options = {}) {
187
189
  if (params.etag && typeof params.etag === 'string' && params.etag.length < 300) {
188
190
  headers['If-None-Match'] = params.etag;
189
191
  }
190
- const releaseResponse = await fetchWithTimeout(fetchImpl, GITHUB_LATEST_URL, {
192
+ const releaseResponse = await requestRelease(GITHUB_LATEST_URL, {
193
+ ...network, fetch: fetchImpl, stage: 'version_check',
191
194
  headers,
192
195
  method: 'HEAD',
193
- redirect: 'follow'
194
- }, 12000);
196
+ allowedHosts: new Set(['github.com']),
197
+ httpErrorCode: 'update_github_http_error'
198
+ });
195
199
  if (releaseResponse.status === 304) {
196
200
  const candidate = readJsonSafe(path.join(context.updatesRoot, CANDIDATE_FILE), null);
197
201
  if (candidate?.manifestBase64 && candidate?.signatureBase64) {
@@ -247,12 +251,12 @@ async function checkForUpdate(context, params = {}, options = {}) {
247
251
  const manifestBytes = await fetchReleaseAsset(
248
252
  fetchImpl,
249
253
  buildReleaseAssetUrl(releaseTag, 'release-manifest.json'),
250
- 256 * 1024
254
+ 256 * 1024, { ...network, stage: 'release_manifest' }
251
255
  );
252
256
  const signatureBytes = await fetchReleaseAsset(
253
257
  fetchImpl,
254
258
  buildReleaseAssetUrl(releaseTag, 'release-manifest.sig'),
255
- 16 * 1024
259
+ 16 * 1024, { ...network, stage: 'release_signature' }
256
260
  );
257
261
  const manifest = verifySignedReleaseManifest(manifestBytes, signatureBytes);
258
262
  if (manifest.version !== latestVersion || manifest.tag !== releaseTag) {
@@ -271,6 +275,24 @@ async function checkForUpdate(context, params = {}, options = {}) {
271
275
  return { managed: true, available: true, currentVersion, latestVersion, etag: candidate.etag };
272
276
  }
273
277
 
278
+ function readAuthorizedCandidate(context, currentVersion) {
279
+ const authorization = readAuthorization(context);
280
+ if (!authorization || !['authorized', 'bound'].includes(authorization.state)) return null;
281
+ const candidate = readJsonSafe(path.join(context.updatesRoot, CANDIDATE_FILE), null);
282
+ if (!candidate) throw updateError('update_candidate_missing', 'The authorized update candidate is missing.');
283
+ const manifest = verifySignedReleaseManifest(
284
+ Buffer.from(candidate.manifestBase64 || '', 'base64'),
285
+ Buffer.from(candidate.signatureBase64 || '', 'base64')
286
+ );
287
+ if (candidate.latestVersion !== manifest.version || authorization.targetVersion !== manifest.version ||
288
+ authorization.sourceVersion !== currentVersion || !isNewerStableVersion(manifest.version, currentVersion)) {
289
+ throw updateError('update_consent_mismatch', 'The signed candidate no longer matches the authorized update.');
290
+ }
291
+ requireAuthorization(context, manifest.version, ['authorized', 'bound']);
292
+ return { managed: true, available: true, currentVersion, latestVersion: manifest.version,
293
+ etag: candidate.etag || '', cached: true, authorized: true };
294
+ }
295
+
274
296
  function parseStableReleaseTag(value) {
275
297
  try {
276
298
  const url = new URL(String(value || ''));
@@ -388,8 +410,8 @@ async function stageCandidate(context, options = {}) {
388
410
  fs.mkdirSync(stageRoot, { recursive: true, mode: 0o700 });
389
411
  try {
390
412
  const archivePath = path.join(stageRoot, manifest.updateBundle.name);
391
- const fetchImpl = options.fetch || globalThis.fetch;
392
- const bundleBytes = await fetchReleaseAsset(fetchImpl, candidate.bundleUrl, manifest.updateBundle.size + 1);
413
+ const bundleBytes = await fetchReleaseAsset(options.fetch, candidate.bundleUrl, manifest.updateBundle.size + 1,
414
+ { ...options.network, totalTimeoutMs: 90000, timeoutMs: 30000, stage: 'update_bundle' });
393
415
  if (bundleBytes.length !== manifest.updateBundle.size) {
394
416
  throw updateError('update_bundle_size_mismatch', 'Downloaded update bundle size does not match the signed manifest.');
395
417
  }
@@ -764,6 +786,8 @@ function recoverAndReadStatus(context) {
764
786
  return {
765
787
  managed: true,
766
788
  activeVersion: readVersionPointer(context.nativeRoot, 'active-version'),
789
+ runtimeVersion: context.env?.CODEX_OVERLEAF_ACTIVE_VERSION || RUNNING_VERSION,
790
+ installedAligned: Boolean(alignedVersion),
767
791
  previousVersion: readVersionPointer(context.nativeRoot, 'previous-version'),
768
792
  transaction: journal ? publicTransaction(journal) : null,
769
793
  authorization: publicAuthorization(readAuthorization(context))
@@ -941,38 +965,15 @@ function rewriteManagedManifestVersion(manifestPath, version) {
941
965
  atomicWriteText(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
942
966
  }
943
967
 
944
- async function fetchReleaseAsset(fetchImpl, url, limit) {
968
+ async function fetchReleaseAsset(fetchImpl, url, limit, network = {}) {
945
969
  const parsed = new URL(String(url || ''));
946
970
  if (parsed.protocol !== 'https:' || parsed.hostname !== 'github.com' || !parsed.pathname.startsWith('/Ghqqqq/codex-overleaf-link/releases/download/')) {
947
971
  throw updateError('update_asset_url_forbidden', 'Release asset URL is outside the trusted repository.');
948
972
  }
949
- const response = await fetchWithTimeout(fetchImpl, parsed.href, { redirect: 'follow' }, 20000);
950
- if (!response.ok) throw updateError('update_asset_http_error', 'Release asset download failed with HTTP ' + response.status + '.');
951
- const finalUrl = new URL(response.url || parsed.href);
952
- if (finalUrl.protocol !== 'https:' || !RELEASE_ASSET_HOSTS.has(finalUrl.hostname)) {
953
- throw updateError('update_asset_redirect_forbidden', 'Release asset redirected to an untrusted host.');
954
- }
955
- return readResponseBytes(response, limit);
956
- }
957
-
958
- async function fetchWithTimeout(fetchImpl, url, options, timeoutMs) {
959
- const controller = new AbortController();
960
- const timer = setTimeout(() => controller.abort(), timeoutMs);
961
- try {
962
- return await fetchImpl(url, { ...options, signal: controller.signal });
963
- } catch (error) {
964
- throw updateError('update_network_failed', 'Update network request failed.', { cause: error });
965
- } finally {
966
- clearTimeout(timer);
967
- }
968
- }
969
-
970
- async function readResponseBytes(response, limit) {
971
- const declared = Number(response.headers?.get?.('content-length') || 0);
972
- if (declared > limit) throw updateError('update_download_limit', 'Update response exceeds its size limit.');
973
- const buffer = Buffer.from(await response.arrayBuffer());
974
- if (!buffer.length || buffer.length > limit) throw updateError('update_download_limit', 'Update response exceeds its size limit.');
975
- return buffer;
973
+ const response = await requestRelease(parsed.href, {
974
+ timeoutMs: 20000, ...network, fetch: fetchImpl, limit, allowedHosts: RELEASE_ASSET_HOSTS
975
+ });
976
+ return response.bytes;
976
977
  }
977
978
 
978
979
  function findReleaseAsset(assets, name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-overleaf-link",
3
- "version": "2.3.5",
3
+ "version": "2.4.0",
4
4
  "description": "Cross-platform Chrome bridge that connects Codex to the active Overleaf project.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -17,9 +17,12 @@
17
17
  "build:content": "node scripts/build-content-bundle.mjs",
18
18
  "check:module-graph": "node scripts/check-content-module-graph.mjs",
19
19
  "test": "npm run check:module-graph && node scripts/run-tests.mjs",
20
+ "test:fast": "npm run check:module-graph && node scripts/run-tests.mjs --fast",
21
+ "test:focus": "npm run check:module-graph && node scripts/run-tests.mjs",
20
22
  "check:architecture": "node scripts/check-architecture-budget.mjs --enforce-target",
21
23
  "benchmark:large": "node scripts/benchmark-large-project.mjs --gate",
22
24
  "smoke:extension": "npm run build:content && node scripts/smoke-extension.mjs",
25
+ "smoke:prepare:chrome": "npm run build:content && npm run test:fast && npm run install:managed",
23
26
  "verify:release": "node scripts/verify-release.mjs",
24
27
  "verify:source": "node scripts/verify-release.mjs --source-hygiene-only",
25
28
  "verify:release-artifacts": "node scripts/verify-release-artifacts.mjs",
@@ -60,6 +63,7 @@
60
63
  "scripts/uninstall-native-host.mjs",
61
64
  "scripts/verify-npm-package.mjs",
62
65
  "README.md",
66
+ "README.zh-CN.md",
63
67
  "package.json"
64
68
  ],
65
69
  "devDependencies": {