diff --git a/keyserver/src/endpoints.js b/keyserver/src/endpoints.js index e7113da6a..e03b6be55 100644 --- a/keyserver/src/endpoints.js +++ b/keyserver/src/endpoints.js @@ -1,525 +1,532 @@ // @flow import t from 'tcomb'; import type { TType } from 'tcomb'; import { baseLegalPolicies } from 'lib/facts/policies.js'; import type { PolicyType } from 'lib/facts/policies.js'; import type { Endpoint } from 'lib/types/endpoints.js'; import { calendarQueryValidator } from 'lib/types/entry-types.js'; +import { sessionStateValidator } from 'lib/types/session-types.js'; import { endpointValidators } from 'lib/types/validators/endpoint-validators.js'; import { updateUserAvatarRequestValidator } from 'lib/utils/avatar-utils.js'; import { updateActivityResponder, threadSetUnreadStatusResponder, setThreadUnreadStatusValidator, updateActivityResponderInputValidator, } from './responders/activity-responders.js'; import { fetchCommunityInfosResponder } from './responders/community-responders.js'; import { deviceTokenUpdateResponder, deviceTokenUpdateRequestInputValidator, } from './responders/device-responders.js'; import { entryFetchResponder, entryRevisionFetchResponder, entryCreationResponder, entryUpdateResponder, entryDeletionResponder, entryRestorationResponder, calendarQueryUpdateResponder, createEntryRequestInputValidator, deleteEntryRequestInputValidator, entryQueryInputValidator, entryRevisionHistoryFetchInputValidator, restoreEntryRequestInputValidator, saveEntryRequestInputValidator, } from './responders/entry-responders.js'; import { createOrUpdateFarcasterChannelTagResponder, deleteFarcasterChannelTagResponder, createOrUpdateFarcasterChannelTagInputValidator, deleteFarcasterChannelTagInputValidator, } from './responders/farcaster-channel-tag-responders.js'; import type { JSONResponder } from './responders/handlers.js'; import { createJSONResponder } from './responders/handlers.js'; import { getOlmSessionInitializationDataResponder } from './responders/keys-responders.js'; import { createOrUpdatePublicLinkResponder, disableInviteLinkResponder, fetchPrimaryInviteLinksResponder, inviteLinkVerificationResponder, createOrUpdatePublicLinkInputValidator, disableInviteLinkInputValidator, inviteLinkVerificationRequestInputValidator, } from './responders/link-responders.js'; import { messageReportCreationResponder, messageReportCreationRequestInputValidator, } from './responders/message-report-responder.js'; import { textMessageCreationResponder, messageFetchResponder, multimediaMessageCreationResponder, reactionMessageCreationResponder, editMessageCreationResponder, fetchPinnedMessagesResponder, searchMessagesResponder, sendMultimediaMessageRequestInputValidator, sendReactionMessageRequestInputValidator, editMessageRequestInputValidator, sendTextMessageRequestInputValidator, fetchMessageInfosRequestInputValidator, fetchPinnedMessagesResponderInputValidator, searchMessagesResponderInputValidator, } from './responders/message-responders.js'; import { getInitialReduxStateResponder, initialReduxStateRequestValidator, } from './responders/redux-state-responders.js'; import { updateRelationshipsResponder, updateRelationshipInputValidator, } from './responders/relationship-responders.js'; import { reportCreationResponder, reportMultiCreationResponder, errorReportFetchInfosResponder, reportCreationRequestInputValidator, fetchErrorReportInfosRequestInputValidator, reportMultiCreationRequestInputValidator, } from './responders/report-responders.js'; import { userSearchResponder, exactUserSearchResponder, exactUserSearchRequestInputValidator, userSearchRequestInputValidator, } from './responders/search-responders.js'; import { siweNonceResponder } from './responders/siwe-nonce-responders.js'; import { threadDeletionResponder, roleUpdateResponder, memberRemovalResponder, threadLeaveResponder, threadUpdateResponder, threadCreationResponder, threadFetchMediaResponder, threadJoinResponder, toggleMessagePinResponder, roleModificationResponder, roleDeletionResponder, newThreadRequestInputValidator, threadDeletionRequestInputValidator, joinThreadRequestInputValidator, leaveThreadRequestInputValidator, threadFetchMediaRequestInputValidator, removeMembersRequestInputValidator, roleChangeRequestInputValidator, toggleMessagePinRequestInputValidator, updateThreadRequestInputValidator, roleDeletionRequestInputValidator, roleModificationRequestInputValidator, } from './responders/thread-responders.js'; +import { fetchPendingUpdatesResponder } from './responders/update-responders.js'; import { keyserverAuthRequestInputValidator, keyserverAuthResponder, userSubscriptionUpdateResponder, passwordUpdateResponder, sendVerificationEmailResponder, sendPasswordResetEmailResponder, logOutResponder, accountDeletionResponder, accountCreationResponder, logInResponder, siweAuthResponder, oldPasswordUpdateResponder, updateUserSettingsResponder, policyAcknowledgmentResponder, updateUserAvatarResponder, registerRequestInputValidator, logInRequestInputValidator, policyAcknowledgmentRequestInputValidator, accountUpdateInputValidator, resetPasswordRequestInputValidator, siweAuthRequestInputValidator, subscriptionUpdateRequestInputValidator, updatePasswordRequestInputValidator, updateUserSettingsInputValidator, claimUsernameResponder, claimUsernameRequestInputValidator, } from './responders/user-responders.js'; import { codeVerificationResponder, codeVerificationRequestInputValidator, } from './responders/verification-responders.js'; import { versionResponder } from './responders/version-responders.js'; import type { Viewer } from './session/viewer.js'; import { uploadMediaMetadataResponder, uploadDeletionResponder, UploadDeletionRequestInputValidator, uploadMediaMetadataInputValidator, } from './uploads/uploads.js'; const ignoredArgumentValidator = t.irreducible( 'Ignored argument', () => true, ); type EndpointData = { responder: (viewer: Viewer, input: any) => Promise<*>, inputValidator: TType<*>, policies: $ReadOnlyArray, }; const jsonEndpointsData: { +[id: Endpoint]: EndpointData } = { create_account: { responder: accountCreationResponder, inputValidator: registerRequestInputValidator, policies: [], }, create_entry: { responder: entryCreationResponder, inputValidator: createEntryRequestInputValidator, policies: baseLegalPolicies, }, create_error_report: { responder: reportCreationResponder, inputValidator: reportCreationRequestInputValidator, policies: [], }, create_message_report: { responder: messageReportCreationResponder, inputValidator: messageReportCreationRequestInputValidator, policies: baseLegalPolicies, }, create_multimedia_message: { responder: multimediaMessageCreationResponder, inputValidator: sendMultimediaMessageRequestInputValidator, policies: baseLegalPolicies, }, create_or_update_public_link: { responder: createOrUpdatePublicLinkResponder, inputValidator: createOrUpdatePublicLinkInputValidator, policies: baseLegalPolicies, }, create_reaction_message: { responder: reactionMessageCreationResponder, inputValidator: sendReactionMessageRequestInputValidator, policies: baseLegalPolicies, }, disable_invite_link: { responder: disableInviteLinkResponder, inputValidator: disableInviteLinkInputValidator, policies: baseLegalPolicies, }, edit_message: { responder: editMessageCreationResponder, inputValidator: editMessageRequestInputValidator, policies: baseLegalPolicies, }, create_report: { responder: reportCreationResponder, inputValidator: reportCreationRequestInputValidator, policies: [], }, create_reports: { responder: reportMultiCreationResponder, inputValidator: reportMultiCreationRequestInputValidator, policies: [], }, create_text_message: { responder: textMessageCreationResponder, inputValidator: sendTextMessageRequestInputValidator, policies: baseLegalPolicies, }, create_thread: { responder: threadCreationResponder, inputValidator: newThreadRequestInputValidator, policies: baseLegalPolicies, }, delete_account: { responder: accountDeletionResponder, inputValidator: ignoredArgumentValidator, policies: [], }, delete_entry: { responder: entryDeletionResponder, inputValidator: deleteEntryRequestInputValidator, policies: baseLegalPolicies, }, delete_community_role: { responder: roleDeletionResponder, inputValidator: roleDeletionRequestInputValidator, policies: baseLegalPolicies, }, delete_thread: { responder: threadDeletionResponder, inputValidator: threadDeletionRequestInputValidator, policies: baseLegalPolicies, }, delete_upload: { responder: uploadDeletionResponder, inputValidator: UploadDeletionRequestInputValidator, policies: baseLegalPolicies, }, exact_search_user: { responder: exactUserSearchResponder, inputValidator: exactUserSearchRequestInputValidator, policies: [], }, fetch_entries: { responder: entryFetchResponder, inputValidator: entryQueryInputValidator, policies: baseLegalPolicies, }, fetch_entry_revisions: { responder: entryRevisionFetchResponder, inputValidator: entryRevisionHistoryFetchInputValidator, policies: baseLegalPolicies, }, fetch_error_report_infos: { responder: errorReportFetchInfosResponder, inputValidator: fetchErrorReportInfosRequestInputValidator, policies: baseLegalPolicies, }, fetch_messages: { responder: messageFetchResponder, inputValidator: fetchMessageInfosRequestInputValidator, policies: baseLegalPolicies, }, + fetch_pending_updates: { + responder: fetchPendingUpdatesResponder, + inputValidator: sessionStateValidator, + policies: baseLegalPolicies, + }, fetch_pinned_messages: { responder: fetchPinnedMessagesResponder, inputValidator: fetchPinnedMessagesResponderInputValidator, policies: baseLegalPolicies, }, fetch_primary_invite_links: { responder: fetchPrimaryInviteLinksResponder, inputValidator: ignoredArgumentValidator, policies: baseLegalPolicies, }, fetch_thread_media: { responder: threadFetchMediaResponder, inputValidator: threadFetchMediaRequestInputValidator, policies: baseLegalPolicies, }, get_initial_redux_state: { responder: getInitialReduxStateResponder, inputValidator: initialReduxStateRequestValidator, policies: [], }, join_thread: { responder: threadJoinResponder, inputValidator: joinThreadRequestInputValidator, policies: baseLegalPolicies, }, keyserver_auth: { responder: keyserverAuthResponder, inputValidator: keyserverAuthRequestInputValidator, policies: [], }, leave_thread: { responder: threadLeaveResponder, inputValidator: leaveThreadRequestInputValidator, policies: baseLegalPolicies, }, log_in: { responder: logInResponder, inputValidator: logInRequestInputValidator, policies: [], }, log_out: { responder: logOutResponder, inputValidator: ignoredArgumentValidator, policies: [], }, modify_community_role: { responder: roleModificationResponder, inputValidator: roleModificationRequestInputValidator, policies: baseLegalPolicies, }, policy_acknowledgment: { responder: policyAcknowledgmentResponder, inputValidator: policyAcknowledgmentRequestInputValidator, policies: [], }, remove_members: { responder: memberRemovalResponder, inputValidator: removeMembersRequestInputValidator, policies: baseLegalPolicies, }, restore_entry: { responder: entryRestorationResponder, inputValidator: restoreEntryRequestInputValidator, policies: baseLegalPolicies, }, search_messages: { responder: searchMessagesResponder, inputValidator: searchMessagesResponderInputValidator, policies: baseLegalPolicies, }, search_users: { responder: userSearchResponder, inputValidator: userSearchRequestInputValidator, policies: baseLegalPolicies, }, send_password_reset_email: { responder: sendPasswordResetEmailResponder, inputValidator: resetPasswordRequestInputValidator, policies: [], }, send_verification_email: { responder: sendVerificationEmailResponder, inputValidator: ignoredArgumentValidator, policies: [], }, set_thread_unread_status: { responder: threadSetUnreadStatusResponder, inputValidator: setThreadUnreadStatusValidator, policies: baseLegalPolicies, }, toggle_message_pin: { responder: toggleMessagePinResponder, inputValidator: toggleMessagePinRequestInputValidator, policies: baseLegalPolicies, }, update_account: { responder: passwordUpdateResponder, inputValidator: accountUpdateInputValidator, policies: baseLegalPolicies, }, update_activity: { responder: updateActivityResponder, inputValidator: updateActivityResponderInputValidator, policies: baseLegalPolicies, }, update_calendar_query: { responder: calendarQueryUpdateResponder, inputValidator: calendarQueryValidator, policies: baseLegalPolicies, }, update_user_settings: { responder: updateUserSettingsResponder, inputValidator: updateUserSettingsInputValidator, policies: baseLegalPolicies, }, update_device_token: { responder: deviceTokenUpdateResponder, inputValidator: deviceTokenUpdateRequestInputValidator, policies: [], }, update_entry: { responder: entryUpdateResponder, inputValidator: saveEntryRequestInputValidator, policies: baseLegalPolicies, }, update_password: { responder: oldPasswordUpdateResponder, inputValidator: updatePasswordRequestInputValidator, policies: baseLegalPolicies, }, update_relationships: { responder: updateRelationshipsResponder, inputValidator: updateRelationshipInputValidator, policies: baseLegalPolicies, }, update_role: { responder: roleUpdateResponder, inputValidator: roleChangeRequestInputValidator, policies: baseLegalPolicies, }, update_thread: { responder: threadUpdateResponder, inputValidator: updateThreadRequestInputValidator, policies: baseLegalPolicies, }, update_user_subscription: { responder: userSubscriptionUpdateResponder, inputValidator: subscriptionUpdateRequestInputValidator, policies: baseLegalPolicies, }, verify_code: { responder: codeVerificationResponder, inputValidator: codeVerificationRequestInputValidator, policies: baseLegalPolicies, }, verify_invite_link: { responder: inviteLinkVerificationResponder, inputValidator: inviteLinkVerificationRequestInputValidator, policies: baseLegalPolicies, }, siwe_nonce: { responder: siweNonceResponder, inputValidator: ignoredArgumentValidator, policies: [], }, siwe_auth: { responder: siweAuthResponder, inputValidator: siweAuthRequestInputValidator, policies: [], }, claim_username: { responder: claimUsernameResponder, inputValidator: claimUsernameRequestInputValidator, policies: [], }, update_user_avatar: { responder: updateUserAvatarResponder, inputValidator: updateUserAvatarRequestValidator, policies: baseLegalPolicies, }, upload_media_metadata: { responder: uploadMediaMetadataResponder, inputValidator: uploadMediaMetadataInputValidator, policies: baseLegalPolicies, }, get_olm_session_initialization_data: { responder: getOlmSessionInitializationDataResponder, inputValidator: ignoredArgumentValidator, policies: [], }, version: { responder: versionResponder, inputValidator: ignoredArgumentValidator, policies: [], }, fetch_community_infos: { responder: fetchCommunityInfosResponder, inputValidator: ignoredArgumentValidator, policies: baseLegalPolicies, }, create_or_update_farcaster_channel_tag: { responder: createOrUpdateFarcasterChannelTagResponder, inputValidator: createOrUpdateFarcasterChannelTagInputValidator, policies: baseLegalPolicies, }, delete_farcaster_channel_tag: { responder: deleteFarcasterChannelTagResponder, inputValidator: deleteFarcasterChannelTagInputValidator, policies: baseLegalPolicies, }, }; function createJSONResponders(obj: { +[Endpoint]: EndpointData }): { +[Endpoint]: JSONResponder, } { const result: { [Endpoint]: JSONResponder } = {}; Object.keys(obj).forEach((endpoint: Endpoint) => { const responder = createJSONResponder( obj[endpoint].responder, obj[endpoint].inputValidator, endpointValidators[endpoint].validator, obj[endpoint].policies, ); result[endpoint] = responder; }); return result; } const jsonEndpoints: { +[Endpoint]: JSONResponder } = createJSONResponders(jsonEndpointsData); export { jsonEndpoints }; diff --git a/keyserver/src/responders/update-responders.js b/keyserver/src/responders/update-responders.js new file mode 100644 index 000000000..4f0116959 --- /dev/null +++ b/keyserver/src/responders/update-responders.js @@ -0,0 +1,16 @@ +// @flow + +import type { SessionState } from 'lib/types/session-types.js'; +import type { ServerStateSyncSocketPayload } from 'lib/types/socket-types.js'; + +import { Viewer } from '../session/viewer.js'; +import { fetchDataForSocketInit } from '../socket/fetch-data.js'; + +function fetchPendingUpdatesResponder( + viewer: Viewer, + request: SessionState, +): Promise { + return fetchDataForSocketInit(viewer, request); +} + +export { fetchPendingUpdatesResponder }; diff --git a/lib/types/endpoints.js b/lib/types/endpoints.js index e64da74b8..3acdeee12 100644 --- a/lib/types/endpoints.js +++ b/lib/types/endpoints.js @@ -1,141 +1,142 @@ // @flow export type APIRequest = { endpoint: Endpoint, input?: Object, }; export type SocketAPIHandler = (request: APIRequest) => Promise; export type Endpoint = | HTTPOnlyEndpoint | SocketOnlyEndpoint | HTTPPreferredEndpoint | SocketPreferredEndpoint; // Endpoints that can cause session changes should occur over HTTP, since the // socket code does not currently support changing sessions. In the future they // could be made to work for native, but cookie changes on web require HTTP // since websockets aren't able to Set-Cookie. Note that technically any // endpoint can cause a sessionChange, and in that case the server will close // the socket with a specific error code, and the client will proceed via HTTP. const sessionChangingEndpoints = Object.freeze({ LOG_OUT: 'log_out', DELETE_ACCOUNT: 'delete_account', CREATE_ACCOUNT: 'create_account', LOG_IN: 'log_in', UPDATE_PASSWORD: 'update_password', POLICY_ACKNOWLEDGMENT: 'policy_acknowledgment', KEYSERVER_AUTH: 'keyserver_auth', }); type SessionChangingEndpoint = $Values; // We do uploads over HTTP as well. This is because Websockets use TCP, which // guarantees ordering. That means that if we start an upload, any messages we // try to send the server after the upload starts will have to wait until the // upload ends. To avoid blocking other messages we upload using HTTP // multipart/form-data. const uploadEndpoints = Object.freeze({ UPLOAD_MULTIMEDIA: 'upload_multimedia', }); type UploadEndpoint = $Values; -const intialReduxStateEndpoints = Object.freeze({ +const largeDataFetchEndpoints = Object.freeze({ GET_INITIAL_REDUX_STATE: 'get_initial_redux_state', + FETCH_PENDING_UPDATES: 'fetch_pending_updates', }); -type InitialReduxStateEndpoint = $Values; +type LargeDataFetchEndpoint = $Values; type HTTPOnlyEndpoint = | SessionChangingEndpoint | UploadEndpoint - | InitialReduxStateEndpoint; + | LargeDataFetchEndpoint; const socketOnlyEndpoints = Object.freeze({ UPDATE_ACTIVITY: 'update_activity', UPDATE_CALENDAR_QUERY: 'update_calendar_query', }); type SocketOnlyEndpoint = $Values; const socketPreferredEndpoints = Object.freeze({}); type SocketPreferredEndpoint = $Values; const httpPreferredEndpoints = Object.freeze({ CREATE_REPORT: 'create_report', CREATE_REPORTS: 'create_reports', CREATE_ENTRY: 'create_entry', CREATE_ERROR_REPORT: 'create_error_report', CREATE_MESSAGE_REPORT: 'create_message_report', CREATE_MULTIMEDIA_MESSAGE: 'create_multimedia_message', CREATE_OR_UPDATE_PUBLIC_LINK: 'create_or_update_public_link', CREATE_REACTION_MESSAGE: 'create_reaction_message', EDIT_MESSAGE: 'edit_message', CREATE_TEXT_MESSAGE: 'create_text_message', CREATE_THREAD: 'create_thread', DELETE_ENTRY: 'delete_entry', DELETE_COMMUNITY_ROLE: 'delete_community_role', DELETE_THREAD: 'delete_thread', DELETE_UPLOAD: 'delete_upload', DISABLE_INVITE_LINK: 'disable_invite_link', EXACT_SEARCH_USER: 'exact_search_user', FETCH_ENTRIES: 'fetch_entries', FETCH_ENTRY_REVISIONS: 'fetch_entry_revisions', FETCH_ERROR_REPORT_INFOS: 'fetch_error_report_infos', FETCH_MESSAGES: 'fetch_messages', FETCH_PINNED_MESSAGES: 'fetch_pinned_messages', FETCH_PRIMARY_INVITE_LINKS: 'fetch_primary_invite_links', FETCH_THREAD_MEDIA: 'fetch_thread_media', JOIN_THREAD: 'join_thread', LEAVE_THREAD: 'leave_thread', MODIFY_COMMUNITY_ROLE: 'modify_community_role', REMOVE_MEMBERS: 'remove_members', RESTORE_ENTRY: 'restore_entry', SEARCH_USERS: 'search_users', SEND_PASSWORD_RESET_EMAIL: 'send_password_reset_email', SEND_VERIFICATION_EMAIL: 'send_verification_email', SET_THREAD_UNREAD_STATUS: 'set_thread_unread_status', TOGGLE_MESSAGE_PIN: 'toggle_message_pin', UPDATE_ACCOUNT: 'update_account', UPDATE_USER_SETTINGS: 'update_user_settings', UPDATE_DEVICE_TOKEN: 'update_device_token', UPDATE_ENTRY: 'update_entry', UPDATE_RELATIONSHIPS: 'update_relationships', UPDATE_ROLE: 'update_role', UPDATE_THREAD: 'update_thread', UPDATE_USER_SUBSCRIPTION: 'update_user_subscription', VERIFY_CODE: 'verify_code', VERIFY_INVITE_LINK: 'verify_invite_link', SIWE_NONCE: 'siwe_nonce', SIWE_AUTH: 'siwe_auth', CLAIM_USERNAME: 'claim_username', UPDATE_USER_AVATAR: 'update_user_avatar', UPLOAD_MEDIA_METADATA: 'upload_media_metadata', SEARCH_MESSAGES: 'search_messages', GET_OLM_SESSION_INITIALIZATION_DATA: 'get_olm_session_initialization_data', VERSION: 'version', FETCH_COMMUNITY_INFOS: 'fetch_community_infos', CREATE_OR_UPDATE_FARCASTER_CHANNEL_TAG: 'create_or_update_farcaster_channel_tag', DELETE_FARCASTER_CHANNEL_TAG: 'delete_farcaster_channel_tag', }); type HTTPPreferredEndpoint = $Values; const socketPreferredEndpointSet = new Set([ ...Object.values(socketOnlyEndpoints), ...Object.values(socketPreferredEndpoints), ]); export function endpointIsSocketPreferred(endpoint: Endpoint): boolean { return socketPreferredEndpointSet.has(endpoint); } const socketSafeEndpointSet = new Set([ ...Object.values(socketOnlyEndpoints), ...Object.values(socketPreferredEndpoints), ...Object.values(httpPreferredEndpoints), ]); export function endpointIsSocketSafe(endpoint: Endpoint): boolean { return socketSafeEndpointSet.has(endpoint); } const socketOnlyEndpointSet = new Set(Object.values(socketOnlyEndpoints)); export function endpointIsSocketOnly(endpoint: Endpoint): boolean { return socketOnlyEndpointSet.has(endpoint); } diff --git a/lib/types/socket-types.js b/lib/types/socket-types.js index 4f51d23a4..15776bd36 100644 --- a/lib/types/socket-types.js +++ b/lib/types/socket-types.js @@ -1,551 +1,552 @@ // @flow import invariant from 'invariant'; import t, { type TInterface, type TUnion } from 'tcomb'; import { type RecoveryFromReduxActionSource, recoveryFromReduxActionSources, } from './account-types.js'; import { type ActivityUpdate, activityUpdateValidator, type UpdateActivityResult, updateActivityResultValidator, } from './activity-types.js'; import { type CompressedData, compressedDataValidator, } from './compression-types.js'; import type { APIRequest } from './endpoints.js'; import { type RawEntryInfo, rawEntryInfoValidator, type CalendarQuery, } from './entry-types.js'; import { type MessagesResponse, messagesResponseValidator, type NewMessagesPayload, newMessagesPayloadValidator, } from './message-types.js'; import { type ServerServerRequest, serverServerRequestValidator, type ClientServerRequest, type ClientResponse, type ClientClientResponse, } from './request-types.js'; import type { SessionState, SessionIdentification } from './session-types.js'; import type { MixedRawThreadInfos, RawThreadInfos } from './thread-types.js'; import { type ClientUpdatesResult, type ClientUpdatesResultWithUserInfos, type ServerUpdatesResult, serverUpdatesResultValidator, type ServerUpdatesResultWithUserInfos, serverUpdatesResultWithUserInfosValidator, } from './update-types.js'; import { type UserInfo, userInfoValidator, type CurrentUserInfo, currentUserInfoValidator, type LoggedOutUserInfo, loggedOutUserInfoValidator, } from './user-types.js'; import { mixedRawThreadInfoValidator } from '../permissions/minimally-encoded-raw-thread-info-validators.js'; import { values } from '../utils/objects.js'; import { tShape, tNumber, tID } from '../utils/validation-utils.js'; // The types of messages that the client sends across the socket export const clientSocketMessageTypes = Object.freeze({ INITIAL: 0, RESPONSES: 1, //ACTIVITY_UPDATES: 2, (DEPRECATED) PING: 3, ACK_UPDATES: 4, API_REQUEST: 5, }); export type ClientSocketMessageType = $Values; export function assertClientSocketMessageType( ourClientSocketMessageType: number, ): ClientSocketMessageType { invariant( ourClientSocketMessageType === 0 || ourClientSocketMessageType === 1 || ourClientSocketMessageType === 3 || ourClientSocketMessageType === 4 || ourClientSocketMessageType === 5, 'number is not ClientSocketMessageType enum', ); return ourClientSocketMessageType; } export type InitialClientSocketMessage = { +type: 0, +id: number, +payload: { +sessionIdentification: SessionIdentification, +sessionState: SessionState, +clientResponses: $ReadOnlyArray, }, }; export type ResponsesClientSocketMessage = { +type: 1, +id: number, +payload: { +clientResponses: $ReadOnlyArray, }, }; export type PingClientSocketMessage = { +type: 3, +id: number, }; export type AckUpdatesClientSocketMessage = { +type: 4, +id: number, +payload: { +currentAsOf: number, }, }; export type APIRequestClientSocketMessage = { +type: 5, +id: number, +payload: APIRequest, }; export type ClientSocketMessage = | InitialClientSocketMessage | ResponsesClientSocketMessage | PingClientSocketMessage | AckUpdatesClientSocketMessage | APIRequestClientSocketMessage; export type ClientInitialClientSocketMessage = { +type: 0, +id: number, +payload: { +sessionIdentification: SessionIdentification, +sessionState: SessionState, +clientResponses: $ReadOnlyArray, }, }; export type ClientResponsesClientSocketMessage = { +type: 1, +id: number, +payload: { +clientResponses: $ReadOnlyArray, }, }; export type ClientClientSocketMessage = | ClientInitialClientSocketMessage | ClientResponsesClientSocketMessage | PingClientSocketMessage | AckUpdatesClientSocketMessage | APIRequestClientSocketMessage; export type ClientSocketMessageWithoutID = $Diff< ClientClientSocketMessage, { id: number }, >; // The types of messages that the server sends across the socket export const serverSocketMessageTypes = Object.freeze({ STATE_SYNC: 0, REQUESTS: 1, ERROR: 2, AUTH_ERROR: 3, ACTIVITY_UPDATE_RESPONSE: 4, PONG: 5, UPDATES: 6, MESSAGES: 7, API_RESPONSE: 8, COMPRESSED_MESSAGE: 9, }); export type ServerSocketMessageType = $Values; export function assertServerSocketMessageType( ourServerSocketMessageType: number, ): ServerSocketMessageType { invariant( ourServerSocketMessageType === 0 || ourServerSocketMessageType === 1 || ourServerSocketMessageType === 2 || ourServerSocketMessageType === 3 || ourServerSocketMessageType === 4 || ourServerSocketMessageType === 5 || ourServerSocketMessageType === 6 || ourServerSocketMessageType === 7 || ourServerSocketMessageType === 8 || ourServerSocketMessageType === 9, 'number is not ServerSocketMessageType enum', ); return ourServerSocketMessageType; } export const stateSyncPayloadTypes = Object.freeze({ FULL: 0, INCREMENTAL: 1, }); export const fullStateSyncActionType = 'FULL_STATE_SYNC'; export type BaseFullStateSync = { +messagesResult: MessagesResponse, +rawEntryInfos: $ReadOnlyArray, +userInfos: $ReadOnlyArray, +updatesCurrentAsOf: number, }; const baseFullStateSyncValidator = tShape({ messagesResult: messagesResponseValidator, rawEntryInfos: t.list(rawEntryInfoValidator), userInfos: t.list(userInfoValidator), updatesCurrentAsOf: t.Number, }); export type ClientFullStateSync = $ReadOnly<{ ...BaseFullStateSync, +threadInfos: RawThreadInfos, +currentUserInfo: CurrentUserInfo, }>; export type StateSyncFullActionPayload = $ReadOnly<{ ...ClientFullStateSync, +calendarQuery: CalendarQuery, +keyserverID: string, }>; export type ClientStateSyncFullSocketPayload = $ReadOnly<{ ...ClientFullStateSync, +type: 0, // Included iff client is using sessionIdentifierTypes.BODY_SESSION_ID +sessionID?: string, }>; export type ServerFullStateSync = $ReadOnly<{ ...BaseFullStateSync, +threadInfos: MixedRawThreadInfos, +currentUserInfo: CurrentUserInfo, }>; const serverFullStateSyncValidator = tShape({ ...baseFullStateSyncValidator.meta.props, threadInfos: t.dict(tID, mixedRawThreadInfoValidator), currentUserInfo: currentUserInfoValidator, }); export type ServerStateSyncFullSocketPayload = { ...ServerFullStateSync, +type: 0, // Included iff client is using sessionIdentifierTypes.BODY_SESSION_ID +sessionID?: string, }; const serverStateSyncFullSocketPayloadValidator = tShape({ ...serverFullStateSyncValidator.meta.props, type: tNumber(stateSyncPayloadTypes.FULL), sessionID: t.maybe(t.String), }); export const incrementalStateSyncActionType = 'INCREMENTAL_STATE_SYNC'; export type BaseIncrementalStateSync = { +messagesResult: MessagesResponse, +deltaEntryInfos: $ReadOnlyArray, +deletedEntryIDs: $ReadOnlyArray, +userInfos: $ReadOnlyArray, }; const baseIncrementalStateSyncValidator = tShape({ messagesResult: messagesResponseValidator, deltaEntryInfos: t.list(rawEntryInfoValidator), deletedEntryIDs: t.list(tID), userInfos: t.list(userInfoValidator), }); export type ClientIncrementalStateSync = { ...BaseIncrementalStateSync, +updatesResult: ClientUpdatesResult, }; export type StateSyncIncrementalActionPayload = { ...ClientIncrementalStateSync, +calendarQuery: CalendarQuery, +keyserverID: string, }; type ClientStateSyncIncrementalSocketPayload = { +type: 1, ...ClientIncrementalStateSync, }; export type ServerIncrementalStateSync = { ...BaseIncrementalStateSync, +updatesResult: ServerUpdatesResult, }; const serverIncrementalStateSyncValidator = tShape({ ...baseIncrementalStateSyncValidator.meta.props, updatesResult: serverUpdatesResultValidator, }); type ServerStateSyncIncrementalSocketPayload = { +type: 1, ...ServerIncrementalStateSync, }; const serverStateSyncIncrementalSocketPayloadValidator = tShape({ type: tNumber(stateSyncPayloadTypes.INCREMENTAL), ...serverIncrementalStateSyncValidator.meta.props, }); export type ClientStateSyncSocketPayload = | ClientStateSyncFullSocketPayload | ClientStateSyncIncrementalSocketPayload; export type ServerStateSyncSocketPayload = | ServerStateSyncFullSocketPayload | ServerStateSyncIncrementalSocketPayload; -const serverStateSyncSocketPayloadValidator = t.union([ - serverStateSyncFullSocketPayloadValidator, - serverStateSyncIncrementalSocketPayloadValidator, -]); +export const serverStateSyncSocketPayloadValidator: TUnion = + t.union([ + serverStateSyncFullSocketPayloadValidator, + serverStateSyncIncrementalSocketPayloadValidator, + ]); export type ServerStateSyncServerSocketMessage = { +type: 0, +responseTo: number, +payload: ServerStateSyncSocketPayload, }; export const serverStateSyncServerSocketMessageValidator: TInterface = tShape({ type: tNumber(serverSocketMessageTypes.STATE_SYNC), responseTo: t.Number, payload: serverStateSyncSocketPayloadValidator, }); type ServerRequestsServerSocketMessagePayload = { +serverRequests: $ReadOnlyArray, }; export type ServerRequestsServerSocketMessage = { +type: 1, +responseTo?: number, +payload: ServerRequestsServerSocketMessagePayload, }; export const serverRequestsServerSocketMessageValidator: TInterface = tShape({ type: tNumber(serverSocketMessageTypes.REQUESTS), responseTo: t.maybe(t.Number), payload: tShape({ serverRequests: t.list(serverServerRequestValidator), }), }); export type ErrorServerSocketMessage = { type: 2, responseTo?: number, message: string, payload?: Object, }; export const errorServerSocketMessageValidator: TInterface = tShape({ type: tNumber(serverSocketMessageTypes.ERROR), responseTo: t.maybe(t.Number), message: t.String, payload: t.maybe(t.Object), }); type SessionChange = { +cookie: string, +currentUserInfo: LoggedOutUserInfo, }; export type AuthErrorServerSocketMessage = { +type: 3, +responseTo: number, +message: string, +sessionChange: SessionChange, }; export const authErrorServerSocketMessageValidator: TInterface = tShape({ type: tNumber(serverSocketMessageTypes.AUTH_ERROR), responseTo: t.Number, message: t.String, sessionChange: t.maybe( tShape({ cookie: t.String, currentUserInfo: loggedOutUserInfoValidator, }), ), }); export type ActivityUpdateResponseServerSocketMessage = { +type: 4, +responseTo: number, +payload: UpdateActivityResult, }; export const activityUpdateResponseServerSocketMessageValidator: TInterface = tShape({ type: tNumber(serverSocketMessageTypes.ACTIVITY_UPDATE_RESPONSE), responseTo: t.Number, payload: updateActivityResultValidator, }); export type PongServerSocketMessage = { +type: 5, +responseTo: number, }; export const pongServerSocketMessageValidator: TInterface = tShape({ type: tNumber(serverSocketMessageTypes.PONG), responseTo: t.Number, }); export type ServerUpdatesServerSocketMessage = { +type: 6, +payload: ServerUpdatesResultWithUserInfos, }; export const serverUpdatesServerSocketMessageValidator: TInterface = tShape({ type: tNumber(serverSocketMessageTypes.UPDATES), payload: serverUpdatesResultWithUserInfosValidator, }); export type MessagesServerSocketMessage = { +type: 7, +payload: NewMessagesPayload, }; export const messagesServerSocketMessageValidator: TInterface = tShape({ type: tNumber(serverSocketMessageTypes.MESSAGES), payload: newMessagesPayloadValidator, }); export type APIResponseServerSocketMessage = { +type: 8, +responseTo: number, +payload?: Object, }; export const apiResponseServerSocketMessageValidator: TInterface = tShape({ type: tNumber(serverSocketMessageTypes.API_RESPONSE), responseTo: t.Number, payload: t.maybe(t.Object), }); export type CompressedMessageServerSocketMessage = { +type: 9, +payload: CompressedData, }; export const compressedMessageServerSocketMessageValidator: TInterface = tShape({ type: tNumber(serverSocketMessageTypes.COMPRESSED_MESSAGE), payload: compressedDataValidator, }); export type ServerServerSocketMessage = | ServerStateSyncServerSocketMessage | ServerRequestsServerSocketMessage | ErrorServerSocketMessage | AuthErrorServerSocketMessage | ActivityUpdateResponseServerSocketMessage | PongServerSocketMessage | ServerUpdatesServerSocketMessage | MessagesServerSocketMessage | APIResponseServerSocketMessage | CompressedMessageServerSocketMessage; export const serverServerSocketMessageValidator: TUnion = t.union([ serverStateSyncServerSocketMessageValidator, serverRequestsServerSocketMessageValidator, errorServerSocketMessageValidator, authErrorServerSocketMessageValidator, activityUpdateResponseServerSocketMessageValidator, pongServerSocketMessageValidator, serverUpdatesServerSocketMessageValidator, messagesServerSocketMessageValidator, apiResponseServerSocketMessageValidator, compressedMessageServerSocketMessageValidator, ]); export type ClientRequestsServerSocketMessage = { +type: 1, +responseTo?: number, +payload: { +serverRequests: $ReadOnlyArray, }, }; export type ClientStateSyncServerSocketMessage = { +type: 0, +responseTo: number, +payload: ClientStateSyncSocketPayload, }; export type ClientUpdatesServerSocketMessage = { +type: 6, +payload: ClientUpdatesResultWithUserInfos, }; export type ClientServerSocketMessage = | ClientStateSyncServerSocketMessage | ClientRequestsServerSocketMessage | ErrorServerSocketMessage | AuthErrorServerSocketMessage | ActivityUpdateResponseServerSocketMessage | PongServerSocketMessage | ClientUpdatesServerSocketMessage | MessagesServerSocketMessage | APIResponseServerSocketMessage | CompressedMessageServerSocketMessage; export type SocketListener = (message: ClientServerSocketMessage) => void; export type ConnectionStatus = | 'connecting' | 'connected' | 'reconnecting' | 'disconnecting' | 'forcedDisconnecting' | 'disconnected'; export type ConnectionIssue = 'client_version_unsupported'; export type ConnectionInfo = { +status: ConnectionStatus, +queuedActivityUpdates: $ReadOnlyArray, +lateResponses: $ReadOnlyArray, +unreachable: boolean, +connectionIssue: ?ConnectionIssue, // When this is flipped to truthy, a session recovery is attempted // This can happen when the keyserver invalidates the session +activeSessionRecovery: null | RecoveryFromReduxActionSource, }; export const connectionInfoValidator: TInterface = tShape({ status: t.enums.of([ 'connecting', 'connected', 'reconnecting', 'disconnecting', 'forcedDisconnecting', 'disconnected', ]), queuedActivityUpdates: t.list(activityUpdateValidator), lateResponses: t.list(t.Number), unreachable: t.Boolean, connectionIssue: t.maybe(t.enums.of([])), activeSessionRecovery: t.maybe( t.enums.of(values(recoveryFromReduxActionSources)), ), }); export const defaultConnectionInfo: ConnectionInfo = { status: 'connecting', queuedActivityUpdates: [], lateResponses: [], unreachable: false, connectionIssue: null, activeSessionRecovery: null, }; export type SetActiveSessionRecoveryPayload = { +activeSessionRecovery: null | RecoveryFromReduxActionSource, +keyserverID: string, }; export type OneTimeKeyGenerator = (inc: number) => string; export type GRPCStream = { readyState: number, onopen: (ev: any) => mixed, onmessage: (ev: MessageEvent) => mixed, onclose: (ev: CloseEvent) => mixed, close(code?: number, reason?: string): void, send(data: string | Blob | ArrayBuffer | $ArrayBufferView): void, }; export type CommTransportLayer = GRPCStream | WebSocket; diff --git a/lib/types/validators/endpoint-validators.js b/lib/types/validators/endpoint-validators.js index d181e77d0..7a965f791 100644 --- a/lib/types/validators/endpoint-validators.js +++ b/lib/types/validators/endpoint-validators.js @@ -1,186 +1,190 @@ // @flow import t from 'tcomb'; import { fetchCommunityInfosResponseValidator } from './community-validators.js'; import { saveEntryResponseValidator, deltaEntryInfosResultValidator, fetchEntryInfosResponseValidator, fetchEntryRevisionInfosResultValidator, deleteEntryResponseValidator, restoreEntryResponseValidator, } from './entry-validators.js'; import { createOrUpdateFarcasterChannelTagResponseValidator } from './farcaster-channel-tag-validators.js'; import { fetchInviteLinksResponseValidator, inviteLinkVerificationResponseValidator, } from './link-validators.js'; import { messageReportCreationResultValidator } from './message-report-validators.js'; import { fetchMessageInfosResponseValidator, fetchPinnedMessagesResultValidator, sendEditMessageResponseValidator, sendMessageResponseValidator, searchMessagesResponseValidator, } from './message-validators.js'; import { initialReduxStateValidator } from './redux-state-validators.js'; import { relationshipErrorsValidator } from './relationship-validators.js'; import { fetchErrorReportInfosResponseValidator, reportCreationResponseValidator, } from './report-validators.js'; import { exactUserSearchResultValidator, userSearchResultValidator, } from './search-validators.js'; import { siweNonceResponseValidator } from './siwe-nonce-validators.js'; import { changeThreadSettingsResultValidator, leaveThreadResultValidator, newThreadResponseValidator, roleDeletionResultValidator, roleModificationResultValidator, threadFetchMediaResultValidator, threadJoinResultValidator, toggleMessagePinResultValidator, } from './thread-validators.js'; import { MultimediaUploadResultValidator } from './upload-validators.js'; import { claimUsernameResponseValidator, subscriptionUpdateResponseValidator, updateUserAvatarResponderValidator, logInResponseValidator, logOutResponseValidator, registerResponseValidator, } from './user-validators.js'; import { versionResponseValidator } from './version-validators.js'; import { setThreadUnreadStatusResultValidator, updateActivityResultValidator, } from '../activity-types.js'; import { inviteLinkValidator } from '../link-types.js'; import { uploadMultimediaResultValidator } from '../media-types.js'; import { getOlmSessionInitializationDataResponseValidator } from '../request-types.js'; +import { serverStateSyncSocketPayloadValidator } from '../socket-types.js'; const sessionChangingEndpoints = Object.freeze({ log_out: { validator: logOutResponseValidator, }, delete_account: { validator: logOutResponseValidator, }, create_account: { validator: registerResponseValidator, }, log_in: { validator: logInResponseValidator, }, update_password: { validator: logInResponseValidator, }, policy_acknowledgment: { validator: t.Nil, }, keyserver_auth: { validator: logInResponseValidator, }, }); const uploadEndpoints = Object.freeze({ upload_multimedia: { validator: MultimediaUploadResultValidator, }, }); -const initialReduxStateEndpoints = Object.freeze({ +const largeDataFetchEndpoints = Object.freeze({ get_initial_redux_state: { validator: initialReduxStateValidator, }, + fetch_pending_updates: { + validator: serverStateSyncSocketPayloadValidator, + }, }); const socketOnlyEndpoints = Object.freeze({ update_activity: { validator: updateActivityResultValidator, }, update_calendar_query: { validator: deltaEntryInfosResultValidator, }, }); const socketPreferredEndpoints = Object.freeze({}); const httpPreferredEndpoints = Object.freeze({ create_report: { validator: reportCreationResponseValidator, }, create_reports: { validator: t.Nil }, create_entry: { validator: saveEntryResponseValidator }, create_error_report: { validator: reportCreationResponseValidator }, create_message_report: { validator: messageReportCreationResultValidator }, create_multimedia_message: { validator: sendMessageResponseValidator }, create_or_update_public_link: { validator: inviteLinkValidator }, create_reaction_message: { validator: sendMessageResponseValidator }, edit_message: { validator: sendEditMessageResponseValidator }, create_text_message: { validator: sendMessageResponseValidator }, create_thread: { validator: newThreadResponseValidator }, delete_entry: { validator: deleteEntryResponseValidator }, delete_community_role: { validator: roleDeletionResultValidator }, delete_thread: { validator: leaveThreadResultValidator }, delete_upload: { validator: t.Nil }, disable_invite_link: { validator: t.Nil }, exact_search_user: { validator: exactUserSearchResultValidator }, fetch_entries: { validator: fetchEntryInfosResponseValidator }, fetch_entry_revisions: { validator: fetchEntryRevisionInfosResultValidator }, fetch_error_report_infos: { validator: fetchErrorReportInfosResponseValidator, }, fetch_messages: { validator: fetchMessageInfosResponseValidator }, fetch_pinned_messages: { validator: fetchPinnedMessagesResultValidator }, fetch_primary_invite_links: { validator: fetchInviteLinksResponseValidator }, fetch_thread_media: { validator: threadFetchMediaResultValidator }, join_thread: { validator: threadJoinResultValidator }, leave_thread: { validator: leaveThreadResultValidator }, modify_community_role: { validator: roleModificationResultValidator }, remove_members: { validator: changeThreadSettingsResultValidator }, restore_entry: { validator: restoreEntryResponseValidator }, search_users: { validator: userSearchResultValidator }, send_password_reset_email: { validator: t.Nil }, send_verification_email: { validator: t.Nil }, set_thread_unread_status: { validator: setThreadUnreadStatusResultValidator }, toggle_message_pin: { validator: toggleMessagePinResultValidator }, update_account: { validator: t.Nil }, update_user_settings: { validator: t.Nil }, update_device_token: { validator: t.Nil }, update_entry: { validator: saveEntryResponseValidator }, update_relationships: { validator: relationshipErrorsValidator }, update_role: { validator: changeThreadSettingsResultValidator }, update_thread: { validator: changeThreadSettingsResultValidator }, update_user_subscription: { validator: subscriptionUpdateResponseValidator }, verify_code: { validator: t.Nil }, verify_invite_link: { validator: inviteLinkVerificationResponseValidator }, siwe_nonce: { validator: siweNonceResponseValidator }, siwe_auth: { validator: logInResponseValidator }, claim_username: { validator: claimUsernameResponseValidator }, update_user_avatar: { validator: updateUserAvatarResponderValidator }, upload_media_metadata: { validator: uploadMultimediaResultValidator }, search_messages: { validator: searchMessagesResponseValidator }, get_olm_session_initialization_data: { validator: getOlmSessionInitializationDataResponseValidator, }, version: { validator: versionResponseValidator }, fetch_community_infos: { validator: fetchCommunityInfosResponseValidator }, create_or_update_farcaster_channel_tag: { validator: createOrUpdateFarcasterChannelTagResponseValidator, }, delete_farcaster_channel_tag: { validator: t.Nil }, }); export const endpointValidators = Object.freeze({ ...sessionChangingEndpoints, ...uploadEndpoints, - ...initialReduxStateEndpoints, + ...largeDataFetchEndpoints, ...socketOnlyEndpoints, ...socketPreferredEndpoints, ...httpPreferredEndpoints, });