Skip to content

Commit

Permalink
Feature/device push identifier (#148)
Browse files Browse the repository at this point in the history
* feat: update backbone return type

* feat: handle device push identifier in the app-runtime

* fix: add devicePushIdentifier to LocalAccountDTO

* chore: add lgos

* chore: simplify the event bus and make it actually work

* fix: assert on the dpi

* feat: add a test for push notifications

* fix: remove TODO comment

* chore: bumpy

* chore: bump transport

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
jkoenig134 and mergify[bot] authored Jun 6, 2024
1 parent c67ed06 commit f7dd1f2
Show file tree
Hide file tree
Showing 18 changed files with 152 additions and 63 deletions.
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/app-runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nmshd/app-runtime",
"version": "3.2.1",
"version": "3.3.0",
"description": "The App Runtime",
"homepage": "https://enmeshed.eu",
"repository": {
Expand Down Expand Up @@ -54,7 +54,7 @@
"@types/luxon": "^3.4.2"
},
"peerDependencies": {
"@nmshd/runtime": "^4.4.1"
"@nmshd/runtime": "^4.11.0"
},
"publishConfig": {
"access": "public",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export enum BackboneEventName {
}

export interface IBackboneEventContent {
accRef: string;
devicePushIdentifier: string;
eventName: BackboneEventName;
sentAt: string;
payload: any;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ export class PushNotificationModule extends AppRuntimeModule<PushNotificationMod
const notification = event.notification;
const content = notification.content as IBackboneEventContent;

const accRef = await this.runtime.multiAccountController.getAccountReferenceForDevicePushIdentifier(content.devicePushIdentifier);

try {
const services = await this.runtime.getServices(content.accRef);
const services = await this.runtime.getServices(accRef);

switch (content.eventName) {
case BackboneEventName.DatawalletModificationsCreated:
Expand All @@ -33,7 +35,7 @@ export class PushNotificationModule extends AppRuntimeModule<PushNotificationMod
this.logger.error(walletResult);
return;
}
this.runtime.eventBus.publish(new DatawalletSynchronizedEvent(content.accRef));
this.runtime.eventBus.publish(new DatawalletSynchronizedEvent(accRef));
break;
case BackboneEventName.ExternalEventCreated:
const syncResult = await services.transportServices.account.syncEverything();
Expand All @@ -42,7 +44,7 @@ export class PushNotificationModule extends AppRuntimeModule<PushNotificationMod
return;
}

this.runtime.eventBus.publish(new ExternalEventReceivedEvent(content.accRef, syncResult.value));
this.runtime.eventBus.publish(new ExternalEventReceivedEvent(accRef, syncResult.value));

break;
default:
Expand Down Expand Up @@ -87,6 +89,7 @@ export class PushNotificationModule extends AppRuntimeModule<PushNotificationMod

const deviceResult = await services.transportServices.account.getDeviceInfo();
if (deviceResult.isError) {
this.logger.error(deviceResult.error);
throw AppRuntimeErrors.modules.pushNotificationModule.tokenRegistrationNotPossible("No device for this account found", deviceResult.error).logWith(this.logger);
}

Expand All @@ -101,15 +104,25 @@ export class PushNotificationModule extends AppRuntimeModule<PushNotificationMod
appId,
environment: environment
});

if (result.isError) {
this.logger.error(result.error);
throw AppRuntimeErrors.modules.pushNotificationModule.tokenRegistrationNotPossible(result.error.message, result.error).logWith(this.logger);
} else {
this.logger.info(
`PushNotificationModule.registerPushTokenForLocalAccount: Token ${handle} registered for account ${address} on platform ${platform}${
environment ? ` (${environment})` : ""
} and appId ${appId}`
);
}

this.logger.info(
`PushNotificationModule.registerPushTokenForLocalAccount: Token ${handle} registered for account ${address} on platform ${platform}${
environment ? ` (${environment})` : ""
} and appId ${appId}`
);

await this.registerPushIdentifierForAccount(address, result.value.devicePushIdentifier);
}

private async registerPushIdentifierForAccount(address: string, devicePushIdentifier: string): Promise<void> {
this.logger.trace("PushNotificationModule.registerPushIdentifierForAccount", { address, pushIdentifier: devicePushIdentifier });

await this.runtime.multiAccountController.updatePushIdentifierForAccount(address, devicePushIdentifier);
}

