# 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:

Among them, the operative keys are these:
CollaborationCommunicatorThis 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/collaborationdirectory, there is a sockjs based communicator, you may refer to its implementation for more details.CollabroationDataHandlerSuppose 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
CollabroationDataHandlerinterface, which defines two methods:accept(data: CollaborationData): Promise<boolean>This method will be called and used to determine whether the current
CollaborationDataHandlerinterface 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 inCOLLABORATION_ACTION, there is no longer to do other synchronizing operations.
CollaborationSessionInfoWhen 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.
UserCustomizeCollaborationDataOur API design allows users to customize collaborative operations. You can send custom collaborative data through the
PDFViewer.collaborateAPI, and then receive the custom collaborative data in the customCollaborationDataHandlerto perform synchronization.COLLABORATION_ACTIONEnumerations 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.
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-QClient A shares the collaborative link to other clients, who will be connected to the same server once they open the link.
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.
When other clients receive the synchronizing data, Web SDK will find the first data handler that
acceptthe operation data, and process the received data using theCollaborationDataHandler, 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
})
});
});
});