diff --git a/keyserver/src/responders/handlers.js b/keyserver/src/responders/handlers.js --- a/keyserver/src/responders/handlers.js +++ b/keyserver/src/responders/handlers.js @@ -3,6 +3,10 @@ import type { $Response, $Request } from 'express'; import { ServerError } from 'lib/utils/errors.js'; +import { + assertWithValidator, + tPlatformDetails, +} from 'lib/utils/validation-utils.js'; import { getMessageForException } from './utils.js'; import { deleteCookie } from '../deleters/cookie-deleters.js'; @@ -13,6 +17,7 @@ fetchViewerForHomeRequest, addCookieToHomeResponse, createNewAnonymousCookie, + setCookiePlatformDetails, } from '../session/cookies.js'; import type { Viewer } from '../session/viewer.js'; import { @@ -44,9 +49,25 @@ if (!req.body || typeof req.body !== 'object') { throw new ServerError('invalid_parameters'); } - const { input } = req.body; + const { input, platformDetails } = req.body; viewer = await fetchViewerForJSONRequest(req); - await policiesValidator(viewer, responder.requiredPolicies); + + const promises = [policiesValidator(viewer, responder.requiredPolicies)]; + + if (platformDetails) { + if (!tPlatformDetails.is(platformDetails)) { + throw new ServerError('invalid_platform_details'); + } + promises.push( + setCookiePlatformDetails( + viewer, + assertWithValidator(platformDetails, tPlatformDetails), + ), + ); + } + + await Promise.all(promises); + const responderResult = await responder.responder(viewer, input); if (res.headersSent) { return; diff --git a/lib/selectors/server-calls.js b/lib/selectors/server-calls.js --- a/lib/selectors/server-calls.js +++ b/lib/selectors/server-calls.js @@ -2,6 +2,7 @@ import { createSelector } from 'reselect'; +import type { LastCommunicatedPlatformDetails } from '../types/device-types.js'; import type { AppState } from '../types/redux-types.js'; import { type ConnectionStatus } from '../types/socket-types.js'; import { type CurrentUserInfo } from '../types/user-types.js'; @@ -12,6 +13,7 @@ +sessionID: ?string, +currentUserInfo: ?CurrentUserInfo, +connectionStatus: ConnectionStatus, + +lastCommunicatedPlatformDetails: LastCommunicatedPlatformDetails, }; const serverCallStateSelector: (state: AppState) => ServerCallState = @@ -21,18 +23,21 @@ (state: AppState) => state.sessionID, (state: AppState) => state.currentUserInfo, (state: AppState) => state.connection.status, + (state: AppState) => state.lastCommunicatedPlatformDetails, ( cookie: ?string, urlPrefix: string, sessionID: ?string, currentUserInfo: ?CurrentUserInfo, connectionStatus: ConnectionStatus, + lastCommunicatedPlatformDetails: LastCommunicatedPlatformDetails, ) => ({ cookie, urlPrefix, sessionID, currentUserInfo, connectionStatus, + lastCommunicatedPlatformDetails, }), ); diff --git a/lib/socket/socket.react.js b/lib/socket/socket.react.js --- a/lib/socket/socket.react.js +++ b/lib/socket/socket.react.js @@ -1,6 +1,7 @@ // @flow import invariant from 'invariant'; +import _isEqual from 'lodash/fp/isEqual.js'; import _throttle from 'lodash/throttle.js'; import * as React from 'react'; @@ -13,6 +14,7 @@ import RequestResponseHandler from './request-response-handler.react.js'; import UpdateHandler from './update-handler.react.js'; import { updateActivityActionTypes } from '../actions/activity-actions.js'; +import { updateLastCommunicatedPlatformDetailsActionType } from '../actions/device-actions.js'; import { logOutActionTypes } from '../actions/user-actions.js'; import { unsupervisedBackgroundActionType } from '../reducers/lifecycle-state-reducer.js'; import { @@ -25,7 +27,7 @@ logInActionSources, type LogOutResult, } from '../types/account-types.js'; -import { isWebPlatform } from '../types/device-types.js'; +import { isWebPlatform, type PlatformDetails } from '../types/device-types.js'; import type { CalendarQuery } from '../types/entry-types.js'; import { forcePolicyAcknowledgmentActionType } from '../types/policy-types.js'; import type { Dispatch } from '../types/redux-types.js'; @@ -98,6 +100,7 @@ +frozen: boolean, +preRequestUserState: PreRequestUserState, +noDataAfterPolicyAcknowledgment?: boolean, + +lastCommunicatedPlatformDetails: ?PlatformDetails, // Redux dispatch functions +dispatch: Dispatch, +dispatchActionPromise: DispatchActionPromise, @@ -119,7 +122,6 @@ listeners: Set = new Set(); pingTimeoutID: ?TimeoutID; messageLastReceived: ?number; - initialPlatformDetailsSent: boolean = false; reopenConnectionAfterClosing: boolean = false; invalidationRecoveryInProgress: boolean = false; initializedWithUserState: ?PreRequestUserState; @@ -530,9 +532,12 @@ const messageID = this.nextClientMessageID++; const promises = {}; + const shouldSendinitialPlatformDetails = !_isEqual( + this.props.lastCommunicatedPlatformDetails, + )(getConfig().platformDetails); + const clientResponses = []; - if (!this.initialPlatformDetailsSent) { - this.initialPlatformDetailsSent = true; + if (shouldSendinitialPlatformDetails) { clientResponses.push({ type: serverRequestTypes.PLATFORM_DETAILS, platformDetails: getConfig().platformDetails, @@ -574,6 +579,13 @@ promises, ); + if (shouldSendinitialPlatformDetails) { + this.props.dispatch({ + type: updateLastCommunicatedPlatformDetailsActionType, + payload: { [this.props.urlPrefix]: getConfig().platformDetails }, + }); + } + if (activityUpdateMessage) { this.props.dispatch({ type: updateActivityActionTypes.success, diff --git a/lib/utils/action-utils.js b/lib/utils/action-utils.js --- a/lib/utils/action-utils.js +++ b/lib/utils/action-utils.js @@ -18,6 +18,7 @@ type LogInStartingPayload, type LogInResult, } from '../types/account-types.js'; +import type { LastCommunicatedPlatformDetails } from '../types/device-types.js'; import type { Endpoint, SocketAPIHandler } from '../types/endpoints.js'; import type { LoadingOptions, LoadingInfo } from '../types/loading-types.js'; import type { @@ -209,6 +210,7 @@ null, 'disconnected', null, + null, endpoint, data, dispatch, @@ -259,6 +261,7 @@ sessionID, currentUserInfo, connectionStatus, + lastCommunicatedPlatformDetails, } = params; const loggedIn = !!(currentUserInfo && !currentUserInfo.anonymous && true); const boundSetNewSession = ( @@ -351,6 +354,7 @@ urlPrefix, sessionID, connectionStatus, + lastCommunicatedPlatformDetails, socketAPIHandler, endpoint, data, @@ -368,6 +372,7 @@ +sessionID: ?string, +currentUserInfo: ?CurrentUserInfo, +connectionStatus: ConnectionStatus, + +lastCommunicatedPlatformDetails: LastCommunicatedPlatformDetails, }; // All server calls needs to include some information from the Redux state @@ -389,6 +394,7 @@ (state: BindServerCallsParams) => state.sessionID, (state: BindServerCallsParams) => state.currentUserInfo, (state: BindServerCallsParams) => state.connectionStatus, + (state: BindServerCallsParams) => state.lastCommunicatedPlatformDetails, ( dispatch: Dispatch, cookie: ?string, @@ -396,6 +402,7 @@ sessionID: ?string, currentUserInfo: ?CurrentUserInfo, connectionStatus: ConnectionStatus, + lastCommunicatedPlatformDetails: LastCommunicatedPlatformDetails, ) => { const boundCallServerEndpoint = bindCookieAndUtilsIntoCallServerEndpoint({ dispatch, @@ -404,6 +411,7 @@ sessionID, currentUserInfo, connectionStatus, + lastCommunicatedPlatformDetails, }); return actionFunc(boundCallServerEndpoint); }, diff --git a/lib/utils/call-server-endpoint.js b/lib/utils/call-server-endpoint.js --- a/lib/utils/call-server-endpoint.js +++ b/lib/utils/call-server-endpoint.js @@ -1,5 +1,7 @@ // @flow +import _isEqual from 'lodash/fp/isEqual.js'; + import { getConfig } from './config.js'; import { ServerError, @@ -9,8 +11,13 @@ } from './errors.js'; import sleep from './sleep.js'; import { uploadBlob, type UploadBlob } from './upload-blob.js'; +import { updateLastCommunicatedPlatformDetailsActionType } from '../actions/device-actions.js'; import { callServerEndpointTimeout } from '../shared/timeouts.js'; import type { Shape } from '../types/core.js'; +import type { + PlatformDetails, + LastCommunicatedPlatformDetails, +} from '../types/device-types.js'; import { type Endpoint, type SocketAPIHandler, @@ -68,6 +75,7 @@ input: { [key: string]: mixed }, cookie?: ?string, sessionID?: ?string, + platformDetails?: PlatformDetails, }; // If cookie is undefined, then we will defer to the underlying environment to @@ -87,6 +95,7 @@ urlPrefix: string, sessionID: ?string, connectionStatus: ConnectionStatus, + lastCommunicatedPlatformDetails: ?LastCommunicatedPlatformDetails, socketAPIHandler: ?SocketAPIHandler, endpoint: Endpoint, input: { [key: string]: mixed }, @@ -98,6 +107,12 @@ return await possibleReplacement(endpoint, input, options); } + const shouldSendPlatformDetails = + lastCommunicatedPlatformDetails && + !_isEqual(lastCommunicatedPlatformDetails[urlPrefix])( + getConfig().platformDetails, + ); + if ( endpointIsSocketPreferred(endpoint) && connectionStatus === 'connected' && @@ -151,6 +166,9 @@ // is not logged in on web. mergedData.sessionID = sessionID ? sessionID : null; } + if (shouldSendPlatformDetails) { + mergedData.platformDetails = getConfig().platformDetails; + } const callEndpointPromise = (async (): Promise => { const response = await fetch(url, { @@ -206,6 +224,15 @@ setNewSession(clientSessionChange, error); } + if (!error && shouldSendPlatformDetails) { + dispatch({ + type: updateLastCommunicatedPlatformDetailsActionType, + payload: { + [urlPrefix]: getConfig().platformDetails, + }, + }); + } + if (error === 'policies_not_accepted') { dispatch({ type: forcePolicyAcknowledgmentActionType, diff --git a/native/socket.react.js b/native/socket.react.js --- a/native/socket.react.js +++ b/native/socket.react.js @@ -90,6 +90,10 @@ return activeMessageListSelector(navContext); }, [active, navContext]); + const lastCommunicatedPlatformDetails = useSelector( + state => state.lastCommunicatedPlatformDetails[urlPrefix], + ); + const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); const callLogOut = useServerCall(logOut); @@ -150,6 +154,7 @@ getInitialNotificationsEncryptedMessage={ getInitialNotificationsEncryptedMessage } + lastCommunicatedPlatformDetails={lastCommunicatedPlatformDetails} /> ); }); diff --git a/web/socket.react.js b/web/socket.react.js --- a/web/socket.react.js +++ b/web/socket.react.js @@ -55,6 +55,10 @@ const dispatchActionPromise = useDispatchActionPromise(); const callLogOut = useServerCall(logOut); + const lastCommunicatedPlatformDetails = useSelector( + state => state.lastCommunicatedPlatformDetails[urlPrefix], + ); + return ( ); });