diff --git a/lib/shared/messages/add-members-message-spec.js b/lib/shared/messages/add-members-message-spec.js index 673c90472..a6bb1a689 100644 --- a/lib/shared/messages/add-members-message-spec.js +++ b/lib/shared/messages/add-members-message-spec.js @@ -1,159 +1,159 @@ // @flow import invariant from 'invariant'; import type { CreateMessageInfoParams, MessageSpec } from './message-spec.js'; import { joinResult } from './utils.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { AddMembersMessageData, AddMembersMessageInfo, RawAddMembersMessageInfo, } from '../../types/messages/add-members.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { ET, type EntityText, pluralizeEntityText, } from '../../utils/entity-text.js'; import { values } from '../../utils/objects.js'; import { notifRobotextForMessageInfo } from '../notif-utils.js'; export const addMembersMessageSpec: MessageSpec< AddMembersMessageData, RawAddMembersMessageInfo, AddMembersMessageInfo, > = Object.freeze({ messageContentForServerDB( data: AddMembersMessageData | RawAddMembersMessageInfo, ): string { return JSON.stringify(data.addedUserIDs); }, messageContentForClientDB(data: RawAddMembersMessageInfo): string { return this.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow(row: Object): RawAddMembersMessageInfo { return { type: messageTypes.ADD_MEMBERS, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), addedUserIDs: JSON.parse(row.content), }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawAddMembersMessageInfo { const content = clientDBMessageInfo.content; invariant( content !== undefined && content !== null, - 'content must be defined', + 'content must be defined for AddMembers', ); const rawAddMembersMessageInfo: RawAddMembersMessageInfo = { type: messageTypes.ADD_MEMBERS, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, addedUserIDs: JSON.parse(content), }; return rawAddMembersMessageInfo; }, createMessageInfo( rawMessageInfo: RawAddMembersMessageInfo, creator: RelativeUserInfo, params: CreateMessageInfoParams, ): AddMembersMessageInfo { const addedMembers = params.createRelativeUserInfos( rawMessageInfo.addedUserIDs, ); return { type: messageTypes.ADD_MEMBERS, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, addedMembers, }; }, rawMessageInfoFromMessageData( messageData: AddMembersMessageData, id: ?string, ): RawAddMembersMessageInfo { invariant(id, 'RawAddMembersMessageInfo needs id'); return { ...messageData, id }; }, robotext(messageInfo: AddMembersMessageInfo): EntityText { const users = messageInfo.addedMembers; invariant(users.length !== 0, 'added who??'); const creator = ET.user({ userInfo: messageInfo.creator }); const addedUsers = pluralizeEntityText( users.map(user => ET`${ET.user({ userInfo: user })}`), ); return ET`${creator} added ${addedUsers}`; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { const addedMembersObject = {}; for (const messageInfo of messageInfos) { invariant( messageInfo.type === messageTypes.ADD_MEMBERS, 'messageInfo should be messageTypes.ADD_MEMBERS!', ); for (const member of messageInfo.addedMembers) { addedMembersObject[member.id] = member; } } const addedMembers = values(addedMembersObject); const mostRecentMessageInfo = messageInfos[0]; invariant( mostRecentMessageInfo.type === messageTypes.ADD_MEMBERS, 'messageInfo should be messageTypes.ADD_MEMBERS!', ); const mergedMessageInfo = { ...mostRecentMessageInfo, addedMembers }; const robotext = notifRobotextForMessageInfo(mergedMessageInfo, threadInfo); const merged = ET`${robotext} to ${ET.thread({ display: 'shortName', threadInfo, })}`; return { merged, title: threadInfo.uiName, body: robotext, }; }, notificationCollapseKey(rawMessageInfo: RawAddMembersMessageInfo): string { return joinResult( rawMessageInfo.type, rawMessageInfo.threadID, rawMessageInfo.creatorID, ); }, generatesNotifs: async () => undefined, userIDs(rawMessageInfo: RawAddMembersMessageInfo): $ReadOnlyArray { return rawMessageInfo.addedUserIDs; }, }); diff --git a/lib/shared/messages/change-role-message-spec.js b/lib/shared/messages/change-role-message-spec.js index 869093983..c9de693f7 100644 --- a/lib/shared/messages/change-role-message-spec.js +++ b/lib/shared/messages/change-role-message-spec.js @@ -1,177 +1,177 @@ // @flow import invariant from 'invariant'; import { pushTypes, type CreateMessageInfoParams, type MessageSpec, type RobotextParams, } from './message-spec.js'; import { joinResult } from './utils.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { ChangeRoleMessageData, ChangeRoleMessageInfo, RawChangeRoleMessageInfo, } from '../../types/messages/change-role.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { ET, type EntityText, pluralizeEntityText, } from '../../utils/entity-text.js'; import { values } from '../../utils/objects.js'; import { notifRobotextForMessageInfo } from '../notif-utils.js'; export const changeRoleMessageSpec: MessageSpec< ChangeRoleMessageData, RawChangeRoleMessageInfo, ChangeRoleMessageInfo, > = Object.freeze({ messageContentForServerDB( data: ChangeRoleMessageData | RawChangeRoleMessageInfo, ): string { return JSON.stringify({ userIDs: data.userIDs, newRole: data.newRole, }); }, messageContentForClientDB(data: RawChangeRoleMessageInfo): string { return this.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow(row: Object): RawChangeRoleMessageInfo { const content = JSON.parse(row.content); return { type: messageTypes.CHANGE_ROLE, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), userIDs: content.userIDs, newRole: content.newRole, }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawChangeRoleMessageInfo { invariant( clientDBMessageInfo.content !== undefined && clientDBMessageInfo.content !== null, - 'content must be defined', + 'content must be defined for ChangeRole', ); const content = JSON.parse(clientDBMessageInfo.content); const rawChangeRoleMessageInfo: RawChangeRoleMessageInfo = { type: messageTypes.CHANGE_ROLE, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, userIDs: content.userIDs, newRole: content.newRole, }; return rawChangeRoleMessageInfo; }, createMessageInfo( rawMessageInfo: RawChangeRoleMessageInfo, creator: RelativeUserInfo, params: CreateMessageInfoParams, ): ChangeRoleMessageInfo { const members = params.createRelativeUserInfos(rawMessageInfo.userIDs); return { type: messageTypes.CHANGE_ROLE, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, members, newRole: rawMessageInfo.newRole, }; }, rawMessageInfoFromMessageData( messageData: ChangeRoleMessageData, id: ?string, ): RawChangeRoleMessageInfo { invariant(id, 'RawChangeRoleMessageInfo needs id'); return { ...messageData, id }; }, robotext( messageInfo: ChangeRoleMessageInfo, params: RobotextParams, ): EntityText { const users = messageInfo.members; invariant(users.length !== 0, 'changed whose role??'); const creator = ET.user({ userInfo: messageInfo.creator }); const affectedUsers = pluralizeEntityText( users.map(user => ET`${ET.user({ userInfo: user })}`), ); const { threadInfo } = params; invariant(threadInfo, 'ThreadInfo should be set for CHANGE_ROLE message'); const verb = threadInfo.roles[messageInfo.newRole].isDefault ? 'removed' : 'added'; const noun = users.length === 1 ? 'an admin' : 'admins'; return ET`${creator} ${verb} ${affectedUsers} as ${noun}`; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { const membersObject = {}; for (const messageInfo of messageInfos) { invariant( messageInfo.type === messageTypes.CHANGE_ROLE, 'messageInfo should be messageTypes.CHANGE_ROLE!', ); for (const member of messageInfo.members) { membersObject[member.id] = member; } } const members = values(membersObject); const mostRecentMessageInfo = messageInfos[0]; invariant( mostRecentMessageInfo.type === messageTypes.CHANGE_ROLE, 'messageInfo should be messageTypes.CHANGE_ROLE!', ); const mergedMessageInfo = { ...mostRecentMessageInfo, members }; const robotext = notifRobotextForMessageInfo(mergedMessageInfo, threadInfo); const merged = ET`${robotext} of ${ET.thread({ display: 'shortName', threadInfo, })}`; return { merged, title: threadInfo.uiName, body: robotext, }; }, notificationCollapseKey(rawMessageInfo: RawChangeRoleMessageInfo): string { return joinResult( rawMessageInfo.type, rawMessageInfo.threadID, rawMessageInfo.creatorID, rawMessageInfo.newRole, ); }, generatesNotifs: async () => pushTypes.NOTIF, }); diff --git a/lib/shared/messages/change-settings-message-spec.js b/lib/shared/messages/change-settings-message-spec.js index 542ef50d8..67a3acba1 100644 --- a/lib/shared/messages/change-settings-message-spec.js +++ b/lib/shared/messages/change-settings-message-spec.js @@ -1,166 +1,166 @@ // @flow import invariant from 'invariant'; import { pushTypes, type MessageSpec } from './message-spec.js'; import { joinResult } from './utils.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { ChangeSettingsMessageData, ChangeSettingsMessageInfo, RawChangeSettingsMessageInfo, } from '../../types/messages/change-settings.js'; import type { NotifTexts } from '../../types/notif-types.js'; import { assertThreadType } from '../../types/thread-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { ET, type EntityText } from '../../utils/entity-text.js'; import { validHexColorRegex } from '../account-utils.js'; import { notifRobotextForMessageInfo } from '../notif-utils.js'; import { threadLabel } from '../thread-utils.js'; export const changeSettingsMessageSpec: MessageSpec< ChangeSettingsMessageData, RawChangeSettingsMessageInfo, ChangeSettingsMessageInfo, > = Object.freeze({ messageContentForServerDB( data: ChangeSettingsMessageData | RawChangeSettingsMessageInfo, ): string { return JSON.stringify({ [data.field]: data.value, }); }, messageContentForClientDB(data: RawChangeSettingsMessageInfo): string { return this.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow(row: Object): RawChangeSettingsMessageInfo { const content = JSON.parse(row.content); const field = Object.keys(content)[0]; return { type: messageTypes.CHANGE_SETTINGS, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), field, value: content[field], }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawChangeSettingsMessageInfo { invariant( clientDBMessageInfo.content !== undefined && clientDBMessageInfo.content !== null, - 'content must be defined', + 'content must be defined for ChangeSettings', ); const content = JSON.parse(clientDBMessageInfo.content); const field = Object.keys(content)[0]; const rawChangeSettingsMessageInfo: RawChangeSettingsMessageInfo = { type: messageTypes.CHANGE_SETTINGS, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, field, value: content[field], }; return rawChangeSettingsMessageInfo; }, createMessageInfo( rawMessageInfo: RawChangeSettingsMessageInfo, creator: RelativeUserInfo, ): ChangeSettingsMessageInfo { return { type: messageTypes.CHANGE_SETTINGS, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, field: rawMessageInfo.field, value: rawMessageInfo.value, }; }, rawMessageInfoFromMessageData( messageData: ChangeSettingsMessageData, id: ?string, ): RawChangeSettingsMessageInfo { invariant(id, 'RawChangeSettingsMessageInfo needs id'); return { ...messageData, id }; }, robotext(messageInfo: ChangeSettingsMessageInfo): EntityText { const creator = ET.user({ userInfo: messageInfo.creator }); if ( (messageInfo.field === 'name' || messageInfo.field === 'description') && messageInfo.value.toString() === '' ) { return ET`${creator} cleared ${ET.thread({ display: 'alwaysDisplayShortName', threadID: messageInfo.threadID, possessive: true, })} ${messageInfo.field}`; } let value; if ( messageInfo.field === 'color' && messageInfo.value.toString().match(validHexColorRegex) ) { value = ET.color({ hex: `#${messageInfo.value}` }); } else if (messageInfo.field === 'type') { invariant( typeof messageInfo.value === 'number', 'messageInfo.value should be number for thread type change ', ); const newThreadType = assertThreadType(messageInfo.value); value = threadLabel(newThreadType); } else { value = messageInfo.value.toString(); } return ET`${creator} updated ${ET.thread({ display: 'alwaysDisplayShortName', threadID: messageInfo.threadID, possessive: true, })} ${messageInfo.field} to "${value}"`; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { const mostRecentMessageInfo = messageInfos[0]; invariant( mostRecentMessageInfo.type === messageTypes.CHANGE_SETTINGS, 'messageInfo should be messageTypes.CHANGE_SETTINGS!', ); const body = notifRobotextForMessageInfo(mostRecentMessageInfo, threadInfo); return { merged: body, title: threadInfo.uiName, body, }; }, notificationCollapseKey( rawMessageInfo: RawChangeSettingsMessageInfo, ): string { return joinResult( rawMessageInfo.type, rawMessageInfo.threadID, rawMessageInfo.creatorID, rawMessageInfo.field, ); }, generatesNotifs: async () => pushTypes.NOTIF, }); diff --git a/lib/shared/messages/create-entry-message-spec.js b/lib/shared/messages/create-entry-message-spec.js index de9f1cd68..6a42183c5 100644 --- a/lib/shared/messages/create-entry-message-spec.js +++ b/lib/shared/messages/create-entry-message-spec.js @@ -1,122 +1,122 @@ // @flow import invariant from 'invariant'; import { pushTypes, type MessageSpec } from './message-spec.js'; import { joinResult } from './utils.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { CreateEntryMessageData, CreateEntryMessageInfo, RawCreateEntryMessageInfo, } from '../../types/messages/create-entry.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { prettyDate } from '../../utils/date-utils.js'; import { ET, type EntityText } from '../../utils/entity-text.js'; import { notifTextsForEntryCreationOrEdit } from '../notif-utils.js'; export const createEntryMessageSpec: MessageSpec< CreateEntryMessageData, RawCreateEntryMessageInfo, CreateEntryMessageInfo, > = Object.freeze({ messageContentForServerDB( data: CreateEntryMessageData | RawCreateEntryMessageInfo, ): string { return JSON.stringify({ entryID: data.entryID, date: data.date, text: data.text, }); }, messageContentForClientDB(data: RawCreateEntryMessageInfo): string { return this.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow(row: Object): RawCreateEntryMessageInfo { const content = JSON.parse(row.content); return { type: messageTypes.CREATE_ENTRY, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), entryID: content.entryID, date: content.date, text: content.text, }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawCreateEntryMessageInfo { invariant( clientDBMessageInfo.content !== undefined && clientDBMessageInfo.content !== null, - 'content must be defined', + 'content must be defined for CreateEntry', ); const content = JSON.parse(clientDBMessageInfo.content); const rawCreateEntryMessageInfo: RawCreateEntryMessageInfo = { type: messageTypes.CREATE_ENTRY, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, entryID: content.entryID, date: content.date, text: content.text, }; return rawCreateEntryMessageInfo; }, createMessageInfo( rawMessageInfo: RawCreateEntryMessageInfo, creator: RelativeUserInfo, ): CreateEntryMessageInfo { return { type: messageTypes.CREATE_ENTRY, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, entryID: rawMessageInfo.entryID, date: rawMessageInfo.date, text: rawMessageInfo.text, }; }, rawMessageInfoFromMessageData( messageData: CreateEntryMessageData, id: ?string, ): RawCreateEntryMessageInfo { invariant(id, 'RawCreateEntryMessageInfo needs id'); return { ...messageData, id }; }, robotext(messageInfo: CreateEntryMessageInfo): EntityText { const date = prettyDate(messageInfo.date); const creator = ET.user({ userInfo: messageInfo.creator }); const { text } = messageInfo; return ET`${creator} created an event scheduled for ${date}: "${text}"`; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { return notifTextsForEntryCreationOrEdit(messageInfos, threadInfo); }, notificationCollapseKey(rawMessageInfo: RawCreateEntryMessageInfo): string { return joinResult(rawMessageInfo.creatorID, rawMessageInfo.entryID); }, generatesNotifs: async () => pushTypes.NOTIF, }); diff --git a/lib/shared/messages/create-sidebar-message-spec.js b/lib/shared/messages/create-sidebar-message-spec.js index 678011d36..96a4a43c8 100644 --- a/lib/shared/messages/create-sidebar-message-spec.js +++ b/lib/shared/messages/create-sidebar-message-spec.js @@ -1,224 +1,224 @@ // @flow import invariant from 'invariant'; import { pushTypes, type CreateMessageInfoParams, type MessageSpec, type NotificationTextsParams, } from './message-spec.js'; import { joinResult } from './utils.js'; import type { PlatformDetails } from '../../types/device-types.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { CreateSidebarMessageData, CreateSidebarMessageInfo, RawCreateSidebarMessageInfo, } from '../../types/messages/create-sidebar.js'; import type { RawUnsupportedMessageInfo } from '../../types/messages/unsupported.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { ET, type EntityText, pluralizeEntityText, } from '../../utils/entity-text.js'; import { notifTextsForSidebarCreation } from '../notif-utils.js'; import { hasMinCodeVersion } from '../version-utils.js'; export const createSidebarMessageSpec: MessageSpec< CreateSidebarMessageData, RawCreateSidebarMessageInfo, CreateSidebarMessageInfo, > = Object.freeze({ messageContentForServerDB( data: CreateSidebarMessageData | RawCreateSidebarMessageInfo, ): string { return JSON.stringify({ ...data.initialThreadState, sourceMessageAuthorID: data.sourceMessageAuthorID, }); }, messageContentForClientDB(data: RawCreateSidebarMessageInfo): string { return this.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow(row: Object): RawCreateSidebarMessageInfo { const { sourceMessageAuthorID, ...initialThreadState } = JSON.parse( row.content, ); return { type: messageTypes.CREATE_SIDEBAR, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), sourceMessageAuthorID, initialThreadState, }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawCreateSidebarMessageInfo { invariant( clientDBMessageInfo.content !== undefined && clientDBMessageInfo.content !== null, - 'content must be defined', + 'content must be defined for CreateSidebar', ); const { sourceMessageAuthorID, ...initialThreadState } = JSON.parse( clientDBMessageInfo.content, ); const rawCreateSidebarMessageInfo: RawCreateSidebarMessageInfo = { type: messageTypes.CREATE_SIDEBAR, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, sourceMessageAuthorID: sourceMessageAuthorID, initialThreadState: initialThreadState, }; return rawCreateSidebarMessageInfo; }, createMessageInfo( rawMessageInfo: RawCreateSidebarMessageInfo, creator: RelativeUserInfo, params: CreateMessageInfoParams, ): ?CreateSidebarMessageInfo { const { threadInfos } = params; const parentThreadInfo = threadInfos[rawMessageInfo.initialThreadState.parentThreadID]; const sourceMessageAuthor = params.createRelativeUserInfos([ rawMessageInfo.sourceMessageAuthorID, ])[0]; if (!sourceMessageAuthor) { return null; } return { type: messageTypes.CREATE_SIDEBAR, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, sourceMessageAuthor, initialThreadState: { name: rawMessageInfo.initialThreadState.name, parentThreadInfo, color: rawMessageInfo.initialThreadState.color, otherMembers: params.createRelativeUserInfos( rawMessageInfo.initialThreadState.memberIDs.filter( (userID: string) => userID !== rawMessageInfo.creatorID, ), ), }, }; }, rawMessageInfoFromMessageData( messageData: CreateSidebarMessageData, id: ?string, ): RawCreateSidebarMessageInfo { invariant(id, 'RawCreateSidebarMessageInfo needs id'); return { ...messageData, id }; }, robotext(messageInfo: CreateSidebarMessageInfo): EntityText { let text = ET`started ${ET.thread({ display: 'alwaysDisplayShortName', threadID: messageInfo.threadID, })}`; const users = messageInfo.initialThreadState.otherMembers.filter( member => member.id !== messageInfo.sourceMessageAuthor.id, ); if (users.length !== 0) { const initialUsers = pluralizeEntityText( users.map(user => ET`${ET.user({ userInfo: user })}`), ); text = ET`${text} and added ${initialUsers}`; } const creator = ET.user({ userInfo: messageInfo.creator }); return ET`${creator} ${text}`; }, shimUnsupportedMessageInfo( rawMessageInfo: RawCreateSidebarMessageInfo, platformDetails: ?PlatformDetails, ): RawCreateSidebarMessageInfo | RawUnsupportedMessageInfo { if (hasMinCodeVersion(platformDetails, 75)) { return rawMessageInfo; } const { id } = rawMessageInfo; invariant(id !== null && id !== undefined, 'id should be set on server'); return { type: messageTypes.UNSUPPORTED, id, threadID: rawMessageInfo.threadID, creatorID: rawMessageInfo.creatorID, time: rawMessageInfo.time, robotext: 'created a thread', unsupportedMessageInfo: rawMessageInfo, }; }, unshimMessageInfo( unwrapped: RawCreateSidebarMessageInfo, ): RawCreateSidebarMessageInfo { return unwrapped; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, params: NotificationTextsParams, ): Promise { const createSidebarMessageInfo = messageInfos[0]; invariant( createSidebarMessageInfo.type === messageTypes.CREATE_SIDEBAR, 'first MessageInfo should be messageTypes.CREATE_SIDEBAR!', ); let sidebarSourceMessageInfo; const secondMessageInfo = messageInfos[1]; if ( secondMessageInfo && secondMessageInfo.type === messageTypes.SIDEBAR_SOURCE ) { sidebarSourceMessageInfo = secondMessageInfo; } return notifTextsForSidebarCreation({ createSidebarMessageInfo, sidebarSourceMessageInfo, threadInfo, params, }); }, notificationCollapseKey(rawMessageInfo: RawCreateSidebarMessageInfo): string { return joinResult(messageTypes.CREATE_SIDEBAR, rawMessageInfo.threadID); }, generatesNotifs: async () => pushTypes.NOTIF, userIDs(rawMessageInfo: RawCreateSidebarMessageInfo): $ReadOnlyArray { return rawMessageInfo.initialThreadState.memberIDs; }, threadIDs( rawMessageInfo: RawCreateSidebarMessageInfo, ): $ReadOnlyArray { const { parentThreadID } = rawMessageInfo.initialThreadState; return [parentThreadID]; }, }); diff --git a/lib/shared/messages/create-sub-thread-message-spec.js b/lib/shared/messages/create-sub-thread-message-spec.js index f6d6aecbc..6074cb77a 100644 --- a/lib/shared/messages/create-sub-thread-message-spec.js +++ b/lib/shared/messages/create-sub-thread-message-spec.js @@ -1,163 +1,163 @@ // @flow import invariant from 'invariant'; import { pushTypes, type CreateMessageInfoParams, type MessageSpec, type GeneratesNotifsParams, } from './message-spec.js'; import { assertSingleMessageInfo } from './utils.js'; import { permissionLookup } from '../../permissions/thread-permissions.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { CreateSubthreadMessageData, CreateSubthreadMessageInfo, RawCreateSubthreadMessageInfo, } from '../../types/messages/create-subthread.js'; import type { NotifTexts } from '../../types/notif-types.js'; import { threadPermissions, threadTypes } from '../../types/thread-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { ET, type EntityText } from '../../utils/entity-text.js'; import { notifTextsForSubthreadCreation } from '../notif-utils.js'; export const createSubThreadMessageSpec: MessageSpec< CreateSubthreadMessageData, RawCreateSubthreadMessageInfo, CreateSubthreadMessageInfo, > = Object.freeze({ messageContentForServerDB( data: CreateSubthreadMessageData | RawCreateSubthreadMessageInfo, ): string { return data.childThreadID; }, messageContentForClientDB(data: RawCreateSubthreadMessageInfo): string { return this.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow(row: Object): ?RawCreateSubthreadMessageInfo { const subthreadPermissions = row.subthread_permissions; if (!permissionLookup(subthreadPermissions, threadPermissions.KNOW_OF)) { return null; } return { type: messageTypes.CREATE_SUB_THREAD, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), childThreadID: row.content, }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawCreateSubthreadMessageInfo { const content = clientDBMessageInfo.content; invariant( content !== undefined && content !== null, - 'content must be defined', + 'content must be defined for CreateSubThread', ); const rawCreateSubthreadMessageInfo: RawCreateSubthreadMessageInfo = { type: messageTypes.CREATE_SUB_THREAD, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, childThreadID: content, }; return rawCreateSubthreadMessageInfo; }, createMessageInfo( rawMessageInfo: RawCreateSubthreadMessageInfo, creator: RelativeUserInfo, params: CreateMessageInfoParams, ): ?CreateSubthreadMessageInfo { const { threadInfos } = params; const childThreadInfo = threadInfos[rawMessageInfo.childThreadID]; if (!childThreadInfo) { return null; } return { type: messageTypes.CREATE_SUB_THREAD, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, childThreadInfo, }; }, rawMessageInfoFromMessageData( messageData: CreateSubthreadMessageData, id: ?string, ): RawCreateSubthreadMessageInfo { invariant(id, 'RawCreateSubthreadMessageInfo needs id'); return { ...messageData, id }; }, robotext(messageInfo: CreateSubthreadMessageInfo): EntityText { const threadEntity = ET.thread({ display: 'shortName', threadInfo: messageInfo.childThreadInfo, subchannel: true, }); let text; if (messageInfo.childThreadInfo.name) { const childNoun = messageInfo.childThreadInfo.type === threadTypes.SIDEBAR ? 'thread' : 'subchannel'; text = ET`created a ${childNoun} named "${threadEntity}"`; } else { text = ET`created a ${threadEntity}`; } const creator = ET.user({ userInfo: messageInfo.creator }); return ET`${creator} ${text}`; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { const messageInfo = assertSingleMessageInfo(messageInfos); invariant( messageInfo.type === messageTypes.CREATE_SUB_THREAD, 'messageInfo should be messageTypes.CREATE_SUB_THREAD!', ); return notifTextsForSubthreadCreation({ creator: messageInfo.creator, threadType: messageInfo.childThreadInfo.type, parentThreadInfo: threadInfo, childThreadName: messageInfo.childThreadInfo.name, childThreadUIName: messageInfo.childThreadInfo.uiName, }); }, generatesNotifs: async ( rawMessageInfo: RawCreateSubthreadMessageInfo, messageData: CreateSubthreadMessageData, params: GeneratesNotifsParams, ) => { const { userNotMemberOfSubthreads } = params; return userNotMemberOfSubthreads.has(rawMessageInfo.childThreadID) ? pushTypes.NOTIF : undefined; }, threadIDs( rawMessageInfo: RawCreateSubthreadMessageInfo, ): $ReadOnlyArray { return [rawMessageInfo.childThreadID]; }, }); diff --git a/lib/shared/messages/create-thread-message-spec.js b/lib/shared/messages/create-thread-message-spec.js index a3b6878f6..2bc68355d 100644 --- a/lib/shared/messages/create-thread-message-spec.js +++ b/lib/shared/messages/create-thread-message-spec.js @@ -1,197 +1,197 @@ // @flow import invariant from 'invariant'; import { pushTypes, type CreateMessageInfoParams, type MessageSpec, } from './message-spec.js'; import { assertSingleMessageInfo } from './utils.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { CreateThreadMessageData, CreateThreadMessageInfo, RawCreateThreadMessageInfo, } from '../../types/messages/create-thread.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { ET, type EntityText, pluralizeEntityText, } from '../../utils/entity-text.js'; import { notifTextsForSubthreadCreation } from '../notif-utils.js'; import { threadNoun } from '../thread-utils.js'; export const createThreadMessageSpec: MessageSpec< CreateThreadMessageData, RawCreateThreadMessageInfo, CreateThreadMessageInfo, > = Object.freeze({ messageContentForServerDB( data: CreateThreadMessageData | RawCreateThreadMessageInfo, ): string { return JSON.stringify(data.initialThreadState); }, messageContentForClientDB(data: RawCreateThreadMessageInfo): string { return this.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow(row: Object): RawCreateThreadMessageInfo { return { type: messageTypes.CREATE_THREAD, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), initialThreadState: JSON.parse(row.content), }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawCreateThreadMessageInfo { const content = clientDBMessageInfo.content; invariant( content !== undefined && content !== null, - 'content must be defined', + 'content must be defined for CreateThread', ); const rawCreateThreadMessageInfo: RawCreateThreadMessageInfo = { type: messageTypes.CREATE_THREAD, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, initialThreadState: JSON.parse(content), }; return rawCreateThreadMessageInfo; }, createMessageInfo( rawMessageInfo: RawCreateThreadMessageInfo, creator: RelativeUserInfo, params: CreateMessageInfoParams, ): CreateThreadMessageInfo { const initialParentThreadID = rawMessageInfo.initialThreadState.parentThreadID; const parentThreadInfo = initialParentThreadID ? params.threadInfos[initialParentThreadID] : null; return { type: messageTypes.CREATE_THREAD, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, initialThreadState: { name: rawMessageInfo.initialThreadState.name, parentThreadInfo, type: rawMessageInfo.initialThreadState.type, color: rawMessageInfo.initialThreadState.color, otherMembers: params.createRelativeUserInfos( rawMessageInfo.initialThreadState.memberIDs.filter( (userID: string) => userID !== rawMessageInfo.creatorID, ), ), }, }; }, rawMessageInfoFromMessageData( messageData: CreateThreadMessageData, id: ?string, ): RawCreateThreadMessageInfo { invariant(id, 'RawCreateThreadMessageInfo needs id'); return { ...messageData, id }; }, robotext(messageInfo: CreateThreadMessageInfo): EntityText { let text = ET`created ${ET.thread({ display: 'alwaysDisplayShortName', threadID: messageInfo.threadID, })}`; const parentThread = messageInfo.initialThreadState.parentThreadInfo; if (parentThread) { text = ET`${text} as a child of ${ET.thread({ display: 'uiName', threadInfo: parentThread, })}`; } if (messageInfo.initialThreadState.name) { text = ET`${text} with the name "${messageInfo.initialThreadState.name}"`; } const users = messageInfo.initialThreadState.otherMembers; if (users.length !== 0) { const initialUsers = pluralizeEntityText( users.map(user => ET`${ET.user({ userInfo: user })}`), ); text = ET`${text} and added ${initialUsers}`; } const creator = ET.user({ userInfo: messageInfo.creator }); return ET`${creator} ${text}`; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { const messageInfo = assertSingleMessageInfo(messageInfos); invariant( messageInfo.type === messageTypes.CREATE_THREAD, 'messageInfo should be messageTypes.CREATE_THREAD!', ); const threadType = messageInfo.initialThreadState.type; const parentThreadInfo = messageInfo.initialThreadState.parentThreadInfo; const threadName = messageInfo.initialThreadState.name; if (parentThreadInfo) { return notifTextsForSubthreadCreation({ creator: messageInfo.creator, threadType, parentThreadInfo, childThreadName: threadName, childThreadUIName: threadInfo.uiName, }); } const creator = ET.user({ userInfo: messageInfo.creator }); const prefix = ET`${creator}`; const body = `created a new ${threadNoun(threadType)}`; let merged = ET`${prefix} ${body}`; if (threadName) { merged = ET`${merged} called "${threadName}"`; } return { merged, body, title: threadInfo.uiName, prefix, }; }, generatesNotifs: async () => pushTypes.NOTIF, userIDs(rawMessageInfo: RawCreateThreadMessageInfo): $ReadOnlyArray { return rawMessageInfo.initialThreadState.memberIDs; }, startsThread: true, threadIDs( rawMessageInfo: RawCreateThreadMessageInfo, ): $ReadOnlyArray { const { parentThreadID } = rawMessageInfo.initialThreadState; return parentThreadID ? [parentThreadID] : []; }, }); diff --git a/lib/shared/messages/delete-entry-message-spec.js b/lib/shared/messages/delete-entry-message-spec.js index e0a137310..24a407b5d 100644 --- a/lib/shared/messages/delete-entry-message-spec.js +++ b/lib/shared/messages/delete-entry-message-spec.js @@ -1,137 +1,137 @@ // @flow import invariant from 'invariant'; import { pushTypes, type MessageSpec } from './message-spec.js'; import { assertSingleMessageInfo } from './utils.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { DeleteEntryMessageData, DeleteEntryMessageInfo, RawDeleteEntryMessageInfo, } from '../../types/messages/delete-entry.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { prettyDate } from '../../utils/date-utils.js'; import { ET, type EntityText } from '../../utils/entity-text.js'; export const deleteEntryMessageSpec: MessageSpec< DeleteEntryMessageData, RawDeleteEntryMessageInfo, DeleteEntryMessageInfo, > = Object.freeze({ messageContentForServerDB( data: DeleteEntryMessageData | RawDeleteEntryMessageInfo, ): string { return JSON.stringify({ entryID: data.entryID, date: data.date, text: data.text, }); }, messageContentForClientDB(data: RawDeleteEntryMessageInfo): string { return this.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow(row: Object): RawDeleteEntryMessageInfo { const content = JSON.parse(row.content); return { type: messageTypes.DELETE_ENTRY, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), entryID: content.entryID, date: content.date, text: content.text, }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawDeleteEntryMessageInfo { invariant( clientDBMessageInfo.content !== undefined && clientDBMessageInfo.content !== null, - 'content must be defined', + 'content must be defined for DeleteEntry', ); const content = JSON.parse(clientDBMessageInfo.content); const rawDeleteEntryMessageInfo: RawDeleteEntryMessageInfo = { type: messageTypes.DELETE_ENTRY, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, entryID: content.entryID, date: content.date, text: content.text, }; return rawDeleteEntryMessageInfo; }, createMessageInfo( rawMessageInfo: RawDeleteEntryMessageInfo, creator: RelativeUserInfo, ): DeleteEntryMessageInfo { return { type: messageTypes.DELETE_ENTRY, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, entryID: rawMessageInfo.entryID, date: rawMessageInfo.date, text: rawMessageInfo.text, }; }, rawMessageInfoFromMessageData( messageData: DeleteEntryMessageData, id: ?string, ): RawDeleteEntryMessageInfo { invariant(id, 'RawDeleteEntryMessageInfo needs id'); return { ...messageData, id }; }, robotext(messageInfo: DeleteEntryMessageInfo): EntityText { const date = prettyDate(messageInfo.date); const creator = ET.user({ userInfo: messageInfo.creator }); const { text } = messageInfo; return ET`${creator} deleted an event scheduled for ${date}: "${text}"`; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { const messageInfo = assertSingleMessageInfo(messageInfos); invariant( messageInfo.type === messageTypes.DELETE_ENTRY, 'messageInfo should be messageTypes.DELETE_ENTRY!', ); const thread = ET.thread({ display: 'shortName', threadInfo }); const creator = ET.user({ userInfo: messageInfo.creator }); const date = prettyDate(messageInfo.date); const prefix = ET`${creator}`; let body = ET`deleted an event in ${thread}`; body = ET`${body} scheduled for ${date}: "${messageInfo.text}"`; const merged = ET`${prefix} ${body}`; return { merged, title: threadInfo.uiName, body, prefix, }; }, generatesNotifs: async () => pushTypes.NOTIF, }); diff --git a/lib/shared/messages/edit-entry-message-spec.js b/lib/shared/messages/edit-entry-message-spec.js index ffb331e59..51a802cfe 100644 --- a/lib/shared/messages/edit-entry-message-spec.js +++ b/lib/shared/messages/edit-entry-message-spec.js @@ -1,122 +1,122 @@ // @flow import invariant from 'invariant'; import { pushTypes, type MessageSpec } from './message-spec.js'; import { joinResult } from './utils.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { EditEntryMessageData, EditEntryMessageInfo, RawEditEntryMessageInfo, } from '../../types/messages/edit-entry.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { prettyDate } from '../../utils/date-utils.js'; import { ET, type EntityText } from '../../utils/entity-text.js'; import { notifTextsForEntryCreationOrEdit } from '../notif-utils.js'; export const editEntryMessageSpec: MessageSpec< EditEntryMessageData, RawEditEntryMessageInfo, EditEntryMessageInfo, > = Object.freeze({ messageContentForServerDB( data: EditEntryMessageData | RawEditEntryMessageInfo, ): string { return JSON.stringify({ entryID: data.entryID, date: data.date, text: data.text, }); }, messageContentForClientDB(data: RawEditEntryMessageInfo): string { return this.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow(row: Object): RawEditEntryMessageInfo { const content = JSON.parse(row.content); return { type: messageTypes.EDIT_ENTRY, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), entryID: content.entryID, date: content.date, text: content.text, }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawEditEntryMessageInfo { invariant( clientDBMessageInfo.content !== null && clientDBMessageInfo.content !== undefined, - 'content must be defined', + 'content must be defined for EditEntry', ); const content = JSON.parse(clientDBMessageInfo.content); const rawEditEntryMessageInfo: RawEditEntryMessageInfo = { type: messageTypes.EDIT_ENTRY, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, entryID: content.entryID, date: content.date, text: content.text, }; return rawEditEntryMessageInfo; }, createMessageInfo( rawMessageInfo: RawEditEntryMessageInfo, creator: RelativeUserInfo, ): EditEntryMessageInfo { return { type: messageTypes.EDIT_ENTRY, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, entryID: rawMessageInfo.entryID, date: rawMessageInfo.date, text: rawMessageInfo.text, }; }, rawMessageInfoFromMessageData( messageData: EditEntryMessageData, id: ?string, ): RawEditEntryMessageInfo { invariant(id, 'RawEditEntryMessageInfo needs id'); return { ...messageData, id }; }, robotext(messageInfo: EditEntryMessageInfo): EntityText { const date = prettyDate(messageInfo.date); const creator = ET.user({ userInfo: messageInfo.creator }); const { text } = messageInfo; return ET`${creator} updated the text of an event scheduled for ${date}: "${text}"`; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { return notifTextsForEntryCreationOrEdit(messageInfos, threadInfo); }, notificationCollapseKey(rawMessageInfo: RawEditEntryMessageInfo): string { return joinResult(rawMessageInfo.creatorID, rawMessageInfo.entryID); }, generatesNotifs: async () => pushTypes.NOTIF, }); diff --git a/lib/shared/messages/reaction-message-spec.js b/lib/shared/messages/reaction-message-spec.js index ad06df1fd..63374d199 100644 --- a/lib/shared/messages/reaction-message-spec.js +++ b/lib/shared/messages/reaction-message-spec.js @@ -1,217 +1,217 @@ // @flow import invariant from 'invariant'; import { pushTypes, type MessageSpec, type MessageTitleParam, type GeneratesNotifsParams, } from './message-spec.js'; import { assertSingleMessageInfo, joinResult } from './utils.js'; import type { PlatformDetails } from '../../types/device-types.js'; import { assertMessageType, messageTypes, type MessageInfo, type ClientDBMessageInfo, } from '../../types/message-types.js'; import type { ReactionMessageData, RawReactionMessageInfo, ReactionMessageInfo, } from '../../types/messages/reaction.js'; import type { RawUnsupportedMessageInfo } from '../../types/messages/unsupported.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { ET } from '../../utils/entity-text.js'; import { threadIsGroupChat } from '../thread-utils.js'; import { hasMinCodeVersion } from '../version-utils.js'; export const reactionMessageSpec: MessageSpec< ReactionMessageData, RawReactionMessageInfo, ReactionMessageInfo, > = Object.freeze({ messageContentForServerDB( data: ReactionMessageData | RawReactionMessageInfo, ): string { return JSON.stringify({ reaction: data.reaction, action: data.action, }); }, messageContentForClientDB(data: RawReactionMessageInfo): string { return JSON.stringify({ targetMessageID: data.targetMessageID, reaction: data.reaction, action: data.action, }); }, messageTitle({ messageInfo }: MessageTitleParam) { const creator = ET.user({ userInfo: messageInfo.creator }); const preview = messageInfo.action === 'add_reaction' ? `reacted ${messageInfo.reaction} to a message` : 'unreacted to a message'; return ET`${creator} ${preview}`; }, rawMessageInfoFromServerDBRow(row: Object): RawReactionMessageInfo { invariant( row.targetMessageID !== null && row.targetMessageID !== undefined, 'targetMessageID should be set', ); const content = JSON.parse(row.content); return { type: messageTypes.REACTION, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), targetMessageID: row.targetMessageID.toString(), reaction: content.reaction, action: content.action, }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawReactionMessageInfo { const messageType = assertMessageType(parseInt(clientDBMessageInfo.type)); invariant( messageType === messageTypes.REACTION, 'message must be of type REACTION', ); invariant( clientDBMessageInfo.content !== undefined && clientDBMessageInfo.content !== null, - 'content must be defined', + 'content must be defined for Reaction', ); const content = JSON.parse(clientDBMessageInfo.content); const rawReactionMessageInfo: RawReactionMessageInfo = { type: messageTypes.REACTION, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, targetMessageID: content.targetMessageID, reaction: content.reaction, action: content.action, }; return rawReactionMessageInfo; }, createMessageInfo( rawMessageInfo: RawReactionMessageInfo, creator: RelativeUserInfo, ): ReactionMessageInfo { return { type: messageTypes.REACTION, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, targetMessageID: rawMessageInfo.targetMessageID, reaction: rawMessageInfo.reaction, action: rawMessageInfo.action, }; }, rawMessageInfoFromMessageData( messageData: ReactionMessageData, id: ?string, ): RawReactionMessageInfo { invariant(id, 'RawReactionMessageInfo needs id'); return { ...messageData, id }; }, shimUnsupportedMessageInfo( rawMessageInfo: RawReactionMessageInfo, platformDetails: ?PlatformDetails, ): RawReactionMessageInfo | RawUnsupportedMessageInfo { if (hasMinCodeVersion(platformDetails, 167)) { return rawMessageInfo; } const { id } = rawMessageInfo; invariant(id !== null && id !== undefined, 'id should be set on server'); return { type: messageTypes.UNSUPPORTED, id, threadID: rawMessageInfo.threadID, creatorID: rawMessageInfo.creatorID, time: rawMessageInfo.time, robotext: 'reacted to a message', unsupportedMessageInfo: rawMessageInfo, }; }, unshimMessageInfo(unwrapped: RawReactionMessageInfo): RawReactionMessageInfo { return unwrapped; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { const messageInfo = assertSingleMessageInfo(messageInfos); invariant( messageInfo.type === messageTypes.REACTION, 'messageInfo should be reaction type', ); const creator = ET.user({ userInfo: messageInfo.creator }); const body = messageInfo.action === 'add_reaction' ? `reacted ${messageInfo.reaction} to your message` : 'unreacted to your message'; let merged = ET`${creator} ${body}`; if (threadInfo.name || threadIsGroupChat(threadInfo)) { const thread = ET.thread({ display: 'shortName', threadInfo }); merged = ET`${merged} in ${thread}`; } return { merged, body, title: threadInfo.uiName, prefix: ET`${creator}`, }; }, notificationCollapseKey(rawMessageInfo: RawReactionMessageInfo): string { return joinResult( rawMessageInfo.type, rawMessageInfo.threadID, rawMessageInfo.creatorID, rawMessageInfo.targetMessageID, ); }, generatesNotifs: async ( rawMessageInfo: RawReactionMessageInfo, messageData: ReactionMessageData, params: GeneratesNotifsParams, ) => { const { action } = rawMessageInfo; const { notifTargetUserID, fetchMessageInfoByID } = params; const targetMessageInfo = await fetchMessageInfoByID( rawMessageInfo.targetMessageID, ); if (targetMessageInfo?.creatorID !== notifTargetUserID) { return undefined; } return action === 'add_reaction' ? pushTypes.NOTIF : pushTypes.RESCIND; }, }); diff --git a/lib/shared/messages/remove-members-message-spec.js b/lib/shared/messages/remove-members-message-spec.js index 8ac4e5b44..9ceef3afe 100644 --- a/lib/shared/messages/remove-members-message-spec.js +++ b/lib/shared/messages/remove-members-message-spec.js @@ -1,159 +1,159 @@ // @flow import invariant from 'invariant'; import type { CreateMessageInfoParams, MessageSpec } from './message-spec.js'; import { joinResult } from './utils.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { RawRemoveMembersMessageInfo, RemoveMembersMessageData, RemoveMembersMessageInfo, } from '../../types/messages/remove-members.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { ET, type EntityText, pluralizeEntityText, } from '../../utils/entity-text.js'; import { values } from '../../utils/objects.js'; import { notifRobotextForMessageInfo } from '../notif-utils.js'; export const removeMembersMessageSpec: MessageSpec< RemoveMembersMessageData, RawRemoveMembersMessageInfo, RemoveMembersMessageInfo, > = Object.freeze({ messageContentForServerDB( data: RemoveMembersMessageData | RawRemoveMembersMessageInfo, ): string { return JSON.stringify(data.removedUserIDs); }, messageContentForClientDB(data: RawRemoveMembersMessageInfo): string { return this.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow(row: Object): RawRemoveMembersMessageInfo { return { type: messageTypes.REMOVE_MEMBERS, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), removedUserIDs: JSON.parse(row.content), }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawRemoveMembersMessageInfo { const content = clientDBMessageInfo.content; invariant( content !== undefined && content !== null, - 'content must be defined', + 'content must be defined for RemoveMembers', ); const rawRemoveMembersMessageInfo: RawRemoveMembersMessageInfo = { type: messageTypes.REMOVE_MEMBERS, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, removedUserIDs: JSON.parse(content), }; return rawRemoveMembersMessageInfo; }, createMessageInfo( rawMessageInfo: RawRemoveMembersMessageInfo, creator: RelativeUserInfo, params: CreateMessageInfoParams, ): RemoveMembersMessageInfo { const removedMembers = params.createRelativeUserInfos( rawMessageInfo.removedUserIDs, ); return { type: messageTypes.REMOVE_MEMBERS, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, removedMembers, }; }, rawMessageInfoFromMessageData( messageData: RemoveMembersMessageData, id: ?string, ): RawRemoveMembersMessageInfo { invariant(id, 'RawRemoveMembersMessageInfo needs id'); return { ...messageData, id }; }, robotext(messageInfo: RemoveMembersMessageInfo): EntityText { const users = messageInfo.removedMembers; invariant(users.length !== 0, 'added who??'); const creator = ET.user({ userInfo: messageInfo.creator }); const removedUsers = pluralizeEntityText( users.map(user => ET`${ET.user({ userInfo: user })}`), ); return ET`${creator} removed ${removedUsers}`; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { const removedMembersObject = {}; for (const messageInfo of messageInfos) { invariant( messageInfo.type === messageTypes.REMOVE_MEMBERS, 'messageInfo should be messageTypes.REMOVE_MEMBERS!', ); for (const member of messageInfo.removedMembers) { removedMembersObject[member.id] = member; } } const removedMembers = values(removedMembersObject); const mostRecentMessageInfo = messageInfos[0]; invariant( mostRecentMessageInfo.type === messageTypes.REMOVE_MEMBERS, 'messageInfo should be messageTypes.REMOVE_MEMBERS!', ); const mergedMessageInfo = { ...mostRecentMessageInfo, removedMembers }; const robotext = notifRobotextForMessageInfo(mergedMessageInfo, threadInfo); const merged = ET`${robotext} from ${ET.thread({ display: 'shortName', threadInfo, })}`; return { merged, title: threadInfo.uiName, body: robotext, }; }, notificationCollapseKey(rawMessageInfo: RawRemoveMembersMessageInfo): string { return joinResult( rawMessageInfo.type, rawMessageInfo.threadID, rawMessageInfo.creatorID, ); }, generatesNotifs: async () => undefined, userIDs(rawMessageInfo: RawRemoveMembersMessageInfo): $ReadOnlyArray { return rawMessageInfo.removedUserIDs; }, }); diff --git a/lib/shared/messages/restore-entry-message-spec.js b/lib/shared/messages/restore-entry-message-spec.js index df969c7fa..74683deff 100644 --- a/lib/shared/messages/restore-entry-message-spec.js +++ b/lib/shared/messages/restore-entry-message-spec.js @@ -1,137 +1,137 @@ // @flow import invariant from 'invariant'; import { pushTypes, type MessageSpec } from './message-spec.js'; import { assertSingleMessageInfo } from './utils.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { RawRestoreEntryMessageInfo, RestoreEntryMessageData, RestoreEntryMessageInfo, } from '../../types/messages/restore-entry.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { prettyDate } from '../../utils/date-utils.js'; import { ET, type EntityText } from '../../utils/entity-text.js'; export const restoreEntryMessageSpec: MessageSpec< RestoreEntryMessageData, RawRestoreEntryMessageInfo, RestoreEntryMessageInfo, > = Object.freeze({ messageContentForServerDB( data: RestoreEntryMessageData | RawRestoreEntryMessageInfo, ): string { return JSON.stringify({ entryID: data.entryID, date: data.date, text: data.text, }); }, messageContentForClientDB(data: RawRestoreEntryMessageInfo): string { return this.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow(row: Object): RawRestoreEntryMessageInfo { const content = JSON.parse(row.content); return { type: messageTypes.RESTORE_ENTRY, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), entryID: content.entryID, date: content.date, text: content.text, }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawRestoreEntryMessageInfo { invariant( clientDBMessageInfo.content !== undefined && clientDBMessageInfo.content !== null, - 'content must be defined', + 'content must be defined for RestoreEntry', ); const content = JSON.parse(clientDBMessageInfo.content); const rawRestoreEntryMessageInfo: RawRestoreEntryMessageInfo = { type: messageTypes.RESTORE_ENTRY, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, entryID: content.entryID, date: content.date, text: content.text, }; return rawRestoreEntryMessageInfo; }, createMessageInfo( rawMessageInfo: RawRestoreEntryMessageInfo, creator: RelativeUserInfo, ): RestoreEntryMessageInfo { return { type: messageTypes.RESTORE_ENTRY, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, entryID: rawMessageInfo.entryID, date: rawMessageInfo.date, text: rawMessageInfo.text, }; }, rawMessageInfoFromMessageData( messageData: RestoreEntryMessageData, id: ?string, ): RawRestoreEntryMessageInfo { invariant(id, 'RawRestoreEntryMessageInfo needs id'); return { ...messageData, id }; }, robotext(messageInfo: RestoreEntryMessageInfo): EntityText { const date = prettyDate(messageInfo.date); const creator = ET.user({ userInfo: messageInfo.creator }); const { text } = messageInfo; return ET`${creator} restored an event scheduled for ${date}: "${text}"`; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { const messageInfo = assertSingleMessageInfo(messageInfos); invariant( messageInfo.type === messageTypes.RESTORE_ENTRY, 'messageInfo should be messageTypes.RESTORE_ENTRY!', ); const thread = ET.thread({ display: 'shortName', threadInfo }); const creator = ET.user({ userInfo: messageInfo.creator }); const date = prettyDate(messageInfo.date); const prefix = ET`${creator}`; let body = ET`restored an event in ${thread}`; body = ET`${body} scheduled for ${date}: "${messageInfo.text}"`; const merged = ET`${prefix} ${body}`; return { merged, title: threadInfo.uiName, body, prefix, }; }, generatesNotifs: async () => pushTypes.NOTIF, }); diff --git a/lib/shared/messages/sidebar-source-message-spec.js b/lib/shared/messages/sidebar-source-message-spec.js index 9e6ef1888..b970e3677 100644 --- a/lib/shared/messages/sidebar-source-message-spec.js +++ b/lib/shared/messages/sidebar-source-message-spec.js @@ -1,177 +1,177 @@ // @flow import invariant from 'invariant'; import { pushTypes, type CreateMessageInfoParams, type MessageSpec, type RawMessageInfoFromServerDBRowParams, } from './message-spec.js'; import { joinResult } from './utils.js'; import type { PlatformDetails } from '../../types/device-types.js'; import type { RawSidebarSourceMessageInfo, SidebarSourceMessageData, SidebarSourceMessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import { messageTypes } from '../../types/message-types.js'; import type { RawUnsupportedMessageInfo } from '../../types/messages/unsupported.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { hasMinCodeVersion } from '../version-utils.js'; export const sidebarSourceMessageSpec: MessageSpec< SidebarSourceMessageData, RawSidebarSourceMessageInfo, SidebarSourceMessageInfo, > = Object.freeze({ messageContentForServerDB( data: SidebarSourceMessageData | RawSidebarSourceMessageInfo, ): string { const sourceMessageID = data.sourceMessage?.id; invariant(sourceMessageID, 'Source message id should be set'); return JSON.stringify({ sourceMessageID, }); }, messageContentForClientDB(data: RawSidebarSourceMessageInfo): string { invariant( data.sourceMessage && data.sourceMessage.id, 'sourceMessage and sourceMessage.id should be defined', ); return JSON.stringify(data.sourceMessage); }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawSidebarSourceMessageInfo { invariant( clientDBMessageInfo.content !== undefined && clientDBMessageInfo.content !== null, - 'content must be defined', + 'content must be defined for SidebarSource', ); const sourceMessage = JSON.parse(clientDBMessageInfo.content); const rawSidebarSourceMessageInfo: RawSidebarSourceMessageInfo = { type: messageTypes.SIDEBAR_SOURCE, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, sourceMessage, }; return rawSidebarSourceMessageInfo; }, messageTitle() { invariant(false, 'Cannot call messageTitle on sidebarSourceMessageSpec'); }, rawMessageInfoFromServerDBRow( row: Object, params: RawMessageInfoFromServerDBRowParams, ): RawSidebarSourceMessageInfo { const { derivedMessages } = params; invariant(derivedMessages, 'Derived messages should be provided'); const content = JSON.parse(row.content); const sourceMessage = derivedMessages.get(content.sourceMessageID); if (!sourceMessage) { console.warn( `Message with id ${row.id} has a derived message ` + `${content.sourceMessageID} which is not present in the database`, ); } return { type: messageTypes.SIDEBAR_SOURCE, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), sourceMessage, }; }, createMessageInfo( rawMessageInfo: RawSidebarSourceMessageInfo, creator: RelativeUserInfo, params: CreateMessageInfoParams, ): ?SidebarSourceMessageInfo { if (!rawMessageInfo.sourceMessage) { return null; } const sourceMessage = params.createMessageInfoFromRaw( rawMessageInfo.sourceMessage, ); invariant( sourceMessage && sourceMessage.type !== messageTypes.SIDEBAR_SOURCE && sourceMessage.type !== messageTypes.REACTION, 'Sidebars can not be created from SIDEBAR SOURCE OR REACTION', ); return { type: messageTypes.SIDEBAR_SOURCE, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, sourceMessage, }; }, rawMessageInfoFromMessageData( messageData: SidebarSourceMessageData, id: ?string, ): RawSidebarSourceMessageInfo { invariant(id, 'RawSidebarSourceMessageInfo needs id'); return { ...messageData, id }; }, shimUnsupportedMessageInfo( rawMessageInfo: RawSidebarSourceMessageInfo, platformDetails: ?PlatformDetails, ): RawSidebarSourceMessageInfo | RawUnsupportedMessageInfo { if ( hasMinCodeVersion(platformDetails, 75) && rawMessageInfo.sourceMessage ) { return rawMessageInfo; } const { id } = rawMessageInfo; invariant(id !== null && id !== undefined, 'id should be set on server'); return { type: messageTypes.UNSUPPORTED, id, threadID: rawMessageInfo.threadID, creatorID: rawMessageInfo.creatorID, time: rawMessageInfo.time, robotext: 'first message in thread', dontPrefixCreator: true, unsupportedMessageInfo: rawMessageInfo, }; }, unshimMessageInfo( unwrapped: RawSidebarSourceMessageInfo, ): RawSidebarSourceMessageInfo { return unwrapped; }, notificationTexts(): Promise { invariant( false, 'SIDEBAR_SOURCE notificationTexts should never be called directly!', ); }, notificationCollapseKey(rawMessageInfo: RawSidebarSourceMessageInfo): string { return joinResult(messageTypes.CREATE_SIDEBAR, rawMessageInfo.threadID); }, generatesNotifs: async () => pushTypes.NOTIF, startsThread: true, }); diff --git a/lib/shared/messages/unsupported-message-spec.js b/lib/shared/messages/unsupported-message-spec.js index e9e91f8ef..ecf7d9546 100644 --- a/lib/shared/messages/unsupported-message-spec.js +++ b/lib/shared/messages/unsupported-message-spec.js @@ -1,77 +1,77 @@ // @flow import invariant from 'invariant'; import { pushTypes, type MessageSpec } from './message-spec.js'; import { messageTypes, type ClientDBMessageInfo, } from '../../types/message-types.js'; import type { RawUnsupportedMessageInfo, UnsupportedMessageInfo, } from '../../types/messages/unsupported.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { ET, type EntityText } from '../../utils/entity-text.js'; export const unsupportedMessageSpec: MessageSpec< null, RawUnsupportedMessageInfo, UnsupportedMessageInfo, > = Object.freeze({ messageContentForClientDB(data: RawUnsupportedMessageInfo): string { return JSON.stringify({ robotext: data.robotext, dontPrefixCreator: data.dontPrefixCreator, unsupportedMessageInfo: data.unsupportedMessageInfo, }); }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawUnsupportedMessageInfo { invariant( clientDBMessageInfo.content !== undefined && clientDBMessageInfo.content !== null, - 'content must be defined', + 'content must be defined for Unsupported', ); const content = JSON.parse(clientDBMessageInfo.content); const rawUnsupportedMessageInfo: RawUnsupportedMessageInfo = { type: messageTypes.UNSUPPORTED, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, robotext: content.robotext, dontPrefixCreator: content.dontPrefixCreator, unsupportedMessageInfo: content.unsupportedMessageInfo, }; return rawUnsupportedMessageInfo; }, createMessageInfo( rawMessageInfo: RawUnsupportedMessageInfo, creator: RelativeUserInfo, ): UnsupportedMessageInfo { return { type: messageTypes.UNSUPPORTED, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, time: rawMessageInfo.time, robotext: rawMessageInfo.robotext, dontPrefixCreator: rawMessageInfo.dontPrefixCreator, unsupportedMessageInfo: rawMessageInfo.unsupportedMessageInfo, }; }, robotext(messageInfo: UnsupportedMessageInfo): EntityText { if (messageInfo.dontPrefixCreator) { return ET`${messageInfo.robotext}`; } const creator = ET.user({ userInfo: messageInfo.creator }); return ET`${creator} ${messageInfo.robotext}`; }, generatesNotifs: async () => pushTypes.NOTIF, }); diff --git a/lib/shared/messages/update-relationship-message-spec.js b/lib/shared/messages/update-relationship-message-spec.js index fc6a4ec14..4fa93e7bc 100644 --- a/lib/shared/messages/update-relationship-message-spec.js +++ b/lib/shared/messages/update-relationship-message-spec.js @@ -1,176 +1,176 @@ // @flow import invariant from 'invariant'; import { pushTypes, type CreateMessageInfoParams, type MessageSpec, } from './message-spec.js'; import { assertSingleMessageInfo } from './utils.js'; import type { PlatformDetails } from '../../types/device-types.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, ClientDBMessageInfo, } from '../../types/message-types.js'; import type { RawUnsupportedMessageInfo } from '../../types/messages/unsupported.js'; import type { RawUpdateRelationshipMessageInfo, UpdateRelationshipMessageData, UpdateRelationshipMessageInfo, } from '../../types/messages/update-relationship.js'; import type { NotifTexts } from '../../types/notif-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; import { ET, type EntityText } from '../../utils/entity-text.js'; import { hasMinCodeVersion } from '../version-utils.js'; export const updateRelationshipMessageSpec: MessageSpec< UpdateRelationshipMessageData, RawUpdateRelationshipMessageInfo, UpdateRelationshipMessageInfo, > = Object.freeze({ messageContentForServerDB( data: UpdateRelationshipMessageData | RawUpdateRelationshipMessageInfo, ): string { return JSON.stringify({ operation: data.operation, targetID: data.targetID, }); }, messageContentForClientDB(data: RawUpdateRelationshipMessageInfo): string { return this.messageContentForServerDB(data); }, rawMessageInfoFromServerDBRow(row: Object): RawUpdateRelationshipMessageInfo { const content = JSON.parse(row.content); return { type: messageTypes.UPDATE_RELATIONSHIP, id: row.id.toString(), threadID: row.threadID.toString(), time: row.time, creatorID: row.creatorID.toString(), targetID: content.targetID, operation: content.operation, }; }, rawMessageInfoFromClientDB( clientDBMessageInfo: ClientDBMessageInfo, ): RawUpdateRelationshipMessageInfo { invariant( clientDBMessageInfo.content !== undefined && clientDBMessageInfo.content !== null, - 'content must be defined', + 'content must be defined for UpdateRelationship', ); const content = JSON.parse(clientDBMessageInfo.content); const rawUpdateRelationshipMessageInfo: RawUpdateRelationshipMessageInfo = { type: messageTypes.UPDATE_RELATIONSHIP, id: clientDBMessageInfo.id, threadID: clientDBMessageInfo.thread, time: parseInt(clientDBMessageInfo.time), creatorID: clientDBMessageInfo.user, targetID: content.targetID, operation: content.operation, }; return rawUpdateRelationshipMessageInfo; }, createMessageInfo( rawMessageInfo: RawUpdateRelationshipMessageInfo, creator: RelativeUserInfo, params: CreateMessageInfoParams, ): ?UpdateRelationshipMessageInfo { const target = params.createRelativeUserInfos([rawMessageInfo.targetID])[0]; if (!target) { return null; } return { type: messageTypes.UPDATE_RELATIONSHIP, id: rawMessageInfo.id, threadID: rawMessageInfo.threadID, creator, target, time: rawMessageInfo.time, operation: rawMessageInfo.operation, }; }, rawMessageInfoFromMessageData( messageData: UpdateRelationshipMessageData, id: ?string, ): RawUpdateRelationshipMessageInfo { invariant(id, 'RawUpdateRelationshipMessageInfo needs id'); return { ...messageData, id }; }, robotext(messageInfo: UpdateRelationshipMessageInfo): EntityText { const creator = ET.user({ userInfo: messageInfo.creator }); if (messageInfo.operation === 'request_sent') { const target = ET.user({ userInfo: messageInfo.target }); return ET`${creator} sent ${target} a friend request`; } else if (messageInfo.operation === 'request_accepted') { const targetPossessive = ET.user({ userInfo: messageInfo.target, possessive: true, }); return ET`${creator} accepted ${targetPossessive} friend request`; } invariant( false, `Invalid operation ${messageInfo.operation} ` + `of message with type ${messageInfo.type}`, ); }, shimUnsupportedMessageInfo( rawMessageInfo: RawUpdateRelationshipMessageInfo, platformDetails: ?PlatformDetails, ): RawUpdateRelationshipMessageInfo | RawUnsupportedMessageInfo { if (hasMinCodeVersion(platformDetails, 71)) { return rawMessageInfo; } const { id } = rawMessageInfo; invariant(id !== null && id !== undefined, 'id should be set on server'); return { type: messageTypes.UNSUPPORTED, id, threadID: rawMessageInfo.threadID, creatorID: rawMessageInfo.creatorID, time: rawMessageInfo.time, robotext: 'performed a relationship action', unsupportedMessageInfo: rawMessageInfo, }; }, unshimMessageInfo( unwrapped: RawUpdateRelationshipMessageInfo, ): RawUpdateRelationshipMessageInfo { return unwrapped; }, async notificationTexts( messageInfos: $ReadOnlyArray, threadInfo: ThreadInfo, ): Promise { const messageInfo = assertSingleMessageInfo(messageInfos); const creator = ET.user({ userInfo: messageInfo.creator }); const prefix = ET`${creator}`; const title = threadInfo.uiName; const body = messageInfo.operation === 'request_sent' ? 'sent you a friend request' : 'accepted your friend request'; const merged = ET`${prefix} ${body}`; return { merged, body, title, prefix, }; }, generatesNotifs: async () => pushTypes.NOTIF, });