Page MenuHomePhorge

D15407.1765035783.diff
No OneTemporary

Size
16 KB
Referenced Files
None
Subscribers
None

D15407.1765035783.diff

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
@@ -1,5 +1,6 @@
// @flow
+import invariant from 'invariant';
import * as React from 'react';
import uuid from 'uuid';
@@ -13,7 +14,10 @@
useFetchFarcasterInbox,
useFetchFarcasterMessages,
} from './farcaster-api.js';
-import type { FarcasterConversation } from './farcaster-conversation-types.js';
+import type {
+ FarcasterConversation,
+ FarcasterInboxConversation,
+} from './farcaster-conversation-types.js';
import { useFarcasterMessageFetching } from './farcaster-message-fetching-context.js';
import {
type FarcasterMessage,
@@ -40,7 +44,10 @@
import type { ClientUpdateInfo } from '../../types/update-types.js';
import { extractFarcasterIDsFromPayload } from '../../utils/conversion-utils.js';
import { convertFarcasterMessageToCommMessages } from '../../utils/convert-farcaster-message-to-comm-messages.js';
-import { createFarcasterRawThreadInfo } from '../../utils/create-farcaster-raw-thread-info.js';
+import {
+ createFarcasterRawThreadInfo,
+ createUpdatedThread,
+} from '../../utils/create-farcaster-raw-thread-info.js';
import { getMessageForException } from '../../utils/errors.js';
import {
useCurrentUserSupportsDCs,
@@ -531,23 +538,17 @@
);
}
-function useFarcasterConversationsSync(): (
- limit: number,
- onProgress?: (completed: number, total: number) => void,
-) => Promise<void> {
+function useFetchInboxes(): (
+ category?: 'archived' | 'request',
+) => Promise<$ReadOnlyArray<FarcasterInboxConversation>> {
const fetchFarcasterInbox = useFetchFarcasterInbox();
- const dispatch = useDispatch();
- const fetchConversationWithMessages = useFetchConversationWithMessages();
- const setFarcasterDCsLoaded = useSetFarcasterDCsLoaded();
const { addLog } = useDebugLogs();
- const threadInfos = useSelector(state => state.threadStore.threadInfos);
-
- const fetchInboxes = React.useCallback(
+ return React.useCallback(
async (
category?: 'archived' | 'request',
- ): Promise<$ReadOnlyArray<string>> => {
- const allConversations: Array<string> = [];
+ ): Promise<$ReadOnlyArray<FarcasterInboxConversation>> => {
+ const allConversations: Array<FarcasterInboxConversation> = [];
let currentCursor = null;
while (true) {
@@ -565,10 +566,7 @@
RETRY_DELAY_MS,
);
- const ids = result.conversations.map(
- conversation => conversation.conversationId,
- );
- allConversations.push(...ids);
+ allConversations.push(...result.conversations);
if (next?.cursor) {
currentCursor = next.cursor;
@@ -593,8 +591,28 @@
},
[addLog, fetchFarcasterInbox],
);
+}
- const removeDeadThreads = React.useCallback(
+function useFetchInboxIDs(): (
+ category?: 'archived' | 'request',
+) => Promise<$ReadOnlyArray<string>> {
+ const fetchInboxes = useFetchInboxes();
+ return React.useCallback(
+ async (category?: 'archived' | 'request') => {
+ const conversations = await fetchInboxes(category);
+ return conversations.map(conversation => conversation.conversationId);
+ },
+ [fetchInboxes],
+ );
+}
+
+function useRemoveDeadThreads(): (
+ conversations: $ReadOnlyArray<string>,
+) => void {
+ const dispatch = useDispatch();
+ const threadInfos = useSelector(state => state.threadStore.threadInfos);
+
+ return React.useCallback(
(conversations: $ReadOnlyArray<string>) => {
const conversationsSet = new Set(conversations);
const time = Date.now();
@@ -624,6 +642,19 @@
},
[dispatch, threadInfos],
);
+}
+
+function useFarcasterConversationsSync(): (
+ limit: number,
+ onProgress?: (completed: number, total: number) => void,
+) => Promise<void> {
+ const dispatch = useDispatch();
+ const fetchConversationWithMessages = useFetchConversationWithMessages();
+ const setFarcasterDCsLoaded = useSetFarcasterDCsLoaded();
+ const { addLog } = useDebugLogs();
+ const threadInfos = useSelector(state => state.threadStore.threadInfos);
+ const fetchInboxes = useFetchInboxIDs();
+ const removeDeadThreads = useRemoveDeadThreads();
return React.useCallback(
async (
@@ -670,7 +701,7 @@
setFarcasterDCsLoaded(true);
} catch (e) {
addLog(
- 'Farcaster: Failed to sync conversations',
+ 'Farcaster: Failed to sync conversations (full sync)',
JSON.stringify({
error: getMessageForException(e),
}),
@@ -691,6 +722,113 @@
);
}
+function useLightweightFarcasterConversationsSync(): (
+ onProgress?: (completed: number, total: number) => void,
+) => Promise<void> {
+ const dispatch = useDispatch();
+ const fetchConversationWithMessages = useFetchConversationWithMessages();
+ const { addLog } = useDebugLogs();
+ const threadInfos = useSelector(state => state.threadStore.threadInfos);
+ const fetchInboxes = useFetchInboxes();
+ const removeDeadThreads = useRemoveDeadThreads();
+ const viewerID = useSelector(
+ state => state.currentUserInfo && state.currentUserInfo.id,
+ );
+
+ return React.useCallback(
+ async (onProgress?: (completed: number, total: number) => void) => {
+ try {
+ invariant(viewerID, 'Viewer ID should be set');
+ const inboxResults = await Promise.all([fetchInboxes()]);
+ const conversations = inboxResults.flat();
+ const conversationIDs = conversations.map(
+ conversation => conversation.conversationId,
+ );
+
+ removeDeadThreads(conversationIDs);
+
+ const threadIDs = new Set(Object.keys(threadInfos));
+ const newConversationIDs = conversationIDs.filter(
+ conversationID =>
+ !threadIDs.has(farcasterThreadIDFromConversationID(conversationID)),
+ );
+ const existingConversationIDs = conversationIDs.filter(conversationID =>
+ threadIDs.has(farcasterThreadIDFromConversationID(conversationID)),
+ );
+
+ onProgress?.(0, conversations.length);
+
+ const updates = conversations
+ .map(conversation => {
+ const threadID = farcasterThreadIDFromConversationID(
+ conversation.conversationId,
+ );
+ const thread = threadInfos[threadID];
+ if (thread && thread.farcaster) {
+ return createUpdatedThread(thread, conversation, viewerID);
+ }
+ return null;
+ })
+ .map(result =>
+ result && result.result === 'updated' ? result.threadInfo : null,
+ )
+ .filter(Boolean)
+ .map(thread => ({
+ type: updateTypes.UPDATE_THREAD,
+ time: thread.creationTime,
+ threadInfo: thread,
+ id: uuid.v4(),
+ }));
+ dispatch({
+ type: processFarcasterOpsActionType,
+ payload: {
+ rawMessageInfos: [],
+ updateInfos: updates,
+ },
+ });
+ onProgress?.(existingConversationIDs.length, conversations.length);
+
+ if (newConversationIDs.length > 0) {
+ await processInBatchesWithReduxBatching(
+ newConversationIDs,
+ FARCASTER_DATA_BATCH_SIZE,
+ (conversationID, batchedUpdates) =>
+ fetchConversationWithMessages(
+ conversationID,
+ Number.POSITIVE_INFINITY,
+ batchedUpdates,
+ ),
+ dispatch,
+ completed =>
+ onProgress?.(
+ existingConversationIDs.length + completed,
+ conversations.length,
+ ),
+ );
+ }
+ } catch (e) {
+ addLog(
+ 'Farcaster: Failed to sync conversations (lightweight)',
+ JSON.stringify({
+ error: getMessageForException(e),
+ }),
+ new Set([logTypes.FARCASTER]),
+ );
+ throw e;
+ }
+ },
+ [
+ addLog,
+ dispatch,
+ fetchConversationWithMessages,
+ fetchInboxes,
+ removeDeadThreads,
+ threadInfos,
+ viewerID,
+ ],
+ );
+}
+
function useAddNewFarcasterMessage(): FarcasterMessage => Promise<void> {
const dispatch = useDispatch();
const fetchUsersByFIDs = useGetCommFCUsersForFIDs();
@@ -900,6 +1038,7 @@
export {
useFarcasterConversationsSync,
+ useLightweightFarcasterConversationsSync,
useFetchConversationWithBatching,
useFetchConversationWithMessages,
useFetchConversation,
diff --git a/lib/utils/create-farcaster-raw-thread-info.js b/lib/utils/create-farcaster-raw-thread-info.js
--- a/lib/utils/create-farcaster-raw-thread-info.js
+++ b/lib/utils/create-farcaster-raw-thread-info.js
@@ -1,5 +1,6 @@
// @flow
+import { values } from './objects.js';
import {
getFarcasterRolePermissionsBlobs,
getFarcasterRolePermissionsBlobsFromConversation,
@@ -10,7 +11,10 @@
makePermissionsBlob,
} from '../permissions/thread-permissions.js';
import { generatePendingThreadColor } from '../shared/color-utils.js';
-import type { FarcasterConversation } from '../shared/farcaster/farcaster-conversation-types.js';
+import type {
+ FarcasterConversation,
+ FarcasterInboxConversation,
+} from '../shared/farcaster/farcaster-conversation-types.js';
import { farcasterThreadIDFromConversationID } from '../shared/id-utils.js';
import { stringForUserExplicit } from '../shared/user-utils.js';
import type { ClientAvatar } from '../types/avatar-types.js';
@@ -25,7 +29,10 @@
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 {
+ threadTypes,
+ farcasterThreadTypes,
+} from '../types/thread-types-enum.js';
import type { FarcasterThreadType } from '../types/thread-types-enum.js';
function createPermissionsInfo(
@@ -58,15 +65,29 @@
+avatar: ?ClientAvatar,
};
-function innerCreateFarcasterRawThreadInfo(
- threadData: FarcasterThreadData,
-): FarcasterRawThreadInfo {
- const threadID = threadData.threadID;
- const threadType = threadData.isGroup
- ? farcasterThreadTypes.FARCASTER_GROUP
- : farcasterThreadTypes.FARCASTER_PERSONAL;
- const permissionBlobs = threadData.permissionBlobs;
-
+function createMembersAndCurrentUser(options: {
+ +threadID: string,
+ +permissionBlobs: {
+ +Members: ThreadRolePermissionsBlob,
+ +Admins: ?ThreadRolePermissionsBlob,
+ },
+ +memberIDs: $ReadOnlyArray<string>,
+ +adminIDs: $ReadOnlySet<string>,
+ +currentUserOptions: {
+ +isAdmin: boolean,
+ +unread: boolean,
+ +muted: boolean,
+ },
+ +threadType: FarcasterThreadType,
+}) {
+ const {
+ threadID,
+ permissionBlobs,
+ memberIDs,
+ adminIDs,
+ currentUserOptions,
+ threadType,
+ } = options;
const membersRole: RoleInfo = {
...minimallyEncodeRoleInfo({
id: `${threadID}/member/role`,
@@ -94,39 +115,58 @@
roles[adminsRole.id] = adminsRole;
}
- const members = threadData.memberIDs.map(fid => ({
+ const members = memberIDs.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:
- threadData.adminIDs.has(fid) && adminsRole
- ? adminsRole.id
- : membersRole.id,
+ role: adminIDs.has(fid) && adminsRole ? adminsRole.id : membersRole.id,
}));
const currentUserRole =
- threadData.viewerAccess === 'admin' && adminsRole
- ? adminsRole
- : membersRole;
+ currentUserOptions.isAdmin && adminsRole ? adminsRole : membersRole;
const currentUser: ThreadCurrentUserInfo =
minimallyEncodeThreadCurrentUserInfo({
role: currentUserRole.id,
permissions: createPermissionsInfo(
- threadData.viewerAccess === 'admin' && permissionBlobs.Admins
+ currentUserOptions.isAdmin && permissionBlobs.Admins
? permissionBlobs.Admins
: permissionBlobs.Members,
threadID,
threadType,
),
subscription: {
- home: !threadData.muted,
- pushNotifs: !threadData.muted,
+ home: !currentUserOptions.muted,
+ pushNotifs: !currentUserOptions.muted,
},
- unread: threadData.unread,
+ unread: currentUserOptions.unread,
});
+ return { members, currentUser, roles };
+}
+
+function innerCreateFarcasterRawThreadInfo(
+ threadData: FarcasterThreadData,
+): FarcasterRawThreadInfo {
+ const threadID = threadData.threadID;
+ const threadType = threadData.isGroup
+ ? farcasterThreadTypes.FARCASTER_GROUP
+ : farcasterThreadTypes.FARCASTER_PERSONAL;
+ const permissionBlobs = threadData.permissionBlobs;
+
+ const { members, roles, currentUser } = createMembersAndCurrentUser({
+ threadID,
+ permissionBlobs,
+ memberIDs: threadData.memberIDs,
+ adminIDs: threadData.adminIDs,
+ currentUserOptions: {
+ isAdmin: threadData.viewerAccess === 'admin',
+ unread: threadData.unread,
+ muted: threadData.muted,
+ },
+ threadType,
+ });
return {
farcaster: true,
@@ -235,4 +275,121 @@
return innerCreateFarcasterRawThreadInfo(threadData);
}
-export { createFarcasterRawThreadInfo, createFarcasterRawThreadInfoPersonal };
+function createUpdatedThread(
+ threadInfo: FarcasterRawThreadInfo,
+ conversation: FarcasterInboxConversation,
+ viewerID: string,
+):
+ | { +result: 'unchanged' }
+ | { +result: 'updated', +threadInfo: FarcasterRawThreadInfo } {
+ let updatedThreadInfo = threadInfo;
+
+ if (conversation.name !== threadInfo.name) {
+ updatedThreadInfo = { ...updatedThreadInfo, name: conversation.name ?? '' };
+ }
+
+ if (conversation.description !== threadInfo.description) {
+ updatedThreadInfo = {
+ ...updatedThreadInfo,
+ description: conversation.description ?? '',
+ };
+ }
+
+ let avatarURI = null;
+ if (conversation.isGroup) {
+ avatarURI = conversation.photoUrl;
+ } else {
+ avatarURI = conversation.viewerContext.counterParty?.pfp?.url;
+ }
+ if (!avatarURI && threadInfo.avatar) {
+ updatedThreadInfo = { ...updatedThreadInfo, avatar: null };
+ } else if (avatarURI && avatarURI !== threadInfo.avatar?.uri) {
+ updatedThreadInfo = {
+ ...updatedThreadInfo,
+ avatar: { type: 'image', uri: avatarURI },
+ };
+ }
+
+ const conversationIsUnread =
+ conversation.viewerContext.unreadCount > 0 ||
+ conversation.viewerContext.manuallyMarkedUnread;
+
+ if (conversation.isGroup) {
+ const adminIDs = new Set(conversation.adminFids.map(fid => `${fid}`));
+ const adminsRole = values(threadInfo.roles).find(
+ role => role.specialRole === specialRoles.ADMIN_ROLE,
+ );
+ const threadAdminUserIDs = threadInfo.members
+ .filter(member => member.role === adminsRole?.id)
+ .map(member => member.id);
+ if (
+ (threadAdminUserIDs.some(id => !adminIDs.has(id)) ||
+ adminIDs.size !== threadAdminUserIDs.length) &&
+ adminsRole
+ ) {
+ const permissionBlobs = getFarcasterRolePermissionsBlobs(
+ threadTypes.FARCASTER_GROUP,
+ false,
+ false,
+ );
+ const { members, roles, currentUser } = createMembersAndCurrentUser({
+ threadID: threadInfo.id,
+ permissionBlobs,
+ memberIDs: threadInfo.members.map(member => member.id),
+ adminIDs,
+ currentUserOptions: {
+ isAdmin: adminIDs.has(viewerID),
+ unread: conversationIsUnread,
+ muted: conversation.viewerContext.muted,
+ },
+ threadType: threadTypes.FARCASTER_GROUP,
+ });
+ updatedThreadInfo = {
+ ...updatedThreadInfo,
+ members,
+ roles,
+ currentUser,
+ };
+ }
+ }
+
+ if (updatedThreadInfo.currentUser.unread !== conversationIsUnread) {
+ updatedThreadInfo = {
+ ...updatedThreadInfo,
+ currentUser: {
+ ...updatedThreadInfo.currentUser,
+ unread: conversationIsUnread,
+ },
+ };
+ }
+
+ const threadIsMuted =
+ !threadInfo.currentUser.subscription.home ||
+ !threadInfo.currentUser.subscription.pushNotifs;
+ if (threadIsMuted !== conversation.viewerContext.muted) {
+ updatedThreadInfo = {
+ ...updatedThreadInfo,
+ currentUser: {
+ ...updatedThreadInfo.currentUser,
+ subscription: {
+ home: !conversation.viewerContext.muted,
+ pushNotifs: !conversation.viewerContext.muted,
+ },
+ },
+ };
+ }
+
+ if (threadInfo === updatedThreadInfo) {
+ return { result: 'unchanged' };
+ }
+ return {
+ result: 'updated',
+ threadInfo: updatedThreadInfo,
+ };
+}
+
+export {
+ createFarcasterRawThreadInfo,
+ createFarcasterRawThreadInfoPersonal,
+ createUpdatedThread,
+};

File Metadata

Mime Type
text/plain
Expires
Sat, Dec 6, 3:43 PM (22 h, 21 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5839300
Default Alt Text
D15407.1765035783.diff (16 KB)

Event Timeline