@signiphi/pdf-signer 0.2.0-beta.30 → 0.2.0-beta.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"SubmissionForm.d.ts","sourceRoot":"","sources":["../../src/components/SubmissionForm.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SubmissionForm.d.ts","sourceRoot":"","sources":["../../src/components/SubmissionForm.tsx"],"names":[],"mappings":"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"}
@@ -2827,12 +2827,43 @@ function assertSigningSourceIsClean(params) {
2827
2827
  }
2828
2828
  }
2829
2829
  var REVIEW_MARKUP_SUBTYPES = /* @__PURE__ */ new Set(["Highlight", "FreeText", "Ink", "Stamp", "Text"]);
2830
- async function hasBakedReviewMarkup(pdfBytes, ignoreIds) {
2831
- const { PDFDocument, PDFName: PDFName3, PDFArray, PDFDict, PDFRef } = await loadPdfLib();
2830
+ var REVIEW_MARKER_KEY = "SigniphiReview";
2831
+ var RECT_MATCH_EPSILON = 1;
2832
+ function rectsMatch(a, b) {
2833
+ const norm = (r) => [
2834
+ Math.min(r[0], r[2]),
2835
+ Math.min(r[1], r[3]),
2836
+ Math.max(r[0], r[2]),
2837
+ Math.max(r[1], r[3])
2838
+ ];
2839
+ const na = norm(a);
2840
+ const nb = norm(b);
2841
+ return na.every((v, i) => Math.abs(v - nb[i]) <= RECT_MATCH_EPSILON);
2842
+ }
2843
+ function sourceNativeMarkDescriptors(threads) {
2844
+ const out = [];
2845
+ for (const t of threads) {
2846
+ const a = t.annotation;
2847
+ if (!a.sourceAnnotationId) continue;
2848
+ const rect = a.position?.rect;
2849
+ if (!rect || rect.length < 4) continue;
2850
+ out.push({
2851
+ subtype: ANNOTATION_SUBTYPE_MAP[a.type] || "Text",
2852
+ pageIndex: (a.position.pageNumber ?? 1) - 1,
2853
+ rect: rect.slice(0, 4)
2854
+ });
2855
+ }
2856
+ return out;
2857
+ }
2858
+ async function hasBakedReviewMarkup(pdfBytes, allowedSourceMarks) {
2859
+ const { PDFDocument, PDFName: PDFName3, PDFArray, PDFDict, PDFRef, PDFNumber } = await loadPdfLib();
2832
2860
  const pdfDoc = await PDFDocument.load(pdfBytes);
2833
2861
  const markup = [];
2834
2862
  const byTag = /* @__PURE__ */ new Map();
2835
- for (const page of pdfDoc.getPages()) {
2863
+ const pages = pdfDoc.getPages();
2864
+ for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) {
2865
+ const page = pages[pageIndex];
2866
+ if (!page) continue;
2836
2867
  const annots = page.node.lookupMaybe(PDFName3.of("Annots"), PDFArray);
2837
2868
  if (!annots) continue;
2838
2869
  for (let i = 0; i < annots.size(); i++) {
@@ -2844,47 +2875,64 @@ async function hasBakedReviewMarkup(pdfBytes, ignoreIds) {
2844
2875
  continue;
2845
2876
  }
2846
2877
  const irt = dict.get(PDFName3.of("IRT"));
2878
+ const rt = dict.get(PDFName3.of("RT"));
2879
+ const rectArr = dict.lookup(PDFName3.of("Rect"));
2880
+ let rect = null;
2881
+ if (rectArr instanceof PDFArray && rectArr.size() >= 4) {
2882
+ const values = [];
2883
+ for (let j = 0; j < 4; j++) {
2884
+ const n = rectArr.lookup(j);
2885
+ if (n instanceof PDFNumber) values.push(n.asNumber());
2886
+ }
2887
+ if (values.length === 4) rect = values;
2888
+ }
2889
+ const subtypeText = subtype.decodeText();
2847
2890
  const record = {
2848
- id: entry instanceof PDFRef ? pdfJsRefId(entry) : null,
2891
+ subtype: subtypeText,
2892
+ pageIndex,
2893
+ rect,
2849
2894
  irtTag: irt instanceof PDFRef ? irt.toString() : null,
2850
- tag: entry instanceof PDFRef ? entry.toString() : null
2895
+ tag: entry instanceof PDFRef ? entry.toString() : null,
2896
+ marker: dict.get(PDFName3.of(REVIEW_MARKER_KEY)) !== void 0,
2897
+ replyShaped: subtypeText === "Text" || rt instanceof PDFName3 && rt.decodeText() === "Group"
2851
2898
  };
2852
2899
  markup.push(record);
2853
2900
  if (record.tag) byTag.set(record.tag, record);
2854
2901
  }
2855
2902
  }
2856
2903
  if (markup.length === 0) return false;
2857
- if (!ignoreIds?.size) return true;
2858
- const rootId = (m) => {
2859
- let current = m;
2860
- for (let hops = 0; current.irtTag && hops < 64; hops++) {
2861
- const parent = byTag.get(current.irtTag);
2862
- if (!parent) break;
2863
- current = parent;
2864
- }
2865
- return current.id;
2904
+ if (!allowedSourceMarks?.length) return true;
2905
+ const matchesDescriptor = (m) => m.rect !== null && allowedSourceMarks.some(
2906
+ (d) => d.subtype === m.subtype && d.pageIndex === m.pageIndex && rectsMatch(d.rect, m.rect)
2907
+ );
2908
+ const isExempt = (m, hops = 0) => {
2909
+ if (m.marker) return false;
2910
+ if (matchesDescriptor(m)) return true;
2911
+ if (!m.replyShaped || !m.irtTag || hops >= 64) return false;
2912
+ const parent = byTag.get(m.irtTag);
2913
+ return parent !== void 0 && parent !== m && isExempt(parent, hops + 1);
2866
2914
  };
2867
- for (const m of markup) {
2868
- const id = rootId(m);
2869
- if (!id || !ignoreIds.has(id)) return true;
2870
- }
2871
- return false;
2915
+ return markup.some((m) => !isExempt(m));
2872
2916
  }
2873
2917
  function pdfJsRefId(ref) {
2874
2918
  return `${ref.objectNumber}R${ref.generationNumber === 0 ? "" : ref.generationNumber}`;
2875
2919
  }
2876
2920
  async function removeMarkupAnnotations(pdfBytes, options) {
2877
- const { PDFDocument, PDFName: PDFName3, PDFArray, PDFDict, PDFRef } = await loadPdfLib();
2921
+ const { PDFDocument, PDFName: PDFName3, PDFArray, PDFDict, PDFRef, PDFNumber } = await loadPdfLib();
2878
2922
  const pdfDoc = await PDFDocument.load(pdfBytes);
2879
2923
  const keepIds = options?.keepIds ?? /* @__PURE__ */ new Set();
2924
+ const keepDescriptors = options?.keepDescriptors ?? [];
2880
2925
  const subtypeName = PDFName3.of("Subtype");
2881
2926
  const irtName = PDFName3.of("IRT");
2927
+ const rtName = PDFName3.of("RT");
2882
2928
  const popupName = PDFName3.of("Popup");
2883
2929
  const parentName = PDFName3.of("Parent");
2884
2930
  const annotsName = PDFName3.of("Annots");
2885
2931
  const markupByTag = /* @__PURE__ */ new Map();
2886
2932
  const pages = pdfDoc.getPages();
2887
- for (const page of pages) {
2933
+ for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) {
2934
+ const page = pages[pageIndex];
2935
+ if (!page) continue;
2888
2936
  const annots = page.node.lookupMaybe(annotsName, PDFArray);
2889
2937
  if (!annots) continue;
2890
2938
  for (let i = 0; i < annots.size(); i++) {
@@ -2894,9 +2942,59 @@ async function removeMarkupAnnotations(pdfBytes, options) {
2894
2942
  if (!(dict instanceof PDFDict)) continue;
2895
2943
  const subtype = dict.get(subtypeName);
2896
2944
  if (subtype instanceof PDFName3 && REVIEW_MARKUP_SUBTYPES.has(subtype.decodeText())) {
2897
- markupByTag.set(entry.toString(), { ref: entry, dict });
2945
+ const rectArr = dict.lookup(PDFName3.of("Rect"));
2946
+ let rect = null;
2947
+ if (rectArr instanceof PDFArray && rectArr.size() >= 4) {
2948
+ const values = [];
2949
+ for (let j = 0; j < 4; j++) {
2950
+ const n = rectArr.lookup(j);
2951
+ if (n instanceof PDFNumber) values.push(n.asNumber());
2952
+ }
2953
+ if (values.length === 4) rect = values;
2954
+ }
2955
+ markupByTag.set(entry.toString(), {
2956
+ ref: entry,
2957
+ dict,
2958
+ subtype: subtype.decodeText(),
2959
+ pageIndex,
2960
+ rect
2961
+ });
2962
+ }
2963
+ }
2964
+ }
2965
+ const isChainChild = (entry) => {
2966
+ const irt = entry.dict.get(irtName);
2967
+ return irt instanceof PDFRef && markupByTag.has(irt.toString());
2968
+ };
2969
+ const matchingDescriptorIndices = (entry) => {
2970
+ if (!entry.rect) return [];
2971
+ const indices = [];
2972
+ for (let i = 0; i < keepDescriptors.length; i++) {
2973
+ const d = keepDescriptors[i];
2974
+ if (d.subtype === entry.subtype && d.pageIndex === entry.pageIndex && rectsMatch(d.rect, entry.rect)) {
2975
+ indices.push(i);
2898
2976
  }
2899
2977
  }
2978
+ return indices;
2979
+ };
2980
+ const rootKeep = /* @__PURE__ */ new Set();
2981
+ const descriptorClaimed = /* @__PURE__ */ new Set();
2982
+ const keptDescriptors = [];
2983
+ for (const [tag, entry] of markupByTag) {
2984
+ if (isChainChild(entry)) continue;
2985
+ if (keepIds.has(pdfJsRefId(entry.ref))) {
2986
+ rootKeep.add(tag);
2987
+ for (const i of matchingDescriptorIndices(entry)) descriptorClaimed.add(i);
2988
+ }
2989
+ }
2990
+ for (const [tag, entry] of markupByTag) {
2991
+ if (isChainChild(entry) || rootKeep.has(tag)) continue;
2992
+ const index = matchingDescriptorIndices(entry).find((i) => !descriptorClaimed.has(i));
2993
+ if (index !== void 0) {
2994
+ descriptorClaimed.add(index);
2995
+ rootKeep.add(tag);
2996
+ keptDescriptors.push({ index, id: pdfJsRefId(entry.ref) });
2997
+ }
2900
2998
  }
2901
2999
  const keepDecision = /* @__PURE__ */ new Map();
2902
3000
  const isKept = (tag) => {
@@ -2906,7 +3004,10 @@ async function removeMarkupAnnotations(pdfBytes, options) {
2906
3004
  const entry = markupByTag.get(tag);
2907
3005
  if (!entry) return false;
2908
3006
  const irt = entry.dict.get(irtName);
2909
- const decision = irt instanceof PDFRef && markupByTag.has(irt.toString()) ? isKept(irt.toString()) : keepIds.has(pdfJsRefId(entry.ref));
3007
+ const subtype = entry.dict.get(subtypeName);
3008
+ const rt = entry.dict.get(rtName);
3009
+ const replyShaped = subtype instanceof PDFName3 && subtype.decodeText() === "Text" || rt instanceof PDFName3 && rt.decodeText() === "Group";
3010
+ const decision = irt instanceof PDFRef && markupByTag.has(irt.toString()) ? replyShaped && isKept(irt.toString()) : rootKeep.has(tag);
2910
3011
  keepDecision.set(tag, decision);
2911
3012
  return decision;
2912
3013
  };
@@ -2923,32 +3024,140 @@ async function removeMarkupAnnotations(pdfBytes, options) {
2923
3024
  if (popup instanceof PDFRef) removedTags.add(popup.toString());
2924
3025
  }
2925
3026
  }
3027
+ let removedInline = 0;
2926
3028
  for (const page of pages) {
2927
3029
  const annots = page.node.lookupMaybe(annotsName, PDFArray);
2928
3030
  if (!annots) continue;
2929
3031
  for (let i = annots.size() - 1; i >= 0; i--) {
2930
3032
  const entry = annots.get(i);
2931
- if (!(entry instanceof PDFRef)) continue;
2932
- let remove = removedTags.has(entry.toString());
2933
- if (!remove) {
2934
- const dict = page.node.context.lookup(entry);
2935
- if (dict instanceof PDFDict) {
2936
- const subtype = dict.get(subtypeName);
2937
- const parent = dict.get(parentName);
2938
- remove = subtype instanceof PDFName3 && subtype.decodeText() === "Popup" && parent instanceof PDFRef && removedTags.has(parent.toString());
2939
- }
3033
+ let remove = false;
3034
+ if (entry instanceof PDFRef) {
3035
+ remove = removedTags.has(entry.toString());
3036
+ if (!remove) {
3037
+ const dict = page.node.context.lookup(entry);
3038
+ if (dict instanceof PDFDict) {
3039
+ const subtype = dict.get(subtypeName);
3040
+ const parent = dict.get(parentName);
3041
+ remove = subtype instanceof PDFName3 && subtype.decodeText() === "Popup" && parent instanceof PDFRef && removedTags.has(parent.toString());
3042
+ }
3043
+ }
3044
+ } else if (entry instanceof PDFDict) {
3045
+ const subtype = entry.get(subtypeName);
3046
+ const parent = entry.get(parentName);
3047
+ remove = subtype instanceof PDFName3 && REVIEW_MARKUP_SUBTYPES.has(subtype.decodeText()) || subtype instanceof PDFName3 && subtype.decodeText() === "Popup" && parent instanceof PDFRef && removedTags.has(parent.toString());
3048
+ if (remove) removedInline++;
2940
3049
  }
2941
3050
  if (remove) annots.remove(i);
2942
3051
  }
2943
3052
  }
2944
- if (removedTags.size === 0) {
2945
- return { pdfBytes, removedIds, keptIds };
3053
+ if (removedTags.size === 0 && removedInline === 0) {
3054
+ return { pdfBytes, removedIds, keptIds, keptDescriptors };
2946
3055
  }
2947
3056
  const saved = await pdfDoc.save();
2948
- return { pdfBytes: saved, removedIds, keptIds };
3057
+ return { pdfBytes: saved, removedIds, keptIds, keptDescriptors };
3058
+ }
3059
+ async function appendRepliesToExistingAnnotations(pdfBytes, threadsWithKeptRoots) {
3060
+ if (!threadsWithKeptRoots.some(({ thread }) => thread.replies.length > 0)) {
3061
+ return pdfBytes;
3062
+ }
3063
+ const { PDFDocument, PDFName: PDFName3, PDFArray, PDFDict, PDFRef, PDFNumber, PDFString: PDFString2, PDFHexString: PDFHexString2 } = await loadPdfLib();
3064
+ const pdfDoc = await PDFDocument.load(pdfBytes);
3065
+ const context = pdfDoc.context;
3066
+ const subtypeName = PDFName3.of("Subtype");
3067
+ const irtName = PDFName3.of("IRT");
3068
+ const annotsName = PDFName3.of("Annots");
3069
+ const entriesByTag = /* @__PURE__ */ new Map();
3070
+ const tagById = /* @__PURE__ */ new Map();
3071
+ for (const page of pdfDoc.getPages()) {
3072
+ const annots = page.node.lookupMaybe(annotsName, PDFArray);
3073
+ if (!annots) continue;
3074
+ for (let i = 0; i < annots.size(); i++) {
3075
+ const entry = annots.get(i);
3076
+ if (!(entry instanceof PDFRef)) continue;
3077
+ const dict = page.node.context.lookup(entry);
3078
+ if (!(dict instanceof PDFDict)) continue;
3079
+ const subtype = dict.get(subtypeName);
3080
+ if (subtype instanceof PDFName3 && REVIEW_MARKUP_SUBTYPES.has(subtype.decodeText())) {
3081
+ entriesByTag.set(entry.toString(), { ref: entry, dict, annotsArray: annots });
3082
+ tagById.set(pdfJsRefId(entry), entry.toString());
3083
+ }
3084
+ }
3085
+ }
3086
+ const decodeText = (value) => {
3087
+ if (value instanceof PDFHexString2 || value instanceof PDFString2) return value.decodeText();
3088
+ return null;
3089
+ };
3090
+ const rootTagOf = (tag) => {
3091
+ let current = tag;
3092
+ const seen = /* @__PURE__ */ new Set();
3093
+ while (!seen.has(current)) {
3094
+ seen.add(current);
3095
+ const irt = entriesByTag.get(current)?.dict.get(irtName);
3096
+ if (!(irt instanceof PDFRef) || !entriesByTag.has(irt.toString())) break;
3097
+ current = irt.toString();
3098
+ }
3099
+ return current;
3100
+ };
3101
+ const dedupKey = (author, contents) => `${author ?? "Document"}
3102
+ ${contents ?? ""}`;
3103
+ const existingByRootTag = /* @__PURE__ */ new Map();
3104
+ for (const [tag, entry] of entriesByTag) {
3105
+ if (!(entry.dict.get(irtName) instanceof PDFRef)) continue;
3106
+ const rootTag = rootTagOf(tag);
3107
+ if (rootTag === tag) continue;
3108
+ let keys = existingByRootTag.get(rootTag);
3109
+ if (!keys) existingByRootTag.set(rootTag, keys = /* @__PURE__ */ new Set());
3110
+ keys.add(
3111
+ dedupKey(decodeText(entry.dict.get(PDFName3.of("T"))), decodeText(entry.dict.get(PDFName3.of("Contents"))))
3112
+ );
3113
+ }
3114
+ let appended = 0;
3115
+ for (const { thread, rootId } of threadsWithKeptRoots) {
3116
+ const rootTag = tagById.get(rootId);
3117
+ const root = rootTag ? entriesByTag.get(rootTag) : void 0;
3118
+ if (!root || !rootTag) {
3119
+ logger.debug(
3120
+ `appendRepliesToExistingAnnotations: no markup dict with id ${rootId} in these bytes; skipping ${thread.replies.length} reply(ies).`
3121
+ );
3122
+ continue;
3123
+ }
3124
+ const rectArr = root.dict.get(PDFName3.of("Rect"));
3125
+ const rect = [0, 0, 0, 0];
3126
+ if (rectArr instanceof PDFArray && rectArr.size() >= 4) {
3127
+ for (let j = 0; j < 4; j++) {
3128
+ const n = rectArr.lookup(j);
3129
+ if (n instanceof PDFNumber) rect[j] = n.asNumber();
3130
+ }
3131
+ }
3132
+ let keys = existingByRootTag.get(rootTag);
3133
+ if (!keys) existingByRootTag.set(rootTag, keys = /* @__PURE__ */ new Set());
3134
+ for (const reply of thread.replies) {
3135
+ const key = dedupKey(reply.author.name || reply.author.email || "Unknown", reply.content);
3136
+ if (keys.has(key)) continue;
3137
+ keys.add(key);
3138
+ addReplyAnnotation(
3139
+ context,
3140
+ root.annotsArray,
3141
+ root.ref,
3142
+ reply,
3143
+ PDFName3,
3144
+ PDFArray,
3145
+ PDFString2,
3146
+ PDFNumber,
3147
+ PDFHexString2,
3148
+ rect[0],
3149
+ rect[1],
3150
+ rect[2],
3151
+ rect[3]
3152
+ );
3153
+ appended++;
3154
+ }
3155
+ }
3156
+ if (appended === 0) return pdfBytes;
3157
+ return pdfDoc.save();
2949
3158
  }
2950
- async function assertSigningBytesFreeOfReviewMarkup(pdfBytes, allowedSourceIds) {
2951
- if (await hasBakedReviewMarkup(pdfBytes, allowedSourceIds)) {
3159
+ async function assertSigningBytesFreeOfReviewMarkup(pdfBytes, allowedSourceMarks) {
3160
+ if (await hasBakedReviewMarkup(pdfBytes, allowedSourceMarks)) {
2952
3161
  throw new ReviewAnnotationSigningError(
2953
3162
  "SubmissionForm: the PDF selected as the signing source still carries baked review annotations (markup annotation dicts are present in its pages). Signing it would embed those review marks in the signed copy. Pass `pdfUrlForSigning` pointing at the clean original/prepared PDF (distinct from the baked review PDF) so the deliverable is filled from un-annotated source. This check is content-based, so it holds even when the annotation tree was not hydrated in the viewer."
2954
3163
  );
@@ -3107,6 +3316,7 @@ async function embedAnnotationsInPdf(pdfBytes, annotations, options) {
3107
3316
  const annotDict = context.obj({});
3108
3317
  annotDict.set(PDFName3.of("Type"), PDFName3.of("Annot"));
3109
3318
  annotDict.set(PDFName3.of("Subtype"), PDFName3.of(subtype));
3319
+ annotDict.set(PDFName3.of(REVIEW_MARKER_KEY), PDFName3.of("v1"));
3110
3320
  const [x1, y1, x2, y2] = serializedRect;
3111
3321
  const rectArray = PDFArray.withContext(context);
3112
3322
  rectArray.push(PDFNumber.of(x1));
@@ -3260,6 +3470,7 @@ function addReplyAnnotation(context, annotsArray, parentRef, reply, PDFName3, PD
3260
3470
  const replyDict = context.obj({});
3261
3471
  replyDict.set(PDFName3.of("Type"), PDFName3.of("Annot"));
3262
3472
  replyDict.set(PDFName3.of("Subtype"), PDFName3.of("Text"));
3473
+ replyDict.set(PDFName3.of(REVIEW_MARKER_KEY), PDFName3.of("v1"));
3263
3474
  const rectArray = PDFArray.withContext(context);
3264
3475
  rectArray.push(PDFNumber.of(x1));
3265
3476
  rectArray.push(PDFNumber.of(y1));
@@ -3300,20 +3511,42 @@ async function deriveReviewPdf(preparedPdfBytes, annotationTree, options) {
3300
3511
  for (const t of annotationTree) {
3301
3512
  if (isUnchangedSourceThread(t)) wantKeep.set(t.annotation.sourceAnnotationId, t);
3302
3513
  }
3303
- const { pdfBytes: cleaned, keptIds, removedIds } = await removeMarkupAnnotations(
3304
- preparedPdfBytes,
3305
- { keepIds: new Set(wantKeep.keys()) }
3306
- );
3514
+ const descriptorOwners = [];
3515
+ const descriptors = [];
3516
+ for (const thread of wantKeep.values()) {
3517
+ const [descriptor] = sourceNativeMarkDescriptors([thread]);
3518
+ if (descriptor) {
3519
+ descriptors.push(descriptor);
3520
+ descriptorOwners.push(thread);
3521
+ }
3522
+ }
3523
+ const { pdfBytes: cleaned, keptIds, removedIds, keptDescriptors } = await removeMarkupAnnotations(preparedPdfBytes, {
3524
+ keepIds: new Set(wantKeep.keys()),
3525
+ keepDescriptors: descriptors
3526
+ });
3307
3527
  if (removedIds.length > 0 || keptIds.length > 0) {
3308
3528
  logger.debug(
3309
3529
  `deriveReviewPdf(sanitizeSource): kept ${keptIds.length} source mark(s), removed ${removedIds.length} from the source bytes before embedding.`
3310
3530
  );
3311
3531
  }
3312
3532
  const found = new Set(keptIds);
3533
+ const contentMatched = /* @__PURE__ */ new Map();
3534
+ for (const { index, id } of keptDescriptors) {
3535
+ const owner = descriptorOwners[index];
3536
+ if (owner) contentMatched.set(owner, id);
3537
+ }
3313
3538
  const toEmbed = annotationTree.filter(
3314
- (t) => !(t.annotation.sourceAnnotationId && found.has(t.annotation.sourceAnnotationId))
3539
+ (t) => !(t.annotation.sourceAnnotationId && found.has(t.annotation.sourceAnnotationId)) && !contentMatched.has(t)
3315
3540
  );
3316
- return embedAnnotationsInPdf(cleaned, toEmbed, embedOptions);
3541
+ const keptRootThreads = [];
3542
+ for (const [sourceId, thread] of wantKeep) {
3543
+ const rootId = found.has(sourceId) ? sourceId : contentMatched.get(thread);
3544
+ if (rootId && thread.replies.length > 0) {
3545
+ keptRootThreads.push({ thread, rootId });
3546
+ }
3547
+ }
3548
+ const withReplies = keptRootThreads.length ? await appendRepliesToExistingAnnotations(cleaned, keptRootThreads) : cleaned;
3549
+ return embedAnnotationsInPdf(withReplies, toEmbed, embedOptions);
3317
3550
  }
3318
3551
  if (!allowMarkupInSource && await hasBakedReviewMarkup(preparedPdfBytes)) {
3319
3552
  throw new ReviewAnnotationSigningError(
@@ -5608,13 +5841,13 @@ function usePdfViewer(multiSignerContext) {
5608
5841
  [extractedFields]
5609
5842
  );
5610
5843
  const fillPdf = React9.useCallback(
5611
- async (fieldValues, signatures, currentSignerEmail, metadata, auditTrail, pdfBytesOverride, guardReviewMarkup = false, allowedSourceMarkupIds) => {
5844
+ async (fieldValues, signatures, currentSignerEmail, metadata, auditTrail, pdfBytesOverride, guardReviewMarkup = false, allowedSourceMarks) => {
5612
5845
  const sourceBytes = pdfBytesOverride ?? originalPdfBytes;
5613
5846
  if (!sourceBytes) {
5614
5847
  throw new PdfProcessingError("No PDF loaded");
5615
5848
  }
5616
5849
  if (guardReviewMarkup) {
5617
- await assertSigningBytesFreeOfReviewMarkup(sourceBytes, allowedSourceMarkupIds);
5850
+ await assertSigningBytesFreeOfReviewMarkup(sourceBytes, allowedSourceMarks);
5618
5851
  }
5619
5852
  try {
5620
5853
  const valuesValidation = validateFieldValues(fieldValues);
@@ -6536,6 +6769,31 @@ var AnnotationBridge = class {
6536
6769
  logger.error("AnnotationBridge: Failed to navigate to page:", e);
6537
6770
  }
6538
6771
  }
6772
+ /**
6773
+ * Origin check for inbound viewer messages (defence-in-depth alongside the
6774
+ * event.source check). The viewer iframe is always embedded same-origin
6775
+ * (`public/pdfjs/web/...` served by the host app), so the legitimate sender
6776
+ * origin is our own — but derive the expectation from the iframe's actual
6777
+ * `src` so a host that serves the viewer from another origin keeps working.
6778
+ * blob: URLs inherit their creator's origin, which `new URL(...).origin`
6779
+ * resolves correctly.
6780
+ *
6781
+ * Residual gap: when the iframe's src resolves to an opaque origin
6782
+ * (`'null'`, e.g. a sandboxed data: URL) origins are indistinguishable and
6783
+ * we fall back to the event.source check alone rather than dropping all
6784
+ * messages.
6785
+ */
6786
+ isExpectedOrigin(origin) {
6787
+ const src = this.iframeRef.current?.src;
6788
+ if (!src) return origin === window.location.origin;
6789
+ try {
6790
+ const srcOrigin = new URL(src, window.location.href).origin;
6791
+ if (srcOrigin === "null") return true;
6792
+ return origin === srcOrigin || origin === window.location.origin;
6793
+ } catch {
6794
+ return origin === window.location.origin;
6795
+ }
6796
+ }
6539
6797
  /**
6540
6798
  * Start listening for annotation events from the iframe
6541
6799
  */
@@ -6544,6 +6802,7 @@ var AnnotationBridge = class {
6544
6802
  this.messageHandler = (event) => {
6545
6803
  const expectedSource = this.iframeRef.current?.contentWindow;
6546
6804
  if (!expectedSource || event.source !== expectedSource) return;
6805
+ if (!this.isExpectedOrigin(event.origin)) return;
6547
6806
  if (!event.data || typeof event.data.type !== "string") return;
6548
6807
  if (!event.data.type.startsWith("signiphi:annotation:")) return;
6549
6808
  const { type, payload } = event.data;
@@ -6561,7 +6820,7 @@ var AnnotationBridge = class {
6561
6820
  this.callbacks.onAnnotationsInjected?.(payload?.count ?? 0);
6562
6821
  break;
6563
6822
  case "signiphi:annotation:clicked":
6564
- if (payload?.editorId) {
6823
+ if (typeof payload?.editorId === "string" && payload.editorId) {
6565
6824
  this.callbacks.onAnnotationClicked?.(payload.editorId);
6566
6825
  }
6567
6826
  break;
@@ -7109,11 +7368,12 @@ function useAnnotationChat({
7109
7368
  if (annotationsRef.current.length > 0) return;
7110
7369
  const bridge = bridgeRef.current;
7111
7370
  if (!bridge) return;
7112
- sourceIngestDone.current = true;
7113
7371
  let cancelled = false;
7114
7372
  void (async () => {
7115
7373
  const source = await bridge.getSourceMarkupAnnotations();
7116
- if (cancelled || source.length === 0) return;
7374
+ if (cancelled) return;
7375
+ sourceIngestDone.current = true;
7376
+ if (source.length === 0) return;
7117
7377
  const threads = sourceMarkupToThreads(source);
7118
7378
  if (threads.length === 0) return;
7119
7379
  logger.debug(
@@ -14501,9 +14761,7 @@ function SubmissionForm({
14501
14761
  pdfUrl,
14502
14762
  pdfUrlForSigning
14503
14763
  });
14504
- const sourceNativeIds = new Set(
14505
- annotationChat.annotations.map((t) => t.annotation.sourceAnnotationId).filter((id) => !!id)
14506
- );
14764
+ const sourceNativeMarks = sourceNativeMarkDescriptors(annotationChat.annotations);
14507
14765
  let pdfBytesOverride = null;
14508
14766
  const signingUrl = pdfUrlForSigning?.trim();
14509
14767
  if (signingUrl && signingUrl !== pdfUrl) {
@@ -14523,7 +14781,7 @@ function SubmissionForm({
14523
14781
  auditTrail,
14524
14782
  pdfBytesOverride,
14525
14783
  enableAnnotationChat,
14526
- sourceNativeIds
14784
+ sourceNativeMarks
14527
14785
  );
14528
14786
  const finalPdfBytes = filledPdfBytes;
14529
14787
  const annotationThreads = enableAnnotationChat ? annotationChat.getAnnotationsForPdfSave() : void 0;