Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6d9d0fe
feat: retryNotify job
zasulskii Aug 13, 2024
f87cf29
feat: add logs
zasulskii Aug 13, 2024
aae9a93
feat: config validation, postinstall script
zasulskii Aug 13, 2024
0fb0171
chore: fix index to config rename
zasulskii Aug 13, 2024
89f3a90
fix: logger error message
zasulskii Aug 13, 2024
2a32735
feat: refactor notification service, remove DI
zasulskii Aug 14, 2024
c267081
chore: remove unused file
zasulskii Aug 14, 2024
ac109d9
feat: shutdown, event-based txs
zasulskii Aug 15, 2024
2ff63de
feat: fast transactions fetch after service is shutdown
zasulskii Aug 15, 2024
63c5a07
feat: remove transactions jobs, refactor to transaction channel
zasulskii Aug 16, 2024
2a68205
feat: transaction channel destroy
zasulskii Aug 19, 2024
60f456d
refactor: message to messageMany while processing txs to notify
zasulskii Aug 19, 2024
59defd6
fix: handle transaction ref to unsub from socket
zasulskii Aug 20, 2024
23feef3
docs(README.md): register/unregister device transaction payload
bludnic Dec 4, 2024
cccf684
chore(package.json): upgrade husky
bludnic Dec 4, 2024
1528248
chore: gitignore .env
bludnic Dec 4, 2024
0649db0
chore(package.json): upgrade dependencies
bludnic Dec 4, 2024
a249cf7
fix(build): disable DTS (build error)
bludnic Dec 4, 2024
a0866f6
Merge remote-tracking branch 'origin/dev' into dev
bludnic Dec 4, 2024
9077201
chore: gitignore package-lock.json
bludnic Dec 4, 2024
0432d7d
chore(Node): enable source map support for stack traces
bludnic Dec 6, 2024
71fd99b
chore: configure dotenv
bludnic Dec 6, 2024
d02b247
refactor: remove DATABASE_URL from config (use ENV instead)
bludnic Dec 6, 2024
d29d8b5
fix: unhandled error when processing a Signal Transaction
bludnic Dec 6, 2024
7ac2cc3
chore: configure Vitest
bludnic Dec 7, 2024
d3a103e
chore(prisma): `deviceId` should be provided by the client
bludnic Dec 7, 2024
3ad6f12
feat(zod): add runtime type for `SignalMessagePayload`
bludnic Dec 7, 2024
aa3c4d7
refactor: simplify fastify server
bludnic Dec 7, 2024
9bc5571
refactor: simplify logger
bludnic Dec 7, 2024
3f953dd
refactor(prisma): simplify `checkConnection()`
bludnic Dec 7, 2024
9173b16
refactor(config): prettify
bludnic Dec 7, 2024
d14ddda
refactor(prettier): print semicolons at the ends of statements
bludnic Dec 7, 2024
bb23787
refactor: organize gitignore
bludnic Dec 7, 2024
e04a87f
chore(TypeScript): enable JSON modules
bludnic Dec 7, 2024
dfeebc4
refactor: simplify the config
bludnic Dec 7, 2024
c557205
chore(npm): add `typecheck` command
bludnic Dec 7, 2024
7e88715
refactor(config): sort imports
bludnic Dec 7, 2024
46af467
docs(README.md): provider should be uppercased
bludnic Dec 7, 2024
fdc0777
chore: add .env.example
bludnic Dec 7, 2024
5d16a65
refactor(firebase): rename config example
bludnic Dec 7, 2024
4695e4d
chore: upgrade fastify
bludnic Dec 7, 2024
58599eb
chore(fastify): add zod validation
bludnic Dec 7, 2024
e5ffe76
refactor(TransactionsChannel): simplify
bludnic Dec 7, 2024
3af6933
refactor: improve TransactionChannel
bludnic Dec 8, 2024
2844196
feat(TransactionChannel): type on/emit methods
bludnic Dec 8, 2024
3a9edbf
feat: refactor service
bludnic Dec 12, 2024
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ node_modules
dist/
.idea
firebase-credentials.json
logs/*
!/logs/.gitkeep
config.json5
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# ADAMANT Notification Service (ANS)

## Configuration
1. Fill `config5.json` file with configuration settings (`config.sample.json5` as an example)
2. Create `firebase-credentials.json` file (`firebase-credentials-example.json` as an example)
3. `pnpm install`
4. `export DATABASE_URL=$database-url`
5. `npx prisma migrate deploy`
6. `pnpm run build`
7. `pnpm run start`

## How it works

To deliver notifcations privately and secure, 4 parties are involved:
Expand Down
9 changes: 4 additions & 5 deletions config.json5 → config.sample.json5
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
/**
Pass phrase for the incoming transaction's bot
**/
"passPhrase": "pig update shiver various bus van tourist sister plastic card reform satisfy",
"passPhrase": "",

