@dongdev/fca-unofficial
Version:
Unofficial Facebook Chat API for Node.js - Interact with Facebook Messenger programmatically
468 lines (379 loc) • 26.4 kB
Markdown
# Architecture — @dongdev/fca-unofficial (4.x)
This document describes the internal architecture of the library. The overarching goal is to **separate session management, HTTP transport, and MQTT realtime** from **business-logic domains**, while preserving full **backward compatibility** with the flat FCA API and offering a **modern event-driven bot** interface on top.
## Table of Contents
1. [Design Principles](#design-principles)
2. [Bootstrap Flow](#bootstrap-flow)
3. [Source Tree](#source-tree)
4. [Layer Diagram](#layer-diagram)
5. [Core Layer](#core-layer)
6. [Transport Layer](#transport-layer)
7. [Domain Layer](#domain-layer)
8. [Application Layer](#application-layer)
9. [Compatibility Layer](#compatibility-layer)
10. [Database Layer](#database-layer)
11. [Utilities](#utilities)
12. [Type System](#type-system)
13. [Realtime Subsystem (Deep Dive)](#realtime-subsystem-deep-dive)
14. [MessengerBot (Deep Dive)](#messengerbot-deep-dive)
15. [Thread Cache & Realtime Sync](#thread-cache--realtime-sync)
16. [Related Documentation](#related-documentation)
## Design Principles
1. **Domain isolation** — Each business area (messages, threads, users, account) is a self-contained domain module with its own types, commands, and queries.
2. **Transport abstraction** — HTTP (GraphQL, Mercury, form-data) and MQTT are in the `transport/` layer, decoupled from domain logic.
3. **Backward compatibility** — The flat `api.sendMessage(...)` surface is preserved via `attachLegacyApiSurface`. Existing bots need zero migration.
4. **Progressive API** — New users can choose `createMessengerBot` (event-driven) or `createFcaClient` (namespaced facade) without understanding the internals.
5. **Optional persistence** — SQLite + Sequelize for caching is opt-in. Everything works in-memory when the database is absent.
## Bootstrap Flow
After cookie parsing or credential-based authentication, the following sequence runs:
```
1. core/login-helper.impl.ts
└─ Completes the session, creates `ctx` (FcaContext) and base `api` facade.
2. core/state.ts → attachThreadUpdater(ctx, models, logger)
└─ Wires DB-based message counting per thread (if Thread model exists).
3. app/attach-legacy-api.ts → attachLegacyApiSurface(ctx, api, ...)
└─ Attaches all flat methods + domain namespace methods onto `api`.
4. core/thread-info-realtime-sync.ts → attachThreadInfoRealtimeSync(ctx, models, logger, api)
└─ Hooks MQTT "event" deltas to invalidate / update the thread cache.
Requires `api.getUserInfo` to be present on `api`.
5. compat/api-registry.ts → attachClientFacade(ctx)
└─ Adds `ctx.client` (the namespaced FcaClientFacade).
```
Each step is optional when composing a custom login flow — functions are exported individually.
## Source Tree
```
src/
├── core/ # Session, config, state, fundamental helpers
│ ├── auth.ts # login(), loginLegacy(), loginViaAPI(), tokensViaAPI()
│ ├── auth-helpers.ts # createAuthCore() — low-level auth plumbing
│ ├── config.ts # loadConfig(), defaultConfig(), resolveConfig()
│ ├── login-helper.ts # High-level login orchestration (entry)
│ ├── login-helper.impl.ts # Login implementation: session init, ctx creation
│ ├── mqtt.ts # listenMqtt() helper
│ ├── options.ts # setOptions() — validates and applies FcaOptions
│ ├── request.ts # createRequestHelper() — authenticated HTTP
│ ├── state.ts # FcaContext, createFcaState(), attachThreadUpdater()
│ ├── thread-info-realtime-sync.ts # Cache sync from MQTT events
│ └── update-check.ts # npm version check
│
├── transport/ # Protocol-level I/O
│ ├── http/
│ │ ├── facebook.ts # Core Facebook HTTP endpoints
│ │ ├── form-data.ts # Multipart form-data uploads
│ │ ├── graphql.ts # GraphQL query execution
│ │ ├── mercury.ts # Mercury endpoint (message operations)
│ │ ├── shared-photos.ts # Shared photos endpoint
│ │ ├── threads.ts # Thread-specific HTTP calls
│ │ └── upload-attachment.ts # Attachment upload transport
│ └── realtime/
│ ├── connect-mqtt.ts # WebSocket MQTT connection, subscribe, reconnect
│ ├── get-seq-id.ts # Fetch sequence ID via GraphQL
│ ├── ls-requests.ts # Lightspeed request builder
│ ├── publish.ts # MQTT publish helpers
│ ├── stream.ts # Duplex stream adapter for MQTT
│ ├── task-response.ts # Parse MQTT task response payloads
│ └── topics.ts # MQTT topic constants
│
├── domains/ # Business logic grouped by concern
│ ├── messages/
│ │ ├── index.ts # createMessagesDomain()
│ │ ├── message.types.ts # Domain-specific types
│ │ ├── commands/ # sendMessage, editMessage, unsendMessage, ...
│ │ └── queries/ # getMessage, getEmojiUrl, resolvePhotoUrl, ...
│ ├── threads/
│ │ ├── index.ts # createThreadsDomain()
│ │ ├── thread.types.ts
│ │ ├── commands/ # createNewGroup, addUserToGroup, setTitle, ...
│ │ └── queries/ # getThreadInfo, getThreadList, searchForThread, ...
│ ├── users/
│ │ ├── index.ts # createUsersDomain()
│ │ ├── user.types.ts
│ │ ├── shared.ts # Shared user utilities
│ │ └── queries/ # getUserInfo, getUserInfoV2, getUserID, getFriendsList
│ ├── account/
│ │ ├── index.ts # createAccountDomain()
│ │ ├── account.types.ts
│ │ └── commands/ # changeAvatar, changeBio, logout, unfriend, ...
│ ├── realtime/
│ │ ├── index.ts # createRealtimeDomain()
│ │ ├── listener.ts # MQTT lifecycle: getSeqID → listen → retry
│ │ ├── emit-auth.ts # MQTT auth emission
│ │ ├── middleware.ts # Realtime middleware chain
│ │ └── parse-delta.ts # Delta → event transformation
│ ├── http/
│ │ ├── index.ts # createHttpDomain()
│ │ ├── commands/ # httpPost, postFormData
│ │ └── queries/ # httpGet
│ └── scheduler/
│ └── index.ts # createSchedulerDomain()
│
├── app/ # High-level application constructs
│ ├── attach-legacy-api.ts # Attaches flat methods + namespaces onto api
│ ├── create-client.ts # createFcaClient() — namespaced facade factory
│ ├── messenger-bot.ts # MessengerBot class (EventEmitter + composer)
│ └── messenger-context.ts # MessengerContext — per-message context object
│
├── compat/ # Backward-compatibility adapters
│ ├── api-registry.ts # attachClientFacade() — ctx.client wiring
│ ├── callbackify.ts # Promise → callback adapter
│ └── legacy-promise.ts # Legacy promise wrapper
│
├── database/ # Optional SQLite + Sequelize persistence
│ ├── helpers.ts # DB initialization helpers
│ ├── models/
│ │ ├── index.ts # Model registry
│ │ ├── thread.ts # Thread model (threadID, data, messageCount)
│ │ └── user.ts # User model
│ ├── threadData.ts # Thread data access layer
│ └── userData.ts # User data access layer
│
├── session/ # Session management
│ ├── capability-resolver.ts # Resolves available capabilities
│ └── session.ts # Session abstraction
│
├── remote/ # Remote control
│ └── remoteClient.ts # WebSocket client for external dashboards
│
├── func/ # Logging infrastructure
│ ├── logger.ts # Main logger
│ └── logAdapter.ts # Log format adapter
│
├── utils/ # Shared utilities
│ ├── broadcast.ts # Broadcast helper
│ ├── client.ts # Client utilities
│ ├── constants.ts # Shared constants
│ ├── cookies.ts # Cookie manipulation
│ ├── headers.ts # HTTP header construction
│ ├── format/ # Data formatting
│ │ ├── index.ts # Format barrel export
│ │ ├── attachment.ts # Attachment formatting
│ │ ├── cookie.ts # Cookie formatting
│ │ ├── date.ts # Date formatting
│ │ ├── decode.ts # Decoding utilities
│ │ ├── delta.ts # MQTT delta formatting
│ │ ├── ids.ts # ID formatting
│ │ ├── message.ts # Message formatting
│ │ ├── presence.ts # Presence formatting
│ │ ├── readTyp.ts # Read/typing formatting
│ │ ├── thread.ts # Thread formatting
│ │ └── utils.ts # General format utilities
│ ├── loginParser/ # Login response parsing
│ │ ├── index.ts # Parser barrel export
│ │ ├── autoLogin.ts # Auto-login logic
│ │ ├── helpers.ts # Parser helpers
│ │ ├── parseAndCheckLogin.ts # Main login response parser
│ │ └── textUtils.ts # Text parsing utilities
│ └── request/ # HTTP request infrastructure
│ ├── index.ts # Request barrel export
│ ├── client.ts # HTTP client setup
│ ├── config.ts # Request configuration
│ ├── defaults.ts # Default request options
│ └── helpers.ts # Request helpers
│
├── types/ # Public TypeScript types
│ ├── index.ts # Type barrel export
│ ├── client.ts # FcaClientFacade, FcaClientNamespace
│ ├── core.ts # Core types (FcaID, etc.)
│ ├── core-modules.ts # Core module types
│ ├── events.ts # MqttEvent, MessageEvent, ReactionEvent, ...
│ ├── messaging.ts # Messaging types
│ ├── threads.ts # Thread types
│ └── scheduler.ts # Scheduler types
│
├── global-types.d.ts # Global type declarations (Loose, etc.)
└── index.ts # Package entry — re-exports everything public
```
## Layer Diagram
```
┌───────────────────────────────────────────────────┐
│ Consumer Code │
│ (bot scripts, integrations, etc.) │
└───────────┬──────────────┬──────────────┬─────────┘
│ │ │
┌───────▼──────┐ ┌────▼─────┐ ┌──────▼──────┐
│ MessengerBot │ │ FcaClient│ │ Flat API │
│ (app/) │ │ Facade │ │ (legacy) │
└───────┬──────┘ └────┬─────┘ └──────┬──────┘
│ │ │
└──────────────┼──────────────┘
│
┌────────────▼────────────┐
│ Domain Layer │
│ messages / threads / │
│ users / account / │
│ realtime / http / │
│ scheduler │
└────────────┬────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌───────▼──────┐ ┌────▼─────┐ ┌──────▼──────┐
│ Transport │ │ Core │ │ Database │
│ http/ │ │ auth, │ │ (optional) │
│ realtime/ │ │ state, │ │ SQLite + │
│ │ │ config │ │ Sequelize │
└──────────────┘ └──────────┘ └─────────────┘
```
## Core Layer
**`src/core/`** manages authentication, session state, configuration, and fundamental helpers that every other layer depends on.
| File | Responsibility |
|-------------------------------|-------------------------------------------------------------------|
| `auth.ts` | Entry points: `login()`, `loginLegacy()`, `loginViaAPI()`, `tokensViaAPI()` |
| `auth-helpers.ts` | Low-level auth plumbing: cookie extraction, token refresh |
| `config.ts` | Load, merge, and write `fca-config.json` |
| `login-helper.ts` | High-level login orchestration |
| `login-helper.impl.ts` | Session initialization, `ctx` and `api` creation |
| `mqtt.ts` | `listenMqtt()` — thin wrapper that delegates to `domains/realtime` |
| `options.ts` | Validate and apply `FcaOptions` onto global state |
| `request.ts` | `createRequestHelper()` — authenticated HTTP client |
| `state.ts` | `FcaContext` type, `createFcaState()`, `createApiFacade()`, `attachThreadUpdater()` |
| `thread-info-realtime-sync.ts`| Sync thread cache from realtime MQTT events |
| `update-check.ts` | Check npm registry for newer versions |
## Transport Layer
**`src/transport/`** handles raw protocol-level communication. Domain modules call into transport functions rather than making HTTP or MQTT calls directly.
### HTTP (`transport/http/`)
| File | Purpose |
|-----------------------|---------------------------------------------------|
| `facebook.ts` | General Facebook API endpoints |
| `graphql.ts` | Execute GraphQL queries with authenticated context |
| `mercury.ts` | Mercury endpoint for message-related operations |
| `form-data.ts` | Multipart form-data POST requests |
| `upload-attachment.ts`| Upload attachments to Facebook's CDN |
| `shared-photos.ts` | Shared photos endpoint |
| `threads.ts` | Thread-specific HTTP operations |
### Realtime (`transport/realtime/`)
| File | Purpose |
|--------------------|--------------------------------------------------------------------|
| `connect-mqtt.ts` | Establish WebSocket connection to Facebook MQTT; subscribe to topics in batch, then publish sync; handle reconnection safely |
| `get-seq-id.ts` | Fetch the latest sequence ID via GraphQL (required before listening) |
| `publish.ts` | MQTT publish helpers |
| `stream.ts` | Duplex stream adapter for the MQTT wire protocol |
| `task-response.ts` | Parse task/response payloads from MQTT frames |
| `topics.ts` | Topic string constants (`/t_ms`, `/thread_typing`, etc.) |
| `ls-requests.ts` | Build Lightspeed request payloads |
## Domain Layer
**`src/domains/`** contains all business logic organized by concern. Each domain exports a `create*Domain()` factory that returns an object of related functions.
### Structure pattern
```
domains/<name>/
├── index.ts # createXxxDomain() factory
├── <name>.types.ts # Domain-specific TypeScript types
├── commands/ # Write operations (mutations)
│ ├── do-something.ts
│ └── ...
└── queries/ # Read operations
├── get-something.ts
└── ...
```
### Domains
| Domain | Commands | Queries |
|-------------|--------------------------------------------------------------------------------------------|------------------------------------------------------------------|
| `messages` | send, edit, unsend, delete, setReaction, sendTyping, markRead, markDelivered, markSeen, markReadAll, upload, forward, shareContact, changeColor, changeEmoji | getMessage, getEmojiUrl, resolvePhotoUrl, getThreadColors |
| `threads` | createGroup, addUser, removeUser, changeAdmin, changeImage, changeNickname, setTitle, createPoll, createThemeAI, delete, archive, mute, handleRequest | getInfo, getList, getHistory, getPictures, getThemePictures, search |
| `users` | — | getInfo, getInfoV2, getID, getFriendsList |
| `account` | changeAvatar, changeBio, changeBlocked, handleFriendReq, unfriend, setPostReaction, refreshDtsg, logout, addModule, enableAutoSave | getCurrentUserID |
| `realtime` | *(listener lifecycle, emit-auth, middleware)* | *(parse-delta)* |
| `http` | httpPost, postFormData | httpGet |
| `scheduler` | *(scheduling primitives)* | — |
## Application Layer
**`src/app/`** provides the high-level constructs that consumers interact with directly.
| File | What it does |
|-------------------------|-----------------------------------------------------------------------------|
| `attach-legacy-api.ts` | Attaches all flat methods + domain namespaces onto the `api` object |
| `create-client.ts` | `createFcaClient(api)` — wraps flat API into a `FcaClientFacade` |
| `messenger-bot.ts` | `MessengerBot` class — EventEmitter + composer middleware engine |
| `messenger-context.ts` | `MessengerContext` — per-message context with `reply()` / `replyAsync()` |
## Compatibility Layer
**`src/compat/`** ensures older code keeps working.
| File | Purpose |
|--------------------|---------------------------------------------------------------|
| `api-registry.ts` | `attachClientFacade(ctx)` — adds `ctx.client` lazily |
| `callbackify.ts` | Convert promise-returning functions to Node-style callbacks |
| `legacy-promise.ts`| Wrap callbacks in a Promise for dual-mode support |
## Database Layer
**`src/database/`** is entirely optional. When SQLite + Sequelize are available:
- **Models:** `Thread` (stores JSON thread data + `messageCount`) and `User` (stores user profile cache).
- **`threadData.ts`** / **`userData.ts`** provide a data access layer with freshness-based reads and writes.
- **`helpers.ts`** handles Sequelize initialization and model synchronization.
When the database is absent, all cache features gracefully degrade to in-memory or no-op behavior.
## Utilities
**`src/utils/`** contains cross-cutting concerns:
| Subdirectory / File | Purpose |
|---------------------|-------------------------------------------------------------|
| `format/` | Transform raw Facebook data into normalized structures (attachments, deltas, messages, threads, presence, IDs, dates) |
| `loginParser/` | Parse login HTTP responses, handle auto-login, extract tokens |
| `request/` | HTTP client setup, default headers, proxy configuration |
| `broadcast.ts` | Broadcast helper for multi-thread messaging |
| `client.ts` | Client-level utility functions |
| `constants.ts` | Shared constant values |
| `cookies.ts` | Cookie parsing and manipulation |
| `headers.ts` | HTTP header construction for Facebook API requests |
## Type System
**`src/types/`** contains all publicly exported TypeScript interfaces and type aliases:
| File | Contents |
|------------------|------------------------------------------------------------|
| `client.ts` | `FcaClientFacade`, `FcaClientNamespace`, `LegacyApiLike` |
| `core.ts` | `FcaID` and other primitive types |
| `core-modules.ts`| Core module interface types |
| `events.ts` | `MqttEvent` union, `MessageEvent`, `ReactionEvent`, `TypingEvent`, etc. |
| `messaging.ts` | Messaging-related types |
| `threads.ts` | Thread-related types |
| `scheduler.ts` | Scheduler types |
`src/global-types.d.ts` declares the global `Loose` type (alias for `any`) used throughout the codebase for dynamic Facebook API payloads.
## Realtime Subsystem (Deep Dive)
### Connection lifecycle
1. **`domains/realtime/listener.ts`** orchestrates the full lifecycle:
- Calls `getSeqID` (GraphQL) to obtain the latest sequence number.
- Invokes `listenMqtt` which delegates to `transport/realtime/connect-mqtt.ts`.
- Manages auto-cycle (periodic reconnection) and retry logic with debounced `getSeqID`.
2. **`transport/realtime/connect-mqtt.ts`** handles the wire protocol:
- Opens a WebSocket to Facebook's MQTT endpoint.
- Subscribes to topics in a batch (`/t_ms`, `/thread_typing`, `/orca_presence`, etc.).
- Only after subscriptions complete does it publish the sync queue.
- On disconnect, the old client is fully cleaned up before creating a new one.
3. **`domains/realtime/parse-delta.ts`** transforms raw MQTT deltas into typed event objects and dispatches them through the callback chain. It also fires `emitThreadInfoEvent` to trigger cache synchronization.
### Topics
Defined in `transport/realtime/topics.ts`. Key topics include thread messages (`/t_ms`), typing indicators (`/thread_typing`), presence (`/orca_presence`), and Lightspeed requests (`/ls_req`, `/ls_resp`).
## MessengerBot (Deep Dive)
### Event dispatch
`MessengerBot` listens on the MQTT emitter's `"message"` event and maps each `MqttEvent` to named channels:
- `message` / `messageCreate` for chat messages and replies
- `messageReactionAdd`, `messageDelete`, `typingStart`, `typingStop`, `threadUpdate`, `ready`, `shardReady`
- `raw` / `update` for every delta
The `emitIf` helper only fires `emit()` when there are active listeners, keeping overhead minimal.
### Composer engine
The composer is a Koa-style middleware chain that runs only for `message` and `message_reply` events. Execution order:
1. Global `use()` middlewares in registration order.
2. `command()` handlers check for `{prefix}{name}` at the start of the message.
3. `hears()` handlers match by regex or substring.
4. If any middleware throws, the `catch()` handler receives the error.
Middleware is dispatched via `queueMicrotask` to avoid blocking the MQTT event loop.
## Thread Cache & Realtime Sync
**`core/thread-info-realtime-sync.ts`** subscribes to MQTT events of type `"event"` and performs:
| Log message type | Action |
|-----------------------------------|---------------------------------------------------------|
| `log:subscribe` | Add participant to cache, fetch fresh `userInfo` |
| `log:unsubscribe` | Remove participant from cache |
| Thread metadata changes | Invalidate the cached `data` (set to `null`) |
| Unknown/unhandled types | Invalidate as a safety measure |
This keeps the SQLite cache consistent with the actual thread state without requiring periodic full refetches.
## Related Documentation
- [README.md](../README.md) — Installation and quick start guide
- [DOCS.md](./DOCS.md) — Full API reference and usage guide
- Version history: [`CHANGELOG.md`](https://github.com/dongp06/fca-unofficial/blob/main/CHANGELOG.md) in the repository (not shipped in the npm tarball).