GhostText Protocol Client
Relevant source files
The following files were used as context for generating this wiki page:
The GhostText Protocol Client is the core component of the Ghostbird add-on responsible for communicating with an external text editor's GhostText server. It enables users to compose Thunderbird emails in their preferred editor by synchronizing text changes over a WebSocket connection. The client implements the logic defined by the GhostText protocol.
This system is designed to be decoupled from the Thunderbird-specific APIs. It operates as a state machine that issues commands to, and receives results from, an intermediary "runner" layer. This architecture isolates the protocol logic, making it more testable and maintainable. For more details on the overall architecture, see the design documentation.
Sources: README.md, doc/design.md, doc/faq.md
High-Level Architecture
The Ghostbird add-on acts as the client in the GhostText ecosystem. It initiates a connection from the Thunderbird compose window to a GhostText server running in a text editor. Once connected, it relays text updates between the two endpoints.
The following diagram illustrates the data flow between the main components.
This diagram shows the basic interaction flow when a user initiates a Ghostbird session.
Sources: README.md, doc/design.md
Protocol Data Structures
The communication between the client (Ghostbird) and the server (text editor plugin) uses specific JSON data structures. These are defined in src/ghosttext-session/types.ts.
Initial Handshake
The connection begins with an HTTP GET request from the client. The server responds with the WebSocket port.
| Field | Type | Description |
|---|---|---|
ProtocolVersion | number | The protocol version, currently 1. |
WebSocketPort | number | The port on which the WebSocket server is listening. |
| Sources: src/ghosttext-session/types.ts:11-17 |
Client-to-Server: UpdateRequest
When the text in the Thunderbird compose window changes, the client sends an UpdateRequest to the server. (Note: Bidirectional editing is planned for v2.0.0 and is not yet fully implemented).
| Field | Type | Description |
|---|---|---|
title | string | The title of the document (email subject). |
url | string | The host of the document's URL. |
syntax | string | Not currently used; an empty string. |
text | string | The full content of the text area (email body). |
selections | ReadonlyArray<SelectionObject> | The user's current cursor selections. |
| Sources: src/ghosttext-session/types.ts:28-40 |
Server-to-Client: EditorChangeResponse
When the text in the external editor changes, the server sends an EditorChangeResponse to the client.
| Field | Type | Description |
|---|---|---|
text | string (optional) | The updated content of the temporary file. |
selections | ReadonlyArray<SelectionObject> (optional) | Updated cursor selections. |
| Sources: src/ghosttext-session/types.ts:46-50 |
Session Management (GhostTextClient)
The core logic for managing a GhostText session is encapsulated in the GhostTextClient class. It is implemented as a state machine using a generator function, run(). This design allows the client logic to be independent of the asynchronous operations it needs to perform (like network requests or UI updates).
Sources: src/ghosttext-session/ghost_text_client.ts:22-24
Session Lifecycle and Status
A session progresses through several states, tracked by the SessionStatus type.
| Status | Description |
|---|---|
unconnected | The initial state before any connection attempt. |
connecting | The client is attempting to connect to the GhostText server. |
running | A WebSocket connection is established, and text is being synchronized. |
error | A connection or runtime error occurred. |
finished | The session was closed gracefully. |
| Sources: src/ghosttext-session/ghost_text_client.ts:7 |
The lifecycle of a session can be visualized as follows:
This diagram shows the possible state transitions during a GhostText session.
Command and Result Model
The GhostTextClient.run() generator does not perform I/O directly. Instead, it yields Command objects describing the action to be taken. The caller (a "runner") executes the command and passes a CommandResult back into the generator via next().
Commands (Command)
These are instructions yielded by the GhostTextClient to the runner.
| Command | Payload | Description |
|---|---|---|
connect | - | Instructs the runner to establish a connection with the GhostText server. |
queryEditor | - | Asks the runner for the initial state of the Thunderbird compose window. |
requestUpdate | update: UpdateRequest | Sends a text/selection update to the server. |
applyChange | change: ExternalEdit | Applies a change from the server to the local editor. |
notifyStatus | status: SessionStatus | Informs the runner of a status change. |
| Sources: src/ghosttext-session/ghost_text_client.ts:9-15 |
Results (CommandResult)
These are outcomes of commands, passed back to the GhostTextClient.
| Result | Payload | Description |
|---|---|---|
connected | init: ServerInitialResponse | Returned after a successful connection, containing server info. |
statusUpdated | - | Confirms that a status notification was handled. |
serverChanged | change: EditorChangeResponse | Provides a text change from the server. |
clientState | state: EmailState | Provides the full state of the local editor. |
clientEdited | edit: InternalEdit | Provides a partial edit from the local editor. |
disconnected | error?: Error | Signals that the connection was lost. |
editorClosed | - | Signals that the compose window was closed. |
| Sources: src/ghosttext-session/ghost_text_client.ts:17-25 |
Session Flow
The following sequence diagram details the interaction between the GhostTextClient generator, the runner, and the external server.
Runner Interfaces
The ghosttext-runner module defines the interfaces that bridge the abstract GhostTextClient with the concrete implementation details of Thunderbird.
This diagram shows that GhostTextClient's commands are executed by components that implement these key interfaces.
IGhostTextConnector: Responsible for the initial HTTP handshake and establishing the WebSocket connection, returning anISessionobject.ISession: Represents an active WebSocket connection, handling sending and receiving of messages.IClientEditor: Represents the Thunderbird compose window, providing methods to get its state and apply changes.IStatusIndicator: A UI component to show the connection status (active,inactive,error).IClientOptionsLoader: Loads configuration like the server URL.
Sources: src/ghosttext-runner/api.ts
Conclusion
The GhostText Protocol Client in Ghostbird is a well-defined, stateful module that manages the complex task of real-time text synchronization. Its implementation as a generator-based state machine, combined with a command/result pattern and clear interface boundaries, effectively decouples the core protocol logic from the browser-specific APIs. This design promotes testability and makes the system easier to understand and extend.