public getNotificationTokenFromConfig(): Result<string> {
Expand Down
20 changes: 20 additions & 0 deletions packages/app-runtime/src/multiAccount/MultiAccountController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,24 @@ export class MultiAccountController {

await this._localAccounts.update(document, localAccount);
}

public async updatePushIdentifierForAccount(address: string, devicePushIdentifier: string): Promise<void> {
const document = await this._localAccounts.findOne({ address });
if (!document) {
throw TransportCoreErrors.general.recordNotFound(LocalAccount, address).logWith(this._log);
}

const localAccount = LocalAccount.from(document);
localAccount.devicePushIdentifier = devicePushIdentifier;

await this._localAccounts.update(document, localAccount);
}

public async getAccountReferenceForDevicePushIdentifier(devicePushIdentifier: string): Promise<string> {
const document = await this._localAccounts.findOne({ devicePushIdentifier });
if (!document) throw new Error(`Could not resolve a local account reference for the device push identifier '${devicePushIdentifier}'.`);

const localAccount = LocalAccount.from(document);
return localAccount.id.toString();
}
}
5 changes: 5 additions & 0 deletions packages/app-runtime/src/multiAccount/data/LocalAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface ILocalAccount extends ICoreSerializable {
directory: string;
order: number;
lastAccessedAt?: ICoreDate;
devicePushIdentifier?: string;
}

