Integrate WebViewer with SQLite3 to Enable Collaboration

This is a WebViewer sample to show how you can construct a real time collaboration server for WebViewer using WebSocket, SQLite3, and Node.js server.

WebViewer provides a slick out-of-the-box responsive UI that enables you to view, annotate and manipulate PDFs and other document types inside any web project.

Click the button below to view the full project in GitHub.

1const viewerElement = document.getElementById('viewer');
2
3let annotationManager = null;
4const DOCUMENT_ID = 'webviewer-demo-1';
5const hostName = window.location.hostname;
6const url = `ws://${hostName}:8181`;
7const connection = new WebSocket(url);
8const nameList = ['Andy', 'Andrew', 'Logan', 'Justin', 'Matt', 'Sardor', 'Zhijie', 'James', 'Kristian', 'Mary', 'Patricia', 'Jennifer', 'Linda', 'David', 'Joseph', 'Thomas', 'Naman', 'Nancy', 'Sandra'];
9const serializer = new XMLSerializer();
10
11connection.onerror = error => {
12 console.warn(`Error from WebSocket: ${error}`);
13}
14
15WebViewer.Iframe({
16 path: 'lib', // path to the PDFTron 'lib' folder
17 initialDoc: 'https://pdftron.s3.amazonaws.com/downloads/pl/webviewer-demo.pdf',
18 documentXFDFRetriever: async () => {
19 const rows = await loadXfdfStrings(DOCUMENT_ID);
20 return JSON.parse(rows).map(row => row.xfdfString);
21 },
22}, viewerElement).then( instance => {
23
24 // Instance is ready here
25 instance.UI.openElements(['leftPanel']);
26 annotationManager = instance.Core.documentViewer.getAnnotationManager();
27 // Assign a random name to client
28 annotationManager.setCurrentUser(nameList[Math.floor(Math.random()*nameList.length)]);
29 annotationManager.addEventListener('annotationChanged', async e => {
30 // If annotation change is from import, return
31 if (e.imported) {
32 return;
33 }
34
35 const xfdfString = await annotationManager.exportAnnotationCommand();
36 // Parse xfdfString to separate multiple annotation changes to individual annotation change
37 const parser = new DOMParser();
38 const commandData = parser.parseFromString(xfdfString, 'text/xml');
39 const addedAnnots = commandData.getElementsByTagName('add')[0];
40 const modifiedAnnots = commandData.getElementsByTagName('modify')[0];
41 const deletedAnnots = commandData.getElementsByTagName('delete')[0];
42
43 // List of added annotations
44 addedAnnots.childNodes.forEach((child) => {
45 sendAnnotationChange(child, 'add');
46 });
47
48 // List of modified annotations
49 modifiedAnnots.childNodes.forEach((child) => {
50 sendAnnotationChange(child, 'modify');
51 });
52
53 // List of deleted annotations
54 deletedAnnots.childNodes.forEach((child) => {
55 sendAnnotationChange(child, 'delete');
56 });
57 });
58
59 connection.onmessage = async (message) => {
60 const annotation = JSON.parse(message.data);
61 const annotations = await annotationManager.importAnnotationCommand(annotation.xfdfString);
62 await annotationManager.drawAnnotationsFromList(annotations);
63 }
64});
65
66const loadXfdfStrings = (documentId) => {
67 return new Promise((resolve, reject) => {
68 fetch(`/server/annotationHandler.js?documentId=${documentId}`, {
69 method: 'GET',
70 }).then((res) => {
71 if (res.status < 400) {
72 res.text().then(xfdfStrings => {
73 resolve(xfdfStrings);
74 });
75 } else {
76 reject(res);
77 }
78 });
79 });
80};
81
82
83// wrapper function to convert xfdf fragments to full xfdf strings
84const convertToXfdf = (changedAnnotation, action) => {
85 let xfdfString = `<?xml version="1.0" encoding="UTF-8" ?><xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve"><fields />`;
86 if (action === 'add') {
87 xfdfString += `<add>${changedAnnotation}</add><modify /><delete />`;
88 } else if (action === 'modify') {
89 xfdfString += `<add /><modify>${changedAnnotation}</modify><delete />`;
90 } else if (action === 'delete') {
91 xfdfString += `<add /><modify /><delete>${changedAnnotation}</delete>`;
92 }
93 xfdfString += `</xfdf>`;
94 return xfdfString;
95}
96
97// helper function to send annotation changes to WebSocket server
98const sendAnnotationChange = (annotation, action) => {
99 if (annotation.nodeType !== annotation.TEXT_NODE) {
100 const annotationString = serializer.serializeToString(annotation);
101 connection.send(JSON.stringify({
102 documentId: DOCUMENT_ID,
103 annotationId: annotation.getAttribute('name'),
104 xfdfString: convertToXfdf(annotationString, action)
105 }));
106 }
107}

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales