From 745223ce64ec708b274484cbe47d429e2b2461f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:39:17 +0100 Subject: [PATCH] Simplify identity deletion process handling on second device by republishing events (#347) * WIP: Enhance LocalAccountDTO with deletion Info * feat: try to untangle changes * feat: add getAccounts(Not)InDeletion functions * test: clean up IdentityDeletionProcessStatusChangedModule test * refactor: stuff * refactor: clean up changes * test: notes * feat: publish DatawalletSynchronizedEvent in AccountController * test: receive DatawalletSynchronizedEvent calling syncDatawallet use case * feat: use runtime event in app-runtime * feat: add DatawalletSynchronized module * chore: remove LocalAccountDeletionDateChangedModule * test: clean up IdentityDeletionProcessStatusChangedModule test * fix: DatawalletSynchronizedModule * fix: don't publish event updating LocalAccount deletionDate * fix and test: getAccounts(Not)InDeletion * test: don's skip tests * test: remove unrelated test * chore: remove dangerous getters * fix: write deletionDate to cached local account from MultiAccountController * fix: use correct apis * refactor: massively simplify tests * chore: naming * chore: more asserts * refactor: move event publish location * fix: change location of publishing * refactor: collect IDs for changed objects during datawallet sync * feat: re-publish event * refactor: update usage * chore: simplify method * refactor: remove all occurences of the DatawalletSynchronizedModule * fix: publish event * fix: for..of instead of for..in * fix: properly await events * chore: remove unused testutils * chore: use proper eventbus * chore: use for loop * refactor: allow to announce that there are changes in IdentityDeletionProcesses without having a specific * chore: simplify publishing * chore: test wording * feat: update logic * fix: tests --------- Co-authored-by: Siolto Co-authored-by: Milena Czierlinski Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- packages/app-runtime/src/AppConfig.ts | 6 - packages/app-runtime/src/AppRuntime.ts | 2 - .../DatawalletSynchronizedModule.ts | 59 ------- ...ntityDeletionProcessStatusChangedModule.ts | 35 ++++- .../src/modules/runtimeEvents/index.ts | 1 - .../multiAccount/MultiAccountController.ts | 4 +- packages/app-runtime/test/lib/TestUtil.ts | 44 +----- .../DatawalletSynchronizedModule.test.ts | 83 ---------- ...DeletionProcessStatusChangedModule.test.ts | 146 +++++++++++++++--- .../test/runtime/AccountName.test.ts | 2 +- .../test/runtime/TranslationProvider.test.ts | 2 +- packages/consumption/test/core/TestUtil.ts | 2 +- packages/runtime/src/events/EventProxy.ts | 5 +- ...entityDeletionProcessStatusChangedEvent.ts | 4 +- .../transport/identityDeletionProcess.test.ts | 24 +-- ...entityDeletionProcessStatusChangedEvent.ts | 4 +- .../src/modules/accounts/AccountController.ts | 15 +- .../src/modules/sync/ChangedItems.ts | 7 +- .../sync/DatawalletModificationsProcessor.ts | 12 ++ .../src/modules/sync/SyncController.ts | 76 ++++----- .../modules/devices/DeviceOnboarding.test.ts | 4 +- 21 files changed, 244 insertions(+), 293 deletions(-) delete mode 100644 packages/app-runtime/src/modules/runtimeEvents/DatawalletSynchronizedModule.ts delete mode 100644 packages/app-runtime/test/modules/DatawalletSynchronizedModule.test.ts diff --git a/packages/app-runtime/src/AppConfig.ts b/packages/app-runtime/src/AppConfig.ts index b92b91798..2cd3b6e15 100644 --- a/packages/app-runtime/src/AppConfig.ts +++ b/packages/app-runtime/src/AppConfig.ts @@ -52,12 +52,6 @@ export function createAppConfig(...configs: AppConfigOverwrite[]): AppConfig { location: "onboardingChangeReceived", enabled: true }, - datawalletSynchronized: { - name: "datawalletSynchronized", - displayName: "Datawallet Synchronized Module", - location: "datawalletSynchronized", - enabled: true - }, identityDeletionProcessStatusChanged: { name: "identityDeletionProcessStatusChanged", displayName: "Identity Deletion Process Status Changed Module", diff --git a/packages/app-runtime/src/AppRuntime.ts b/packages/app-runtime/src/AppRuntime.ts index 62a409cd2..42bc87722 100644 --- a/packages/app-runtime/src/AppRuntime.ts +++ b/packages/app-runtime/src/AppRuntime.ts @@ -15,7 +15,6 @@ import { AppLaunchModule, AppRuntimeModuleConfiguration, AppSyncModule, - DatawalletSynchronizedModule, IAppRuntimeModuleConstructor, IdentityDeletionProcessStatusChangedModule, MailReceivedModule, @@ -264,7 +263,6 @@ export class AppRuntime extends Runtime { pushNotification: PushNotificationModule, mailReceived: MailReceivedModule, onboardingChangeReceived: OnboardingChangeReceivedModule, - datawalletSynchronized: DatawalletSynchronizedModule, identityDeletionProcessStatusChanged: IdentityDeletionProcessStatusChangedModule, messageReceived: MessageReceivedModule, relationshipChanged: RelationshipChangedModule, diff --git a/packages/app-runtime/src/modules/runtimeEvents/DatawalletSynchronizedModule.ts b/packages/app-runtime/src/modules/runtimeEvents/DatawalletSynchronizedModule.ts deleted file mode 100644 index 45e8cb76d..000000000 --- a/packages/app-runtime/src/modules/runtimeEvents/DatawalletSynchronizedModule.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { CoreDate } from "@nmshd/core-types"; -import { DatawalletSynchronizedEvent, IdentityDeletionProcessStatus } from "@nmshd/runtime"; -import { AppRuntimeError } from "../../AppRuntimeError"; -import { LocalAccountDeletionDateChangedEvent } from "../../events"; -import { LocalAccountMapper } from "../../multiAccount"; -import { AppRuntimeModule, AppRuntimeModuleConfiguration } from "../AppRuntimeModule"; - -export interface DatawalletSynchronizedModuleConfig extends AppRuntimeModuleConfiguration {} - -export class DatawalletSynchronizedModuleError extends AppRuntimeError {} - -export class DatawalletSynchronizedModule extends AppRuntimeModule { - public async init(): Promise { - // Nothing to do here - } - - public start(): Promise | void { - this.subscribeToEvent(DatawalletSynchronizedEvent, this.handleDatawalletSynchronized.bind(this)); - } - - private async handleDatawalletSynchronized(event: DatawalletSynchronizedEvent) { - const services = await this.runtime.getServices(event.eventTargetAddress); - const identityDeletionProcessResult = await services.transportServices.identityDeletionProcesses.getIdentityDeletionProcesses(); - - if (identityDeletionProcessResult.isError) { - this.logger.error(identityDeletionProcessResult); - return; - } - - if (identityDeletionProcessResult.value.length === 0) return; - - const mostRecentIdentityDeletionProcess = identityDeletionProcessResult.value.at(-1)!; - let newDeletionDate; - switch (mostRecentIdentityDeletionProcess.status) { - case IdentityDeletionProcessStatus.Approved: - newDeletionDate = CoreDate.from(mostRecentIdentityDeletionProcess.gracePeriodEndsAt!); - break; - case IdentityDeletionProcessStatus.Cancelled: - case IdentityDeletionProcessStatus.Rejected: - case IdentityDeletionProcessStatus.WaitingForApproval: - newDeletionDate = undefined; - break; - } - - const account = await this.runtime.multiAccountController.getAccountByAddress(event.eventTargetAddress); - const previousDeletionDate = account.deletionDate; - - if (previousDeletionDate === newDeletionDate) return; - - await this.runtime.multiAccountController.updateLocalAccountDeletionDate(event.eventTargetAddress, newDeletionDate); - - const updatedAccount = await this.runtime.multiAccountController.getAccountByAddress(event.eventTargetAddress); - this.runtime.eventBus.publish(new LocalAccountDeletionDateChangedEvent(event.eventTargetAddress, LocalAccountMapper.toLocalAccountDTO(updatedAccount))); - } - - public override stop(): Promise | void { - this.unsubscribeFromAllEvents(); - } -} diff --git a/packages/app-runtime/src/modules/runtimeEvents/IdentityDeletionProcessStatusChangedModule.ts b/packages/app-runtime/src/modules/runtimeEvents/IdentityDeletionProcessStatusChangedModule.ts index a8306b593..be0319832 100644 --- a/packages/app-runtime/src/modules/runtimeEvents/IdentityDeletionProcessStatusChangedModule.ts +++ b/packages/app-runtime/src/modules/runtimeEvents/IdentityDeletionProcessStatusChangedModule.ts @@ -1,6 +1,8 @@ import { CoreDate } from "@nmshd/core-types"; import { IdentityDeletionProcessStatus, IdentityDeletionProcessStatusChangedEvent } from "@nmshd/runtime"; import { AppRuntimeError } from "../../AppRuntimeError"; +import { LocalAccountDeletionDateChangedEvent } from "../../events"; +import { LocalAccountMapper } from "../../multiAccount/data/LocalAccountMapper"; import { AppRuntimeModule, AppRuntimeModuleConfiguration } from "../AppRuntimeModule"; export interface IdentityDeletionProcessStatusChangedModuleConfig extends AppRuntimeModuleConfiguration {} @@ -19,18 +21,24 @@ export class IdentityDeletionProcessStatusChangedModule extends AppRuntimeModule private async handleIdentityDeletionProcessStatusChanged(event: IdentityDeletionProcessStatusChangedEvent) { const identityDeletionProcess = event.data; + if (!identityDeletionProcess) { + const services = await this.runtime.getServices(event.eventTargetAddress); + const identityDeletionProcesses = await services.transportServices.identityDeletionProcesses.getIdentityDeletionProcesses(); + + const approvedIdentityDeletionProcess = identityDeletionProcesses.value.filter((idp) => idp.status === IdentityDeletionProcessStatus.Approved).at(0); + const deletionDate = approvedIdentityDeletionProcess?.gracePeriodEndsAt ? CoreDate.from(approvedIdentityDeletionProcess.gracePeriodEndsAt) : undefined; + + await this.updateLocalAccountDeletionDate(event.eventTargetAddress, deletionDate, true); + return; + } + switch (identityDeletionProcess.status) { case IdentityDeletionProcessStatus.Approved: - await this.runtime.multiAccountController.updateLocalAccountDeletionDate(event.eventTargetAddress, CoreDate.from(identityDeletionProcess.gracePeriodEndsAt!)); + await this.updateLocalAccountDeletionDate(event.eventTargetAddress, CoreDate.from(identityDeletionProcess.gracePeriodEndsAt!)); break; case IdentityDeletionProcessStatus.Cancelled: - const account = await this.runtime.multiAccountController.getAccountByAddress(event.eventTargetAddress); - const previousDeletionDate = account.deletionDate; - - if (!previousDeletionDate) break; - - await this.runtime.multiAccountController.updateLocalAccountDeletionDate(event.eventTargetAddress, undefined); + await this.updateLocalAccountDeletionDate(event.eventTargetAddress, undefined); break; default: @@ -38,6 +46,19 @@ export class IdentityDeletionProcessStatusChangedModule extends AppRuntimeModule } } + private async updateLocalAccountDeletionDate(eventTargetAddress: string, deletionDate: CoreDate | undefined, publishEvent = false) { + const account = await this.runtime.multiAccountController.getAccountByAddress(eventTargetAddress); + const previousDeletionDate = account.deletionDate; + + if (!deletionDate && !previousDeletionDate) return; + if (deletionDate && previousDeletionDate && deletionDate.equals(previousDeletionDate)) return; + + const localAccount = await this.runtime.multiAccountController.updateLocalAccountDeletionDate(eventTargetAddress, deletionDate); + + if (!publishEvent) return; + this.runtime.eventBus.publish(new LocalAccountDeletionDateChangedEvent(eventTargetAddress, LocalAccountMapper.toLocalAccountDTO(localAccount))); + } + public override stop(): Promise | void { this.unsubscribeFromAllEvents(); } diff --git a/packages/app-runtime/src/modules/runtimeEvents/index.ts b/packages/app-runtime/src/modules/runtimeEvents/index.ts index e37a1aa45..7a3710bda 100644 --- a/packages/app-runtime/src/modules/runtimeEvents/index.ts +++ b/packages/app-runtime/src/modules/runtimeEvents/index.ts @@ -1,4 +1,3 @@ -export * from "./DatawalletSynchronizedModule"; export * from "./IdentityDeletionProcessStatusChangedModule"; export * from "./MessageReceivedModule"; export * from "./RelationshipChangedModule"; diff --git a/packages/app-runtime/src/multiAccount/MultiAccountController.ts b/packages/app-runtime/src/multiAccount/MultiAccountController.ts index 08ce2db46..fe9e45f3f 100644 --- a/packages/app-runtime/src/multiAccount/MultiAccountController.ts +++ b/packages/app-runtime/src/multiAccount/MultiAccountController.ts @@ -251,7 +251,7 @@ export class MultiAccountController { await this._localAccounts.update(oldAccount, renamedAccount); } - public async updateLocalAccountDeletionDate(address: string, deletionDate?: CoreDate): Promise { + public async updateLocalAccountDeletionDate(address: string, deletionDate?: CoreDate): Promise { const oldAccount = await this._localAccounts.findOne({ address }); if (!oldAccount) { @@ -265,6 +265,8 @@ export class MultiAccountController { const cachedAccount = this.sessionStorage.findSession(address)?.account; if (cachedAccount) cachedAccount.deletionDate = deletionDate?.toString(); + + return account; } public async updateLastAccessedAt(accountId: string): Promise { diff --git a/packages/app-runtime/test/lib/TestUtil.ts b/packages/app-runtime/test/lib/TestUtil.ts index 2cebf2259..2190dbcf6 100644 --- a/packages/app-runtime/test/lib/TestUtil.ts +++ b/packages/app-runtime/test/lib/TestUtil.ts @@ -1,7 +1,7 @@ /* eslint-disable jest/no-standalone-expect */ import { ILoggerFactory } from "@js-soft/logging-abstractions"; import { SimpleLoggerFactory } from "@js-soft/simple-logger"; -import { EventBus, Result, sleep, SubscriptionTarget } from "@js-soft/ts-utils"; +import { EventBus, Result, sleep } from "@js-soft/ts-utils"; import { ArbitraryMessageContent, ArbitraryRelationshipCreationContent, ArbitraryRelationshipTemplateContent } from "@nmshd/content"; import { CoreDate } from "@nmshd/core-types"; import { @@ -81,46 +81,6 @@ export class TestUtil { TransportLoggerFactory.init(this.oldLogger); } - public static async awaitEvent( - runtime: AppRuntime, - subscriptionTarget: SubscriptionTarget, - timeout?: number, - assertionFunction?: (t: TEvent) => boolean - ): Promise { - const eventBus = runtime.eventBus; - let subscriptionId: number; - - const eventPromise = new Promise((resolve) => { - subscriptionId = eventBus.subscribe(subscriptionTarget, (event: TEvent) => { - if (assertionFunction && !assertionFunction(event)) return; - - resolve(event); - }); - }); - if (!timeout) { - return await eventPromise.finally(() => eventBus.unsubscribe(subscriptionId)); - } - - let timeoutId: NodeJS.Timeout; - const timeoutPromise = new Promise((_resolve, reject) => { - timeoutId = setTimeout( - () => reject(new Error(`timeout exceeded for waiting for event ${typeof subscriptionTarget === "string" ? subscriptionTarget : subscriptionTarget.name}`)), - timeout - ); - }); - - return await Promise.race([eventPromise, timeoutPromise]).finally(() => { - eventBus.unsubscribe(subscriptionId); - clearTimeout(timeoutId); - }); - } - - public static async expectEvent(runtime: AppRuntime, subscriptionTarget: SubscriptionTarget, timeoutInMS = 1000): Promise { - const eventInstance: T = await this.awaitEvent(runtime, subscriptionTarget, timeoutInMS); - expect(eventInstance, "Event received").toBeDefined(); - return eventInstance; - } - public static expectThrows(method: Function | Promise, errorMessageRegexp: RegExp | string): void { let error: Error | undefined; try { @@ -292,7 +252,7 @@ export class TestUtil { expiresAt: CoreDate.utc().add({ minutes: 5 }).toString(), filename: "Test.bin", mimetype: "application/json", - title: "Test", + title: "aFileName", content: fileContent }); return file.value; diff --git a/packages/app-runtime/test/modules/DatawalletSynchronizedModule.test.ts b/packages/app-runtime/test/modules/DatawalletSynchronizedModule.test.ts deleted file mode 100644 index 961aa0f73..000000000 --- a/packages/app-runtime/test/modules/DatawalletSynchronizedModule.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { CoreId } from "@nmshd/core-types"; -import { IdentityDeletionProcessStatus } from "@nmshd/runtime"; -import { AppRuntime, LocalAccountDeletionDateChangedEvent, LocalAccountMapper, LocalAccountSession } from "../../src"; -import { TestUtil } from "../lib"; - -describe("DatawalletSynchronized", function () { - let runtimeDevice1: AppRuntime; - let sessionDevice1: LocalAccountSession; - - let runtimeDevice2: AppRuntime; - let sessionDevice2: LocalAccountSession; - - beforeAll(async function () { - runtimeDevice1 = await TestUtil.createRuntime(); - await runtimeDevice1.start(); - - const [localAccountDevice1] = await TestUtil.provideAccounts(runtimeDevice1, 1); - sessionDevice1 = await runtimeDevice1.selectAccount(localAccountDevice1.id); - - runtimeDevice2 = await TestUtil.createRuntime(); - await runtimeDevice2.start(); - - const createDeviceResult = await sessionDevice1.transportServices.devices.createDevice({ name: "test", isAdmin: true }); - const onboardingInfoResult = await sessionDevice1.transportServices.devices.getDeviceOnboardingInfo({ id: createDeviceResult.value.id, profileName: "Test" }); - const localAccountDevice2 = await runtimeDevice2.accountServices.onboardAccount(onboardingInfoResult.value); - sessionDevice2 = await runtimeDevice2.selectAccount(localAccountDevice2.id.toString()); - - await sessionDevice1.transportServices.account.syncDatawallet(); - await sessionDevice2.transportServices.account.syncDatawallet(); - }); - - afterEach(async () => { - const activeIdentityDeletionProcess = await sessionDevice1.transportServices.identityDeletionProcesses.getActiveIdentityDeletionProcess(); - if (!activeIdentityDeletionProcess.isSuccess) return; - - if (activeIdentityDeletionProcess.value.status === IdentityDeletionProcessStatus.Approved) { - const abortResult = await sessionDevice1.transportServices.identityDeletionProcesses.cancelIdentityDeletionProcess(); - if (abortResult.isError) throw abortResult.error; - - await sessionDevice2.transportServices.account.syncDatawallet(); - await TestUtil.awaitEvent(runtimeDevice2, LocalAccountDeletionDateChangedEvent); - } - }); - - afterAll(async function () { - await runtimeDevice1.stop(); - await runtimeDevice2.stop(); - }); - - test("should set the deletionDate on the LocalAccount on a second device when an IdentityDeletionProcess is initiated", async function () { - const initiateDeletionResult = await sessionDevice1.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); - expect(sessionDevice2.account.deletionDate).toBeUndefined(); - - await sessionDevice2.transportServices.account.syncDatawallet(); - const event = await TestUtil.awaitEvent(runtimeDevice2, LocalAccountDeletionDateChangedEvent); - const updatedAccount = await runtimeDevice2.multiAccountController.getAccountByAddress(sessionDevice2.account.address!); - - expect(event.data).toStrictEqual(LocalAccountMapper.toLocalAccountDTO(updatedAccount)); - expect(event.data.deletionDate).toBe(initiateDeletionResult.value.gracePeriodEndsAt); - - const account = await runtimeDevice2.multiAccountController.getAccount(CoreId.from(sessionDevice2.account.id)); - expect(account.deletionDate!.toString()).toBe(initiateDeletionResult.value.gracePeriodEndsAt); - }); - - test("should unset the deletionDate on the LocalAccount on a second device when an IdentityDeletionProcess is cancelled", async function () { - await sessionDevice1.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); - await sessionDevice2.transportServices.account.syncDatawallet(); - await TestUtil.awaitEvent(runtimeDevice2, LocalAccountDeletionDateChangedEvent); - - await sessionDevice1.transportServices.identityDeletionProcesses.cancelIdentityDeletionProcess(); - expect(sessionDevice2.account.deletionDate).toBeDefined(); - - await sessionDevice2.transportServices.account.syncDatawallet(); - const event = await TestUtil.awaitEvent(runtimeDevice2, LocalAccountDeletionDateChangedEvent); - const updatedAccount = await runtimeDevice2.multiAccountController.getAccountByAddress(sessionDevice2.account.address!); - - expect(event.data).toStrictEqual(LocalAccountMapper.toLocalAccountDTO(updatedAccount)); - expect(event.data.deletionDate).toBeUndefined(); - - const account = await runtimeDevice2.multiAccountController.getAccount(CoreId.from(sessionDevice2.account.id)); - expect(account.deletionDate).toBeUndefined(); - }); -}); diff --git a/packages/app-runtime/test/modules/IdentityDeletionProcessStatusChangedModule.test.ts b/packages/app-runtime/test/modules/IdentityDeletionProcessStatusChangedModule.test.ts index 88a4648ec..e11cd4457 100644 --- a/packages/app-runtime/test/modules/IdentityDeletionProcessStatusChangedModule.test.ts +++ b/packages/app-runtime/test/modules/IdentityDeletionProcessStatusChangedModule.test.ts @@ -1,51 +1,151 @@ import { CoreId } from "@nmshd/core-types"; import { IdentityDeletionProcessStatus } from "@nmshd/runtime"; -import { AppRuntime, LocalAccountSession } from "../../src"; -import { TestUtil } from "../lib"; +import { AppRuntime, LocalAccountDeletionDateChangedEvent, LocalAccountDTO, LocalAccountSession } from "../../src"; +import { MockEventBus, TestUtil } from "../lib"; describe("IdentityDeletionProcessStatusChanged", function () { - let runtime: AppRuntime; - let session: LocalAccountSession; + const eventBusRuntime1 = new MockEventBus(); + let runtime1: AppRuntime; + let localAccount: LocalAccountDTO; + let session1: LocalAccountSession; + + const eventBusRuntime2 = new MockEventBus(); + let runtime2: AppRuntime | undefined; + let session2: LocalAccountSession | undefined; beforeAll(async function () { - runtime = await TestUtil.createRuntime(); - await runtime.start(); + runtime1 = await TestUtil.createRuntime(undefined, undefined, eventBusRuntime1); + await runtime1.start(); - const [localAccount] = await TestUtil.provideAccounts(runtime, 1); - session = await runtime.selectAccount(localAccount.id); + [localAccount] = await TestUtil.provideAccounts(runtime1, 1); + session1 = await runtime1.selectAccount(localAccount.id); }); afterEach(async () => { - const activeIdentityDeletionProcess = await session.transportServices.identityDeletionProcesses.getActiveIdentityDeletionProcess(); + const activeIdentityDeletionProcess = await session1.transportServices.identityDeletionProcesses.getActiveIdentityDeletionProcess(); if (!activeIdentityDeletionProcess.isSuccess) return; if (activeIdentityDeletionProcess.value.status === IdentityDeletionProcessStatus.Approved) { - const abortResult = await session.transportServices.identityDeletionProcesses.cancelIdentityDeletionProcess(); + const abortResult = await session1.transportServices.identityDeletionProcesses.cancelIdentityDeletionProcess(); if (abortResult.isError) throw abortResult.error; + + if (session2 && runtime2 && session2.account.deletionDate) { + await session2.transportServices.account.syncDatawallet(); + await eventBusRuntime2.waitForRunningEventHandlers(); + } } }); - afterAll(async () => await runtime.stop()); + afterAll(async function () { + await runtime1.stop(); + await runtime2?.stop(); + await eventBusRuntime2.close(); + }); - test("should set the deletionDate on the LocalAccount initiating an IdentityDeletionProcess", async function () { - expect(session.account.deletionDate).toBeUndefined(); + test("should set the deletionDate of the LocalAccount initiating an IdentityDeletionProcess", async function () { + expect(session1.account.deletionDate).toBeUndefined(); - const initiateDeletionResult = await session.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); + const initiateDeletionResult = await session1.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); + await eventBusRuntime1.waitForRunningEventHandlers(); - expect(session.account.deletionDate).toBe(initiateDeletionResult.value.gracePeriodEndsAt); + expect(session1.account.deletionDate).toBe(initiateDeletionResult.value.gracePeriodEndsAt); - const account = await runtime.multiAccountController.getAccount(CoreId.from(session.account.id)); - expect(account.deletionDate!.toString()).toBe(initiateDeletionResult.value.gracePeriodEndsAt); + const account = await runtime1.multiAccountController.getAccount(CoreId.from(session1.account.id)); + expect(account.deletionDate!.toString()).toBe(initiateDeletionResult.value.gracePeriodEndsAt!.toString()); }); - test("should unset the deletionDate on the LocalAccount cancelling an IdentityDeletionProcess", async function () { - await session.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); - expect(session.account.deletionDate).toBeDefined(); + test("should unset the deletionDate of the LocalAccount cancelling an IdentityDeletionProcess", async function () { + await session1.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); + await eventBusRuntime1.waitForRunningEventHandlers(); + expect(session1.account.deletionDate).toBeDefined(); - await session.transportServices.identityDeletionProcesses.cancelIdentityDeletionProcess(); - expect(session.account.deletionDate).toBeUndefined(); + await session1.transportServices.identityDeletionProcesses.cancelIdentityDeletionProcess(); + await eventBusRuntime1.waitForRunningEventHandlers(); + expect(session1.account.deletionDate).toBeUndefined(); - const account = await runtime.multiAccountController.getAccount(CoreId.from(session.account.id)); + const account = await runtime1.multiAccountController.getAccount(CoreId.from(session1.account.id)); expect(account.deletionDate).toBeUndefined(); }); + + describe("multi device", function () { + beforeAll(async function () { + runtime2 = await TestUtil.createRuntime(undefined, undefined, eventBusRuntime2); + await runtime2.start(); + + const createDeviceResult = await session1.transportServices.devices.createDevice({ name: "aName", isAdmin: true }); + const onboardingInfoResult = await session1.transportServices.devices.getDeviceOnboardingInfo({ id: createDeviceResult.value.id, profileName: "aProfileName" }); + const localAccountDevice2 = await runtime2.accountServices.onboardAccount(onboardingInfoResult.value); + session2 = await runtime2.selectAccount(localAccountDevice2.id.toString()); + + await session1.transportServices.account.syncDatawallet(); + await session2.transportServices.account.syncDatawallet(); + }); + + test("should set the deletionDate of the LocalAccount on a second device when an IdentityDeletionProcess is initiated", async function () { + const initiateDeletionResult = await session1.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); + expect(session2!.account.deletionDate).toBeUndefined(); + + await session2!.transportServices.account.syncDatawallet(); + + await expect(eventBusRuntime2).toHavePublished(LocalAccountDeletionDateChangedEvent, (e) => e.data.deletionDate! === initiateDeletionResult.value.gracePeriodEndsAt!); + + expect(session2!.account.deletionDate!.toString()).toStrictEqual(initiateDeletionResult.value.gracePeriodEndsAt); + + const account = await runtime2!.multiAccountController.getAccount(CoreId.from(session2!.account.id)); + expect(account.deletionDate!.toString()).toBe(initiateDeletionResult.value.gracePeriodEndsAt!.toString()); + }); + + test("should unset the deletionDate of the LocalAccount on a second device when an IdentityDeletionProcess is cancelled", async function () { + await session1.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); + await session2!.transportServices.account.syncDatawallet(); + await expect(eventBusRuntime2).toHavePublished(LocalAccountDeletionDateChangedEvent); + + await session1.transportServices.identityDeletionProcesses.cancelIdentityDeletionProcess(); + expect(session2!.account.deletionDate).toBeDefined(); + + await session2!.transportServices.account.syncDatawallet(); + + await expect(eventBusRuntime2).toHavePublished(LocalAccountDeletionDateChangedEvent, (e) => e.data.deletionDate === undefined); + + expect(session2!.account.deletionDate).toBeUndefined(); + + const account = await runtime2!.multiAccountController.getAccount(CoreId.from(session2!.account.id)); + expect(account.deletionDate).toBeUndefined(); + }); + + test("should handle multiple synced IdentityDeletionProcesses that happend while not syncing with the last one approved", async function () { + await session1.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); + await session1.transportServices.identityDeletionProcesses.cancelIdentityDeletionProcess(); + await session1.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); + await session1.transportServices.identityDeletionProcesses.cancelIdentityDeletionProcess(); + + const initiateDeletionResult = await session1.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); + expect(session2!.account.deletionDate).toBeUndefined(); + + await session2!.transportServices.account.syncDatawallet(); + + await expect(eventBusRuntime2).toHavePublished(LocalAccountDeletionDateChangedEvent, (e) => e.data.deletionDate! === initiateDeletionResult.value.gracePeriodEndsAt!); + + expect(session2!.account.deletionDate!.toString()).toStrictEqual(initiateDeletionResult.value.gracePeriodEndsAt); + + const account = await runtime2!.multiAccountController.getAccount(CoreId.from(session2!.account.id)); + expect(account.deletionDate!.toString()).toBe(initiateDeletionResult.value.gracePeriodEndsAt!.toString()); + }); + + test("should handle multiple synced IdentityDeletionProcesses that happend while not syncing with the last one cancelled", async function () { + await session1.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); + await session1.transportServices.identityDeletionProcesses.cancelIdentityDeletionProcess(); + await session1.transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess(); + await session1.transportServices.identityDeletionProcesses.cancelIdentityDeletionProcess(); + + await session2!.transportServices.account.syncDatawallet(); + + await expect(eventBusRuntime2).toHavePublished(LocalAccountDeletionDateChangedEvent, (e) => e.data.deletionDate === undefined); + + expect(session2!.account.deletionDate).toBeUndefined(); + + const account = await runtime2!.multiAccountController.getAccount(CoreId.from(session2!.account.id)); + expect(account.deletionDate).toBeUndefined(); + }); + }); }); diff --git a/packages/app-runtime/test/runtime/AccountName.test.ts b/packages/app-runtime/test/runtime/AccountName.test.ts index 099ddbaf0..e6dd5fa14 100644 --- a/packages/app-runtime/test/runtime/AccountName.test.ts +++ b/packages/app-runtime/test/runtime/AccountName.test.ts @@ -18,7 +18,7 @@ describe("Test setting the account name", function () { }); test("should set the account name", async function () { - const accountName = "test"; + const accountName = "anAccountName"; expect(localAccount).toBeDefined(); diff --git a/packages/app-runtime/test/runtime/TranslationProvider.test.ts b/packages/app-runtime/test/runtime/TranslationProvider.test.ts index 73c45c95b..fb2c5fa70 100644 --- a/packages/app-runtime/test/runtime/TranslationProvider.test.ts +++ b/packages/app-runtime/test/runtime/TranslationProvider.test.ts @@ -37,7 +37,7 @@ describe("TranslationProvider", function () { }); test("should translate 'test' to the default message", async function () { - const translation = await runtime.translate("test"); + const translation = await runtime.translate("aKeyWithoutAvailableTranslation"); expect(translation).toBeInstanceOf(Result); expect(translation.isSuccess).toBe(true); expect(translation.value).toBe(noTranslationAvailable); diff --git a/packages/consumption/test/core/TestUtil.ts b/packages/consumption/test/core/TestUtil.ts index d5ef334f8..6d5c0ae54 100644 --- a/packages/consumption/test/core/TestUtil.ts +++ b/packages/consumption/test/core/TestUtil.ts @@ -465,7 +465,7 @@ export class TestUtil { public static async uploadFile(from: AccountController, fileContent: CoreBuffer): Promise { const params: ISendFileParameters = { buffer: fileContent, - title: "Test", + title: "aFileName", description: "Dies ist eine Beschreibung", filename: "Test.bin", filemodified: CoreDate.from("2019-09-30T00:00:00.000Z"), diff --git a/packages/runtime/src/events/EventProxy.ts b/packages/runtime/src/events/EventProxy.ts index 020e8ad17..a05977fa0 100644 --- a/packages/runtime/src/events/EventProxy.ts +++ b/packages/runtime/src/events/EventProxy.ts @@ -94,7 +94,10 @@ export class EventProxy { this.subscribeToSourceEvent(transport.IdentityDeletionProcessStatusChangedEvent, (event) => { this.targetEventBus.publish( - new IdentityDeletionProcessStatusChangedEvent(event.eventTargetAddress, IdentityDeletionProcessMapper.toIdentityDeletionProcessDTO(event.data)) + new IdentityDeletionProcessStatusChangedEvent( + event.eventTargetAddress, + event.data ? IdentityDeletionProcessMapper.toIdentityDeletionProcessDTO(event.data) : undefined + ) ); }); diff --git a/packages/runtime/src/events/transport/IdentityDeletionProcessStatusChangedEvent.ts b/packages/runtime/src/events/transport/IdentityDeletionProcessStatusChangedEvent.ts index ff15e9962..823731a72 100644 --- a/packages/runtime/src/events/transport/IdentityDeletionProcessStatusChangedEvent.ts +++ b/packages/runtime/src/events/transport/IdentityDeletionProcessStatusChangedEvent.ts @@ -1,10 +1,10 @@ import { IdentityDeletionProcessDTO } from "../../types/transport/IdentityDeletionProcessDTO"; import { DataEvent } from "../DataEvent"; -export class IdentityDeletionProcessStatusChangedEvent extends DataEvent { +export class IdentityDeletionProcessStatusChangedEvent extends DataEvent { public static readonly namespace = "transport.identityDeletionProcessStatusChanged"; - public constructor(eventTargetAddress: string, data: IdentityDeletionProcessDTO) { + public constructor(eventTargetAddress: string, data?: IdentityDeletionProcessDTO) { super(IdentityDeletionProcessStatusChangedEvent.namespace, eventTargetAddress, data); } } diff --git a/packages/runtime/test/transport/identityDeletionProcess.test.ts b/packages/runtime/test/transport/identityDeletionProcess.test.ts index f40c0d773..0832aeb3a 100644 --- a/packages/runtime/test/transport/identityDeletionProcess.test.ts +++ b/packages/runtime/test/transport/identityDeletionProcess.test.ts @@ -53,8 +53,8 @@ describe("IdentityDeletionProcess", () => { const initiatedIdentityDeletionProcess = await startIdentityDeletionProcessFromBackboneAdminApi(transportService, accountAddress); expect(initiatedIdentityDeletionProcess.status).toStrictEqual(IdentityDeletionProcessStatus.WaitingForApproval); - await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data.id === initiatedIdentityDeletionProcess.id); - await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data.status === IdentityDeletionProcessStatus.WaitingForApproval); + await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data!.id === initiatedIdentityDeletionProcess.id); + await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data!.status === IdentityDeletionProcessStatus.WaitingForApproval); await transportService.identityDeletionProcesses.approveIdentityDeletionProcess(); eventBus.reset(); @@ -64,8 +64,8 @@ describe("IdentityDeletionProcess", () => { const identityDeletionProcess = result.value; expect(identityDeletionProcess.status).toStrictEqual(IdentityDeletionProcessStatus.Cancelled); - await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data.id === initiatedIdentityDeletionProcess.id); - await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data.status === IdentityDeletionProcessStatus.Cancelled); + await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data!.id === initiatedIdentityDeletionProcess.id); + await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data!.status === IdentityDeletionProcessStatus.Cancelled); }); describe(InitiateIdentityDeletionProcessUseCase.name, () => { @@ -76,8 +76,8 @@ describe("IdentityDeletionProcess", () => { const identityDeletionProcess = result.value; expect(identityDeletionProcess.status).toBe(IdentityDeletionProcessStatus.Approved); - await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data.id === identityDeletionProcess.id); - await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data.status === IdentityDeletionProcessStatus.Approved); + await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data!.id === identityDeletionProcess.id); + await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data!.status === IdentityDeletionProcessStatus.Approved); }); test("should return an error trying to initiate an IdentityDeletionProcess if there already is one approved", async function () { @@ -186,8 +186,8 @@ describe("IdentityDeletionProcess", () => { const cancelledIdentityDeletionProcess = result.value; expect(cancelledIdentityDeletionProcess.status).toBe(IdentityDeletionProcessStatus.Cancelled); - await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data.id === cancelledIdentityDeletionProcess.id); - await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data.status === IdentityDeletionProcessStatus.Cancelled); + await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data!.id === cancelledIdentityDeletionProcess.id); + await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data!.status === IdentityDeletionProcessStatus.Cancelled); }); test("should return an error trying to cancel an IdentityDeletionProcess if there is none active", async function () { @@ -205,8 +205,8 @@ describe("IdentityDeletionProcess", () => { const approvedIdentityDeletionProcess = result.value; expect(approvedIdentityDeletionProcess.status).toBe(IdentityDeletionProcessStatus.Approved); - await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data.id === approvedIdentityDeletionProcess.id); - await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data.status === IdentityDeletionProcessStatus.Approved); + await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data!.id === approvedIdentityDeletionProcess.id); + await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data!.status === IdentityDeletionProcessStatus.Approved); }); test("should return an error trying to approve an IdentityDeletionProcess if there is none active", async function () { @@ -227,8 +227,8 @@ describe("IdentityDeletionProcess", () => { const rejectedIdentityDeletionProcess = result.value; expect(rejectedIdentityDeletionProcess.status).toBe(IdentityDeletionProcessStatus.Rejected); - await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data.id === rejectedIdentityDeletionProcess.id); - await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data.status === IdentityDeletionProcessStatus.Rejected); + await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data!.id === rejectedIdentityDeletionProcess.id); + await expect(eventBus).toHavePublished(IdentityDeletionProcessStatusChangedEvent, (e) => e.data!.status === IdentityDeletionProcessStatus.Rejected); }); test("should return an error trying to reject an IdentityDeletionProcess if there is none active", async function () { diff --git a/packages/transport/src/events/IdentityDeletionProcessStatusChangedEvent.ts b/packages/transport/src/events/IdentityDeletionProcessStatusChangedEvent.ts index 33f47f9bf..032f835c8 100644 --- a/packages/transport/src/events/IdentityDeletionProcessStatusChangedEvent.ts +++ b/packages/transport/src/events/IdentityDeletionProcessStatusChangedEvent.ts @@ -1,10 +1,10 @@ import { IdentityDeletionProcess } from "../modules"; import { TransportDataEvent } from "./TransportDataEvent"; -export class IdentityDeletionProcessStatusChangedEvent extends TransportDataEvent { +export class IdentityDeletionProcessStatusChangedEvent extends TransportDataEvent { public static readonly namespace = "transport.identityDeletionProcessStatusChanged"; - public constructor(eventTargetAddress: string, data: IdentityDeletionProcess) { + public constructor(eventTargetAddress: string, data?: IdentityDeletionProcess) { super(IdentityDeletionProcessStatusChangedEvent.namespace, eventTargetAddress, data); } } diff --git a/packages/transport/src/modules/accounts/AccountController.ts b/packages/transport/src/modules/accounts/AccountController.ts index 96e761ea4..df78544ec 100644 --- a/packages/transport/src/modules/accounts/AccountController.ts +++ b/packages/transport/src/modules/accounts/AccountController.ts @@ -8,6 +8,7 @@ import { CoreCrypto } from "../../core/CoreCrypto"; import { DbCollectionName } from "../../core/DbCollectionName"; import { DependencyOverrides } from "../../core/DependencyOverrides"; import { TransportLoggerFactory } from "../../core/TransportLoggerFactory"; +import { IdentityDeletionProcessStatusChangedEvent } from "../../events/IdentityDeletionProcessStatusChangedEvent"; import { PasswordGenerator } from "../../util"; import { CertificateController } from "../certificates/CertificateController"; import { CertificateIssuer } from "../certificates/CertificateIssuer"; @@ -235,11 +236,21 @@ export class AccountController { public async syncDatawallet(force = false): Promise { if (!force && !this.autoSync) return; - await this.synchronization.sync("OnlyDatawallet"); + const changedItems = await this.synchronization.sync("OnlyDatawallet"); + this.triggerEventsForChangedItems(changedItems); } public async syncEverything(): Promise { - return await this.synchronization.sync("Everything"); + const changedItems = await this.synchronization.sync("Everything"); + this.triggerEventsForChangedItems(changedItems); + + return changedItems; + } + + private triggerEventsForChangedItems(changedItems: ChangedItems) { + if (changedItems.changedObjectIdentifiersDuringDatawalletSync.some((x) => x.startsWith("IDP"))) { + this.transport.eventBus.publish(new IdentityDeletionProcessStatusChangedEvent(this.identity.address.toString())); + } } public async getLastCompletedSyncTime(): Promise { diff --git a/packages/transport/src/modules/sync/ChangedItems.ts b/packages/transport/src/modules/sync/ChangedItems.ts index 4f9c9a01f..7ffde03e5 100644 --- a/packages/transport/src/modules/sync/ChangedItems.ts +++ b/packages/transport/src/modules/sync/ChangedItems.ts @@ -12,7 +12,8 @@ export class ChangedItems implements IChangedItems { public constructor( public readonly relationships: Relationship[] = [], public readonly messages: Message[] = [], - public readonly identityDeletionProcesses: IdentityDeletionProcess[] = [] + public readonly identityDeletionProcesses: IdentityDeletionProcess[] = [], + public readonly changedObjectIdentifiersDuringDatawalletSync: string[] = [] ) {} public addItem(item: Relationship | Message | IdentityDeletionProcess): void { @@ -24,4 +25,8 @@ export class ChangedItems implements IChangedItems { this.identityDeletionProcesses.push(item); } } + + public addChangedObjectsIdentifiersDuringDatawalletSync(identifiers: string[]): void { + this.changedObjectIdentifiersDuringDatawalletSync.push(...identifiers); + } } diff --git a/packages/transport/src/modules/sync/DatawalletModificationsProcessor.ts b/packages/transport/src/modules/sync/DatawalletModificationsProcessor.ts index 4ebc5a778..cc78fcf09 100644 --- a/packages/transport/src/modules/sync/DatawalletModificationsProcessor.ts +++ b/packages/transport/src/modules/sync/DatawalletModificationsProcessor.ts @@ -32,6 +32,11 @@ export class DatawalletModificationsProcessor { private readonly cacheChanges: DatawalletModification[]; private readonly deletedObjectIdentifiers: string[] = []; + private readonly _changedObjectIdentifiers: Set = new Set(); + public get changedObjectIdentifiers(): string[] { + return Array.from(this._changedObjectIdentifiers); + } + public get log(): ILogger { return this.logger; } @@ -64,6 +69,8 @@ export class DatawalletModificationsProcessor { const modificationsGroupedByObjectIdentifier = _.groupBy(this.modificationsWithoutCacheChanges, (m) => m.objectIdentifier); for (const objectIdentifier in modificationsGroupedByObjectIdentifier) { + this._changedObjectIdentifiers.add(objectIdentifier); + const currentModifications = modificationsGroupedByObjectIdentifier[objectIdentifier]; const targetCollectionName = currentModifications[0].collection; @@ -137,6 +144,11 @@ export class DatawalletModificationsProcessor { this.ensureAllItemsAreCacheable(); const cacheChangesWithoutDeletes = this.cacheChanges.filter((c) => !this.deletedObjectIdentifiers.some((d) => c.objectIdentifier.equals(d))); + + for (const objectIdentifier of cacheChangesWithoutDeletes.map((c) => c.objectIdentifier.toString())) { + this._changedObjectIdentifiers.add(objectIdentifier); + } + const cacheChangesGroupedByCollection = this.groupCacheChangesByCollection(cacheChangesWithoutDeletes); const caches = await this.cacheFetcher.fetchCacheFor({ diff --git a/packages/transport/src/modules/sync/SyncController.ts b/packages/transport/src/modules/sync/SyncController.ts index 7c2b01d74..e44399b53 100644 --- a/packages/transport/src/modules/sync/SyncController.ts +++ b/packages/transport/src/modules/sync/SyncController.ts @@ -68,10 +68,7 @@ export class SyncController extends TransportController { private currentSync?: LocalSyncRun; private currentSyncRun?: BackboneSyncRun; - public async sync(whatToSync: "OnlyDatawallet"): Promise; - public async sync(whatToSync: "Everything"): Promise; - public async sync(whatToSync: WhatToSync): Promise; - public async sync(whatToSync: WhatToSync = "Everything"): Promise { + public async sync(whatToSync: WhatToSync = "Everything"): Promise { if (this.currentSync?.includes(whatToSync)) { return await this.currentSync.promise; } @@ -94,32 +91,33 @@ export class SyncController extends TransportController { } private async _syncAndResyncDatawallet(whatToSync: WhatToSync = "Everything") { + const changedItems = new ChangedItems(); + try { - return await this._sync(whatToSync); + await this._sync(whatToSync, changedItems); } finally { if (this.datawalletEnabled && (await this.unpushedDatawalletModifications.exists())) { - await this.syncDatawallet().catch((e) => this.log.error(e)); + await this.syncDatawallet(changedItems).catch((e) => this.log.error(e)); } this.transport.eventBus.publish(new DatawalletSynchronizedEvent(this.parent.identity.address.toString())); } + + return changedItems; } @log() - private async _sync(whatToSync: WhatToSync): Promise { - if (whatToSync === "OnlyDatawallet") { - const value = await this.syncDatawallet(); - return value; - } + private async _sync(whatToSync: WhatToSync, changedItems: ChangedItems): Promise { + if (whatToSync === "OnlyDatawallet") return await this.syncDatawallet(changedItems); - const externalEventSyncResult = await this.syncExternalEvents(); + const externalEventSyncResult = await this.syncExternalEvents(changedItems); await this.setLastCompletedSyncTime(); - if (externalEventSyncResult.externalEventResults.some((r) => r.errorCode !== undefined)) { + if (externalEventSyncResult.some((r) => r.errorCode !== undefined)) { throw new CoreError( "error.transport.errorWhileApplyingExternalEvents", - externalEventSyncResult.externalEventResults + externalEventSyncResult .filter((r) => r.errorCode !== undefined) .map((r) => r.errorCode) .join(" | ") @@ -127,36 +125,28 @@ export class SyncController extends TransportController { } if (this.datawalletEnabled && (await this.unpushedDatawalletModifications.exists())) { - await this.syncDatawallet().catch((e) => this.log.error(e)); + await this.syncDatawallet(changedItems).catch((e) => this.log.error(e)); } - - return externalEventSyncResult.changedItems; } - private async syncExternalEvents(): Promise<{ - externalEventResults: FinalizeSyncRunRequestExternalEventResult[]; - changedItems: ChangedItems; - }> { + private async syncExternalEvents(changedItems: ChangedItems): Promise { const syncRunWasStarted = await this.startExternalEventsSyncRun(); + if (!syncRunWasStarted) { - await this.syncDatawallet(); - return { - changedItems: new ChangedItems(), - externalEventResults: [] - }; + await this.syncDatawallet(changedItems); + return []; } await this.applyIncomingDatawalletModifications(); - const result = await this.applyIncomingExternalEvents(); - await this.finalizeExternalEventsSyncRun(result.externalEventResults); + const result = await this.applyIncomingExternalEvents(changedItems); + await this.finalizeExternalEventsSyncRun(result); return result; } @log() - private async syncDatawallet() { - if (!this.datawalletEnabled) { - return; - } + private async syncDatawallet(changedItems: ChangedItems): Promise { + if (!this.datawalletEnabled) return; + const identityDatawalletVersion = await this.getIdentityDatawalletVersion(); if (this.config.supportedDatawalletVersion < identityDatawalletVersion) { @@ -168,7 +158,9 @@ export class SyncController extends TransportController { this.log.trace("Synchronization of Datawallet events started..."); try { - await this.applyIncomingDatawalletModifications(); + const changedObjectIdentifiers = await this.applyIncomingDatawalletModifications(); + changedItems.addChangedObjectsIdentifiersDuringDatawalletSync(changedObjectIdentifiers); + await this.pushLocalDatawalletModifications(); await this.setLastCompletedDatawalletSyncTime(); @@ -254,13 +246,11 @@ export class SyncController extends TransportController { } } - private async applyIncomingDatawalletModifications() { + private async applyIncomingDatawalletModifications(): Promise { const getDatawalletModificationsResult = await this.client.getDatawalletModifications({ localIndex: await this.getLocalDatawalletModificationIndex() }); const encryptedIncomingModifications = await getDatawalletModificationsResult.value.collect(); - if (encryptedIncomingModifications.length === 0) { - return; - } + if (encryptedIncomingModifications.length === 0) return []; const incomingModifications = await this.decryptDatawalletModifications(encryptedIncomingModifications); @@ -278,6 +268,8 @@ export class SyncController extends TransportController { this.log.trace(`${incomingModifications.length} incoming modifications executed`, incomingModifications); await this.updateLocalDatawalletModificationIndex(encryptedIncomingModifications.sort(descending)[0].index); + + return datawalletModificationsProcessor.changedObjectIdentifiers; } private async promiseAllWithProgess(promises: Promise[], callback: (percentage: number) => void) { @@ -385,7 +377,7 @@ export class SyncController extends TransportController { return this.currentSyncRun !== undefined; } - private async applyIncomingExternalEvents() { + private async applyIncomingExternalEvents(changedItems: ChangedItems): Promise { const getExternalEventsResult = await this.client.getExternalEventsOfSyncRun(this.currentSyncRun!.id.toString()); if (getExternalEventsResult.isError) throw getExternalEventsResult.error; @@ -393,7 +385,6 @@ export class SyncController extends TransportController { const externalEvents = await getExternalEventsResult.value.collect(); const results: FinalizeSyncRunRequestExternalEventResult[] = []; - const changedItems = new ChangedItems(); for (const externalEvent of externalEvents) { try { @@ -416,10 +407,7 @@ export class SyncController extends TransportController { } } - return { - externalEventResults: results, - changedItems: changedItems - }; + return results; } private async finalizeExternalEventsSyncRun(externalEventResults: FinalizeSyncRunRequestExternalEventResult[]): Promise { @@ -514,7 +502,7 @@ function descending(modification1: BackboneDatawalletModification, modification2 class LocalSyncRun { public constructor( - public readonly promise: Promise, + public readonly promise: Promise, public readonly whatToSync: WhatToSync ) {} diff --git a/packages/transport/test/modules/devices/DeviceOnboarding.test.ts b/packages/transport/test/modules/devices/DeviceOnboarding.test.ts index 4f835151b..3457ff6f9 100644 --- a/packages/transport/test/modules/devices/DeviceOnboarding.test.ts +++ b/packages/transport/test/modules/devices/DeviceOnboarding.test.ts @@ -35,10 +35,10 @@ describe("Device Onboarding", function () { }); test("should create correct device", async function () { - newDevice = await device1Account.devices.sendDevice({ name: "Test", isAdmin: true }); + newDevice = await device1Account.devices.sendDevice({ name: "aDeviceName", isAdmin: true }); await device1Account.syncDatawallet(); expect(newDevice).toBeInstanceOf(Device); - expect(newDevice.name).toBe("Test"); + expect(newDevice.name).toBe("aDeviceName"); expect(newDevice.publicKey).toBeUndefined(); expect(newDevice.operatingSystem).toBeUndefined(); expect(newDevice.lastLoginAt).toBeUndefined();