Blueprint by Tiny
Return to Tiny.cloud
Return to Tiny.cloudTry TinyMCE for Free
Search by

How to use Real-time Collaboration with TinyMCE Cloud

Joe Robinson

October 27th, 2021

Written by

Joe Robinson

Category

How-tos & Tutorials

With the recent TinyMCE 5.9 release, we’re also delighted to bring you Real-time Collaboration for TinyMCE – it’s the new plugin that was just made publicly available.

Real-time collaboration (RTC) is the ability for two or more people to work together on some online text simultaneously. TinyMCE 5.9 now has this capability, and the RTC plugin provides secure, end-to-end encryption for all your collaborative sessions. 

Whether your use case is a learning system, content management system, or another custom application, this post explains how to set up RTC using TinyMCE Cloud.

Installing Real-time Collaboration prerequisites

For the purpose of testing, and to fulfill your curiosity, you can find a demo application available in a teaching repository on GitHub. This demo uses a React app with TinyMCE as a front end, the client, and a server running Axios and SQLite to establish a database and route data.

To try out the demo you need to: 

  1. Make sure you have access to git, yarn, and ssh-keygen on the command line.
  2. Open the command line, and clone the repository: git clone git@github.com:tinymce/teach-rtc.git
  3. Set up a TinyMCE account, and get your API key
  4. Open two terminals; one for the client side files and another for the server side files. This can be done within visual studio code using terminal view and split terminal, or with a pair of terminal prompts open if you don’t mind switching windows. 
  5. Run yarn install on the client and server folders to set up the needed packages.
  6. Make a new file in the client folder called .env.local and type in REACT_APP_TINYMCE_API_KEY=

Place the API key copied from your account dashboard directly after the “=” with no space

  1. In the terminal window, or in the command prompt, generate an SSH key in PEM format in the server directory using ssh-keygen -m PEM -t rsa -b 2048 -f rsa-key. Leave the passphrase empty.
  2. Move the key into a new .pem file with mv rsa-key rsa-key.private.pem
  3. Generate a public SSH key in PEM format using ssh-keygen -f rsa-key.pub -e -m PEM > rsa-key.public.pem and import the public key into your Tiny account in the JWT Keys tab, under the Import Public Key heading. Copy all the content in the .public.pem file, including the BEGIN and END notes.
  4. Run yarn start in the server directory to start the server. Repeat the yarn start in the client directory, and open the local address in your browser. This will start the app, and you can create some demo users.
  5. Log in as one of your users, and create a new document in the demo app. Then in another browser or private window, create another user:

    Create another user to test out Real-time Collaboration
  6. Add the new user as a collaborator on the document.

Adding a user to the Real-time Collaboration demo document
In its first form, the demo does not have Real-time Collaboration features available. If you log with your user accounts, you won’t be able to make changes to the document at the same time.

Setting up TinyMCE with RTC, including the cloud channel

Using the TinyMCE React integration, you can load the Real-time Collaboration plugin into TinyMCE using the init object.

Before doing anything else, stop the server with the cmd+c key combination. Then start making changes to the files. 

Modify client/src/pages/DocumentEdit.js

  1. Remove line 31, which references OwnsLock. Take away references to OwnsLock, and then remove the 'use document lock' import.
  2. Remove line 32, which contains useDocumentAutosave, and the associated import on line 4.
  3. Remove lines 42 and 43 with the inInit and onRemove objects. Here is an example of the modified JavaScript:

