[FIX] web_editor: move _getNewPtp out of a closure

In a subsequent commit, we need to change the content of the method
`_getNewPtp` and to patch it in tests.

To make that commit more readable by understanding which lines
changed, this commit moves the creation _getNewPtp to the Wysiwyg
prototype rather in a closure.

task-3217965

Part-of: odoo/odoo#136277
This commit is contained in:
Nicolas Bayet
2023-09-27 14:57:13 +00:00
parent 89a06b3ea5
commit 804b824f8a
+143 -143
View File
@@ -657,149 +657,6 @@ export class Wysiwyg extends Component {
this.busService.deleteChannel(this._collaborationChannelName);
}
// Check wether clientA is before clientB.
const isClientFirst = (clientA, clientB) => {
if (clientA.startTime === clientB.startTime) {
return clientA.id.localeCompare(clientB.id) < 1;
} else {
return clientA.startTime < clientB.startTime;
}
};
const rpcMutex = new Mutex();
this._getNewPtp = () => {
// Wether or not the history has been sent or received at least once.
let historySyncAtLeastOnce = false;
let historySyncFinished = false;
let historyStepsBuffer = [];
return new PeerToPeer({
peerConnectionConfig: { iceServers: this._iceServers },
currentClientId: this._currentClientId,
broadcastAll: (rpcData) => {
return rpcMutex.exec(async () => {
return this._serviceRpc(
'/web_editor/bus_broadcast',
{
model_name: modelName,
field_name: fieldName,
res_id: resId,
bus_data: rpcData,
},
);
});
},
onRequest: {
get_start_time: () => this._startCollaborationTime,
get_client_name: async () => {
if (!this._userName) {
const [user] = await this.orm.read(
'res.users',
[session.uid],
['name'],
);
this._userName = user.name;
}
return this._userName;
},
get_client_avatar: () => `${browser.location.origin}/web/image?model=res.users&field=avatar_128&id=${encodeURIComponent(session.uid)}`,
get_missing_steps: (params) => this.odooEditor.historyGetMissingSteps(params.requestPayload),
get_history_from_snapshot: () => this.odooEditor.historyGetSnapshotSteps(),
get_collaborative_selection: () => this.odooEditor.getCurrentCollaborativeSelection(),
},
onNotification: async ({ fromClientId, notificationName, notificationPayload }) => {
switch (notificationName) {
case 'ptp_remove':
this.odooEditor.multiselectionRemove(notificationPayload);
break;
case 'rtc_signal_description':
const pc = this.ptp.clientsInfos[fromClientId].peerConnection;
if (this._couldBeDisconnected && this._navigatorCheckOnlineWorking && (!pc || pc.connectionState === 'closed')) {
this._signalOnline();
}
break;
case 'ptp_disconnect':
this.ptp.removeClient(fromClientId);
this.odooEditor.multiselectionRemove(fromClientId);
break;
case 'rtc_data_channel_open': {
fromClientId = notificationPayload.connectionClientId;
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;
});
this.ptp.requestClient(fromClientId, 'get_client_avatar', undefined, { transport: 'rtc' }).then(clientAvatarUrl => {
this.ptp.clientsInfos[fromClientId].clientAvatarUrl = clientAvatarUrl;
});
if (!historySyncAtLeastOnce) {
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);
}
this.options.onHistoryResetFromSteps();
}
// In case there are steps received in the meantime, process them.
if (historyStepsBuffer.length) {
this.odooEditor.onExternalHistorySteps(historyStepsBuffer);
historyStepsBuffer = [];
}
historySyncFinished = true;
} else {
const currentStep = this.odooEditor._historySteps[this.odooEditor._historySteps.length - 1];
const remoteSelection = await this.ptp.requestClient(fromClientId, 'get_collaborative_selection', undefined, { transport: 'rtc' });
if (remoteSelection) {
this.odooEditor.onExternalMultiselectionUpdate(remoteSelection);
}
this.ptp.notifyClient(fromClientId, 'oe_history_step', currentStep, { transport: 'rtc' });
}
break;
}
case 'oe_history_step':
// Avoid race condition where the step is received
// before the history has synced at least once.
if (historySyncFinished) {
this.odooEditor.onExternalHistorySteps([notificationPayload]);
} else {
historyStepsBuffer.push(notificationPayload);
}
break;
case 'oe_history_set_selection': {
const client = this.ptp.clientsInfos[fromClientId];
if (!client) {
return;
}
const selection = notificationPayload;
selection.clientName = client.clientName;
selection.clientAvatarUrl = client.clientAvatarUrl;
this.odooEditor.onExternalMultiselectionUpdate(selection);
break;
}
}
}
});
}
this._currentClientId = this._generateClientId();
this._startCollaborationTime = new Date().getTime();
@@ -2864,6 +2721,140 @@ export class Wysiwyg extends Component {
// No need for secure random number.
return Math.floor(Math.random() * Math.pow(2, 52)).toString();
}
_getNewPtp() {
const rpcMutex = new Mutex();
const {collaborationChannel} = this.options;
const modelName = collaborationChannel.collaborationModelName;
const fieldName = collaborationChannel.collaborationFieldName;
const resId = collaborationChannel.collaborationResId;
// Wether or not the history has been sent or received at least
// once.
this._historySyncAtLeastOnce = false;
this._historySyncFinished = false;
this._historyStepsBuffer = [];
return new PeerToPeer({
peerConnectionConfig: { iceServers: this._iceServers },
currentClientId: this._currentClientId,
broadcastAll: (rpcData) => {
return rpcMutex.exec(async () => {
return this._serviceRpc('/web_editor/bus_broadcast', {
model_name: modelName,
field_name: fieldName,
res_id: resId,
bus_data: rpcData,
});
});
},
onRequest: {
get_start_time: () => this._startCollaborationTime,
get_client_name: async () => {
if (!this._userName) {
const [user] = await this.orm.read(
'res.users',
[session.uid],
['name'],
);
this._userName = user.name;
}
return this._userName;
},
get_client_avatar: () => `${browser.location.origin}/web/image?model=res.users&field=avatar_128&id=${encodeURIComponent(session.uid)}`,
get_missing_steps: (params) => this.odooEditor.historyGetMissingSteps(params.requestPayload),
get_history_from_snapshot: () => this.odooEditor.historyGetSnapshotSteps(),
get_collaborative_selection: () => this.odooEditor.getCurrentCollaborativeSelection(),
},
onNotification: async ({ fromClientId, notificationName, notificationPayload }) => {
switch (notificationName) {
case 'ptp_remove':
this.odooEditor.multiselectionRemove(notificationPayload);
break;
case 'rtc_signal_description':
const pc = this.ptp.clientsInfos[fromClientId].peerConnection;
if (this._couldBeDisconnected && this._navigatorCheckOnlineWorking && (!pc || pc.connectionState === 'closed')) {
this._signalOnline();
}
break;
case 'ptp_disconnect':
this.ptp.removeClient(fromClientId);
this.odooEditor.multiselectionRemove(fromClientId);
break;
case 'rtc_data_channel_open': {
fromClientId = notificationPayload.connectionClientId;
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;
});
this.ptp.requestClient(fromClientId, 'get_client_avatar', undefined, { transport: 'rtc' }).then(clientAvatarUrl => {
this.ptp.clientsInfos[fromClientId].clientAvatarUrl = clientAvatarUrl;
});
if (!this._historySyncAtLeastOnce) {
const localClient = { id: this._currentClientId, startTime: this._startCollaborationTime };
const remoteClient = { id: fromClientId, startTime: remoteStartTime };
if (isClientFirst(localClient, remoteClient)) {
this._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 (this._historySyncAtLeastOnce) {
return;
}
const firstStepId = this.odooEditor.historyGetBranchIds()[0];
const staleDocument = !historyIds.includes(firstStepId);
if (staleDocument) {
return false;
}
this._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);
}
this.options.onHistoryResetFromSteps();
}
// In case there are steps received in the meantime, process them.
if (this._historyStepsBuffer.length) {
this.odooEditor.onExternalHistorySteps(this._historyStepsBuffer);
this._historyStepsBuffer = [];
}
this._historySyncFinished = true;
} else {
const currentStep = this.odooEditor._historySteps[this.odooEditor._historySteps.length - 1];
const remoteSelection = await this.ptp.requestClient(fromClientId, 'get_collaborative_selection', undefined, { transport: 'rtc' });
if (remoteSelection) {
this.odooEditor.onExternalMultiselectionUpdate(remoteSelection);
}
this.ptp.notifyClient(fromClientId, 'oe_history_step', currentStep, { transport: 'rtc' });
}
break;
}
case 'oe_history_step':
if (this._historySyncFinished) {
this.odooEditor.onExternalHistorySteps([notificationPayload]);
} else {
this._historyStepsBuffer.push(notificationPayload);
}
break;
case 'oe_history_set_selection': {
const client = this.ptp.clientsInfos[fromClientId];
if (!client) {
return;
}
const selection = notificationPayload;
selection.clientName = client.clientName;
selection.clientAvatarUrl = client.clientAvatarUrl;
this.odooEditor.onExternalMultiselectionUpdate(selection);
break;
}
}
}
});
}
_stopPeerToPeer() {
this.ptp && this.ptp.stop();
this._collaborationStopBus && this._collaborationStopBus();
@@ -3129,6 +3120,15 @@ Wysiwyg.setRange = function (startNode, startOffset = 0, endNode = startNode, en
selection.addRange(range);
};
// Check wether clientA is before clientB.
function isClientFirst(clientA, clientB) {
if (clientA.startTime === clientB.startTime) {
return clientA.id.localeCompare(clientB.id) < 1;
} else {
return clientA.startTime < clientB.startTime;
}
};
export function stripHistoryIds(value) {
return value && value.replace(/\sdata-last-history-steps="[^"]*?"/, '') || value;
}