diff --git a/docker-compose.yml b/docker-compose.yml index 846fda29..f8aea9fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,6 +72,8 @@ services: RECEIVER: atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep API_ROOT: 'http://api-main:3000/v1' AUTH: dev + COMMUNITY_ACCOUNT_V1: atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep + # Uncomment to enable fast sync # ECLESIA_GRAPHQL_ENDPOINT: "https://graphql-atomone-testnet-1.allinbits.services/v1/graphql" # ECLESIA_GRAPHQL_SECRET: "" diff --git a/packages/frontend-main/public/csp.json b/packages/frontend-main/public/csp.json index 98044a80..bedd6c64 100644 --- a/packages/frontend-main/public/csp.json +++ b/packages/frontend-main/public/csp.json @@ -26,6 +26,7 @@ "https://cloud.umami.is", "https://api-gateway.umami.dev", "https://*.dither.chat", - "https://*.allinbits.com" + "https://*.allinbits.com", + "https://*.allinbits.services" ] } diff --git a/packages/reader-main/docker-compose.yml b/packages/reader-main/docker-compose.yml index a16b2424..7a0c96fb 100644 --- a/packages/reader-main/docker-compose.yml +++ b/packages/reader-main/docker-compose.yml @@ -6,11 +6,17 @@ services: restart: always container_name: chronosync environment: - API_URLS: 'https://atomone-api.allinbits.com,https://atomone-rest.publicnode.com' + # Mainnet + # API_URLS: 'https://atomone-rest.publicnode.com' + # + # Testnet + API_URLS: 'https://atomone-testnet-1-api.allinbits.services' + START_BLOCK: '2605764' BATCH_SIZE: 50 MEMO_PREFIX: dither. RECEIVER: atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep AUTH: dev + COMMUNITY_ACCOUNT_V1: atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep # LOG: process.env.LOG === 'true' ? true : false, command: [bun, run, start] diff --git a/packages/reader-main/src/config/index.ts b/packages/reader-main/src/config/index.ts index 0a1c72eb..d88cdd04 100644 --- a/packages/reader-main/src/config/index.ts +++ b/packages/reader-main/src/config/index.ts @@ -6,6 +6,7 @@ let config: Config & { AUTH: string; ECLESIA_GRAPHQL_ENDPOINT?: string; ECLESIA_GRAPHQL_SECRET?: string; + COMMUNITY_ACCOUNT_V1: string; }; export function useConfig(): typeof config { @@ -18,6 +19,10 @@ export function useConfig(): typeof config { throw new Error(`AUTH must be set to a strong secret`); } + if (!process.env.COMMUNITY_ACCOUNT_V1) { + throw new Error(`COMMUNITY_ACCOUNT_V1 value is required`); + } + config = { API_URLS: process.env.API_URLS ? process.env.API_URLS.split(',') : [], MEMO_PREFIX: process.env.MEMO_PREFIX, @@ -30,6 +35,8 @@ export function useConfig(): typeof config { ECLESIA_GRAPHQL_ENDPOINT: process.env.ECLESIA_GRAPHQL_ENDPOINT, ECLESIA_GRAPHQL_SECRET: process.env.ECLESIA_GRAPHQL_SECRET, + + COMMUNITY_ACCOUNT_V1: process.env.COMMUNITY_ACCOUNT_V1, }; return config; diff --git a/packages/reader-main/src/index.ts b/packages/reader-main/src/index.ts index c160c124..377c3801 100644 --- a/packages/reader-main/src/index.ts +++ b/packages/reader-main/src/index.ts @@ -9,7 +9,7 @@ import { ChronoState } from '@atomone/chronostate'; import { useConfig } from './config/index'; import { EclesiaClient } from './eclesia/client'; -import { MessageHandlers } from './messages/index'; +import { messages } from './messages'; import { useQueue } from './queue'; const config = useConfig(); @@ -97,13 +97,6 @@ async function processAction(action: Action): Promise { } const actionType = match[1]; - - const actionTypeKey = actionType as keyof typeof MessageHandlers; - if (!MessageHandlers[actionTypeKey]) { - console.warn(`Skipped ${action.hash}, unknown action type: ${actionType}`); - return 'SKIP'; - } - const transfer = getTransferMessage(action.messages as unknown as Array); const quantity = getTransferQuantities(action.messages as unknown as Array); if (!transfer) { @@ -111,7 +104,13 @@ async function processAction(action: Action): Promise { return 'SKIP'; } - return await MessageHandlers[actionTypeKey]({ ...action, sender: transfer.from_address, quantity }); + const handler = messages.get(transfer.to_address, actionType); + if (!handler) { + console.warn(`Skipped ${action.hash}, unknown action type: ${actionType}`); + return 'SKIP'; + } + + return await handler({ ...action, sender: transfer.from_address, quantity }); } async function updateLastBlock(height: string, attempt = 0) { @@ -216,7 +215,7 @@ export async function start() { if (Number.parseInt(config.START_BLOCK) > lastBlockStored) { console.info(`START_BLOCK is higher than last block stored, starting from START_BLOCK=${config.START_BLOCK}`); - config.START_BLOCK = lastBlockStored.toString(); + startBlock = Number.parseInt(config.START_BLOCK); } else { startBlock = lastBlockStored; } diff --git a/packages/reader-main/src/messages/index.ts b/packages/reader-main/src/messages/index.ts index 74d530d4..4323bcb5 100644 --- a/packages/reader-main/src/messages/index.ts +++ b/packages/reader-main/src/messages/index.ts @@ -1,19 +1,24 @@ -import { Dislike } from './dislike'; -import { Flag } from './flag'; -import { Follow } from './follow'; -import { Like } from './like'; -import { Post } from './post'; -import { Remove } from './remove'; -import { Reply } from './reply'; -import { Unfollow } from './unfollow'; +import { useConfig } from '../config'; +import { Messages } from './messages'; +import { v1 } from './v1'; -export const MessageHandlers = { - Dislike, - Flag, - Follow, - Like, - Post, - Remove, - Reply, - Unfollow, -}; +const config = useConfig(); + +// Messages contains message handlers for different Dither protocol versions. +// +// Versions are defined by the address of the destination account used in the +// send messages. +// +// New versions must be created when the protocol changes because new functions +// are added, or because existing function parameters change. A new version means +// that the destination account for the send transactions must be changed to a +// different one. +// +// Dither protocol is open, it uses a memo field and a send message, so anyone +// can "pollute" Dither namespace sending multiple transactions using different +// non existent function names, which might be expected to be supported in the +// future. Apart from polluting the namespace they could also generate issues +// when replaying historical transactions to restore the data. +export const messages = new Messages({ + [config.COMMUNITY_ACCOUNT_V1]: v1, +}); diff --git a/packages/reader-main/src/messages/messages.ts b/packages/reader-main/src/messages/messages.ts new file mode 100644 index 00000000..be41fd79 --- /dev/null +++ b/packages/reader-main/src/messages/messages.ts @@ -0,0 +1,28 @@ +import type { ActionWithData, ResponseStatus } from '../types'; + +export type MessageHandler = (action: ActionWithData) => Promise; +export type Handlers = Record>; + +// Messages keeps track of message handlers for different versions. +export class Messages { + private handlers: Handlers; + + constructor(handlers: Handlers) { + this.handlers = handlers; + } + + // Registers messages for a specific version. + register(version: string, handlers: Record) { + this.handlers[version] = handlers; + } + + // Gets a message handler by name for a specific version. + get(version: string, name: string): MessageHandler | undefined { + return this.handlers[version]?.[name]; + } + + // Lists registered versions. + versions(): string[] { + return Object.keys(this.handlers); + } +} diff --git a/packages/reader-main/src/messages/dislike.ts b/packages/reader-main/src/messages/v1/dislike.ts similarity index 94% rename from packages/reader-main/src/messages/dislike.ts rename to packages/reader-main/src/messages/v1/dislike.ts index 15a7b78c..57eda6f8 100644 --- a/packages/reader-main/src/messages/dislike.ts +++ b/packages/reader-main/src/messages/v1/dislike.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config'; +import { useConfig } from '../../config'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/flag.ts b/packages/reader-main/src/messages/v1/flag.ts similarity index 94% rename from packages/reader-main/src/messages/flag.ts rename to packages/reader-main/src/messages/v1/flag.ts index c29220a9..e95482fc 100644 --- a/packages/reader-main/src/messages/flag.ts +++ b/packages/reader-main/src/messages/v1/flag.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config'; +import { useConfig } from '../../config'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/follow.ts b/packages/reader-main/src/messages/v1/follow.ts similarity index 94% rename from packages/reader-main/src/messages/follow.ts rename to packages/reader-main/src/messages/v1/follow.ts index 279bbc1d..dd866e4c 100644 --- a/packages/reader-main/src/messages/follow.ts +++ b/packages/reader-main/src/messages/v1/follow.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config/index'; +import { useConfig } from '../../config/index'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/v1/index.ts b/packages/reader-main/src/messages/v1/index.ts new file mode 100644 index 00000000..c0cf0978 --- /dev/null +++ b/packages/reader-main/src/messages/v1/index.ts @@ -0,0 +1,19 @@ +import { Dislike } from './dislike'; +import { Flag } from './flag'; +import { Follow } from './follow'; +import { Like } from './like'; +import { Post } from './post'; +import { Remove } from './remove'; +import { Reply } from './reply'; +import { Unfollow } from './unfollow'; + +export const v1 = { + Dislike, + Flag, + Follow, + Like, + Post, + Remove, + Reply, + Unfollow, +}; diff --git a/packages/reader-main/src/messages/like.ts b/packages/reader-main/src/messages/v1/like.ts similarity index 94% rename from packages/reader-main/src/messages/like.ts rename to packages/reader-main/src/messages/v1/like.ts index e83ceba2..50b6ecfa 100644 --- a/packages/reader-main/src/messages/like.ts +++ b/packages/reader-main/src/messages/v1/like.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config/index'; +import { useConfig } from '../../config/index'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/post.ts b/packages/reader-main/src/messages/v1/post.ts similarity index 94% rename from packages/reader-main/src/messages/post.ts rename to packages/reader-main/src/messages/v1/post.ts index a9b7beb7..bd06c7dd 100644 --- a/packages/reader-main/src/messages/post.ts +++ b/packages/reader-main/src/messages/v1/post.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config/index'; +import { useConfig } from '../../config/index'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/remove.ts b/packages/reader-main/src/messages/v1/remove.ts similarity index 93% rename from packages/reader-main/src/messages/remove.ts rename to packages/reader-main/src/messages/v1/remove.ts index c9180e00..bb806028 100644 --- a/packages/reader-main/src/messages/remove.ts +++ b/packages/reader-main/src/messages/v1/remove.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config/index'; +import { useConfig } from '../../config/index'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/reply.ts b/packages/reader-main/src/messages/v1/reply.ts similarity index 94% rename from packages/reader-main/src/messages/reply.ts rename to packages/reader-main/src/messages/v1/reply.ts index 2ad40ab8..0cca92f7 100644 --- a/packages/reader-main/src/messages/reply.ts +++ b/packages/reader-main/src/messages/v1/reply.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config/index'; +import { useConfig } from '../../config/index'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/unfollow.ts b/packages/reader-main/src/messages/v1/unfollow.ts similarity index 94% rename from packages/reader-main/src/messages/unfollow.ts rename to packages/reader-main/src/messages/v1/unfollow.ts index dccfa570..5a9e9381 100644 --- a/packages/reader-main/src/messages/unfollow.ts +++ b/packages/reader-main/src/messages/v1/unfollow.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config/index'; +import { useConfig } from '../../config/index'; declare module '@atomone/chronostate' { export namespace MemoExtractor {