Giter VIP home page Giter VIP logo

whiteboard's Introduction

Whiteboard

Online multi-user whiteboard. Multiple users can draw together on the same whiteboard in real time.

Whiteboard demo

Technical details

Client-server architecture. Each client sends to server all its drawing actions. Server broadcasts drawing actions to all other clients in its turn. Each client also accepts and applies to its whiteboard all drawing actions received from the server.

Client is built using React and MobX. Server is built using .NET. SignalR is used for real-time communication between clients and server. SignalR configured to use WebSocket as transport and MessagePack as data transfer protocol. Server-Sent Events and Long Polling are fallback options when WebSocket is not available.

Build and run

VSCode devcontainers

VSCode devcontainers are configured for client and server for local development. You can build, run, debug client and server using devcontainer.

Client

  • Open client folder as devcontainer in VSCode
  • Launch terminal and execute cd source && yarn start

Server

  • Open server folder as devcontainer in VSCode
  • Open Run and Debug tab in VSCode
  • Select and start .NET Core Launch (web)

Docker

Use docker-compose up to build and run release version of the client and server.

URLs

Tips

  • Client uses localhost by default to access server. You can change it by setting server URL in ./client/source/src/constants/server.ts:SERVER_URL
  • Initial drawing setting can be specified in query params. For example ?color=blue&bgcolor=lime&size=15
    • size -- pen size
    • color -- pen color
    • bgcolor -- background color

whiteboard's People

Contributors

dfilipuk avatar

Watchers

 avatar

whiteboard's Issues

Debug output

Client app should log (as debug) all its communication with server

Global multi-user mode

All concurrent users draw on the same board, drawing history doesn't save. Replace W2 from #9 with web server which is accessed via WebSockets.

Server broadcasts to client:

  • Drawing actions
  • Board color

Conflicting Data

Board colors may be delivered to clients out of order. It results in different clients have different colors. To address this issue server generates version and broadcasts it with color to all clients on each change color request. Each new version is greater than any previous. Client stores version of current color and resolves conflicts by last-write-wins (LWW) policy: new color is applied if its version is greater than current.

Server generates versions by atomically incrementing [Interlocked.Increment] singleton counter.

Dependencies

ARM Support

Check that project can run natively on ARM and update appropriate configurations if necessary

Automated tests

Simulate concurrent users with automated tests.

  • Implement UI test in Cypress which opens whiteboard and draws on it. Pen size and color are selected randomly
  • Implement Docker container which executes the test N times concurrently for given whiteboard. Input params of the container
    • Whiteboard ID
    • Concurrent users count
    • Drawing operations count per test / execution time of each test
    • Storage mechanism to use by the client (Redis or Apache Cassandra)

Dependencies

Client app enhancements

  • Setup ESLint and apply its recommendations to the code
  • Make use of hooks where necessary
  • Replace global variables with props, states, contexts, etc.
  • Extract services, models, etc. from components into separate files / folders

Dependencies

Redis & Apache Cassandra as options for select

  • Overview of drawing history can be found in #12

There are 2 implementations of history storing mechanism. Make possible to select it on the go.

There are options how to support both storage mechanisms at the same time

  • Separate server instance for each storage mechanism
    • Storage configuration data located in configuration file of the server
    • Server register in dependency injection one of storage implementations based on configuration
    • Create 2 server instances. One for Redis another one for Apache Cassandra
    • Client app have connection strings for both server instances. Apache Cassandra is used by default
    • Make possible to switch server instances in client app (query param and / or UI)
  • Single server instance with different endpoints for each storage mechanism. Client allows to select storage mechanism

Dependencies

README file

Create README file with description of the project

Whiteboard content in Redis

  • Overview of drawing history can be found in #12

Implement storing of whiteboard content using Redis.

  • Use sorted sets as it support reading items in range
  • Sorted set name - whiteboard ID
  • Score - figure ID
  • Value - drawing data

Dependencies

UI reactivity

Make UI reactive using MobX. Drawing settings selected in sidebar influence drawing on the board.
Sidebar icons use drawing settings:

  • Background color icon - background color value
  • Pen color icon - pen color value
  • Pen size icon - pen color value

Make possible to specify default drawing settings via query params (for debug and automated testing)

Dependencies

Drawing history overview & initial implementation

Overview

Drawing history can be divided into 2 independent parts:

  • Whiteboard background color
  • Whiteboard content

Drawing history is sent by the server to the client app per client request. Client requests history once it has connected to the server and may request it during work if needed.

Whiteboard background color

Stores in Redis. Key -- whiteboard ID, value -- color. Default color is specified in the server configuration.

Whiteboard content

It can be represented as log of drawing operations (figures). Each figure has ID and drawing data (color, points, etc.)

When someone draws something on the board server broadcasts it to all other clients of the same board and saves this operation into storage. When client requests history server reads it from storage and sends to the client in batches.

History storing mechanism is write heavy: write on each client action, read when new client joins. Server should only store and broadcast history to clients. It doesn't need to work with history content. As a result NoSQL is a good choice as history storage. Redis and Apache Cassandra are suitable for this use case.

Architecture

