This project aims to teach how to use the TinyMCE Real-Time Collaboration plugin.
Real-Time Collaboration (RTC) allows multiple users to edit a document simultaneously, while automatically combining their changes. This makes it easier for integrators since they don't need to lock the document to avoid multiple users overwriting each other.
This repository shows the before and after stages of applying RTC to a simple collaborative document editing system.
This project is split into client and server.
The server was created with the
tool express-generator
using
the setting --no-view
as we weren't using the view engine.
Then the following packages were added:
bcrypt
- to salt and hash passwords.cors
- to enable accessing the API from another server.dotenv-flow
- to allow loading settings from.env
and.env.local
files.jsonwebtoken
- to allow signing and validating JSON web tokens.knex
- to connect to our database and build queries safely.luxon
- to parse and generate ISO date-time values.passport
- to handle authentication for requests.passport-jwt
- apassport
plugin that supports JWT authentication.sqlite3
- to connect to a SQLite database, used byknex
.swagger-ui-express
- to provide interactive documentation for the API.uuid
- generate universally unique identifiers for the documents.
The server application is started by the node script in
server/bin/www
which has not been modified in this project.
It then loads the main script server/app.js
which sets up
how express handles requests.
The app.js
does the following:
- Loads settings from
.env
and.env.local
, these can also be specified as environment variables. - Loads database settings from the
knexfile.js
. - Connects to the database using
knex
. - Loads the private and public keys for signing JWT tokens using the settings loaded in step 1.
- Sets up the authentication middleware using
passport
to read JWT bearer tokens and authenticate against the database. - Loads the routes.
- Creates the express app.
- Registers middleware to:
- Provide the database
- Log request details
- Respond with CORS headers
- Parse JSON encoded requests
- Parse URL encoded requests
- Parse cookies
- Serve static files
- Registers the routes.
- Exports the app.
The database is accessed through the knex
query builder
which is configured by the file server/knexfile.js
.
There are 3 configurations listed but for the purposes of this demo we will only
be using the development
configuration which connects to an SQLite database
at the file server/dev.sqlite3
. When you first checkout the project this
database file will not exist but it can be created by running the database
migration scripts.
The migration scripts are in the folder server/migrations
and are run in alphabetical order, hence the scripts all start with the timestamp
of their creation. For this project we are only using one script for each
phase of the project.
For the initial phase without RTC the database design looks like this:
Column | Type | Description |
---|---|---|
username | string | The username for logging in to the application. |
hash | string | The salt and hash of the users password created by bcrypt . |
fullName | string | The display name of the user. |
Column | Type | Description |
---|---|---|
uuid | uuid | The universally unique identifier for the document. |
title | string | The human readable title of the document. |
content | string | The content of the document. |
lockUser | string | The user which currently has exclusive write access to the document. |
lockTime | string | The last time that the locking user requested exclusive write access. |
Column | Type | Description |
---|---|---|
document | uuid | The document's universally unique identifier. |
user | string | The user's username. |
permissions | integer | The permissions stored as a bitset. |
The most important part of the server is the API routes that are defined to handle all the different parts of the application.
Route | Method | Description |
---|---|---|
/ | GET | The interactive documentation for the API. |
/jwt | POST | Login and create a JSON web token to represent the user. |
/users | GET | Get a list of all usernames on the server. |
/users | POST | Create a new user. |
/users/:username | GET | Get the user details, in this case a full name. |
/documents | GET | Get a list of all documents the user can access. |
/documents | POST | Create a new document. |
After following the server setup steps below and starting the server open the URL http://localhost:3001/api/
to view interactive documentation.
Note that this documentation is defined in the file server/docs/swagger.json
.
The client was created with the tool create-react-app
. The .git
folder and
.gitignore
created by the script was removed in favor of the one in the parent directory.
The following packages were added:
@tinymce/tinymce-react
- to load TinyMCE as a react component.axios
- to handle requesting.bootstrap
- to supply styling.jquery
- to allow interactive parts frombootstrap
.jsonwebtoken
- to decode JSON web tokens to extract the user and expiry time.popper.js
- to handle popups inbootstrap
.react-bootstrap
- to supply components for creating the website.react-router-bootstrap
- to supply some replacement components fromreact-router-dom
that work withbootstrap
.react-router-dom
- to handle navigation without page loads.
The application uses JSON web tokens to store who is the logged in user. When these are retrieved after login they are stored in local storage and retrieved whenever the page is reloaded. These are automatically removed when the expiry time on the token is reached.
Additionally the application makes use of the default headers setting on axios to send the JSON web token with every request without having to manually pass the token.
The application is made up of 4 pages, 3 modals and a common component to provide navigation between each of the pages.
Navigation.js
- A navigation bar shared between pages.
DocumentEdit.js
- A page using TinyMCE which is used for viewing and editing the document.DocumentList.js
- A page which shows a list of all documents available to the currently logged-in user.LoginRegister.js
- A page which allows new users to register or to log-in to use the rest of the site.Logout.js
- A page which simply logs out the user and redirects to the login page.
EditCollaboratorModal.js
- a modal used to change the access of a collaborator.NewCollaboratorModal.js
- a modal used to add a new collaborator.NewDocModal.js
- a modal used to create a new document.
The following commands are run from the subfolder server
.
yarn knex migrate:latest
If you have migrated your database to the RTC version and want to return to the pre-RTC schema you can use.
yarn knex migrate:down
ssh-keygen -m PEM -t rsa -b 2048 -f rsa-key
mv rsa-key rsa-key.private.pem
ssh-keygen -f rsa-key.pub -e -m pem > rsa-key.public.pem
Note that the passphrase should be left blank.
The .env
file has the default values so only put in values that need to be changed.
The port the server runs on. By default this is port 3001
. If you change
this you will need to change the REACT_APP_API
on the client.
The path to the private key. By default this is ./rsa-key.private.pem
.
The path to the public key. By default this is ./rsa-key.public.pem
.
yarn start
Note if you have nodemon
installed you might prefer:
nodemon start
as it will automatically restart if you make changes.
Create the cloud account at https://www.tiny.cloud/auth/signup/ and note the API key for a later step.
Register the public key that was generated previously at https://www.tiny.cloud/my-account/jwt/ .
The following commands are run from the subfolder client
.
The .env
file has the default values so only put in values that need to be changed.
Note that you must provide an API key to REACT_APP_TINYMCE_API_KEY
.
The port that the client runs on. By default this is port 3000
.
The URL of the server API. By default this is http://localhost:3001/api
.
If you have changed the port of the server you will need to update this value.
Set this value with the API key of the cloud account you registered in a previous step.
yarn start