Page MenuHomePhabricator

D12835.id42740.diff
No OneTemporary

D12835.id42740.diff

diff --git a/lib/shared/dm-ops/add-members-spec.js b/lib/shared/dm-ops/add-members-spec.js
new file mode 100644
--- /dev/null
+++ b/lib/shared/dm-ops/add-members-spec.js
@@ -0,0 +1,117 @@
+// @flow
+
+import invariant from 'invariant';
+import uuid from 'uuid';
+
+import { createRoleAndPermissionForThickThreads } from './create-thread-spec.js';
+import type {
+ DMOperationSpec,
+ ProcessDMOperationUtilities,
+} from './dm-op-spec.js';
+import type { DMAddMembersOperation } from '../../types/dm-ops.js';
+import { messageTypes } from '../../types/message-types-enum.js';
+import type { ThickRawThreadInfo } from '../../types/minimally-encoded-thread-permissions-types.js';
+import { minimallyEncodeMemberInfo } from '../../types/minimally-encoded-thread-permissions-types.js';
+import { joinThreadSubscription } from '../../types/subscription-types.js';
+import type { ThickMemberInfo } from '../../types/thread-types.js';
+import { updateTypes } from '../../types/update-types-enum.js';
+import type { ClientUpdateInfo } from '../../types/update-types.js';
+import { values } from '../../utils/objects.js';
+import { roleIsDefaultRole, userIsMember } from '../thread-utils.js';
+
+const addMembersSpec: DMOperationSpec<DMAddMembersOperation> = Object.freeze({
+ processDMOperation: async (
+ dmOperation: DMAddMembersOperation,
+ viewerID: string,
+ utilities: ProcessDMOperationUtilities,
+ ) => {
+ const {
+ editorID,
+ time,
+ messageID,
+ addedUserIDs,
+ threadInfo,
+ rawMessageInfos,
+ truncationStatus,
+ rawEntryInfos,
+ } = dmOperation;
+ const addMembersMessage = {
+ type: messageTypes.ADD_MEMBERS,
+ id: messageID,
+ threadID: threadInfo.id,
+ creatorID: editorID,
+ time,
+ addedUserIDs: [...addedUserIDs],
+ };
+
+ const viewerIsAdded = addedUserIDs.includes(viewerID);
+ const updateInfos: Array<ClientUpdateInfo> = [];
+ if (viewerIsAdded) {
+ updateInfos.push(
+ {
+ type: updateTypes.JOIN_THREAD,
+ id: uuid.v4(),
+ time,
+ threadInfo,
+ rawMessageInfos,
+ truncationStatus,
+ rawEntryInfos,
+ },
+ {
+ type: updateTypes.UPDATE_THREAD_READ_STATUS,
+ id: uuid.v4(),
+ time,
+ threadID: threadInfo.id,
+ unread: true,
+ },
+ );
+ } else {
+ const currentThreadInfoOptional = utilities.getThreadInfo(threadInfo.id);
+ if (!currentThreadInfoOptional || !currentThreadInfoOptional.thick) {
+ // We can't perform this operation now. It should be queued for later.
+ return {
+ rawMessageInfos: [],
+ updateInfos: [],
+ };
+ }
+ const currentThreadInfo: ThickRawThreadInfo = currentThreadInfoOptional;
+ const defaultRoleID = values(currentThreadInfo.roles).find(role =>
+ roleIsDefaultRole(role),
+ )?.id;
+ invariant(defaultRoleID, 'Default role ID must exist');
+ const { membershipPermissions } = createRoleAndPermissionForThickThreads(
+ currentThreadInfo.type,
+ currentThreadInfo.id,
+ defaultRoleID,
+ );
+ const newMembers = addedUserIDs
+ .filter(userID => !userIsMember(currentThreadInfo, userID))
+ .map(userID =>
+ minimallyEncodeMemberInfo<ThickMemberInfo>({
+ id: userID,
+ role: defaultRoleID,
+ permissions: membershipPermissions,
+ isSender: editorID === viewerID,
+ subscription: joinThreadSubscription,
+ }),
+ );
+
+ const newThreadInfo = {
+ ...currentThreadInfo,
+ members: [...currentThreadInfo.members, ...newMembers],
+ };
+ updateInfos.push({
+ type: updateTypes.UPDATE_THREAD,
+ id: uuid.v4(),
+ time,
+ threadInfo: newThreadInfo,
+ });
+ }
+ return {
+ rawMessageInfos: [addMembersMessage],
+ updateInfos,
+ };
+ },
+});
+
+export { addMembersSpec };
diff --git a/lib/shared/dm-ops/create-thread-spec.js b/lib/shared/dm-ops/create-thread-spec.js
--- a/lib/shared/dm-ops/create-thread-spec.js
+++ b/lib/shared/dm-ops/create-thread-spec.js
@@ -9,7 +9,6 @@
makePermissionsBlob,
getThickThreadRolePermissionsBlob,
} from '../../permissions/thread-permissions.js';
-import { generatePendingThreadColor } from '../../shared/color-utils.js';
import type { DMCreateThreadOperation } from '../../types/dm-ops.js';
import { messageTypes } from '../../types/message-types-enum.js';
import {
@@ -24,9 +23,11 @@
minimallyEncodeThreadCurrentUserInfo,
} from '../../types/minimally-encoded-thread-permissions-types.js';
import { joinThreadSubscription } from '../../types/subscription-types.js';
+import type { ThreadPermissionsInfo } from '../../types/thread-permission-types.js';
import type { ThickThreadType } from '../../types/thread-types-enum.js';
import type { ThickMemberInfo } from '../../types/thread-types.js';
import { updateTypes } from '../../types/update-types-enum.js';
+import { generatePendingThreadColor } from '../color-utils.js';
type CreateThickRawThreadInfoInput = {
+threadID: string,
@@ -38,6 +39,32 @@
+creatorID: string,
+viewerID: string,
};
+
+function createRoleAndPermissionForThickThreads(
+ threadType: ThickThreadType,
+ threadID: string,
+ roleID: string,
+): { +role: RoleInfo, +membershipPermissions: ThreadPermissionsInfo } {
+ const rolePermissions = getThickThreadRolePermissionsBlob(threadType);
+ const membershipPermissions = getAllThreadPermissions(
+ makePermissionsBlob(rolePermissions, null, threadID, threadType),
+ threadID,
+ );
+ const role: RoleInfo = {
+ ...minimallyEncodeRoleInfo({
+ id: roleID,
+ name: 'Members',
+ permissions: rolePermissions,
+ isDefault: true,
+ }),
+ specialRole: specialRoles.DEFAULT_ROLE,
+ };
+ return {
+ membershipPermissions,
+ role,
+ };
+}
+
type MutableThickRawThreadInfo = { ...ThickRawThreadInfo };
function createThickRawThreadInfo(
input: CreateThickRawThreadInfoInput,
@@ -55,20 +82,8 @@
const color = generatePendingThreadColor(allMemberIDs);
- const rolePermissions = getThickThreadRolePermissionsBlob(threadType);
- const membershipPermissions = getAllThreadPermissions(
- makePermissionsBlob(rolePermissions, null, threadID, threadType),
- threadID,
- );
- const role: RoleInfo = {
- ...minimallyEncodeRoleInfo({
- id: roleID,
- name: 'Members',
- permissions: rolePermissions,
- isDefault: true,
- }),
- specialRole: specialRoles.DEFAULT_ROLE,
- };
+ const { membershipPermissions, role } =
+ createRoleAndPermissionForThickThreads(threadType, threadID, roleID);
return {
thick: true,
@@ -159,4 +174,8 @@
},
});
-export { createThickRawThreadInfo, createThreadSpec };
+export {
+ createThickRawThreadInfo,
+ createThreadSpec,
+ createRoleAndPermissionForThickThreads,
+};
diff --git a/lib/shared/dm-ops/dm-op-spec.js b/lib/shared/dm-ops/dm-op-spec.js
--- a/lib/shared/dm-ops/dm-op-spec.js
+++ b/lib/shared/dm-ops/dm-op-spec.js
@@ -2,10 +2,12 @@
import type { DMOperation, DMOperationResult } from '../../types/dm-ops.js';
import type { RawMessageInfo } from '../../types/message-types.js';
+import type { RawThreadInfo } from '../../types/minimally-encoded-thread-permissions-types.js';
export type ProcessDMOperationUtilities = {
// Needed to fetch sidebar source messages
+fetchMessage: (messageID: string) => Promise<?RawMessageInfo>,
+ +getThreadInfo: (threadID: string) => ?RawThreadInfo,
};
export type DMOperationSpec<DMOp: DMOperation> = {
diff --git a/lib/shared/dm-ops/dm-op-specs.js b/lib/shared/dm-ops/dm-op-specs.js
--- a/lib/shared/dm-ops/dm-op-specs.js
+++ b/lib/shared/dm-ops/dm-op-specs.js
@@ -1,5 +1,6 @@
// @flow
+import { addMembersSpec } from './add-members-spec.js';
import { createSidebarSpec } from './create-sidebar-spec.js';
import { createThreadSpec } from './create-thread-spec.js';
import type { DMOperationSpec } from './dm-op-spec.js';
@@ -16,4 +17,5 @@
[dmOperationTypes.SEND_TEXT_MESSAGE]: sendTextMessageSpec,
[dmOperationTypes.SEND_REACTION_MESSAGE]: sendReactionMessageSpec,
[dmOperationTypes.SEND_EDIT_MESSAGE]: sendEditMessageSpec,
+ [dmOperationTypes.ADD_MEMBERS]: addMembersSpec,
});
diff --git a/lib/shared/dm-ops/process-dm-ops.js b/lib/shared/dm-ops/process-dm-ops.js
--- a/lib/shared/dm-ops/process-dm-ops.js
+++ b/lib/shared/dm-ops/process-dm-ops.js
@@ -9,15 +9,23 @@
type DMOperation,
processDMOpsActionType,
} from '../../types/dm-ops.js';
-import { useDispatch } from '../../utils/redux-utils.js';
+import { useDispatch, useSelector } from '../../utils/redux-utils.js';
function useProcessDMOperation(): (dmOp: DMOperation) => Promise<void> {
const fetchMessage = useGetLatestMessageEdit();
+ const threadInfos = useSelector(state => state.threadStore.threadInfos);
+
+ const getThreadInfo = React.useCallback(
+ (id: string) => threadInfos[id],
+ [threadInfos],
+ );
+
const utilities = React.useMemo(
() => ({
fetchMessage,
+ getThreadInfo,
}),
- [fetchMessage],
+ [fetchMessage, getThreadInfo],
);
const dispatch = useDispatch();
diff --git a/lib/types/dm-ops.js b/lib/types/dm-ops.js
--- a/lib/types/dm-ops.js
+++ b/lib/types/dm-ops.js
@@ -1,14 +1,23 @@
// @flow
-import type { TInterface, TUnion } from 'tcomb';
-import t from 'tcomb';
+import t, { type TInterface, type TUnion } from 'tcomb';
-import type { RawMessageInfo } from './message-types.js';
+import { type RawEntryInfo, rawEntryInfoValidator } from './entry-types.js';
+import type {
+ MessageTruncationStatus,
+ RawMessageInfo,
+} from './message-types.js';
+import {
+ messageTruncationStatusValidator,
+ rawMessageInfoValidator,
+} from './message-types.js';
+import type { ThickRawThreadInfo } from './minimally-encoded-thread-permissions-types.js';
import {
type NonSidebarThickThreadType,
nonSidebarThickThreadTypes,
} from './thread-types-enum.js';
import type { ClientUpdateInfo } from './update-types.js';
+import { threadInfoValidator } from '../permissions/minimally-encoded-thread-permissions-validators.js';
import { values } from '../utils/objects.js';
import { tShape, tString, tUserID } from '../utils/validation-utils.js';
@@ -18,6 +27,7 @@
SEND_TEXT_MESSAGE: 'send_text_message',
SEND_REACTION_MESSAGE: 'send_reaction_message',
SEND_EDIT_MESSAGE: 'send_edit_message',
+ ADD_MEMBERS: 'add_members',
});
export type DMOperationType = $Values<typeof dmOperationTypes>;
@@ -129,18 +139,44 @@
text: t.String,
});
+export type DMAddMembersOperation = {
+ +type: 'add_members',
+ +editorID: string,
+ +time: number,
+ +messageID: string,
+ +addedUserIDs: $ReadOnlyArray<string>,
+ +threadInfo: ThickRawThreadInfo,
+ +rawMessageInfos: $ReadOnlyArray<RawMessageInfo>,
+ +truncationStatus: MessageTruncationStatus,
+ +rawEntryInfos: $ReadOnlyArray<RawEntryInfo>,
+};
+export const dmAddMembersOperation: TInterface<DMAddMembersOperation> =
+ tShape<DMAddMembersOperation>({
+ type: tString(dmOperationTypes.ADD_MEMBERS),
+ editorID: tUserID,
+ time: t.Number,
+ messageID: t.String,
+ addedUserIDs: t.list(tUserID),
+ threadInfo: threadInfoValidator,
+ rawMessageInfos: t.list(rawMessageInfoValidator),
+ truncationStatus: messageTruncationStatusValidator,
+ rawEntryInfos: t.list(rawEntryInfoValidator),
+ });
+
export type DMOperation =
| DMCreateThreadOperation
| DMCreateSidebarOperation
| DMSendTextMessageOperation
| DMSendReactionMessageOperation
- | DMSendEditMessageOperation;
+ | DMSendEditMessageOperation
+ | DMAddMembersOperation;
export const dmOperationValidator: TUnion<DMOperation> = t.union([
dmCreateThreadOperationValidator,
dmCreateSidebarOperationValidator,
dmSendTextMessageOperationValidator,
dmSendReactionMessageOperation,
dmSendEditMessageOperation,
+ dmAddMembersOperation,
]);
export type DMOperationResult = {

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 9, 6:26 AM (19 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2449690
Default Alt Text
D12835.id42740.diff (11 KB)

Event Timeline