/**
Transaction types to notify
Expand All @@ -62,9 +62,8 @@
"chatTxTypeIncludeSubtype": [1, 2],

/**
Notification service to use
APNS, FCM
If the service has been shut down, it will process blocks from the moment of switching off.
The setting specifies how many last blocks to notify users about new transactions
**/

"notificationService": "FCM"
"latestHeightToNotify": 500
}
3 changes: 2 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default tseslint.config({
'@typescript-eslint/no-unsafe-assignment': 'error',
'@typescript-eslint/no-unsafe-call': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
'@typescript-eslint/no-unsafe-return': 'error'
'@typescript-eslint/no-unsafe-return': 'error',
'@typescript-eslint/no-explicit-any': 'off'
}
})
257 changes: 0 additions & 257 deletions logs/index.log

This file was deleted.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write ./src",
"postinstall": "husky install"
"postinstall": "husky install && prisma generate"
},
"author": "ADAMANT Foundation <[email protected]>",
"license": "GPL-3.0",
Expand Down Expand Up @@ -57,6 +57,7 @@
"firebase-admin": "^12.2.0",
"json5": "^2.2.3",
"node-cron": "^3.0.3",
"prisma": "^5.16.0"
"prisma": "^5.16.0",
"zod": "^3.23.8"
}
}
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Warnings:

- Added the required column `admTx` to the `NotifyTransaction` table without a default value. This is not possible if the table is not empty.

*/
-- AlterTable
ALTER TABLE "NotifyTransaction" ADD COLUMN "admTx" JSONB NOT NULL;
3 changes: 3 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ model Device {
model NotifyTransaction {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())

admTxId String @db.VarChar(20)
admTxDate DateTime
admTx Json

isNotified Boolean @default(false)
lastNotifyDate DateTime?

Expand Down
24 changes: 0 additions & 24 deletions send.cjs

This file was deleted.

14 changes: 14 additions & 0 deletions src/adapters/notification/apnsNotification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { BaseNotificationInterface } from './baseNotification.js'
import { PushServiceProvider } from '../../types/models.js'

export class ApnsNotification implements BaseNotificationInterface {
provider = PushServiceProvider.APNS

message(): Promise<string> {
throw new Error('Not implemented')
}

messageMany() {
throw new Error('Not implemented')
}
}
20 changes: 5 additions & 15 deletions src/adapters/notification/baseNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,13 @@ export interface BaseNotificationInterface {

message(
pushToken: string,
notification: { title: string; body: string }
): void
notification: { title: string; body: string },
data?: { [key: string]: string }
): Promise<string>

messageMany(
pushTokens: string[],
notification: { title: string; body: string }
notification: { title: string; body: string },
data?: { [key: string]: string }
): void
}

export class BaseNotification implements BaseNotificationInterface {
provider: PushServiceProvider | undefined

message(): void {
throw new Error('Not implemented')
}

messageMany(): void {
throw new Error('Not implemented')
}
}
36 changes: 28 additions & 8 deletions src/adapters/notification/fcmNotification.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,48 @@
import { BaseNotificationInterface } from './baseNotification.js'
import admin from 'firebase-admin'
import App = admin.app.App
import admin, { ServiceAccount } from 'firebase-admin'
import { PushServiceProvider } from '../../types/models.js'
import JSON5 from 'json5'
import fs from 'fs'
import path from 'path'
import { config } from '../../config/index.js'

