-
Notifications
You must be signed in to change notification settings - Fork 104
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CT-1040] Push Notifications (1 of 4) - Add Notification Package (#2185)
- Loading branch information
1 parent
7bca22d
commit 2ba777e
Showing
25 changed files
with
1,562 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Service Level Variables | ||
|
||
SERVICE_NAME=notifications |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
DB_NAME=dydx_dev | ||
DB_USERNAME=dydx_dev | ||
DB_PASSWORD=dydxserver123 | ||
PG_POOL_MAX=2 | ||
PG_POOL_MIN=1 | ||
DB_HOSTNAME=postgres | ||
DB_READONLY_HOSTNAME=postgres | ||
DB_PORT=5432 |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
SERVICE_NAME=notifications | ||
|
||
FIREBASE_PROJECT_ID=projectID | ||
FIREBASE_PRIVATE_KEY='-----BEGIN RSA PRIVATE KEY----------END RSA PRIVATE KEY-----' | ||
FIREBASE_CLIENT_EMAIL=[email protected] | ||
|
||
DB_NAME=dydx_test | ||
DB_USERNAME=dydx_test | ||
DB_PASSWORD=dydxserver123 | ||
DB_PORT=5436 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
const baseConfig = require('./node_modules/@dydxprotocol-indexer/dev/.eslintrc'); | ||
|
||
module.exports = { | ||
...baseConfig, | ||
|
||
// Override the base configuraiton to set the correct tsconfigRootDir. | ||
parserOptions: { | ||
...baseConfig.parserOptions, | ||
tsconfigRootDir: __dirname, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Notifications | ||
|
||
Notification package to create and send push notifications, using [Google Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) | ||
|
||
## Package Configuration | ||
|
||
### Firebase Setup Requirements | ||
|
||
To initialize Firebase correctly, this package requires the following three environment variables to be configured. These values can be obtained by downloading the service account file from the Firebase Admin Console: | ||
|
||
- FIREBASE_PRIVATE_KEY_BASE64: A Base64-encoded private key string. | ||
- FIREBASE_PROJECT_ID: The Firebase project ID as a string. | ||
- FIREBASE_CLIENT_EMAIL: The client email associated with the Firebase project. | ||
|
||
Each of these environment variables must be securely stored in AWS Secrets Manager for the service that uses the notifications package. | ||
|
||
For V1 of this project, which triggers notifications via Ender, these values must specifically be added to the Ender secrets vault in AWS. | ||
|
||
## Mobile App Token Registration | ||
|
||
To enable push notifications, users must register their devices by using the following endpoint: | ||
|
||
**POST** ```v4/:address/registerToken``` | ||
|
||
**Request Payload**: ```{ token: "<TOKEN_HASH>", language: 'en' }``` | ||
|
||
`token`: A valid push notification token generated by the Google Firebase SDK. | ||
|
||
`language`: A string representing the user's preferred language, following the ISO 639-1 standard. This must be one of the supported languages listed below: | ||
|
||
- 'en' (English) | ||
- 'es' (Spanish) | ||
- 'fr' (French) | ||
- 'de' (German) | ||
- 'it' (Italian) | ||
- 'ja' (Japanese) | ||
- 'ko' (Korean) | ||
- 'zh' (Chinese) |
65 changes: 65 additions & 0 deletions
65
indexer/packages/notifications/__tests__/localization.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { | ||
deriveLocalizedNotificationMessage, | ||
} from '../src/localization'; | ||
import { | ||
NotificationType, | ||
NotificationDynamicFieldKey, | ||
createNotification, | ||
isValidLanguageCode, | ||
} from '../src/types'; | ||
|
||
describe('deriveLocalizedNotificationMessage', () => { | ||
test('should generate a correct message for DepositSuccessNotification', () => { | ||
const notification = createNotification(NotificationType.DEPOSIT_SUCCESS, { | ||
[NotificationDynamicFieldKey.AMOUNT]: '1000', | ||
[NotificationDynamicFieldKey.MARKET]: 'USDT', | ||
}); | ||
|
||
const expected = { | ||
title: 'Deposit Successful', | ||
body: 'You have successfully deposited 1000 USDT to your dYdX account.', | ||
}; | ||
|
||
const result = deriveLocalizedNotificationMessage(notification); | ||
expect(result).toEqual(expected); | ||
}); | ||
|
||
test('should generate a correct message for OrderFilledNotification', () => { | ||
const notification = createNotification(NotificationType.ORDER_FILLED, { | ||
[NotificationDynamicFieldKey.MARKET]: 'BTC/USD', | ||
[NotificationDynamicFieldKey.AVERAGE_PRICE]: '45000', | ||
[NotificationDynamicFieldKey.AMOUNT]: '1000', | ||
}); | ||
|
||
const expected = { | ||
title: 'Order Filled', | ||
body: 'Your order for 1000 BTC/USD was filled at $45000', | ||
}; | ||
|
||
const result = deriveLocalizedNotificationMessage(notification); | ||
expect(result).toEqual(expected); | ||
}); | ||
|
||
describe('isValidLanguageCode', () => { | ||
test('should return true for valid language codes', () => { | ||
const validCodes = ['en', 'es', 'fr', 'de', 'it', 'ja', 'ko', 'zh']; | ||
validCodes.forEach((code) => { | ||
expect(isValidLanguageCode(code)).toBe(true); | ||
}); | ||
}); | ||
|
||
test('should return false for invalid language codes', () => { | ||
const invalidCodes = ['', 'EN', 'eng', 'esp', 'fra', 'deu', 'ita', 'jpn', 'kor', 'zho', 'xx']; | ||
invalidCodes.forEach((code) => { | ||
expect(isValidLanguageCode(code)).toBe(false); | ||
}); | ||
}); | ||
|
||
test('should return false for non-string inputs', () => { | ||
const nonStringInputs = [null, undefined, 123, {}, []]; | ||
nonStringInputs.forEach((input) => { | ||
expect(isValidLanguageCode(input as any)).toBe(false); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { logger } from '@dydxprotocol-indexer/base'; | ||
import { sendFirebaseMessage } from '../src/message'; | ||
import { sendMulticast } from '../src/lib/firebase'; | ||
import { createNotification, NotificationType } from '../src/types'; | ||
|
||
jest.mock('../src/lib/firebase', () => ({ | ||
sendMulticast: jest.fn(), | ||
})); | ||
|
||
describe('sendFirebaseMessage', () => { | ||
let loggerInfoSpy: jest.SpyInstance; | ||
let loggerWarnSpy: jest.SpyInstance; | ||
let loggerErrorSpy: jest.SpyInstance; | ||
|
||
beforeAll(() => { | ||
loggerInfoSpy = jest.spyOn(logger, 'info').mockImplementation(); | ||
loggerWarnSpy = jest.spyOn(logger, 'warning').mockImplementation(); | ||
loggerErrorSpy = jest.spyOn(logger, 'error').mockImplementation(); | ||
}); | ||
|
||
afterAll(() => { | ||
loggerInfoSpy.mockRestore(); | ||
loggerWarnSpy.mockRestore(); | ||
loggerErrorSpy.mockRestore(); | ||
}); | ||
|
||
const defaultToken = { | ||
token: 'faketoken', | ||
language: 'en', | ||
}; | ||
|
||
const mockNotification = createNotification(NotificationType.ORDER_FILLED, { | ||
AMOUNT: '10', | ||
MARKET: 'BTC-USD', | ||
AVERAGE_PRICE: '100.50', | ||
}); | ||
|
||
it('should send a Firebase message successfully', async () => { | ||
await sendFirebaseMessage( | ||
[{ token: defaultToken.token, language: defaultToken.language }], | ||
mockNotification, | ||
); | ||
|
||
expect(sendMulticast).toHaveBeenCalledWith(expect.objectContaining( | ||
{ | ||
tokens: [defaultToken.token], | ||
notification: { body: 'Your order for 10 BTC-USD was filled at $100.50', title: 'Order Filled' }, | ||
})); | ||
}); | ||
|
||
it('should log an error if sending the message fails', async () => { | ||
const mockedSendMulticast = sendMulticast as jest.MockedFunction<typeof sendMulticast>; | ||
mockedSendMulticast.mockRejectedValueOnce(new Error('Send failed')); | ||
|
||
await sendFirebaseMessage( | ||
[{ token: defaultToken.token, language: defaultToken.language }], | ||
mockNotification, | ||
); | ||
|
||
expect(logger.error).toHaveBeenCalledWith(expect.objectContaining({ | ||
message: 'Send failed', | ||
notificationType: mockNotification.type, | ||
})); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// Use the base configuration as-is. | ||
module.exports = require('./node_modules/@dydxprotocol-indexer/dev/jest.config'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// This function runs once before all tests. | ||
module.exports = () => { | ||
// This loads the environment variables for tests. | ||
// eslint-disable-next-line global-require | ||
require('dotenv-flow/config'); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
// This file runs before each test file. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
{ | ||
"name": "@dydxprotocol-indexer/notifications", | ||
"version": "0.0.1", | ||
"description": "", | ||
"main": "build/src/index.js", | ||
"devDependencies": { | ||
"@dydxprotocol-indexer/dev": "workspace:^0.0.1", | ||
"@types/jest": "^28.1.4", | ||
"jest": "^28.1.2", | ||
"typescript": "^4.7.4", | ||
"ts-node": "^10.8.2" | ||
}, | ||
"scripts": { | ||
"lint": "eslint --ext .ts,.js .", | ||
"lint:fix": "eslint --ext .ts,.js . --fix", | ||
"build": "rm -rf build/ && tsc", | ||
"build:prod": "pnpm run build", | ||
"build:watch": "pnpm run build -- --watch", | ||
"test": "NODE_ENV=test jest --runInBand --forceExit" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/dydxprotocol/indexer.git" | ||
}, | ||
"author": "", | ||
"license": "AGPL-3.0", | ||
"bugs": { | ||
"url": "https://github.com/dydxprotocol/indexer/issues" | ||
}, | ||
"homepage": "https://github.com/dydxprotocol/indexer#readme", | ||
"dependencies": { | ||
"firebase-admin": "^12.4.0", | ||
"@dydxprotocol-indexer/base": "workspace:^0.0.1", | ||
"@dydxprotocol-indexer/postgres": "workspace:^0.0.1", | ||
"dotenv-flow": "^3.2.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/** | ||
* Environment variables required for Notifications module. | ||
*/ | ||
|
||
import { | ||
parseString, | ||
parseSchema, | ||
baseConfigSchema, | ||
} from '@dydxprotocol-indexer/base'; | ||
|
||
export const notificationsConfigSchema = { | ||
...baseConfigSchema, | ||
|
||
// Private Key for the Google Firebase Messaging project | ||
// default is a dummy value | ||
FIREBASE_PRIVATE_KEY_BASE64: parseString({ default: 'LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tClBMQUNFSE9MREVSX0tFWV9GT1JfREVWRUxPUE1FTlQKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=' }), | ||
|
||
// APP ID for the Google Firebase Messaging project | ||
// default is a dummy value | ||
FIREBASE_PROJECT_ID: parseString({ default: 'dydx-v4' }), | ||
|
||
// Client email for the Google Firebase Messaging project | ||
// default is a dummy value | ||
FIREBASE_CLIENT_EMAIL: parseString({ default: '[email protected]' }), | ||
}; | ||
|
||
export default parseSchema(notificationsConfigSchema); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './lib/firebase'; | ||
export * from './localization'; | ||
export * from './types'; | ||
export * from './message'; |
Oops, something went wrong.