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

Define user permissions in PDF.js Express

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

This PDF.js Express sample demonstrates how you can apply permissions to users viewing your PDF document (no other external dependencies required). In this specific example you define how a user interacts with annotations in the PDF viewer. One common use case is removing a user’s ability to add annotation by providing them read-only access. Another example would be defining the types of annotations a user sees on a document – they can see highlighted text from another user but not the sticky notes they added. For other users you can give them the ability to respond to comments or give administrative rights to delete annotations. View demo

Note: This sample uses a custom Server class created in this guide.

const server = new Server();

  WebViewer({
    path: '/static/WebViewer/lib/', // make sure that this relative path to the lib folder is correct
    pdftronServer: 'https://demo.pdftron.com/', // comment this out to do client-side only
    initialDoc: 'https://pdftron.s3.amazonaws.com/downloads/pl/demo-annotated.pdf',
  }, document.getElementById('viewer'))
    .then((instance) => {
      const { docViewer, annotManager } = instance;
      const urlInput = document.getElementById('url');
      const copyButton = document.getElementById('copy');
      instance.openElement('notesPanel');

      if (window.location.origin === 'http://localhost:3000') {
        const xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange =  () => {
          if (xhttp.readyState === 4 && xhttp.status === 200) {
            urlInput.value = `http://${xhttp.responseText}:3000/samples/annotation/realtime-collaboration/`;
          }
        };
        xhttp.open('GET', '/ip', true);
        xhttp.send();
      } else {
        urlInput.value = 'https://pdftron.com/samples/web/samples/annotation/realtime-collaboration/';
      }

      copyButton.onclick = () => {
        urlInput.select();
        document.execCommand('copy');
        document.getSelection().empty();
      };

      docViewer.on('documentLoaded', () => {
        let authorId = null;

        async function onAnnotationCreated(data) {
        // Import the annotation based on xfdf command
          const [annotation] = await annotManager.importAnnotCommand(data.val().xfdf);
          // Set a custom field authorId to be used in client-side permission check
          annotation.authorId = data.val().authorId;
          annotManager.redrawAnnotation(annotation);
        }

        async function onAnnotationUpdated(data) {
        // Import the annotation based on xfdf command
          const [annotation] = annotManager.importAnnotCommand(data.val().xfdf);
          // Set a custom field authorId to be used in client-side permission check
          annotation.authorId = data.val().authorId;
          annotManager.redrawAnnotation(annotation);
        }

        function onAnnotationDeleted(data) {
        // data.key would return annotationId since our server method is designed as
        // annotationsRef.child(annotationId).set(annotationData)
          const command = `<delete><id>${data.key}</id></delete>`;
          annotManager.importAnnotCommand(command).then(importedAnnotations => { });
        }

        function openReturningAuthorPopup(authorName) {
        // The author name will be used for both WebViewer and annotations in PDF
          annotManager.setCurrentUser(authorName);
          // Open popup for the returning author
          window.alert(`Welcome back ${authorName}`);
        }

        function updateAuthor(authorName) {
          // The author name will be used for both WebViewer and annotations in PDF
          annotManager.setCurrentUser(authorName);
          // Create/update author information in the server
          server.updateAuthor(authorId, { authorName });
        }

        function openNewAuthorPopup() {
        // Open prompt for a new author
          const name = window.prompt('Welcome! Tell us your name :)');
          if (name) {
            updateAuthor(name);
          }
        }

        // Bind server-side authorization state change to a callback function
        // The event is triggered in the beginning as well to check if author has already signed in
        server.bind('onAuthStateChanged', (user) => {
        // Author is logged in
          if (user) {
          // Using uid property from Firebase Database as an author id
          // It is also used as a reference for server-side permission
            authorId = user.uid;
            // Check if author exists, and call appropriate callback functions
            server.checkAuthor(authorId, openReturningAuthorPopup, openNewAuthorPopup);
            // Bind server-side data events to callback functions
            // When loaded for the first time, onAnnotationCreated event will be triggered for all database entries
            server.bind('onAnnotationCreated', onAnnotationCreated);
            server.bind('onAnnotationUpdated', onAnnotationUpdated);
            server.bind('onAnnotationDeleted', onAnnotationDeleted);
          } else {
          // Author is not logged in
            server.signInAnonymously();
          }
        });

        // Bind annotation change events to a callback function
        annotManager.on('annotationChanged', (annotations, type, e) => {
        // e.imported is true by default for annotations from pdf and annotations added by importAnnotCommand
          if (e.imported) {
            return;
          }
          // Iterate through all annotations and call appropriate server methods
          annotations.forEach((annotation) => {
            annotManager.exportAnnotCommand().then(xfdf =>{
              let parentAuthorId = null;
              if (type === 'add') {
              // In case of replies, add extra field for server-side permission to be granted to the
              // parent annotation's author
                if (annotation.InReplyTo) {
                  parentAuthorId = annotManager.getAnnotationById(annotation.InReplyTo).authorId || 'default';
                }
                server.createAnnotation(annotation.Id, {
                  authorId,
                  parentAuthorId,
                  xfdf,
                });
              } else if (type === 'modify') {
              // In case of replies, add extra field for server-side permission to be granted to the
              // parent annotation's author
                if (annotation.InReplyTo) {
                  parentAuthorId = annotManager.getAnnotationById(annotation.InReplyTo).authorId || 'default';
                }
                server.updateAnnotation(annotation.Id, {
                  authorId,
                  parentAuthorId,
                  xfdf,
                });
              } else if (type === 'delete') {
                server.deleteAnnotation(annotation.Id);
              }
            });
          });
        });

        // Overwrite client-side permission check method on the annotation manager
        // The default was set to compare the authorName
        // Instead of the authorName, we will compare authorId created from the server
        annotManager.setPermissionCheckCallback((author, annotation) => annotation.authorId === authorId);
      });
  });
