-
-
Notifications
You must be signed in to change notification settings - Fork 2
Initial version #1
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
Open
bludnic
wants to merge
46
commits into
master
Choose a base branch
from
dev
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
6d9d0fe
feat: retryNotify job
zasulskii f87cf29
feat: add logs
zasulskii aae9a93
feat: config validation, postinstall script
zasulskii 0fb0171
chore: fix index to config rename
zasulskii 89f3a90
fix: logger error message
zasulskii 2a32735
feat: refactor notification service, remove DI
zasulskii c267081
chore: remove unused file
zasulskii ac109d9
feat: shutdown, event-based txs
zasulskii 2ff63de
feat: fast transactions fetch after service is shutdown
zasulskii 63c5a07
feat: remove transactions jobs, refactor to transaction channel
zasulskii 2a68205
feat: transaction channel destroy
zasulskii 60f456d
refactor: message to messageMany while processing txs to notify
zasulskii 59defd6
fix: handle transaction ref to unsub from socket
zasulskii 23feef3
docs(README.md): register/unregister device transaction payload
bludnic cccf684
chore(package.json): upgrade husky
bludnic 1528248
chore: gitignore .env
bludnic 0649db0
chore(package.json): upgrade dependencies
bludnic a249cf7
fix(build): disable DTS (build error)
bludnic a0866f6
Merge remote-tracking branch 'origin/dev' into dev
bludnic 9077201
chore: gitignore package-lock.json
bludnic 0432d7d
chore(Node): enable source map support for stack traces
bludnic 71fd99b
chore: configure dotenv
bludnic d02b247
refactor: remove DATABASE_URL from config (use ENV instead)
bludnic d29d8b5
fix: unhandled error when processing a Signal Transaction
bludnic 7ac2cc3
chore: configure Vitest
bludnic d3a103e
chore(prisma): `deviceId` should be provided by the client
bludnic 3ad6f12
feat(zod): add runtime type for `SignalMessagePayload`
bludnic aa3c4d7
refactor: simplify fastify server
bludnic 9bc5571
refactor: simplify logger
bludnic 3f953dd
refactor(prisma): simplify `checkConnection()`
bludnic 9173b16
refactor(config): prettify
bludnic d14ddda
refactor(prettier): print semicolons at the ends of statements
bludnic bb23787
refactor: organize gitignore
bludnic e04a87f
chore(TypeScript): enable JSON modules
bludnic dfeebc4
refactor: simplify the config
bludnic c557205
chore(npm): add `typecheck` command
bludnic 7e88715
refactor(config): sort imports
bludnic 46af467
docs(README.md): provider should be uppercased
bludnic fdc0777
chore: add .env.example
bludnic 5d16a65
refactor(firebase): rename config example
bludnic 4695e4d
chore: upgrade fastify
bludnic 58599eb
chore(fastify): add zod validation
bludnic e5ffe76
refactor(TransactionsChannel): simplify
bludnic 3af6933
refactor: improve TransactionChannel
bludnic 2844196
feat(TransactionChannel): type on/emit methods
bludnic 3a9edbf
feat: refactor service
bludnic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 was deleted.
Oops, something went wrong.
This file contains hidden or 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,167 @@ | ||
| import EventEmitter from 'events' | ||
| import { AnyTransaction, WebSocketClient } from 'adamant-api' | ||
| import { schedule } from 'node-cron' | ||
| import { logger } from '../../modules/logger.js' | ||
| import { config } from '../../config/index.js' | ||
| import { adamantClient } from '../../modules/adamantClient.js' | ||
| import { prisma } from '../../modules/prisma.js' | ||
| import { JobName } from '@prisma/client' | ||
| import { isSignalTx } from '../../services/parser/isSignalTx.js' | ||
| import { isTxToNotify } from '../../services/parser/isTxToNotify.js' | ||
| import { ChatMessageTransaction } from 'adamant-api/dist/api/generated.js' | ||
|
|
||
| class TransactionsChannel extends EventEmitter { | ||
| // Specify types of events | ||
| override emit(event: 'newSignalMessage', tx: ChatMessageTransaction): boolean | ||
| override emit(event: 'newMessage', tx: AnyTransaction): boolean | ||
| override emit(event: string, ...args: never[]): boolean { | ||
| return super.emit(event, ...args) | ||
| } | ||
|
|
||
| override on( | ||
| event: 'newSignalMessage', | ||
| listener: (tx: ChatMessageTransaction) => void | ||
| ): this | ||
| override on(event: 'newMessage', listener: (tx: AnyTransaction) => void): this | ||
| override on(event: string, listener: (...args: any[]) => void): this { | ||
| return super.on(event, listener) | ||
| } | ||
|
|
||
| private isLocked: boolean | ||
| private processedTxs: { [key: string]: AnyTransaction } = {} // cache for processed transactions | ||
| adamantSocket?: WebSocketClient | ||
|
|
||
| constructor() { | ||
| super() | ||
| this.isLocked = false | ||
| } | ||
|
|
||
| initSocket() { | ||
| adamantClient.initSocket({ | ||
| wsType: 'ws', | ||
| admAddress: config.adamantAccount.address | ||
| }) | ||
| logger.info( | ||
| `Adamant Client socket initialized on ${config.adamantAccount.address} address` | ||
| ) | ||
|
|
||
| if (adamantClient.socket) { | ||
| adamantClient.socket.on((tx) => { | ||
| this.handleTransaction(tx) | ||
| }) | ||
| adamantClient.socket.catch((error) => logger.error(error)) | ||
|
|
||
| this.adamantSocket = adamantClient.socket | ||
| } | ||
| } | ||
|
|
||
| startJob() { | ||
| logger.info( | ||
| `Spawned transaction parser job with ${config.app.txCheckInterval} interval` | ||
| ) | ||
| return schedule(config.app.txCheckInterval, async () => { | ||
| if (this.isLocked) return | ||
|
|
||
| this.isLocked = true | ||
|
|
||
| try { | ||
| const getHeightResponse = await adamantClient.getHeight() | ||
| const currentHeight = getHeightResponse.success | ||
| ? getHeightResponse.height | ||
| : 0 | ||
|
|
||
| let jobStatus = await prisma.cronJobStatus.findFirst({ | ||
| where: { jobName: JobName.TRANSACTIONS } | ||
| }) | ||
|
|
||
| if (!jobStatus) { | ||
| jobStatus = await prisma.cronJobStatus.create({ | ||
| data: { | ||
| jobName: JobName.TRANSACTIONS, | ||
| state: JSON.stringify({ | ||
| lastHeight: currentHeight | ||
| }) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| const lastCheckHeight = ( | ||
| JSON.parse(jobStatus.state as string) as { lastHeight: number } | ||
| ).lastHeight | ||
|
|
||
| // Determine fetch interval | ||
| let heightToFetch = lastCheckHeight + config.app.heightSkipPerHeight | ||
|
|
||
| if (heightToFetch > currentHeight) { | ||
| this.isLocked = false | ||
| return | ||
| } | ||
|
|
||
| if (currentHeight - heightToFetch > 1) { | ||
| // If the gap is more than 1 block, fetch transactions in chunks of 1000 blocks | ||
| heightToFetch = lastCheckHeight + 1000 | ||
|
|
||
| if (currentHeight - heightToFetch < 0) { | ||
| // if the gap reaches currenHeight and more, than set it to currentHeight | ||
| heightToFetch = currentHeight | ||
| } | ||
| } | ||
|
|
||
| const txs = await adamantClient.getTransactions({ | ||
| fromHeight: lastCheckHeight, | ||
| and: { | ||
| toHeight: heightToFetch | ||
| }, | ||
| returnAsset: 1 | ||
| }) | ||
|
|
||
| if (!txs.success) { | ||
| this.isLocked = false | ||
| return | ||
| } | ||
|
|
||
| txs.transactions.forEach((tx) => { | ||
| if (tx.height < currentHeight - config.notify.latestHeightToNotify) { | ||
| // skip if transaction is too old | ||
| return | ||
| } | ||
|
|
||
| this.handleTransaction(tx) | ||
| }) | ||
|
|
||
| await prisma.cronJobStatus.update({ | ||
| where: { jobName: JobName.TRANSACTIONS }, | ||
| data: { | ||
| state: JSON.stringify({ | ||
| lastHeight: heightToFetch | ||
| }) | ||
| } | ||
| }) | ||
| } catch (error) { | ||
| logger.error(error, 'Error while running transactions channel job') | ||
| } finally { | ||
| this.isLocked = false | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| handleTransaction(tx: AnyTransaction) { | ||
| if (this.processedTxs[tx.id]) { | ||
| delete this.processedTxs[tx.id] // removing from cache because we got tx again from rest api or socket | ||
| return | ||
| } | ||
|
|
||
| this.processedTxs[tx.id] = tx | ||
|
|
||
| if (isSignalTx(tx)) { | ||
| logger.info( | ||
| `Got signal transaction to (un)subscribe to notifications, txId: ${tx.id}, processing...` | ||
| ) | ||
| this.emit('newSignalMessage', tx as ChatMessageTransaction) | ||
| } else if (isTxToNotify(tx)) { | ||
| this.emit('newMessage', tx) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export const transactionsChannel = new TransactionsChannel() | ||
This file contains hidden or 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 hidden or 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 hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using
schedule, you can use a regularsetTimeoutthat invokes itself after each iteration. In this case, you can remove thethis.isLockedcheck, as invoking the callback multiple times won't be possible.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally add
destroymethod to this class that will:adamantClient.socket.off(callback))Invoke
transactionsChannel.destroy()from main.ts on SIGINT/SIGTERM