Drawing history may contain a lot of data so it may take time to download it in full. Client should be fully functional (broadcast drawings and accepts ones from the server) while history is downloading. Client should download history via separate connection to satisfy this requirement. In such case main connection could be used to process drawing as usual. Connection is closed once history was downloaded.

Contracts

  • Server:GetHistory(id) -- request history for board with specified ID
  • Client:SetBackground(color, version) -- set background color
  • Client:Draw(figures) -- draw figures on the whiteboard

Server

There is separate hub (ex. HistoryHub) for loading history. GetHistory() method reads background color and drawing actions from storage and calls SetBackground() and Draw() methods of the caller client. It's preferred to read and send drawing actions in batches as history may contain thousands of records.

Client

RemoteWorkspace component saves board ID into RemoteWorkspaceState store once connection is established and client joined the board. There is separate component for loading history (ex. RemoteWorkspaceHistory). It has backgroundColor store and serverToClientBus message bus as input params. RemoteWorkspaceHistory connects to HistoryHub and invokes its GetHistory method once board ID appears in the store. SetBackground() and Draw() methods set color to backgroundColor store and push figures to serverToClientBus message bus accordingly. Connection is closed once history is loaded.

Pitfalls

Current design doesn't allow to handle several use cases effectively

  • We can't resume history downloading if connection was lost in the middle. We should start downloading from the very beginning
  • We can't download some part of the history if client was offline for some time. We should either download whole history again or ignore missed data

Implementation details

Common

  • IDs can be generated by Redis using INCR operation
  • All operations with storage should be extracted into interface. There are two implementations of the interface
    • Redis only
    • Redis + Apache Cassandra
  • This functionality should be implemented as V3. Both client and server should support V1, V2 and V3 (create separate hubs on server and separate components to work with it on client)

Color

Storing of background color requires 2 values: color and version (more about color version in #10). Setting new color should be followed by updating its version. These 2 actions should be wrapped into Redis transaction to be atomic.

Scope of work

Scope of this issue is storing of whiteboard background color. Storing of whiteboard content should be implemented firstly with Redis and then with Apache Cassandra.

Dependencies

UI layout

Create primary UI

  • Board which can be used to draw with mouse
  • Sidebar with drawing settings
    • Background color
    • Pen color
    • Pen size
  • Each setting is represented by button with appropriate icon
  • Press on setting button opens popup to select a value
    • Background color - color picker
    • Pen color - color picker
    • Pen size - slider

Dependencies

Production environment

  • Create Docker images with production builds of client and server apps
  • Setup production environment with docker-compose

Local multi-user mode

UI has 2 boards (workspaces). Everything that is drawn on the first board displays on the second one and vise versa. Background color is common for the both boards.

  • Divide drawing settings into local and global. Create separate MobX store for each group. Global settings store is input param for the workspace. Local settings store is created inside the workspace.
  • Implement publish-subscribe pattern using mobx-utils (with batching). Let's call it messagebus. Workspace accepts 2 messagebuses. All drawings are pushed to the first one. Everything which comes from second messagebus is drawn on the board
  • Create workspaces W1 and W2, messagebuses A and B. W1 pushes into A and subscribes to B; W2 pushes into B and subscribes to A. W1 and W2 share store with global settings

Dependencies

Whiteboard content in Apache Cassandra

  • Overview of drawing history can be found in #12

Implement storing of whiteboard content using Apache Cassandra.

  • Whiteboard ID - partition key
  • Figure ID - clustering key
  • Drawing data - raw value (text / json / any other appropriate data type)

Dependencies

Multiple concurrent whiteboards

Each whiteboard has ID. Concurrent users are grouped by the ID.

  • Connect user to existing whiteboard if he opens app with valid ID
  • Create new whiteboard if user opens app without ID
  • Show warning and create new whiteboard if user opens app with invalid ID
  • ID is provided via query params

Implementation details

Connections should be joined into groups. Each group represents whiteboard. Whiteboard ID can be used as group name. SignalR doesn't preserve any info about groups and its members so application should handle it itself.

Let's assume groups are not removed once created. When client asks to join group N we should check whether corresponding whiteboard exists. If yes we add the client connection into connection group otherwise client asks server to create new whiteboard and then joins its connection group.

As mentioned in #10, whiteboard background color should have version. Once we have many whiteboards server should generate background color version separately for each whiteboard. Creating new whiteboard means creating background color version generator for it. Let's assume presence of background color version generator means existence of corresponding whiteboard.

Single server instance may not be able to serve all clients so we may have several server instances. Whiteboards accounting can be implemented per server instance (local) and for all server instances (global). Implementations of local and global whiteboards accounting should have the same interface.

Local whiteboards accounting

  • Whiteboard ID is generated by singleton interlocked counter
  • Background color version generator is represented by interlocked counter. Each whiteboard has it own counter. All counters are stored in ConcurrentDictionary where key is whiteboard ID and value is counter itself.

Global whiteboards accounting

  • Whiteboard ID is generated by Redis counter [INCR]
  • Background color version generator is represented by Redis counter. Each whiteboard has it own counter. Whiteboard ID is counter name

This functionality should be implemented as V2. Both client and server should support V1 and V2 (create separate hubs on server and separate components to work with it on client)

Dependencies

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.