diff --git a/lib/shared/farcaster/farcaster-hooks.js b/lib/shared/farcaster/farcaster-hooks.js --- a/lib/shared/farcaster/farcaster-hooks.js +++ b/lib/shared/farcaster/farcaster-hooks.js @@ -13,11 +13,9 @@ import type { FarcasterConversation } from './farcaster-conversation-types.js'; import { messageTruncationStatus } from '../../types/message-types.js'; import { updateTypes } from '../../types/update-types-enum.js'; -import { - convertFarcasterMessageToCommMessages, - createFarcasterRawThreadInfo, - useSetFarcasterDCsLoaded, -} from '../../utils/farcaster-utils.js'; +import { convertFarcasterMessageToCommMessages } from '../../utils/convert-farcaster-message-to-comm-messages.js'; +import { createFarcasterRawThreadInfo } from '../../utils/create-farcaster-raw-thread-info.js'; +import { useSetFarcasterDCsLoaded } from '../../utils/farcaster-utils.js'; import { useDispatch } from '../../utils/redux-utils.js'; import { useSendDMOperationUtils } from '../dm-ops/dm-op-utils.js'; diff --git a/lib/utils/convert-farcaster-message-to-comm-messages.js b/lib/utils/convert-farcaster-message-to-comm-messages.js new file mode 100644 --- /dev/null +++ b/lib/utils/convert-farcaster-message-to-comm-messages.js @@ -0,0 +1,85 @@ +// @flow + +import uuid from 'uuid'; + +import type { FarcasterMessage } from '../shared/farcaster/farcaster-messages-types.js'; +import { farcasterThreadIDFromConversationID } from '../shared/id-utils.js'; +import type { Media } from '../types/media-types.js'; +import { messageTypes } from '../types/message-types-enum.js'; +import type { RawMessageInfo } from '../types/message-types.js'; + +function convertFarcasterMessageToCommMessages( + farcasterMessage: FarcasterMessage, +): $ReadOnlyArray { + const creatorID = `${farcasterMessage.senderFid}`; + const threadID = farcasterThreadIDFromConversationID( + farcasterMessage.conversationId, + ); + if ( + farcasterMessage.type === 'group_membership_addition' && + farcasterMessage.actionTargetUserContext?.fid + ) { + const addedUser = `${farcasterMessage.actionTargetUserContext.fid}`; + return [ + { + id: farcasterMessage.messageId, + type: messageTypes.ADD_MEMBERS, + threadID, + creatorID, + time: parseInt(farcasterMessage.serverTimestamp, 10), + addedUserIDs: [addedUser], + }, + ]; + } + + if ( + farcasterMessage.type === 'text' && + !!farcasterMessage?.metadata?.medias + ) { + const media = farcasterMessage.metadata.medias.map( + med => + ({ + id: uuid.v4(), + uri: med.staticRaster, + type: 'photo', + thumbHash: null, + dimensions: { + height: med.height, + width: med.width, + }, + }: Media), + ); + return [ + { + id: farcasterMessage.messageId, + type: messageTypes.MULTIMEDIA, + threadID, + creatorID, + time: parseInt(farcasterMessage.serverTimestamp, 10), + media, + }, + ]; + } + + if (farcasterMessage.type === 'text') { + return [ + { + id: farcasterMessage.messageId, + type: messageTypes.TEXT, + threadID, + creatorID, + time: parseInt(farcasterMessage.serverTimestamp, 10), + text: farcasterMessage.message, + }, + ]; + } + + console.log( + 'UNSUPPORTED FARCASTER MESSAGE', + JSON.stringify(farcasterMessage, null, 2), + ); + + return []; +} + +export { convertFarcasterMessageToCommMessages }; diff --git a/lib/utils/create-farcaster-raw-thread-info.js b/lib/utils/create-farcaster-raw-thread-info.js new file mode 100644 --- /dev/null +++ b/lib/utils/create-farcaster-raw-thread-info.js @@ -0,0 +1,143 @@ +// @flow + +import { getFarcasterRolePermissionsBlobsFromConversation } from '../permissions/farcaster-permissions.js'; +import { specialRoles } from '../permissions/special-roles.js'; +import { + getAllThreadPermissions, + makePermissionsBlob, +} from '../permissions/thread-permissions.js'; +import { generatePendingThreadColor } from '../shared/color-utils.js'; +import type { FarcasterConversation } from '../shared/farcaster/farcaster-conversation-types.js'; +import { farcasterThreadIDFromConversationID } from '../shared/id-utils.js'; +import type { ClientAvatar } from '../types/avatar-types.js'; +import type { + FarcasterRawThreadInfo, + RoleInfo, + ThreadCurrentUserInfo, +} from '../types/minimally-encoded-thread-permissions-types.js'; +import { + minimallyEncodeRoleInfo, + minimallyEncodeThreadCurrentUserInfo, +} from '../types/minimally-encoded-thread-permissions-types.js'; +import type { ThreadRolePermissionsBlob } from '../types/thread-permission-types.js'; +import { farcasterThreadTypes } from '../types/thread-types-enum.js'; +import type { FarcasterThreadType } from '../types/thread-types-enum.js'; + +function createPermissionsInfo( + permissionsBlob: ThreadRolePermissionsBlob, + threadID: string, + threadType: FarcasterThreadType, +) { + return getAllThreadPermissions( + makePermissionsBlob(permissionsBlob, null, threadID, threadType), + threadID, + ); +} + +function createFarcasterRawThreadInfo( + conversation: FarcasterConversation, +): FarcasterRawThreadInfo { + const threadID = farcasterThreadIDFromConversationID( + conversation.conversationId, + ); + const threadType = conversation.isGroup + ? farcasterThreadTypes.FARCASTER_GROUP + : farcasterThreadTypes.FARCASTER_PERSONAL; + const permissionBlobs = + getFarcasterRolePermissionsBlobsFromConversation(conversation); + + const membersRole: RoleInfo = { + ...minimallyEncodeRoleInfo({ + id: `${threadID}/member/role`, + name: 'Members', + permissions: permissionBlobs.Members, + isDefault: true, + }), + specialRole: specialRoles.DEFAULT_ROLE, + }; + const adminsRole: ?RoleInfo = permissionBlobs.Admins + ? { + ...minimallyEncodeRoleInfo({ + id: `${threadID}/admin/role`, + name: 'Admins', + permissions: permissionBlobs.Admins, + isDefault: false, + }), + specialRole: specialRoles.ADMIN_ROLE, + } + : null; + const roles: { [id: string]: RoleInfo } = { + [membersRole.id]: membersRole, + }; + if (adminsRole) { + roles[adminsRole.id] = adminsRole; + } + + const removedUsers = new Set(conversation.removedFids); + const userIDs = conversation.participants + .filter(p => !removedUsers.has(p.fid)) + .map(p => `${p.fid}`); + const adminIDs = new Set(conversation.adminFids.map(fid => `${fid}`)); + + const members = userIDs.map(fid => ({ + id: fid, + // This flag was introduced for sidebars to show who replied to a thread. + // Now it doesn't seem to be used anywhere. Regardless, for Farcaster + // threads its value doesn't matter. + isSender: true, + minimallyEncoded: true, + role: adminIDs.has(fid) && adminsRole ? adminsRole.id : membersRole.id, + })); + + const currentUserRole = + conversation.viewerContext.access === 'admin' && adminsRole + ? adminsRole + : membersRole; + const currentUser: ThreadCurrentUserInfo = + minimallyEncodeThreadCurrentUserInfo({ + role: currentUserRole.id, + permissions: createPermissionsInfo( + conversation.viewerContext.access === 'admin' && permissionBlobs.Admins + ? permissionBlobs.Admins + : permissionBlobs.Members, + threadID, + threadType, + ), + subscription: { + home: true, + pushNotifs: !conversation.viewerContext.muted, + }, + unread: conversation.viewerContext.unreadCount > 0, + }); + + let avatar: ?ClientAvatar; + if (conversation.isGroup) { + avatar = conversation.photoUrl + ? { type: 'image', uri: conversation.photoUrl } + : null; + } else { + const uri = conversation.viewerContext.counterParty?.pfp?.url; + avatar = uri ? { type: 'image', uri } : null; + } + + return { + farcaster: true, + id: threadID, + type: threadType, + name: conversation.name, + avatar, + description: conversation.description, + color: generatePendingThreadColor(userIDs), + parentThreadID: null, + community: null, + creationTime: conversation.createdAt, + repliesCount: 0, + pinnedCount: conversation.pinnedMessages.length, + minimallyEncoded: true, + members, + roles, + currentUser, + }; +} + +export { createFarcasterRawThreadInfo }; diff --git a/lib/utils/farcaster-utils.js b/lib/utils/farcaster-utils.js --- a/lib/utils/farcaster-utils.js +++ b/lib/utils/farcaster-utils.js @@ -6,42 +6,16 @@ import { getConfig } from './config.js'; import { getContentSigningKey } from './crypto-utils.js'; -import { useSelector, useDispatch } from './redux-utils.js'; +import { useDispatch, useSelector } from './redux-utils.js'; import { setSyncedMetadataEntryActionType } from '../actions/synced-metadata-actions.js'; import { useUserIdentityCache } from '../components/user-identity-cache.react.js'; -import { getFarcasterRolePermissionsBlobsFromConversation } from '../permissions/farcaster-permissions.js'; -import { specialRoles } from '../permissions/special-roles.js'; -import { - getAllThreadPermissions, - makePermissionsBlob, -} from '../permissions/thread-permissions.js'; import { getOwnPeerDevices } from '../selectors/user-selectors.js'; -import { generatePendingThreadColor } from '../shared/color-utils.js'; import { processFarcasterOpsActionType } from '../shared/farcaster/farcaster-actions.js'; -import type { FarcasterConversation } from '../shared/farcaster/farcaster-conversation-types.js'; -import type { FarcasterMessage } from '../shared/farcaster/farcaster-messages-types.js'; -import { farcasterThreadIDFromConversationID } from '../shared/id-utils.js'; import { IdentityClientContext } from '../shared/identity-client-context.js'; import { PeerToPeerContext } from '../tunnelbroker/peer-to-peer-context.js'; -import type { ClientAvatar } from '../types/avatar-types.js'; import { databaseIdentifier } from '../types/database-identifier-types.js'; -import type { Media } from '../types/media-types.js'; -import { messageTypes } from '../types/message-types-enum.js'; -import type { RawMessageInfo } from '../types/message-types.js'; -import { - minimallyEncodeRoleInfo, - minimallyEncodeThreadCurrentUserInfo, -} from '../types/minimally-encoded-thread-permissions-types.js'; -import type { - ThreadCurrentUserInfo, - FarcasterRawThreadInfo, - RoleInfo, -} from '../types/minimally-encoded-thread-permissions-types.js'; import { outboundP2PMessageStatuses } from '../types/sqlite-types.js'; import { syncedMetadataNames } from '../types/synced-metadata-types.js'; -import type { ThreadRolePermissionsBlob } from '../types/thread-permission-types.js'; -import { farcasterThreadTypes } from '../types/thread-types-enum.js'; -import type { FarcasterThreadType } from '../types/thread-types-enum.js'; import type { FarcasterConnectionUpdated } from '../types/tunnelbroker/user-actions-peer-to-peer-message-types.js'; import { updateTypes } from '../types/update-types-enum.js'; @@ -327,197 +301,6 @@ ); } -function createPermissionsInfo( - permissionsBlob: ThreadRolePermissionsBlob, - threadID: string, - threadType: FarcasterThreadType, -) { - return getAllThreadPermissions( - makePermissionsBlob(permissionsBlob, null, threadID, threadType), - threadID, - ); -} - -function createFarcasterRawThreadInfo( - conversation: FarcasterConversation, -): FarcasterRawThreadInfo { - const threadID = farcasterThreadIDFromConversationID( - conversation.conversationId, - ); - const threadType = conversation.isGroup - ? farcasterThreadTypes.FARCASTER_GROUP - : farcasterThreadTypes.FARCASTER_PERSONAL; - const permissionBlobs = - getFarcasterRolePermissionsBlobsFromConversation(conversation); - - const membersRole: RoleInfo = { - ...minimallyEncodeRoleInfo({ - id: `${threadID}/member/role`, - name: 'Members', - permissions: permissionBlobs.Members, - isDefault: true, - }), - specialRole: specialRoles.DEFAULT_ROLE, - }; - const adminsRole: ?RoleInfo = permissionBlobs.Admins - ? { - ...minimallyEncodeRoleInfo({ - id: `${threadID}/admin/role`, - name: 'Admins', - permissions: permissionBlobs.Admins, - isDefault: false, - }), - specialRole: specialRoles.ADMIN_ROLE, - } - : null; - const roles: { [id: string]: RoleInfo } = { - [membersRole.id]: membersRole, - }; - if (adminsRole) { - roles[adminsRole.id] = adminsRole; - } - - const removedUsers = new Set(conversation.removedFids); - const userIDs = conversation.participants - .filter(p => !removedUsers.has(p.fid)) - .map(p => `${p.fid}`); - const adminIDs = new Set(conversation.adminFids.map(fid => `${fid}`)); - - const members = userIDs.map(fid => ({ - id: fid, - // This flag was introduced for sidebars to show who replied to a thread. - // Now it doesn't seem to be used anywhere. Regardless, for Farcaster - // threads its value doesn't matter. - isSender: true, - minimallyEncoded: true, - role: adminIDs.has(fid) && adminsRole ? adminsRole.id : membersRole.id, - })); - - const currentUserRole = - conversation.viewerContext.access === 'admin' && adminsRole - ? adminsRole - : membersRole; - const currentUser: ThreadCurrentUserInfo = - minimallyEncodeThreadCurrentUserInfo({ - role: currentUserRole.id, - permissions: createPermissionsInfo( - conversation.viewerContext.access === 'admin' && permissionBlobs.Admins - ? permissionBlobs.Admins - : permissionBlobs.Members, - threadID, - threadType, - ), - subscription: { - home: true, - pushNotifs: !conversation.viewerContext.muted, - }, - unread: conversation.viewerContext.unreadCount > 0, - }); - - let avatar: ?ClientAvatar; - if (conversation.isGroup) { - avatar = conversation.photoUrl - ? { type: 'image', uri: conversation.photoUrl } - : null; - } else { - const uri = conversation.viewerContext.counterParty?.pfp?.url; - avatar = uri ? { type: 'image', uri } : null; - } - - return { - farcaster: true, - id: threadID, - type: threadType, - name: conversation.name, - avatar, - description: conversation.description, - color: generatePendingThreadColor(userIDs), - parentThreadID: null, - community: null, - creationTime: conversation.createdAt, - repliesCount: 0, - pinnedCount: conversation.pinnedMessages.length, - minimallyEncoded: true, - members, - roles, - currentUser, - }; -} - -function convertFarcasterMessageToCommMessages( - farcasterMessage: FarcasterMessage, -): $ReadOnlyArray { - const creatorID = `${farcasterMessage.senderFid}`; - const threadID = farcasterThreadIDFromConversationID( - farcasterMessage.conversationId, - ); - if ( - farcasterMessage.type === 'group_membership_addition' && - farcasterMessage.actionTargetUserContext?.fid - ) { - const addedUser = `${farcasterMessage.actionTargetUserContext.fid}`; - return [ - { - id: farcasterMessage.messageId, - type: messageTypes.ADD_MEMBERS, - threadID, - creatorID, - time: parseInt(farcasterMessage.serverTimestamp, 10), - addedUserIDs: [addedUser], - }, - ]; - } - - if ( - farcasterMessage.type === 'text' && - !!farcasterMessage?.metadata?.medias - ) { - const media = farcasterMessage.metadata.medias.map( - med => - ({ - id: uuid.v4(), - uri: med.staticRaster, - type: 'photo', - thumbHash: null, - dimensions: { - height: med.height, - width: med.width, - }, - }: Media), - ); - return [ - { - id: farcasterMessage.messageId, - type: messageTypes.MULTIMEDIA, - threadID, - creatorID, - time: parseInt(farcasterMessage.serverTimestamp, 10), - media, - }, - ]; - } - - if (farcasterMessage.type === 'text') { - return [ - { - id: farcasterMessage.messageId, - type: messageTypes.TEXT, - threadID, - creatorID, - time: parseInt(farcasterMessage.serverTimestamp, 10), - text: farcasterMessage.message, - }, - ]; - } - - console.log( - 'UNSUPPORTED FARCASTER MESSAGE', - JSON.stringify(farcasterMessage, null, 2), - ); - - return []; -} - export { DISABLE_CONNECT_FARCASTER_ALERT, NO_FID_METADATA, @@ -532,7 +315,5 @@ useUnlinkFID, useLinkFarcasterDCs, createFarcasterDCsAuthMessage, - createFarcasterRawThreadInfo, - convertFarcasterMessageToCommMessages, useClearFarcasterThreads, };