diff --git a/keyserver/src/responders/activity-responders.js b/keyserver/src/responders/activity-responders.js index 4ae0722b5..6b551e5c7 100644 --- a/keyserver/src/responders/activity-responders.js +++ b/keyserver/src/responders/activity-responders.js @@ -1,61 +1,62 @@ // @flow import t from 'tcomb'; -import type { TList, TInterface } from 'tcomb'; +import type { TList } from 'tcomb'; import type { UpdateActivityResult, UpdateActivityRequest, SetThreadUnreadStatusRequest, SetThreadUnreadStatusResult, + ActivityUpdate, } from 'lib/types/activity-types.js'; import { tShape } from 'lib/utils/validation-utils.js'; import type { Viewer } from '../session/viewer.js'; import { activityUpdater, setThreadUnreadStatus, } from '../updaters/activity-updaters.js'; import { validateInput } from '../utils/validation-utils.js'; -const activityUpdatesInputValidator: TList = t.list( +const activityUpdatesInputValidator: TList> = t.list( tShape({ focus: t.Bool, threadID: t.String, latestMessage: t.maybe(t.String), }), ); const inputValidator = tShape({ updates: activityUpdatesInputValidator, }); async function updateActivityResponder( viewer: Viewer, input: any, ): Promise { const request: UpdateActivityRequest = input; await validateInput(viewer, inputValidator, request); return await activityUpdater(viewer, request); } const setThreadUnreadStatusValidator = tShape({ threadID: t.String, unread: t.Bool, latestMessage: t.maybe(t.String), }); async function threadSetUnreadStatusResponder( viewer: Viewer, input: any, ): Promise { const request: SetThreadUnreadStatusRequest = input; await validateInput(viewer, setThreadUnreadStatusValidator, request); return await setThreadUnreadStatus(viewer, request); } export { activityUpdatesInputValidator, updateActivityResponder, threadSetUnreadStatusResponder, }; diff --git a/keyserver/src/responders/device-responders.js b/keyserver/src/responders/device-responders.js index 38974a732..5c4022b60 100644 --- a/keyserver/src/responders/device-responders.js +++ b/keyserver/src/responders/device-responders.js @@ -1,28 +1,29 @@ // @flow import t from 'tcomb'; import type { TInterface } from 'tcomb'; import type { DeviceTokenUpdateRequest } from 'lib/types/device-types.js'; import { tShape, tPlatformDetails } from 'lib/utils/validation-utils.js'; import type { Viewer } from '../session/viewer.js'; import { deviceTokenUpdater } from '../updaters/device-token-updaters.js'; import { validateInput } from '../utils/validation-utils.js'; -const deviceTokenUpdateRequestInputValidator: TInterface = tShape({ - deviceToken: t.maybe(t.String), - deviceType: t.maybe(t.enums.of(['ios', 'android'])), - platformDetails: t.maybe(tPlatformDetails), -}); +const deviceTokenUpdateRequestInputValidator: TInterface = + tShape({ + deviceToken: t.maybe(t.String), + deviceType: t.maybe(t.enums.of(['ios', 'android'])), + platformDetails: t.maybe(tPlatformDetails), + }); async function deviceTokenUpdateResponder( viewer: Viewer, input: any, ): Promise { const request: DeviceTokenUpdateRequest = input; await validateInput(viewer, deviceTokenUpdateRequestInputValidator, request); await deviceTokenUpdater(viewer, request); } export { deviceTokenUpdateRequestInputValidator, deviceTokenUpdateResponder }; diff --git a/keyserver/src/responders/entry-responders.js b/keyserver/src/responders/entry-responders.js index 1ec49e652..22d60ba5a 100644 --- a/keyserver/src/responders/entry-responders.js +++ b/keyserver/src/responders/entry-responders.js @@ -1,252 +1,263 @@ // @flow import t from 'tcomb'; import type { TInterface } from 'tcomb'; import { filteredThreadIDs } from 'lib/selectors/calendar-filter-selectors.js'; import type { CalendarQuery, SaveEntryRequest, CreateEntryRequest, DeleteEntryRequest, DeleteEntryResponse, RestoreEntryRequest, RestoreEntryResponse, FetchEntryInfosResponse, DeltaEntryInfosResult, SaveEntryResponse, } from 'lib/types/entry-types.js'; -import { calendarThreadFilterTypes } from 'lib/types/filter-types.js'; +import { + type CalendarFilter, + calendarThreadFilterTypes, +} from 'lib/types/filter-types.js'; import type { FetchEntryRevisionInfosResult, FetchEntryRevisionInfosRequest, } from 'lib/types/history-types.js'; import { ServerError } from 'lib/utils/errors.js'; import { tString, tShape, tDate } from 'lib/utils/validation-utils.js'; import createEntry from '../creators/entry-creator.js'; import { deleteEntry, restoreEntry } from '../deleters/entry-deleters.js'; import { fetchEntryInfos, fetchEntryRevisionInfo, fetchEntriesForSession, } from '../fetchers/entry-fetchers.js'; import { verifyThreadIDs } from '../fetchers/thread-fetchers.js'; import type { Viewer } from '../session/viewer.js'; import { updateEntry, compareNewCalendarQuery, } from '../updaters/entry-updaters.js'; import { commitSessionUpdate } from '../updaters/session-updaters.js'; import { validateInput } from '../utils/validation-utils.js'; -const entryQueryInputValidator: TInterface = tShape({ +type EntryQueryInput = { + +startDate: string, + +endDate: string, + +navID?: ?string, + +includeDeleted?: ?boolean, + +filters?: ?$ReadOnlyArray, +}; +const entryQueryInputValidator: TInterface = tShape({ navID: t.maybe(t.String), startDate: tDate, endDate: tDate, includeDeleted: t.maybe(t.Boolean), filters: t.maybe( t.list( t.union([ tShape({ type: tString(calendarThreadFilterTypes.NOT_DELETED), }), tShape({ type: tString(calendarThreadFilterTypes.THREAD_LIST), threadIDs: t.list(t.String), }), ]), ), ), }); -const newEntryQueryInputValidator: TInterface = tShape({ + +const newEntryQueryInputValidator: TInterface = tShape({ startDate: tDate, endDate: tDate, filters: t.list( t.union([ tShape({ type: tString(calendarThreadFilterTypes.NOT_DELETED), }), tShape({ type: tString(calendarThreadFilterTypes.THREAD_LIST), threadIDs: t.list(t.String), }), ]), ), }); function normalizeCalendarQuery(input: any): CalendarQuery { if (input.filters) { return { startDate: input.startDate, endDate: input.endDate, filters: input.filters, }; } const filters = []; if (!input.includeDeleted) { filters.push({ type: calendarThreadFilterTypes.NOT_DELETED }); } if (input.navID !== 'home') { filters.push({ type: calendarThreadFilterTypes.THREAD_LIST, threadIDs: [input.navID], }); } return { startDate: input.startDate, endDate: input.endDate, filters, }; } async function verifyCalendarQueryThreadIDs( request: CalendarQuery, ): Promise { const threadIDsToFilterTo = filteredThreadIDs(request.filters); if (threadIDsToFilterTo && threadIDsToFilterTo.size > 0) { const verifiedThreadIDs = await verifyThreadIDs([...threadIDsToFilterTo]); if (verifiedThreadIDs.length !== threadIDsToFilterTo.size) { throw new ServerError('invalid_parameters'); } } } async function entryFetchResponder( viewer: Viewer, input: any, ): Promise { await validateInput(viewer, entryQueryInputValidator, input); const request = normalizeCalendarQuery(input); await verifyCalendarQueryThreadIDs(request); const response = await fetchEntryInfos(viewer, [request]); return { ...response, userInfos: {} }; } const entryRevisionHistoryFetchInputValidator = tShape({ id: t.String, }); async function entryRevisionFetchResponder( viewer: Viewer, input: any, ): Promise { const request: FetchEntryRevisionInfosRequest = input; await validateInput(viewer, entryRevisionHistoryFetchInputValidator, request); const entryHistory = await fetchEntryRevisionInfo(viewer, request.id); return { result: entryHistory }; } const createEntryRequestInputValidator = tShape({ text: t.String, sessionID: t.maybe(t.String), timestamp: t.Number, date: tDate, threadID: t.String, localID: t.maybe(t.String), calendarQuery: t.maybe(newEntryQueryInputValidator), }); async function entryCreationResponder( viewer: Viewer, input: any, ): Promise { const request: CreateEntryRequest = input; await validateInput(viewer, createEntryRequestInputValidator, request); return await createEntry(viewer, request); } const saveEntryRequestInputValidator = tShape({ entryID: t.String, text: t.String, prevText: t.String, sessionID: t.maybe(t.String), timestamp: t.Number, calendarQuery: t.maybe(newEntryQueryInputValidator), }); async function entryUpdateResponder( viewer: Viewer, input: any, ): Promise { const request: SaveEntryRequest = input; await validateInput(viewer, saveEntryRequestInputValidator, request); return await updateEntry(viewer, request); } const deleteEntryRequestInputValidator = tShape({ entryID: t.String, prevText: t.String, sessionID: t.maybe(t.String), timestamp: t.Number, calendarQuery: t.maybe(newEntryQueryInputValidator), }); async function entryDeletionResponder( viewer: Viewer, input: any, ): Promise { const request: DeleteEntryRequest = input; await validateInput(viewer, deleteEntryRequestInputValidator, request); return await deleteEntry(viewer, request); } const restoreEntryRequestInputValidator = tShape({ entryID: t.String, sessionID: t.maybe(t.String), timestamp: t.Number, calendarQuery: t.maybe(newEntryQueryInputValidator), }); async function entryRestorationResponder( viewer: Viewer, input: any, ): Promise { const request: RestoreEntryRequest = input; await validateInput(viewer, restoreEntryRequestInputValidator, request); return await restoreEntry(viewer, request); } async function calendarQueryUpdateResponder( viewer: Viewer, input: any, ): Promise { const request: CalendarQuery = input; await validateInput(viewer, newEntryQueryInputValidator, input); await verifyCalendarQueryThreadIDs(request); if (!viewer.loggedIn) { throw new ServerError('not_logged_in'); } const { difference, oldCalendarQuery, sessionUpdate } = compareNewCalendarQuery(viewer, request); const [response] = await Promise.all([ fetchEntriesForSession(viewer, difference, oldCalendarQuery), commitSessionUpdate(viewer, sessionUpdate), ]); return { rawEntryInfos: response.rawEntryInfos, deletedEntryIDs: response.deletedEntryIDs, // Old clients expect userInfos object userInfos: [], }; } export { entryQueryInputValidator, newEntryQueryInputValidator, normalizeCalendarQuery, verifyCalendarQueryThreadIDs, entryFetchResponder, entryRevisionFetchResponder, entryCreationResponder, entryUpdateResponder, entryDeletionResponder, entryRestorationResponder, calendarQueryUpdateResponder, }; diff --git a/keyserver/src/responders/report-responders.js b/keyserver/src/responders/report-responders.js index d29af327d..0ff769b45 100644 --- a/keyserver/src/responders/report-responders.js +++ b/keyserver/src/responders/report-responders.js @@ -1,239 +1,243 @@ // @flow import type { $Response, $Request } from 'express'; import t from 'tcomb'; import type { TStructProps } from 'tcomb'; import { type ReportCreationResponse, type ReportCreationRequest, type FetchErrorReportInfosResponse, type FetchErrorReportInfosRequest, + type ThreadInconsistencyReportShape, + type EntryInconsistencyReportShape, reportTypes, } from 'lib/types/report-types.js'; import { ServerError } from 'lib/utils/errors.js'; import { tShape, tPlatform, tPlatformDetails, } from 'lib/utils/validation-utils.js'; import { newEntryQueryInputValidator } from './entry-responders.js'; import createReport from '../creators/report-creator.js'; import { fetchErrorReportInfos, fetchReduxToolsImport, } from '../fetchers/report-fetchers.js'; import type { Viewer } from '../session/viewer.js'; import { validateInput } from '../utils/validation-utils.js'; const tActionSummary = tShape({ type: t.String, time: t.Number, summary: t.String, }); -const threadInconsistencyReportValidatorShape: TStructProps = { - platformDetails: tPlatformDetails, - beforeAction: t.Object, - action: t.Object, - pollResult: t.maybe(t.Object), - pushResult: t.Object, - lastActionTypes: t.maybe(t.list(t.String)), - lastActions: t.maybe(t.list(tActionSummary)), - time: t.maybe(t.Number), -}; -const entryInconsistencyReportValidatorShape: TStructProps = { - platformDetails: tPlatformDetails, - beforeAction: t.Object, - action: t.Object, - calendarQuery: newEntryQueryInputValidator, - pollResult: t.maybe(t.Object), - pushResult: t.Object, - lastActionTypes: t.maybe(t.list(t.String)), - lastActions: t.maybe(t.list(tActionSummary)), - time: t.Number, -}; +const threadInconsistencyReportValidatorShape: TStructProps = + { + platformDetails: tPlatformDetails, + beforeAction: t.Object, + action: t.Object, + pollResult: t.maybe(t.Object), + pushResult: t.Object, + lastActionTypes: t.maybe(t.list(t.String)), + lastActions: t.maybe(t.list(tActionSummary)), + time: t.maybe(t.Number), + }; +const entryInconsistencyReportValidatorShape: TStructProps = + { + platformDetails: tPlatformDetails, + beforeAction: t.Object, + action: t.Object, + calendarQuery: newEntryQueryInputValidator, + pollResult: t.maybe(t.Object), + pushResult: t.Object, + lastActionTypes: t.maybe(t.list(t.String)), + lastActions: t.maybe(t.list(tActionSummary)), + time: t.Number, + }; const userInconsistencyReportValidatorShape = { platformDetails: tPlatformDetails, action: t.Object, beforeStateCheck: t.Object, afterStateCheck: t.Object, lastActions: t.list(tActionSummary), time: t.Number, }; const threadInconsistencyReportCreationRequest = tShape({ ...threadInconsistencyReportValidatorShape, type: t.irreducible( 'reportTypes.THREAD_INCONSISTENCY', x => x === reportTypes.THREAD_INCONSISTENCY, ), }); const entryInconsistencyReportCreationRquest = tShape({ ...entryInconsistencyReportValidatorShape, type: t.irreducible( 'reportTypes.ENTRY_INCONSISTENCY', x => x === reportTypes.ENTRY_INCONSISTENCY, ), }); const mediaMissionReportCreationRequest = tShape({ type: t.irreducible( 'reportTypes.MEDIA_MISSION', x => x === reportTypes.MEDIA_MISSION, ), platformDetails: tPlatformDetails, time: t.Number, mediaMission: t.Object, uploadServerID: t.maybe(t.String), uploadLocalID: t.maybe(t.String), mediaLocalID: t.maybe(t.String), messageServerID: t.maybe(t.String), messageLocalID: t.maybe(t.String), }); const userInconsistencyReportCreationRequest = tShape({ ...userInconsistencyReportValidatorShape, type: t.irreducible( 'reportTypes.USER_INCONSISTENCY', x => x === reportTypes.USER_INCONSISTENCY, ), }); const reportCreationRequestInputValidator = t.union([ tShape({ type: t.maybe( t.irreducible('reportTypes.ERROR', x => x === reportTypes.ERROR), ), platformDetails: t.maybe(tPlatformDetails), deviceType: t.maybe(tPlatform), codeVersion: t.maybe(t.Number), stateVersion: t.maybe(t.Number), errors: t.list( tShape({ errorMessage: t.String, stack: t.maybe(t.String), componentStack: t.maybe(t.String), }), ), preloadedState: t.Object, currentState: t.Object, actions: t.list(t.union([t.Object, t.String])), }), threadInconsistencyReportCreationRequest, entryInconsistencyReportCreationRquest, mediaMissionReportCreationRequest, userInconsistencyReportCreationRequest, ]); async function reportCreationResponder( viewer: Viewer, input: any, ): Promise { await validateInput(viewer, reportCreationRequestInputValidator, input); if (input.type === null || input.type === undefined) { input.type = reportTypes.ERROR; } if (!input.platformDetails && input.deviceType) { const { deviceType, codeVersion, stateVersion, ...rest } = input; input = { ...rest, platformDetails: { platform: deviceType, codeVersion, stateVersion }, }; } const request: ReportCreationRequest = input; const response = await createReport(viewer, request); if (!response) { throw new ServerError('ignored_report'); } return response; } const reportMultiCreationRequestInputValidator = tShape({ reports: t.list( t.union([ tShape({ type: t.irreducible('reportTypes.ERROR', x => x === reportTypes.ERROR), platformDetails: tPlatformDetails, errors: t.list( tShape({ errorMessage: t.String, stack: t.maybe(t.String), componentStack: t.maybe(t.String), }), ), preloadedState: t.Object, currentState: t.Object, actions: t.list(t.union([t.Object, t.String])), }), threadInconsistencyReportCreationRequest, entryInconsistencyReportCreationRquest, mediaMissionReportCreationRequest, userInconsistencyReportCreationRequest, ]), ), }); type ReportMultiCreationRequest = { reports: $ReadOnlyArray, }; async function reportMultiCreationResponder( viewer: Viewer, input: any, ): Promise { const request: ReportMultiCreationRequest = input; await validateInput( viewer, reportMultiCreationRequestInputValidator, request, ); await Promise.all( request.reports.map(reportCreationRequest => createReport(viewer, reportCreationRequest), ), ); } const fetchErrorReportInfosRequestInputValidator = tShape({ cursor: t.maybe(t.String), }); async function errorReportFetchInfosResponder( viewer: Viewer, input: any, ): Promise { const request: FetchErrorReportInfosRequest = input; await validateInput( viewer, fetchErrorReportInfosRequestInputValidator, request, ); return await fetchErrorReportInfos(viewer, request); } async function errorReportDownloadResponder( viewer: Viewer, req: $Request, res: $Response, ): Promise { const id = req.params.reportID; if (!id) { throw new ServerError('invalid_parameters'); } const result = await fetchReduxToolsImport(viewer, id); res.set('Content-Disposition', `attachment; filename=report-${id}.json`); res.json({ preloadedState: JSON.stringify(result.preloadedState), payload: JSON.stringify(result.payload), }); } export { threadInconsistencyReportValidatorShape, entryInconsistencyReportValidatorShape, reportCreationResponder, reportMultiCreationResponder, errorReportFetchInfosResponder, errorReportDownloadResponder, }; diff --git a/keyserver/src/responders/thread-responders.js b/keyserver/src/responders/thread-responders.js index 7697d8832..d349f1d98 100644 --- a/keyserver/src/responders/thread-responders.js +++ b/keyserver/src/responders/thread-responders.js @@ -1,226 +1,226 @@ // @flow import t from 'tcomb'; -import type { TUnion, TInterface } from 'tcomb'; +import type { TUnion } from 'tcomb'; import { type ThreadDeletionRequest, type RoleChangeRequest, type ChangeThreadSettingsResult, type RemoveMembersRequest, type LeaveThreadRequest, type LeaveThreadResult, type UpdateThreadRequest, type ServerNewThreadRequest, type NewThreadResponse, type ServerThreadJoinRequest, type ThreadJoinResult, type ThreadFetchMediaResult, type ThreadFetchMediaRequest, type ToggleMessagePinRequest, type ToggleMessagePinResult, threadTypes, } from 'lib/types/thread-types.js'; import { updateUserAvatarRequestValidator } from 'lib/utils/avatar-utils.js'; import { values } from 'lib/utils/objects.js'; import { tShape, tNumEnum, tColor, tPassword, } from 'lib/utils/validation-utils.js'; import { entryQueryInputValidator, verifyCalendarQueryThreadIDs, } from './entry-responders.js'; import { createThread } from '../creators/thread-creator.js'; import { deleteThread } from '../deleters/thread-deleters.js'; import { fetchMediaForThread } from '../fetchers/upload-fetchers.js'; import type { Viewer } from '../session/viewer.js'; import { updateRole, removeMembers, leaveThread, updateThread, joinThread, toggleMessagePinForThread, } from '../updaters/thread-updaters.js'; import { validateInput } from '../utils/validation-utils.js'; const threadDeletionRequestInputValidator = tShape({ threadID: t.String, accountPassword: t.maybe(tPassword), }); async function threadDeletionResponder( viewer: Viewer, input: any, ): Promise { const request: ThreadDeletionRequest = input; await validateInput(viewer, threadDeletionRequestInputValidator, request); return await deleteThread(viewer, request); } const roleChangeRequestInputValidator = tShape({ threadID: t.String, memberIDs: t.list(t.String), role: t.refinement(t.String, str => { const int = parseInt(str, 10); return String(int) === str && int > 0; }), }); async function roleUpdateResponder( viewer: Viewer, input: any, ): Promise { const request: RoleChangeRequest = input; await validateInput(viewer, roleChangeRequestInputValidator, request); return await updateRole(viewer, request); } const removeMembersRequestInputValidator = tShape({ threadID: t.String, memberIDs: t.list(t.String), }); async function memberRemovalResponder( viewer: Viewer, input: any, ): Promise { const request: RemoveMembersRequest = input; await validateInput(viewer, removeMembersRequestInputValidator, request); return await removeMembers(viewer, request); } const leaveThreadRequestInputValidator = tShape({ threadID: t.String, }); async function threadLeaveResponder( viewer: Viewer, input: any, ): Promise { const request: LeaveThreadRequest = input; await validateInput(viewer, leaveThreadRequestInputValidator, request); return await leaveThread(viewer, request); } const updateThreadRequestInputValidator = tShape({ threadID: t.String, changes: tShape({ type: t.maybe(tNumEnum(values(threadTypes))), name: t.maybe(t.String), description: t.maybe(t.String), color: t.maybe(tColor), parentThreadID: t.maybe(t.String), newMemberIDs: t.maybe(t.list(t.String)), avatar: t.maybe(updateUserAvatarRequestValidator), }), accountPassword: t.maybe(tPassword), }); async function threadUpdateResponder( viewer: Viewer, input: any, ): Promise { const request: UpdateThreadRequest = input; await validateInput(viewer, updateThreadRequestInputValidator, request); return await updateThread(viewer, request); } const threadRequestValidationShape = { name: t.maybe(t.String), description: t.maybe(t.String), color: t.maybe(tColor), parentThreadID: t.maybe(t.String), initialMemberIDs: t.maybe(t.list(t.String)), calendarQuery: t.maybe(entryQueryInputValidator), }; -const newThreadRequestInputValidator: TUnion = t.union([ +const newThreadRequestInputValidator: TUnion = t.union([ tShape({ type: tNumEnum([threadTypes.SIDEBAR]), sourceMessageID: t.String, ...threadRequestValidationShape, }), tShape({ type: tNumEnum([ threadTypes.COMMUNITY_OPEN_SUBTHREAD, threadTypes.COMMUNITY_SECRET_SUBTHREAD, threadTypes.PERSONAL, threadTypes.LOCAL, ]), ...threadRequestValidationShape, }), ]); async function threadCreationResponder( viewer: Viewer, input: any, ): Promise { const request: ServerNewThreadRequest = input; await validateInput(viewer, newThreadRequestInputValidator, request); return await createThread(viewer, request, { silentlyFailMembers: request.type === threadTypes.SIDEBAR, }); } const joinThreadRequestInputValidator = tShape({ threadID: t.String, calendarQuery: t.maybe(entryQueryInputValidator), inviteLinkSecret: t.maybe(t.String), }); async function threadJoinResponder( viewer: Viewer, input: any, ): Promise { const request: ServerThreadJoinRequest = input; await validateInput(viewer, joinThreadRequestInputValidator, request); if (request.calendarQuery) { await verifyCalendarQueryThreadIDs(request.calendarQuery); } return await joinThread(viewer, request); } const threadFetchMediaRequestInputValidator = tShape({ threadID: t.String, limit: t.Number, offset: t.Number, }); async function threadFetchMediaResponder( viewer: Viewer, input: any, ): Promise { const request: ThreadFetchMediaRequest = input; await validateInput(viewer, threadFetchMediaRequestInputValidator, request); return await fetchMediaForThread(viewer, request); } const toggleMessagePinRequestInputValidator = tShape({ messageID: t.String, action: t.enums.of(['pin', 'unpin']), }); async function toggleMessagePinResponder( viewer: Viewer, input: any, ): Promise { const request: ToggleMessagePinRequest = input; await validateInput(viewer, toggleMessagePinRequestInputValidator, request); return await toggleMessagePinForThread(viewer, request); } export { threadDeletionResponder, roleUpdateResponder, memberRemovalResponder, threadLeaveResponder, threadUpdateResponder, threadCreationResponder, threadJoinResponder, threadFetchMediaResponder, newThreadRequestInputValidator, toggleMessagePinResponder, }; diff --git a/keyserver/src/socket/session-utils.js b/keyserver/src/socket/session-utils.js index 78dff919f..e0666abae 100644 --- a/keyserver/src/socket/session-utils.js +++ b/keyserver/src/socket/session-utils.js @@ -1,601 +1,601 @@ // @flow import invariant from 'invariant'; import t from 'tcomb'; -import type { TUnion, TInterface } from 'tcomb'; +import type { TUnion } from 'tcomb'; import { usersInRawEntryInfos, serverEntryInfo, serverEntryInfosObject, } from 'lib/shared/entry-utils.js'; import { usersInThreadInfo } from 'lib/shared/thread-utils.js'; import { hasMinCodeVersion } from 'lib/shared/version-utils.js'; import type { UpdateActivityResult } from 'lib/types/activity-types.js'; import type { IdentityKeysBlob } from 'lib/types/crypto-types.js'; import { isDeviceType } from 'lib/types/device-types.js'; import type { CalendarQuery, DeltaEntryInfosResponse, } from 'lib/types/entry-types.js'; import { reportTypes, type ThreadInconsistencyReportCreationRequest, type EntryInconsistencyReportCreationRequest, } from 'lib/types/report-types.js'; import { serverRequestTypes, type ThreadInconsistencyClientResponse, type EntryInconsistencyClientResponse, type ClientResponse, type ServerServerRequest, type ServerCheckStateServerRequest, } from 'lib/types/request-types.js'; import { sessionCheckFrequency } from 'lib/types/session-types.js'; import { signedIdentityKeysBlobValidator } from 'lib/utils/crypto-utils.js'; import { hash } from 'lib/utils/objects.js'; import { promiseAll } from 'lib/utils/promises.js'; import { tShape, tPlatform, tPlatformDetails, } from 'lib/utils/validation-utils.js'; import { saveOneTimeKeys } from '../creators/one-time-keys-creator.js'; import createReport from '../creators/report-creator.js'; import { SQL } from '../database/database.js'; import { fetchEntryInfos, fetchEntryInfosByID, fetchEntriesForSession, } from '../fetchers/entry-fetchers.js'; import { checkIfSessionHasEnoughOneTimeKeys } from '../fetchers/key-fetchers.js'; import { fetchThreadInfos } from '../fetchers/thread-fetchers.js'; import { fetchCurrentUserInfo, fetchUserInfos, fetchKnownUserInfos, } from '../fetchers/user-fetchers.js'; import { activityUpdatesInputValidator } from '../responders/activity-responders.js'; import { handleAsyncPromise } from '../responders/handlers.js'; import { threadInconsistencyReportValidatorShape, entryInconsistencyReportValidatorShape, } from '../responders/report-responders.js'; import { setNewSession, setCookiePlatform, setCookiePlatformDetails, setCookieSignedIdentityKeysBlob, } from '../session/cookies.js'; import type { Viewer } from '../session/viewer.js'; import { activityUpdater } from '../updaters/activity-updaters.js'; import { compareNewCalendarQuery } from '../updaters/entry-updaters.js'; import type { SessionUpdate } from '../updaters/session-updaters.js'; import { getOlmUtility } from '../utils/olm-utils.js'; -const clientResponseInputValidator: TUnion = t.union([ +const clientResponseInputValidator: TUnion = t.union([ tShape({ type: t.irreducible( 'serverRequestTypes.PLATFORM', x => x === serverRequestTypes.PLATFORM, ), platform: tPlatform, }), tShape({ ...threadInconsistencyReportValidatorShape, type: t.irreducible( 'serverRequestTypes.THREAD_INCONSISTENCY', x => x === serverRequestTypes.THREAD_INCONSISTENCY, ), }), tShape({ ...entryInconsistencyReportValidatorShape, type: t.irreducible( 'serverRequestTypes.ENTRY_INCONSISTENCY', x => x === serverRequestTypes.ENTRY_INCONSISTENCY, ), }), tShape({ type: t.irreducible( 'serverRequestTypes.PLATFORM_DETAILS', x => x === serverRequestTypes.PLATFORM_DETAILS, ), platformDetails: tPlatformDetails, }), tShape({ type: t.irreducible( 'serverRequestTypes.CHECK_STATE', x => x === serverRequestTypes.CHECK_STATE, ), hashResults: t.dict(t.String, t.Boolean), }), tShape({ type: t.irreducible( 'serverRequestTypes.INITIAL_ACTIVITY_UPDATES', x => x === serverRequestTypes.INITIAL_ACTIVITY_UPDATES, ), activityUpdates: activityUpdatesInputValidator, }), tShape({ type: t.irreducible( 'serverRequestTypes.MORE_ONE_TIME_KEYS', x => x === serverRequestTypes.MORE_ONE_TIME_KEYS, ), keys: t.list(t.String), }), tShape({ type: t.irreducible( 'serverRequestTypes.SIGNED_IDENTITY_KEYS_BLOB', x => x === serverRequestTypes.SIGNED_IDENTITY_KEYS_BLOB, ), signedIdentityKeysBlob: signedIdentityKeysBlobValidator, }), ]); type StateCheckStatus = | { status: 'state_validated' } | { status: 'state_invalid', invalidKeys: $ReadOnlyArray } | { status: 'state_check' }; type ProcessClientResponsesResult = { serverRequests: ServerServerRequest[], stateCheckStatus: ?StateCheckStatus, activityUpdateResult: ?UpdateActivityResult, }; async function processClientResponses( viewer: Viewer, clientResponses: $ReadOnlyArray, ): Promise { let viewerMissingPlatform = !viewer.platform; const { platformDetails } = viewer; let viewerMissingPlatformDetails = !platformDetails || (isDeviceType(viewer.platform) && (platformDetails.codeVersion === null || platformDetails.codeVersion === undefined || platformDetails.stateVersion === null || platformDetails.stateVersion === undefined)); const promises = []; let activityUpdates = []; let stateCheckStatus = null; const clientSentPlatformDetails = clientResponses.some( response => response.type === serverRequestTypes.PLATFORM_DETAILS, ); for (const clientResponse of clientResponses) { if ( clientResponse.type === serverRequestTypes.PLATFORM && !clientSentPlatformDetails ) { promises.push(setCookiePlatform(viewer, clientResponse.platform)); viewerMissingPlatform = false; if (!isDeviceType(clientResponse.platform)) { viewerMissingPlatformDetails = false; } } else if ( clientResponse.type === serverRequestTypes.THREAD_INCONSISTENCY ) { promises.push(recordThreadInconsistency(viewer, clientResponse)); } else if (clientResponse.type === serverRequestTypes.ENTRY_INCONSISTENCY) { promises.push(recordEntryInconsistency(viewer, clientResponse)); } else if (clientResponse.type === serverRequestTypes.PLATFORM_DETAILS) { promises.push( setCookiePlatformDetails(viewer, clientResponse.platformDetails), ); viewerMissingPlatform = false; viewerMissingPlatformDetails = false; } else if ( clientResponse.type === serverRequestTypes.INITIAL_ACTIVITY_UPDATES ) { activityUpdates = [...activityUpdates, ...clientResponse.activityUpdates]; } else if (clientResponse.type === serverRequestTypes.CHECK_STATE) { const invalidKeys = []; for (const key in clientResponse.hashResults) { const result = clientResponse.hashResults[key]; if (!result) { invalidKeys.push(key); } } stateCheckStatus = invalidKeys.length > 0 ? { status: 'state_invalid', invalidKeys } : { status: 'state_validated' }; } else if (clientResponse.type === serverRequestTypes.MORE_ONE_TIME_KEYS) { invariant(clientResponse.keys, 'keys expected in client response'); handleAsyncPromise(saveOneTimeKeys(viewer, clientResponse.keys)); } else if ( clientResponse.type === serverRequestTypes.SIGNED_IDENTITY_KEYS_BLOB ) { invariant( clientResponse.signedIdentityKeysBlob, 'signedIdentityKeysBlob expected in client response', ); const { signedIdentityKeysBlob } = clientResponse; const identityKeys: IdentityKeysBlob = JSON.parse( signedIdentityKeysBlob.payload, ); const olmUtil = getOlmUtility(); try { olmUtil.ed25519_verify( identityKeys.primaryIdentityPublicKeys.ed25519, signedIdentityKeysBlob.payload, signedIdentityKeysBlob.signature, ); handleAsyncPromise( setCookieSignedIdentityKeysBlob( viewer.cookieID, signedIdentityKeysBlob, ), ); } catch (e) { continue; } } } const activityUpdatePromise = (async () => { if (activityUpdates.length === 0) { return undefined; } return await activityUpdater(viewer, { updates: activityUpdates }); })(); const serverRequests = []; const checkOneTimeKeysPromise = (async () => { if (!viewer.loggedIn) { return; } const enoughOneTimeKeys = await checkIfSessionHasEnoughOneTimeKeys( viewer.session, ); if (!enoughOneTimeKeys) { serverRequests.push({ type: serverRequestTypes.MORE_ONE_TIME_KEYS }); } })(); const { activityUpdateResult } = await promiseAll({ all: Promise.all(promises), activityUpdateResult: activityUpdatePromise, checkOneTimeKeysPromise, }); if ( !stateCheckStatus && viewer.loggedIn && viewer.sessionLastValidated + sessionCheckFrequency < Date.now() ) { stateCheckStatus = { status: 'state_check' }; } if (viewerMissingPlatform) { serverRequests.push({ type: serverRequestTypes.PLATFORM }); } if (viewerMissingPlatformDetails) { serverRequests.push({ type: serverRequestTypes.PLATFORM_DETAILS }); } return { serverRequests, stateCheckStatus, activityUpdateResult }; } async function recordThreadInconsistency( viewer: Viewer, response: ThreadInconsistencyClientResponse, ): Promise { const { type, ...rest } = response; const reportCreationRequest = ({ ...rest, type: reportTypes.THREAD_INCONSISTENCY, }: ThreadInconsistencyReportCreationRequest); await createReport(viewer, reportCreationRequest); } async function recordEntryInconsistency( viewer: Viewer, response: EntryInconsistencyClientResponse, ): Promise { const { type, ...rest } = response; const reportCreationRequest = ({ ...rest, type: reportTypes.ENTRY_INCONSISTENCY, }: EntryInconsistencyReportCreationRequest); await createReport(viewer, reportCreationRequest); } type SessionInitializationResult = | { sessionContinued: false } | { sessionContinued: true, deltaEntryInfoResult: DeltaEntryInfosResponse, sessionUpdate: SessionUpdate, }; async function initializeSession( viewer: Viewer, calendarQuery: CalendarQuery, oldLastUpdate: number, ): Promise { if (!viewer.loggedIn) { return { sessionContinued: false }; } if (!viewer.hasSessionInfo) { // If the viewer has no session info but is logged in, that is indicative // of an expired / invalidated session and we should generate a new one await setNewSession(viewer, calendarQuery, oldLastUpdate); return { sessionContinued: false }; } if (oldLastUpdate < viewer.sessionLastUpdated) { // If the client has an older last_update than the server is tracking for // that client, then the client either had some issue persisting its store, // or the user restored the client app from a backup. Either way, we should // invalidate the existing session, since the server has assumed that the // checkpoint is further along than it is on the client, and might not still // have all of the updates necessary to do an incremental update await setNewSession(viewer, calendarQuery, oldLastUpdate); return { sessionContinued: false }; } let comparisonResult = null; try { comparisonResult = compareNewCalendarQuery(viewer, calendarQuery); } catch (e) { if (e.message !== 'unknown_error') { throw e; } } if (comparisonResult) { const { difference, oldCalendarQuery } = comparisonResult; const sessionUpdate = { ...comparisonResult.sessionUpdate, lastUpdate: oldLastUpdate, }; const deltaEntryInfoResult = await fetchEntriesForSession( viewer, difference, oldCalendarQuery, ); return { sessionContinued: true, deltaEntryInfoResult, sessionUpdate }; } else { await setNewSession(viewer, calendarQuery, oldLastUpdate); return { sessionContinued: false }; } } type StateCheckResult = { sessionUpdate?: SessionUpdate, checkStateRequest?: ServerCheckStateServerRequest, }; async function checkState( viewer: Viewer, status: StateCheckStatus, calendarQuery: CalendarQuery, ): Promise { const shouldCheckUserInfos = hasMinCodeVersion(viewer.platformDetails, 59); if (status.status === 'state_validated') { return { sessionUpdate: { lastValidated: Date.now() } }; } else if (status.status === 'state_check') { const promises = { threadsResult: fetchThreadInfos(viewer), entriesResult: fetchEntryInfos(viewer, [calendarQuery]), currentUserInfo: fetchCurrentUserInfo(viewer), userInfosResult: undefined, }; if (shouldCheckUserInfos) { promises.userInfosResult = fetchKnownUserInfos(viewer); } const fetchedData = await promiseAll(promises); let hashesToCheck = { threadInfos: hash(fetchedData.threadsResult.threadInfos), entryInfos: hash( serverEntryInfosObject(fetchedData.entriesResult.rawEntryInfos), ), currentUserInfo: hash(fetchedData.currentUserInfo), }; if (shouldCheckUserInfos) { hashesToCheck = { ...hashesToCheck, userInfos: hash(fetchedData.userInfosResult), }; } const checkStateRequest = { type: serverRequestTypes.CHECK_STATE, hashesToCheck, }; return { checkStateRequest }; } const { invalidKeys } = status; let fetchAllThreads = false, fetchAllEntries = false, fetchAllUserInfos = false, fetchUserInfo = false; const threadIDsToFetch = [], entryIDsToFetch = [], userIDsToFetch = []; for (const key of invalidKeys) { if (key === 'threadInfos') { fetchAllThreads = true; } else if (key === 'entryInfos') { fetchAllEntries = true; } else if (key === 'userInfos') { fetchAllUserInfos = true; } else if (key === 'currentUserInfo') { fetchUserInfo = true; } else if (key.startsWith('threadInfo|')) { const [, threadID] = key.split('|'); threadIDsToFetch.push(threadID); } else if (key.startsWith('entryInfo|')) { const [, entryID] = key.split('|'); entryIDsToFetch.push(entryID); } else if (key.startsWith('userInfo|')) { const [, userID] = key.split('|'); userIDsToFetch.push(userID); } } const fetchPromises = {}; if (fetchAllThreads) { fetchPromises.threadsResult = fetchThreadInfos(viewer); } else if (threadIDsToFetch.length > 0) { fetchPromises.threadsResult = fetchThreadInfos( viewer, SQL`t.id IN (${threadIDsToFetch})`, ); } if (fetchAllEntries) { fetchPromises.entriesResult = fetchEntryInfos(viewer, [calendarQuery]); } else if (entryIDsToFetch.length > 0) { fetchPromises.entryInfos = fetchEntryInfosByID(viewer, entryIDsToFetch); } if (fetchAllUserInfos) { fetchPromises.userInfos = fetchKnownUserInfos(viewer); } else if (userIDsToFetch.length > 0) { fetchPromises.userInfos = fetchKnownUserInfos(viewer, userIDsToFetch); } if (fetchUserInfo) { fetchPromises.currentUserInfo = fetchCurrentUserInfo(viewer); } const fetchedData = await promiseAll(fetchPromises); const hashesToCheck = {}, failUnmentioned = {}, stateChanges = {}; for (const key of invalidKeys) { if (key === 'threadInfos') { // Instead of returning all threadInfos, we want to narrow down and figure // out which threadInfos don't match first const { threadInfos } = fetchedData.threadsResult; for (const threadID in threadInfos) { hashesToCheck[`threadInfo|${threadID}`] = hash(threadInfos[threadID]); } failUnmentioned.threadInfos = true; } else if (key === 'entryInfos') { // Instead of returning all entryInfos, we want to narrow down and figure // out which entryInfos don't match first const { rawEntryInfos } = fetchedData.entriesResult; for (const rawEntryInfo of rawEntryInfos) { const entryInfo = serverEntryInfo(rawEntryInfo); invariant(entryInfo, 'should be set'); const { id: entryID } = entryInfo; invariant(entryID, 'should be set'); hashesToCheck[`entryInfo|${entryID}`] = hash(entryInfo); } failUnmentioned.entryInfos = true; } else if (key === 'userInfos') { // Instead of returning all userInfos, we want to narrow down and figure // out which userInfos don't match first const { userInfos } = fetchedData; for (const userID in userInfos) { hashesToCheck[`userInfo|${userID}`] = hash(userInfos[userID]); } failUnmentioned.userInfos = true; } else if (key === 'currentUserInfo') { stateChanges.currentUserInfo = fetchedData.currentUserInfo; } else if (key.startsWith('threadInfo|')) { const [, threadID] = key.split('|'); const { threadInfos } = fetchedData.threadsResult; const threadInfo = threadInfos[threadID]; if (!threadInfo) { if (!stateChanges.deleteThreadIDs) { stateChanges.deleteThreadIDs = []; } stateChanges.deleteThreadIDs.push(threadID); continue; } if (!stateChanges.rawThreadInfos) { stateChanges.rawThreadInfos = []; } stateChanges.rawThreadInfos.push(threadInfo); } else if (key.startsWith('entryInfo|')) { const [, entryID] = key.split('|'); const rawEntryInfos = fetchedData.entriesResult ? fetchedData.entriesResult.rawEntryInfos : fetchedData.entryInfos; const entryInfo = rawEntryInfos.find( candidate => candidate.id === entryID, ); if (!entryInfo) { if (!stateChanges.deleteEntryIDs) { stateChanges.deleteEntryIDs = []; } stateChanges.deleteEntryIDs.push(entryID); continue; } if (!stateChanges.rawEntryInfos) { stateChanges.rawEntryInfos = []; } stateChanges.rawEntryInfos.push(entryInfo); } else if (key.startsWith('userInfo|')) { const { userInfos: fetchedUserInfos } = fetchedData; const [, userID] = key.split('|'); const userInfo = fetchedUserInfos[userID]; if (!userInfo || !userInfo.username) { if (!stateChanges.deleteUserInfoIDs) { stateChanges.deleteUserInfoIDs = []; } stateChanges.deleteUserInfoIDs.push(userID); } else { if (!stateChanges.userInfos) { stateChanges.userInfos = []; } stateChanges.userInfos.push({ ...userInfo, // Flow gets confused if we don't do this username: userInfo.username, }); } } } if (!shouldCheckUserInfos) { const userIDs = new Set(); if (stateChanges.rawThreadInfos) { for (const threadInfo of stateChanges.rawThreadInfos) { for (const userID of usersInThreadInfo(threadInfo)) { userIDs.add(userID); } } } if (stateChanges.rawEntryInfos) { for (const userID of usersInRawEntryInfos(stateChanges.rawEntryInfos)) { userIDs.add(userID); } } const userInfos = []; if (userIDs.size > 0) { const fetchedUserInfos = await fetchUserInfos([...userIDs]); for (const userID in fetchedUserInfos) { const userInfo = fetchedUserInfos[userID]; if (userInfo && userInfo.username) { const { id, username } = userInfo; userInfos.push({ id, username }); } } } if (userInfos.length > 0) { stateChanges.userInfos = userInfos; } } const checkStateRequest = { type: serverRequestTypes.CHECK_STATE, hashesToCheck, failUnmentioned, stateChanges, }; if (Object.keys(hashesToCheck).length === 0) { return { checkStateRequest, sessionUpdate: { lastValidated: Date.now() } }; } else { return { checkStateRequest }; } } export { clientResponseInputValidator, processClientResponses, initializeSession, checkState, }; diff --git a/lib/flow-typed/npm/tcomb_v3.x.x.js b/lib/flow-typed/npm/tcomb_v3.x.x.js index b3ceb1536..7bc387e85 100644 --- a/lib/flow-typed/npm/tcomb_v3.x.x.js +++ b/lib/flow-typed/npm/tcomb_v3.x.x.js @@ -1,533 +1,139 @@ // flow-typed signature: f712ee1961974c799331608650bc7eb2 // flow-typed version: <>/tcomb_v3.2.29/flow_v0.137.0 declare module 'tcomb' { - declare type TBaseMeta = { - +kind: string, - +name: string, - +identity: boolean, - ... - }; - - declare export class TType { + declare class TBaseType<+T> { (val: T): this; is(val: mixed): boolean; displayName: string; - meta: M; - t: T; + +t: T; } - declare export class TIrreducible extends TType boolean, - |}> { } + declare export type TType<+T> = + | TIrreducible + | TMaybe + | TList + | TDict + | TUnion + | TEnums + | TRefinement + | TInterface; + + declare export class TIrreducible<+T> extends TBaseType { + meta: { + +kind: 'irreducible', + +name: string, + +identity: boolean, + +predicate: mixed => boolean, + }; + } + + declare export class TMaybe<+T> extends TBaseType { + meta: { + +kind: 'maybe', + +name: string, + +identity: boolean, + +type: TType, + }; + } - declare export class TMaybe extends TType, - |}> { } + declare export class TList<+T> extends TBaseType { + meta: { + +kind: 'list', + +name: string, + +identity: boolean, + +type: TType, + }; + } - declare export class TList extends TType, {| - +kind: string, - +name: string, - +identity: boolean, - +type: TType, - |}> { } + declare export class TDict<+T> extends TBaseType { + meta: { + +kind: 'dict', + +name: string, + +identity: boolean, + +domain: TType, + +codomain: TType, + }; + } - declare export class TDict extends TType<{ [key: string]: T }, {| - +kind: string, - +name: string, - +identity: boolean, - +domain: TType, - +codomain: T, - |}> { } + declare export class TUnion<+T> extends TBaseType { + meta: { + +kind: 'union', + +name: string, + +identity: boolean, + +types: Array>, + }; + } - declare export class TUnion extends TType>, - |}> { } + declare export class TEnums extends TBaseType { + meta: { + +kind: 'enums', + +name: string, + +identity: boolean, + +map: Object, + }; + } - declare export class TEnums extends TType { } + declare export class TRefinement<+T> extends TBaseType { + meta: { + +kind: 'subtype', + +name: string, + +identity: boolean, + +type: TType, + +predicate: mixed => boolean, + }; + } - declare export class TRefinement extends TType, - +predicate: mixed => boolean, - |}> { } + declare type TypeToValidator = (v: V) => TType; - declare export type TStructProps = { [key: string]: TType }; - declare type TStructOptions = {| + declare export type TStructProps<+T> = $ObjMap; + declare type TStructOptions = { name?: string, strict?: boolean, defaultProps?: Object, - |}; - declare export class TInterface extends TType { } + }; + + declare export class TInterface<+T> extends TBaseType { + meta: { + +kind: 'interface', + +name: string, + +identity: boolean, + +props: TStructProps, + +strict: boolean, + }; + } declare export default { + +Nil: TIrreducible, +Bool: TIrreducible, +Boolean: TIrreducible, +String: TIrreducible, +Number: TIrreducible, +Object: TIrreducible, - maybe(type: TType, name?: string): TMaybe, - list(type: TType, name?: string): TList, - dict( - domain: TType, - codomain: TType, + maybe(type: TType, name?: string): TMaybe, + list(type: TType, name?: string): TList>, + dict( + domain: TType, + codomain: TType, name?: string, - ): TDict, - union(types: Array>, name?: string): TUnion, - +enums: {| + ): TDict<{ [key: string]: T }>, + union<+T>(types: $ReadOnlyArray>, name?: string): TUnion, + +enums: { of(enums: $ReadOnlyArray, name?: string): TEnums, - |}, - irreducible(name: string, predicate: mixed => boolean): TIrreducible, - refinement( - type: TType, - predicate: T => boolean, + }, + irreducible( + name: string, + predicate: (mixed) => boolean, + ): TIrreducible, + refinement( + type: TType, + predicate: (T) => boolean, name?: string, ): TRefinement, - interface( - props: TStructProps, + interface( + props: TStructProps, options?: string | TStructOptions, - ): TInterface, + ): TInterface, ... }; } - -/** - * We include stubs for each file inside this npm package in case you need to - * require those files directly. Feel free to delete any files that aren't - * needed. - */ -declare module 'tcomb/lib/Any' { - declare module.exports: any; -} - -declare module 'tcomb/lib/Array' { - declare module.exports: any; -} - -declare module 'tcomb/lib/assert' { - declare module.exports: any; -} - -declare module 'tcomb/lib/assign' { - declare module.exports: any; -} - -declare module 'tcomb/lib/Boolean' { - declare module.exports: any; -} - -declare module 'tcomb/lib/create' { - declare module.exports: any; -} - -declare module 'tcomb/lib/Date' { - declare module.exports: any; -} - -declare module 'tcomb/lib/declare' { - declare module.exports: any; -} - -declare module 'tcomb/lib/decompose' { - declare module.exports: any; -} - -declare module 'tcomb/lib/dict' { - declare module.exports: any; -} - -declare module 'tcomb/lib/enums' { - declare module.exports: any; -} - -declare module 'tcomb/lib/Error' { - declare module.exports: any; -} - -declare module 'tcomb/lib/extend' { - declare module.exports: any; -} - -declare module 'tcomb/lib/fail' { - declare module.exports: any; -} - -declare module 'tcomb/lib/forbidNewOperator' { - declare module.exports: any; -} - -declare module 'tcomb/lib/fromJSON' { - declare module.exports: any; -} - -declare module 'tcomb/lib/func' { - declare module.exports: any; -} - -declare module 'tcomb/lib/Function' { - declare module.exports: any; -} - -declare module 'tcomb/lib/getDefaultInterfaceName' { - declare module.exports: any; -} - -declare module 'tcomb/lib/getFunctionName' { - declare module.exports: any; -} - -declare module 'tcomb/lib/getTypeName' { - declare module.exports: any; -} - -declare module 'tcomb/lib/installTypeFormatter' { - declare module.exports: any; -} - -declare module 'tcomb/lib/Integer' { - declare module.exports: any; -} - -declare module 'tcomb/lib/interface' { - declare module.exports: any; -} - -declare module 'tcomb/lib/intersection' { - declare module.exports: any; -} - -declare module 'tcomb/lib/irreducible' { - declare module.exports: any; -} - -declare module 'tcomb/lib/is' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isArray' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isBoolean' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isFunction' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isIdentity' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isInterface' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isMaybe' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isNil' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isNumber' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isObject' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isString' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isStruct' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isSubsetOf' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isType' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isTypeName' { - declare module.exports: any; -} - -declare module 'tcomb/lib/isUnion' { - declare module.exports: any; -} - -declare module 'tcomb/lib/list' { - declare module.exports: any; -} - -declare module 'tcomb/lib/match' { - declare module.exports: any; -} - -declare module 'tcomb/lib/maybe' { - declare module.exports: any; -} - -declare module 'tcomb/lib/mixin' { - declare module.exports: any; -} - -declare module 'tcomb/lib/Nil' { - declare module.exports: any; -} - -declare module 'tcomb/lib/Number' { - declare module.exports: any; -} - -declare module 'tcomb/lib/Object' { - declare module.exports: any; -} - -declare module 'tcomb/lib/refinement' { - declare module.exports: any; -} - -declare module 'tcomb/lib/RegExp' { - declare module.exports: any; -} - -declare module 'tcomb/lib/String' { - declare module.exports: any; -} - -declare module 'tcomb/lib/stringify' { - declare module.exports: any; -} - -declare module 'tcomb/lib/struct' { - declare module.exports: any; -} - -declare module 'tcomb/lib/tuple' { - declare module.exports: any; -} - -declare module 'tcomb/lib/Type' { - declare module.exports: any; -} - -declare module 'tcomb/lib/union' { - declare module.exports: any; -} - -declare module 'tcomb/lib/update' { - declare module.exports: any; -} - -// Filename aliases -declare module 'tcomb/index' { - declare module.exports: $Exports<'tcomb'>; -} -declare module 'tcomb/index.js' { - declare module.exports: $Exports<'tcomb'>; -} -declare module 'tcomb/lib/Any.js' { - declare module.exports: $Exports<'tcomb/lib/Any'>; -} -declare module 'tcomb/lib/Array.js' { - declare module.exports: $Exports<'tcomb/lib/Array'>; -} -declare module 'tcomb/lib/assert.js' { - declare module.exports: $Exports<'tcomb/lib/assert'>; -} -declare module 'tcomb/lib/assign.js' { - declare module.exports: $Exports<'tcomb/lib/assign'>; -} -declare module 'tcomb/lib/Boolean.js' { - declare module.exports: $Exports<'tcomb/lib/Boolean'>; -} -declare module 'tcomb/lib/create.js' { - declare module.exports: $Exports<'tcomb/lib/create'>; -} -declare module 'tcomb/lib/Date.js' { - declare module.exports: $Exports<'tcomb/lib/Date'>; -} -declare module 'tcomb/lib/declare.js' { - declare module.exports: $Exports<'tcomb/lib/declare'>; -} -declare module 'tcomb/lib/decompose.js' { - declare module.exports: $Exports<'tcomb/lib/decompose'>; -} -declare module 'tcomb/lib/dict.js' { - declare module.exports: $Exports<'tcomb/lib/dict'>; -} -declare module 'tcomb/lib/enums.js' { - declare module.exports: $Exports<'tcomb/lib/enums'>; -} -declare module 'tcomb/lib/Error.js' { - declare module.exports: $Exports<'tcomb/lib/Error'>; -} -declare module 'tcomb/lib/extend.js' { - declare module.exports: $Exports<'tcomb/lib/extend'>; -} -declare module 'tcomb/lib/fail.js' { - declare module.exports: $Exports<'tcomb/lib/fail'>; -} -declare module 'tcomb/lib/forbidNewOperator.js' { - declare module.exports: $Exports<'tcomb/lib/forbidNewOperator'>; -} -declare module 'tcomb/lib/fromJSON.js' { - declare module.exports: $Exports<'tcomb/lib/fromJSON'>; -} -declare module 'tcomb/lib/func.js' { - declare module.exports: $Exports<'tcomb/lib/func'>; -} -declare module 'tcomb/lib/Function.js' { - declare module.exports: $Exports<'tcomb/lib/Function'>; -} -declare module 'tcomb/lib/getDefaultInterfaceName.js' { - declare module.exports: $Exports<'tcomb/lib/getDefaultInterfaceName'>; -} -declare module 'tcomb/lib/getFunctionName.js' { - declare module.exports: $Exports<'tcomb/lib/getFunctionName'>; -} -declare module 'tcomb/lib/getTypeName.js' { - declare module.exports: $Exports<'tcomb/lib/getTypeName'>; -} -declare module 'tcomb/lib/installTypeFormatter.js' { - declare module.exports: $Exports<'tcomb/lib/installTypeFormatter'>; -} -declare module 'tcomb/lib/Integer.js' { - declare module.exports: $Exports<'tcomb/lib/Integer'>; -} -declare module 'tcomb/lib/interface.js' { - declare module.exports: $Exports<'tcomb/lib/interface'>; -} -declare module 'tcomb/lib/intersection.js' { - declare module.exports: $Exports<'tcomb/lib/intersection'>; -} -declare module 'tcomb/lib/irreducible.js' { - declare module.exports: $Exports<'tcomb/lib/irreducible'>; -} -declare module 'tcomb/lib/is.js' { - declare module.exports: $Exports<'tcomb/lib/is'>; -} -declare module 'tcomb/lib/isArray.js' { - declare module.exports: $Exports<'tcomb/lib/isArray'>; -} -declare module 'tcomb/lib/isBoolean.js' { - declare module.exports: $Exports<'tcomb/lib/isBoolean'>; -} -declare module 'tcomb/lib/isFunction.js' { - declare module.exports: $Exports<'tcomb/lib/isFunction'>; -} -declare module 'tcomb/lib/isIdentity.js' { - declare module.exports: $Exports<'tcomb/lib/isIdentity'>; -} -declare module 'tcomb/lib/isInterface.js' { - declare module.exports: $Exports<'tcomb/lib/isInterface'>; -} -declare module 'tcomb/lib/isMaybe.js' { - declare module.exports: $Exports<'tcomb/lib/isMaybe'>; -} -declare module 'tcomb/lib/isNil.js' { - declare module.exports: $Exports<'tcomb/lib/isNil'>; -} -declare module 'tcomb/lib/isNumber.js' { - declare module.exports: $Exports<'tcomb/lib/isNumber'>; -} -declare module 'tcomb/lib/isObject.js' { - declare module.exports: $Exports<'tcomb/lib/isObject'>; -} -declare module 'tcomb/lib/isString.js' { - declare module.exports: $Exports<'tcomb/lib/isString'>; -} -declare module 'tcomb/lib/isStruct.js' { - declare module.exports: $Exports<'tcomb/lib/isStruct'>; -} -declare module 'tcomb/lib/isSubsetOf.js' { - declare module.exports: $Exports<'tcomb/lib/isSubsetOf'>; -} -declare module 'tcomb/lib/isType.js' { - declare module.exports: $Exports<'tcomb/lib/isType'>; -} -declare module 'tcomb/lib/isTypeName.js' { - declare module.exports: $Exports<'tcomb/lib/isTypeName'>; -} -declare module 'tcomb/lib/isUnion.js' { - declare module.exports: $Exports<'tcomb/lib/isUnion'>; -} -declare module 'tcomb/lib/list.js' { - declare module.exports: $Exports<'tcomb/lib/list'>; -} -declare module 'tcomb/lib/match.js' { - declare module.exports: $Exports<'tcomb/lib/match'>; -} -declare module 'tcomb/lib/maybe.js' { - declare module.exports: $Exports<'tcomb/lib/maybe'>; -} -declare module 'tcomb/lib/mixin.js' { - declare module.exports: $Exports<'tcomb/lib/mixin'>; -} -declare module 'tcomb/lib/Nil.js' { - declare module.exports: $Exports<'tcomb/lib/Nil'>; -} -declare module 'tcomb/lib/Number.js' { - declare module.exports: $Exports<'tcomb/lib/Number'>; -} -declare module 'tcomb/lib/Object.js' { - declare module.exports: $Exports<'tcomb/lib/Object'>; -} -declare module 'tcomb/lib/refinement.js' { - declare module.exports: $Exports<'tcomb/lib/refinement'>; -} -declare module 'tcomb/lib/RegExp.js' { - declare module.exports: $Exports<'tcomb/lib/RegExp'>; -} -declare module 'tcomb/lib/String.js' { - declare module.exports: $Exports<'tcomb/lib/String'>; -} -declare module 'tcomb/lib/stringify.js' { - declare module.exports: $Exports<'tcomb/lib/stringify'>; -} -declare module 'tcomb/lib/struct.js' { - declare module.exports: $Exports<'tcomb/lib/struct'>; -} -declare module 'tcomb/lib/tuple.js' { - declare module.exports: $Exports<'tcomb/lib/tuple'>; -} -declare module 'tcomb/lib/Type.js' { - declare module.exports: $Exports<'tcomb/lib/Type'>; -} -declare module 'tcomb/lib/union.js' { - declare module.exports: $Exports<'tcomb/lib/union'>; -} -declare module 'tcomb/lib/update.js' { - declare module.exports: $Exports<'tcomb/lib/update'>; -} diff --git a/lib/types/messages/media.js b/lib/types/messages/media.js index 28e2d9d5e..3398fda18 100644 --- a/lib/types/messages/media.js +++ b/lib/types/messages/media.js @@ -1,79 +1,81 @@ // @flow import type { Media } from '../media-types.js'; import type { RelativeUserInfo } from '../user-types.js'; type MediaSharedBase = { +type: 15, +localID?: string, // for optimistic creations. included by new clients +threadID: string, +creatorID: string, +time: number, +media: $ReadOnlyArray, }; export type MediaMessageData = { ...MediaSharedBase, +sidebarCreation?: boolean, }; export type RawMediaMessageInfo = { ...MediaSharedBase, +id?: string, // null if local copy without ID yet }; export type MediaMessageInfo = { +type: 15, +id?: string, // null if local copy without ID yet +localID?: string, // for optimistic creations +threadID: string, +creator: RelativeUserInfo, +time: number, // millisecond timestamp +media: $ReadOnlyArray, }; +export type PhotoMessageServerDBContent = { + +type: 'photo', + +uploadID: string, +}; +export type VideoMessageServerDBContent = { + +type: 'video', + +uploadID: string, + +thumbnailUploadID: string, +}; export type MediaMessageServerDBContent = - | { - +type: 'photo', - +uploadID: string, - } - | { - +type: 'video', - +uploadID: string, - +thumbnailUploadID: string, - }; + | PhotoMessageServerDBContent + | VideoMessageServerDBContent; function getUploadIDsFromMediaMessageServerDBContents( mediaMessageContents: $ReadOnlyArray, ): $ReadOnlyArray { const uploadIDs: string[] = []; for (const mediaContent of mediaMessageContents) { uploadIDs.push(mediaContent.uploadID); if (mediaContent.type === 'video') { uploadIDs.push(mediaContent.thumbnailUploadID); } } return uploadIDs; } function getMediaMessageServerDBContentsFromMedia( media: $ReadOnlyArray, ): $ReadOnlyArray { return media.map(m => { if (m.type === 'photo' || m.type === 'encrypted_photo') { return { type: 'photo', uploadID: m.id }; } else if (m.type === 'video' || m.type === 'encrypted_video') { return { type: 'video', uploadID: m.id, thumbnailUploadID: m.thumbnailID, }; } throw new Error(`Unexpected media type: ${m.type}`); }); } export { getUploadIDsFromMediaMessageServerDBContents, getMediaMessageServerDBContentsFromMedia, }; diff --git a/lib/utils/avatar-utils.js b/lib/utils/avatar-utils.js index 0a42af732..7c1bf7fa0 100644 --- a/lib/utils/avatar-utils.js +++ b/lib/utils/avatar-utils.js @@ -1,42 +1,51 @@ // @flow import t from 'tcomb'; import type { TUnion, TInterface } from 'tcomb'; import { tRegex, tShape, tString } from './validation-utils.js'; import { validHexColorRegex } from '../shared/account-utils.js'; import { onlyOneEmojiRegex } from '../shared/emojis.js'; - -const emojiAvatarDBContentValidator: TInterface = tShape({ +import type { + ENSAvatarDBContent, + EmojiAvatarDBContent, + ImageAvatarDBContent, + UpdateUserAvatarRemoveRequest, + UpdateUserAvatarRequest, +} from '../types/avatar-types'; + +const emojiAvatarDBContentValidator: TInterface = tShape({ type: tString('emoji'), emoji: tRegex(onlyOneEmojiRegex), color: tRegex(validHexColorRegex), }); -const imageAvatarDBContentValidator: TInterface = tShape({ +const imageAvatarDBContentValidator: TInterface = tShape({ type: tString('image'), uploadID: t.String, }); -const ensAvatarDBContentValidator: TInterface = tShape({ +const ensAvatarDBContentValidator: TInterface = tShape({ type: tString('ens'), }); -const updateUserAvatarRemoveRequestValidator: TInterface = tShape({ - type: tString('remove'), -}); - -const updateUserAvatarRequestValidator: TUnion = t.union([ - emojiAvatarDBContentValidator, - imageAvatarDBContentValidator, - ensAvatarDBContentValidator, - updateUserAvatarRemoveRequestValidator, -]); +const updateUserAvatarRemoveRequestValidator: TInterface = + tShape({ + type: tString('remove'), + }); + +const updateUserAvatarRequestValidator: TUnion = + t.union([ + emojiAvatarDBContentValidator, + imageAvatarDBContentValidator, + ensAvatarDBContentValidator, + updateUserAvatarRemoveRequestValidator, + ]); export { emojiAvatarDBContentValidator, imageAvatarDBContentValidator, ensAvatarDBContentValidator, updateUserAvatarRemoveRequestValidator, updateUserAvatarRequestValidator, }; diff --git a/lib/utils/crypto-utils.js b/lib/utils/crypto-utils.js index 78bfae7db..e519b4549 100644 --- a/lib/utils/crypto-utils.js +++ b/lib/utils/crypto-utils.js @@ -1,30 +1,36 @@ // @flow import t from 'tcomb'; import { type TInterface } from 'tcomb'; import { primaryIdentityPublicKeyRegex } from './siwe-utils.js'; import { tRegex, tShape } from './validation-utils.js'; +import type { + IdentityKeysBlob, + OLMIdentityKeys, + SignedIdentityKeysBlob, +} from '../types/crypto-types'; const minimumOneTimeKeysRequired = 10; -const signedIdentityKeysBlobValidator: TInterface = tShape({ - payload: t.String, - signature: t.String, -}); +const signedIdentityKeysBlobValidator: TInterface = + tShape({ + payload: t.String, + signature: t.String, + }); -const olmIdentityKeysValidator: TInterface = tShape({ +const olmIdentityKeysValidator: TInterface = tShape({ ed25519: tRegex(primaryIdentityPublicKeyRegex), curve25519: tRegex(primaryIdentityPublicKeyRegex), }); -const identityKeysBlobValidator: TInterface = tShape({ +const identityKeysBlobValidator: TInterface = tShape({ primaryIdentityPublicKeys: olmIdentityKeysValidator, notificationIdentityPublicKeys: olmIdentityKeysValidator, }); export { minimumOneTimeKeysRequired, signedIdentityKeysBlobValidator, identityKeysBlobValidator, }; diff --git a/lib/utils/validation-utils.js b/lib/utils/validation-utils.js index 79ce9699d..d0107108b 100644 --- a/lib/utils/validation-utils.js +++ b/lib/utils/validation-utils.js @@ -1,111 +1,117 @@ // @flow import t from 'tcomb'; import type { TStructProps, TIrreducible, TRefinement, TEnums, TInterface, TUnion, } from 'tcomb'; import { validEmailRegex, oldValidUsernameRegex, validHexColorRegex, } from '../shared/account-utils.js'; +import type { PlatformDetails } from '../types/device-types'; +import type { + MediaMessageServerDBContent, + PhotoMessageServerDBContent, + VideoMessageServerDBContent, +} from '../types/messages/media'; function tBool(value: boolean): TIrreducible { return t.irreducible('literal bool', x => x === value); } function tString(value: string): TIrreducible { return t.irreducible('literal string', x => x === value); } function tNumber(value: number): TIrreducible { return t.irreducible('literal number', x => x === value); } -function tShape(spec: TStructProps): TInterface { +function tShape(spec: TStructProps): TInterface { return t.interface(spec, { strict: true }); } type TRegex = TRefinement; function tRegex(regex: RegExp): TRegex { return t.refinement(t.String, val => regex.test(val)); } function tNumEnum(nums: $ReadOnlyArray): TRefinement { return t.refinement(t.Number, (input: number) => { for (const num of nums) { if (input === num) { return true; } } return false; }); } const tDate: TRegex = tRegex(/^[0-9]{4}-[0-1][0-9]-[0-3][0-9]$/); const tColor: TRegex = tRegex(validHexColorRegex); // we don't include # char const tPlatform: TEnums = t.enums.of([ 'ios', 'android', 'web', 'windows', 'macos', ]); const tDeviceType: TEnums = t.enums.of(['ios', 'android']); -const tPlatformDetails: TInterface = tShape({ +const tPlatformDetails: TInterface = tShape({ platform: tPlatform, codeVersion: t.maybe(t.Number), stateVersion: t.maybe(t.Number), }); const tPassword: TRefinement = t.refinement( t.String, (password: string) => !!password, ); const tCookie: TRegex = tRegex(/^(user|anonymous)=[0-9]+:[0-9a-f]+$/); const tEmail: TRegex = tRegex(validEmailRegex); const tOldValidUsername: TRegex = tRegex(oldValidUsernameRegex); const tID: TRefinement = t.refinement(t.String, (id: string) => !!id); -const tMediaMessagePhoto: TInterface = tShape({ +const tMediaMessagePhoto: TInterface = tShape({ type: tString('photo'), uploadID: t.String, }); -const tMediaMessageVideo: TInterface = tShape({ +const tMediaMessageVideo: TInterface = tShape({ type: tString('video'), uploadID: t.String, thumbnailUploadID: t.String, }); -const tMediaMessageMedia: TUnion = t.union([ +const tMediaMessageMedia: TUnion = t.union([ tMediaMessagePhoto, tMediaMessageVideo, ]); export { tBool, tString, tNumber, tShape, tRegex, tNumEnum, tDate, tColor, tPlatform, tDeviceType, tPlatformDetails, tPassword, tCookie, tEmail, tOldValidUsername, tID, tMediaMessagePhoto, tMediaMessageVideo, tMediaMessageMedia, };