PDF.js Express Plusplay_arrow

Professional PDF.js Viewing & Annotations - Try for free

side menu

Get Started

play_arrow

Learn more

play_arrow

Common use cases

play_arrow

Open a document

play_arrow

Save a document

play_arrow

Viewer

play_arrow

UI Customization

play_arrow

Annotations

play_arrow

Collaboration

play_arrow

Forms

play_arrow

Signature

play_arrow

Searching

play_arrow

Measurement

play_arrow

Compare

play_arrow

Advanced Capabilities

play_arrow

PDF.js Express REST API

play_arrow

Migration Guides

play_arrow

Set up server for real-time collaboration

The following features are available in:

check

PDF.js Express Viewer

help_outline

PDF.js Express Viewer is a free viewer with limited capabilities compared to PDF.js Express Plus

check

PDF.js Express Plus

help_outline

PDF.js Express Plus is a commercial PDF SDK for viewing, annotating, signing, form filling and more

In realtime collaboration, a server will merely act as an online database that triggers events upon data creation/modification/deletion. As long as the above requirement is met, your server can be built in any language and stack of your choice. For the simplicity of this guide, we will be using Firebase.

  1. Go to the Firebase Console, login and create a project.
  2. Click "Add Firebase to your Web App" and copy the whole code for "Initializing Firebase". If storageBucket is empty, close the popup and try again (that's a known bug from Firebase).
  3. Create a JavaScript file and name it server.js.
  4. Paste the code that you have copied from Firebase. (Note that you should remove the script tags)
  5. Store the firebase.database.References for annotations and users. We will use these to create/update/delete data, and listen to data change events as well.
window.Server = () => {
  const config = {
    apiKey: "YOUR_API_KEY",
    authDomain: "PROJECT_ID.firebaseapp.com",
    databaseURL: "https://PROJECT_ID.firebaseio.com",
    storageBucket: "PROJECT_ID.appspot.com",
    messagingSenderId: "YOUR_SENDER_ID"
  };
  firebase.initializeApp(config);

  this.annotationsRef = firebase.database().ref().child('annotations');
  this.authorsRef = firebase.database().ref().child('authors');
};
  1. Create a custom bind function for authorization and data using firebase.auth.Auth#onAuthStateChanged and firebase.database.Reference#on.
Server.prototype.bind = (action, callbackFunction) => {
  switch(action) {
    case 'onAuthStateChanged':
      firebase.auth().onAuthStateChanged(callbackFunction);
      break;
    case 'onAnnotationCreated':
      this.annotationsRef.on('child_added', callbackFunction);
      break;
    case 'onAnnotationUpdated':
      this.annotationsRef.on('child_changed', callbackFunction);
      break;
    case 'onAnnotationDeleted':
      this.annotationsRef.on('child_removed', callbackFunction);
      break;
    default:
      console.error('The action is not defined.');
      break;
  }
};
  1. Define a method to check if author exists in the database. We will use firebase.database.Reference#once and firebase.database.DataSnapshot#hasChild to do so.
Server.prototype.checkAuthor = (authorId, openReturningAuthorPopup, openNewAuthorPopup) => {
  this.authorsRef.once('value', (authors) => {
    if (authors.hasChild(authorId)) {
      this.authorsRef.child(authorId).once('value', (author) => {
        openReturningAuthorPopup(author.val().authorName);
      });
    } else {
      openNewAuthorPopup();
    }
  }.bind(this));
};
  1. Define a sign-in method. In this guide, we will use firebase.auth.Auth#signInAnonymously.
Server.prototype.signInAnonymously = () => {
  firebase.auth().signInAnonymously().catch((error) => {
    if (error.code === 'auth/operation-not-allowed') {
      alert('You must enable Anonymous auth in the Firebase Console.');
    } else {
      console.error(error);
    }
  });
};
  1. From the Firebase console click the "Authentication" button on the left panel and then click the "Sign-in Method" tab, just to the right of "Users". From this page click the "Anonymous" button and choose to enable Anonymous login.

  2. Define data-write methods using firebase.database.Reference#set and firebase.database.Reference#remove.

Server.prototype.createAnnotation = (annotationId, annotationData) => {
  this.annotationsRef.child(annotationId).set(annotationData);
};

Server.prototype.updateAnnotation = (annotationId, annotationData) => {
  this.annotationsRef.child(annotationId).set(annotationData);
};

Server.prototype.deleteAnnotation = (annotationId) => {
  this.annotationsRef.child(annotationId).remove();
};

Server.prototype.updateAuthor = (authorId, authorData) => {
  this.authorsRef.child(authorId).set(authorData);
};

Last but not least, you should add server-side permission rules for writing data. Although client-side permission checking is supported in PDF.js Express Web Viewer, every user does have access to each annotation's information (including authorId and authorName). Thus, data-write permission should be regulated in the server as well. In this guide, we have used Firebase's Database Rules.

  1. Copy the JSON below and paste it in your Firebase Console's Database Rules. From the console click the "Database" button on the left panel and then click the "Rules" tab, just to the right of "Data". This will make sure that trying to modify someone else's annotation isn't allowed.
{
  "rules": {
    ".read": "auth != null",

    "annotations": {
      "$annotationId": {
        ".write": "auth.uid === newData.child('authorId').val() || auth.uid === data.child('authorId').val() || auth.uid === newData.child('parentAuthorId').val() || auth.uid === data.child('parentAuthorId').val()"
      }
    },

    "authors": {
      "$authorId": {
        ".write": "auth.uid === $authorId"
      }
    }
  }
}