Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add fcm push notifications #1

Merged
merged 7 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@ POSTGRES_PASSWORD=
POSTGRES_HOST=
POSTGRES_ADMIN_USER=
POSTGRES_ADMIN_PASSWORD=

USE_PUSH_NOTIFICATIONS=true

FIREBASE_PROJECT_ID=
FIREBASE_PRIVATE_KEY=
FIREBASE_CLIENT_EMAIL=
FIREBASE_NOTIFICATION_TITLE=You have message from your contacts
FIREBASE_NOTIFICATION_BODY=Please open your app to read the message
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@
"prepublishOnly": "yarn run build",
"dev": "ts-node dev",
"start": "NODE_ENV=production node build/index.js",
"validate": "yarn build && yarn check-format"
"validate": "yarn build && yarn check-format",
"postinstall": "patch-package"
},
"dependencies": {
"@aries-framework/askar": "^0.4.1",
"@aries-framework/core": "^0.4.1",
"@aries-framework/node": "^0.4.1",
"@hyperledger/aries-askar-nodejs": "^0.1.0",
"express": "^4.18.1",
"firebase-admin": "^11.10.1",
"prettier": "^2.8.4",
"tslog": "^3.3.3",
"tsyringe": "^4.7.0",
Expand All @@ -49,6 +51,7 @@
"dotenv": "^16.0.1",
"jest": "^29.2.2",
"ngrok": "^4.3.1",
"patch-package": "^8.0.0",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
Expand Down
93 changes: 93 additions & 0 deletions patches/@aries-framework+core+0.4.1.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
diff --git a/node_modules/@aries-framework/core/build/modules/routing/RoutingEvents.d.ts b/node_modules/@aries-framework/core/build/modules/routing/RoutingEvents.d.ts
index 578a26f..3cb7221 100644
--- a/node_modules/@aries-framework/core/build/modules/routing/RoutingEvents.d.ts
+++ b/node_modules/@aries-framework/core/build/modules/routing/RoutingEvents.d.ts
@@ -2,11 +2,12 @@ import type { KeylistUpdate } from './messages/KeylistUpdateMessage';
import type { MediationState } from './models/MediationState';
import type { MediationRecord } from './repository/MediationRecord';
import type { BaseEvent } from '../../agent/Events';
-import type { Routing } from '../connections';
+import type { ConnectionRecord, Routing } from '../connections';
export declare enum RoutingEventTypes {
MediationStateChanged = "MediationStateChanged",
RecipientKeylistUpdated = "RecipientKeylistUpdated",
- RoutingCreatedEvent = "RoutingCreatedEvent"
+ RoutingCreatedEvent = "RoutingCreatedEvent",
+ ForwardMessageEvent = "ForwardMessageEvent"
}
export interface RoutingCreatedEvent extends BaseEvent {
type: typeof RoutingEventTypes.RoutingCreatedEvent;
@@ -28,3 +29,9 @@ export interface KeylistUpdatedEvent extends BaseEvent {
keylist: KeylistUpdate[];
};
}
+export interface ForwardMessageEvent extends BaseEvent {
+ type: typeof RoutingEventTypes.ForwardMessageEvent;
+ payload: {
+ connectionRecord: ConnectionRecord;
+ };
+}
diff --git a/node_modules/@aries-framework/core/build/modules/routing/RoutingEvents.js b/node_modules/@aries-framework/core/build/modules/routing/RoutingEvents.js
index ad9e68b..1aa5cb6 100644
--- a/node_modules/@aries-framework/core/build/modules/routing/RoutingEvents.js
+++ b/node_modules/@aries-framework/core/build/modules/routing/RoutingEvents.js
@@ -6,5 +6,6 @@ var RoutingEventTypes;
RoutingEventTypes["MediationStateChanged"] = "MediationStateChanged";
RoutingEventTypes["RecipientKeylistUpdated"] = "RecipientKeylistUpdated";
RoutingEventTypes["RoutingCreatedEvent"] = "RoutingCreatedEvent";
+ RoutingEventTypes["ForwardMessageEvent"] = "ForwardMessageEvent";
})(RoutingEventTypes = exports.RoutingEventTypes || (exports.RoutingEventTypes = {}));
//# sourceMappingURL=RoutingEvents.js.map
\ No newline at end of file
diff --git a/node_modules/@aries-framework/core/build/modules/routing/handlers/ForwardHandler.js b/node_modules/@aries-framework/core/build/modules/routing/handlers/ForwardHandler.js
index bb61eee..8e2069b 100644
--- a/node_modules/@aries-framework/core/build/modules/routing/handlers/ForwardHandler.js
+++ b/node_modules/@aries-framework/core/build/modules/routing/handlers/ForwardHandler.js
@@ -12,6 +12,8 @@ class ForwardHandler {
async handle(messageContext) {
const { encryptedMessage, mediationRecord } = await this.mediatorService.processForwardMessage(messageContext);
const connectionRecord = await this.connectionService.getById(messageContext.agentContext, mediationRecord.connectionId);
+ // Send forward message to the event emitter so that it can be picked up events
+ await this.mediatorService.emitForwardEvent(messageContext.agentContext, connectionRecord);
// The message inside the forward message is packed so we just send the packed
// message to the connection associated with it
await this.messageSender.sendPackage(messageContext.agentContext, {
diff --git a/node_modules/@aries-framework/core/build/modules/routing/services/MediatorService.d.ts b/node_modules/@aries-framework/core/build/modules/routing/services/MediatorService.d.ts
index e02dbab..ec9ba0d 100644
--- a/node_modules/@aries-framework/core/build/modules/routing/services/MediatorService.d.ts
+++ b/node_modules/@aries-framework/core/build/modules/routing/services/MediatorService.d.ts
@@ -2,6 +2,7 @@ import type { AgentContext } from '../../../agent';
import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext';
import type { Query } from '../../../storage/StorageService';
import type { EncryptedMessage } from '../../../types';
+import type { ConnectionRecord } from '../../connections';
import type { ForwardMessage, MediationRequestMessage } from '../messages';
import { EventEmitter } from '../../../agent/EventEmitter';
import { Logger } from '../../../logger';
@@ -23,6 +24,7 @@ export declare class MediatorService {
mediationRecord: MediationRecord;
encryptedMessage: EncryptedMessage;
}>;
+ emitForwardEvent(agentContext: AgentContext, connectionRecord: ConnectionRecord): Promise<void>;
processKeylistUpdateRequest(messageContext: InboundMessageContext<KeylistUpdateMessage>): Promise<KeylistUpdateResponseMessage>;
createGrantMediationMessage(agentContext: AgentContext, mediationRecord: MediationRecord): Promise<{
mediationRecord: MediationRecord;
diff --git a/node_modules/@aries-framework/core/build/modules/routing/services/MediatorService.js b/node_modules/@aries-framework/core/build/modules/routing/services/MediatorService.js
index ec8cd1b..2118939 100644
--- a/node_modules/@aries-framework/core/build/modules/routing/services/MediatorService.js
+++ b/node_modules/@aries-framework/core/build/modules/routing/services/MediatorService.js
@@ -61,6 +61,14 @@ let MediatorService = class MediatorService {
mediationRecord,
};
}
+ async emitForwardEvent(agentContext, connectionRecord) {
+ this.eventEmitter.emit(agentContext, {
+ type: RoutingEvents_1.RoutingEventTypes.ForwardMessageEvent,
+ payload: {
+ connectionRecord,
+ },
+ });
+ }
async processKeylistUpdateRequest(messageContext) {
// Assert Ready connection
const connection = messageContext.assertReadyConnection();
33 changes: 31 additions & 2 deletions src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,26 @@ import type { Socket } from 'net'
import express from 'express'
import { Server } from 'ws'

import { AGENT_ENDPOINTS, AGENT_NAME, AGENT_PORT, LOG_LEVEL, POSTGRES_HOST, WALLET_KEY, WALLET_NAME } from './constants'
import {
AGENT_ENDPOINTS,
AGENT_NAME,
AGENT_PORT,
FIREBASE_CLIENT_EMAIL,
FIREBASE_PRIVATE_KEY,
FIREBASE_PROJECT_ID,
LOG_LEVEL,
POSTGRES_HOST,
USE_PUSH_NOTIFICATIONS,
WALLET_KEY,
WALLET_NAME,
} from './constants'
import { askarPostgresConfig } from './database'
import { Logger } from './logger'
import { StorageMessageQueueModule } from './storage/StorageMessageQueueModule'
import { PushNotificationsFcmModule } from './push-notifications/fcm'
import { routingEvents } from './events/RoutingEvents'
import { initializeApp } from 'firebase-admin/app'
import { credential } from 'firebase-admin'

function createModules() {
const modules = {
Expand All @@ -40,6 +56,7 @@ function createModules() {
ariesAskar,
multiWalletDatabaseScheme: AskarMultiWalletDatabaseScheme.ProfilePerWallet,
}),
pushNotificationsFcm: new PushNotificationsFcmModule(),
}

return modules
Expand Down Expand Up @@ -123,6 +140,18 @@ export async function createAgent() {

await agent.initialize()

// Register all event handlers and initialize fcm module
if (USE_PUSH_NOTIFICATIONS) {
initializeApp({
credential: credential.cert({
projectId: FIREBASE_PROJECT_ID,
clientEmail: FIREBASE_CLIENT_EMAIL,
privateKey: FIREBASE_PRIVATE_KEY,
}),
})
routingEvents(agent)
}

// When an 'upgrade' to WS is made on our http server, we forward the
// request to the WS server
httpInboundTransport.server?.on('upgrade', (request, socket, head) => {
Expand All @@ -134,4 +163,4 @@ export async function createAgent() {
return agent
}

export type MediatorAgent = ReturnType<typeof createAgent>
export type MediatorAgent = Agent<ReturnType<typeof createModules>>
11 changes: 11 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,14 @@ export const INVITATION_URL = process.env.INVITATION_URL
export const LOG_LEVEL = LogLevel.debug

export const IS_DEV = process.env.NODE_ENV === 'development'

export const USE_PUSH_NOTIFICATIONS = process.env.USE_PUSH_NOTIFICATIONS === 'true'

export const FIREBASE_PROJECT_ID = process.env.FIREBASE_PROJECT_ID
export const FIREBASE_PRIVATE_KEY = process.env.FIREBASE_PRIVATE_KEY
? process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n')
: undefined
export const FIREBASE_CLIENT_EMAIL = process.env.FIREBASE_CLIENT_EMAIL

export const FIREBASE_NOTIFICATION_TITLE = process.env.FIREBASE_NOTIFICATION_TITLE
export const FIREBASE_NOTIFICATION_BODY = process.env.FIREBASE_NOTIFICATION_BODY
18 changes: 18 additions & 0 deletions src/events/RoutingEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { ForwardMessageEvent } from '@aries-framework/core'

import { RoutingEventTypes } from '@aries-framework/core'
import { MediatorAgent } from '../agent'

export const routingEvents = async (agent: MediatorAgent) => {
agent.events.on(RoutingEventTypes.ForwardMessageEvent, async (event: ForwardMessageEvent) => {
try {
const record = event.payload.connectionRecord

await agent.modules.pushNotificationsFcm.sendNotification(record.id)
} catch (error) {
agent.config.logger.error(`Error sending notification`, {
cause: error,
})
}
})
}
75 changes: 75 additions & 0 deletions src/push-notifications/fcm/PushNotificationsFcmApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { FcmDeviceInfo } from './models'

import {
OutboundMessageContext,
AgentContext,
ConnectionService,
injectable,
MessageSender,
} from '@aries-framework/core'

import { PushNotificationsFcmService } from './services/PushNotificationsFcmService'
import {
PushNotificationsFcmDeviceInfoHandler,
PushNotificationsFcmProblemReportHandler,
PushNotificationsFcmSetDeviceInfoHandler,
} from './handlers'

@injectable()
export class PushNotificationsFcmApi {
private messageSender: MessageSender
private pushNotificationsService: PushNotificationsFcmService
private connectionService: ConnectionService
private agentContext: AgentContext

public constructor(
messageSender: MessageSender,
pushNotificationsService: PushNotificationsFcmService,
connectionService: ConnectionService,
agentContext: AgentContext
) {
this.messageSender = messageSender
this.pushNotificationsService = pushNotificationsService
this.connectionService = connectionService
this.agentContext = agentContext

this.agentContext.dependencyManager.registerMessageHandlers([
new PushNotificationsFcmSetDeviceInfoHandler(this.pushNotificationsService),
new PushNotificationsFcmDeviceInfoHandler(),
new PushNotificationsFcmProblemReportHandler(this.pushNotificationsService),
])
}

/**
* Sends the requested fcm device info (token) to another agent via a `connectionId`
* Response for `push-notifications-fcm/get-device-info`
*
* @param connectionId The connection ID string
* @param threadId get-device-info message ID
* @param deviceInfo The FCM device info
* @returns Promise<void>
*/
public async deviceInfo(options: { connectionId: string; threadId: string; deviceInfo: FcmDeviceInfo }) {
const { connectionId, threadId, deviceInfo } = options
const connection = await this.connectionService.getById(this.agentContext, connectionId)
connection.assertReady()

const message = this.pushNotificationsService.createDeviceInfo({ threadId, deviceInfo })

const outbound = new OutboundMessageContext(message, {
agentContext: this.agentContext,
connection: connection,
})
await this.messageSender.sendMessage(outbound)
}

/**
* Send push notification to device
*
* @param connectionId The connection ID string
* @returns Promise<void>
*/
public async sendNotification(connectionId: string) {
return this.pushNotificationsService.sendNotification(this.agentContext, connectionId)
}
}
34 changes: 34 additions & 0 deletions src/push-notifications/fcm/PushNotificationsFcmModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { DependencyManager, FeatureRegistry, Module } from '@aries-framework/core'

import { Protocol } from '@aries-framework/core'

import { PushNotificationsFcmApi } from './PushNotificationsFcmApi'
import { PushNotificationsFcmService } from './services/PushNotificationsFcmService'
import { PushNotificationsFcmRole } from './models'
import { PushNotificationsFcmRepository } from './repository'

/**
* Module that exposes push notification get and set functionality
*/
export class PushNotificationsFcmModule implements Module {
public readonly api = PushNotificationsFcmApi

public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void {
// Api
dependencyManager.registerContextScoped(PushNotificationsFcmApi)

// Services
dependencyManager.registerSingleton(PushNotificationsFcmService)

// Repository
dependencyManager.registerSingleton(PushNotificationsFcmRepository)

// Feature Registry
featureRegistry.register(
new Protocol({
id: 'https://didcomm.org/push-notifications-fcm/1.0',
roles: [PushNotificationsFcmRole.Sender, PushNotificationsFcmRole.Receiver],
})
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { PushNotificationsFcmProblemReportReason } from './PushNotificationsFcmProblemReportReason'
import type { ProblemReportErrorOptions } from '@aries-framework/core'

import { ProblemReportError } from '@aries-framework/core'

import { PushNotificationsFcmProblemReportMessage } from '../messages'

/**
* @internal
*/
interface PushNotificationsFcmProblemReportErrorOptions extends ProblemReportErrorOptions {
problemCode: PushNotificationsFcmProblemReportReason
}

/**
* @internal
*/
export class PushNotificationsFcmProblemReportError extends ProblemReportError {
public problemReport: PushNotificationsFcmProblemReportMessage

public constructor(public message: string, { problemCode }: PushNotificationsFcmProblemReportErrorOptions) {
super(message, { problemCode })
this.problemReport = new PushNotificationsFcmProblemReportMessage({
description: {
en: message,
code: problemCode,
},
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Push Notification FCM errors discussed in RFC 0734.
*
* @see https://github.com/hyperledger/aries-rfcs/tree/main/features/0734-push-notifications-fcm#set-device-info
* @see https://github.com/hyperledger/aries-rfcs/tree/main/features/0734-push-notifications-fcm#device-info
* @internal
*/
export enum PushNotificationsFcmProblemReportReason {
MissingValue = 'missing-value',
NotRegistered = 'not-registered-for-push-notifications',
}
2 changes: 2 additions & 0 deletions src/push-notifications/fcm/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './PushNotificationsFcmProblemReportReason'
export * from './PushNotificationsFcmProblemReportError'
Loading