diff --git a/lib/actions/link-actions.js b/lib/actions/link-actions.js --- a/lib/actions/link-actions.js +++ b/lib/actions/link-actions.js @@ -1,4 +1,5 @@ // @flow + import * as React from 'react'; import type { CallSingleKeyserverEndpoint } from '../keyserver-conn/call-single-keyserver-endpoint.js'; diff --git a/lib/actions/upload-actions.js b/lib/actions/upload-actions.js --- a/lib/actions/upload-actions.js +++ b/lib/actions/upload-actions.js @@ -6,7 +6,7 @@ import blobService from '../facts/blob-service.js'; import type { CallSingleKeyserverEndpoint } from '../keyserver-conn/call-single-keyserver-endpoint.js'; -import { extractKeyserverIDFromID } from '../keyserver-conn/keyserver-call-utils.js'; +import { extractKeyserverIDFromIDOptional } from '../keyserver-conn/keyserver-call-utils.js'; import { useKeyserverCall } from '../keyserver-conn/keyserver-call.js'; import type { CallKeyserverEndpoint } from '../keyserver-conn/keyserver-conn-types.js'; import { type PerformHTTPMultipartUpload } from '../keyserver-conn/multipart-upload.js'; @@ -115,7 +115,9 @@ ): ((input: DeleteUploadInput) => Promise) => async input => { const { id, keyserverOrThreadID } = input; - const keyserverID = extractKeyserverIDFromID(keyserverOrThreadID); + const keyserverID: string = + extractKeyserverIDFromIDOptional(keyserverOrThreadID) ?? + keyserverOrThreadID; const requests = { [keyserverID]: { id } }; await callKeyserverEndpoint('delete_upload', requests); }; @@ -224,7 +226,9 @@ } // 3. Upload metadata to keyserver - const keyserverID = extractKeyserverIDFromID(keyserverOrThreadID); + const keyserverID: string = + extractKeyserverIDFromIDOptional(keyserverOrThreadID) ?? + keyserverOrThreadID; const requests = { [keyserverID]: { blobHash, diff --git a/lib/keyserver-conn/keyserver-call-utils.js b/lib/keyserver-conn/keyserver-call-utils.js --- a/lib/keyserver-conn/keyserver-call-utils.js +++ b/lib/keyserver-conn/keyserver-call-utils.js @@ -5,17 +5,29 @@ import type { CalendarQuery } from '../types/entry-types.js'; import type { NotDeletedFilter } from '../types/filter-types.js'; -function extractKeyserverIDFromID(id: string): string { +function extractKeyserverIDFromIDOptional(id: string): ?string { + if (!id.includes('|')) { + return null; + } return id.split('|')[0]; } +function extractKeyserverIDFromID(id: string): string { + const keyserverID = extractKeyserverIDFromIDOptional(id); + invariant(keyserverID, 'Keyserver ID should be present'); + return keyserverID; +} + function sortThreadIDsPerKeyserver(threadIDs: $ReadOnlyArray): { +[keyserverID: string]: $ReadOnlyArray, } { const results: { [string]: string[] } = {}; for (const threadID of threadIDs) { - const keyserverID = extractKeyserverIDFromID(threadID); - invariant(keyserverID, 'keyserver data missing from thread id'); + const optionalKeyserverID = extractKeyserverIDFromIDOptional(threadID); + if (!optionalKeyserverID) { + continue; + } + const keyserverID: string = optionalKeyserverID; if (results[keyserverID] === undefined) { results[keyserverID] = []; } @@ -61,7 +73,7 @@ } else if (filter.type === 'threads') { for (const threadID of filter.threadIDs) { const keyserverID = extractKeyserverIDFromID(threadID); - if (results[keyserverID] === undefined) { + if (!keyserverID || results[keyserverID] === undefined) { continue; } let threadFilter = results[keyserverID].filters.find( @@ -94,12 +106,14 @@ return []; } const keyserverIDsSet = new Set(keyserverIDs); - return threadIDs.filter(threadID => - keyserverIDsSet.has(extractKeyserverIDFromID(threadID)), - ); + return threadIDs.filter(threadID => { + const keyserverID = extractKeyserverIDFromIDOptional(threadID); + return keyserverID && keyserverIDsSet.has(keyserverID); + }); } export { + extractKeyserverIDFromIDOptional, extractKeyserverIDFromID, sortThreadIDsPerKeyserver, sortCalendarQueryPerKeyserver, diff --git a/lib/keyserver-conn/keyserver-call-utils.test.js b/lib/keyserver-conn/keyserver-call-utils.test.js --- a/lib/keyserver-conn/keyserver-call-utils.test.js +++ b/lib/keyserver-conn/keyserver-call-utils.test.js @@ -4,6 +4,7 @@ extractKeyserverIDFromID, sortCalendarQueryPerKeyserver, getThreadIDsForKeyservers, + extractKeyserverIDFromIDOptional, } from './keyserver-call-utils.js'; import type { CalendarQuery } from '../types/entry-types'; @@ -14,9 +15,22 @@ expect(extractKeyserverIDFromID(id)).toBe(keyserverID); }); - it('should return for ', () => { + it('should throw for non-keyserver ID', () => { + const id = '404'; + expect(() => extractKeyserverIDFromID(id)).toThrow(); + }); +}); + +describe('extractKeyserverIDFromIDOptional', () => { + it('should return for |', () => { const keyserverID = '404'; - expect(extractKeyserverIDFromID(keyserverID)).toBe(keyserverID); + const id = keyserverID + '|1234'; + expect(extractKeyserverIDFromIDOptional(id)).toBe(keyserverID); + }); + + it('should return null for non-keyserver ID', () => { + const id = '404'; + expect(extractKeyserverIDFromIDOptional(id)).toBe(null); }); }); diff --git a/lib/reducers/calendar-filters-reducer.js b/lib/reducers/calendar-filters-reducer.js --- a/lib/reducers/calendar-filters-reducer.js +++ b/lib/reducers/calendar-filters-reducer.js @@ -18,7 +18,7 @@ legacyLogInActionTypes, legacyKeyserverRegisterActionTypes, } from '../actions/user-actions.js'; -import { extractKeyserverIDFromID } from '../keyserver-conn/keyserver-call-utils.js'; +import { extractKeyserverIDFromIDOptional } from '../keyserver-conn/keyserver-call-utils.js'; import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js'; import { filteredThreadIDs, @@ -220,8 +220,10 @@ keyserverIDs: $ReadOnlyArray, ): $ReadOnlyArray { const keyserverIDsSet = new Set(keyserverIDs); - const filterCondition = (threadID: string) => - !keyserverIDsSet.has(extractKeyserverIDFromID(threadID)); + const filterCondition = (threadID: string) => { + const keyserverID = extractKeyserverIDFromIDOptional(threadID); + return !keyserverID || !keyserverIDsSet.has(keyserverID); + }; return filterThreadIDsInFilterList(state, filterCondition); } diff --git a/lib/reducers/draft-reducer.js b/lib/reducers/draft-reducer.js --- a/lib/reducers/draft-reducer.js +++ b/lib/reducers/draft-reducer.js @@ -6,7 +6,7 @@ updateDraftActionType, } from '../actions/draft-actions.js'; import { deleteKeyserverAccountActionTypes } from '../actions/user-actions.js'; -import { extractKeyserverIDFromID } from '../keyserver-conn/keyserver-call-utils.js'; +import { extractKeyserverIDFromIDOptional } from '../keyserver-conn/keyserver-call-utils.js'; import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js'; import type { DraftStore, DraftStoreOperation } from '../types/draft-types.js'; import type { BaseAction } from '../types/redux-types.js'; @@ -25,7 +25,8 @@ const drafts: { [key: string]: string } = {}; const ids: string[] = []; for (const key in draftStore.drafts) { - if (keyserverIDsSet.has(extractKeyserverIDFromID(key))) { + const keyserverID = extractKeyserverIDFromIDOptional(key); + if (keyserverID && keyserverIDsSet.has(keyserverID)) { ids.push(key); } else { drafts[key] = draftStore.drafts[key]; diff --git a/lib/reducers/invite-links-reducer.js b/lib/reducers/invite-links-reducer.js --- a/lib/reducers/invite-links-reducer.js +++ b/lib/reducers/invite-links-reducer.js @@ -6,7 +6,7 @@ fetchPrimaryInviteLinkActionTypes, } from '../actions/link-actions.js'; import { deleteKeyserverAccountActionTypes } from '../actions/user-actions.js'; -import { extractKeyserverIDFromID } from '../keyserver-conn/keyserver-call-utils.js'; +import { extractKeyserverIDFromIDOptional } from '../keyserver-conn/keyserver-call-utils.js'; import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js'; import type { InviteLinksStore, CommunityLinks } from '../types/link-types.js'; import type { BaseAction } from '../types/redux-types.js'; @@ -60,7 +60,8 @@ const keyserverIDsSet = new Set(action.payload.keyserverIDs); const newLinks: { [communityID: string]: CommunityLinks } = {}; for (const linkID in state.links) { - if (!keyserverIDsSet.has(extractKeyserverIDFromID(linkID))) { + const keyserverID = extractKeyserverIDFromIDOptional(linkID); + if (!keyserverID || !keyserverIDsSet.has(keyserverID)) { newLinks[linkID] = state.links[linkID]; } } @@ -75,7 +76,7 @@ const { keyserverID } = action.payload; const newLinks: { [communityID: string]: CommunityLinks } = {}; for (const linkID in state.links) { - if (extractKeyserverIDFromID(linkID) !== keyserverID) { + if (extractKeyserverIDFromIDOptional(linkID) !== keyserverID) { newLinks[linkID] = state.links[linkID]; } } diff --git a/lib/reducers/thread-activity-reducer.js b/lib/reducers/thread-activity-reducer.js --- a/lib/reducers/thread-activity-reducer.js +++ b/lib/reducers/thread-activity-reducer.js @@ -17,7 +17,7 @@ } from '../actions/thread-actions.js'; import { fetchPendingUpdatesActionTypes } from '../actions/update-actions.js'; import { deleteKeyserverAccountActionTypes } from '../actions/user-actions.js'; -import { extractKeyserverIDFromID } from '../keyserver-conn/keyserver-call-utils.js'; +import { extractKeyserverIDFromIDOptional } from '../keyserver-conn/keyserver-call-utils.js'; import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js'; import { threadActivityStoreOpsHandlers, @@ -183,7 +183,8 @@ const keyserverIDsSet = new Set(action.payload.keyserverIDs); for (const threadID in state) { - if (!keyserverIDsSet.has(extractKeyserverIDFromID(threadID))) { + const keyserverID = extractKeyserverIDFromIDOptional(threadID); + if (!keyserverID || !keyserverIDsSet.has(keyserverID)) { continue; } threadIDsToRemove.push(threadID); @@ -208,7 +209,7 @@ const { keyserverID } = action.payload; for (const threadID in state) { - if (extractKeyserverIDFromID(threadID) !== keyserverID) { + if (extractKeyserverIDFromIDOptional(threadID) !== keyserverID) { continue; } threadIDsToRemove.push(threadID); diff --git a/lib/shared/message-utils.js b/lib/shared/message-utils.js --- a/lib/shared/message-utils.js +++ b/lib/shared/message-utils.js @@ -11,7 +11,7 @@ import { messageSpecs } from './messages/message-specs.js'; import { threadIsGroupChat } from './thread-utils.js'; import { useStringForUser } from '../hooks/ens-cache.js'; -import { extractKeyserverIDFromID } from '../keyserver-conn/keyserver-call-utils.js'; +import { extractKeyserverIDFromIDOptional } from '../keyserver-conn/keyserver-call-utils.js'; import { contentStringForMediaArray } from '../media/media-utils.js'; import type { ChatMessageInfoItem } from '../selectors/chat-selectors.js'; import { threadInfoSelector } from '../selectors/thread-selectors.js'; @@ -661,11 +661,12 @@ const timePerKeyserver: { [keyserverID: string]: number } = {}; for (const messageInfo of messageInfos) { - const keyserverID = extractKeyserverIDFromID(messageInfo.threadID); + const keyserverID = extractKeyserverIDFromIDOptional(messageInfo.threadID); if ( - !timePerKeyserver[keyserverID] || - timePerKeyserver[keyserverID] < messageInfo.time + keyserverID && + (!timePerKeyserver[keyserverID] || + timePerKeyserver[keyserverID] < messageInfo.time) ) { timePerKeyserver[keyserverID] = messageInfo.time; } diff --git a/native/calendar/entry.react.js b/native/calendar/entry.react.js --- a/native/calendar/entry.react.js +++ b/native/calendar/entry.react.js @@ -26,7 +26,7 @@ useDeleteEntry, useSaveEntry, } from 'lib/actions/entry-actions.js'; -import { extractKeyserverIDFromID } from 'lib/keyserver-conn/keyserver-call-utils.js'; +import { extractKeyserverIDFromIDOptional } from 'lib/keyserver-conn/keyserver-call-utils.js'; import { registerFetchKey } from 'lib/reducers/loading-reducer.js'; import { connectionSelector } from 'lib/selectors/keyserver-selectors.js'; import { colorIsDark } from 'lib/shared/color-utils.js'; @@ -809,11 +809,18 @@ const { threadInfo: unresolvedThreadInfo, ...restProps } = props; const threadInfo = useResolvedThreadInfo(unresolvedThreadInfo); - const keyserverID = extractKeyserverIDFromID(threadInfo.id); - const connection = useSelector(connectionSelector(keyserverID)); + const keyserverID = extractKeyserverIDFromIDOptional(threadInfo.id); + const connection = useSelector(state => { + if (!keyserverID) { + return { + status: 'connected', + }; + } + return connectionSelector(keyserverID)(state); + }); invariant( connection, - `keyserver ${keyserverID} missing from keyserverStore`, + `keyserver ${keyserverID ?? 'null'} missing from keyserverStore`, ); const online = connection.status === 'connected'; diff --git a/native/chat/settings/thread-settings-push-notifs.react.js b/native/chat/settings/thread-settings-push-notifs.react.js --- a/native/chat/settings/thread-settings-push-notifs.react.js +++ b/native/chat/settings/thread-settings-push-notifs.react.js @@ -8,7 +8,7 @@ updateSubscriptionActionTypes, useUpdateSubscription, } from 'lib/actions/user-actions.js'; -import { extractKeyserverIDFromID } from 'lib/keyserver-conn/keyserver-call-utils.js'; +import { extractKeyserverIDFromIDOptional } from 'lib/keyserver-conn/keyserver-call-utils.js'; import { deviceTokenSelector } from 'lib/selectors/keyserver-selectors.js'; import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import type { @@ -175,8 +175,13 @@ React.memo(function ConnectedThreadSettingsPushNotifs( props: BaseProps, ) { - const keyserverID = extractKeyserverIDFromID(props.threadInfo.id); - const deviceToken = useSelector(deviceTokenSelector(keyserverID)); + const keyserverID = extractKeyserverIDFromIDOptional(props.threadInfo.id); + const deviceToken = useSelector(state => { + if (!keyserverID) { + return state.tunnelbrokerDeviceToken; + } + return deviceTokenSelector(keyserverID)(state); + }); const hasPushPermissions = deviceToken !== null && deviceToken !== undefined; const styles = useStyles(unboundStyles); diff --git a/native/components/auto-join-community-handler.react.js b/native/components/auto-join-community-handler.react.js --- a/native/components/auto-join-community-handler.react.js +++ b/native/components/auto-join-community-handler.react.js @@ -9,7 +9,7 @@ } from 'lib/actions/thread-actions.js'; import { NeynarClientContext } from 'lib/components/neynar-client-provider.react.js'; import blobService from 'lib/facts/blob-service.js'; -import { extractKeyserverIDFromID } from 'lib/keyserver-conn/keyserver-call-utils.js'; +import { extractKeyserverIDFromIDOptional } from 'lib/keyserver-conn/keyserver-call-utils.js'; import { cookieSelector } from 'lib/selectors/keyserver-selectors.js'; import { farcasterChannelTagBlobHash } from 'lib/shared/community-utils.js'; import type { AuthMetadata } from 'lib/shared/identity-client-context.js'; @@ -126,9 +126,9 @@ } const { commCommunityID } = await blobResult.json(); - const keyserverID = extractKeyserverIDFromID(commCommunityID); + const keyserverID = extractKeyserverIDFromIDOptional(commCommunityID); - if (!keyserverInfos[keyserverID]) { + if (!keyserverID || !keyserverInfos[keyserverID]) { return; } diff --git a/web/calendar/entry.react.js b/web/calendar/entry.react.js --- a/web/calendar/entry.react.js +++ b/web/calendar/entry.react.js @@ -474,12 +474,12 @@ ); const calendarQuery = useSelector(nonThreadCalendarQuery); const keyserverID = extractKeyserverIDFromID(threadID); - const connection = useSelector(connectionSelector(keyserverID)); - invariant( - connection, - `keyserver ${keyserverID} missing from keyserverStore`, - ); - const online = connection.status === 'connected'; + const online = useSelector(state => { + if (!keyserverID) { + return true; + } + return connectionSelector(keyserverID)(state) === 'connected'; + }); const callCreateEntry = useCreateEntry(); const callSaveEntry = useSaveEntry(); const callDeleteEntry = useDeleteEntry();