const server = new Server();

  WebViewer({
    path: '/static/WebViewer/lib/', // make sure that this relative path to the lib folder is correct
    pdftronServer: 'https://demo.pdftron.com/', // comment this out to do client-side only
    initialDoc: 'https://pdftron.s3.amazonaws.com/downloads/pl/demo-annotated.pdf',
  }, document.getElementById('viewer'))
    .then((instance) => {
      const { documentViewer, annotationManager } = instance.Core;
      const urlInput = document.getElementById('url');
      const copyButton = document.getElementById('copy');
      instance.UI.openElement('notesPanel');

      if (window.location.origin === 'http://localhost:3000') {
        const xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange =  () => {
          if (xhttp.readyState === 4 && xhttp.status === 200) {
            urlInput.value = `http://${xhttp.responseText}:3000/samples/annotation/realtime-collaboration/`;
          }
        };
        xhttp.open('GET', '/ip', true);
        xhttp.send();
      } else {
        urlInput.value = 'https://pdftron.com/samples/web/samples/annotation/realtime-collaboration/';
      }

      copyButton.onclick = () => {
        urlInput.select();
        document.execCommand('copy');
        document.getSelection().empty();
      };

      documentViewer.addEventListener('documentLoaded', () => {
        let authorId = null;

        async function onAnnotationCreated(data) {
        // Import the annotation based on xfdf command
          const [annotation] = await annotationManager.importAnnotCommand(data.val().xfdf);
          // Set a custom field authorId to be used in client-side permission check
          annotation.authorId = data.val().authorId;
          annotationManager.redrawAnnotation(annotation);
        }

        async function onAnnotationUpdated(data) {
        // Import the annotation based on xfdf command
          const [annotation] = annotationManager.importAnnotCommand(data.val().xfdf);
          // Set a custom field authorId to be used in client-side permission check
          annotation.authorId = data.val().authorId;
          annotationManager.redrawAnnotation(annotation);
        }

        function onAnnotationDeleted(data) {
        // data.key would return annotationId since our server method is designed as
        // annotationsRef.child(annotationId).set(annotationData)
          const command = `<delete><id>${data.key}</id></delete>`;
          annotationManager.importAnnotCommand(command).then(importedAnnotations => { });
        }

        function openReturningAuthorPopup(authorName) {
        // The author name will be used for both WebViewer and annotations in PDF
          annotationManager.setCurrentUser(authorName);
          // Open popup for the returning author
          window.alert(`Welcome back ${authorName}`);
        }

        function updateAuthor(authorName) {
          // The author name will be used for both WebViewer and annotations in PDF
          annotationManager.setCurrentUser(authorName);
          // Create/update author information in the server
          server.updateAuthor(authorId, { authorName });
        }

        function openNewAuthorPopup() {
        // Open prompt for a new author
          const name = window.prompt('Welcome! Tell us your name :)');
          if (name) {
            updateAuthor(name);
          }
        }

        // Bind server-side authorization state change to a callback function
        // The event is triggered in the beginning as well to check if author has already signed in
        server.bind('onAuthStateChanged', (user) => {
        // Author is logged in
          if (user) {
          // Using uid property from Firebase Database as an author id
          // It is also used as a reference for server-side permission
            authorId = user.uid;
            // Check if author exists, and call appropriate callback functions
            server.checkAuthor(authorId, openReturningAuthorPopup, openNewAuthorPopup);
            // Bind server-side data events to callback functions
            // When loaded for the first time, onAnnotationCreated event will be triggered for all database entries
            server.bind('onAnnotationCreated', onAnnotationCreated);
            server.bind('onAnnotationUpdated', onAnnotationUpdated);
            server.bind('onAnnotationDeleted', onAnnotationDeleted);
          } else {
          // Author is not logged in
            server.signInAnonymously();
          }
        });

        // Bind annotation change events to a callback function
        annotationManager.addEventListener('annotationChanged', (annotations, type, e) => {
        // e.imported is true by default for annotations from pdf and annotations added by importAnnotCommand
          if (e.imported) {
            return;
          }
          // Iterate through all annotations and call appropriate server methods
          annotations.forEach((annotation) => {
            annotationManager.exportAnnotCommand().then(xfdf =>{
              let parentAuthorId = null;
              if (type === 'add') {
              // In case of replies, add extra field for server-side permission to be granted to the
              // parent annotation's author
                if (annotation.InReplyTo) {
                  parentAuthorId = annotationManager.getAnnotationById(annotation.InReplyTo).authorId || 'default';
                }
                server.createAnnotation(annotation.Id, {
                  authorId,
                  parentAuthorId,
                  xfdf,
                });
              } else if (type === 'modify') {
              // In case of replies, add extra field for server-side permission to be granted to the
              // parent annotation's author
                if (annotation.InReplyTo) {
                  parentAuthorId = annotationManager.getAnnotationById(annotation.InReplyTo).authorId || 'default';
                }
                server.updateAnnotation(annotation.Id, {
                  authorId,
                  parentAuthorId,
                  xfdf,
                });
              } else if (type === 'delete') {
                server.deleteAnnotation(annotation.Id);
              }
            });
          });
        });

        // Overwrite client-side permission check method on the annotation manager
        // The default was set to compare the authorName
        // Instead of the authorName, we will compare authorId created from the server
        annotationManager.setPermissionCheckCallback((author, annotation) => annotation.authorId === authorId);
      });
  });
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <link rel="stylesheet" href="../../style.css">
    <script src="/static/WebViewer/lib/webviewer.min.js"></script>
    <script src='../../old-browser-checker.js'></script>
    <script src="https://www.gstatic.com/firebasejs/3.5.3/firebase.js"></script>
    <script src="server.js"></script>
  </head>
  <body>
    <header>
      <div className="title sample">Realtime collaboration sample</div>
    </header>
    <aside>
      <h1>Controls</h1>
      <h2>Share this url:</h2>
      <input id="url" type="text" style={{"width":"calc(100% - 8px)","padding":"2px"}} readonly>
      <br />
      <button id="copy">Copy</button>
      <hr />
      <h1>Instructions</h1>
      <p>Type your name when the prompt comes up. It will set your username and log you in to the realtime collaboration backend. Please note that the backend is being shared globally, so you might see annotations created by strangers.</p>
      <p>Share the link with others in your local network to start collaborating!</p>
    </aside>
    <div id="viewer"></div>
    <script src="../../menu-button.js"></script>
    <script src="realtime-collaboration.js"></script>

  </body>
</html>