diff --git a/lib/permissions/minimally-encoded-raw-thread-info-validators.js b/lib/permissions/minimally-encoded-raw-thread-info-validators.js --- a/lib/permissions/minimally-encoded-raw-thread-info-validators.js +++ b/lib/permissions/minimally-encoded-raw-thread-info-validators.js @@ -8,6 +8,7 @@ } from './minimally-encoded-thread-permissions.js'; import { specialRoleValidator } from './special-roles.js'; import type { + FarcasterRawThreadInfo, MemberInfoWithPermissions, ThreadCurrentUserInfo, ThinRawThreadInfo, @@ -23,6 +24,7 @@ legacyThinRawThreadInfoValidator, legacyThreadCurrentUserInfoValidator, legacyThickRawThreadInfoValidator, + legacyFarcasterRawThreadInfoValidator, } from '../types/thread-types.js'; import { tBool, tID, tShape, tUserID } from '../utils/validation-utils.js'; @@ -111,12 +113,26 @@ currentUser: threadCurrentUserInfoValidator, }); +const farcasterRawThreadInfoValidator: TInterface = + tShape({ + ...legacyFarcasterRawThreadInfoValidator.meta.props, + minimallyEncoded: tBool(true), + members: t.list(memberInfoSansPermissionsValidator), + roles: t.dict(tID, roleInfoValidator), + currentUser: threadCurrentUserInfoValidator, + }); + const mixedThinRawThreadInfoValidator: TUnion< LegacyThinRawThreadInfo | ThinRawThreadInfo, > = t.union([legacyThinRawThreadInfoValidator, thinRawThreadInfoValidator]); -const rawThreadInfoValidator: TUnion = - t.union([thinRawThreadInfoValidator, thickRawThreadInfoValidator]); +const rawThreadInfoValidator: TUnion< + ThinRawThreadInfo | ThickRawThreadInfo | FarcasterRawThreadInfo, +> = t.union([ + thinRawThreadInfoValidator, + thickRawThreadInfoValidator, + farcasterRawThreadInfoValidator, +]); export { memberInfoWithPermissionsValidator, diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js --- a/lib/shared/thread-utils.js +++ b/lib/shared/thread-utils.js @@ -815,7 +815,8 @@ const minimallyEncodedRawThreadInfoWithMemberPermissions = minimallyEncodeRawThreadInfoWithMemberPermissions(rawThreadInfo); invariant( - !minimallyEncodedRawThreadInfoWithMemberPermissions.thick, + !minimallyEncodedRawThreadInfoWithMemberPermissions.thick && + !minimallyEncodedRawThreadInfoWithMemberPermissions.farcaster, 'ServerThreadInfo should be thin thread', ); diff --git a/lib/shared/updates/delete-account-spec.js b/lib/shared/updates/delete-account-spec.js --- a/lib/shared/updates/delete-account-spec.js +++ b/lib/shared/updates/delete-account-spec.js @@ -37,6 +37,16 @@ members: newMembers, }; } + } else if (threadInfo.farcaster) { + const newMembers = threadInfo.members.filter( + member => member.id !== update.deletedUserID, + ); + if (newMembers.length < threadInfo.members.length) { + replacedThreadInfo = { + ...threadInfo, + members: newMembers, + }; + } } else { const newMembers = threadInfo.members.filter( member => member.id !== update.deletedUserID, diff --git a/lib/types/minimally-encoded-thread-permissions-types.js b/lib/types/minimally-encoded-thread-permissions-types.js --- a/lib/types/minimally-encoded-thread-permissions-types.js +++ b/lib/types/minimally-encoded-thread-permissions-types.js @@ -14,6 +14,7 @@ LegacyThinRawThreadInfo, LegacyThreadCurrentUserInfo, ThickMemberInfo, + LegacyFarcasterRawThreadInfo, } from './thread-types.js'; import { decodeThreadRolePermissionsBitmaskArray, @@ -173,7 +174,18 @@ +currentUser: ThreadCurrentUserInfo, }>; -export type RawThreadInfo = ThinRawThreadInfo | ThickRawThreadInfo; +export type FarcasterRawThreadInfo = $ReadOnly<{ + ...LegacyFarcasterRawThreadInfo, + +minimallyEncoded: true, + +members: $ReadOnlyArray, + +roles: { +[id: string]: RoleInfo }, + +currentUser: ThreadCurrentUserInfo, +}>; + +export type RawThreadInfo = + | ThinRawThreadInfo + | ThickRawThreadInfo + | FarcasterRawThreadInfo; const minimallyEncodeRawThreadInfoWithMemberPermissions = ( rawThreadInfo: LegacyRawThreadInfo, diff --git a/lib/types/thread-types-enum.js b/lib/types/thread-types-enum.js --- a/lib/types/thread-types-enum.js +++ b/lib/types/thread-types-enum.js @@ -127,9 +127,8 @@ ); return threadType; } -export const farcasterThreadTypeValidator: TRefinement = tNumEnum( - values(farcasterThreadTypes), -); +export const farcasterThreadTypeValidator: TRefinement = + tNumEnum(values(farcasterThreadTypes)); export function assertThreadType(threadType: number): ThreadType { invariant( diff --git a/lib/types/thread-types.js b/lib/types/thread-types.js --- a/lib/types/thread-types.js +++ b/lib/types/thread-types.js @@ -36,6 +36,8 @@ type ThickThreadType, thinThreadTypeValidator, thickThreadTypeValidator, + type FarcasterThreadType, + farcasterThreadTypeValidator, } from './thread-types-enum.js'; import type { ClientUpdateInfo, ServerUpdateInfo } from './update-types.js'; import type { UserInfo, UserInfos } from './user-types.js'; @@ -174,9 +176,29 @@ +timestamps: ThreadTimestamps, }; +export type LegacyFarcasterRawThreadInfo = { + +farcaster: true, + +id: string, + +type: FarcasterThreadType, + +name?: ?string, + +avatar?: ?ClientAvatar, + +description?: ?string, + +color: string, // hex, without "#" or "0x" + +creationTime: number, // millisecond timestamp + +parentThreadID: ?string, + +containingThreadID?: ?string, + +community: ?string, + +members: $ReadOnlyArray, + +roles: { +[id: string]: ClientLegacyRoleInfo }, + +currentUser: LegacyThreadCurrentUserInfo, + +repliesCount: number, + +pinnedCount?: number, +}; + export type LegacyRawThreadInfo = | LegacyThinRawThreadInfo - | LegacyThickRawThreadInfo; + | LegacyThickRawThreadInfo + | LegacyFarcasterRawThreadInfo; export type LegacyRawThreadInfos = { +[id: string]: LegacyRawThreadInfo, @@ -231,11 +253,34 @@ timestamps: threadTimestampsValidator, }); +export const legacyFarcasterRawThreadInfoValidator: TInterface = + tShape({ + farcaster: tBool(true), + id: tID, + type: farcasterThreadTypeValidator, + name: t.maybe(t.String), + avatar: t.maybe(clientAvatarValidator), + description: t.maybe(t.String), + color: t.String, + creationTime: t.Number, + parentThreadID: t.maybe(tID), + containingThreadID: t.maybe(tID), + community: t.maybe(tID), + members: t.list(legacyMemberInfoValidator), + roles: t.dict(tID, clientLegacyRoleInfoValidator), + currentUser: legacyThreadCurrentUserInfoValidator, + repliesCount: t.Number, + pinnedCount: t.maybe(t.Number), + }); + export const legacyThreadInfoValidator: TUnion< - LegacyThinRawThreadInfo | LegacyThickRawThreadInfo, + | LegacyThinRawThreadInfo + | LegacyThickRawThreadInfo + | LegacyFarcasterRawThreadInfo, > = t.union([ legacyThinRawThreadInfoValidator, legacyThickRawThreadInfoValidator, + legacyFarcasterRawThreadInfoValidator, ]); export type MixedRawThreadInfos = { diff --git a/lib/utils/thread-ops-utils.js b/lib/utils/thread-ops-utils.js --- a/lib/utils/thread-ops-utils.js +++ b/lib/utils/thread-ops-utils.js @@ -32,7 +32,7 @@ function convertRawThreadInfoToClientDBThreadInfo( rawThreadInfo: LegacyRawThreadInfo | RawThreadInfo, ): ClientDBThreadInfo { - const { minimallyEncoded, thick, ...rest } = rawThreadInfo; + const { minimallyEncoded, thick, farcaster, ...rest } = rawThreadInfo; return { ...rest, creationTime: rawThreadInfo.creationTime.toString(), diff --git a/native/redux/edit-thread-permission-migration.js b/native/redux/edit-thread-permission-migration.js --- a/native/redux/edit-thread-permission-migration.js +++ b/native/redux/edit-thread-permission-migration.js @@ -90,6 +90,17 @@ roles: updatedRoles, }; newThreadInfos[threadID] = newThreadInfo; + } else if (threadInfo.farcaster) { + const updatedMembers = threadInfo.members.map(member => + addDetailedThreadEditPermissionsToUser(threadInfo, member, threadID), + ); + const newThreadInfo = { + ...threadInfo, + members: updatedMembers, + currentUser: updatedCurrentUser, + roles: updatedRoles, + }; + newThreadInfos[threadID] = newThreadInfo; } else { const updatedMembers = threadInfo.members.map(member => addDetailedThreadEditPermissionsToUser(threadInfo, member, threadID), diff --git a/native/redux/manage-pins-permission-migration.js b/native/redux/manage-pins-permission-migration.js --- a/native/redux/manage-pins-permission-migration.js +++ b/native/redux/manage-pins-permission-migration.js @@ -90,6 +90,17 @@ roles: updatedRoles, }; newThreadInfos[threadID] = updatedThreadInfo; + } else if (threadInfo.farcaster) { + const updatedMembers = threadInfo.members.map(member => + addManagePinsThreadPermissionToUser(threadInfo, member, threadID), + ); + const updatedThreadInfo = { + ...threadInfo, + members: updatedMembers, + currentUser: updatedCurrentUser, + roles: updatedRoles, + }; + newThreadInfos[threadID] = updatedThreadInfo; } else { const updatedMembers = threadInfo.members.map(member => addManagePinsThreadPermissionToUser(threadInfo, member, threadID),