MCPcopy Index your code
hub / github.com/ether/etherpad / handleUserChanges

Function handleUserChanges

src/node/handler/PadMessageHandler.ts:824–1011  ·  view source on GitHub ↗
(socket:any, message: {
  data: ClientUserChangesMessage
})

Source from the content-addressed store, hash-verified

822 * @param message the message from the client
823 */
824const handleUserChanges = async (socket:any, message: {
825 data: ClientUserChangesMessage
826}) => {
827 // This one's no longer pending, as we're gonna process it now
828 stats.counter('pendingEdits').dec();
829
830 // The client might disconnect between our callbacks. We should still
831 // finish processing the changeset, so keep a reference to the session.
832 const thisSession = sessioninfos[socket.id];
833
834 // TODO: this might happen with other messages too => find one place to copy the session
835 // and always use the copy. atm a message will be ignored if the session is gone even
836 // if the session was valid when the message arrived in the first place
837 if (!thisSession) throw new Error('client disconnected');
838
839 // Measure time to process edit. stats.timer('edits') spans the full handler
840 // (apply + fan-out) for backwards-compat; the new Prometheus histogram below
841 // wraps only the apply path so the scaling-dive harness can distinguish
842 // "apply is slow" from "fan-out is slow". Failed applies do not call the
843 // stopper — leaving the timer un-observed keeps the success-path
844 // distribution clean.
845 const stopWatch = stats.timer('edits').start();
846 const stopApplyHistogram = recordChangesetApply();
847 try {
848 const {data: {baseRev, apool, changeset}} = message;
849 if (baseRev == null) throw new Error('missing baseRev');
850 if (apool == null) throw new Error('missing apool');
851 if (changeset == null) throw new Error('missing changeset');
852 const wireApool = (new AttributePool()).fromJsonable(apool);
853 const pad = await padManager.getPad(thisSession.padId, null, thisSession.author);
854
855 // Verify that the changeset has valid syntax and is in canonical form
856 checkRep(changeset);
857
858 // Validate all added 'author' attribs to be the same value as the current user.
859 // Exception: '=' ops (attribute changes on existing text) are allowed to restore other authors'
860 // IDs, but only if that author already exists in the pad's pool (i.e., they genuinely
861 // contributed to this pad). This is necessary for undoing "clear authorship colors", which
862 // re-applies the original author attributes for all authors.
863 // See https://github.com/ether/etherpad-lite/issues/2802
864 for (const op of deserializeOps(unpack(changeset).ops)) {
865 // + can add text with attribs
866 // = can change or add attribs
867 // - can have attribs, but they are discarded and don't show up in the attribs -
868 // but do show up in the pool
869
870 // Besides verifying the author attribute, this serves a second purpose:
871 // AttributeMap.fromString() ensures that all attribute numbers are valid (it will throw if
872 // an attribute number isn't in the pool).
873 const opAuthorId = AttributeMap.fromString(op.attribs, wireApool).get('author');
874 if (opAuthorId && opAuthorId !== thisSession.author) {
875 if (op.opcode === '=') {
876 // Allow restoring author attributes on existing text (undo of clear authorship),
877 // but only if the author ID is already known to this pad. This prevents a user
878 // from attributing text to a fabricated author who never contributed to the pad.
879 const knownAuthor = pad.pool.putAttrib(['author', opAuthorId], true) !== -1;
880 if (!knownAuthor) {
881 throw new Error(`Author ${thisSession.author} tried to set unknown author ` +

Callers 1

Calls 15

recordChangesetApplyFunction · 0.90
checkRepFunction · 0.90
unpackFunction · 0.90
moveOpsToNewPoolFunction · 0.90
identityFunction · 0.90
followFunction · 0.90
oldLenFunction · 0.90
applyToTextFunction · 0.90
_correctMarkersInPadFunction · 0.85
startMethod · 0.80
fromJsonableMethod · 0.80
getMethod · 0.80

Tested by

no test coverage detected