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
@@ -2,6 +2,9 @@
2
2
  if (typeof module === 'object' && module.exports) {
3
3
  module.exports = factory(root);
4
4
  } else {
5
+ if (typeof root.CodexOverleafRealtimeObserver?.dispose === 'function') {
6
+ root.CodexOverleafRealtimeObserver.dispose();
7
+ }
5
8
  root.CodexOverleafRealtimeObserver = factory(root);
6
9
  }
7
10
  })(typeof globalThis !== 'undefined' ? globalThis : window, function overleafRealtimeObserverFactory(root) {
@@ -12,8 +15,34 @@
12
15
  const BASELINE_REFRESH_EVENT_TYPES = ['click', 'focusin', 'change'];
13
16
  const CHANNEL_KEY_PATTERN = /socket|websocket|channel|realtime|collab|share|ot|doc|editor|connection|event|broadcast|presence/i;
14
17
  const SENSITIVE_KEY_PATTERN = /^(content|previousContent|nextContent|text|body|raw|rawContent|source|sourceText)$/i;
18
+ const LIMITS = Object.freeze({ maxFileBytes: 1024 * 1024, maxEvents: 64, maxQueuedBytes: 4 * 1024 * 1024 });
19
+ const encoder = typeof TextEncoder === 'function' ? new TextEncoder() : null;
20
+ const ownedObservers = new WeakMap();
15
21
 
16
22
  function create(deps = {}) {
23
+ const documentRef = Object.prototype.hasOwnProperty.call(deps, 'document')
24
+ ? deps.document
25
+ : getDefaultDocument();
26
+ dispose(documentRef);
27
+ const observer = createObserver(deps);
28
+ if (documentRef && ['object', 'function'].includes(typeof documentRef)) {
29
+ ownedObservers.set(documentRef, observer);
30
+ }
31
+ return observer;
32
+ }
33
+
34
+ function dispose(documentRef = getDefaultDocument()) {
35
+ if (!documentRef || !['object', 'function'].includes(typeof documentRef)) return;
36
+ const previous = ownedObservers.get(documentRef);
37
+ if (!previous) return;
38
+ try {
39
+ previous.stop();
40
+ } finally {
41
+ ownedObservers.delete(documentRef);
42
+ }
43
+ }
44
+
45
+ function createObserver(deps = {}) {
17
46
  const pageWindow = deps.window || getDefaultWindow();
18
47
  const pageDocument = Object.prototype.hasOwnProperty.call(deps, 'document')
19
48
  ? deps.document
@@ -21,6 +50,9 @@
21
50
  const events = [];
22
51
  let activePath = '';
23
52
  let lastContent = '';
53
+ let baselineByteCount = 0;
54
+ let queuedTextBytes = 0;
55
+ let coalescedEventCount = 0;
24
56
  let running = false;
25
57
  let listenersAttached = false;
26
58
  let statusName = 'off';
@@ -31,6 +63,7 @@
31
63
 
32
64
  function start(_params = {}) {
33
65
  clearQueuedEvents();
66
+ coalescedEventCount = 0;
34
67
  channelCandidates = collectChannelCandidates(pageWindow);
35
68
  if (!canAttachDocumentListeners()) {
36
69
  running = false;
@@ -40,13 +73,18 @@
40
73
 
41
74
  running = true;
42
75
  refreshActiveBaseline();
43
- attachDocumentListeners();
76
+ // The editor may mount after the page bridge. Keep the read-only
77
+ // observer alive until its first file identity is available.
78
+ if (statusName === 'unavailable' && lastErrorCode === 'missing_active_path') statusName = 'starting';
79
+ if (running) attachDocumentListeners();
44
80
  return getStatus();
45
81
  }
46
82
 
47
83
  function stop() {
48
84
  detachDocumentListeners();
49
85
  clearQueuedEvents();
86
+ lastContent = '';
87
+ baselineByteCount = 0;
50
88
  running = false;
51
89
  statusName = 'off';
52
90
  statusReason = '';
@@ -61,24 +99,74 @@
61
99
  strategy: STRATEGY_ACTIVE_EDITOR,
62
100
  activePath,
63
101
  queuedEventCount: events.length,
102
+ queuedTextBytes,
103
+ baselineByteCount,
104
+ coalescedEventCount,
64
105
  lastEventAt,
65
106
  lastErrorCode,
66
107
  channelCandidates: cloneChannelCandidates(channelCandidates)
67
108
  };
68
- if (statusName === 'unavailable' && statusReason) {
109
+ if ((statusName === 'unavailable' || statusName === 'starting') && statusReason) {
69
110
  status.reason = statusReason;
70
111
  }
71
112
  return status;
72
113
  }
73
114
 
74
115
  function drainEvents() {
116
+ // Capture-phase input can precede the editor transaction; paste, undo
117
+ // and collaboration updates may dispatch no DOM input at all. The
118
+ // existing bounded poll samples committed text before draining.
119
+ if (running) handleEditorInput();
75
120
  const drained = events.slice();
76
121
  clearQueuedEvents();
77
- return drained;
122
+ if (!drained.length) return [];
123
+ const otText = resolveOtText(deps, pageWindow, root);
124
+ if (typeof otText?.normalizeObservedTextEvent !== 'function') {
125
+ stopObservation('missing_ot_text');
126
+ return [];
127
+ }
128
+ const normalized = [];
129
+ for (const pending of drained) {
130
+ let event;
131
+ const { previousBytes, nextBytes, ...input } = pending;
132
+ try { event = otText.normalizeObservedTextEvent(input); } catch (_error) { /* fail closed below */ }
133
+ if (!event || event.ok !== true) {
134
+ stopObservation(event?.reason || 'normalize_observed_text_event_failed');
135
+ return [];
136
+ }
137
+ normalized.push(sanitizeObservedEvent(event));
138
+ }
139
+ return normalized;
78
140
  }
79
141
 
80
142
  function clearQueuedEvents() {
81
143
  events.length = 0;
144
+ queuedTextBytes = 0;
145
+ }
146
+
147
+ function stopObservation(reason) {
148
+ stop();
149
+ markUnavailable(reason);
150
+ }
151
+
152
+ function queueObservedChange(event, previousBytes, nextBytes) {
153
+ const tail = events[events.length - 1];
154
+ const merge = tail?.path === event.path && tail.nextContent === event.previousContent;
155
+ const pending = { ...event, previousContent: merge ? tail.previousContent : event.previousContent,
156
+ previousBytes: merge ? tail.previousBytes : previousBytes, nextBytes };
157
+ const remainingBytes = queuedTextBytes - (merge ? tail.previousBytes + tail.nextBytes : 0);
158
+ const noChange = pending.previousContent === pending.nextContent;
159
+ const nextLength = events.length - (merge ? 1 : 0) + (noChange ? 0 : 1);
160
+ const nextTotal = remainingBytes + (noChange ? 0 : pending.previousBytes + pending.nextBytes);
161
+ if (nextLength > LIMITS.maxEvents || nextTotal > LIMITS.maxQueuedBytes) {
162
+ stopObservation('ot_queue_limit');
163
+ return false;
164
+ }
165
+ if (merge) { events.pop(); coalescedEventCount += 1; }
166
+ if (!noChange) events.push(pending);
167
+ queuedTextBytes = nextTotal;
168
+ lastEventAt = event.observedAt;
169
+ return true;
82
170
  }
83
171
 
84
172
  function canAttachDocumentListeners() {
@@ -118,12 +206,14 @@
118
206
 
119
207
  const textResult = readEditorText();
120
208
  if (!textResult.ok) {
121
- markUnavailable(textResult.reason);
209
+ if (textResult.reason === 'ot_file_limit') stopObservation(textResult.reason);
210
+ else markUnavailable(textResult.reason);
122
211
  return false;
123
212
  }
124
213
 
125
214
  activePath = pathResult.path;
126
215
  lastContent = textResult.text;
216
+ baselineByteCount = textResult.bytes;
127
217
  markObserving();
128
218
  return true;
129
219
  }
@@ -144,16 +234,19 @@
144
234
 
145
235
  const textResult = readEditorText();
146
236
  if (!textResult.ok) {
147
- markUnavailable(textResult.reason);
237
+ if (textResult.reason === 'ot_file_limit') stopObservation(textResult.reason);
238
+ else markUnavailable(textResult.reason);
148
239
  return;
149
240
  }
150
241
 
151
242
  activePath = pathResult.path;
152
243
  lastContent = textResult.text;
244
+ baselineByteCount = textResult.bytes;
153
245
  markObserving();
154
246
  }
155
247
 
156
- function handleEditorInput() {
248
+ function handleEditorInput(inputEvent) {
249
+ if (inputEvent?.target?.closest?.('#codex-overleaf-panel')) return;
157
250
  if (!running) {
158
251
  return;
159
252
  }
@@ -166,7 +259,8 @@
166
259
 
167
260
  const textResult = readEditorText();
168
261
  if (!textResult.ok) {
169
- markUnavailable(textResult.reason);
262
+ if (textResult.reason === 'ot_file_limit') stopObservation(textResult.reason);
263
+ else markUnavailable(textResult.reason);
170
264
  return;
171
265
  }
172
266
 
@@ -175,6 +269,7 @@
175
269
  // Fallback for file switches that were not preceded by a selection/focus event.
176
270
  activePath = pathResult.path;
177
271
  lastContent = nextContent;
272
+ baselineByteCount = textResult.bytes;
178
273
  markObserving();
179
274
  return;
180
275
  }
@@ -185,34 +280,25 @@
185
280
  }
186
281
 
187
282
  const observedAt = resolveObservedAt();
188
- const otText = resolveOtText(deps, pageWindow, root);
189
- if (!otText || typeof otText.normalizeObservedTextEvent !== 'function') {
190
- markUnavailable('missing_ot_text');
191
- return;
192
- }
193
-
194
- const event = otText.normalizeObservedTextEvent({
283
+ const queued = queueObservedChange({
195
284
  path: activePath,
196
285
  previousContent: lastContent,
197
286
  nextContent,
198
287
  observedAt,
199
288
  source: STRATEGY_ACTIVE_EDITOR
200
- });
201
- if (event && event.ok === true) {
202
- const queuedEvent = sanitizeObservedEvent(event);
203
- events.push(queuedEvent);
204
- lastEventAt = queuedEvent.observedAt || observedAt;
289
+ }, baselineByteCount, textResult.bytes);
290
+ if (queued) {
291
+ lastContent = nextContent;
292
+ baselineByteCount = textResult.bytes;
205
293
  markObserving();
206
- } else {
207
- lastErrorCode = event?.reason || 'normalize_observed_text_event_failed';
208
294
  }
209
- lastContent = nextContent;
210
295
  }
211
296
 
212
297
  function handleActivePathUnavailable(reason) {
213
298
  if (reason === 'missing_active_path') {
214
299
  activePath = '';
215
300
  lastContent = '';
301
+ baselineByteCount = 0;
216
302
  }
217
303
  markUnavailable(reason);
218
304
  }
@@ -252,9 +338,17 @@
252
338
  };
253
339
  }
254
340
  try {
341
+ const text = deps.readActiveEditorText();
342
+ if (typeof text !== 'string') {
343
+ return { ok: false, reason: 'missing_editor_content' };
344
+ }
345
+ const bytes = text.length > LIMITS.maxFileBytes ? LIMITS.maxFileBytes + 1
346
+ : encoder ? encoder.encode(text).byteLength : text.length * 3;
347
+ if (bytes > LIMITS.maxFileBytes) return { ok: false, reason: 'ot_file_limit' };
255
348
  return {
256
349
  ok: true,
257
- text: String(deps.readActiveEditorText() ?? '')
350
+ text,
351
+ bytes
258
352
  };
259
353
  } catch (_error) {
260
354
  return {
@@ -444,7 +538,6 @@
444
538
 
445
539
  function normalizePath(value) {
446
540
  return String(value || '')
447
- .replace(/\s+/g, ' ')
448
541
  .replace(/\\/g, '/')
449
542
  .trim()
450
543
  .replace(/^\/+/, '');
@@ -485,7 +578,9 @@
485
578
  }
486
579
 
487
580
  return {
581
+ LIMITS,
488
582
  collectChannelCandidates,
489
- create
583
+ create,
584
+ dispose
490
585
  };
491
586
  });