WebViewer Version: 8.3.0
Do you have an issue with a specific file(s)? N/A
Can you reproduce using one of our samples or online demos? No
Are you using the WebViewer server? No
Does the issue only happen on certain browsers? No
Is your issue related to a front-end framework? No
Is your issue related to annotations? Yes
Please give a brief summary of your issue:
I am have a page that uses pdftron realtime collaboration outlined in the following links:
Our realtime collaboration setup utilizes firebase as it’s backend. Every change to annotation in the pdf triggers exporting the annotation to xfdf format and saving it our SQL database.
My issue is that for some reason, one of our users managed to create an annotation that has a InReplyTo value but that value does not point to an existing annotation. I am under the impression that an annotation is not supposed to have an InReplyTo value that does not link to another annoation. This problematic annotation is preventing our “Save to SQL database” function from proceeding. I know a simple null check would prevent this issue. But I would like to ask how did this happen and how can we prevent it from occurring again?
Here is the annotation in question extracted from annotationManager.getAnnotationsList():
{
"fR": true,
"Subject": "Note",
"uD": 18,
"Dx": 337.79,
"Ex": 313.06999999999994,
"kt": 31,
"it": 31,
"Rotation": 0,
"Wd": null,
"BL": 0,
"Ax": true,
"EL": false,
"FL": false,
"Hi": {
"trn-mention": "{\"contents\":\"igone this.\",\"ids\":[]}"
},
"xx": true,
"zL": false,
"sD": false,
"yx": true,
"AL": false,
"rD": true,
"NoZoom": true,
"NoRotate": true,
"GL": true,
"Jl": {
"R": 255,
"G": 255,
"B": 0,
"A": 1
},
"VL": "Annotation Author Name",
"HD": "2022-02-13T08:35:30.000Z",
"cy": "e89570db-6040-eb9a-fe43-ad55bae3c0d6",
"VD": false,
"On": false,
"LM": false,
"jN": {},
"hk": [],
"oi": null,
"hC": null,
"mI": null,
"aq": [],
"Bn": {},
"An": {},
"DL": "772d1055-ec80-2b57-04a9-566a78fd7fb5",
"HL": null,
"ak": null,
"bk": null,
"cN": 1,
"i_": null,
"gM": "2022-02-13T08:35:30.000Z",
"wy": null,
"_xsi:type": "Sticky",
"Icon": "Comment",
"Cx": "None",
"KL": "Review",
"isImporting": false,
"eb": {},
"CB": "337.790,497.850,368.790,528.850",
"z8": false,
"Invisible": false,
"Hidden": false,
"NoView": false,
"ReadOnly": false,
"Locked": false,
"ToggleNoView": false,
"LockedContents": false,
"op": "D:20220213190530+10'30'",
"Lv": 1644741330000,
"nI": "igone this.",
"OS": "igone this.",
"Zaa": "igone this.",
"lI": null,
"kI": "D:20220213190530+10'30'",
"PS": 1644741330000,
"statemodel": "Review",
"AB": {
"x1": 337.79,
"y1": 313.06999999999994,
"x2": 368.79,
"y2": 344.06999999999994
},
"ToolName": "AnnotationCreateSticky",
"authorId": "49"
}
Here is the annotationChanged event function:
annotManager.addEventListener('annotationChanged', async (annotations, type, { imported }) => {
if (imported)
return;
const xfdf = await annotManager.exportAnnotCommand();
const processAnnotations = () => {
annotations.forEach((annotation) => {
if (type === 'add') {
let parentAuthorId = null;
if (annotation.InReplyTo) {
parentAuthorId = annotManager.getAnnotationById(annotation.InReplyTo).authorId || 'default';
}
if (authorId) {
annotation.authorId = authorId;
annotation.setCustomData('userId', authorId);
annotation.setCustomData('fromENT', 1);
}
server.createAnnotation(annotation.Id, {
authorId: authorId,
parentAuthorId: parentAuthorId,
xfdf: xfdf
});
} else if (type === 'modify') {
let parentAuthorId = null;
if (annotation.InReplyTo) {
parentAuthorId = annotManager.getAnnotationById(annotation.InReplyTo).authorId || 'default';
} else {
if (authorId != annotation.authorId)
return;
}
server.updateAnnotation(annotation.Id, {
authorId: authorId,
parentAuthorId: parentAuthorId,
xfdf: xfdf
});
} else if (type === 'delete') {
server.deleteAnnotation(annotation.Id);
}
});
}
processAnnotations();
self.saveAnnotations();
});
And here is the saveAnnotations function (the code shown here filters out annotations with Rejected and Cancelled status):
self.saveAnnotations = function () {
...
let annotList = annotManager.getAnnotationsList().sort((a, b) => a.DateModified - b.DateModified);
annotList.forEach(function (annotation) {
let parentAnnot = null;
if (annotation.InReplyTo) {
parentAnnot = annotManager.getAnnotationById(annotation.InReplyTo);
if (typeof annotation.getState === "function") {
if (annotation.getState() == "Accepted"
|| annotation.getState() == "Rejected"
|| annotation.getState() == "Completed"
|| annotation.getState() == "Cancelled")
parentAnnot.mainState = annotation.getState();
} else
parentAnnot.mainState = "Completed";
}
});
...
var filteredAnnotList = annotList.filter(function (annotation) {
if (annotation.mainState !== undefined) {
return annotation.mainState !== "Rejected" && annotation.mainState !== "Cancelled";
} else {
let parentAnnot = null;
if (annotation.InReplyTo) {
parentAnnot = annotManager.getAnnotationById(annotation.InReplyTo); //parentAnnot is null when this annotation is process
return parentAnnot.mainState !== "Rejected" && parentAnnot.mainState !== "Cancelled"; //an error is thrown in this line due to parentAnnot being null
} else {
return true;
}
}
});
...
// Save to database code here
Please describe your issue and provide steps to reproduce it:
(The more descriptive your answer, the faster we are able to help you)
N/A
Please provide a link to a minimal sample where the issue is reproducible:
N/A