[FIX] web_editor: prevent corruption from stale document

When a peer "A" connect to the peer to peer network, if he receive a
stale document from a peer "B", peer "A" will never be able to save.
This situation can make impossible for any new peer to receive a
converging document and block the ability to save until every peer
disconnect from the network.

task-3196778

closes odoo/odoo#114268

X-original-commit: 537f34f764fae3967496d466c420d72d172b241d
Signed-off-by: David Monjoie (dmo) <dmo@odoo.com>
This commit is contained in:
Nicolas Bayet
2023-03-03 10:15:51 +01:00
parent cf93d39822
commit f93e294fff
@@ -519,7 +519,8 @@ const Wysiwyg = Widget.extend({
break;
case 'rtc_data_channel_open': {
fromClientId = notificationPayload.connectionClientId;
this.ptp.clientsInfos[fromClientId].startTime = await this.ptp.requestClient(fromClientId, 'get_start_time', undefined, { transport: 'rtc' });
const remoteStartTime = await this.ptp.requestClient(fromClientId, 'get_start_time', undefined, { transport: 'rtc' });
this.ptp.clientsInfos[fromClientId].startTime = remoteStartTime;
this.ptp.requestClient(fromClientId, 'get_client_name', undefined, { transport: 'rtc' }).then((clientName) => {
this.ptp.clientsInfos[fromClientId].clientName = clientName;
});
@@ -528,8 +529,30 @@ const Wysiwyg = Widget.extend({
});
if (!historySyncAtLeastOnce) {
historySyncAtLeastOnce = true;
await editorCollaborationOptions.onHistoryNeedSync();
const localClient = { id: this._currentClientId, startTime: this._startCollaborationTime };
const remoteClient = { id: fromClientId, startTime: remoteStartTime };
if (isClientFirst(localClient, remoteClient)) {
historySyncAtLeastOnce = true;
} else {
const { steps, historyIds } = await this.ptp.requestClient(fromClientId, 'get_history_from_snapshot', undefined, { transport: 'rtc' });
// Ensure that the history hasn't been
// synced by another client before this
// `get_history_from_snapshot` finished.
if (historySyncAtLeastOnce) {
return;
}
const firstStepId = this.odooEditor.historyGetBranchIds()[0];
const staleDocument = !historyIds.includes(firstStepId);
if (staleDocument) {
return false;
}
historySyncAtLeastOnce = true;
this.odooEditor.historyResetFromSteps(steps, historyIds);
const remoteSelection = await this.ptp.requestClient(fromClientId, 'get_collaborative_selection', undefined, { transport: 'rtc' });
if (remoteSelection) {
this.odooEditor.onExternalMultiselectionUpdate(remoteSelection);
}
}
historySyncFinished = true;
} else {
const remoteSelection = await this.ptp.requestClient(fromClientId, 'get_collaborative_selection', undefined, { transport: 'rtc' });
@@ -646,44 +669,6 @@ const Wysiwyg = Widget.extend({
}
this.ptp && this.odooEditor.onExternalHistorySteps(missingSteps.concat([step]));
},
onHistoryNeedSync: async () => {
if (!this.ptp) return;
let firstClientId = this._currentClientId;
let firstClientStartTime = this._startCollaborationTime;
const connectedClientIds = this.ptp.getConnectedClientIds();
for (const clientId of connectedClientIds) {
const clientInfo = this.ptp.clientsInfos[clientId];
// Ensure we already retreived remote client starting time.
if (!clientInfo.startTime) {
continue;
}
const isCurrentClientFirst = isClientFirst(
{
startTime: clientInfo.startTime,
id: clientId,
},
{
startTime: firstClientStartTime,
id: firstClientId,
}
);
if (isCurrentClientFirst) {
firstClientStartTime = clientInfo.startTime;
firstClientId = clientId;
}
}
if (firstClientId !== this._currentClientId) {
const { steps, historyIds } = await this.ptp.requestClient(firstClientId, 'get_history_from_snapshot', undefined, { transport: 'rtc' });
this.odooEditor.historyResetFromSteps(steps, historyIds);
const remoteSelection = await this.ptp.requestClient(firstClientId, 'get_collaborative_selection', undefined, { transport: 'rtc' });
if (remoteSelection) {
this.odooEditor.onExternalMultiselectionUpdate(remoteSelection);
}
}
},
};
return editorCollaborationOptions;
},