Skip to content

Commit

Permalink
Notifications (#584)
Browse files Browse the repository at this point in the history
* add NotificationsBroker to dispatch notification via arbitrary channels

* add nodemailer

* clena up mail package

* refine interface

* await send

* add NotificationBroker to incomingMessage

* read notificationChannels from config

* add formats to ajv

* add persistance for Notifications

* add endpoints to set user config

* fix build

* add docs

* fix typo in NotificationChannel

* move NotificationBroker to a dedicated file

* release new versions of shared and delivery

* release react v2.10

* fix imports

* fix imports #2

* fix: backend logging issue

---------

Co-authored-by: Heiko Burkhardt <[email protected]>
  • Loading branch information
AlexNi245 and hai-ko authored Sep 15, 2023
1 parent 9a98ce9 commit b367477
Show file tree
Hide file tree
Showing 41 changed files with 968 additions and 44 deletions.
45 changes: 41 additions & 4 deletions packages/backend/src/config/getDeliveryServiceProperties.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { getDeliveryServiceProperties } from './getDeliveryServiceProperties';
import { writeFileSync, unlinkSync, existsSync } from 'fs';
import { stringify } from 'yaml';
import { existsSync, unlinkSync, writeFileSync } from 'fs';
import { resolve } from 'path';
import { stringify } from 'yaml';
import { getDeliveryServiceProperties } from './getDeliveryServiceProperties';
import { NotificationChannelType } from 'dm3-lib-delivery';

describe('ReadDeliveryServiceProperties', () => {
let path: string;
Expand All @@ -19,9 +20,14 @@ describe('ReadDeliveryServiceProperties', () => {
const config = getDeliveryServiceProperties('/unknown-path', {
messageTTL: 12345,
sizeLimit: 456,
notificationChannel: [],
});

expect(config).toStrictEqual({ messageTTL: 12345, sizeLimit: 456 });
expect(config).toStrictEqual({
messageTTL: 12345,
sizeLimit: 456,
notificationChannel: [],
});
});

it('Returns Config from path', () => {
Expand All @@ -30,6 +36,21 @@ describe('ReadDeliveryServiceProperties', () => {
stringify({
messageTTL: 12345,
sizeLimit: 456,
notificationChannel: [
{
type: NotificationChannelType.EMAIL,
config: {
host: 'mail.alice.com',
port: 465,
secure: true,
auth: {
user: 'foo',
pass: 'bar',
},
senderAddress: '[email protected]',
},
},
],
}),
{ encoding: 'utf-8' },
);
Expand All @@ -38,6 +59,21 @@ describe('ReadDeliveryServiceProperties', () => {
expect(config).toStrictEqual({
messageTTL: 12345,
sizeLimit: 456,
notificationChannel: [
{
type: NotificationChannelType.EMAIL,
config: {
host: 'mail.alice.com',
port: 465,
secure: true,
auth: {
user: 'foo',
pass: 'bar',
},
senderAddress: '[email protected]',
},
},
],
});
});
it('Adds default properties if config.yml is not fully specified', () => {
Expand All @@ -53,6 +89,7 @@ describe('ReadDeliveryServiceProperties', () => {
expect(config).toStrictEqual({
messageTTL: 12345,
sizeLimit: 100000,
notificationChannel: [],
});
});
});
14 changes: 10 additions & 4 deletions packages/backend/src/config/getDeliveryServiceProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const DEFAULT_DELIVERY_SERVICE_PROPERTIES: DeliveryServiceProperties = {
messageTTL: 0,
//100Kb
sizeLimit: 100000,
notificationChannel: [],
};

export function getDeliveryServiceProperties(
Expand All @@ -28,7 +29,14 @@ export function getDeliveryServiceProperties(
//The interface DeliveryServiceProperties requires all properties to be non-null. But since we are accepting a partially filled config.yml we are overwriting the required fields so basically no property is required at all. This can be done because every missing property is replaced by a default property
{
...schema.DeliveryServiceProperties,
required: [],
definitions: {
...schema.DeliveryServiceProperties.definitions,
DeliveryServiceProperties: {
...schema.DeliveryServiceProperties.definitions
.DeliveryServiceProperties,
required: [],
},
},
},
deliveryServiceProfile,
);
Expand All @@ -37,10 +45,8 @@ export function getDeliveryServiceProperties(
throw Error('Invalid config.yml');
}

const { messageTTL, sizeLimit } = {
return {
...defaultDeliveryServiceProperties,
...parse(yamlString),
} as DeliveryServiceProperties;

return { messageTTL, sizeLimit };
}
2 changes: 2 additions & 0 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
readKeysFromEnv,
socketAuth,
} from './utils';
import Notifications from './notifications';

const app = express();
app.use(express.json({ limit: '50mb' }));
Expand Down Expand Up @@ -69,6 +70,7 @@ global.logger = winston.createLogger({
app.use('/storage', Storage());
app.use('/auth', Auth());
app.use('/delivery', Delivery());
app.use('/notifications', Notifications());
app.use('/rpc', RpcProxy(new Axios({ url: process.env.RPC })));
app.use(logError);
app.use(errorHandler);
Expand Down
80 changes: 75 additions & 5 deletions packages/backend/src/messaging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ describe('Messaging', () => {
encryption: keysA.encryptionKeyPair,
},

deliveryServiceProperties: { sizeLimit: 2 ** 14 },
deliveryServiceProperties: {
sizeLimit: 2 ** 14,
notificationChannel: [],
},
web3Provider: {
resolveName: async () =>
'0x25A643B6e52864d0eD816F1E43c0CF49C83B8292',
Expand All @@ -63,9 +66,72 @@ describe('Messaging', () => {
getSession,
createMessage: () => {},
getIdEnsName: async (ensName: string) => ensName,
getUsersNotificationChannels: () => Promise.resolve([]),
},
redisClient: {
zAdd: () => {},
io: {
sockets: {
to: (_: any) => ({
emit: (_: any, __any: any) => {},
}),
},
},
} as any,
} as express.Express & WithLocals;

//The same data used in Messages.test
const data = {
envelop: testData.envelopA,
token: '123',
};

const getSocketMock = () => {
return {
on: async (name: string, onSubmitMessage: any) => {
//We just want to test the submitMessage callback fn
if (name === 'submitMessage') {
await onSubmitMessage(data, callback);
}
},
} as unknown as Socket;
};

onConnection(app)(getSocketMock());
});
it('sends notification after message was submitted', (done: any) => {
//We expect the callback functions called once witht he value 'success'
expect.assertions(1);
const callback = (e: any) => {
// eslint-disable-next-line max-len
//Even though the method fails jest dosen't recognize it becuase of the catch block used in messaging.ts. So we have to throw another error if the callback returns anything else then the expected result.
if (e.response !== 'success') {
throw Error(e);
}
expect(e.response).toBe('success');
done();
};
//We provide an mocked express app with all needes locals vars
const app = {
locals: {
logger,
keys: {
signing: keysA.signingKeyPair,
encryption: keysA.encryptionKeyPair,
},

deliveryServiceProperties: {
sizeLimit: 2 ** 14,
notificationChannel: [],
},
web3Provider: {
resolveName: async () =>
'0x25A643B6e52864d0eD816F1E43c0CF49C83B8292',
},

db: {
getSession,
createMessage: () => {},
getIdEnsName: async (ensName: string) => ensName,
getUsersNotificationChannels: () => Promise.resolve([]),
},
io: {
sockets: {
Expand Down Expand Up @@ -96,7 +162,6 @@ describe('Messaging', () => {

onConnection(app)(getSocketMock());
});
//TODO remove skip once spam-filter is implemented
it.skip('returns error if message is spam', (done: any) => {
//We expect the callback functions called once witht he value 'success'
expect.assertions(1);
Expand Down Expand Up @@ -125,12 +190,16 @@ describe('Messaging', () => {
encryption: keysA.encryptionKeyPair,
},

deliveryServiceProperties: { sizeLimit: 2 ** 14 },
deliveryServiceProperties: {
sizeLimit: 2 ** 14,
notificationChannel: [],
},

db: {
getSession: session,
createMessage: () => {},
getIdEnsName: async (ensName: string) => ensName,
getUsersNotificationChannels: () => Promise.resolve([]),
},
web3Provider: {
getTransactionCount: (_: string) => Promise.resolve(0),
Expand Down Expand Up @@ -199,6 +268,7 @@ describe('Messaging', () => {
db: {
getSession,
getIdEnsName: async (ensName: string) => ensName,
getUsersNotificationChannels: () => Promise.resolve([]),
},
} as any,
} as express.Express & WithLocals;
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export function onConnection(app: express.Application & WithLocals) {
app.locals.keys.signing,
app.locals.keys.encryption,
app.locals.deliveryServiceProperties.sizeLimit,
app.locals.deliveryServiceProperties
.notificationChannel,
app.locals.db.getSession,
app.locals.db.createMessage,
(socketId: string, envelop: EncryptionEnvelop) => {
Expand All @@ -69,6 +71,7 @@ export function onConnection(app: express.Application & WithLocals) {
},
app.locals.web3Provider,
app.locals.db.getIdEnsName,
app.locals.db.getUsersNotificationChannels,
),
callback({ response: 'success' });
} catch (error: any) {
Expand Down
Loading

0 comments on commit b367477

Please sign in to comment.