[CE/SP] Hotfix 6.2.1 (#35140)

GitOrigin-RevId: 015db4b74ac04398e0227775daf8aa8782195489
This commit is contained in:
Miguel Serrano
2026-09-17 11:17:47 +00:00
committed by Copybot
parent 9394f64c54
commit fa6eccf939
4 changed files with 151 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
FROM sharelatex/sharelatex:6.2.0
# [real-time] add strict validation for incoming applyOtUpdate messages
COPY pr_35045.patch .
RUN patch -p1 < pr_35045.patch && rm pr_35045.patch
# [real-time] gracefully handle undefined meta.tc
COPY pr_35049.patch .
RUN patch -p1 < pr_35049.patch && rm pr_35049.patch
# [web] avoid sending empty sharejs op
COPY pr_35066.patch .
RUN patch -p1 < pr_35066.patch && rm pr_35066.patch
RUN node genScript compile | bash
+49
View File
@@ -0,0 +1,49 @@
diff --git a/services/real-time/app/js/Router.js b/services/real-time/app/js/Router.js
index d25b804a83bc..7bd4864322b5 100644
--- a/services/real-time/app/js/Router.js
+++ b/services/real-time/app/js/Router.js
@@ -26,7 +26,43 @@ const joinDocSchema = z.object({
const applyOtUpdateSchema = z.object({
doc_id: zz.objectId(),
- update: z.object(),
+ update: z.strictObject({
+ doc: zz.objectId().optional(),
+ dupIfSource: z.array(z.string()).optional(),
+ hash: z.string().optional(),
+ lastV: z.number().optional(),
+ meta: z
+ .strictObject({
+ tc: z.string(),
+ })
+ .optional(),
+ op: z
+ .array(
+ z
+ .strictObject({
+ i: z.string(),
+ p: z.number().int().min(0),
+ u: z.boolean().optional(),
+ })
+ .or(
+ z.strictObject({
+ d: z.string(),
+ p: z.number().int().min(0),
+ u: z.boolean().optional(),
+ })
+ )
+ .or(
+ z.strictObject({
+ c: z.string(),
+ p: z.number().int().min(0),
+ t: z.string(),
+ u: z.boolean().optional(),
+ })
+ )
+ )
+ .min(1),
+ v: z.number().int().min(0),
+ }),
})
let Router
+13
View File
@@ -0,0 +1,13 @@
diff --git a/services/real-time/app/js/Router.js b/services/real-time/app/js/Router.js
index e683c0d261ba..7d68a6911a50 100644
--- a/services/real-time/app/js/Router.js
+++ b/services/real-time/app/js/Router.js
@@ -33,7 +33,7 @@ const applyOtUpdateSchema = z.object({
lastV: z.number().optional(),
meta: z
.strictObject({
- tc: z.string(),
+ tc: z.string().optional(),
})
.optional(),
op: z
+75
View File
@@ -0,0 +1,75 @@
diff --git a/services/web/frontend/js/vendor/libs/sharejs.js b/services/web/frontend/js/vendor/libs/sharejs.js
index e3bec4804e22..1749e61e6dfa 100644
--- a/services/web/frontend/js/vendor/libs/sharejs.js
+++ b/services/web/frontend/js/vendor/libs/sharejs.js
@@ -1048,6 +1048,24 @@ export const { Doc } = (() => {
this.version = msg.v;
}
+ // Overleaf: an inflight op that cancelled out against a remote op is
+ // empty; there is nothing to resend. Drop it (resolving its callbacks and
+ // clearing the dedup ids) so we do not resend an empty op. If there is no
+ // pending op either, the doc is fully in sync, so emit 'saved'; otherwise
+ // flush() below sends the pending op normally.
+ if (this.inflightOp && this.inflightOp.length === 0) {
+ this.inflightOp = null;
+ var inflightCallbacks = this.inflightCallbacks;
+ this.inflightCallbacks = [];
+ this.inflightSubmittedIds.length = 0;
+ inflightCallbacks.forEach(function (callback) {
+ callback(null);
+ });
+ if (this.pendingOp === null) {
+ this.emit('saved');
+ }
+ }
+
// Resend any previously queued operation.
if (this.inflightOp) {
var response = {
@@ -1145,6 +1163,21 @@ export const { Doc } = (() => {
this.pendingOp = _Array$from8[0];
undo = _Array$from8[1];
+
+ // Overleaf: a pending op fully reverted by the undo is a no-op.
+ // Drop it (rather than leaving an empty op list) so it is not
+ // flushed and sent as an empty op. Resolve its submit callbacks; the
+ // inflight op was already cleared above, so the doc is fully in
+ // sync: emit 'saved'.
+ if (this.pendingOp.length === 0) {
+ this.pendingOp = null;
+ var pendingCallbacks = this.pendingCallbacks;
+ this.pendingCallbacks = [];
+ pendingCallbacks.forEach(function (callback) {
+ callback(null);
+ });
+ this.emit('saved');
+ }
}
// ... and apply it locally, reverting the changes.
@@ -1261,6 +1294,23 @@ export const { Doc } = (() => {
this.pendingOp = _Array$from12[0];
docOp = _Array$from12[1];
+
+ // Overleaf: a pending op that fully cancels against the server op is a
+ // no-op. Drop it (rather than leaving an empty op list) so it is not
+ // flushed and sent to the server as an empty op. Resolve its submit
+ // callbacks (it completed as a no-op), and if nothing is inflight either
+ // the doc is fully in sync, so emit 'saved'.
+ if (this.pendingOp.length === 0) {
+ this.pendingOp = null;
+ var pendingCallbacks = this.pendingCallbacks;
+ this.pendingCallbacks = [];
+ pendingCallbacks.forEach(function (callback) {
+ callback(null);
+ });
+ if (this.inflightOp === null) {
+ this.emit('saved');
+ }
+ }
}
this.version++;