@type("LocalAccount")
Expand Down Expand Up @@ -41,6 +42,10 @@ export class LocalAccount extends CoreSerializable implements ILocalAccount {
@serialize()
public lastAccessedAt?: CoreDate;

@validate({ nullable: true })
@serialize()
public devicePushIdentifier?: string;

public static from(value: ILocalAccount): LocalAccount {
return this.fromAny(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export interface LocalAccountDTO {
directory: string;
order: number;
lastAccessedAt?: string;
devicePushIdentifier?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export class LocalAccountMapper {
realm: localAccount.realm,
directory: localAccount.directory.toString(),
order: localAccount.order,
lastAccessedAt: localAccount.lastAccessedAt?.toString()
lastAccessedAt: localAccount.lastAccessedAt?.toString(),
devicePushIdentifier: localAccount.devicePushIdentifier
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export interface LocalAccountSession {
consumptionController: ConsumptionController;
selectedRelationship?: IdentityDVO;
expiresAt?: string;
devicePushIdentifier?: string;
}
32 changes: 1 addition & 31 deletions packages/app-runtime/test/mocks/NativeEventBusMock.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { AppReadyEvent, INativeEventBus } from "@js-soft/native-abstractions";
import { INativeEventBus } from "@js-soft/native-abstractions";
import { Event, EventBus, EventEmitter2EventBus, Result } from "@js-soft/ts-utils";

export class NativeEventBusMock implements INativeEventBus {
private eventBus: EventBus;
private locked = true;
private queue: Event[] = [];

public get isLocked(): boolean {
return this.locked;
}

public init(): Promise<Result<void>> {
this.eventBus = new EventEmitter2EventBus(() => {
Expand All @@ -32,31 +26,7 @@ export class NativeEventBusMock implements INativeEventBus {
return Result.ok(undefined);
}

/**
* Publish Events on the EventBus.
* The EventBus is initially locked.
* Published events are queued to be published after the EventBus is unlocked.
* To unlock the EventBus an AppReadyEvent has to be published.
* @param event
* @returns
*/
public publish(event: Event): Result<void> {
if (this.locked) {
if (event instanceof AppReadyEvent) {
this.locked = false;
// eslint-disable-next-line no-console
console.log("Unlocked EventBus."); // No js-soft logger available at this stage
this.queue.forEach((event: Event) => this.publish(event));
this.queue = [];
// eslint-disable-next-line no-console
console.log("All queued events have been published."); // No js-soft logger available at this stage
} else {
this.queue.push(event);
// eslint-disable-next-line no-console
console.warn("EventBus is locked. Queued the following event:", event); // No js-soft logger available at this stage
}
return Result.ok(undefined);
}
this.eventBus.publish(event);
return Result.ok(undefined);
}
Expand Down
67 changes: 67 additions & 0 deletions packages/app-runtime/test/modules/PushNotification.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { RemoteNotificationEvent, RemoteNotificationRegistrationEvent } from "@js-soft/native-abstractions";
import { sleep } from "@js-soft/ts-utils";
import { AppRuntime, DatawalletSynchronizedEvent, ExternalEventReceivedEvent, LocalAccountSession } from "../../src";
import { TestUtil } from "../lib";

describe("PushNotificationModuleTest", function () {
let runtime: AppRuntime;
let session: LocalAccountSession;
let devicePushIdentifier = "dummy value";

beforeAll(async function () {
runtime = await TestUtil.createRuntime();
await runtime.start();

const accounts = await TestUtil.provideAccounts(runtime, 1);
session = await runtime.selectAccount(accounts[0].id);
});

afterAll(async function () {
await runtime.stop();
});

test("should persist push identifier", async function () {
runtime.nativeEnvironment.eventBus.publish(new RemoteNotificationRegistrationEvent("handleLongerThan10Characters"));

// wait for the registration to finish
// there is no event to wait for, so we just wait for a second
await sleep(1000);

const account = await runtime.accountServices.getAccount(session.account.id);
expect(account.devicePushIdentifier).toBeDefined();

devicePushIdentifier = account.devicePushIdentifier!;
});

test("should do a datawallet sync when DatawalletModificationsCreated is received", async function () {
runtime.nativeEnvironment.eventBus.publish(
new RemoteNotificationEvent({
content: {
devicePushIdentifier: devicePushIdentifier,
eventName: "DatawalletModificationsCreated",
sentAt: new Date().toISOString(),
payload: {}
}
})
);

const event = await TestUtil.awaitEvent(runtime, DatawalletSynchronizedEvent);
expect(event).toBeDefined();
});

test("should do a sync everything when ExternalEventCreated is received", async function () {
runtime.nativeEnvironment.eventBus.publish(
new RemoteNotificationEvent({
content: {
devicePushIdentifier: devicePushIdentifier,
eventName: "ExternalEventCreated",
sentAt: new Date().toISOString(),
payload: {}
}
})
);

const event = await TestUtil.awaitEvent(runtime, ExternalEventReceivedEvent);
expect(event).toBeDefined();
});
});
4 changes: 2 additions & 2 deletions packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nmshd/runtime",
"version": "4.10.6",
"version": "4.11.0",
"description": "The enmeshed client runtime.",
"homepage": "https://enmeshed.eu",
"repository": {
Expand Down Expand Up @@ -59,7 +59,7 @@
"@nmshd/consumption": "3.11.0",
"@nmshd/content": "2.10.1",
"@nmshd/crypto": "2.0.6",
"@nmshd/transport": "2.7.5",
"@nmshd/transport": "2.8.0",
"ajv": "^8.16.0",
"ajv-errors": "^3.0.0",
"ajv-formats": "^3.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
LoadItemFromTruncatedReferenceResponse,
LoadItemFromTruncatedReferenceUseCase,
RegisterPushNotificationTokenRequest,
RegisterPushNotificationTokenResponse,
RegisterPushNotificationTokenUseCase,
SyncDatawalletRequest,
SyncDatawalletUseCase,
Expand Down Expand Up @@ -44,7 +45,7 @@ export class AccountFacade {
return await this.getDeviceInfoUseCase.execute();
}

public async registerPushNotificationToken(request: RegisterPushNotificationTokenRequest): Promise<Result<void, ApplicationError>> {
public async registerPushNotificationToken(request: RegisterPushNotificationTokenRequest): Promise<Result<RegisterPushNotificationTokenResponse, ApplicationError>> {
return await this.registerPushNotificationTokenUseCase.execute(request);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,32 @@ export interface RegisterPushNotificationTokenRequest {
environment?: "Development" | "Production";
}

export interface RegisterPushNotificationTokenResponse {
devicePushIdentifier: string;
}

class Validator extends SchemaValidator<RegisterPushNotificationTokenRequest> {
public constructor(@Inject schemaRepository: SchemaRepository) {
super(schemaRepository.getSchema("RegisterPushNotificationTokenRequest"));
}
}

export class RegisterPushNotificationTokenUseCase extends UseCase<RegisterPushNotificationTokenRequest, void> {
export class RegisterPushNotificationTokenUseCase extends UseCase<RegisterPushNotificationTokenRequest, RegisterPushNotificationTokenResponse> {
public constructor(
@Inject private readonly accountController: AccountController,
@Inject validator: Validator
) {
super(validator);
}

protected async executeInternal(request: RegisterPushNotificationTokenRequest): Promise<Result<void>> {
await this.accountController.registerPushNotificationToken({
protected async executeInternal(request: RegisterPushNotificationTokenRequest): Promise<Result<RegisterPushNotificationTokenResponse>> {
const result = await this.accountController.registerPushNotificationToken({
handle: request.handle,
platform: request.platform,
appId: request.appId,
environment: request.environment
});

return Result.ok(undefined);
return Result.ok({ devicePushIdentifier: result.devicePushIdentifier });
}
}
Loading

0 comments on commit f7dd1f2

Please sign in to comment.