# Getting started with collaboration

# Quickly run collaboration example

Please refer to quickly-run-samples to start the service, and then open the following address in the browser to access the collaboration example:

http://127.0.0.1:8080/examples/UIExtension/collaboration/index.html (opens new window)

# Working principle

First, open the API reference, there are collaboration related interfaces and classes as shown below:

Collaboration classes list in API reference

Among them, the operative keys are these:

  1. CollaborationCommunicator

    This interface is the communication bridge between the browser and the server. It is responsible for connecting to the server, creating a collaborative session, synchronizing data, etc. In the /examples/UIExtension/collaboration directory, there is a sockjs based communicator, you may refer to its implementation for more details.

  2. CollabroationDataHandler

    Suppose we have two client devices: client A and client B. When client A creates an annotation, its related annotation data will be sent to the server, where data will be further forwarded to the client B. When client B receives the data, it immediately repeats the same operation as the client A based on the data content. At this point, the data synchronization is realized. The above operation process of client B is implemented by the CollabroationDataHandler interface, which defines two methods:

    • accept(data: CollaborationData): Promise<boolean>

      This method will be called and used to determine whether the current CollaborationDataHandler interface should handle the data when the data arrives.

    • receive(data: CollaborationData, builtinHandler?: CollaborationDataHandler)

      When the accept() method returns true, this method will be called with two parameters. The first is the currently received collaborative data and the second is a built-in data handler. In some custom cases, you should decide whether to call the built-in data handler, since if the built-in data handler is called when the operation action is not defined in COLLABORATION_ACTION, there is no longer to do other synchronizing operations.

  3. CollaborationSessionInfo

    When the client opens a PDF file, it will send a request to the server to create a new session. This session contains a unique shareId and information about the current PDF file. In this way, after sharing the link with the shareId to other users, the other users can open this link and see the original PDF file and start the collaboration session.

  4. UserCustomizeCollaborationData

    Our API design allows users to customize collaborative operations. You can send custom collaborative data through the PDFViewer.collaborate API, and then receive the custom collaborative data in the custom CollaborationDataHandler to perform synchronization.

  5. COLLABORATION_ACTION

    Enumerations of the built-in collaboration actions.

How does the collaboration work? The following section explain a complete process in details.

Note: Our collaboration example uses Node.js as the backend server and uses websocket as a communication channel to establish a long connection for collaboration.

  1. Client A opens a PDF document, where the document information will be sent to server to start a new session with a unique shareId. In the browser, you will see the address like this: http://localhost:8080/examples/UIExtension/collaboration/index.html?shareId=2MF8Rp7-Q

  2. Client A shares the collaborative link to other clients, who will be connected to the same server once they open the link.

  3. Client A performs an action to create an annotation for example. The action operation data is sent to server synchronously on the background, and then forwarded to other clients.

  4. When other clients receive the synchronizing data, Web SDK will find the first data handler that accept the operation data, and process the received data using the CollaborationDataHandler, and then output the same action of the Client A on screen.

At this point, a complete collaborative operation is completed!

# The infinite loop problems

Looking at the following code, first, register the DataEvent.annotationAdded event to send collaborative data after the event is triggered, then register a click event on a button to create annotation via PDFPage.addAnnot API. The call of the PDFPage.addAnnot method will trigger the DataEvent.annotationAdded event. This logic looks perfect. However, assuming that there are two client A and B at this time. The button of client A is clicked first and a collaborative data is sent, and then client B also calls the PDFPage.addAnnot method after receiving the collaborative data, and then the problem appears, because the PDFPage.addAnnot triggered the DataEvents.annotationAdded event, and the event callback sent a collaborative data, all clients would continue to send request after receiving the collaborative data. At this process an infinite loop problem rises.

var DataEvents = PDFViewCtrl.PDF.DataEvents;
pdfViewer.eventEmitter.on(DataEvents.annotationAdded, function(annots){
    var doc = pdfViewer.getCurrentPDFDoc();
    doc.exportAnnotsToJSON(annots).then(function(annotsJSONArray) {
        pdfViewer.collaborate(PDFViewCtrl.collab.COLLABORATION_ACTION.CREATE_ANNOT, {
            annots: annotsJSONArray
        })
    });
});
var $button = jQuery('#create-square-button');
$button.on('click', function() {
    var doc = pdfViewer.getCurrentPDFDoc();
    doc.getPageByIndex(0).then(function(page) {
        page.addAnnot({
            color: 0xff0000,
            rect: {  left: 300, right: 400, top: 300, bottom: 200 },
            flags: 4,
            type: 'square'
        });
    });
});

var $button = jQuery('#create-line-button');
$button.on('click', function() {
    var doc = pdfViewer.getCurrentPDFDoc();
    doc.getPageByIndex(0).then(function(page) {
        page.addAnnot({
            type: 'line',
            startStyle: 0,
            endStyle: PDF.constant.Ending_Style.Square,
            startPoint: {x: 0, y: 0},
            endPoint: {x: 100, y: 100},
            rect: {
                left: 0,
                right: 100,
                top: 0,
                bottom: 100
            }
        });
    });
});

To avoid the problem, we should move off the call of pdfViewer.collaborate from the DataEvents callback and place it in the click event callback.

var $button = jQuery('#create-square-button');
$button.on('click', function() {
    var doc = pdfViewer.getCurrentPDFDoc();
    doc.getPageByIndex(0).then(function(page) {
        page.addAnnot({
            color: 0xff0000,
            rect: {  left: 300, right: 400, top: 300, bottom: 200 },
            flags: 4,
            type: 'square'
        }).then(function(annots) {
            return doc.exportAnnotsToJSON(annots);
        }).then(function(annotsJSONArray) {
            pdfViewer.collaborate(PDFViewCtrl.collab.COLLABORATION_ACTION.CREATE_ANNOT, {
                annots: annotsJSONArray
            })
        });
    });
});

var $button = jQuery('#create-line-button');
$button.on('click', function() {
    var doc = pdfViewer.getCurrentPDFDoc();
    doc.getPageByIndex(0).then(function(page) {
        page.addAnnot({
            type: 'line',
            startStyle: 0,
            endStyle: PDF.constant.Ending_Style.Square,
            startPoint: {x: 0, y: 0},
            endPoint: {x: 100, y: 100},
            rect: {
                left: 0,
                right: 100,
                top: 0,
                bottom: 100
            }
        }).then(function(annots) {
            return doc.exportAnnotsToJSON(annots);
        }).then(function(annotsJSONArray) {
            pdfViewer.collaborate(PDFViewCtrl.collab.COLLABORATION_ACTION.CREATE_ANNOT, {
                annots: annotsJSONArray
            })
        });
    });
});