export class FcmNotification implements BaseNotificationInterface {
private service
provider = PushServiceProvider.FCM

constructor(fcmClient: App) {
this.service = fcmClient
constructor() {
const credentials = JSON5.parse(
fs.readFileSync(
path.join(config.app.projectRoot, 'firebase-credentials.json'),
'utf8'
)
) as ServiceAccount

this.service = admin.initializeApp({
credential: admin.credential.cert(credentials)
})
}

message(pushToken: string, notification: { title: string; body: string }) {
this.service.messaging().send({ notification, token: pushToken })
message(
pushToken: string,
notification: { title: string; body: string },
data?: { [key: string]: string }
) {
return this.service
.messaging()
.send({ notification, token: pushToken, ...(data ? { data } : {}) })
}

messageMany(
pushTokens: string[],
notification: { title: string; body: string }
notification: { title: string; body: string },
data?: { [key: string]: string }
) {
this.service.messaging().sendEach(
pushTokens.map((pushToken) => ({
notification,
token: pushToken
token: pushToken,
...(data ? { data } : {})
}))
)
}
Expand Down
34 changes: 15 additions & 19 deletions src/config.ts → src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,30 @@ import {
createAddressFromPublicKey,
createKeypairFromPassphrase
} from 'adamant-api'
import { schema, Schema as ConfigSchema } from './schema.js'
import { fromZodError } from '../utils/zod.js'

const projectRoot = process.cwd()

interface ConfigFile {
database: {
url: string
}
app: {
port: number
}
notificationExpiryHours: number
admNodes: string[]
passPhrase: string
notifyTxTypes: number[]
chatTxTypeIncludeSubtype: number[]
notificationService: 'FCM' | 'APNS'
}

interface PackageFile {
version: string
}

const configFile = JSON5.parse(
fs.readFileSync(path.join(projectRoot, 'config.json5'), 'utf8')
) as ConfigFile
) as ConfigSchema
const packageFile = JSON5.parse(
fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')
) as PackageFile

const result = schema.safeParse(configFile)

if (!result.success) {
const message = fromZodError(result.error)

throw new Error(`Service's config is wrong:\n${message}Cannot start the bot.`)
}

export const config = {
app: {
name: 'adamant-ns',
Expand All @@ -42,8 +37,8 @@ export const config = {
projectRoot,
notificationExpiryHours: configFile.notificationExpiryHours,
txCheckInterval: '*/4 * * * * *', // in cron language
heightSkipPerHeight: 1,
notificationService: configFile.notificationService
retryNotifyInterval: '*/4 * * * * *', // */10 * * * * in cron language
heightSkipPerHeight: 1
},
database: {
url: configFile.database.url
Expand All @@ -52,7 +47,8 @@ export const config = {
notify: {
passPhrase: configFile.passPhrase,
notifyTxTypes: configFile.notifyTxTypes,
chatTxTypeIncludeSubtype: configFile.chatTxTypeIncludeSubtype
chatTxTypeIncludeSubtype: configFile.chatTxTypeIncludeSubtype,
latestHeightToNotify: configFile.latestHeightToNotify
},
adamantAccount: {
passPhrase: configFile.passPhrase,
Expand Down
22 changes: 22 additions & 0 deletions src/config/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { z } from 'zod'

export const schema = z
.object({
database: z.object({
url: z.string()
}),
app: z.object({
port: z.number()
}),
notificationExpiryHours: z.number(),
admNodes: z.array(z.string()),
passPhrase: z.string(),
notifyTxTypes: z.array(z.number()),
chatTxTypeIncludeSubtype: z.array(z.number()),
latestHeightToNotify: z.number()
})
.strict() /* Throw error on unknown properties. This will help users to migrate from the
* older versions of the bot that use different config schema
*/

export type Schema = z.infer<typeof schema>
Loading