Hone logo
Hone
Problems

Real-time Collaborative Editor Synchronization

This challenge focuses on building a simplified synchronization protocol for a real-time collaborative editor in React using TypeScript. You'll implement a mechanism to ensure that changes made by multiple users are applied consistently and efficiently to a shared document. This is a fundamental building block for applications like Google Docs or Figma.

Problem Description

Your task is to create a React component that manages the synchronization of text edits between multiple clients. The core idea is to represent edits as "operations" and broadcast these operations to other clients. Each client will receive operations from a central server (simulated in this challenge) and apply them to their local document state.

Key Requirements:

  1. Operation Representation: Define a TypeScript interface for an "edit operation." This operation should capture the type of edit (insert or delete), the position in the document where the edit occurred, and the content being inserted or deleted.
  2. Local State Management: Implement a React component that maintains the current state of the document (e.g., a string).
  3. Operation Application: Develop a function that takes the current document state and an operation, and returns the new document state after applying the operation.
  4. Operation Broadcasting (Simulation): Create a mechanism to simulate broadcasting operations to other clients. In this challenge, you'll have a broadcastOperation function that receives an operation and a callback function to simulate processing it on the "server" and sending it back.
  5. Receiving and Applying Remote Operations: Implement logic within your React component to receive simulated remote operations and apply them to the local document state.
  6. Conflict Resolution (Simplified): For this challenge, we'll assume a simplified conflict resolution strategy: operations are applied in the order they are received. More complex scenarios involving concurrent edits at the same position might require more advanced algorithms like Operational Transformation (OT) or Conflict-free Replicated Data Types (CRDTs), which are outside the scope of this basic challenge.

Expected Behavior:

  • When a user makes a local edit (e.g., types a character, deletes text), the component should generate an operation.
  • This operation should be broadcasted.
  • Upon receiving a broadcasted operation (simulated), the component should apply it to its current document state.
  • The UI should update to reflect the applied changes.

Edge Cases to Consider:

  • Inserting at the beginning or end of the document.
  • Deleting text at the beginning or end.
  • Deleting multiple characters.
  • Large insertions.

Examples

Example 1: Single Insertion

Initial Document: "Hello"

User Action: Inserts " World" at index 5 (after "Hello")

Simulated Broadcast Operation:
{
  type: "insert",
  position: 5,
  content: " World"
}

Component receives operation and applies it.

Output Document: "Hello World"

Example 2: Single Deletion

Initial Document: "Hello, beautiful world!"

User Action: Deletes 9 characters starting from index 7 (deleting ", beautiful")

Simulated Broadcast Operation:
{
  type: "delete",
  position: 7,
  content: ", beautiful" // Content deleted for clarity, though only length is strictly needed for delete
}

Component receives operation and applies it.

Output Document: "Hello world!"

Example 3: Concurrent (Simplified) Edits

This example demonstrates how operations are applied sequentially. Imagine two users typing at different locations simultaneously.

Scenario:

  • Client A has: "abc"
  • Client B has: "abc"

Event 1 (Client A): Inserts "XYZ" at index 1. * Client A broadcasts: { type: "insert", position: 1, content: "XYZ" } * Assume this operation reaches Client B first. * Client B applies: a + XYZ + bc = "aXYZbc"

Event 2 (Client B): Inserts "123" at index 4. * Client B broadcasts: { type: "insert", position: 4, content: "123" } * Assume this operation reaches Client A first. * Client A applies: a + XYZ + b + 123 + c = "aXYZb123c" (Note: Position 4 in "aXYZbc" corresponds to the 'b').

Simulated Server Log (illustrative):

  1. Operation from Client A: { type: "insert", position: 1, content: "XYZ" }
  2. Operation from Client B: { type: "insert", position: 4, content: "123" }

If Client A receives Event 1 then Event 2:

  • Initial: "abc"
  • After Event 1: "aXYZbc"
  • After Event 2: "aXYZb123c"

If Client B receives Event 1 then Event 2:

  • Initial: "abc"
  • After Event 1: "aXYZbc" (assuming Client B also receives Event 1)
  • After Event 2: "aXYZb123c"

(The output document for both clients, after receiving both operations in sequence, should be the same)

Output Document (for both clients after receiving both operations): "aXYZb123c"

Constraints

  • The document content will be a simple string.
  • The maximum document length will not exceed 10,000 characters.
  • The number of operations processed per second will not exceed 100.
  • Your solution should be implemented using React and TypeScript.
  • The simulation of the server will involve calling a provided broadcastOperation function.

Notes

  • You'll need to define interfaces for your EditOperation.
  • Think carefully about how you will update the document string based on insertions and deletions. String manipulation in JavaScript can be done efficiently using slice and concatenation.
  • The broadcastOperation function is a placeholder. You will call it when a local edit occurs, and it will return a simulated remote operation (or an array of them, depending on how you structure your simulation) that your component needs to handle.
  • This challenge is about implementing the core logic of applying operations. You do not need to build a full UI with text input fields, cursors, etc. A simple display of the document and a way to trigger simulated local edits will suffice.
  • Consider using useReducer for managing the document state and operations, as it's well-suited for complex state updates triggered by events.
  • For the broadcastOperation simulation, you can simply have it call a provided callback with the operation it received. The callback will then simulate the "remote" processing and call another function to deliver the (potentially reordered or modified) operation back to your component.

This challenge provides a solid foundation for understanding real-time synchronization and its complexities in web applications. Good luck!

Loading editor...
typescript