export default function DocumentEdit({ token }) {
  const { documentId } = useParams();
  const { sub: username } = token ? decode(token) : {};
  const title = useDocumentTitle({ documentId });
  const { access } = useCollaborators({ documentId, username });
  const accessCanEdit = access === 'manage' || access === 'edit';
  const canEdit = accessCanEdit;
  const initialValue = useDocumentInitialValue({ documentId, canEdit });
  const editorRef = useDocumentAutosave({ documentId, canSave: canEdit, initialValue}
  1. Configure the Real-time Collaboration plugin and its options. Note the cloud channel option, and that the properties of the encryption option are not secure, and should not be used in an actual application: 

<Editor
  key={documentId}
  apiKey={process.env.REACT_APP_TINYMCE_API_KEY}
  cloudChannel="5-dev" //don’t forget to add the cloud channel to avoid any version errors
  disabled={!canEdit}
  initialValue={initialValue}
  onInit={(_evt, editor) => {
    editorRef.current = editor;
  }}
  onRemove={() => {
    editorRef.current = undefined;
  }}
  init={{
    ...config,
    plugins: "rtc " + config.plugins.join(" "),
    rtc_document_id: documentId,
    rtc_token_provider: ({ documentId }) => Promise.resolve({ token }),
    rtc_encryption_provider: ({ documentId, keyHint }) =>
      Promise.resolve({
        key: "This is not secure, Fix me!",
        keyhint: "1970-01-01T00:00:00.000Z",
      }),
  }}
/>;

Modify client/src/api/api.js

  1. Navigate to line 163 of the file, and include the 'version' function in the SaveContent information, and in the axios put call.

export const saveContent = async ({ documentId, version, content }) => {
  const { data } = await axios.put(`/documents/${documentId}/content`, { content, version });

Modify server/routes/api.js

  1. Navigate to line 341, and remove the ‘haslock’ middleware from the route.put statement.

Displaying more user information in a Real-time Collaboration session

For users within a collaboration session, there are three further configuration options recommended to provide the best possible experience. These are:

  • rtc_user_details_provider - displays more information to users within the collaborative session such as full name and contact information.
  • rtc_client_connected - tells the users if another client connects
  • rtc_client_disconnected - tells the users if another client disconnects from the session.

Inside the DocumentEdit.js file, include the following:

  1. Set a rtc_user_details_provider option directly after the rtc_encryption_provider option, and set the value as ‘getUserDetails’. When you hover your mouse over the other user’s cursor, you will see their full name.

  2. Set up a console.log call for the rtc_client_connect and rtc_client connected properties:

rtc_user_details_provider: getUserDetails,
rtc_client_connected: (data) => console.log('connected:', data),
rtc_client_disconnected: (data) => console.log('disconnected:', data),

Configuring RTC snapshots for the server component

Configuring rtc_snapshot will ensure that a user's work won’t be overwritten by a time-based autosave feature.

  1. Switch back to the DocumentEdit.js, and specify the rtc_snapshot property after the encryption provider option.
  2. Set the following to configure snapshots:
  • documentId
  • version
  • getContent
rtc_snapshot: ({ documentId, version, getContent }) =>
  saveContent({ documentId, content: getContent() });
  1. Remove the UseDocumentAutosave function, and well as the oninit and onRemove handlers.
  2. Add saveContent to the imports:
Import { [...] saveContent} from '../api/api';

Modify the app.js file found in the client/src/api folder.

  1. Locate the SaveContent function on line 163, and add “version” between the documentId and content parameters.
  2. Within the list of @param Axios put calls, add a new @param that specifies the version:
@param {string} inputs.version the version.
  1. Change to the api.js file in the server/routes folder.
  2. Locate the const content object under the router.put request. This can be found on line 343 of the api.js file.
  3. Add another const
const version = req.body.version;
  1. Add another if statement on line 350 to check that the version value is an integer:
if (typeof version !== ‘number’ || !/^\d+$/.test(version)) {
    return res.status(400).json({ success: falsem message: ‘The version must be an integrer.’});
}
  1. Adjust the database to only store new content by locating the where call on line 354.
  2. Append a .andWhere call afterwards, and set the inputs to be the following values:
await req
  .db("documents")
  .where("uuid", req.params.documentUuid)
  .andWhere("version", "<", version)
  .update({ content, version });
  1. Save the file, and then update the database using knex to create a new file named “add_version” with a timestamp:
$ yarn knex migrate:make add_version
  1. In the new timestamp file, which can be found in the server/migrations folder, modify the exports up and export down properties:
exports.up = function (knex) {
  return knex.schema.alterTable("documents", (table) => {
    table.integer("version").notNullable().defaultTo(0);
  });
};

exports.down = function (knex) {
  return knex.schema.alterTable("documents", (table) => {
    table.dropColumn("version");
  });
};
  1. Run the yarn knex migrate:up command to add the changes to your database.

Now when you make changes as one of the users in the application, you can be certain that only the latest version of the document is saved.

Testing out RTC with TinyMCE

Now that you have set up RTC and its options, you can restart the server with the yarn start command. Log in to your demo application in two browser windows, one private browsing, and one regular browsing, and experiment with entering text:

Open up the console by accessing developer tools in your browser. You will then be able to see information on the other client when they connect.

Seeing when clients connect is essential for Real-time Collaboration

You can also check on user information by hovering over the cursor, and checking the console:

Hover over a user name to see more client information in Real-time Collaboration

What to try next?

If you’ve signed up with TinyMCE to test out Real-time Collaboration, let us know what you think – contact us directly at tiny.contact@tiny.cloud, or get involved in our new GitHub discussion space!

Your feedback helps us to continue building and developing the Real-time Collaboration plugin, and evolve it from version 1.0, to version 2.0. 

CollaborationConfiguration
byJoe Robinson

Technical and creative writer, editor, and a TinyMCE advocate. An enthusiast for teamwork, open source software projects, and baking. Can often be found puzzling over obscure history, cryptic words, and lucid writing.

Related Articles

  • How-tos & Tutorials

    How to get started with TinyMCE Mentions

    by Joe Robinson in How-tos & Tutorials
Subscribe for the latest insights served straight to your inbox every month.

Deploy TinyMCE in just 6 lines of code

Built to scale. Developed in open source. Designed to innovate.

Begin with your FREE API Key
Tiny Editor
Tiny logo
Privacy Policy - Terms of Use© 2021 Tiny Technologies Inc.TinyMCE® and Tiny® are registered trademarks of Tiny Technologies, Inc.

Products

  • TinyMCE
  • Tiny Drive
  • Customer Stories
  • Pricing