diff --git a/lib/types/crypto-types.js b/lib/types/crypto-types.js --- a/lib/types/crypto-types.js +++ b/lib/types/crypto-types.js @@ -171,6 +171,7 @@ contentIdentityKeys: OLMIdentityKeys, contentInitializationInfo: OlmSessionInitializationInfo, ) => Promise, + +isContentSessionInitialized: (deviceID: string) => Promise, +keyserverNotificationsSessionCreator: ( cookie: ?string, notificationsIdentityKeys: OLMIdentityKeys, @@ -182,6 +183,12 @@ notificationsIdentityKeys: OLMIdentityKeys, notificationsInitializationInfo: OlmSessionInitializationInfo, ) => Promise, + +isDeviceNotificationsSessionInitialized: ( + deviceID: string, + ) => Promise, + +isNotificationsSessionInitializedWithDevices: ( + deviceIDs: $ReadOnlyArray, + ) => Promise<{ +[deviceID: string]: boolean }>, +reassignNotificationsSession?: ( prevCookie: ?string, newCookie: ?string, diff --git a/lib/utils/__mocks__/config.js b/lib/utils/__mocks__/config.js --- a/lib/utils/__mocks__/config.js +++ b/lib/utils/__mocks__/config.js @@ -24,6 +24,9 @@ contentOutboundSessionCreator: jest.fn(), keyserverNotificationsSessionCreator: jest.fn(), notificationsOutboundSessionCreator: jest.fn(), + isContentSessionInitialized: jest.fn(), + isDeviceNotificationsSessionInitialized: jest.fn(), + isNotificationsSessionInitializedWithDevices: jest.fn(), getOneTimeKeys: jest.fn(), validateAndUploadPrekeys: jest.fn(), signMessage: jest.fn(), diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h @@ -110,6 +110,12 @@ jsi::String keyserverID) override; virtual jsi::Value isNotificationsSessionInitialized(jsi::Runtime &rt) override; + virtual jsi::Value isDeviceNotificationsSessionInitialized( + jsi::Runtime &rt, + jsi::String deviceID) override; + virtual jsi::Value isNotificationsSessionInitializedWithDevices( + jsi::Runtime &rt, + jsi::Array deviceIDs) override; virtual jsi::Value updateKeyserverDataInNotifStorage( jsi::Runtime &rt, jsi::Array keyserversData) override; @@ -133,6 +139,8 @@ jsi::String deviceID, double sessionVersion, bool overwrite) override; + virtual jsi::Value + isContentSessionInitialized(jsi::Runtime &rt, jsi::String deviceID) override; virtual jsi::Value initializeNotificationsOutboundSession( jsi::Runtime &rt, jsi::String identityKeys, diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -1207,6 +1207,96 @@ }); } +jsi::Value CommCoreModule::isDeviceNotificationsSessionInitialized( + jsi::Runtime &rt, + jsi::String deviceID) { + auto deviceIDCpp{deviceID.utf8(rt)}; + return createPromiseAsJSIValue( + rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { + taskType job = [=, &innerRt]() { + if (this->contentCryptoModule == nullptr || + !NotificationsCryptoModule::isNotificationsAccountInitialized()) { + this->jsInvoker_->invokeAsync([=, &innerRt]() { + promise->reject("user has not been initialized"); + }); + return; + } + + std::string error; + bool result; + try { + result = NotificationsCryptoModule:: + isDeviceNotificationsSessionInitialized(deviceIDCpp); + } catch (const std::exception &e) { + error = e.what(); + } + + this->jsInvoker_->invokeAsync([=, &innerRt]() { + if (error.size()) { + promise->reject(error); + return; + } + promise->resolve(result); + }); + }; + this->cryptoThread->scheduleTask(job); + }); +} + +jsi::Value CommCoreModule::isNotificationsSessionInitializedWithDevices( + jsi::Runtime &rt, + jsi::Array deviceIDs) { + std::vector deviceIDsCpp; + for (auto idx = 0; idx < deviceIDs.size(rt); idx++) { + std::string deviceIDCpp = + deviceIDs.getValueAtIndex(rt, idx).asString(rt).utf8(rt); + deviceIDsCpp.push_back(deviceIDCpp); + } + + return createPromiseAsJSIValue( + rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { + taskType job = [=, &innerRt]() { + if (this->contentCryptoModule == nullptr || + !NotificationsCryptoModule::isNotificationsAccountInitialized()) { + this->jsInvoker_->invokeAsync([=, &innerRt]() { + promise->reject("user has not been initialized"); + }); + return; + } + + std::string error; + std::vector> result; + + try { + result = NotificationsCryptoModule:: + isNotificationsSessionInitializedWithDevices(deviceIDsCpp); + } catch (const std::exception &e) { + error = e.what(); + } + + auto resultPtr = + std::make_shared>>( + std::move(result)); + + this->jsInvoker_->invokeAsync( + [&innerRt, resultPtr, error, promise]() { + if (error.size()) { + promise->reject(error); + return; + } + + jsi::Object jsiResult = jsi::Object(innerRt); + for (const auto &deviceResult : *resultPtr) { + jsiResult.setProperty( + innerRt, deviceResult.first.c_str(), deviceResult.second); + } + promise->resolve(std::move(jsiResult)); + }); + }; + this->cryptoThread->scheduleTask(job); + }); +} + jsi::Value CommCoreModule::updateKeyserverDataInNotifStorage( jsi::Runtime &rt, jsi::Array keyserversData) { @@ -1453,6 +1543,42 @@ }); } +jsi::Value CommCoreModule::isContentSessionInitialized( + jsi::Runtime &rt, + jsi::String deviceID) { + auto deviceIDCpp{deviceID.utf8(rt)}; + return createPromiseAsJSIValue( + rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { + taskType job = [=, &innerRt]() { + std::string error; + bool result; + + if (this->contentCryptoModule == nullptr || + this->notifsCryptoModule == nullptr) { + this->jsInvoker_->invokeAsync([=, &innerRt]() { + promise->reject("user has not been initialized"); + }); + return; + } + + try { + result = this->contentCryptoModule->hasSessionFor(deviceIDCpp); + } catch (const std::exception &e) { + error = e.what(); + } + + this->jsInvoker_->invokeAsync([=, &innerRt]() { + if (error.size()) { + promise->reject(error); + return; + } + promise->resolve(result); + }); + }; + this->cryptoThread->scheduleTask(job); + }); +} + jsi::Value CommCoreModule::initializeNotificationsOutboundSession( jsi::Runtime &rt, jsi::String identityKeys, diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h --- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h +++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h @@ -54,6 +54,9 @@ static bool isNotificationsSessionInitialized(const std::string &keyserverID); static bool isDeviceNotificationsSessionInitialized(const std::string &deviceID); + static std::vector> + isNotificationsSessionInitializedWithDevices( + const std::vector &deviceIDs); class BaseStatefulDecryptResult { BaseStatefulDecryptResult( diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp --- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp +++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp @@ -6,6 +6,7 @@ #include "../../Tools/Logger.h" #include "../../Tools/PlatformSpecificTools.h" +#include "Logger.h" #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include namespace comm { const std::string @@ -296,6 +298,26 @@ return CommMMKV::getString(peerNotificationsSessionKey).has_value(); } +std::vector> +NotificationsCryptoModule::isNotificationsSessionInitializedWithDevices( + const std::vector &deviceIDs) { + std::vector allKeys = CommMMKV::getAllKeys(); + std::unordered_set allKeysSet(allKeys.begin(), allKeys.end()); + std::vector> result; + + for (const auto &deviceID : deviceIDs) { + std::string mmkvDeviceIDKey = + NotificationsCryptoModule::getDeviceNotificationsSessionKey(deviceID); + if (allKeysSet.find(mmkvDeviceIDKey) == allKeysSet.end()) { + result.push_back({deviceID, false}); + } else { + result.push_back({deviceID, true}); + } + } + + return result; +} + NotificationsCryptoModule::BaseStatefulDecryptResult::BaseStatefulDecryptResult( std::string picklingKey, std::string decryptedData) diff --git a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp --- a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp +++ b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp @@ -69,6 +69,12 @@ static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isNotificationsSessionInitialized(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->isNotificationsSessionInitialized(rt); } +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isDeviceNotificationsSessionInitialized(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->isDeviceNotificationsSessionInitialized(rt, args[0].asString(rt)); +} +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isNotificationsSessionInitializedWithDevices(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->isNotificationsSessionInitializedWithDevices(rt, args[0].asObject(rt).asArray(rt)); +} static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateKeyserverDataInNotifStorage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->updateKeyserverDataInNotifStorage(rt, args[0].asObject(rt).asArray(rt)); } @@ -84,6 +90,9 @@ static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeContentInboundSession(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->initializeContentInboundSession(rt, args[0].asString(rt), args[1].asObject(rt), args[2].asString(rt), args[3].asNumber(), args[4].asBool()); } +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isContentSessionInitialized(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->isContentSessionInitialized(rt, args[0].asString(rt)); +} static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeNotificationsOutboundSession(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->initializeNotificationsOutboundSession(rt, args[0].asString(rt), args[1].asString(rt), args[2].asString(rt), args[3].isNull() || args[3].isUndefined() ? std::nullopt : std::make_optional(args[3].asString(rt)), args[4].asString(rt)); } @@ -244,11 +253,14 @@ methodMap_["validateAndUploadPrekeys"] = MethodMetadata {3, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_validateAndUploadPrekeys}; methodMap_["initializeNotificationsSession"] = MethodMetadata {5, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeNotificationsSession}; methodMap_["isNotificationsSessionInitialized"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isNotificationsSessionInitialized}; + methodMap_["isDeviceNotificationsSessionInitialized"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isDeviceNotificationsSessionInitialized}; + methodMap_["isNotificationsSessionInitializedWithDevices"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isNotificationsSessionInitializedWithDevices}; methodMap_["updateKeyserverDataInNotifStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_updateKeyserverDataInNotifStorage}; methodMap_["removeKeyserverDataFromNotifStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_removeKeyserverDataFromNotifStorage}; methodMap_["getKeyserverDataFromNotifStorage"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getKeyserverDataFromNotifStorage}; methodMap_["initializeContentOutboundSession"] = MethodMetadata {5, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeContentOutboundSession}; methodMap_["initializeContentInboundSession"] = MethodMetadata {5, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeContentInboundSession}; + methodMap_["isContentSessionInitialized"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isContentSessionInitialized}; methodMap_["initializeNotificationsOutboundSession"] = MethodMetadata {5, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeNotificationsOutboundSession}; methodMap_["encrypt"] = MethodMetadata {2, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_encrypt}; methodMap_["encryptNotification"] = MethodMetadata {2, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_encryptNotification}; diff --git a/native/cpp/CommonCpp/_generated/commJSI.h b/native/cpp/CommonCpp/_generated/commJSI.h --- a/native/cpp/CommonCpp/_generated/commJSI.h +++ b/native/cpp/CommonCpp/_generated/commJSI.h @@ -38,11 +38,14 @@ virtual jsi::Value validateAndUploadPrekeys(jsi::Runtime &rt, jsi::String authUserID, jsi::String authDeviceID, jsi::String authAccessToken) = 0; virtual jsi::Value initializeNotificationsSession(jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, std::optional oneTimeKey, jsi::String keyserverID) = 0; virtual jsi::Value isNotificationsSessionInitialized(jsi::Runtime &rt) = 0; + virtual jsi::Value isDeviceNotificationsSessionInitialized(jsi::Runtime &rt, jsi::String deviceID) = 0; + virtual jsi::Value isNotificationsSessionInitializedWithDevices(jsi::Runtime &rt, jsi::Array deviceIDs) = 0; virtual jsi::Value updateKeyserverDataInNotifStorage(jsi::Runtime &rt, jsi::Array keyserversData) = 0; virtual jsi::Value removeKeyserverDataFromNotifStorage(jsi::Runtime &rt, jsi::Array keyserverIDsToDelete) = 0; virtual jsi::Value getKeyserverDataFromNotifStorage(jsi::Runtime &rt, jsi::Array keyserverIDs) = 0; virtual jsi::Value initializeContentOutboundSession(jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, std::optional oneTimeKey, jsi::String deviceID) = 0; virtual jsi::Value initializeContentInboundSession(jsi::Runtime &rt, jsi::String identityKeys, jsi::Object encryptedContent, jsi::String deviceID, double sessionVersion, bool overwrite) = 0; + virtual jsi::Value isContentSessionInitialized(jsi::Runtime &rt, jsi::String deviceID) = 0; virtual jsi::Value initializeNotificationsOutboundSession(jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, std::optional oneTimeKey, jsi::String deviceID) = 0; virtual jsi::Value encrypt(jsi::Runtime &rt, jsi::String message, jsi::String deviceID) = 0; virtual jsi::Value encryptNotification(jsi::Runtime &rt, jsi::String payload, jsi::String deviceID) = 0; @@ -253,6 +256,22 @@ return bridging::callFromJs( rt, &T::isNotificationsSessionInitialized, jsInvoker_, instance_); } + jsi::Value isDeviceNotificationsSessionInitialized(jsi::Runtime &rt, jsi::String deviceID) override { + static_assert( + bridging::getParameterCount(&T::isDeviceNotificationsSessionInitialized) == 2, + "Expected isDeviceNotificationsSessionInitialized(...) to have 2 parameters"); + + return bridging::callFromJs( + rt, &T::isDeviceNotificationsSessionInitialized, jsInvoker_, instance_, std::move(deviceID)); + } + jsi::Value isNotificationsSessionInitializedWithDevices(jsi::Runtime &rt, jsi::Array deviceIDs) override { + static_assert( + bridging::getParameterCount(&T::isNotificationsSessionInitializedWithDevices) == 2, + "Expected isNotificationsSessionInitializedWithDevices(...) to have 2 parameters"); + + return bridging::callFromJs( + rt, &T::isNotificationsSessionInitializedWithDevices, jsInvoker_, instance_, std::move(deviceIDs)); + } jsi::Value updateKeyserverDataInNotifStorage(jsi::Runtime &rt, jsi::Array keyserversData) override { static_assert( bridging::getParameterCount(&T::updateKeyserverDataInNotifStorage) == 2, @@ -293,6 +312,14 @@ return bridging::callFromJs( rt, &T::initializeContentInboundSession, jsInvoker_, instance_, std::move(identityKeys), std::move(encryptedContent), std::move(deviceID), std::move(sessionVersion), std::move(overwrite)); } + jsi::Value isContentSessionInitialized(jsi::Runtime &rt, jsi::String deviceID) override { + static_assert( + bridging::getParameterCount(&T::isContentSessionInitialized) == 2, + "Expected isContentSessionInitialized(...) to have 2 parameters"); + + return bridging::callFromJs( + rt, &T::isContentSessionInitialized, jsInvoker_, instance_, std::move(deviceID)); + } jsi::Value initializeNotificationsOutboundSession(jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, std::optional oneTimeKey, jsi::String deviceID) override { static_assert( bridging::getParameterCount(&T::initializeNotificationsOutboundSession) == 6, diff --git a/native/crypto/olm-api.js b/native/crypto/olm-api.js --- a/native/crypto/olm-api.js +++ b/native/crypto/olm-api.js @@ -42,6 +42,7 @@ overwrite, ); }, + isContentSessionInitialized: commCoreModule.isContentSessionInitialized, async contentOutboundSessionCreator( contentIdentityKeys: OLMIdentityKeys, contentInitializationInfo: OlmSessionInitializationInfo, @@ -95,6 +96,10 @@ deviceID, ); }, + isDeviceNotificationsSessionInitialized: + commCoreModule.isDeviceNotificationsSessionInitialized, + isNotificationsSessionInitializedWithDevices: + commCoreModule.isNotificationsSessionInitializedWithDevices, async getOneTimeKeys(numberOfKeys: number): Promise { const { contentOneTimeKeys, notificationsOneTimeKeys } = await commCoreModule.getOneTimeKeys(numberOfKeys); diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -68,6 +68,12 @@ keyserverID: string, ) => Promise; +isNotificationsSessionInitialized: () => Promise; + +isDeviceNotificationsSessionInitialized: ( + deviceID: string, + ) => Promise; + +isNotificationsSessionInitializedWithDevices: ( + deviceIDs: $ReadOnlyArray, + ) => Promise<{ +[deviceID: string]: boolean }>; +updateKeyserverDataInNotifStorage: ( keyserversData: $ReadOnlyArray<{ +id: string, +unreadCount: number }>, ) => Promise; @@ -91,6 +97,7 @@ sessionVersion: number, overwrite: boolean, ) => Promise; + +isContentSessionInitialized: (deviceID: string) => Promise; +initializeNotificationsOutboundSession: ( identityKeys: string, prekey: string, diff --git a/web/crypto/olm-api.js b/web/crypto/olm-api.js --- a/web/crypto/olm-api.js +++ b/web/crypto/olm-api.js @@ -51,6 +51,13 @@ decryptSequentialAndPersist: proxyToWorker('decryptSequentialAndPersist'), contentInboundSessionCreator: proxyToWorker('contentInboundSessionCreator'), contentOutboundSessionCreator: proxyToWorker('contentOutboundSessionCreator'), + isContentSessionInitialized: proxyToWorker('isContentSessionInitialized'), + isDeviceNotificationsSessionInitialized: proxyToWorker( + 'isDeviceNotificationsSessionInitialized', + ), + isNotificationsSessionInitializedWithDevices: proxyToWorker( + 'isNotificationsSessionInitializedWithDevices', + ), keyserverNotificationsSessionCreator: proxyToWorker( 'keyserverNotificationsSessionCreator', ), diff --git a/web/shared-worker/worker/worker-crypto.js b/web/shared-worker/worker/worker-crypto.js --- a/web/shared-worker/worker/worker-crypto.js +++ b/web/shared-worker/worker/worker-crypto.js @@ -731,6 +731,12 @@ return { encryptedData, sessionVersion: newSessionVersion }; }, + async isContentSessionInitialized(deviceID: string) { + if (!cryptoStore) { + throw new Error('Crypto account not initialized'); + } + return !!cryptoStore.contentSessions[deviceID]; + }, async notificationsOutboundSessionCreator( deviceID: string, notificationsIdentityKeys: OLMIdentityKeys, @@ -739,6 +745,7 @@ const dataPersistenceKey = getOlmDataKeyForDeviceID(deviceID); const dataEncryptionKeyDBLabel = getOlmEncryptionKeyDBLabelForDeviceID(deviceID); + return createAndPersistNotificationsOutboundSession( notificationsIdentityKeys, notificationsInitializationInfo, @@ -746,6 +753,37 @@ dataEncryptionKeyDBLabel, ); }, + async isDeviceNotificationsSessionInitialized(deviceID: string) { + const dataPersistenceKey = getOlmDataKeyForDeviceID(deviceID); + const dataEncryptionKeyDBLabel = + getOlmEncryptionKeyDBLabelForDeviceID(deviceID); + + const allKeys = await localforage.keys(); + const allKeysSet = new Set(allKeys); + return ( + allKeysSet.has(dataPersistenceKey) && + allKeysSet.has(dataEncryptionKeyDBLabel) + ); + }, + async isNotificationsSessionInitializedWithDevices( + deviceIDs: $ReadOnlyArray, + ) { + const allKeys = await localforage.keys(); + const allKeysSet = new Set(allKeys); + + const deviceInfoPairs = deviceIDs.map(deviceID => { + const dataPersistenceKey = getOlmDataKeyForDeviceID(deviceID); + const dataEncryptionKeyDBLabel = + getOlmEncryptionKeyDBLabelForDeviceID(deviceID); + return [ + deviceID, + allKeysSet.has(dataPersistenceKey) && + allKeysSet.has(dataEncryptionKeyDBLabel), + ]; + }); + + return Object.fromEntries(deviceInfoPairs); + }, async keyserverNotificationsSessionCreator( cookie: ?string, notificationsIdentityKeys: OLMIdentityKeys,