You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

161 lines
4.6 KiB
JavaScript

"use strict";
const defaultValue = require("default-value");
const flatten = require("flatten");
const addCommonFields = require("../event-add-common-fields");
const mapMaybeRedacted = require("../map-maybe-redacted");
const numberDerivedEvents = require("../number-derived-events");
function mapProfile(eventContent) {
return {
avatar: normalizeProfileField(eventContent.avatar_url),
displayName: normalizeProfileField(eventContent.displayname)
};
}
function normalizeProfileField(value) {
if (value === null || value === undefined) {
// We make this an *explicit* null, as this makes it easier for the consuming code to distinguish between "this user has unset the profile field" and "we just haven't seen a profile field yet".
return null;
} else {
return value;
}
}
module.exports = function mapMemberEvent(event, _context) {
let previousContent = defaultValue(event.unsigned.prev_content, { membership: "leave" });
let oldState = previousContent.membership;
let newState = event.content.membership;
let oldProfile = mapProfile(previousContent);
let newProfile = mapProfile(event.content);
let voluntaryChange = (event.state_key === event.sender);
let transitions = {
invite: {
invite: null,
join: "_userAcceptedInviteAndJoined",
leave: (voluntaryChange)
? "userRejectedInvite"
: "userInviteWasRevoked",
ban: "userWasBanned"
},
join: {
join: "_userChangedProfile",
leave: (voluntaryChange)
? "userLeft"
: "userWasKicked",
ban: "_userWasKickedAndBanned"
},
leave: {
invite: "userWasInvited",
join: "userJoined",
leave: null,
ban: "userWasBanned"
},
ban: {
leave: "userWasUnbanned",
ban: null
}
};
// userChangedProfile -> userChangedDisplayName / userChangedAvatar
// use prev_content to determine diff
let typesWithProfileData = new Set([ "_userChangedProfile", "userJoined", "_userAcceptedInviteAndJoined" ]);
if (transitions[oldState] !== undefined && transitions[oldState][newState] !== undefined) {
let type = transitions[oldState][newState];
let hasProfileData = typesWithProfileData.has(type);
let deltaEvents = [];
if (hasProfileData) {
if (oldProfile.avatar != newProfile.avatar) {
deltaEvents.push({
type: "userChangedAvatar",
user: event.state_key,
sender: event.sender,
... mapMaybeRedacted(event, () => {
return {
url: newProfile.avatar,
previousURL: oldProfile.avatar
};
})
});
}
if (oldProfile.displayName != newProfile.displayName) {
deltaEvents.push({
type: "userChangedDisplayName",
user: event.state_key,
sender: event.sender,
... mapMaybeRedacted(event, () => {
return {
name: newProfile.displayName,
previousName: oldProfile.displayName
};
})
});
}
}
// We ignore any "no change" transitions, as well as any profile changes (since those will have been handled in full above)
if (type !== null && type !== "_userChangedProfile") {
let membershipFields = {
user: event.state_key,
sender: event.sender,
... mapMaybeRedacted(event, () => {
return {
reason: event.content.reason,
// FIXME: is_direct flag, translate into "is direct message" event if prev_content does not contain it
// FIXME: third_party_invite.display_name
};
})
};
// NOTE: We have a few special cases below, where certain membership changes result in multiple logical actions. To leave it up to the consumer whether to care about the cause of eg. a kick or join, we represent the logical actions as individual events, annotating them with metadata so that a cause-interested client can ignore the implicitly-generated events.
if (type === "_userWasKickedAndBanned") {
deltaEvents.push([
addCommonFields(event, {
type: "userWasKicked",
causedByBan: true,
... membershipFields
}),
addCommonFields(event, {
type: "userWasBanned",
... membershipFields
}),
]);
} else if (type === "_userAcceptedInviteAndJoined") {
deltaEvents.push([
addCommonFields(event, {
type: "userAcceptedInvite",
... membershipFields
}),
addCommonFields(event, {
type: "userJoined",
causedByInvite: true,
... membershipFields
}),
]);
} else {
deltaEvents.push(addCommonFields(event, {
type: type,
... membershipFields
}));
}
}
let flattenedDeltaEvents = flatten(deltaEvents);
numberDerivedEvents(event.event_id, flattenedDeltaEvents);
return flattenedDeltaEvents;
} else {
// FIXME: Error type
throw new Error(`Membership transition not allowed: ${oldState} => ${newState}`);
}
};