diff --git a/lib/actions/siwe-actions.js b/lib/actions/siwe-actions.js --- a/lib/actions/siwe-actions.js +++ b/lib/actions/siwe-actions.js @@ -12,6 +12,7 @@ CallServerEndpointOptions, } from '../utils/call-server-endpoint.js'; import { getConfig } from '../utils/config.js'; +import { ashoatKeyserverID } from '../utils/validation-utils.js'; const getSIWENonceActionTypes = Object.freeze({ started: 'GET_SIWE_NONCE_STARTED', @@ -40,11 +41,15 @@ ) => Promise) => async (siweAuthPayload, options) => { const watchedIDs = threadWatcher.getWatchedIDs(); + const deviceTokenUpdateRequest = + siweAuthPayload.deviceTokenUpdateRequest[ashoatKeyserverID]; + const response = await callServerEndpoint( 'siwe_auth', { ...siweAuthPayload, watchedIDs, + deviceTokenUpdateRequest, platformDetails: getConfig().platformDetails, }, { diff --git a/lib/actions/user-actions.js b/lib/actions/user-actions.js --- a/lib/actions/user-actions.js +++ b/lib/actions/user-actions.js @@ -121,10 +121,14 @@ options?: CallServerEndpointOptions, ) => Promise) => async (registerInfo, options) => { + const deviceTokenUpdateRequest = + registerInfo.deviceTokenUpdateRequest[ashoatKeyserverID]; + const response = await callServerEndpoint( 'create_account', { ...registerInfo, + deviceTokenUpdateRequest, platformDetails: getConfig().platformDetails, }, { @@ -169,10 +173,14 @@ const watchedIDs = threadWatcher.getWatchedIDs(); const { logInActionSource, ...restLogInInfo } = logInInfo; + const deviceTokenUpdateRequest = + logInInfo.deviceTokenUpdateRequest[ashoatKeyserverID]; + const response = await callServerEndpoint( 'log_in', { ...restLogInInfo, + deviceTokenUpdateRequest, source: logInActionSource, watchedIDs, platformDetails: getConfig().platformDetails, diff --git a/lib/reducers/keyserver-reducer.js b/lib/reducers/keyserver-reducer.js --- a/lib/reducers/keyserver-reducer.js +++ b/lib/reducers/keyserver-reducer.js @@ -1,6 +1,7 @@ // @flow import reduceConnectionInfo from './connection-reducer.js'; +import { reduceDeviceToken } from './device-token-reducer.js'; import reduceLastCommunicatedPlatformDetails from './last-communicated-platform-details-reducer.js'; import reduceUpdatesCurrentAsOf from './updates-reducer.js'; import { siweAuthActionTypes } from '../actions/siwe-actions.js'; @@ -99,10 +100,15 @@ state.keyserverInfos[ashoatKeyserverID].connection, action, ); + const deviceToken = reduceDeviceToken( + state.keyserverInfos[ashoatKeyserverID].deviceToken, + action, + ); if ( connection !== state.keyserverInfos[ashoatKeyserverID].connection || lastCommunicatedPlatformDetails !== - state.keyserverInfos[ashoatKeyserverID].lastCommunicatedPlatformDetails + state.keyserverInfos[ashoatKeyserverID].lastCommunicatedPlatformDetails || + deviceToken !== state.keyserverInfos[ashoatKeyserverID].deviceToken ) { state = { ...state, @@ -112,6 +118,7 @@ ...state.keyserverInfos[ashoatKeyserverID], connection, lastCommunicatedPlatformDetails, + deviceToken, }, }, }; diff --git a/lib/reducers/master-reducer.js b/lib/reducers/master-reducer.js --- a/lib/reducers/master-reducer.js +++ b/lib/reducers/master-reducer.js @@ -3,7 +3,6 @@ import reduceCalendarFilters from './calendar-filters-reducer.js'; import { reduceCalendarQuery } from './calendar-query-reducer.js'; import reduceDataLoaded from './data-loaded-reducer.js'; -import { reduceDeviceToken } from './device-token-reducer.js'; import { reduceDraftStore } from './draft-reducer.js'; import reduceEnabledApps from './enabled-apps-reducer.js'; import { reduceEntryInfos } from './entry-reducer.js'; @@ -147,7 +146,6 @@ nextLocalID: reduceNextLocalID(state.nextLocalID, action), dataLoaded: reduceDataLoaded(state.dataLoaded, action), userPolicies: policiesReducer(state.userPolicies, action), - deviceToken: reduceDeviceToken(state.deviceToken, action), commServicesAccessToken: reduceServicesAccessToken( state.commServicesAccessToken, action, diff --git a/lib/selectors/account-selectors.js b/lib/selectors/account-selectors.js --- a/lib/selectors/account-selectors.js +++ b/lib/selectors/account-selectors.js @@ -2,7 +2,11 @@ import { createSelector } from 'reselect'; -import { cookieSelector, sessionIDSelector } from './keyserver-selectors.js'; +import { + cookieSelector, + sessionIDSelector, + deviceTokensSelector, +} from './keyserver-selectors.js'; import { currentCalendarQuery } from './nav-selectors.js'; import type { LogInExtraInfo } from '../types/account-types.js'; import type { CalendarQuery } from '../types/entry-types.js'; @@ -13,15 +17,20 @@ const logInExtraInfoSelector: ( state: AppState, ) => (calendarActive: boolean) => LogInExtraInfo = createSelector( - (state: AppState) => state.deviceToken, + deviceTokensSelector, currentCalendarQuery, ( - deviceToken: ?string, + deviceTokens: { +[keyserverID: string]: ?string }, calendarQuery: (calendarActive: boolean) => CalendarQuery, ) => { - let deviceTokenUpdateRequest = null; - if (deviceToken) { - deviceTokenUpdateRequest = { deviceToken }; + const deviceTokenUpdateRequest = {}; + + for (const keyserverID in deviceTokens) { + if (deviceTokens[keyserverID]) { + deviceTokenUpdateRequest[keyserverID] = { + deviceToken: deviceTokens[keyserverID], + }; + } } // Return a function since we depend on the time of evaluation return (calendarActive: boolean): LogInExtraInfo => ({ diff --git a/lib/selectors/keyserver-selectors.js b/lib/selectors/keyserver-selectors.js --- a/lib/selectors/keyserver-selectors.js +++ b/lib/selectors/keyserver-selectors.js @@ -86,6 +86,28 @@ }, ); +const deviceTokensSelector: (state: AppState) => { + +[keyserverID: string]: ?string, +} = createSelector( + (state: AppState) => state.keyserverStore.keyserverInfos, + (infos: { +[key: string]: KeyserverInfo }) => { + const deviceTokens = {}; + for (const keyserverID in infos) { + deviceTokens[keyserverID] = infos[keyserverID].deviceToken; + } + return deviceTokens; + }, +); + +const baseDeviceTokenSelector: ( + keyserverID: string, +) => (state: AppState) => ?string = keyserverID => (state: AppState) => + state.keyserverStore.keyserverInfos[keyserverID]?.deviceToken; + +const deviceTokenSelector: ( + keyserverID: string, +) => (state: AppState) => ?string = _memoize(baseDeviceTokenSelector); + export { cookieSelector, cookiesSelector, @@ -95,5 +117,7 @@ urlPrefixSelector, connectionSelector, lastCommunicatedPlatformDetailsSelector, + deviceTokensSelector, + deviceTokenSelector, selectedKeyserversSelector, }; diff --git a/lib/types/account-types.js b/lib/types/account-types.js --- a/lib/types/account-types.js +++ b/lib/types/account-types.js @@ -48,6 +48,10 @@ +deviceToken: string, }; +type DeviceTokenUpdateInput = { + +[keyserverID: string]: DeviceTokenUpdateRequest, +}; + export type RegisterRequest = { +username: string, +email?: empty, @@ -103,7 +107,7 @@ export type LogInExtraInfo = { +calendarQuery: CalendarQuery, - +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest, + +deviceTokenUpdateRequest: DeviceTokenUpdateInput, +signedIdentityKeysBlob?: SignedIdentityKeysBlob, +initialNotificationsEncryptedMessage?: string, }; diff --git a/lib/types/keyserver-types.js b/lib/types/keyserver-types.js --- a/lib/types/keyserver-types.js +++ b/lib/types/keyserver-types.js @@ -14,6 +14,7 @@ +urlPrefix: string, +connection: ConnectionInfo, +lastCommunicatedPlatformDetails: ?PlatformDetails, + +deviceToken: ?string, }; export type KeyserverInfos = { +[key: string]: KeyserverInfo }; @@ -40,6 +41,7 @@ urlPrefix: t.String, connection: connectionInfoValidator, lastCommunicatedPlatformDetails: t.maybe(tPlatformDetails), + deviceToken: t.maybe(t.String), }); export const keyserverStoreValidator: TInterface = diff --git a/lib/types/redux-types.js b/lib/types/redux-types.js --- a/lib/types/redux-types.js +++ b/lib/types/redux-types.js @@ -136,7 +136,6 @@ +nextLocalID: number, +dataLoaded: boolean, +userPolicies: UserPolicies, - +deviceToken: ?string, +commServicesAccessToken: ?string, +inviteLinksStore: InviteLinksStore, +keyserverStore: KeyserverStore, diff --git a/lib/utils/sanitization.js b/lib/utils/sanitization.js --- a/lib/utils/sanitization.js +++ b/lib/utils/sanitization.js @@ -297,15 +297,20 @@ ): AppState { const { keyserverInfos } = state.keyserverStore; const keyserverInfosCopy = Object.fromEntries( - Object.keys(keyserverInfos).map(key => [ - key, - keyserverInfos[key].cookie === undefined - ? keyserverInfos[key] - : { - ...keyserverInfos[key], - cookie: null, - }, - ]), + Object.keys(keyserverInfos).map(key => { + const cookie = + keyserverInfos[key].cookie === undefined ? undefined : null; + const deviceToken = + keyserverInfos[key].deviceToken === undefined ? undefined : null; + return [ + key, + { + ...keyserverInfos[key], + cookie, + deviceToken, + }, + ]; + }), ); const keyserverStore = { ...state.keyserverStore, @@ -314,9 +319,6 @@ state = { ...state, keyserverStore }; - if (state.deviceToken !== undefined && state.deviceToken !== null) { - state = { ...state, deviceToken: null }; - } const stateCopy = clone(state); sanitizePII(stateCopy, redactionHelpers); return stateCopy; diff --git a/native/chat/settings/thread-settings-push-notifs.react.js b/native/chat/settings/thread-settings-push-notifs.react.js --- a/native/chat/settings/thread-settings-push-notifs.react.js +++ b/native/chat/settings/thread-settings-push-notifs.react.js @@ -8,13 +8,17 @@ updateSubscriptionActionTypes, useUpdateSubscription, } from 'lib/actions/user-actions.js'; +import { deviceTokenSelector } from 'lib/selectors/keyserver-selectors.js'; import type { SubscriptionUpdateRequest, SubscriptionUpdateResult, } from 'lib/types/subscription-types.js'; import { type ThreadInfo } from 'lib/types/thread-types.js'; import type { DispatchActionPromise } from 'lib/utils/action-utils.js'; -import { useDispatchActionPromise } from 'lib/utils/action-utils.js'; +import { + useDispatchActionPromise, + extractKeyserverIDFromID, +} from 'lib/utils/action-utils.js'; import SingleLine from '../../components/single-line.react.js'; import SWMansionIcon from '../../components/swmansion-icon.react.js'; @@ -171,12 +175,13 @@ React.memo(function ConnectedThreadSettingsPushNotifs( props: BaseProps, ) { + const keyserverID = extractKeyserverIDFromID(props.threadInfo.id); + const deviceToken = useSelector(deviceTokenSelector(keyserverID)); + const hasPushPermissions = + deviceToken !== null && deviceToken !== undefined; const styles = useStyles(unboundStyles); const dispatchActionPromise = useDispatchActionPromise(); const callUpdateSubscription = useUpdateSubscription(); - const hasPushPermissions = useSelector( - state => state.deviceToken !== null && state.deviceToken !== undefined, - ); return ( state.deviceToken); + const deviceTokens = useSelector(deviceTokensSelector); const threadInfos = useSelector(threadInfoSelector); const notifPermissionAlertInfo = useSelector( state => state.notifPermissionAlertInfo, @@ -663,7 +686,7 @@ {...props} activeThread={activeThread} unreadCount={boundUnreadCount} - deviceToken={deviceToken} + deviceTokens={deviceTokens} threadInfos={threadInfos} notifPermissionAlertInfo={notifPermissionAlertInfo} connection={connection} diff --git a/native/redux/default-state.js b/native/redux/default-state.js --- a/native/redux/default-state.js +++ b/native/redux/default-state.js @@ -42,7 +42,6 @@ storeLoaded: false, loadingStatuses: {}, calendarFilters: defaultCalendarFilters, - deviceToken: null, dataLoaded: false, customServer: natNodeServer, notifPermissionAlertInfo: defaultNotifPermissionAlertInfo, @@ -79,6 +78,7 @@ urlPrefix: defaultURLPrefix, connection: defaultConnectionInfo, lastCommunicatedPlatformDetails: null, + deviceToken: null, }, }, }, diff --git a/native/redux/persist.js b/native/redux/persist.js --- a/native/redux/persist.js +++ b/native/redux/persist.js @@ -853,6 +853,23 @@ }, } : state, + [56]: state => { + const { deviceToken, keyserverStore, ...rest } = state; + + return { + ...rest, + keyserverStore: { + ...keyserverStore, + keyserverInfos: { + ...keyserverStore.keyserverInfos, + [ashoatKeyserverID]: { + ...keyserverStore.keyserverInfos[ashoatKeyserverID], + deviceToken, + }, + }, + }, + }; + }, }; // After migration 31, we'll no longer want to persist `messageStore.messages` @@ -982,7 +999,7 @@ 'connection', ], debug: __DEV__, - version: 55, + version: 56, transforms: [ messageStoreMessagesBlocklistTransform, reportStoreTransform, diff --git a/native/redux/state-types.js b/native/redux/state-types.js --- a/native/redux/state-types.js +++ b/native/redux/state-types.js @@ -38,7 +38,6 @@ +storeLoaded: boolean, +loadingStatuses: { [key: string]: { [idx: number]: LoadingStatus } }, +calendarFilters: $ReadOnlyArray, - +deviceToken: ?string, +dataLoaded: boolean, +customServer: ?string, +notifPermissionAlertInfo: NotifPermissionAlertInfo, diff --git a/web/redux/default-state.js b/web/redux/default-state.js --- a/web/redux/default-state.js +++ b/web/redux/default-state.js @@ -48,7 +48,6 @@ windowDimensions: { width: window.width, height: window.height }, loadingStatuses: {}, calendarFilters: defaultCalendarFilters, - deviceToken: null, dataLoaded: false, notifPermissionAlertInfo: defaultNotifPermissionAlertInfo, watchedThreadIDs: [], @@ -83,6 +82,7 @@ urlPrefix: keyserverURL, connection: { ...defaultConnectionInfo }, lastCommunicatedPlatformDetails: null, + deviceToken: null, }, }, }, diff --git a/web/redux/redux-setup.js b/web/redux/redux-setup.js --- a/web/redux/redux-setup.js +++ b/web/redux/redux-setup.js @@ -80,7 +80,6 @@ +calendarFilters: $ReadOnlyArray, +communityPickerStore: CommunityPickerStore, +windowDimensions: WindowDimensions, - +deviceToken: ?string, +notifPermissionAlertInfo: NotifPermissionAlertInfo, +actualizedCalendarQuery: CalendarQuery, +watchedThreadIDs: $ReadOnlyArray,