diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js --- a/lib/types/identity-service-types.js +++ b/lib/types/identity-service-types.js @@ -1,6 +1,6 @@ // @flow -import t, { type TInterface } from 'tcomb'; +import t, { type TInterface, type TList } from 'tcomb'; import { identityKeysBlobValidator, @@ -128,6 +128,13 @@ // on native, publishing prekeys to Identity is called directly from C++, // there is no need to expose it to JS +publishWebPrekeys?: (prekeys: SignedPrekeys) => Promise; + +getDeviceListHistoryForUser: ( + userID: string, + sinceTimestamp?: number, + ) => Promise<$ReadOnlyArray>; + // updating device list is possible only on Native + // web cannot be a primary device, so there's no need to expose it to JS + +updateDeviceList?: (newDeviceList: SignedDeviceList) => Promise; } export type IdentityServiceAuthLayer = { @@ -159,6 +166,24 @@ +notifOneTimeKeys: $ReadOnlyArray, }; +// Device list types + +export type RawDeviceList = { + +devices: $ReadOnlyArray, + +timestamp: number, +}; + +export type SignedDeviceList = { + // JSON-stringified RawDeviceList + +rawDeviceList: string, +}; +export const signedDeviceListValidator: TInterface = + tShape({ + rawDeviceList: t.String, + }); +export const signedDeviceListHistoryValidator: TList> = + t.list(signedDeviceListValidator); + export const ONE_TIME_KEYS_NUMBER = 10; export const identityDeviceTypes = Object.freeze({ diff --git a/native/identity-service/identity-service-context-provider.react.js b/native/identity-service/identity-service-context-provider.react.js --- a/native/identity-service/identity-service-context-provider.react.js +++ b/native/identity-service/identity-service-context-provider.react.js @@ -10,6 +10,8 @@ type OneTimeKeysResultValues, } from 'lib/types/crypto-types.js'; import { + type SignedDeviceList, + signedDeviceListHistoryValidator, type DeviceOlmOutboundKeys, deviceOlmOutboundKeysValidator, type IdentityServiceClient, @@ -449,6 +451,45 @@ return validatedResult; }, generateNonce: commRustModule.generateNonce, + getDeviceListHistoryForUser: async ( + userID: string, + sinceTimestamp?: number, + ) => { + const { + deviceID: authDeviceID, + userID: authUserID, + accessToken: token, + } = await getAuthMetadata(); + const result = await commRustModule.getDeviceListForUser( + authUserID, + authDeviceID, + token, + userID, + sinceTimestamp, + ); + const rawPayloads: string[] = JSON.parse(result); + const deviceLists: SignedDeviceList[] = rawPayloads.map(payload => + JSON.parse(payload), + ); + return assertWithValidator( + deviceLists, + signedDeviceListHistoryValidator, + ); + }, + updateDeviceList: async (newDeviceList: SignedDeviceList) => { + const { + deviceID: authDeviceID, + userID, + accessToken: authAccessToken, + } = await getAuthMetadata(); + const payload = JSON.stringify(newDeviceList); + await commRustModule.updateDeviceList( + userID, + authDeviceID, + authAccessToken, + payload, + ); + }, }), [getAuthMetadata], ); diff --git a/web/grpc/identity-service-client-proxy.js b/web/grpc/identity-service-client-proxy.js --- a/web/grpc/identity-service-client-proxy.js +++ b/web/grpc/identity-service-client-proxy.js @@ -5,6 +5,7 @@ SignedPrekeys, } from 'lib/types/crypto-types.js'; import type { + SignedDeviceList, IdentityServiceClient, IdentityServiceAuthLayer, DeviceOlmOutboundKeys, @@ -104,6 +105,13 @@ publishWebPrekeys: (prekeys: SignedPrekeys) => Promise = this.proxyToWorker('publishWebPrekeys'); + + getDeviceListHistoryForUser: ( + userID: string, + sinceTimestamp?: number, + ) => Promise<$ReadOnlyArray> = this.proxyToWorker( + 'getDeviceListHistoryForUser', + ); } export { IdentityServiceClientSharedProxy }; diff --git a/web/grpc/identity-service-client-wrapper.js b/web/grpc/identity-service-client-wrapper.js --- a/web/grpc/identity-service-client-wrapper.js +++ b/web/grpc/identity-service-client-wrapper.js @@ -9,6 +9,8 @@ } from 'lib/types/crypto-types.js'; import type { PlatformDetails } from 'lib/types/device-types.js'; import { + type SignedDeviceList, + signedDeviceListHistoryValidator, type IdentityServiceAuthLayer, type IdentityServiceClient, type DeviceOlmOutboundKeys, @@ -447,6 +449,33 @@ request.setNewNotifPrekeys(notifPrekeyUpload); await client.refreshUserPrekeys(request); }; + + getDeviceListHistoryForUser: ( + userID: string, + sinceTimestamp?: number, + ) => Promise<$ReadOnlyArray> = async ( + userID, + sinceTimestamp, + ) => { + const client = this.authClient; + if (!client) { + throw new Error('Identity service client is not initialized'); + } + const request = new IdentityAuthStructs.GetDeviceListRequest(); + request.setUserId(userID); + if (sinceTimestamp) { + request.setSinceTimestamp(sinceTimestamp); + } + const response = await client.getDeviceListForUser(request); + const rawPayloads = response.getDeviceListUpdatesList(); + const deviceListUpdates: SignedDeviceList[] = rawPayloads.map(payload => + JSON.parse(payload), + ); + return assertWithValidator( + deviceListUpdates, + signedDeviceListHistoryValidator, + ); + }; } function authDeviceKeyUpload(