@cloudpdf/engine 3.0.0-next.14 → 3.0.0-next.16
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.
- package/dist/index.cjs +2294 -1204
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +501 -225
- package/dist/index.d.ts +501 -225
- package/dist/index.js +2406 -1299
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
|
|
3
3
|
let _embedpdf_engine_core_wire = require("@embedpdf/engine-core/wire");
|
|
4
|
-
let _embedpdf_engine_services = require("@embedpdf/engine-services");
|
|
5
4
|
//#region src/document/planes.ts
|
|
6
5
|
/**
|
|
7
|
-
* Plane-scope resolution —
|
|
8
|
-
* path family: a read resolves at the
|
|
6
|
+
* Plane-scope resolution — the one rule every cloud service uses to pick a
|
|
7
|
+
* path family: a read resolves at the doc-level (shared base) path iff every
|
|
9
8
|
* plane it depends on is inherited (`'base'`) by this layer. One owned plane
|
|
10
9
|
* → the layer-scoped path.
|
|
11
10
|
*
|
|
@@ -27,7 +26,7 @@ var CloudDocumentActionsService = class {
|
|
|
27
26
|
this.isClosed = isClosed;
|
|
28
27
|
this.manifest = manifest;
|
|
29
28
|
}
|
|
30
|
-
|
|
29
|
+
get() {
|
|
31
30
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
32
31
|
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.getJsonWithRefresh(async (currentSignal) => {
|
|
33
32
|
const manifest = await this.manifest.get(currentSignal);
|
|
@@ -38,42 +37,48 @@ var CloudDocumentActionsService = class {
|
|
|
38
37
|
}
|
|
39
38
|
};
|
|
40
39
|
//#endregion
|
|
41
|
-
//#region
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
40
|
+
//#region src/transport/bundleMultipart.ts
|
|
41
|
+
/** The server holds the bundle limits; the client only checks what arrived. */
|
|
42
|
+
const NO_BUNDLE_LIMITS = {
|
|
43
|
+
bundleBytes: Infinity,
|
|
44
|
+
manifestBytes: Infinity,
|
|
45
|
+
items: Infinity,
|
|
46
|
+
pages: Infinity,
|
|
47
|
+
resources: Infinity,
|
|
48
|
+
resourceBytes: Infinity,
|
|
49
|
+
imagePixels: Infinity
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* An exported bundle as the server sends it: the `body` part (the bundle
|
|
53
|
+
* without its bytes, as JSON) and one `resource:<id>` part per resource.
|
|
54
|
+
* The caller checks the bundle.
|
|
55
|
+
*/
|
|
56
|
+
async function readBundleParts(kind, form) {
|
|
57
|
+
const body = form.get("body");
|
|
58
|
+
if (typeof body !== "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `${kind} export has no body part`);
|
|
59
|
+
const resources = {};
|
|
60
|
+
const parts = [];
|
|
61
|
+
form.forEach((value, name) => parts.push([name, value]));
|
|
62
|
+
for (const [name, value] of parts) {
|
|
63
|
+
if (!name.startsWith("resource:")) continue;
|
|
64
|
+
if (typeof value === "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `${kind} export part ${name} is text`);
|
|
65
|
+
resources[name.slice(9)] = new Uint8Array(await value.arrayBuffer());
|
|
59
66
|
}
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
function toPropertyKey(t) {
|
|
65
|
-
var i = toPrimitive(t, "string");
|
|
66
|
-
return "symbol" == _typeof(i) ? i : i + "";
|
|
67
|
+
return {
|
|
68
|
+
body: JSON.parse(body),
|
|
69
|
+
resources
|
|
70
|
+
};
|
|
67
71
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
/**
|
|
73
|
+
* A bundle import as one multipart body: the `body` part (the bundle
|
|
74
|
+
* without its bytes, and the options), then each resource once, as a
|
|
75
|
+
* `resource:<id>` part.
|
|
76
|
+
*/
|
|
77
|
+
function bundleImportForm(body, resources) {
|
|
78
|
+
const form = new FormData();
|
|
79
|
+
form.append("body", JSON.stringify(body));
|
|
80
|
+
for (const [id, bytes] of Object.entries(resources)) form.append(`resource:${id}`, new Blob([bytes]), id);
|
|
81
|
+
return form;
|
|
77
82
|
}
|
|
78
83
|
//#endregion
|
|
79
84
|
//#region src/document/CloudDocumentAnnotationsService.ts
|
|
@@ -81,29 +86,41 @@ function _defineProperty(e, r, t) {
|
|
|
81
86
|
* version (404 on the immutable leaf → refresh the manifest → retry). */
|
|
82
87
|
const MAX_COHERENCE_RESTARTS = 2;
|
|
83
88
|
var CloudDocumentAnnotationsService = class {
|
|
84
|
-
constructor(http, docId, layerName, isClosed, manifest) {
|
|
89
|
+
constructor(http, docId, layerName, isClosed, manifest, publisher, writes, importLimits) {
|
|
85
90
|
this.http = http;
|
|
86
91
|
this.docId = docId;
|
|
87
92
|
this.layerName = layerName;
|
|
88
93
|
this.isClosed = isClosed;
|
|
89
94
|
this.manifest = manifest;
|
|
95
|
+
this.publisher = publisher;
|
|
96
|
+
this.writes = writes;
|
|
97
|
+
this.importLimits = importLimits;
|
|
90
98
|
}
|
|
91
99
|
/**
|
|
92
|
-
*
|
|
93
|
-
* `annotations/items@annotationsVersion=N` leaf at the
|
|
94
|
-
* materialized server-side by one raw (no page-load)
|
|
95
|
-
* because the pin bumps only when annotation list
|
|
96
|
-
* A stale pin mid-read (a concurrent mutation →
|
|
97
|
-
* manifest and retries, so the result always belongs
|
|
98
|
-
* moment (the torn read this method exists to prevent).
|
|
100
|
+
* Every page: one coherent whole-document snapshot, a single read of the
|
|
101
|
+
* immutable `annotations/items@annotationsVersion=N` leaf at the
|
|
102
|
+
* manifest's pin — materialized server-side by one raw (no page-load)
|
|
103
|
+
* sweep, CDN-cacheable because the pin bumps only when annotation list
|
|
104
|
+
* bodies actually change. A stale pin mid-read (a concurrent mutation →
|
|
105
|
+
* 404) refreshes the manifest and retries, so the result always belongs
|
|
106
|
+
* to one document moment (the torn read this method exists to prevent).
|
|
107
|
+
*
|
|
108
|
+
* Some pages: each page's versioned leaf, the same read as
|
|
109
|
+
* `page.annotations.list()`, in the order asked.
|
|
99
110
|
*/
|
|
100
|
-
|
|
111
|
+
list(options = {}) {
|
|
101
112
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
113
|
+
const { pages } = options;
|
|
114
|
+
if (pages !== void 0) return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => (0, _embedpdf_engine_core_runtime.concatAnnotationLists)(await Promise.all(pages.map((page) => this.readPage(page, signal)))));
|
|
102
115
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
103
116
|
for (let attempt = 0;; attempt++) {
|
|
104
117
|
const manifest = attempt === 0 ? await this.manifest.get(signal) : await this.manifest.refresh(signal);
|
|
105
118
|
try {
|
|
106
|
-
|
|
119
|
+
const list = await this.readBulkAt(manifest, signal);
|
|
120
|
+
return {
|
|
121
|
+
...list,
|
|
122
|
+
auditHead: list.auditHead ?? manifest.auditHead
|
|
123
|
+
};
|
|
107
124
|
} catch (err) {
|
|
108
125
|
if (!_embedpdf_engine_core_runtime.EngineError.is(err, _embedpdf_engine_core_runtime.EngineErrorCode.NotFound) || attempt >= MAX_COHERENCE_RESTARTS) throw err;
|
|
109
126
|
}
|
|
@@ -117,150 +134,130 @@ var CloudDocumentAnnotationsService = class {
|
|
|
117
134
|
* that window over the body is all no-ops. */
|
|
118
135
|
async readBulkAt(manifest, signal) {
|
|
119
136
|
const path = planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docAnnotationsAll(this.docId, manifest.annotationsVersion) : _embedpdf_engine_core_wire.wirePaths.layerAnnotationsAll(this.docId, this.layerName, manifest.annotationsVersion);
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
...body,
|
|
123
|
-
auditHead: body.auditHead ?? manifest.auditHead
|
|
124
|
-
};
|
|
137
|
+
return this.http.getJson(path, (raw) => _embedpdf_engine_core_wire.AnnotationListSchema.parse(raw), signal);
|
|
125
138
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
139
|
+
/** One page's versioned leaf, with the standard stale-pin retry
|
|
140
|
+
* (404 → refresh the manifest → once). */
|
|
141
|
+
readPage(page, signal) {
|
|
142
|
+
return this.http.getJsonWithRefresh(async (s) => this.versionedPagePath(await this.manifest.get(s), page.objectNumber), (raw) => _embedpdf_engine_core_wire.AnnotationListSchema.parse(raw), async (s) => {
|
|
129
143
|
await this.manifest.refresh(s);
|
|
130
|
-
}, signal)
|
|
131
|
-
}
|
|
132
|
-
/** Versioned leaf URL for one manifest page entry, with the same
|
|
133
|
-
* plane routing as `CloudPageAnnotationsService.list()`: an inherited
|
|
134
|
-
* `annotations` plane reads the doc-level base leaf. */
|
|
135
|
-
pagePathAt(manifest, page) {
|
|
136
|
-
return planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageAnnotations(this.docId, page.state.pageObjectNumber, page.cache.annotationVersion) : _embedpdf_engine_core_wire.wirePaths.layerPageAnnotations(this.docId, this.layerName, page.state.pageObjectNumber, page.cache.annotationVersion);
|
|
137
|
-
}
|
|
138
|
-
versionedPagePath(manifest, pageObjectNumber) {
|
|
139
|
-
const page = manifest.pages.find((p) => p.state.pageObjectNumber === pageObjectNumber);
|
|
140
|
-
if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
|
|
141
|
-
return this.pagePathAt(manifest, page);
|
|
144
|
+
}, signal);
|
|
142
145
|
}
|
|
143
|
-
|
|
146
|
+
/**
|
|
147
|
+
* One versioned, CDN-cacheable read at the manifest's annotation and
|
|
148
|
+
* layout pins, with the stale-pin retry (404 → refresh the manifest →
|
|
149
|
+
* once). The response is the bundle without its bytes and one part per
|
|
150
|
+
* resource; every part is checked against its id before it is returned.
|
|
151
|
+
*/
|
|
152
|
+
export(selection = {}) {
|
|
144
153
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
145
154
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
146
|
-
const
|
|
147
|
-
|
|
155
|
+
const read = async (s) => {
|
|
156
|
+
const manifest = await this.manifest.get(s);
|
|
157
|
+
const path = this.exportPathAt(manifest, selection);
|
|
158
|
+
if (path) return this.http.getFormData(path, s);
|
|
159
|
+
return this.http.postJsonFormData(_embedpdf_engine_core_wire.wirePaths.layerAnnotationsExportRequest(this.docId, this.layerName), {
|
|
160
|
+
annotationsVersion: manifest.annotationsVersion,
|
|
161
|
+
layoutVersion: manifest.layoutVersion,
|
|
162
|
+
selection
|
|
163
|
+
}, s);
|
|
164
|
+
};
|
|
165
|
+
let form;
|
|
166
|
+
try {
|
|
167
|
+
form = await read(signal);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (!_embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.NotFound)) throw error;
|
|
170
|
+
await this.manifest.refresh(signal);
|
|
171
|
+
form = await read(signal);
|
|
172
|
+
}
|
|
173
|
+
const { body, resources } = await readBundleParts("annotation", form);
|
|
174
|
+
const bundle = {
|
|
175
|
+
...body,
|
|
176
|
+
resources
|
|
177
|
+
};
|
|
178
|
+
await (0, _embedpdf_engine_core_runtime.assertAnnotationBundle)(bundle, NO_BUNDLE_LIMITS);
|
|
179
|
+
return bundle;
|
|
148
180
|
});
|
|
149
181
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
_defineProperty(this, "released", false);
|
|
159
|
-
this.response = response;
|
|
160
|
-
}
|
|
161
|
-
get id() {
|
|
162
|
-
return this.response.sessionId;
|
|
163
|
-
}
|
|
164
|
-
get expiresAt() {
|
|
165
|
-
return this.response.expiresAt;
|
|
166
|
-
}
|
|
167
|
-
get heartbeatIntervalMs() {
|
|
168
|
-
return this.response.heartbeatIntervalMs;
|
|
169
|
-
}
|
|
170
|
-
get pageObjectNumbers() {
|
|
171
|
-
return this.response.pageObjectNumbers;
|
|
172
|
-
}
|
|
173
|
-
covers(pageObjectNumber) {
|
|
174
|
-
return this.response.pageObjectNumbers.includes(pageObjectNumber);
|
|
175
|
-
}
|
|
176
|
-
updatePages(pageObjectNumbers) {
|
|
177
|
-
if (this.isClosed() || this.released) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `weak annotation session ${this.id} is closed`));
|
|
182
|
+
/**
|
|
183
|
+
* One request: the bundle's manifest and each resource once, as parts of
|
|
184
|
+
* one multipart POST under the import's `opId` as `Idempotency-Key`, so a
|
|
185
|
+
* retry applies once. The server holds the limits; the same numbers are checked
|
|
186
|
+
* here first, so a bundle past one fails before its bytes move.
|
|
187
|
+
*/
|
|
188
|
+
import(bundle, options = {}) {
|
|
189
|
+
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
178
190
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
179
|
-
|
|
191
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
192
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
193
|
+
const { resources, ...rest } = bundle;
|
|
194
|
+
(0, _embedpdf_engine_core_runtime.assertAnnotationBundleManifest)(bundle, new Map(Object.entries(resources).map(([id, bytes]) => [id, bytes.length])), await this.importLimits());
|
|
195
|
+
const form = bundleImportForm({
|
|
196
|
+
bundle: rest,
|
|
197
|
+
options: {
|
|
198
|
+
...options.pages !== void 0 ? { pages: options.pages } : {},
|
|
199
|
+
...options.attribution !== void 0 ? { attribution: options.attribution } : {}
|
|
200
|
+
}
|
|
201
|
+
}, resources);
|
|
202
|
+
const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerAnnotationsImport(this.docId, this.layerName), form, (raw) => _embedpdf_engine_core_wire.AnnotationImportResultSchema.parse(raw), signal, sent));
|
|
203
|
+
this.manifest.apply(result.meta, ["annotations"]);
|
|
204
|
+
this.publisher.publishWrite(opId, ...(0, _embedpdf_engine_core_runtime.annotationImportFacts)(result).map((fact) => ({
|
|
205
|
+
type: "annotations.created",
|
|
206
|
+
...fact
|
|
207
|
+
})));
|
|
208
|
+
return result;
|
|
209
|
+
});
|
|
180
210
|
});
|
|
181
211
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
212
|
+
/**
|
|
213
|
+
* The cacheable export URL for `selection` at the manifest's pins — the
|
|
214
|
+
* base leaf while the layer inherits both planes the bundle depends on —
|
|
215
|
+
* or `null` when a URL can't carry it: a URL has room for a few hundred
|
|
216
|
+
* refs.
|
|
217
|
+
*/
|
|
218
|
+
exportPathAt(manifest, selection) {
|
|
219
|
+
const token = {
|
|
220
|
+
annotationsVersion: manifest.annotationsVersion,
|
|
221
|
+
layoutVersion: manifest.layoutVersion,
|
|
222
|
+
selection
|
|
223
|
+
};
|
|
224
|
+
try {
|
|
225
|
+
return planesInherited(manifest, ["annotations", "layout"]) ? _embedpdf_engine_core_wire.wirePaths.docAnnotationsExport(this.docId, token) : _embedpdf_engine_core_wire.wirePaths.layerAnnotationsExport(this.docId, this.layerName, token);
|
|
226
|
+
} catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/** Versioned leaf URL for one manifest page entry, with the same
|
|
231
|
+
* plane routing as `CloudPageAnnotationsService.list()`: an inherited
|
|
232
|
+
* `annotations` plane reads the doc-level base leaf. */
|
|
233
|
+
pagePathAt(manifest, page) {
|
|
234
|
+
const ref = page.page;
|
|
235
|
+
return planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageAnnotations(this.docId, ref, page.cache.annotationVersion) : _embedpdf_engine_core_wire.wirePaths.layerPageAnnotations(this.docId, this.layerName, ref, page.cache.annotationVersion);
|
|
187
236
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return
|
|
237
|
+
versionedPagePath(manifest, pageObjectNumber) {
|
|
238
|
+
const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
|
|
239
|
+
if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
|
|
240
|
+
return this.pagePathAt(manifest, page);
|
|
192
241
|
}
|
|
193
242
|
};
|
|
194
243
|
//#endregion
|
|
195
|
-
//#region src/document/buildMutationForm.ts
|
|
196
|
-
/**
|
|
197
|
-
* Multipart envelope for mutations that carry binaries: part `body` holds
|
|
198
|
-
* the exact JSON the plain request would have been, plus one
|
|
199
|
-
* `resource:{key}` file part per binary payload. Mirrors the appearance
|
|
200
|
-
* response protocol (`manifest` part + named image parts) in reverse.
|
|
201
|
-
* Shared by the annotation mutations (stamp images, attachment files) and
|
|
202
|
-
* the document-level `attachments.create` — one envelope, one parser on
|
|
203
|
-
* the server side.
|
|
204
|
-
*/
|
|
205
|
-
function buildMutationForm(body, resources) {
|
|
206
|
-
const form = new FormData();
|
|
207
|
-
form.append("body", JSON.stringify(body));
|
|
208
|
-
for (const [key, resource] of Object.entries(resources)) form.append(`resource:${key}`, new Blob([resource.bytes], { type: resource.mimeType ?? "application/octet-stream" }), resource.name ?? key);
|
|
209
|
-
return form;
|
|
210
|
-
}
|
|
211
|
-
function hasResources(resources) {
|
|
212
|
-
return Object.keys(resources).length > 0;
|
|
213
|
-
}
|
|
214
|
-
//#endregion
|
|
215
|
-
//#region src/document/parseAttachmentContent.ts
|
|
216
|
-
/** Header carrying the file's name, token-text encoded (names are
|
|
217
|
-
* arbitrary UTF-8; HTTP header values are not). */
|
|
218
|
-
const FILE_NAME_HEADER = "X-EmbedPDF-File-Name";
|
|
219
|
-
/**
|
|
220
|
-
* Project an attachment-file response into `AttachmentContent`. The body
|
|
221
|
-
* IS the decoded bytes; the metadata rides as headers — `Content-Type`
|
|
222
|
-
* for the declared mime type, `X-EmbedPDF-File-Name` for the file name.
|
|
223
|
-
* Shared by the document-level `attachments.download()` and the
|
|
224
|
-
* annotation-level `annotations.downloadFile()` reads — one wire shape
|
|
225
|
-
* for both homes a file can live in.
|
|
226
|
-
*/
|
|
227
|
-
function parseAttachmentContent(file) {
|
|
228
|
-
const encodedName = file.headers.get(FILE_NAME_HEADER);
|
|
229
|
-
if (encodedName === null) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `attachment response missing ${FILE_NAME_HEADER} header`);
|
|
230
|
-
let name;
|
|
231
|
-
try {
|
|
232
|
-
name = (0, _embedpdf_engine_core_wire.decodeTokenText)(encodedName);
|
|
233
|
-
} catch (err) {
|
|
234
|
-
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `malformed ${FILE_NAME_HEADER} header: ${err?.message ?? err}`, { cause: err });
|
|
235
|
-
}
|
|
236
|
-
const mimeType = file.headers.get("Content-Type");
|
|
237
|
-
return {
|
|
238
|
-
bytes: file.bytes,
|
|
239
|
-
name,
|
|
240
|
-
...mimeType !== null ? { mimeType } : {}
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
//#endregion
|
|
244
244
|
//#region src/document/CloudDocumentAttachmentsService.ts
|
|
245
|
-
/** The `/attachments@…` listing leaf is a bare array of name-tree entries. */
|
|
246
|
-
const EmbeddedFileListSchema = _embedpdf_engine_core_wire.EmbeddedFileItemSchema.array();
|
|
247
245
|
/**
|
|
248
|
-
*
|
|
249
|
-
* name tree)
|
|
250
|
-
* `
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
* never
|
|
246
|
+
* The reads of document-level attachments (the catalog's `/EmbeddedFiles`
|
|
247
|
+
* name tree) on the cloud engine; `create` and `delete` are changes
|
|
248
|
+
* (`attachmentVerbs`, through `POST …/changes`). Reads use immutable leaves
|
|
249
|
+
* pinned by the manifest's `attachmentsVersion`: `/attachments@…` for the
|
|
250
|
+
* metadata listing and `/attachment-files/…@…` for one file's decoded
|
|
251
|
+
* bytes — a separate capability tier, so a CDN credential for the listing
|
|
252
|
+
* can never egress content.
|
|
255
253
|
*/
|
|
256
254
|
var CloudDocumentAttachmentsService = class {
|
|
257
|
-
constructor(http, docId, layerName, isClosed, manifest
|
|
255
|
+
constructor(http, docId, layerName, isClosed, manifest) {
|
|
258
256
|
this.http = http;
|
|
259
257
|
this.docId = docId;
|
|
260
258
|
this.layerName = layerName;
|
|
261
259
|
this.isClosed = isClosed;
|
|
262
260
|
this.manifest = manifest;
|
|
263
|
-
this.publisher = publisher;
|
|
264
261
|
}
|
|
265
262
|
/**
|
|
266
263
|
* Snapshot of the name tree, in tree (key-sorted) order. Pulls
|
|
@@ -276,16 +273,15 @@ var CloudDocumentAttachmentsService = class {
|
|
|
276
273
|
const manifest = await this.manifest.get(s);
|
|
277
274
|
return planesInherited(manifest, ["attachments"]) ? _embedpdf_engine_core_wire.wirePaths.docAttachments(this.docId, manifest.attachmentsVersion) : _embedpdf_engine_core_wire.wirePaths.layerAttachments(this.docId, this.layerName, manifest.attachmentsVersion);
|
|
278
275
|
};
|
|
279
|
-
return this.http.getJsonWithRefresh(buildPath, (raw) =>
|
|
276
|
+
return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.AttachmentListSchema.parse(raw), async (s) => {
|
|
280
277
|
await this.manifest.refresh(s);
|
|
281
278
|
}, signal);
|
|
282
279
|
});
|
|
283
280
|
}
|
|
284
281
|
/**
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
* as {@link list}.
|
|
282
|
+
* One embedded file's decoded bytes; its name and type are what the
|
|
283
|
+
* listing says. Same versioned-leaf + refresh-retry rails as
|
|
284
|
+
* {@link list}. The server's limit on decoded size applies.
|
|
289
285
|
*/
|
|
290
286
|
download(ref) {
|
|
291
287
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
@@ -294,201 +290,616 @@ var CloudDocumentAttachmentsService = class {
|
|
|
294
290
|
const manifest = await this.manifest.get(s);
|
|
295
291
|
return planesInherited(manifest, ["attachments"]) ? _embedpdf_engine_core_wire.wirePaths.docAttachmentFile(this.docId, ref.key, manifest.attachmentsVersion) : _embedpdf_engine_core_wire.wirePaths.layerAttachmentFile(this.docId, this.layerName, ref.key, manifest.attachmentsVersion);
|
|
296
292
|
};
|
|
297
|
-
return
|
|
293
|
+
return (await this.http.getFileWithRefresh(buildPath, async (s) => {
|
|
298
294
|
await this.manifest.refresh(s);
|
|
299
|
-
}, signal));
|
|
295
|
+
}, signal)).bytes;
|
|
300
296
|
});
|
|
301
297
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
298
|
+
};
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/document/buildMutationForm.ts
|
|
301
|
+
/**
|
|
302
|
+
* The multipart envelope of a write that carries files, in the shape every
|
|
303
|
+
* multipart message has: the `body` part holds the JSON the plain request
|
|
304
|
+
* would have been, which names each file by role
|
|
305
|
+
* (`resources: { <role>: <key> }`), and each file is the part
|
|
306
|
+
* `resource:<key>`.
|
|
307
|
+
*/
|
|
308
|
+
function buildMutationForm(body, resources) {
|
|
309
|
+
const form = new FormData();
|
|
310
|
+
form.append("body", JSON.stringify(body));
|
|
311
|
+
for (const [key, resource] of Object.entries(resources)) form.append(`resource:${key}`, new Blob([resource.bytes], { type: resource.mimeType ?? "application/octet-stream" }), resource.name ?? key);
|
|
312
|
+
return form;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* The envelope of a write with at most one file per role (an annotation's
|
|
316
|
+
* drawing or attached file, a page insert's source PDF, a signature's
|
|
317
|
+
* mark): each file's key is its role's name, and the body names it so,
|
|
318
|
+
* `resources: { <role>: '<role>' }`.
|
|
319
|
+
*/
|
|
320
|
+
function buildRoleMutationForm(body, files) {
|
|
321
|
+
const parts = {};
|
|
322
|
+
const resources = {};
|
|
323
|
+
for (const [role, file] of Object.entries(files)) {
|
|
324
|
+
if (!file) continue;
|
|
325
|
+
parts[role] = file;
|
|
326
|
+
resources[role] = role;
|
|
327
|
+
}
|
|
328
|
+
return buildMutationForm(Object.keys(resources).length > 0 ? {
|
|
329
|
+
...body,
|
|
330
|
+
resources
|
|
331
|
+
} : body, parts);
|
|
332
|
+
}
|
|
333
|
+
/** An annotation write's envelope: its drawing and attached file, by role. */
|
|
334
|
+
function buildAnnotationMutationForm(body, resources) {
|
|
335
|
+
return buildRoleMutationForm(body, {
|
|
336
|
+
appearance: resources.appearance && { bytes: resources.appearance },
|
|
337
|
+
file: resources.file && { bytes: resources.file }
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/typeof.js
|
|
342
|
+
function _typeof(o) {
|
|
343
|
+
"@babel/helpers - typeof";
|
|
344
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
345
|
+
return typeof o;
|
|
346
|
+
} : function(o) {
|
|
347
|
+
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
348
|
+
}, _typeof(o);
|
|
349
|
+
}
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPrimitive.js
|
|
352
|
+
function toPrimitive(t, r) {
|
|
353
|
+
if ("object" != _typeof(t) || !t) return t;
|
|
354
|
+
var e = t[Symbol.toPrimitive];
|
|
355
|
+
if (void 0 !== e) {
|
|
356
|
+
var i = e.call(t, r || "default");
|
|
357
|
+
if ("object" != _typeof(i)) return i;
|
|
358
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
320
359
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
360
|
+
return ("string" === r ? String : Number)(t);
|
|
361
|
+
}
|
|
362
|
+
//#endregion
|
|
363
|
+
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPropertyKey.js
|
|
364
|
+
function toPropertyKey(t) {
|
|
365
|
+
var i = toPrimitive(t, "string");
|
|
366
|
+
return "symbol" == _typeof(i) ? i : i + "";
|
|
367
|
+
}
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/defineProperty.js
|
|
370
|
+
function _defineProperty(e, r, t) {
|
|
371
|
+
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
372
|
+
value: t,
|
|
373
|
+
enumerable: !0,
|
|
374
|
+
configurable: !0,
|
|
375
|
+
writable: !0
|
|
376
|
+
}) : e[r] = t, e;
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/document/CloudDocumentChanges.ts
|
|
380
|
+
/** How many `opId`s a handle remembers having published: far more than any retry spans. */
|
|
381
|
+
const PUBLISHED_MEMORY = 4096;
|
|
382
|
+
/**
|
|
383
|
+
* `doc.apply` on the cloud: each change takes its place in the document's
|
|
384
|
+
* line of writes, and goes out with the changes called right after it, as
|
|
385
|
+
* one `POST …/changes` (see {@link CloudWrites.change}). The server checks
|
|
386
|
+
* every op against the caller inside the write, so nothing about the ops is
|
|
387
|
+
* checked here beyond what the request must hold.
|
|
388
|
+
*
|
|
389
|
+
* A change's events are published once its answer arrives, all sharing its
|
|
390
|
+
* `opId`. A change asked again gets the first answer and publishes nothing:
|
|
391
|
+
* this handle remembers the `opId`s it published.
|
|
392
|
+
*/
|
|
393
|
+
var CloudDocumentChanges = class {
|
|
394
|
+
constructor(isClosed, manifest, publisher, writes) {
|
|
395
|
+
this.isClosed = isClosed;
|
|
396
|
+
this.manifest = manifest;
|
|
397
|
+
this.publisher = publisher;
|
|
398
|
+
this.writes = writes;
|
|
399
|
+
_defineProperty(this, "published", /* @__PURE__ */ new Set());
|
|
400
|
+
}
|
|
401
|
+
apply(change, options) {
|
|
402
|
+
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, "document is closed"));
|
|
403
|
+
let opId;
|
|
404
|
+
try {
|
|
405
|
+
opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
406
|
+
} catch (err) {
|
|
407
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
|
|
408
|
+
}
|
|
409
|
+
if (!(0, _embedpdf_engine_core_runtime.isUndoChange)(change) && change.ops.length === 0) return _embedpdf_engine_core_runtime.AbortablePromise.resolveValue({
|
|
410
|
+
items: [],
|
|
411
|
+
meta: {
|
|
412
|
+
affectedPages: [],
|
|
413
|
+
cacheDelta: null,
|
|
414
|
+
opId,
|
|
415
|
+
undoable: false
|
|
416
|
+
}
|
|
417
|
+
});
|
|
324
418
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
type: "attachment.deleted",
|
|
329
|
-
...result
|
|
419
|
+
const answer = await this.writes.change(signal, sentChangeOf(opId, change), {
|
|
420
|
+
named: (0, _embedpdf_engine_core_runtime.objectNumbersNamedBy)(change),
|
|
421
|
+
alone: carriesBytes(change)
|
|
330
422
|
});
|
|
331
|
-
|
|
423
|
+
if (answer.status === "refused") throw (0, _embedpdf_engine_core_runtime.deserializeError)(answer.error);
|
|
424
|
+
this.absorb(opId, change, answer.result);
|
|
425
|
+
return answer.result;
|
|
332
426
|
});
|
|
333
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* Patch the cached manifest, then publish the change's events (in that
|
|
430
|
+
* order: listeners reading the manifest see the change), unless this
|
|
431
|
+
* handle published them already.
|
|
432
|
+
*/
|
|
433
|
+
absorb(opId, change, result) {
|
|
434
|
+
this.manifest.apply(result.meta, planesOf(result));
|
|
435
|
+
for (const item of result.items) if (item.type === "pages.delete") this.manifest.applyPageDelete(result.meta, item.pages);
|
|
436
|
+
else if (item.type === "pages.insert" || item.type === "pages.insertBlank" || item.type === "pages.restore") this.manifest.applyPageInsert(result.meta);
|
|
437
|
+
if (this.published.has(opId)) return;
|
|
438
|
+
this.published.add(opId);
|
|
439
|
+
if (this.published.size > PUBLISHED_MEMORY) this.published.delete(this.published.values().next().value);
|
|
440
|
+
const undoOf = (0, _embedpdf_engine_core_runtime.isUndoChange)(change) ? change.undoOf : void 0;
|
|
441
|
+
this.publisher.publishChange(opId, (0, _embedpdf_engine_core_runtime.changeEvents)(result), undoOf);
|
|
442
|
+
}
|
|
334
443
|
};
|
|
444
|
+
/**
|
|
445
|
+
* The sender of a document's requests of changes: JSON, or multipart when
|
|
446
|
+
* any change carries bytes, each op naming its parts by key.
|
|
447
|
+
*/
|
|
448
|
+
function changeSender(http, docId, layerName) {
|
|
449
|
+
const path = _embedpdf_engine_core_wire.wirePaths.layerChanges(docId, layerName);
|
|
450
|
+
const parse = (raw) => _embedpdf_engine_core_wire.ChangeResponseSchema.parse(raw).changes;
|
|
451
|
+
return (changes, options) => {
|
|
452
|
+
const { body, parts } = changeRequestOf(changes);
|
|
453
|
+
const signal = new AbortController().signal;
|
|
454
|
+
return Object.keys(parts).length > 0 ? http.postMultipartJson(path, buildMutationForm(body, parts), parse, signal, options) : http.postJson(path, body, parse, signal, options);
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* A change ready to send: its bytes read, and checked against what a request
|
|
459
|
+
* may hold, so a malformed change is refused alone rather than refusing the
|
|
460
|
+
* request it would share.
|
|
461
|
+
*/
|
|
462
|
+
async function sentChangeOf(opId, change) {
|
|
463
|
+
if (!(0, _embedpdf_engine_core_runtime.isUndoChange)(change) && change.ops.length > _embedpdf_engine_core_wire.CHANGE_REQUEST_LIMITS.ops) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.PayloadTooLarge, `a change holds at most ${_embedpdf_engine_core_wire.CHANGE_REQUEST_LIMITS.ops} ops`, { details: {
|
|
464
|
+
limit: "ops",
|
|
465
|
+
max: _embedpdf_engine_core_wire.CHANGE_REQUEST_LIMITS.ops,
|
|
466
|
+
value: change.ops.length
|
|
467
|
+
} });
|
|
468
|
+
const sent = {
|
|
469
|
+
opId,
|
|
470
|
+
change: (await (0, _embedpdf_engine_core_runtime.resolveChangeResources)(change)).change
|
|
471
|
+
};
|
|
472
|
+
const { body } = changeRequestOf([sent]);
|
|
473
|
+
const checked = _embedpdf_engine_core_wire.ChangeRequestSchema.safeParse(JSON.parse(JSON.stringify(body)));
|
|
474
|
+
if (!checked.success) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `invalid change: ${checked.error.message}`, { details: { issues: checked.error.issues } });
|
|
475
|
+
return sent;
|
|
476
|
+
}
|
|
477
|
+
/** A request's JSON and its parts: each op's bytes become a part the op names by key. */
|
|
478
|
+
function changeRequestOf(changes) {
|
|
479
|
+
const parts = {};
|
|
480
|
+
const part = (bytes) => {
|
|
481
|
+
const key = `r${Object.keys(parts).length}`;
|
|
482
|
+
parts[key] = { bytes };
|
|
483
|
+
return key;
|
|
484
|
+
};
|
|
485
|
+
const opOf = (op) => {
|
|
486
|
+
switch (op.type) {
|
|
487
|
+
case "annotations.create":
|
|
488
|
+
case "annotations.update": {
|
|
489
|
+
if (!op.resources) return op;
|
|
490
|
+
const { appearance, file } = op.resources;
|
|
491
|
+
return {
|
|
492
|
+
...op,
|
|
493
|
+
resources: {
|
|
494
|
+
...appearance ? { appearance: part(appearance) } : {},
|
|
495
|
+
...file ? { file: part(file) } : {}
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
case "forms.setSignatureAppearance": {
|
|
500
|
+
const { appearance, ...rest } = op;
|
|
501
|
+
return {
|
|
502
|
+
...rest,
|
|
503
|
+
resources: { appearance: part(appearance.pdf.slice().buffer) }
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
case "pages.insert": {
|
|
507
|
+
const { source, ...rest } = op;
|
|
508
|
+
return {
|
|
509
|
+
...rest,
|
|
510
|
+
resources: { source: part(source.slice().buffer) }
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
case "attachments.create": {
|
|
514
|
+
const { file } = op.resources;
|
|
515
|
+
return {
|
|
516
|
+
...op,
|
|
517
|
+
resources: file ? { file: part(file) } : {}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
default: return op;
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
return {
|
|
524
|
+
body: { changes: changes.map(({ opId, change }) => (0, _embedpdf_engine_core_runtime.isUndoChange)(change) ? {
|
|
525
|
+
opId,
|
|
526
|
+
undoOf: change.undoOf
|
|
527
|
+
} : {
|
|
528
|
+
opId,
|
|
529
|
+
ops: change.ops.map(opOf)
|
|
530
|
+
}) },
|
|
531
|
+
parts
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Whether a change carries bytes: a drawing, an attached file, a signature's
|
|
536
|
+
* artwork, a PDF whose pages come in.
|
|
537
|
+
*/
|
|
538
|
+
function carriesBytes(change) {
|
|
539
|
+
if ((0, _embedpdf_engine_core_runtime.isUndoChange)(change)) return false;
|
|
540
|
+
return change.ops.some((op) => op.type === "forms.setSignatureAppearance" || op.type === "pages.insert" || op.type === "attachments.create" || (op.type === "annotations.create" || op.type === "annotations.update") && op.resources !== void 0 && Object.values(op.resources).some((resource) => resource !== void 0));
|
|
541
|
+
}
|
|
542
|
+
/** The planes a change's writes took over from the base, each op by its family. */
|
|
543
|
+
function planesOf(result) {
|
|
544
|
+
const planes = /* @__PURE__ */ new Set();
|
|
545
|
+
for (const item of result.items) {
|
|
546
|
+
if ((0, _embedpdf_engine_core_runtime.isSkippedItem)(item)) continue;
|
|
547
|
+
if (item.type.startsWith("metadata.")) planes.add("metadata");
|
|
548
|
+
else if (item.type.startsWith("outline.")) planes.add("outline");
|
|
549
|
+
else if (item.type.startsWith("pages.")) planes.add("layout");
|
|
550
|
+
else if (item.type.startsWith("forms.")) planes.add("forms");
|
|
551
|
+
else if (item.type.startsWith("attachments.")) planes.add("attachments");
|
|
552
|
+
else planes.add("annotations");
|
|
553
|
+
}
|
|
554
|
+
return [...planes];
|
|
555
|
+
}
|
|
556
|
+
//#endregion
|
|
557
|
+
//#region src/shared/withObjectNumbers.ts
|
|
558
|
+
/**
|
|
559
|
+
* `path` with the object numbers a create names, in its query string: one
|
|
560
|
+
* (`objectNumber=42`) or a comma list (`objectNumbers=42,43`). The body
|
|
561
|
+
* stays the data. Names whose value is absent are left out.
|
|
562
|
+
*/
|
|
563
|
+
function withObjectNumbers(path, numbers) {
|
|
564
|
+
const query = Object.entries(numbers).filter(([, value]) => value !== void 0 && (!Array.isArray(value) || value.length > 0)).map(([name, value]) => `${name}=${Array.isArray(value) ? value.join(",") : String(value)}`);
|
|
565
|
+
if (query.length === 0) return path;
|
|
566
|
+
return `${path}${path.includes("?") ? "&" : "?"}${query.join("&")}`;
|
|
567
|
+
}
|
|
335
568
|
//#endregion
|
|
336
569
|
//#region src/document/CloudDocumentFormsService.ts
|
|
337
|
-
/** Content types the import POST body may carry; the server sniffs the
|
|
338
|
-
* actual format from the bytes, so this is advisory only. */
|
|
339
|
-
const IMPORT_CONTENT_TYPE = {
|
|
340
|
-
fdf: "application/vnd.fdf",
|
|
341
|
-
xfdf: "application/vnd.adobe.xfdf"
|
|
342
|
-
};
|
|
343
570
|
/**
|
|
344
571
|
* Cloud-side document forms service. Mirrors the local wiring: each call
|
|
345
572
|
* produces an `AbortablePromise` that propagates `signal.abort()` down to
|
|
346
573
|
* `fetch` and validates the JSON response with the wire-stable Zod schema.
|
|
347
574
|
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
575
|
+
* The form is its own read family: `list()` reads the immutable `form@`
|
|
576
|
+
* leaf at the manifest's `formsVersion`. Mutation results carry the
|
|
577
|
+
* `cacheDelta` of the form's pins (`formsVersion`, and each changed widget
|
|
578
|
+
* page's `widgetVersion`); `absorbMutation` folds it into the cached
|
|
579
|
+
* manifest and owns the `forms` plane.
|
|
353
580
|
*/
|
|
354
581
|
var CloudDocumentFormsService = class {
|
|
355
|
-
constructor(http, docId, layerName, isClosed, manifest, publisher) {
|
|
582
|
+
constructor(http, docId, layerName, isClosed, manifest, publisher, writes, importLimits) {
|
|
356
583
|
this.http = http;
|
|
357
584
|
this.docId = docId;
|
|
358
585
|
this.layerName = layerName;
|
|
359
586
|
this.isClosed = isClosed;
|
|
360
587
|
this.manifest = manifest;
|
|
361
588
|
this.publisher = publisher;
|
|
589
|
+
this.writes = writes;
|
|
590
|
+
this.importLimits = importLimits;
|
|
362
591
|
}
|
|
592
|
+
/**
|
|
593
|
+
* The form: one read of the immutable `form@formsVersion=N` leaf at the
|
|
594
|
+
* manifest's pin, at the base's URL while the layer inherits the `forms`
|
|
595
|
+
* plane, CDN-cacheable because the pin moves only when the form does. A
|
|
596
|
+
* stale pin (a write landed → 404) refreshes the manifest and reads again,
|
|
597
|
+
* once.
|
|
598
|
+
*/
|
|
363
599
|
list() {
|
|
364
600
|
const rejected = this.rejectIfClosed();
|
|
365
601
|
if (rejected) return rejected;
|
|
366
|
-
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.
|
|
602
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.getJsonWithRefresh(async (s) => {
|
|
603
|
+
const manifest = await this.manifest.get(s);
|
|
604
|
+
return planesInherited(manifest, ["forms"]) ? _embedpdf_engine_core_wire.wirePaths.docForm(this.docId, manifest.formsVersion) : _embedpdf_engine_core_wire.wirePaths.layerForm(this.docId, this.layerName, manifest.formsVersion);
|
|
605
|
+
}, (raw) => _embedpdf_engine_core_wire.FormSnapshotSchema.parse(raw), async (s) => {
|
|
606
|
+
await this.manifest.refresh(s);
|
|
607
|
+
}, signal));
|
|
367
608
|
}
|
|
368
609
|
get(ref) {
|
|
369
610
|
const rejected = this.rejectIfClosed();
|
|
370
611
|
if (rejected) return rejected;
|
|
371
612
|
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.getJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldByKey(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), (raw) => _embedpdf_engine_core_wire.FormFieldDTOSchema.parse(raw), signal));
|
|
372
613
|
}
|
|
373
|
-
setValue(ref, value) {
|
|
614
|
+
setValue(ref, value, options) {
|
|
374
615
|
const rejected = this.rejectIfClosed();
|
|
375
616
|
if (rejected) return rejected;
|
|
376
617
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
377
|
-
const
|
|
378
|
-
return this.
|
|
618
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
619
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
620
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldValue(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), { value }, (raw) => _embedpdf_engine_core_wire.FormSetValueResultSchema.parse(raw), signal, sent));
|
|
621
|
+
return this.absorbMutation(opId, result, "forms.valueSet");
|
|
622
|
+
});
|
|
379
623
|
});
|
|
380
624
|
}
|
|
381
|
-
reset(
|
|
625
|
+
reset(fields, options) {
|
|
382
626
|
const rejected = this.rejectIfClosed();
|
|
383
627
|
if (rejected) return rejected;
|
|
628
|
+
const refs = fields === void 0 ? void 0 : Array.isArray(fields) ? fields : [fields];
|
|
384
629
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
385
|
-
const
|
|
386
|
-
return this.
|
|
630
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
631
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
632
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormReset(this.docId, this.layerName), refs ? { refs } : {}, (raw) => _embedpdf_engine_core_wire.FormResetResultSchema.parse(raw), signal, sent));
|
|
633
|
+
this.manifest.apply(result.meta, ["forms"]);
|
|
634
|
+
this.publisher.publishWrite(opId, ...(0, _embedpdf_engine_core_runtime.formResetFacts)(result).map((fact) => ({
|
|
635
|
+
type: "forms.valueSet",
|
|
636
|
+
...fact
|
|
637
|
+
})));
|
|
638
|
+
return result;
|
|
639
|
+
});
|
|
387
640
|
});
|
|
388
641
|
}
|
|
389
|
-
|
|
642
|
+
/**
|
|
643
|
+
* One versioned, CDN-cacheable read at the manifest's form and layout
|
|
644
|
+
* pins, with the stale-pin retry (404 → refresh the manifest → once). The
|
|
645
|
+
* response is the bundle without its bytes and one part per resource;
|
|
646
|
+
* every part is checked against its id before it is returned.
|
|
647
|
+
*/
|
|
648
|
+
export(selection = {}) {
|
|
390
649
|
const rejected = this.rejectIfClosed();
|
|
391
650
|
if (rejected) return rejected;
|
|
392
651
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
652
|
+
const read = async (s) => {
|
|
653
|
+
const manifest = await this.manifest.get(s);
|
|
654
|
+
const path = this.exportPathAt(manifest, selection);
|
|
655
|
+
if (path) return this.http.getFormData(path, s);
|
|
656
|
+
return this.http.postJsonFormData(_embedpdf_engine_core_wire.wirePaths.layerFormExportRequest(this.docId, this.layerName), {
|
|
657
|
+
formsVersion: manifest.formsVersion,
|
|
658
|
+
layoutVersion: manifest.layoutVersion,
|
|
659
|
+
selection
|
|
660
|
+
}, s);
|
|
661
|
+
};
|
|
662
|
+
let form;
|
|
663
|
+
try {
|
|
664
|
+
form = await read(signal);
|
|
665
|
+
} catch (error) {
|
|
666
|
+
if (!_embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.NotFound)) throw error;
|
|
667
|
+
await this.manifest.refresh(signal);
|
|
668
|
+
form = await read(signal);
|
|
669
|
+
}
|
|
670
|
+
const { body, resources } = await readBundleParts("form", form);
|
|
671
|
+
const bundle = {
|
|
672
|
+
...body,
|
|
673
|
+
resources
|
|
674
|
+
};
|
|
675
|
+
await (0, _embedpdf_engine_core_runtime.assertFormBundle)(bundle, NO_BUNDLE_LIMITS);
|
|
676
|
+
return bundle;
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* One request: the bundle's manifest and each resource once, as parts of
|
|
681
|
+
* one multipart POST under the import's `opId` as `Idempotency-Key`, so a
|
|
682
|
+
* retry applies once. The server holds the limits; the same numbers are
|
|
683
|
+
* checked here first, so a bundle past one fails before its bytes move.
|
|
684
|
+
*/
|
|
685
|
+
import(bundle, options = {}) {
|
|
686
|
+
const rejected = this.rejectIfClosed();
|
|
687
|
+
if (rejected) return rejected;
|
|
688
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
689
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
690
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
691
|
+
const body = {
|
|
692
|
+
bundle: await this.checkedManifest(bundle),
|
|
693
|
+
options: {
|
|
694
|
+
...options.pages !== void 0 ? { pages: options.pages } : {},
|
|
695
|
+
...options.attribution !== void 0 ? { attribution: options.attribution } : {},
|
|
696
|
+
...options.values !== void 0 ? { values: options.values } : {}
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerFormImport(this.docId, this.layerName), bundleImportForm(body, bundle.resources), (raw) => _embedpdf_engine_core_wire.FormImportResultSchema.parse(raw), signal, sent));
|
|
700
|
+
this.manifest.apply(result.meta, ["forms"]);
|
|
701
|
+
this.publisher.publishWrite(opId, ...(0, _embedpdf_engine_core_runtime.formImportFacts)(result).map((fact) => ({
|
|
702
|
+
type: "forms.created",
|
|
703
|
+
...fact
|
|
704
|
+
})));
|
|
705
|
+
return result;
|
|
399
706
|
});
|
|
400
|
-
return result;
|
|
401
707
|
});
|
|
402
708
|
}
|
|
403
|
-
|
|
709
|
+
/** One request, as {@link import} sends it. */
|
|
710
|
+
importValues(bundle, options = {}) {
|
|
404
711
|
const rejected = this.rejectIfClosed();
|
|
405
712
|
if (rejected) return rejected;
|
|
406
713
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
714
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
715
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
716
|
+
const body = {
|
|
717
|
+
bundle: await this.checkedManifest(bundle),
|
|
718
|
+
options: options.attribution !== void 0 ? { attribution: options.attribution } : {}
|
|
719
|
+
};
|
|
720
|
+
const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerFormImportValues(this.docId, this.layerName), bundleImportForm(body, bundle.resources), (raw) => _embedpdf_engine_core_wire.FormValuesImportResultSchema.parse(raw), signal, sent));
|
|
721
|
+
this.manifest.apply(result.meta, ["forms"]);
|
|
722
|
+
this.publisher.publishWrite(opId, ...(0, _embedpdf_engine_core_runtime.formValuesImportFacts)(result).map((fact) => ({
|
|
723
|
+
type: "forms.valueSet",
|
|
724
|
+
...fact
|
|
725
|
+
})));
|
|
726
|
+
return result;
|
|
727
|
+
});
|
|
411
728
|
});
|
|
412
729
|
}
|
|
413
|
-
|
|
730
|
+
create(draft, options = {}) {
|
|
414
731
|
const rejected = this.rejectIfClosed();
|
|
415
732
|
if (rejected) return rejected;
|
|
416
|
-
const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
417
733
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
418
|
-
const
|
|
419
|
-
|
|
734
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
735
|
+
const { objectNumber, widgetObjectNumbers } = options;
|
|
736
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
737
|
+
const result = await write.send((sent) => this.http.postJson(withObjectNumbers(_embedpdf_engine_core_wire.wirePaths.layerFormFields(this.docId, this.layerName), {
|
|
738
|
+
objectNumber,
|
|
739
|
+
widgetObjectNumbers
|
|
740
|
+
}), draft, (raw) => _embedpdf_engine_core_wire.FormFieldCreateResultSchema.parse(raw), signal, sent));
|
|
741
|
+
return this.absorbMutation(opId, result, "forms.created");
|
|
742
|
+
}, [objectNumber, ...widgetObjectNumbers ?? []]);
|
|
420
743
|
});
|
|
421
744
|
}
|
|
422
|
-
|
|
745
|
+
update(ref, patch, options) {
|
|
423
746
|
const rejected = this.rejectIfClosed();
|
|
424
747
|
if (rejected) return rejected;
|
|
425
748
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
426
|
-
const
|
|
427
|
-
return this.
|
|
749
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
750
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
751
|
+
const result = await write.send((sent) => this.http.patchJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldByKey(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), patch, (raw) => _embedpdf_engine_core_wire.FormFieldUpdateResultSchema.parse(raw), signal, sent));
|
|
752
|
+
return this.absorbMutation(opId, result, "forms.updated");
|
|
753
|
+
});
|
|
428
754
|
});
|
|
429
755
|
}
|
|
430
|
-
|
|
756
|
+
setSignatureAppearance(ref, appearance, options) {
|
|
431
757
|
const rejected = this.rejectIfClosed();
|
|
432
758
|
if (rejected) return rejected;
|
|
433
759
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
434
|
-
const
|
|
435
|
-
return this.
|
|
760
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
761
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
762
|
+
const bytes = new ArrayBuffer(appearance.pdf.byteLength);
|
|
763
|
+
new Uint8Array(bytes).set(appearance.pdf);
|
|
764
|
+
const form = buildRoleMutationForm({}, { appearance: {
|
|
765
|
+
bytes,
|
|
766
|
+
mimeType: "application/pdf",
|
|
767
|
+
name: "appearance.pdf"
|
|
768
|
+
} });
|
|
769
|
+
const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldSignatureAppearance(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), form, (raw) => _embedpdf_engine_core_wire.FormFieldUpdateResultSchema.parse(raw), signal, sent));
|
|
770
|
+
return this.absorbMutation(opId, result, "forms.updated");
|
|
771
|
+
});
|
|
436
772
|
});
|
|
437
773
|
}
|
|
438
|
-
|
|
774
|
+
delete(ref, options) {
|
|
439
775
|
const rejected = this.rejectIfClosed();
|
|
440
776
|
if (rejected) return rejected;
|
|
441
777
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
return this.absorbMutation(result, "form.fieldUpdated");
|
|
778
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
779
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
780
|
+
const result = await write.send((sent) => this.http.deleteJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldByKey(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), (raw) => _embedpdf_engine_core_wire.FormFieldDeleteResultSchema.parse(raw), signal, sent));
|
|
781
|
+
this.manifest.apply(result.meta, ["forms"]);
|
|
782
|
+
this.publisher.publishWrite(opId, {
|
|
783
|
+
type: "forms.deleted",
|
|
784
|
+
deleted: (0, _embedpdf_engine_core_runtime.deletedFieldOf)(result),
|
|
785
|
+
...result
|
|
786
|
+
});
|
|
787
|
+
return result;
|
|
788
|
+
});
|
|
454
789
|
});
|
|
455
790
|
}
|
|
456
|
-
|
|
791
|
+
addWidget(ref, placement, options = {}) {
|
|
457
792
|
const rejected = this.rejectIfClosed();
|
|
458
793
|
if (rejected) return rejected;
|
|
459
794
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
460
|
-
const
|
|
461
|
-
|
|
795
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
796
|
+
const { objectNumber, splitObjectNumber } = options;
|
|
797
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
798
|
+
const result = await write.send((sent) => this.http.postJson(withObjectNumbers(_embedpdf_engine_core_wire.wirePaths.layerFormFieldWidgets(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), {
|
|
799
|
+
objectNumber,
|
|
800
|
+
splitObjectNumber
|
|
801
|
+
}), placement, (raw) => _embedpdf_engine_core_wire.FormWidgetLinkResultSchema.parse(raw), signal, sent));
|
|
802
|
+
return this.absorbMutation(opId, result, "forms.widgetAdded");
|
|
803
|
+
}, [objectNumber, splitObjectNumber]);
|
|
462
804
|
});
|
|
463
805
|
}
|
|
464
|
-
|
|
806
|
+
removeWidget(ref, widget, options) {
|
|
465
807
|
const rejected = this.rejectIfClosed();
|
|
466
808
|
if (rejected) return rejected;
|
|
467
|
-
const onState = options?.onState;
|
|
468
809
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
810
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
811
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
812
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldWidgetsDetach(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), { widget }, (raw) => _embedpdf_engine_core_wire.FormWidgetLinkResultSchema.parse(raw), signal, sent));
|
|
813
|
+
return this.absorbMutation(opId, result, "forms.widgetRemoved");
|
|
814
|
+
});
|
|
474
815
|
});
|
|
475
816
|
}
|
|
476
|
-
|
|
817
|
+
updateWidget(widget, patch, options) {
|
|
477
818
|
const rejected = this.rejectIfClosed();
|
|
478
819
|
if (rejected) return rejected;
|
|
479
820
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
480
|
-
const
|
|
481
|
-
return this.
|
|
821
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
822
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
823
|
+
const result = await write.send((sent) => this.http.patchJson(_embedpdf_engine_core_wire.wirePaths.layerFormWidget(this.docId, this.layerName, widget.page, (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(widget)), { patch }, (raw) => _embedpdf_engine_core_wire.FormWidgetUpdateResultSchema.parse(raw), signal, sent));
|
|
824
|
+
return this.absorbMutation(opId, result, "forms.widgetUpdated");
|
|
825
|
+
});
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
deleteWidget(widget, options) {
|
|
829
|
+
const rejected = this.rejectIfClosed();
|
|
830
|
+
if (rejected) return rejected;
|
|
831
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
832
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
833
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
834
|
+
const result = await write.send((sent) => this.http.deleteJson(_embedpdf_engine_core_wire.wirePaths.layerFormWidget(this.docId, this.layerName, widget.page, (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(widget)), (raw) => _embedpdf_engine_core_wire.FormWidgetDeleteResultSchema.parse(raw), signal, sent));
|
|
835
|
+
return this.absorbMutation(opId, result, "forms.widgetDeleted");
|
|
836
|
+
});
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
reorderCalculations(fields, position, options) {
|
|
840
|
+
const rejected = this.rejectIfClosed();
|
|
841
|
+
if (rejected) return rejected;
|
|
842
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
843
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
844
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
845
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormCalculationsReorder(this.docId, this.layerName), {
|
|
846
|
+
fields,
|
|
847
|
+
position
|
|
848
|
+
}, (raw) => _embedpdf_engine_core_wire.FormCalculationsReorderResultSchema.parse(raw), signal, sent));
|
|
849
|
+
return this.absorbMutation(opId, result, "forms.calculationsReordered");
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
reorderWidgets(widgets, position, options) {
|
|
854
|
+
const rejected = this.rejectIfClosed();
|
|
855
|
+
if (rejected) return rejected;
|
|
856
|
+
const page = widgets[0]?.page;
|
|
857
|
+
if (!page) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "a reorder names at least one widget", { details: { field: "widgets" } }));
|
|
858
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
859
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
860
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
861
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormWidgetsReorder(this.docId, this.layerName, page), {
|
|
862
|
+
widgets,
|
|
863
|
+
position
|
|
864
|
+
}, (raw) => _embedpdf_engine_core_wire.FormWidgetsReorderResultSchema.parse(raw), signal, sent));
|
|
865
|
+
return this.absorbMutation(opId, result, "forms.widgetsReordered");
|
|
866
|
+
});
|
|
482
867
|
});
|
|
483
868
|
}
|
|
484
869
|
repair(options) {
|
|
485
870
|
const rejected = this.rejectIfClosed();
|
|
486
871
|
if (rejected) return rejected;
|
|
487
872
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
488
|
-
const
|
|
489
|
-
return this.
|
|
873
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
874
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
875
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormRepair(this.docId, this.layerName), { bakeAppearances: options?.bakeAppearances ?? false }, (raw) => _embedpdf_engine_core_wire.FormRepairResultSchema.parse(raw), signal, sent));
|
|
876
|
+
return this.absorbMutation(opId, result, "forms.repaired");
|
|
877
|
+
});
|
|
490
878
|
});
|
|
491
879
|
}
|
|
880
|
+
/** The bundle without its bytes, checked against the server's limits first. */
|
|
881
|
+
async checkedManifest(bundle) {
|
|
882
|
+
const { resources, ...manifest } = bundle;
|
|
883
|
+
(0, _embedpdf_engine_core_runtime.assertFormBundleManifest)(bundle, new Map(Object.entries(resources).map(([id, bytes]) => [id, bytes.length])), await this.importLimits());
|
|
884
|
+
return manifest;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* The cacheable export URL for `selection` at the manifest's pins — the
|
|
888
|
+
* base leaf while the layer inherits both planes the bundle depends on —
|
|
889
|
+
* or `null` when a URL can't carry it.
|
|
890
|
+
*/
|
|
891
|
+
exportPathAt(manifest, selection) {
|
|
892
|
+
const token = {
|
|
893
|
+
formsVersion: manifest.formsVersion,
|
|
894
|
+
layoutVersion: manifest.layoutVersion,
|
|
895
|
+
selection
|
|
896
|
+
};
|
|
897
|
+
try {
|
|
898
|
+
return planesInherited(manifest, ["forms", "layout"]) ? _embedpdf_engine_core_wire.wirePaths.docFormExport(this.docId, token) : _embedpdf_engine_core_wire.wirePaths.layerFormExport(this.docId, this.layerName, token);
|
|
899
|
+
} catch {
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
492
903
|
rejectIfClosed() {
|
|
493
904
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
494
905
|
return null;
|
|
@@ -498,9 +909,9 @@ var CloudDocumentFormsService = class {
|
|
|
498
909
|
* event stream (in that order — listeners reading the manifest in their
|
|
499
910
|
* callback must see post-mutation state). Same rails as annotations.
|
|
500
911
|
*/
|
|
501
|
-
absorbMutation(result, type) {
|
|
502
|
-
this.manifest.apply(result.meta, ["
|
|
503
|
-
this.publisher.
|
|
912
|
+
absorbMutation(opId, result, type) {
|
|
913
|
+
this.manifest.apply(result.meta, ["forms"]);
|
|
914
|
+
this.publisher.publishWrite(opId, {
|
|
504
915
|
type,
|
|
505
916
|
...result
|
|
506
917
|
});
|
|
@@ -508,36 +919,81 @@ var CloudDocumentFormsService = class {
|
|
|
508
919
|
}
|
|
509
920
|
};
|
|
510
921
|
//#endregion
|
|
511
|
-
//#region src/document/
|
|
512
|
-
/**
|
|
513
|
-
*
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
922
|
+
//#region src/document/CloudDocumentOutlineService.ts
|
|
923
|
+
/**
|
|
924
|
+
* The read behind the cloud `doc.outline`; its verbs are one-op changes
|
|
925
|
+
* through `doc.apply` (`outlineVerbs`). The outline lives at a
|
|
926
|
+
* content-addressed leaf pinned by `outlineVersion` and `layoutVersion` (it
|
|
927
|
+
* is read in page space). The read takes both pins from the cached
|
|
928
|
+
* manifest, and on a 404 (a stale pin) refreshes the manifest and tries
|
|
929
|
+
* once more. While the layer inherits both planes, one doc-level URL serves
|
|
930
|
+
* every visitor.
|
|
931
|
+
*/
|
|
932
|
+
function readCloudOutline(http, docId, layerName, isClosed, manifest) {
|
|
933
|
+
if (isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${docId} is closed`));
|
|
934
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
935
|
+
const buildPath = async (s) => {
|
|
936
|
+
const current = await manifest.get(s);
|
|
937
|
+
const token = {
|
|
938
|
+
outlineVersion: current.outlineVersion,
|
|
939
|
+
layoutVersion: current.layoutVersion
|
|
940
|
+
};
|
|
941
|
+
return planesInherited(current, ["outline", "layout"]) ? _embedpdf_engine_core_wire.wirePaths.docOutline(docId, token) : _embedpdf_engine_core_wire.wirePaths.layerOutline(docId, layerName, token);
|
|
942
|
+
};
|
|
943
|
+
return http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.DocumentOutlineSchema.parse(raw), async (s) => {
|
|
944
|
+
await manifest.refresh(s);
|
|
945
|
+
}, signal);
|
|
946
|
+
});
|
|
518
947
|
}
|
|
948
|
+
//#endregion
|
|
949
|
+
//#region src/shared/awaitSignal.ts
|
|
519
950
|
/**
|
|
520
|
-
*
|
|
521
|
-
*
|
|
951
|
+
* Wait for a shared promise under one caller's signal: an abort rejects
|
|
952
|
+
* this caller only and leaves the shared work running for the others.
|
|
953
|
+
*/
|
|
954
|
+
function awaitSignal(promise, signal) {
|
|
955
|
+
if (signal.aborted) return Promise.reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason));
|
|
956
|
+
return new Promise((resolve, reject) => {
|
|
957
|
+
const onAbort = () => {
|
|
958
|
+
reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason));
|
|
959
|
+
};
|
|
960
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
961
|
+
promise.then((value) => {
|
|
962
|
+
signal.removeEventListener("abort", onAbort);
|
|
963
|
+
resolve(value);
|
|
964
|
+
}, (reason) => {
|
|
965
|
+
signal.removeEventListener("abort", onAbort);
|
|
966
|
+
reject(reason);
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
//#endregion
|
|
971
|
+
//#region src/document/CloudDocumentPagesService.ts
|
|
972
|
+
/**
|
|
973
|
+
* Cloud-side document pages service: the reads, extract, flatten and the page
|
|
974
|
+
* names, over HTTP, as `LocalDocumentPagesService` has them. The structure
|
|
975
|
+
* verbs are changes (`pageVerbs`), so they can be undone.
|
|
522
976
|
*
|
|
523
977
|
* Page identity rule (locked with the user, do not change):
|
|
524
|
-
* - Pages are addressed exclusively by their indirect
|
|
525
|
-
* `pageObjectNumber
|
|
526
|
-
*
|
|
527
|
-
* account for index drift
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
978
|
+
* - Pages are addressed exclusively by `PageRef` (their indirect
|
|
979
|
+
* `pageObjectNumber`); mutation bodies ship `{ pages: PageRef[] }`.
|
|
980
|
+
* The wire never sends a page index for a mutation. This keeps
|
|
981
|
+
* multi-call client logic from having to account for index drift
|
|
982
|
+
* between requests.
|
|
983
|
+
* - A page reorder (a change) returns the new `layout` (order + geometry)
|
|
984
|
+
* plus cloud coherence pins. It bumps only `docVersion` +
|
|
985
|
+
* `layoutVersion`; every per-page pin stays warm.
|
|
532
986
|
*/
|
|
533
987
|
var CloudDocumentPagesService = class {
|
|
534
|
-
constructor(http, docId, layerName, isClosed, manifest, publisher) {
|
|
988
|
+
constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
|
|
535
989
|
this.http = http;
|
|
536
990
|
this.docId = docId;
|
|
537
991
|
this.layerName = layerName;
|
|
538
992
|
this.isClosed = isClosed;
|
|
539
993
|
this.manifest = manifest;
|
|
540
994
|
this.publisher = publisher;
|
|
995
|
+
this.writes = writes;
|
|
996
|
+
_defineProperty(this, "layoutMemo", null);
|
|
541
997
|
}
|
|
542
998
|
/**
|
|
543
999
|
* Page-geometry list. The geometry bytes live at the content-addressed
|
|
@@ -551,142 +1007,104 @@ var CloudDocumentPagesService = class {
|
|
|
551
1007
|
*/
|
|
552
1008
|
list() {
|
|
553
1009
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
554
|
-
return _embedpdf_engine_core_runtime.AbortablePromise.run(
|
|
555
|
-
const buildPath = async (s) => {
|
|
556
|
-
const manifest = await this.manifest.get(s);
|
|
557
|
-
return planesInherited(manifest, ["layout"]) ? _embedpdf_engine_core_wire.wirePaths.docLayout(this.docId, manifest.layoutVersion) : _embedpdf_engine_core_wire.wirePaths.layerLayout(this.docId, this.layerName, manifest.layoutVersion);
|
|
558
|
-
};
|
|
559
|
-
return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.PageListSnapshotSchema.parse(raw), async (s) => {
|
|
560
|
-
await this.manifest.refresh(s);
|
|
561
|
-
}, signal);
|
|
562
|
-
});
|
|
1010
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.fetchLayout(signal));
|
|
563
1011
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
1012
|
+
/**
|
|
1013
|
+
* One page's layout, for renders to size their image without a round
|
|
1014
|
+
* trip per tile. The layout leaf never changes at a given URL, so the
|
|
1015
|
+
* last one read is kept and shared until the manifest points elsewhere.
|
|
1016
|
+
*/
|
|
1017
|
+
async pageLayout(ref, signal) {
|
|
1018
|
+
const path = await this.layoutPath(signal);
|
|
1019
|
+
let memo = this.layoutMemo;
|
|
1020
|
+
if (memo?.path !== path) {
|
|
1021
|
+
const snapshot = this.fetchLayout(new AbortController().signal);
|
|
1022
|
+
const next = {
|
|
1023
|
+
path,
|
|
1024
|
+
snapshot
|
|
1025
|
+
};
|
|
1026
|
+
snapshot.catch(() => {
|
|
1027
|
+
if (this.layoutMemo === next) this.layoutMemo = null;
|
|
577
1028
|
});
|
|
578
|
-
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
return
|
|
583
|
-
}
|
|
584
|
-
removeName(input) {
|
|
585
|
-
return this.runNameMutation(_embedpdf_engine_core_wire.wirePaths.layerPagesNamesDelete(this.docId, this.layerName), input, input.name, null);
|
|
1029
|
+
this.layoutMemo = memo = next;
|
|
1030
|
+
}
|
|
1031
|
+
const page = (await awaitSignal(memo.snapshot, signal)).pages.find((p) => (0, _embedpdf_engine_core_runtime.pageRefsEqual)(p.ref, ref));
|
|
1032
|
+
if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${ref.objectNumber} in document ${this.docId}`);
|
|
1033
|
+
return page;
|
|
586
1034
|
}
|
|
587
1035
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
*
|
|
1036
|
+
* Plane-scope rule: the layout leaf depends on the `layout` plane —
|
|
1037
|
+
* while inherited (no reorder/rotate/insert/delete ever ran), every
|
|
1038
|
+
* visitor's page list is one doc-level URL served from the base
|
|
1039
|
+
* session; the SDK open sequence creates no layer session.
|
|
591
1040
|
*/
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
return
|
|
595
|
-
const result = await this.http.postJson(path, body, (raw) => _embedpdf_engine_core_wire.PageNameResultSchema.parse(raw), signal);
|
|
596
|
-
if (result.cache) this.manifest.applyPageStructure(result.cache);
|
|
597
|
-
this.publisher.publishLocal({
|
|
598
|
-
type: "pages.named",
|
|
599
|
-
name,
|
|
600
|
-
pageObjectNumber,
|
|
601
|
-
...result
|
|
602
|
-
});
|
|
603
|
-
return result;
|
|
604
|
-
});
|
|
1041
|
+
async layoutPath(signal) {
|
|
1042
|
+
const manifest = await this.manifest.get(signal);
|
|
1043
|
+
return planesInherited(manifest, ["layout"]) ? _embedpdf_engine_core_wire.wirePaths.docLayout(this.docId, manifest.layoutVersion) : _embedpdf_engine_core_wire.wirePaths.layerLayout(this.docId, this.layerName, manifest.layoutVersion);
|
|
605
1044
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
pageObjectNumbers,
|
|
611
|
-
rotation
|
|
612
|
-
}, (raw) => _embedpdf_engine_core_wire.PageRotateResultSchema.parse(raw), signal);
|
|
613
|
-
if (result.cache) this.manifest.applyPageStructure(result.cache);
|
|
614
|
-
this.publisher.publishLocal({
|
|
615
|
-
type: "pages.rotated",
|
|
616
|
-
pageObjectNumbers,
|
|
617
|
-
rotation,
|
|
618
|
-
...result
|
|
619
|
-
});
|
|
620
|
-
return result;
|
|
621
|
-
});
|
|
1045
|
+
fetchLayout(signal) {
|
|
1046
|
+
return this.http.getJsonWithRefresh((s) => this.layoutPath(s), (raw) => _embedpdf_engine_core_wire.PageListSnapshotSchema.parse(raw), async (s) => {
|
|
1047
|
+
await this.manifest.refresh(s);
|
|
1048
|
+
}, signal);
|
|
622
1049
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
626
|
-
const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesDelete(this.docId, this.layerName), { pageObjectNumbers }, (raw) => _embedpdf_engine_core_wire.PageDeleteResultSchema.parse(raw), signal);
|
|
627
|
-
if (result.cache) this.manifest.applyPageDelete(result.cache, pageObjectNumbers);
|
|
628
|
-
this.publisher.publishLocal({
|
|
629
|
-
type: "pages.deleted",
|
|
630
|
-
pageObjectNumbers,
|
|
631
|
-
...result
|
|
632
|
-
});
|
|
633
|
-
return result;
|
|
634
|
-
});
|
|
1050
|
+
setName(input, options) {
|
|
1051
|
+
return this.runNameMutation(_embedpdf_engine_core_wire.wirePaths.layerPagesNames(this.docId, this.layerName), input, input.name, input.page, options);
|
|
635
1052
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
639
|
-
const buffer = bytes instanceof ArrayBuffer ? bytes : copyToExactBuffer(bytes);
|
|
640
|
-
const form = buildMutationForm(destIndex !== void 0 ? { destIndex } : {}, { source: {
|
|
641
|
-
bytes: buffer,
|
|
642
|
-
mimeType: "application/pdf",
|
|
643
|
-
name: "source.pdf"
|
|
644
|
-
} });
|
|
645
|
-
const result = await this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerPagesInsert(this.docId, this.layerName), form, (raw) => _embedpdf_engine_core_wire.PageInsertResultSchema.parse(raw), signal);
|
|
646
|
-
if (result.cache) this.manifest.applyPageInsert(result.cache);
|
|
647
|
-
this.publisher.publishLocal({
|
|
648
|
-
type: "pages.inserted",
|
|
649
|
-
destIndex,
|
|
650
|
-
...result
|
|
651
|
-
});
|
|
652
|
-
return result;
|
|
653
|
-
});
|
|
1053
|
+
removeName(input, options) {
|
|
1054
|
+
return this.runNameMutation(_embedpdf_engine_core_wire.wirePaths.layerPagesNamesDelete(this.docId, this.layerName), input, input.name, null, options);
|
|
654
1055
|
}
|
|
655
|
-
|
|
1056
|
+
/**
|
|
1057
|
+
* Named pages are layout: both verbs share the page-reorder patch exactly —
|
|
1058
|
+
* docVersion + layoutVersion advance, no per-page pin changes, so the
|
|
1059
|
+
* cached manifest is patched in place and the fresh layout is published.
|
|
1060
|
+
*/
|
|
1061
|
+
runNameMutation(path, body, name, page, options) {
|
|
656
1062
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
657
1063
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
1064
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1065
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1066
|
+
const result = await write.send((sent) => this.http.postJson(path, body, (raw) => _embedpdf_engine_core_wire.PageNameResultSchema.parse(raw), signal, sent));
|
|
1067
|
+
this.manifest.apply(result.meta, ["layout"]);
|
|
1068
|
+
this.publisher.publishWrite(opId, {
|
|
1069
|
+
type: "pages.named",
|
|
1070
|
+
name,
|
|
1071
|
+
page,
|
|
1072
|
+
...result
|
|
1073
|
+
});
|
|
1074
|
+
return result;
|
|
668
1075
|
});
|
|
669
|
-
return result;
|
|
670
1076
|
});
|
|
671
1077
|
}
|
|
672
|
-
extract(
|
|
1078
|
+
extract(pages, options) {
|
|
673
1079
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
674
|
-
|
|
1080
|
+
const body = {
|
|
1081
|
+
pages,
|
|
1082
|
+
...options
|
|
1083
|
+
};
|
|
1084
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.postJsonBytes(_embedpdf_engine_core_wire.wirePaths.layerPagesExtract(this.docId, this.layerName), body, signal));
|
|
675
1085
|
}
|
|
676
|
-
flatten(
|
|
1086
|
+
flatten(pages, options) {
|
|
1087
|
+
const usage = options?.usage ?? "display";
|
|
677
1088
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
678
1089
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1090
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1091
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1092
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesFlatten(this.docId, this.layerName), {
|
|
1093
|
+
pages,
|
|
1094
|
+
usage
|
|
1095
|
+
}, (raw) => _embedpdf_engine_core_wire.PageFlattenResultSchema.parse(raw), signal, sent));
|
|
1096
|
+
if (result.meta.cacheDelta === null) return result;
|
|
1097
|
+
this.manifest.apply(result.meta, [
|
|
1098
|
+
"content",
|
|
1099
|
+
"annotations",
|
|
1100
|
+
"forms"
|
|
1101
|
+
]);
|
|
1102
|
+
this.publisher.publishWrite(opId, {
|
|
1103
|
+
type: "pages.flattened",
|
|
1104
|
+
...result
|
|
1105
|
+
});
|
|
1106
|
+
return result;
|
|
688
1107
|
});
|
|
689
|
-
return result;
|
|
690
1108
|
});
|
|
691
1109
|
}
|
|
692
1110
|
};
|
|
@@ -697,29 +1115,37 @@ var CloudDocumentPagesService = class {
|
|
|
697
1115
|
* HTTP (POST /redactions/apply); the server enforces the capability gate
|
|
698
1116
|
* (`doc.pages.modify` + `doc.annotate.modify` + `doc.redact`) and persists
|
|
699
1117
|
* the rewritten layer artifact. See `DocumentRedactionService` for the
|
|
700
|
-
* two-stage model and the layer trust boundary — an apply rewrites
|
|
701
|
-
*
|
|
1118
|
+
* two-stage model and the layer trust boundary — an apply rewrites this
|
|
1119
|
+
* layer's bytes; the immutable base keeps the original.
|
|
702
1120
|
*/
|
|
703
1121
|
var CloudDocumentRedactionService = class {
|
|
704
|
-
constructor(http, docId, layerName, isClosed, manifest, publisher) {
|
|
1122
|
+
constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
|
|
705
1123
|
this.http = http;
|
|
706
1124
|
this.docId = docId;
|
|
707
1125
|
this.layerName = layerName;
|
|
708
1126
|
this.isClosed = isClosed;
|
|
709
1127
|
this.manifest = manifest;
|
|
710
1128
|
this.publisher = publisher;
|
|
1129
|
+
this.writes = writes;
|
|
711
1130
|
}
|
|
712
|
-
apply(scope) {
|
|
1131
|
+
apply(scope, options) {
|
|
713
1132
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
714
1133
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1134
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1135
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1136
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerRedactionsApply(this.docId, this.layerName), scope, (raw) => _embedpdf_engine_core_wire.RedactionApplyResultSchema.parse(raw), signal, sent));
|
|
1137
|
+
if (result.meta.cacheDelta === null) return result;
|
|
1138
|
+
this.manifest.apply(result.meta, [
|
|
1139
|
+
"content",
|
|
1140
|
+
"annotations",
|
|
1141
|
+
"forms"
|
|
1142
|
+
]);
|
|
1143
|
+
this.publisher.publishWrite(opId, {
|
|
1144
|
+
type: "redaction.applied",
|
|
1145
|
+
...result
|
|
1146
|
+
});
|
|
1147
|
+
return result;
|
|
721
1148
|
});
|
|
722
|
-
return result;
|
|
723
1149
|
});
|
|
724
1150
|
}
|
|
725
1151
|
};
|
|
@@ -730,14 +1156,14 @@ var CloudDocumentRedactionService = class {
|
|
|
730
1156
|
*
|
|
731
1157
|
* Every slice URL is a canonical cache key — the search token pins the
|
|
732
1158
|
* layer's content epoch (computed from the cached manifest), the
|
|
733
|
-
*
|
|
1159
|
+
* canonical query (default-fold literals collapse case/diacritic
|
|
734
1160
|
* variants into one entry), and the resume position. Identical searches
|
|
735
1161
|
* from any reader of the document hit the same CDN entry; matches are
|
|
736
1162
|
* all that ever crosses the trust boundary, and the rects/full endpoint
|
|
737
1163
|
* split keeps permission tiers in disjoint cache namespaces.
|
|
738
1164
|
*
|
|
739
1165
|
* Staleness rides the standard versioned-read signal: a first slice
|
|
740
|
-
* answered `NotFound` means
|
|
1166
|
+
* answered `NotFound` means our manifest is stale → refresh and retry
|
|
741
1167
|
* once; a continuation answered `NotFound` means the document changed
|
|
742
1168
|
* mid-search → `InvalidArg`, same contract as the local engine's cursor.
|
|
743
1169
|
*/
|
|
@@ -752,9 +1178,10 @@ var CloudDocumentSearchService = class {
|
|
|
752
1178
|
query(request) {
|
|
753
1179
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document not open: ${this.docId}`));
|
|
754
1180
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
755
|
-
const path =
|
|
1181
|
+
const path = request.snippets ? _embedpdf_engine_core_wire.wirePaths.layerSearchFull : _embedpdf_engine_core_wire.wirePaths.layerSearchRects;
|
|
756
1182
|
const get = (token) => this.http.getJson(path(this.docId, this.layerName, token), (raw) => _embedpdf_engine_core_wire.SearchSliceSchema.parse(raw), signal);
|
|
757
|
-
const query = (0, _embedpdf_engine_core_runtime.canonicalSearchQuery)(request
|
|
1183
|
+
const query = (0, _embedpdf_engine_core_runtime.canonicalSearchQuery)(request);
|
|
1184
|
+
const from = request.from?.objectNumber;
|
|
758
1185
|
if (request.cursor !== void 0) {
|
|
759
1186
|
let token;
|
|
760
1187
|
try {
|
|
@@ -763,7 +1190,7 @@ var CloudDocumentSearchService = class {
|
|
|
763
1190
|
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "malformed search cursor");
|
|
764
1191
|
}
|
|
765
1192
|
if (queryIdentity(token.query) !== queryIdentity(query)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "search cursor belongs to a different query — restart the search");
|
|
766
|
-
if (
|
|
1193
|
+
if (from !== void 0 && from !== token.from) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "`from` conflicts with the cursor — omit it when resuming");
|
|
767
1194
|
try {
|
|
768
1195
|
return await get(request.cursor);
|
|
769
1196
|
} catch (err) {
|
|
@@ -774,9 +1201,9 @@ var CloudDocumentSearchService = class {
|
|
|
774
1201
|
const mint = (epoch) => (0, _embedpdf_engine_core_wire.encodeSearchToken)({
|
|
775
1202
|
epoch,
|
|
776
1203
|
query,
|
|
777
|
-
...
|
|
1204
|
+
...from !== void 0 ? { from } : {},
|
|
778
1205
|
skip: 0,
|
|
779
|
-
...request.
|
|
1206
|
+
...request.limit !== void 0 ? { limit: request.limit } : {}
|
|
780
1207
|
});
|
|
781
1208
|
try {
|
|
782
1209
|
return await get(mint((0, _embedpdf_engine_core_runtime.searchContentEpoch)(await this.manifest.get(signal))));
|
|
@@ -842,22 +1269,50 @@ function base64UrlDecode(b64url) {
|
|
|
842
1269
|
//#endregion
|
|
843
1270
|
//#region src/document/CloudDocumentSecurityService.ts
|
|
844
1271
|
var CloudDocumentSecurityService = class {
|
|
845
|
-
constructor(http, docId, layerName, initialHead, view, initialToken = null) {
|
|
1272
|
+
constructor(http, docId, layerName, initialHead, view, initialToken = null, editing = null) {
|
|
846
1273
|
this.http = http;
|
|
847
1274
|
this.docId = docId;
|
|
848
1275
|
this.layerName = layerName;
|
|
849
1276
|
this.view = view;
|
|
850
|
-
|
|
1277
|
+
this.editing = editing;
|
|
1278
|
+
_defineProperty(this, "securityState", void 0);
|
|
851
1279
|
_defineProperty(this, "access", null);
|
|
852
1280
|
_defineProperty(this, "tokenScope", void 0);
|
|
853
1281
|
_defineProperty(this, "tokenIdentity", void 0);
|
|
854
|
-
this
|
|
1282
|
+
_defineProperty(this, "tokenHasDocScope", void 0);
|
|
1283
|
+
_defineProperty(this, "protection", null);
|
|
1284
|
+
_defineProperty(this, "passwordRejected", false);
|
|
1285
|
+
this.securityState = (0, _embedpdf_engine_core_runtime.securityStateFromHead)(initialHead);
|
|
855
1286
|
const claims = initialToken ? safeDecodeClaims(initialToken) : null;
|
|
856
1287
|
this.tokenScope = Array.isArray(claims?.scope) ? claims.scope : [];
|
|
857
1288
|
this.tokenIdentity = claims ? identityFromClaims(claims) : null;
|
|
1289
|
+
this.tokenHasDocScope = typeof claims?.doc_id === "string" && Array.isArray(claims?.scope);
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* What the caller may read of a page picture, when the client can know it:
|
|
1293
|
+
* once `/access` answered, or from a document token's scope. Null without
|
|
1294
|
+
* either (no token, a tenant or API token): the server decides.
|
|
1295
|
+
*/
|
|
1296
|
+
pageLayerRights() {
|
|
1297
|
+
if (!this.access && !this.tokenHasDocScope) return null;
|
|
1298
|
+
return {
|
|
1299
|
+
annotations: this.allows("doc.annotate.read"),
|
|
1300
|
+
formFields: this.allows("doc.forms.read")
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* What the caller may print, when the client can know it (as for
|
|
1305
|
+
* {@link pageLayerRights}). Null leaves it to the server.
|
|
1306
|
+
*/
|
|
1307
|
+
pagePrintRights() {
|
|
1308
|
+
if (!this.access && !this.tokenHasDocScope) return null;
|
|
1309
|
+
return {
|
|
1310
|
+
print: this.allows("doc.print"),
|
|
1311
|
+
printHigh: this.allows("doc.print.high")
|
|
1312
|
+
};
|
|
858
1313
|
}
|
|
859
|
-
get
|
|
860
|
-
return this.
|
|
1314
|
+
get state() {
|
|
1315
|
+
return this.securityState;
|
|
861
1316
|
}
|
|
862
1317
|
get currentAccess() {
|
|
863
1318
|
return this.access;
|
|
@@ -865,52 +1320,60 @@ var CloudDocumentSecurityService = class {
|
|
|
865
1320
|
/**
|
|
866
1321
|
* Expanded capability set. Cloud-canonical post-/access; otherwise
|
|
867
1322
|
* computed locally from the JWT scope + /head's pdf bits using the
|
|
868
|
-
*
|
|
1323
|
+
* same `expandRawScope` helper engine-local calls — so the value
|
|
869
1324
|
* matches across engines bit-for-bit on the same inputs.
|
|
870
1325
|
*/
|
|
871
|
-
get
|
|
1326
|
+
get scope() {
|
|
872
1327
|
if (this.access) return this.access.effectiveScope;
|
|
873
|
-
|
|
874
|
-
return Array.from((0, _embedpdf_engine_core_runtime.expandRawScope)(this.tokenScope, bits)).sort();
|
|
1328
|
+
return Array.from((0, _embedpdf_engine_core_runtime.expandRawScope)(this.tokenScope, this.pdfBits(), this.protection)).sort();
|
|
875
1329
|
}
|
|
876
1330
|
/**
|
|
877
|
-
*
|
|
878
|
-
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
1331
|
+
* The server route guards' own predicate over the same inputs: what the
|
|
1332
|
+
* document's signatures forbid first, then the scope (the server's copy
|
|
1333
|
+
* once `/access` answered, else the JWT's), wildcard included, against the
|
|
1334
|
+
* PDF bits. `scope` alone can't gate UI: it enumerates concrete grants and
|
|
1335
|
+
* drops the `*` admin wildcard.
|
|
882
1336
|
*/
|
|
883
1337
|
allows(cap) {
|
|
884
|
-
|
|
885
|
-
const bits = (0, _embedpdf_engine_core_runtime.decodePdfBits)(this.state.permissions.bits);
|
|
886
|
-
return (0, _embedpdf_engine_core_runtime.checkCapability)(cap, this.tokenScope, bits);
|
|
1338
|
+
return (0, _embedpdf_engine_core_runtime.checkCapability)(cap, this.rawScope(), this.pdfBits(), this.protection);
|
|
887
1339
|
}
|
|
888
1340
|
/**
|
|
889
|
-
*
|
|
890
|
-
*
|
|
891
|
-
* enforces with, over the same inputs: the raw scope
|
|
892
|
-
* (server-canonical post-/access, else the JWT's own claim), the
|
|
893
|
-
* caller's identity, and /head's PDF bits. A control gated on these
|
|
894
|
-
* matches the server's allow/deny for the same mutation. False
|
|
895
|
-
* before any token/access context exists — fail closed, same rule
|
|
896
|
-
* as `allows`.
|
|
1341
|
+
* Cloud-internal: what the document's signatures forbid, from the latest
|
|
1342
|
+
* manifest (it changes only when a signature publishes a new version).
|
|
897
1343
|
*/
|
|
898
|
-
|
|
1344
|
+
setProtection(protection) {
|
|
1345
|
+
this.protection = protection;
|
|
1346
|
+
}
|
|
1347
|
+
allowsAnnotation(action, target) {
|
|
899
1348
|
const id = this.identity ?? {};
|
|
900
|
-
|
|
1349
|
+
const annotationsProtected = (0, _embedpdf_engine_core_runtime.protectedCapabilities)(this.protection).has("doc.annotate.modify");
|
|
1350
|
+
switch (action) {
|
|
1351
|
+
case "create":
|
|
1352
|
+
if (annotationsProtected) return false;
|
|
1353
|
+
return (0, _embedpdf_engine_core_runtime.checkCollab)("create", selfTarget(id), this.rawScope(), id, this.pdfBits());
|
|
1354
|
+
case "set-group": return (0, _embedpdf_engine_core_runtime.checkSetGroup)("annotations", target.groupId, id.groupId, this.rawScope());
|
|
1355
|
+
default: {
|
|
1356
|
+
const annotation = target ?? {};
|
|
1357
|
+
if (annotation.subtype === "widget") return this.allows("doc.forms.modify");
|
|
1358
|
+
if (annotationsProtected) return false;
|
|
1359
|
+
return (0, _embedpdf_engine_core_runtime.checkCollab)(action, (0, _embedpdf_engine_core_runtime.collabTargetOf)(annotation), this.rawScope(), id, this.pdfBits());
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
901
1362
|
}
|
|
902
|
-
|
|
903
|
-
return (0, _embedpdf_engine_core_runtime.
|
|
1363
|
+
allowsField(action, target) {
|
|
1364
|
+
if (action === "set-group") return (0, _embedpdf_engine_core_runtime.checkSetGroup)("fields", target.groupId ?? "", this.identity?.groupId, this.rawScope());
|
|
1365
|
+
return (0, _embedpdf_engine_core_runtime.checkFieldAction)(action, target.groupId, this.rawScope(), this.pdfBits(), this.protection);
|
|
904
1366
|
}
|
|
905
|
-
|
|
906
|
-
|
|
1367
|
+
/** The same inputs as `allowsField`, as lists. */
|
|
1368
|
+
fieldRights() {
|
|
1369
|
+
return (0, _embedpdf_engine_core_runtime.fieldRightsOf)(this.rawScope(), this.pdfBits(), this.protection, this.identity?.groupId);
|
|
907
1370
|
}
|
|
908
1371
|
/** Raw scope for the collab resolver: server-canonical post-/access, else the JWT claim. */
|
|
909
1372
|
rawScope() {
|
|
910
1373
|
return this.access?.scope ?? this.tokenScope;
|
|
911
1374
|
}
|
|
912
1375
|
pdfBits() {
|
|
913
|
-
return (0, _embedpdf_engine_core_runtime.decodePdfBits)(this.
|
|
1376
|
+
return (0, _embedpdf_engine_core_runtime.decodePdfBits)(this.securityState.permissions.bits);
|
|
914
1377
|
}
|
|
915
1378
|
/**
|
|
916
1379
|
* Identity of the current caller, or null when anonymous.
|
|
@@ -930,21 +1393,28 @@ var CloudDocumentSecurityService = class {
|
|
|
930
1393
|
* security state. See `passwordPromptFromState` for the rules.
|
|
931
1394
|
*/
|
|
932
1395
|
get passwordPrompt() {
|
|
933
|
-
return (0, _embedpdf_engine_core_runtime.passwordPromptFromState)(this.
|
|
1396
|
+
return (0, _embedpdf_engine_core_runtime.passwordPromptFromState)(this.securityState, this.passwordRejected);
|
|
934
1397
|
}
|
|
935
1398
|
unlock(input) {
|
|
936
1399
|
if (this.view.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
937
1400
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1401
|
+
try {
|
|
1402
|
+
const result = await this.postAccess(signal, {
|
|
1403
|
+
password: input.password,
|
|
1404
|
+
mode: input.mode ?? "any"
|
|
1405
|
+
});
|
|
1406
|
+
this.passwordRejected = false;
|
|
1407
|
+
return result;
|
|
1408
|
+
} catch (error) {
|
|
1409
|
+
if (_embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.DocPasswordIncorrect)) this.passwordRejected = true;
|
|
1410
|
+
throw error;
|
|
1411
|
+
}
|
|
942
1412
|
});
|
|
943
1413
|
}
|
|
944
1414
|
/**
|
|
945
1415
|
* Cloud-internal: call /v1/access with no password to establish
|
|
946
1416
|
* a CDN-credentialed session. Used by `CloudEngine.open` when
|
|
947
|
-
* /head's `access.reasons` includes 'cdn' but
|
|
1417
|
+
* /head's `access.reasons` includes 'cdn' but not 'password' —
|
|
948
1418
|
* the server accepts an authenticated /access POST without a
|
|
949
1419
|
* password and returns the signed-URL block.
|
|
950
1420
|
*
|
|
@@ -963,7 +1433,9 @@ var CloudDocumentSecurityService = class {
|
|
|
963
1433
|
* password) and `establishAccess()` (no password). Updates cached
|
|
964
1434
|
* security state, caches the access block, and pushes the CDN
|
|
965
1435
|
* binding into the HttpClient so subsequent fetches apply CDN
|
|
966
|
-
* tokens via `applyCdnAccess`.
|
|
1436
|
+
* tokens via `applyCdnAccess`. It goes as the editing session, and a
|
|
1437
|
+
* caller that may create gets the session's object numbers with it
|
|
1438
|
+
* (`edit`).
|
|
967
1439
|
*
|
|
968
1440
|
* `none` CDN adapter → the access block has null overrides/policies
|
|
969
1441
|
* and `applyCdnAccess` short-circuits to origin; safe to call always.
|
|
@@ -971,27 +1443,33 @@ var CloudDocumentSecurityService = class {
|
|
|
971
1443
|
async postAccess(signal, body) {
|
|
972
1444
|
const response = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.access(this.docId, this.layerName), {
|
|
973
1445
|
...body.password ? { password: body.password } : {},
|
|
974
|
-
mode: body.mode
|
|
975
|
-
|
|
976
|
-
|
|
1446
|
+
mode: body.mode,
|
|
1447
|
+
...this.editing ? { objectNumbers: this.editing.wanted() } : {}
|
|
1448
|
+
}, (raw) => _embedpdf_engine_core_wire.AccessResponseSchema.parse(raw), signal, { session: true });
|
|
1449
|
+
this.securityState = response.security;
|
|
1450
|
+
this.protection = response.protection;
|
|
977
1451
|
this.access = {
|
|
978
1452
|
cdn: response.cdn,
|
|
979
1453
|
passwordGrant: response.passwordGrant,
|
|
980
1454
|
pdfPermissions: response.pdfPermissions,
|
|
981
1455
|
scope: response.scope,
|
|
982
1456
|
effectiveScope: response.effectiveScope,
|
|
1457
|
+
protection: response.protection,
|
|
983
1458
|
identity: response.identity,
|
|
984
1459
|
originPasswordPolicy: response.originPasswordPolicy,
|
|
985
1460
|
expiresAt: response.expiresAt,
|
|
986
|
-
...response.renderPolicy ? { renderPolicy: response.renderPolicy } : {}
|
|
1461
|
+
...response.renderPolicy ? { renderPolicy: response.renderPolicy } : {},
|
|
1462
|
+
bundleLimits: response.bundleLimits,
|
|
1463
|
+
...response.edit ? { edit: response.edit } : {}
|
|
987
1464
|
};
|
|
1465
|
+
if (response.edit) this.editing?.opened(response.edit);
|
|
988
1466
|
this.http.setCdnAccess({
|
|
989
1467
|
cdn: response.cdn,
|
|
990
1468
|
docId: this.docId,
|
|
991
1469
|
layerName: this.layerName
|
|
992
1470
|
});
|
|
993
1471
|
return {
|
|
994
|
-
security: this.
|
|
1472
|
+
security: this.securityState,
|
|
995
1473
|
access: this.access ?? void 0
|
|
996
1474
|
};
|
|
997
1475
|
}
|
|
@@ -1011,25 +1489,39 @@ function safeDecodeClaims(token) {
|
|
|
1011
1489
|
}
|
|
1012
1490
|
}
|
|
1013
1491
|
/**
|
|
1014
|
-
*
|
|
1492
|
+
* Create's CollabTarget is the caller's own identity — the same
|
|
1015
1493
|
* derivation as engine-local's `ScopeGuard.targetForSelfCreate`, so
|
|
1016
1494
|
* `:self` trivially passes and `:group=X` matches the caller's
|
|
1017
1495
|
* default group.
|
|
1018
1496
|
*/
|
|
1019
1497
|
function selfTarget(id) {
|
|
1020
1498
|
return {
|
|
1021
|
-
...id.
|
|
1022
|
-
...id.
|
|
1499
|
+
...id.userId !== void 0 ? { userId: id.userId } : {},
|
|
1500
|
+
...id.groupId !== void 0 ? { groupId: id.groupId } : {}
|
|
1023
1501
|
};
|
|
1024
1502
|
}
|
|
1503
|
+
const IDENTITY_STRING_FIELDS = [
|
|
1504
|
+
"userId",
|
|
1505
|
+
"displayName",
|
|
1506
|
+
"email",
|
|
1507
|
+
"title",
|
|
1508
|
+
"organization",
|
|
1509
|
+
"organizationalUnit",
|
|
1510
|
+
"groupId"
|
|
1511
|
+
];
|
|
1512
|
+
/**
|
|
1513
|
+
* The token's `identity` claim, read the way the server reads it: string
|
|
1514
|
+
* fields, empty values absent. The server rejects a
|
|
1515
|
+
* malformed claim; unverified here, anything else is skipped.
|
|
1516
|
+
*/
|
|
1025
1517
|
function identityFromClaims(claims) {
|
|
1518
|
+
const claim = claims["identity"];
|
|
1519
|
+
if (!claim || typeof claim !== "object" || Array.isArray(claim)) return null;
|
|
1520
|
+
const record = claim;
|
|
1026
1521
|
const out = {};
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
if (Array.isArray(claims["groups"])) {
|
|
1031
|
-
const groups = claims["groups"].filter((g) => typeof g === "string");
|
|
1032
|
-
if (groups.length > 0) out.groups = groups;
|
|
1522
|
+
for (const key of IDENTITY_STRING_FIELDS) {
|
|
1523
|
+
const value = record[key];
|
|
1524
|
+
if (typeof value === "string" && value.length > 0) out[key] = value;
|
|
1033
1525
|
}
|
|
1034
1526
|
return Object.keys(out).length > 0 ? out : null;
|
|
1035
1527
|
}
|
|
@@ -1038,11 +1530,11 @@ function identityFromClaims(claims) {
|
|
|
1038
1530
|
/**
|
|
1039
1531
|
* Cloud-side digital signatures. Two kinds of read, two kinds of URL:
|
|
1040
1532
|
*
|
|
1041
|
-
* - The
|
|
1533
|
+
* - The layer view (`list`, and `analyze` of the working copy) is pinned
|
|
1042
1534
|
* by the manifest's `docVersion`, like every other layer read: a
|
|
1043
1535
|
* signature is a layer state change, the immutable URL moves with it.
|
|
1044
|
-
* - Signed
|
|
1045
|
-
* of history) belong to a base
|
|
1536
|
+
* - Signed bytes (`contents`, `digest`, `revisionBytes`, and `analyze`
|
|
1537
|
+
* of history) belong to a base version and are content-addressed by
|
|
1046
1538
|
* the manifest's `baseSha`: immutable forever, shared by every layer
|
|
1047
1539
|
* and every caller.
|
|
1048
1540
|
*
|
|
@@ -1052,13 +1544,14 @@ function identityFromClaims(claims) {
|
|
|
1052
1544
|
* refresh it before returning.
|
|
1053
1545
|
*/
|
|
1054
1546
|
var CloudDocumentSignaturesService = class {
|
|
1055
|
-
constructor(http, docId, layerName, isClosed, manifest, publisher) {
|
|
1547
|
+
constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
|
|
1056
1548
|
this.http = http;
|
|
1057
1549
|
this.docId = docId;
|
|
1058
1550
|
this.layerName = layerName;
|
|
1059
1551
|
this.isClosed = isClosed;
|
|
1060
1552
|
this.manifest = manifest;
|
|
1061
1553
|
this.publisher = publisher;
|
|
1554
|
+
this.writes = writes;
|
|
1062
1555
|
}
|
|
1063
1556
|
list() {
|
|
1064
1557
|
const rejected = this.closedRejection();
|
|
@@ -1070,7 +1563,7 @@ var CloudDocumentSignaturesService = class {
|
|
|
1070
1563
|
await this.manifest.refresh(s);
|
|
1071
1564
|
}, signal));
|
|
1072
1565
|
}
|
|
1073
|
-
|
|
1566
|
+
getContents(field) {
|
|
1074
1567
|
const rejected = this.closedRejection();
|
|
1075
1568
|
if (rejected) return rejected;
|
|
1076
1569
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
@@ -1079,7 +1572,7 @@ var CloudDocumentSignaturesService = class {
|
|
|
1079
1572
|
return this.http.getBytes(_embedpdf_engine_core_wire.wirePaths.docVersionSignatureContents(this.docId, manifest.baseSha, name), signal);
|
|
1080
1573
|
});
|
|
1081
1574
|
}
|
|
1082
|
-
|
|
1575
|
+
getDigest(field, algorithm) {
|
|
1083
1576
|
const rejected = this.closedRejection();
|
|
1084
1577
|
if (rejected) return rejected;
|
|
1085
1578
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
@@ -1088,7 +1581,7 @@ var CloudDocumentSignaturesService = class {
|
|
|
1088
1581
|
return this.http.getBytes(_embedpdf_engine_core_wire.wirePaths.docVersionSignatureDigest(this.docId, manifest.baseSha, name, algorithm), signal);
|
|
1089
1582
|
});
|
|
1090
1583
|
}
|
|
1091
|
-
|
|
1584
|
+
downloadRevision(revisionIndex) {
|
|
1092
1585
|
const rejected = this.closedRejection();
|
|
1093
1586
|
if (rejected) return rejected;
|
|
1094
1587
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
@@ -1097,16 +1590,17 @@ var CloudDocumentSignaturesService = class {
|
|
|
1097
1590
|
});
|
|
1098
1591
|
}
|
|
1099
1592
|
/**
|
|
1100
|
-
* `until: 'working-copy'`
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1593
|
+
* `until: 'working-copy'` judges the layer's pending edits against the
|
|
1594
|
+
* base at the layer URL; anything else, the default `'persisted'`
|
|
1595
|
+
* included, is history of the base version and resolves at the version
|
|
1596
|
+
* URL (the saved file, as the local engine judges by default).
|
|
1103
1597
|
*/
|
|
1104
1598
|
analyze(input) {
|
|
1105
1599
|
const rejected = this.closedRejection();
|
|
1106
1600
|
if (rejected) return rejected;
|
|
1107
1601
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1108
1602
|
const manifest = await this.manifest.get(signal);
|
|
1109
|
-
if (input.until === "working-copy"
|
|
1603
|
+
if (input.until === "working-copy") return this.http.getJsonWithRefresh(async (s) => {
|
|
1110
1604
|
const current = await this.manifest.get(s);
|
|
1111
1605
|
return _embedpdf_engine_core_wire.wirePaths.layerSignaturesAnalysis(this.docId, this.layerName, {
|
|
1112
1606
|
docVersion: current.docVersion,
|
|
@@ -1125,65 +1619,65 @@ var CloudDocumentSignaturesService = class {
|
|
|
1125
1619
|
}), (raw) => _embedpdf_engine_core_wire.ChangeAnalysisSchema.parse(raw), signal);
|
|
1126
1620
|
});
|
|
1127
1621
|
}
|
|
1128
|
-
prepare(input) {
|
|
1622
|
+
prepare(input, options) {
|
|
1129
1623
|
const rejected = this.closedRejection();
|
|
1130
1624
|
if (rejected) return rejected;
|
|
1131
1625
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
...rest
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
signingId: prepared.signingId,
|
|
1149
|
-
field: input.field
|
|
1626
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1627
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1628
|
+
const { appearance, ...rest } = input;
|
|
1629
|
+
const form = buildRoleMutationForm(rest, { appearance: appearance && {
|
|
1630
|
+
bytes: appearance.pdf.slice().buffer,
|
|
1631
|
+
mimeType: "application/pdf",
|
|
1632
|
+
name: "appearance.pdf"
|
|
1633
|
+
} });
|
|
1634
|
+
const prepared = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerSignaturesPrepare(this.docId, this.layerName), form, (raw) => (0, _embedpdf_engine_core_wire.decodePrepared)(_embedpdf_engine_core_wire.SignaturePreparedWireSchema.parse(raw)), signal, sent));
|
|
1635
|
+
await this.manifest.refresh(signal);
|
|
1636
|
+
this.publisher.publishWrite(opId, {
|
|
1637
|
+
type: "signatures.prepared",
|
|
1638
|
+
field: input.field,
|
|
1639
|
+
...prepared
|
|
1640
|
+
});
|
|
1641
|
+
return prepared;
|
|
1150
1642
|
});
|
|
1151
|
-
return prepared;
|
|
1152
1643
|
});
|
|
1153
1644
|
}
|
|
1154
|
-
complete(input) {
|
|
1645
|
+
complete(input, options) {
|
|
1155
1646
|
const rejected = this.closedRejection();
|
|
1156
1647
|
if (rejected) return rejected;
|
|
1157
1648
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
this.
|
|
1165
|
-
|
|
1649
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1650
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1651
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerSignatureComplete(this.docId, this.layerName, input.signingId), {
|
|
1652
|
+
cms: (0, _embedpdf_engine_core_wire.toBase64)(input.cms),
|
|
1653
|
+
expectedVersion: input.expectedVersion
|
|
1654
|
+
}, (raw) => _embedpdf_engine_core_wire.SignatureCompleteResultSchema.parse(raw), signal, sent));
|
|
1655
|
+
await this.manifest.refresh(signal);
|
|
1656
|
+
if (result.status === "completed") this.publisher.publishWrite(opId, {
|
|
1657
|
+
type: "signatures.completed",
|
|
1166
1658
|
signingId: input.signingId,
|
|
1167
1659
|
...result
|
|
1168
|
-
}
|
|
1169
|
-
this.publisher.publishLocal({
|
|
1660
|
+
}, {
|
|
1170
1661
|
type: "document.versioned",
|
|
1171
1662
|
version: result.version
|
|
1172
1663
|
});
|
|
1173
|
-
|
|
1174
|
-
|
|
1664
|
+
return result;
|
|
1665
|
+
});
|
|
1175
1666
|
});
|
|
1176
1667
|
}
|
|
1177
|
-
|
|
1668
|
+
cancel(signingId, options) {
|
|
1178
1669
|
const rejected = this.closedRejection();
|
|
1179
1670
|
if (rejected) return rejected;
|
|
1180
1671
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1672
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1673
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1674
|
+
const result = await write.send((sent) => this.http.deleteJson(_embedpdf_engine_core_wire.wirePaths.layerSignatureCancel(this.docId, this.layerName, signingId), (raw) => _embedpdf_engine_core_wire.SignatureCancelResultSchema.parse(raw), signal, sent));
|
|
1675
|
+
if (result.status === "cancelled") this.publisher.publishWrite(opId, {
|
|
1676
|
+
type: "signatures.cancelled",
|
|
1677
|
+
signingId
|
|
1678
|
+
});
|
|
1679
|
+
return result;
|
|
1185
1680
|
});
|
|
1186
|
-
return result;
|
|
1187
1681
|
});
|
|
1188
1682
|
}
|
|
1189
1683
|
/** Version routes address a field by its fully qualified name; an object-number ref is resolved through the snapshot. */
|
|
@@ -1194,8 +1688,8 @@ var CloudDocumentSignaturesService = class {
|
|
|
1194
1688
|
return _embedpdf_engine_core_wire.wirePaths.layerSignatures(this.docId, this.layerName, manifest.docVersion);
|
|
1195
1689
|
}, (raw) => _embedpdf_engine_core_wire.SignatureSnapshotSchema.parse(raw), async (s) => {
|
|
1196
1690
|
await this.manifest.refresh(s);
|
|
1197
|
-
}, signal)).signatures.find((s) => s.field.kind === "objectNumber" && s.field.
|
|
1198
|
-
if (!match) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no signature field #${field.
|
|
1691
|
+
}, signal)).signatures.find((s) => s.field.kind === "objectNumber" && s.field.objectNumber === field.objectNumber);
|
|
1692
|
+
if (!match) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no signature field #${field.objectNumber}`);
|
|
1199
1693
|
return match.fieldName;
|
|
1200
1694
|
}
|
|
1201
1695
|
closedRejection() {
|
|
@@ -1205,50 +1699,330 @@ var CloudDocumentSignaturesService = class {
|
|
|
1205
1699
|
};
|
|
1206
1700
|
//#endregion
|
|
1207
1701
|
//#region src/document/CloudMetadataService.ts
|
|
1702
|
+
const STANDARD = {
|
|
1703
|
+
docPath: _embedpdf_engine_core_wire.wirePaths.docMetadata,
|
|
1704
|
+
layerPath: _embedpdf_engine_core_wire.wirePaths.layerMetadata,
|
|
1705
|
+
updatePath: _embedpdf_engine_core_wire.wirePaths.layerMetadataUpdate,
|
|
1706
|
+
parse: (raw) => _embedpdf_engine_core_wire.DocumentMetadataSchema.parse(raw),
|
|
1707
|
+
parseResult: (raw) => _embedpdf_engine_core_wire.MetadataUpdateResultSchema.parse(raw)
|
|
1708
|
+
};
|
|
1709
|
+
const CUSTOM = {
|
|
1710
|
+
docPath: _embedpdf_engine_core_wire.wirePaths.docCustomMetadata,
|
|
1711
|
+
layerPath: _embedpdf_engine_core_wire.wirePaths.layerCustomMetadata,
|
|
1712
|
+
updatePath: _embedpdf_engine_core_wire.wirePaths.layerCustomMetadataUpdate,
|
|
1713
|
+
parse: (raw) => _embedpdf_engine_core_wire.CustomMetadataSchema.parse(raw),
|
|
1714
|
+
parseResult: (raw) => _embedpdf_engine_core_wire.CustomMetadataUpdateResultSchema.parse(raw)
|
|
1715
|
+
};
|
|
1716
|
+
function closedError(docId) {
|
|
1717
|
+
return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${docId} is closed`);
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* The read. The JSON lives at a content-addressed `…@metadataVersion=N` leaf
|
|
1721
|
+
* (not in the manifest); the manifest only publishes the `metadataVersion`
|
|
1722
|
+
* pointer, which both halves of the dict share. So the read pulls it from
|
|
1723
|
+
* the cached manifest, fetches the leaf, and on a 404 (stale pointer)
|
|
1724
|
+
* transparently refreshes the manifest and retries once. `metadataVersion`
|
|
1725
|
+
* bumps only on metadata writes, so the leaf stays cached across page and
|
|
1726
|
+
* annotation edits.
|
|
1727
|
+
*/
|
|
1728
|
+
function readLeaf(deps, leaf) {
|
|
1729
|
+
if (deps.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(closedError(deps.docId));
|
|
1730
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1731
|
+
const buildPath = async (s) => {
|
|
1732
|
+
const manifest = await deps.manifest.get(s);
|
|
1733
|
+
return planesInherited(manifest, ["metadata"]) ? leaf.docPath(deps.docId, manifest.metadataVersion) : leaf.layerPath(deps.docId, deps.layerName, manifest.metadataVersion);
|
|
1734
|
+
};
|
|
1735
|
+
return deps.http.getJsonWithRefresh(buildPath, leaf.parse, async (s) => {
|
|
1736
|
+
await deps.manifest.refresh(s);
|
|
1737
|
+
}, signal);
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* The write. It only advances docVersion + metadataVersion (no per-page pin
|
|
1742
|
+
* changes, no layoutVersion), so the cached manifest is patched in place —
|
|
1743
|
+
* no refetch. `publish` gets the write's `opId`.
|
|
1744
|
+
*/
|
|
1745
|
+
function writeLeaf(deps, leaf, patch, options, publish) {
|
|
1746
|
+
if (deps.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(closedError(deps.docId));
|
|
1747
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1748
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1749
|
+
return deps.writes.run(opId, signal, async (write) => {
|
|
1750
|
+
const result = await write.send((sent) => deps.http.postJson(leaf.updatePath(deps.docId, deps.layerName), patch, leaf.parseResult, signal, sent));
|
|
1751
|
+
deps.manifest.apply(result.meta, ["metadata"]);
|
|
1752
|
+
publish(result, opId);
|
|
1753
|
+
return result;
|
|
1754
|
+
});
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
var CloudCustomMetadataService = class {
|
|
1758
|
+
constructor(deps) {
|
|
1759
|
+
this.deps = deps;
|
|
1760
|
+
}
|
|
1761
|
+
get() {
|
|
1762
|
+
return readLeaf(this.deps, CUSTOM);
|
|
1763
|
+
}
|
|
1764
|
+
update(patch, options) {
|
|
1765
|
+
return writeLeaf(this.deps, CUSTOM, patch, options, (result, opId) => this.deps.publisher.publishWrite(opId, {
|
|
1766
|
+
type: "metadata.customUpdated",
|
|
1767
|
+
...result
|
|
1768
|
+
}));
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1208
1771
|
var CloudMetadataService = class {
|
|
1209
|
-
constructor(http, docId, layerName, isClosed, manifest, publisher) {
|
|
1210
|
-
this
|
|
1211
|
-
this
|
|
1212
|
-
this.
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1772
|
+
constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
|
|
1773
|
+
_defineProperty(this, "custom", void 0);
|
|
1774
|
+
_defineProperty(this, "deps", void 0);
|
|
1775
|
+
this.deps = {
|
|
1776
|
+
http,
|
|
1777
|
+
docId,
|
|
1778
|
+
layerName,
|
|
1779
|
+
isClosed,
|
|
1780
|
+
manifest,
|
|
1781
|
+
publisher,
|
|
1782
|
+
writes
|
|
1783
|
+
};
|
|
1784
|
+
this.custom = new CloudCustomMetadataService(this.deps);
|
|
1216
1785
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
* content-addressed `/metadata@metadataVersion=N` leaf (not in the
|
|
1220
|
-
* manifest); the manifest only publishes the `metadataVersion` pointer.
|
|
1221
|
-
* So `read()` pulls `metadataVersion` from the cached manifest, fetches
|
|
1222
|
-
* the leaf, and on a 404 (stale pointer) transparently refreshes the
|
|
1223
|
-
* manifest and retries once. `metadataVersion` bumps only on metadata
|
|
1224
|
-
* writes, so this leaf stays cached across page and annotation edits.
|
|
1225
|
-
*/
|
|
1226
|
-
read() {
|
|
1227
|
-
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
1228
|
-
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1229
|
-
const buildPath = async (s) => {
|
|
1230
|
-
const manifest = await this.manifest.get(s);
|
|
1231
|
-
return planesInherited(manifest, ["metadata"]) ? _embedpdf_engine_core_wire.wirePaths.docMetadata(this.docId, manifest.metadataVersion) : _embedpdf_engine_core_wire.wirePaths.layerMetadata(this.docId, this.layerName, manifest.metadataVersion);
|
|
1232
|
-
};
|
|
1233
|
-
return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.DocumentMetadataSchema.parse(raw), async (s) => {
|
|
1234
|
-
await this.manifest.refresh(s);
|
|
1235
|
-
}, signal);
|
|
1236
|
-
});
|
|
1786
|
+
get() {
|
|
1787
|
+
return readLeaf(this.deps, STANDARD);
|
|
1237
1788
|
}
|
|
1238
|
-
update(patch) {
|
|
1239
|
-
|
|
1789
|
+
update(patch, options) {
|
|
1790
|
+
return writeLeaf(this.deps, STANDARD, patch, options, (result, opId) => this.deps.publisher.publishWrite(opId, {
|
|
1791
|
+
type: "metadata.updated",
|
|
1792
|
+
...result
|
|
1793
|
+
}));
|
|
1794
|
+
}
|
|
1795
|
+
};
|
|
1796
|
+
//#endregion
|
|
1797
|
+
//#region src/document/CloudObjectNumberPool.ts
|
|
1798
|
+
/** Below this many held numbers, a write asks for a top-up… */
|
|
1799
|
+
const LOW_WATER = 8;
|
|
1800
|
+
/** …of up to this many, the most the server tops up by. */
|
|
1801
|
+
const TOP_UP = 32;
|
|
1802
|
+
/** The most one bulk reservation asks for. */
|
|
1803
|
+
const MAX_RESERVATION = 1e3;
|
|
1804
|
+
/** An editing session's lifetime (ms) until the server states one: the protocol's 15 minutes. */
|
|
1805
|
+
const DEFAULT_LIFETIME_MS = 15 * 6e4;
|
|
1806
|
+
/**
|
|
1807
|
+
* The cloud engine's object numbers: what the server handed this editing
|
|
1808
|
+
* session (the engine's `X-Engine-Session-Id` with the token's subject).
|
|
1809
|
+
*
|
|
1810
|
+
* Numbers arrive with `/access`, with a write's response (the top-up a
|
|
1811
|
+
* write asks for when fewer than 8 are held), with the event stream's
|
|
1812
|
+
* `session` events, and from `reserve`'s bulk reservation. A number
|
|
1813
|
+
* `take` hands out is the caller's until a write naming it commits.
|
|
1814
|
+
*
|
|
1815
|
+
* The session lives while the event stream is connected or something
|
|
1816
|
+
* keeps it alive; without that it expires, and its numbers may go to
|
|
1817
|
+
* another session. So once its expiry has passed while the stream is down,
|
|
1818
|
+
* the pool hands out nothing until the server says again what it holds.
|
|
1819
|
+
* The stream's `session` event lists everything the session holds: any
|
|
1820
|
+
* number this pool held or handed out that isn't on it, and that no write
|
|
1821
|
+
* in flight names, is lost (`onLost`, `'reclaimed'`). A new version of the
|
|
1822
|
+
* document numbers objects anew: everything is lost (`'versioned'`).
|
|
1823
|
+
*/
|
|
1824
|
+
var CloudObjectNumberPool = class {
|
|
1825
|
+
constructor(opts) {
|
|
1826
|
+
this.opts = opts;
|
|
1827
|
+
_defineProperty(this, "now", void 0);
|
|
1828
|
+
_defineProperty(this, "available", []);
|
|
1829
|
+
_defineProperty(this, "taken", /* @__PURE__ */ new Set());
|
|
1830
|
+
_defineProperty(this, "writing", /* @__PURE__ */ new Map());
|
|
1831
|
+
_defineProperty(this, "expiresAt", 0);
|
|
1832
|
+
_defineProperty(this, "halfLifetimeMs", DEFAULT_LIFETIME_MS / 2);
|
|
1833
|
+
_defineProperty(this, "streamConnected", false);
|
|
1834
|
+
_defineProperty(this, "listeners", /* @__PURE__ */ new Set());
|
|
1835
|
+
_defineProperty(this, "reserving", Promise.resolve());
|
|
1836
|
+
_defineProperty(this, "version", null);
|
|
1837
|
+
this.now = opts.now ?? Date.now;
|
|
1838
|
+
}
|
|
1839
|
+
get held() {
|
|
1840
|
+
return this.usable() ? this.available.length : 0;
|
|
1841
|
+
}
|
|
1842
|
+
take() {
|
|
1843
|
+
if (!this.usable()) return null;
|
|
1844
|
+
const objectNumber = this.available.shift();
|
|
1845
|
+
if (objectNumber === void 0) return null;
|
|
1846
|
+
this.taken.add(objectNumber);
|
|
1847
|
+
return objectNumber;
|
|
1848
|
+
}
|
|
1849
|
+
reserve(count) {
|
|
1240
1850
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1851
|
+
const turn = this.reserving.then(async () => {
|
|
1852
|
+
while (this.held < count) {
|
|
1853
|
+
const wanted = Math.min(count - this.held, MAX_RESERVATION);
|
|
1854
|
+
const reserved = await this.opts.reserve(wanted, signal);
|
|
1855
|
+
this.receive(reserved.objectNumbers, reserved.expiresIn);
|
|
1856
|
+
}
|
|
1246
1857
|
});
|
|
1247
|
-
|
|
1858
|
+
this.reserving = turn.catch(() => void 0);
|
|
1859
|
+
await turn;
|
|
1248
1860
|
});
|
|
1249
1861
|
}
|
|
1862
|
+
onLost(listener) {
|
|
1863
|
+
this.listeners.add(listener);
|
|
1864
|
+
return () => {
|
|
1865
|
+
this.listeners.delete(listener);
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
/** How many numbers a write should ask the server for: 0 while enough are held. */
|
|
1869
|
+
topUp() {
|
|
1870
|
+
const held = this.held;
|
|
1871
|
+
return held < LOW_WATER ? TOP_UP - held : 0;
|
|
1872
|
+
}
|
|
1873
|
+
/** How many numbers `/access` should ask for: enough to make up the first block. */
|
|
1874
|
+
wantedAtAccess() {
|
|
1875
|
+
return Math.max(0, LOW_WATER - this.held);
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* `/access` answered for the editing session. A session the server made
|
|
1879
|
+
* anew holds nothing of what this pool had.
|
|
1880
|
+
*/
|
|
1881
|
+
opened(edit) {
|
|
1882
|
+
if (edit.session === "new") this.lose(this.unspent(), "reclaimed");
|
|
1883
|
+
this.receive(edit.objectNumbers, edit.expiresIn);
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Numbers handed to the session: by `/access`, a write's top-up or the
|
|
1887
|
+
* bulk reservation; `expiresIn` (seconds) when the answer told it. Numbers
|
|
1888
|
+
* held from before an expiry the server can't vouch for any more are
|
|
1889
|
+
* dropped: only a `session` event says which survived.
|
|
1890
|
+
*/
|
|
1891
|
+
receive(ranges, expiresIn) {
|
|
1892
|
+
if (expiresIn !== void 0) {
|
|
1893
|
+
if (!this.usable()) this.lose(this.available.splice(0), "reclaimed");
|
|
1894
|
+
this.expiresIn(expiresIn);
|
|
1895
|
+
}
|
|
1896
|
+
this.add((0, _embedpdf_engine_core_runtime.objectNumbersIn)(ranges));
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* The event stream's `session` event: everything the session holds now.
|
|
1900
|
+
* What this pool held or handed out and the list doesn't name, unless a
|
|
1901
|
+
* write in flight names it, is lost.
|
|
1902
|
+
*/
|
|
1903
|
+
sync(status) {
|
|
1904
|
+
this.expiresIn(status.expiresIn);
|
|
1905
|
+
const held = new Set((0, _embedpdf_engine_core_runtime.objectNumbersIn)(status.held));
|
|
1906
|
+
const lost = this.unspent().filter((n) => !held.has(n));
|
|
1907
|
+
for (const n of lost) this.taken.delete(n);
|
|
1908
|
+
this.available = [...held].filter((n) => !this.taken.has(n) && !this.writing.has(n)).sort((a, b) => a - b);
|
|
1909
|
+
this.emit(lost, "reclaimed");
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Version `sha256` was published: it numbers objects anew, so every number
|
|
1913
|
+
* held or handed out is gone. Told once per version (the stream and this
|
|
1914
|
+
* engine's own completion both tell it, in either order).
|
|
1915
|
+
*/
|
|
1916
|
+
versioned(sha256) {
|
|
1917
|
+
if (sha256 === this.version) return;
|
|
1918
|
+
this.version = sha256;
|
|
1919
|
+
this.lose(this.unspent(), "versioned");
|
|
1920
|
+
}
|
|
1921
|
+
/** The event stream connected or dropped: while it's up, the server keeps the session alive. */
|
|
1922
|
+
connected(connected) {
|
|
1923
|
+
this.streamConnected = connected;
|
|
1924
|
+
}
|
|
1925
|
+
/** A write that names `numbers` was called. */
|
|
1926
|
+
beginWrite(numbers) {
|
|
1927
|
+
for (const n of numbers) this.writing.set(n, (this.writing.get(n) ?? 0) + 1);
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* The write that named `numbers` settled. A committed write also kept the
|
|
1931
|
+
* session alive: at least half its lifetime is left.
|
|
1932
|
+
*/
|
|
1933
|
+
endWrite(numbers, outcome) {
|
|
1934
|
+
for (const n of numbers) {
|
|
1935
|
+
const left = (this.writing.get(n) ?? 1) - 1;
|
|
1936
|
+
if (left > 0) this.writing.set(n, left);
|
|
1937
|
+
else this.writing.delete(n);
|
|
1938
|
+
if (outcome !== "failed") this.taken.delete(n);
|
|
1939
|
+
}
|
|
1940
|
+
if (outcome === "committed") this.expiresAt = Math.max(this.expiresAt, this.now() + this.halfLifetimeMs);
|
|
1941
|
+
}
|
|
1942
|
+
usable() {
|
|
1943
|
+
return this.streamConnected || this.now() < this.expiresAt;
|
|
1944
|
+
}
|
|
1945
|
+
expiresIn(seconds) {
|
|
1946
|
+
const ms = seconds * 1e3;
|
|
1947
|
+
this.expiresAt = this.now() + ms;
|
|
1948
|
+
this.halfLifetimeMs = Math.max(this.halfLifetimeMs, ms / 2);
|
|
1949
|
+
}
|
|
1950
|
+
add(numbers) {
|
|
1951
|
+
const known = /* @__PURE__ */ new Set([...this.available, ...this.taken]);
|
|
1952
|
+
const fresh = numbers.filter((n) => !known.has(n) && !this.writing.has(n));
|
|
1953
|
+
if (fresh.length === 0) return;
|
|
1954
|
+
this.available = [...this.available, ...fresh].sort((a, b) => a - b);
|
|
1955
|
+
}
|
|
1956
|
+
/** Held or handed out, and named by no write in flight. */
|
|
1957
|
+
unspent() {
|
|
1958
|
+
return [...this.available, ...this.taken].filter((n) => !this.writing.has(n));
|
|
1959
|
+
}
|
|
1960
|
+
lose(numbers, reason) {
|
|
1961
|
+
const gone = new Set(numbers);
|
|
1962
|
+
this.available = this.available.filter((n) => !gone.has(n));
|
|
1963
|
+
for (const n of gone) this.taken.delete(n);
|
|
1964
|
+
this.emit([...gone], reason);
|
|
1965
|
+
}
|
|
1966
|
+
emit(numbers, reason) {
|
|
1967
|
+
if (numbers.length === 0) return;
|
|
1968
|
+
const lost = {
|
|
1969
|
+
numbers: [...numbers].sort((a, b) => a - b),
|
|
1970
|
+
reason
|
|
1971
|
+
};
|
|
1972
|
+
for (const listener of [...this.listeners]) listener(lost);
|
|
1973
|
+
}
|
|
1250
1974
|
};
|
|
1251
1975
|
//#endregion
|
|
1976
|
+
//#region src/document/appearanceForm.ts
|
|
1977
|
+
/**
|
|
1978
|
+
* Parse the appearance `multipart/form-data` response into the same
|
|
1979
|
+
* `AnnotationAppearanceImagesResult` shape the local engine produces. The
|
|
1980
|
+
* `body` part is validated against the wire schema; each image (the part
|
|
1981
|
+
* `resource:<key>` its entry names) is wrapped in a `PageImageHandle`
|
|
1982
|
+
* backed by the in-memory blob we already downloaded.
|
|
1983
|
+
*/
|
|
1984
|
+
async function parseAppearanceForm(form) {
|
|
1985
|
+
const raw = form.get("body");
|
|
1986
|
+
if (typeof raw !== "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, "appearance response missing its body part");
|
|
1987
|
+
let parsedJson;
|
|
1988
|
+
try {
|
|
1989
|
+
parsedJson = JSON.parse(raw);
|
|
1990
|
+
} catch (err) {
|
|
1991
|
+
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `appearance body is not valid JSON: ${err?.message ?? err}`);
|
|
1992
|
+
}
|
|
1993
|
+
const body = _embedpdf_engine_core_wire.AnnotationAppearanceBatchSchema.parse(parsedJson);
|
|
1994
|
+
const appearances = await Promise.all(body.appearances.map(async (entry) => {
|
|
1995
|
+
const name = `resource:${entry.resources.image}`;
|
|
1996
|
+
const partValue = form.get(name);
|
|
1997
|
+
if (partValue === null || typeof partValue === "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `appearance response missing image part "${name}"`);
|
|
1998
|
+
const blob = partValue;
|
|
1999
|
+
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
2000
|
+
const image = (0, _embedpdf_engine_core_runtime.createPageImageHandle)({
|
|
2001
|
+
width: entry.width,
|
|
2002
|
+
height: entry.height,
|
|
2003
|
+
format: entry.format,
|
|
2004
|
+
contentType: entry.contentType,
|
|
2005
|
+
source: {
|
|
2006
|
+
kind: "bytes",
|
|
2007
|
+
bytes
|
|
2008
|
+
}
|
|
2009
|
+
}, { async blob() {
|
|
2010
|
+
return blob;
|
|
2011
|
+
} });
|
|
2012
|
+
return {
|
|
2013
|
+
ref: entry.ref,
|
|
2014
|
+
mode: entry.mode,
|
|
2015
|
+
state: entry.state,
|
|
2016
|
+
rect: entry.rect,
|
|
2017
|
+
image
|
|
2018
|
+
};
|
|
2019
|
+
}));
|
|
2020
|
+
return {
|
|
2021
|
+
page: body.page,
|
|
2022
|
+
appearances
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
//#endregion
|
|
1252
2026
|
//#region src/document/CloudPageAnnotationsService.ts
|
|
1253
2027
|
/**
|
|
1254
2028
|
* Cloud-side page annotation service. Mirrors the local wiring: each
|
|
@@ -1260,45 +2034,46 @@ var CloudMetadataService = class {
|
|
|
1260
2034
|
* manifest. Mutations use unversioned layer URLs and are never cached.
|
|
1261
2035
|
*/
|
|
1262
2036
|
var CloudPageAnnotationsService = class {
|
|
1263
|
-
constructor(http, docId, layerName,
|
|
2037
|
+
constructor(http, docId, layerName, pageRef, isClosed, manifest, publisher, writes) {
|
|
1264
2038
|
this.http = http;
|
|
1265
2039
|
this.docId = docId;
|
|
1266
2040
|
this.layerName = layerName;
|
|
1267
|
-
this.
|
|
2041
|
+
this.pageRef = pageRef;
|
|
1268
2042
|
this.isClosed = isClosed;
|
|
1269
2043
|
this.manifest = manifest;
|
|
1270
2044
|
this.publisher = publisher;
|
|
2045
|
+
this.writes = writes;
|
|
1271
2046
|
}
|
|
1272
2047
|
list() {
|
|
1273
2048
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
1274
2049
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1275
2050
|
const buildPath = async (s) => {
|
|
1276
2051
|
const manifest = await this.manifest.get(s);
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
|
|
2052
|
+
const pageObjectNumber = this.pageRef.objectNumber;
|
|
2053
|
+
const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
|
|
2054
|
+
if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
|
|
2055
|
+
return planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageAnnotations(this.docId, this.pageRef, page.cache.annotationVersion) : _embedpdf_engine_core_wire.wirePaths.layerPageAnnotations(this.docId, this.layerName, this.pageRef, page.cache.annotationVersion);
|
|
1280
2056
|
};
|
|
1281
|
-
return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.
|
|
2057
|
+
return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.AnnotationListSchema.parse(raw), async (s) => {
|
|
1282
2058
|
await this.manifest.refresh(s);
|
|
1283
2059
|
}, signal);
|
|
1284
2060
|
});
|
|
1285
2061
|
}
|
|
1286
|
-
renderAppearances(
|
|
1287
|
-
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "annotations.renderAppearances() raw rasters are not available in the cloud engine; use renderAppearanceImages()"));
|
|
1288
|
-
}
|
|
1289
|
-
renderAppearanceImages(options = {}) {
|
|
2062
|
+
renderAppearances(options = {}) {
|
|
1290
2063
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
1291
2064
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
2065
|
+
(0, _embedpdf_engine_core_runtime.checkImageQuality)(options.quality);
|
|
1292
2066
|
const format = options.format ?? "webp";
|
|
1293
2067
|
const buildPath = async (s) => {
|
|
1294
2068
|
const manifest = await this.manifest.get(s);
|
|
1295
|
-
const
|
|
1296
|
-
|
|
2069
|
+
const pageObjectNumber = this.pageRef.objectNumber;
|
|
2070
|
+
const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
|
|
2071
|
+
if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
|
|
1297
2072
|
const wireToken = (0, _embedpdf_engine_core_wire.annotationAppearancesImageOptionsToWire)({
|
|
1298
2073
|
...options,
|
|
1299
2074
|
format
|
|
1300
2075
|
}, { annotationVersion: page.cache.annotationVersion });
|
|
1301
|
-
return planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageAnnotationAppearances(this.docId, this.
|
|
2076
|
+
return planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageAnnotationAppearances(this.docId, this.pageRef, wireToken) : _embedpdf_engine_core_wire.wirePaths.layerPageAnnotationAppearances(this.docId, this.layerName, this.pageRef, wireToken);
|
|
1302
2077
|
};
|
|
1303
2078
|
return parseAppearanceForm(await this.http.getFormDataWithRefresh(buildPath, async (s) => {
|
|
1304
2079
|
await this.manifest.refresh(s);
|
|
@@ -1306,121 +2081,140 @@ var CloudPageAnnotationsService = class {
|
|
|
1306
2081
|
});
|
|
1307
2082
|
}
|
|
1308
2083
|
/**
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1311
|
-
*
|
|
1312
|
-
*
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1315
|
-
* id — the same `:annotKey` routing update()/delete() use.
|
|
2084
|
+
* One of the annotation's resources. `file` is a read over the immutable
|
|
2085
|
+
* `attachment-files` leaf, pinned by the manifest's `attachmentsVersion`
|
|
2086
|
+
* (annotation-level files re-key on the same pin as document-level ones),
|
|
2087
|
+
* with the same stale-404 refresh retry as {@link list}. `appearance` is a
|
|
2088
|
+
* derived read (no-store), like {@link exportAppearance}. Both address the
|
|
2089
|
+
* annotation by the same `:annotKey` update()/delete() use.
|
|
1316
2090
|
*/
|
|
1317
|
-
|
|
2091
|
+
downloadResource(ref, role) {
|
|
1318
2092
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
1319
|
-
if (ref.
|
|
1320
|
-
|
|
1321
|
-
|
|
2093
|
+
if (ref.page.objectNumber !== this.pageRef.objectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `ref.page ${ref.page.objectNumber} != page ${this.pageRef.objectNumber}`));
|
|
2094
|
+
const annotKey = (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(ref);
|
|
2095
|
+
if (role === "appearance") return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.getBytes(_embedpdf_engine_core_wire.wirePaths.layerAnnotationAppearanceResource(this.docId, this.layerName, this.pageRef, annotKey), signal));
|
|
1322
2096
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1323
2097
|
const buildPath = async (s) => {
|
|
1324
2098
|
const manifest = await this.manifest.get(s);
|
|
1325
|
-
return planesInherited(manifest, ["annotations", "attachments"]) ? _embedpdf_engine_core_wire.wirePaths.docAnnotationFile(this.docId, this.
|
|
2099
|
+
return planesInherited(manifest, ["annotations", "attachments"]) ? _embedpdf_engine_core_wire.wirePaths.docAnnotationFile(this.docId, this.pageRef, annotKey, manifest.attachmentsVersion) : _embedpdf_engine_core_wire.wirePaths.layerAnnotationFile(this.docId, this.layerName, this.pageRef, annotKey, manifest.attachmentsVersion);
|
|
1326
2100
|
};
|
|
1327
|
-
return
|
|
2101
|
+
return (await this.http.getFileWithRefresh(buildPath, async (s) => {
|
|
1328
2102
|
await this.manifest.refresh(s);
|
|
1329
|
-
}, signal));
|
|
2103
|
+
}, signal)).bytes;
|
|
1330
2104
|
});
|
|
1331
2105
|
}
|
|
1332
|
-
create(draft) {
|
|
2106
|
+
create(draft, options = {}) {
|
|
1333
2107
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
2108
|
+
let opId;
|
|
2109
|
+
try {
|
|
2110
|
+
opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
2111
|
+
} catch (err) {
|
|
2112
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
|
|
2113
|
+
}
|
|
2114
|
+
const { resources, objectNumber } = options;
|
|
2115
|
+
const data = (0, _embedpdf_engine_core_runtime.withFileFromResource)(draft, resources);
|
|
2116
|
+
const path = withObjectNumbers(_embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsCreate(this.docId, this.layerName, this.pageRef), { objectNumber });
|
|
2117
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
|
|
2118
|
+
const wireResources = await (0, _embedpdf_engine_core_runtime.resolveAnnotationResources)(resources);
|
|
1337
2119
|
const parse = (raw) => _embedpdf_engine_core_wire.AnnotationCreateResultSchema.parse(raw);
|
|
1338
|
-
const result =
|
|
1339
|
-
return this.absorbMutation(result, "
|
|
1340
|
-
});
|
|
2120
|
+
const result = await write.send((sent) => (0, _embedpdf_engine_core_runtime.hasAnnotationResources)(wireResources) ? this.http.postMultipartJson(path, buildAnnotationMutationForm(data, wireResources), parse, signal, sent) : this.http.postJson(path, data, parse, signal, sent));
|
|
2121
|
+
return this.absorbMutation(opId, result, "annotations.created");
|
|
2122
|
+
}, [objectNumber]));
|
|
1341
2123
|
}
|
|
1342
|
-
update(ref, patch) {
|
|
2124
|
+
update(ref, patch, options = {}) {
|
|
1343
2125
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
const result = await this.patchMutation(path, {
|
|
1350
|
-
ref,
|
|
1351
|
-
patch: wire
|
|
1352
|
-
}, resources, signal);
|
|
1353
|
-
return this.absorbMutation(result, "annotation.updated");
|
|
1354
|
-
});
|
|
2126
|
+
let opId;
|
|
2127
|
+
try {
|
|
2128
|
+
opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
2129
|
+
} catch (err) {
|
|
2130
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
|
|
1355
2131
|
}
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
const
|
|
1361
|
-
|
|
1362
|
-
|
|
2132
|
+
const { resources } = options;
|
|
2133
|
+
if (ref.page.objectNumber !== this.pageRef.objectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `ref.page ${ref.page.objectNumber} != page ${this.pageRef.objectNumber}`));
|
|
2134
|
+
const path = _embedpdf_engine_core_wire.wirePaths.layerAnnotationByKey(this.docId, this.layerName, this.pageRef, (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(ref));
|
|
2135
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
|
|
2136
|
+
const wireResources = await (0, _embedpdf_engine_core_runtime.resolveAnnotationResources)(resources);
|
|
2137
|
+
const result = await this.patchMutation(write, path, { patch }, wireResources, signal);
|
|
2138
|
+
return this.absorbMutation(opId, result, "annotations.updated");
|
|
2139
|
+
}));
|
|
1363
2140
|
}
|
|
1364
|
-
/** PATCH as plain JSON, or as multipart when the patch
|
|
1365
|
-
patchMutation(path, body, resources, signal) {
|
|
2141
|
+
/** PATCH as plain JSON, or as multipart when resources came with the patch. */
|
|
2142
|
+
patchMutation(write, path, body, resources, signal) {
|
|
1366
2143
|
const parse = (raw) => _embedpdf_engine_core_wire.AnnotationUpdateResultSchema.parse(raw);
|
|
1367
|
-
|
|
1368
|
-
return this.http.patchJson(path, body, parse, signal);
|
|
2144
|
+
return write.send((sent) => (0, _embedpdf_engine_core_runtime.hasAnnotationResources)(resources) ? this.http.patchMultipartJson(path, buildAnnotationMutationForm(body, resources), parse, signal, sent) : this.http.patchJson(path, body, parse, signal, sent));
|
|
1369
2145
|
}
|
|
1370
|
-
delete(ref) {
|
|
2146
|
+
delete(ref, options) {
|
|
1371
2147
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
ref,
|
|
1378
|
-
op: "delete"
|
|
1379
|
-
}, (raw) => _embedpdf_engine_core_wire.AnnotationDeleteResultSchema.parse(raw), signal);
|
|
1380
|
-
return this.absorbMutation(result, "annotation.deleted");
|
|
1381
|
-
});
|
|
2148
|
+
let opId;
|
|
2149
|
+
try {
|
|
2150
|
+
opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
2151
|
+
} catch (err) {
|
|
2152
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
|
|
1382
2153
|
}
|
|
1383
|
-
|
|
1384
|
-
const path = _embedpdf_engine_core_wire.wirePaths.layerAnnotationByKey(this.docId, this.layerName,
|
|
1385
|
-
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (
|
|
1386
|
-
const result = await this.http.deleteJson(path, (raw) => _embedpdf_engine_core_wire.AnnotationDeleteResultSchema.parse(raw), signal);
|
|
1387
|
-
return this.
|
|
1388
|
-
});
|
|
2154
|
+
if (ref.page.objectNumber !== this.pageRef.objectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `ref.page ${ref.page.objectNumber} != page ${this.pageRef.objectNumber}`));
|
|
2155
|
+
const path = _embedpdf_engine_core_wire.wirePaths.layerAnnotationByKey(this.docId, this.layerName, this.pageRef, (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(ref));
|
|
2156
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
|
|
2157
|
+
const result = await write.send((sent) => this.http.deleteJson(path, (raw) => _embedpdf_engine_core_wire.AnnotationDeleteResultSchema.parse(raw), signal, sent));
|
|
2158
|
+
return this.absorbDelete(opId, result);
|
|
2159
|
+
}));
|
|
1389
2160
|
}
|
|
1390
|
-
|
|
2161
|
+
reorder(refs, position, options) {
|
|
1391
2162
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
2163
|
+
let opId;
|
|
2164
|
+
try {
|
|
2165
|
+
opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
2166
|
+
} catch (err) {
|
|
2167
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
|
|
2168
|
+
}
|
|
2169
|
+
for (const r of refs) if (r.page.objectNumber !== this.pageRef.objectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `reorder ref points at page ${r.page.objectNumber}; service is bound to page ${this.pageRef.objectNumber}`));
|
|
2170
|
+
const path = _embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsReorder(this.docId, this.layerName, this.pageRef);
|
|
2171
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
|
|
2172
|
+
const result = await write.send((sent) => this.http.postJson(path, {
|
|
1396
2173
|
refs,
|
|
1397
|
-
|
|
1398
|
-
}, (raw) => _embedpdf_engine_core_wire.
|
|
1399
|
-
|
|
1400
|
-
|
|
2174
|
+
position
|
|
2175
|
+
}, (raw) => _embedpdf_engine_core_wire.AnnotationReorderResultSchema.parse(raw), signal, sent));
|
|
2176
|
+
this.manifest.apply(result.meta, ["annotations"]);
|
|
2177
|
+
this.publisher.publishWrite(opId, {
|
|
2178
|
+
type: "annotations.reordered",
|
|
2179
|
+
page: this.pageRef,
|
|
2180
|
+
...result
|
|
2181
|
+
});
|
|
2182
|
+
return result;
|
|
2183
|
+
}));
|
|
1401
2184
|
}
|
|
1402
|
-
flatten(refs,
|
|
2185
|
+
flatten(refs, options) {
|
|
2186
|
+
const usage = options?.usage ?? "display";
|
|
1403
2187
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
2188
|
+
let opId;
|
|
2189
|
+
try {
|
|
2190
|
+
opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
2191
|
+
} catch (err) {
|
|
2192
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
|
|
2193
|
+
}
|
|
2194
|
+
for (const r of refs) if (r.page.objectNumber !== this.pageRef.objectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `flatten ref points at page ${r.page.objectNumber}; service is bound to page ${this.pageRef.objectNumber}`));
|
|
2195
|
+
const path = _embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsFlatten(this.docId, this.layerName, this.pageRef);
|
|
2196
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
|
|
2197
|
+
const result = await write.send((sent) => this.http.postJson(path, {
|
|
1408
2198
|
refs,
|
|
1409
2199
|
usage
|
|
1410
|
-
}, (raw) => _embedpdf_engine_core_wire.AnnotationFlattenResultSchema.parse(raw), signal);
|
|
1411
|
-
if (result.meta === null) return result;
|
|
1412
|
-
this.manifest.apply(result.meta, [
|
|
1413
|
-
|
|
2200
|
+
}, (raw) => _embedpdf_engine_core_wire.AnnotationFlattenResultSchema.parse(raw), signal, sent));
|
|
2201
|
+
if (result.meta.cacheDelta === null) return result;
|
|
2202
|
+
this.manifest.apply(result.meta, [
|
|
2203
|
+
"content",
|
|
2204
|
+
"annotations",
|
|
2205
|
+
"forms"
|
|
2206
|
+
]);
|
|
2207
|
+
this.publisher.publishWrite(opId, {
|
|
1414
2208
|
type: "annotations.flattened",
|
|
1415
2209
|
...result
|
|
1416
2210
|
});
|
|
1417
2211
|
return result;
|
|
1418
|
-
});
|
|
2212
|
+
}));
|
|
1419
2213
|
}
|
|
1420
2214
|
exportAppearance(refs) {
|
|
1421
2215
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
1422
|
-
for (const r of refs) if (r.
|
|
1423
|
-
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.postJsonBytes(_embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsAppearance(this.docId, this.layerName, this.
|
|
2216
|
+
for (const r of refs) if (r.page.objectNumber !== this.pageRef.objectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `exportAppearance ref points at page ${r.page.objectNumber}; service is bound to page ${this.pageRef.objectNumber}`));
|
|
2217
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.postJsonBytes(_embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsAppearance(this.docId, this.layerName, this.pageRef), { refs }, signal));
|
|
1424
2218
|
}
|
|
1425
2219
|
/**
|
|
1426
2220
|
* Patch the cached manifest, then publish the mutation to the document's
|
|
@@ -1429,148 +2223,134 @@ var CloudPageAnnotationsService = class {
|
|
|
1429
2223
|
* `type` with the matching result by construction; the cast localizes that
|
|
1430
2224
|
* pairing here instead of widening every site.
|
|
1431
2225
|
*/
|
|
1432
|
-
|
|
2226
|
+
/** A delete's event names what went, for listeners that didn't delete it. */
|
|
2227
|
+
absorbDelete(opId, result) {
|
|
2228
|
+
this.manifest.apply(result.meta, ["annotations"]);
|
|
2229
|
+
this.publisher.publishWrite(opId, {
|
|
2230
|
+
type: "annotations.deleted",
|
|
2231
|
+
page: this.pageRef,
|
|
2232
|
+
deleted: (0, _embedpdf_engine_core_runtime.deletedAnnotationsOf)(result),
|
|
2233
|
+
...result
|
|
2234
|
+
});
|
|
2235
|
+
return result;
|
|
2236
|
+
}
|
|
2237
|
+
absorbMutation(opId, result, type) {
|
|
1433
2238
|
this.manifest.apply(result.meta, ["annotations"]);
|
|
1434
|
-
this.publisher.
|
|
2239
|
+
this.publisher.publishWrite(opId, {
|
|
1435
2240
|
type,
|
|
1436
|
-
|
|
2241
|
+
page: this.pageRef,
|
|
1437
2242
|
...result
|
|
1438
2243
|
});
|
|
1439
2244
|
return result;
|
|
1440
2245
|
}
|
|
1441
2246
|
};
|
|
2247
|
+
//#endregion
|
|
2248
|
+
//#region src/document/CloudPageFormsService.ts
|
|
1442
2249
|
/**
|
|
1443
|
-
*
|
|
1444
|
-
* `
|
|
1445
|
-
*
|
|
1446
|
-
* wrapped in a `PageImageHandle` backed by the in-memory blob we already
|
|
1447
|
-
* downloaded.
|
|
1448
|
-
*/
|
|
1449
|
-
async function parseAppearanceForm(form) {
|
|
1450
|
-
const manifestRaw = form.get("manifest");
|
|
1451
|
-
if (typeof manifestRaw !== "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, "appearance response missing JSON manifest part");
|
|
1452
|
-
let parsedJson;
|
|
1453
|
-
try {
|
|
1454
|
-
parsedJson = JSON.parse(manifestRaw);
|
|
1455
|
-
} catch (err) {
|
|
1456
|
-
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `appearance manifest is not valid JSON: ${err?.message ?? err}`);
|
|
1457
|
-
}
|
|
1458
|
-
const manifest = _embedpdf_engine_core_wire.AnnotationAppearanceManifestSchema.parse(parsedJson);
|
|
1459
|
-
const appearances = await Promise.all(manifest.appearances.map(async (entry) => {
|
|
1460
|
-
const partValue = form.get(entry.part);
|
|
1461
|
-
if (partValue === null || typeof partValue === "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `appearance response missing image part "${entry.part}"`);
|
|
1462
|
-
const blob = partValue;
|
|
1463
|
-
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
1464
|
-
const image = (0, _embedpdf_engine_core_runtime.createPageImageHandle)({
|
|
1465
|
-
width: entry.width,
|
|
1466
|
-
height: entry.height,
|
|
1467
|
-
format: entry.format,
|
|
1468
|
-
contentType: entry.contentType,
|
|
1469
|
-
source: {
|
|
1470
|
-
kind: "bytes",
|
|
1471
|
-
bytes
|
|
1472
|
-
}
|
|
1473
|
-
}, { async blob() {
|
|
1474
|
-
return blob;
|
|
1475
|
-
} });
|
|
1476
|
-
return {
|
|
1477
|
-
ref: entry.ref,
|
|
1478
|
-
mode: entry.mode,
|
|
1479
|
-
rect: entry.rect,
|
|
1480
|
-
image
|
|
1481
|
-
};
|
|
1482
|
-
}));
|
|
1483
|
-
return {
|
|
1484
|
-
pageState: manifest.pageState,
|
|
1485
|
-
appearances
|
|
1486
|
-
};
|
|
1487
|
-
}
|
|
1488
|
-
/**
|
|
1489
|
-
* Local helper: project a non-index `AnnotationRef` into the matching
|
|
1490
|
-
* `AnnotationStableId` shape so we can route by stable key. The compiler
|
|
1491
|
-
* narrows on `ref.kind` here so we can't accidentally pass an index ref.
|
|
2250
|
+
* Cloud page form service: the page's widget images, one read of the
|
|
2251
|
+
* immutable `form/pages/{p}/appearances@` leaf at the page's `widgetVersion`,
|
|
2252
|
+
* at the base's URL while the layer inherits the `forms` plane.
|
|
1492
2253
|
*/
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
kind: "objectNumber",
|
|
1496
|
-
value: ref.annotObjectNumber
|
|
1497
|
-
};
|
|
1498
|
-
return {
|
|
1499
|
-
kind: "nm",
|
|
1500
|
-
value: ref.nm
|
|
1501
|
-
};
|
|
1502
|
-
}
|
|
1503
|
-
//#endregion
|
|
1504
|
-
//#region src/document/CloudPageGeometryService.ts
|
|
1505
|
-
var CloudPageGeometryService = class {
|
|
1506
|
-
constructor(http, docId, layerName, pageObjectNumber, isClosed, manifest) {
|
|
2254
|
+
var CloudPageFormsService = class {
|
|
2255
|
+
constructor(http, docId, layerName, pageRef, isClosed, manifest) {
|
|
1507
2256
|
this.http = http;
|
|
1508
2257
|
this.docId = docId;
|
|
1509
2258
|
this.layerName = layerName;
|
|
1510
|
-
this.
|
|
2259
|
+
this.pageRef = pageRef;
|
|
1511
2260
|
this.isClosed = isClosed;
|
|
1512
2261
|
this.manifest = manifest;
|
|
1513
2262
|
}
|
|
1514
|
-
|
|
2263
|
+
renderAppearances(options = {}) {
|
|
1515
2264
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
1516
2265
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
2266
|
+
(0, _embedpdf_engine_core_runtime.checkImageQuality)(options.quality);
|
|
2267
|
+
const format = options.format ?? "webp";
|
|
1517
2268
|
const buildPath = async (s) => {
|
|
1518
2269
|
const manifest = await this.manifest.get(s);
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
|
|
2270
|
+
const pageObjectNumber = this.pageRef.objectNumber;
|
|
2271
|
+
const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
|
|
2272
|
+
if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
|
|
2273
|
+
const token = (0, _embedpdf_engine_core_wire.widgetAppearancesImageOptionsToWire)({
|
|
2274
|
+
...options,
|
|
2275
|
+
format
|
|
2276
|
+
}, { widgetVersion: page.cache.widgetVersion });
|
|
2277
|
+
return planesInherited(manifest, ["forms"]) ? _embedpdf_engine_core_wire.wirePaths.docPageFormAppearances(this.docId, this.pageRef, token) : _embedpdf_engine_core_wire.wirePaths.layerPageFormAppearances(this.docId, this.layerName, this.pageRef, token);
|
|
1522
2278
|
};
|
|
1523
|
-
return this.http.
|
|
2279
|
+
return parseAppearanceForm(await this.http.getFormDataWithRefresh(buildPath, async (s) => {
|
|
1524
2280
|
await this.manifest.refresh(s);
|
|
1525
|
-
}, signal);
|
|
2281
|
+
}, signal));
|
|
1526
2282
|
});
|
|
1527
2283
|
}
|
|
1528
2284
|
};
|
|
1529
2285
|
//#endregion
|
|
1530
2286
|
//#region src/document/CloudPageRenderService.ts
|
|
1531
2287
|
var CloudPageRenderService = class {
|
|
1532
|
-
constructor(http, docId, layerName,
|
|
2288
|
+
constructor(http, docId, layerName, pageRef, isClosed, manifest, layout, layerRights, printRights) {
|
|
1533
2289
|
this.http = http;
|
|
1534
2290
|
this.docId = docId;
|
|
1535
2291
|
this.layerName = layerName;
|
|
1536
|
-
this.
|
|
2292
|
+
this.pageRef = pageRef;
|
|
1537
2293
|
this.isClosed = isClosed;
|
|
1538
2294
|
this.manifest = manifest;
|
|
2295
|
+
this.layout = layout;
|
|
2296
|
+
this.layerRights = layerRights;
|
|
2297
|
+
this.printRights = printRights;
|
|
1539
2298
|
}
|
|
1540
2299
|
image(options = {}) {
|
|
1541
2300
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
1542
2301
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
2302
|
+
(0, _embedpdf_engine_core_runtime.checkImageQuality)(options.quality);
|
|
1543
2303
|
const format = normalizeFormat(options.format);
|
|
1544
|
-
|
|
2304
|
+
(0, _embedpdf_engine_core_runtime.checkRenderUsage)(options, this.printRights());
|
|
2305
|
+
const picture = (0, _embedpdf_engine_core_wire.pagePictureOf)((0, _embedpdf_engine_core_runtime.resolvePageLayers)(options, this.layerRights() ?? {
|
|
2306
|
+
annotations: true,
|
|
2307
|
+
formFields: true
|
|
2308
|
+
}), (0, _embedpdf_engine_core_runtime.printDpiOf)(options));
|
|
1545
2309
|
const buildPath = async (s) => {
|
|
1546
2310
|
const manifest = await this.manifest.get(s);
|
|
1547
|
-
const
|
|
1548
|
-
|
|
2311
|
+
const pageObjectNumber = this.pageRef.objectNumber;
|
|
2312
|
+
const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
|
|
2313
|
+
if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
|
|
2314
|
+
const pins = { contentVersion: page.cache.contentVersion };
|
|
2315
|
+
for (const pin of picture.pins) pins[pin] = page.cache[pin];
|
|
1549
2316
|
const wireToken = (0, _embedpdf_engine_core_wire.renderImageOptionsToWire)({
|
|
1550
2317
|
...options,
|
|
1551
2318
|
format
|
|
1552
|
-
},
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
2319
|
+
}, pins);
|
|
2320
|
+
const print = picture.print;
|
|
2321
|
+
if (planesInherited(manifest, picture.planes)) return print ? _embedpdf_engine_core_wire.wirePaths.docPagePrint(this.docId, print.tier, picture.family, this.pageRef, wireToken) : _embedpdf_engine_core_wire.wirePaths.docPageRender(this.docId, picture.family, this.pageRef, wireToken);
|
|
2322
|
+
return print ? _embedpdf_engine_core_wire.wirePaths.layerPagePrint(this.docId, this.layerName, print.tier, picture.family, this.pageRef, wireToken) : _embedpdf_engine_core_wire.wirePaths.layerPageRender(this.docId, this.layerName, picture.family, this.pageRef, wireToken);
|
|
2323
|
+
};
|
|
2324
|
+
const [requestPath, transform] = await Promise.all([buildPath(signal), this.transformOf(options, signal)]);
|
|
2325
|
+
return {
|
|
2326
|
+
...createCloudPageImageHandle({
|
|
2327
|
+
width: transform.width,
|
|
2328
|
+
height: transform.height,
|
|
2329
|
+
format,
|
|
2330
|
+
contentType: `image/${format}`,
|
|
2331
|
+
source: {
|
|
2332
|
+
kind: "url",
|
|
2333
|
+
url: this.http.absoluteUrl(requestPath)
|
|
2334
|
+
}
|
|
2335
|
+
}, this.http, buildPath, async (s) => {
|
|
2336
|
+
await this.manifest.refresh(s);
|
|
2337
|
+
}),
|
|
2338
|
+
transform
|
|
1558
2339
|
};
|
|
1559
|
-
const requestPath = await buildPath(signal);
|
|
1560
|
-
return createCloudPageImageHandle({
|
|
1561
|
-
format,
|
|
1562
|
-
contentType: `image/${format}`,
|
|
1563
|
-
source: {
|
|
1564
|
-
kind: "url",
|
|
1565
|
-
url: this.http.absoluteUrl(requestPath)
|
|
1566
|
-
}
|
|
1567
|
-
}, this.http, buildPath, async (s) => {
|
|
1568
|
-
await this.manifest.refresh(s);
|
|
1569
|
-
});
|
|
1570
2340
|
});
|
|
1571
2341
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
2342
|
+
/**
|
|
2343
|
+
* The image's transform, computed like the server's render (the same
|
|
2344
|
+
* `renderSize` and matrix), so the handle knows its size and its pixels
|
|
2345
|
+
* before any are fetched. Only a full page needs the page's layout.
|
|
2346
|
+
*/
|
|
2347
|
+
async transformOf(options, signal) {
|
|
2348
|
+
const target = options.target;
|
|
2349
|
+
return (0, _embedpdf_engine_core_runtime.renderAreaTransform)(target?.kind === "rect" ? (0, _embedpdf_engine_core_runtime.renderTargetArea)(target.rect) : {
|
|
2350
|
+
x: 0,
|
|
2351
|
+
y: 0,
|
|
2352
|
+
...(await this.layout(signal)).size
|
|
2353
|
+
}, options);
|
|
1574
2354
|
}
|
|
1575
2355
|
};
|
|
1576
2356
|
function createCloudPageImageHandle(result, http, buildPath, onStaleVersion) {
|
|
@@ -1584,65 +2364,78 @@ function normalizeFormat(format) {
|
|
|
1584
2364
|
//#endregion
|
|
1585
2365
|
//#region src/document/CloudPageTextService.ts
|
|
1586
2366
|
/**
|
|
1587
|
-
* Cloud-side per-page text service. `
|
|
1588
|
-
*
|
|
1589
|
-
*
|
|
1590
|
-
* manifest. On a 404 (stale version) the SDK transparently
|
|
1591
|
-
* `/head` + `/manifest@docVersion=N`, rebuilds the URL with the
|
|
1592
|
-
* and retries exactly once.
|
|
2367
|
+
* Cloud-side per-page text service. `get()` fetches the content-addressed
|
|
2368
|
+
* URL `/v1/docs/:id/text/pages/:pon/data@contentVersion=N` and `layout()`
|
|
2369
|
+
* its geometry twin, where `N` is the page's current `contentVersion` from
|
|
2370
|
+
* the cached manifest. On a 404 (stale version) the SDK transparently
|
|
2371
|
+
* refreshes `/head` + `/manifest@docVersion=N`, rebuilds the URL with the
|
|
2372
|
+
* fresh version, and retries exactly once.
|
|
1593
2373
|
*/
|
|
1594
2374
|
var CloudPageTextService = class {
|
|
1595
|
-
constructor(http, docId, layerName,
|
|
2375
|
+
constructor(http, docId, layerName, pageRef, isClosed, manifest) {
|
|
1596
2376
|
this.http = http;
|
|
1597
2377
|
this.docId = docId;
|
|
1598
2378
|
this.layerName = layerName;
|
|
1599
|
-
this.
|
|
2379
|
+
this.pageRef = pageRef;
|
|
1600
2380
|
this.isClosed = isClosed;
|
|
1601
2381
|
this.manifest = manifest;
|
|
1602
2382
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
2383
|
+
get() {
|
|
2384
|
+
return this.readLeaf("text", (raw) => _embedpdf_engine_core_wire.PageTextSnapshotSchema.parse(raw));
|
|
2385
|
+
}
|
|
2386
|
+
slice(range) {
|
|
2387
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => (0, _embedpdf_engine_core_runtime.sliceText)(await this.get().abortWith(signal), range));
|
|
2388
|
+
}
|
|
2389
|
+
layout() {
|
|
2390
|
+
return this.readLeaf("geometry", (raw) => (0, _embedpdf_engine_core_runtime.createTextLayout)(_embedpdf_engine_core_wire.PageGeometrySnapshotSchema.parse(raw)));
|
|
2391
|
+
}
|
|
2392
|
+
/** One of the page's content leaves at the manifest's pin, with the stale-pin retry. */
|
|
2393
|
+
readLeaf(leaf, parse) {
|
|
2394
|
+
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
2395
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.getJsonWithRefresh(async (s) => this.leafPath(leaf, await this.manifest.get(s)), parse, async (s) => {
|
|
2396
|
+
await this.manifest.refresh(s);
|
|
2397
|
+
}, signal));
|
|
2398
|
+
}
|
|
2399
|
+
leafPath(leaf, manifest) {
|
|
2400
|
+
const pageObjectNumber = this.pageRef.objectNumber;
|
|
2401
|
+
const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
|
|
2402
|
+
if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
|
|
2403
|
+
const version = page.cache.contentVersion;
|
|
2404
|
+
if (planesInherited(manifest, ["content"])) return leaf === "text" ? _embedpdf_engine_core_wire.wirePaths.docPageText(this.docId, this.pageRef, version) : _embedpdf_engine_core_wire.wirePaths.docPageGeometry(this.docId, this.pageRef, version);
|
|
2405
|
+
return leaf === "text" ? _embedpdf_engine_core_wire.wirePaths.layerPageText(this.docId, this.layerName, this.pageRef, version) : _embedpdf_engine_core_wire.wirePaths.layerPageGeometry(this.docId, this.layerName, this.pageRef, version);
|
|
1616
2406
|
}
|
|
1617
2407
|
};
|
|
1618
2408
|
//#endregion
|
|
1619
2409
|
//#region src/document/CloudPageMeasureService.ts
|
|
1620
2410
|
var CloudPageMeasureService = class {
|
|
1621
|
-
constructor(http, docId, layerName,
|
|
2411
|
+
constructor(http, docId, layerName, pageRef, isClosed, manifest, publisher, writes) {
|
|
1622
2412
|
this.http = http;
|
|
1623
2413
|
this.docId = docId;
|
|
1624
2414
|
this.layerName = layerName;
|
|
1625
|
-
this.
|
|
2415
|
+
this.pageRef = pageRef;
|
|
1626
2416
|
this.isClosed = isClosed;
|
|
1627
2417
|
this.manifest = manifest;
|
|
1628
2418
|
this.publisher = publisher;
|
|
2419
|
+
this.writes = writes;
|
|
1629
2420
|
}
|
|
1630
|
-
|
|
2421
|
+
listViewports() {
|
|
1631
2422
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1632
2423
|
this.check();
|
|
1633
|
-
return this.http.getJson(_embedpdf_engine_core_wire.wirePaths.layerPageViewports(this.docId, this.layerName, this.
|
|
2424
|
+
return this.http.getJson(_embedpdf_engine_core_wire.wirePaths.layerPageViewports(this.docId, this.layerName, this.pageRef), (raw) => _embedpdf_engine_core_wire.PageMeasurementViewportListSchema.parse(raw), signal);
|
|
1634
2425
|
});
|
|
1635
2426
|
}
|
|
1636
|
-
setScale(measure) {
|
|
2427
|
+
setScale(measure, options) {
|
|
1637
2428
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1638
|
-
|
|
1639
|
-
signal.throwIfAborted();
|
|
2429
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1640
2430
|
this.check();
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
2431
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
2432
|
+
const result = await write.send((sent) => this.http.putJson(_embedpdf_engine_core_wire.wirePaths.layerPageScale(this.docId, this.layerName, this.pageRef), { measure }, (raw) => _embedpdf_engine_core_wire.PageScaleResultSchema.parse(raw), signal, sent));
|
|
2433
|
+
this.manifest.apply(result.meta, []);
|
|
2434
|
+
this.publisher.publishWrite(opId, {
|
|
2435
|
+
type: "pages.scaleSet",
|
|
2436
|
+
...result
|
|
2437
|
+
});
|
|
2438
|
+
return result;
|
|
1646
2439
|
});
|
|
1647
2440
|
});
|
|
1648
2441
|
}
|
|
@@ -1652,103 +2445,284 @@ var CloudPageMeasureService = class {
|
|
|
1652
2445
|
};
|
|
1653
2446
|
//#endregion
|
|
1654
2447
|
//#region src/document/CloudPageHandle.ts
|
|
2448
|
+
/**
|
|
2449
|
+
* Cloud page handle, keyed on `ref` — the page's durable address (its
|
|
2450
|
+
* object number). Every service below addresses the wire by it
|
|
2451
|
+
* (`/pages/obj:N/…`), keys the manifest by its number, and publishes
|
|
2452
|
+
* that number in events.
|
|
2453
|
+
*/
|
|
1655
2454
|
var CloudPageHandle = class {
|
|
1656
|
-
constructor(
|
|
1657
|
-
this.
|
|
1658
|
-
this.pageIndex = pageIndex;
|
|
2455
|
+
constructor(ref, http, docId, layerName, isClosed, manifest, publisher, writes, layout, layerRights, printRights) {
|
|
2456
|
+
this.ref = ref;
|
|
1659
2457
|
_defineProperty(this, "annotations", void 0);
|
|
2458
|
+
_defineProperty(this, "forms", void 0);
|
|
1660
2459
|
_defineProperty(this, "text", void 0);
|
|
1661
|
-
_defineProperty(this, "geometry", void 0);
|
|
1662
2460
|
_defineProperty(this, "render", void 0);
|
|
1663
2461
|
_defineProperty(this, "measure", void 0);
|
|
1664
|
-
this.measure = new CloudPageMeasureService(http, docId, layerName,
|
|
1665
|
-
this.annotations = new CloudPageAnnotationsService(http, docId, layerName,
|
|
1666
|
-
this.
|
|
1667
|
-
this.
|
|
1668
|
-
this.render = new CloudPageRenderService(http, docId, layerName,
|
|
2462
|
+
this.measure = new CloudPageMeasureService(http, docId, layerName, ref, isClosed, manifest, publisher, writes);
|
|
2463
|
+
this.annotations = new CloudPageAnnotationsService(http, docId, layerName, ref, isClosed, manifest, publisher, writes);
|
|
2464
|
+
this.forms = new CloudPageFormsService(http, docId, layerName, ref, isClosed, manifest);
|
|
2465
|
+
this.text = new CloudPageTextService(http, docId, layerName, ref, isClosed, manifest);
|
|
2466
|
+
this.render = new CloudPageRenderService(http, docId, layerName, ref, isClosed, manifest, layout, layerRights, printRights);
|
|
2467
|
+
}
|
|
2468
|
+
};
|
|
2469
|
+
//#endregion
|
|
2470
|
+
//#region src/document/CloudWrites.ts
|
|
2471
|
+
/**
|
|
2472
|
+
* A document's writes, sent one at a time in the order they were called,
|
|
2473
|
+
* so the server applies them in that order: a create, then an update of
|
|
2474
|
+
* what it made, never the other way round. Reads don't wait.
|
|
2475
|
+
*
|
|
2476
|
+
* A write takes its place when it is called: `run` must be the first thing
|
|
2477
|
+
* a write does, before anything it awaits. What it does before `send`
|
|
2478
|
+
* (reading a file to upload, say) happens meanwhile; its turn ends when it
|
|
2479
|
+
* settles, after the event it publishes.
|
|
2480
|
+
*
|
|
2481
|
+
* Changes (`doc.apply`) called one after another share a place: while the
|
|
2482
|
+
* write before them is in flight they wait together, and go out as one
|
|
2483
|
+
* request when their turn comes, each answered on its own.
|
|
2484
|
+
*/
|
|
2485
|
+
var CloudWrites = class {
|
|
2486
|
+
constructor(pool, sendChanges = () => {
|
|
2487
|
+
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "this line sends no changes");
|
|
2488
|
+
}) {
|
|
2489
|
+
this.pool = pool;
|
|
2490
|
+
this.sendChanges = sendChanges;
|
|
2491
|
+
_defineProperty(this, "last", Promise.resolve());
|
|
2492
|
+
_defineProperty(this, "open", null);
|
|
2493
|
+
}
|
|
2494
|
+
run(opId, signal, body, named = []) {
|
|
2495
|
+
this.open = null;
|
|
2496
|
+
const before = this.last;
|
|
2497
|
+
const numbers = named.filter((n) => n !== void 0);
|
|
2498
|
+
this.pool.beginWrite(numbers);
|
|
2499
|
+
const settled = body({ send: async (request) => {
|
|
2500
|
+
await untilTurn(before, signal);
|
|
2501
|
+
const reserve = this.pool.topUp();
|
|
2502
|
+
return request({ write: {
|
|
2503
|
+
opId,
|
|
2504
|
+
...reserve > 0 ? { reserveObjectNumbers: reserve } : {},
|
|
2505
|
+
onObjectNumbers: (ranges) => this.pool.receive(ranges)
|
|
2506
|
+
} });
|
|
2507
|
+
} }).then((result) => {
|
|
2508
|
+
this.pool.endWrite(numbers, "committed");
|
|
2509
|
+
return result;
|
|
2510
|
+
}, (error) => {
|
|
2511
|
+
this.pool.endWrite(numbers, outcomeOf(error));
|
|
2512
|
+
throw error;
|
|
2513
|
+
});
|
|
2514
|
+
this.last = before.then(() => settled.then(noop, noop));
|
|
2515
|
+
return settled;
|
|
2516
|
+
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Take a change's place in the line: it joins the changes waiting for
|
|
2519
|
+
* their turn, and goes out with them. `ready` is what the request sends
|
|
2520
|
+
* for it, which it may still be preparing (reading bytes). A change aborted
|
|
2521
|
+
* before its request goes out is left out of it; once out, it is answered.
|
|
2522
|
+
*/
|
|
2523
|
+
change(signal, ready, { named = [], alone = false } = {}) {
|
|
2524
|
+
this.pool.beginWrite(named);
|
|
2525
|
+
ready.catch(noop);
|
|
2526
|
+
return new Promise((resolve, reject) => {
|
|
2527
|
+
const lined = {
|
|
2528
|
+
ready,
|
|
2529
|
+
signal,
|
|
2530
|
+
numbers: named,
|
|
2531
|
+
resolve,
|
|
2532
|
+
reject
|
|
2533
|
+
};
|
|
2534
|
+
(!alone && this.open !== null && this.open.changes.length < _embedpdf_engine_core_wire.CHANGE_REQUEST_LIMITS.changes ? this.open : this.queue()).changes.push(lined);
|
|
2535
|
+
if (alone) this.open = null;
|
|
2536
|
+
}).then((settled) => {
|
|
2537
|
+
this.pool.endWrite(named, settledOutcomeOf(settled));
|
|
2538
|
+
return settled;
|
|
2539
|
+
}, (error) => {
|
|
2540
|
+
this.pool.endWrite(named, outcomeOf(error));
|
|
2541
|
+
throw error;
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
/** A new batch at the end of the line, open for the changes that follow. */
|
|
2545
|
+
queue() {
|
|
2546
|
+
const before = this.last;
|
|
2547
|
+
const batch = { changes: [] };
|
|
2548
|
+
this.open = batch;
|
|
2549
|
+
const sent = before.then(() => this.send(batch));
|
|
2550
|
+
this.last = sent.then(noop, noop);
|
|
2551
|
+
return batch;
|
|
2552
|
+
}
|
|
2553
|
+
/** A batch's turn: nothing joins it any more, and what's ready goes out. */
|
|
2554
|
+
async send(batch) {
|
|
2555
|
+
if (this.open === batch) this.open = null;
|
|
2556
|
+
const prepared = await Promise.allSettled(batch.changes.map((lined) => lined.ready));
|
|
2557
|
+
const going = [];
|
|
2558
|
+
batch.changes.forEach((lined, index) => {
|
|
2559
|
+
const ready = prepared[index];
|
|
2560
|
+
if (lined.signal.aborted) lined.reject(new _embedpdf_engine_core_runtime.AbortError(lined.signal.reason ?? "aborted"));
|
|
2561
|
+
else if (ready.status === "rejected") lined.reject(ready.reason);
|
|
2562
|
+
else going.push({
|
|
2563
|
+
lined,
|
|
2564
|
+
sent: ready.value
|
|
2565
|
+
});
|
|
2566
|
+
});
|
|
2567
|
+
if (going.length === 0) return;
|
|
2568
|
+
const reserve = this.pool.topUp();
|
|
2569
|
+
try {
|
|
2570
|
+
const answers = await this.sendChanges(going.map(({ sent }) => sent), { write: {
|
|
2571
|
+
...reserve > 0 ? { reserveObjectNumbers: reserve } : {},
|
|
2572
|
+
onObjectNumbers: (ranges) => this.pool.receive(ranges)
|
|
2573
|
+
} });
|
|
2574
|
+
if (answers.length !== going.length) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `${going.length} changes sent, ${answers.length} answered`);
|
|
2575
|
+
going.forEach(({ lined }, index) => lined.resolve(answers[index]));
|
|
2576
|
+
} catch (error) {
|
|
2577
|
+
for (const { lined } of going) lined.reject(error);
|
|
2578
|
+
}
|
|
1669
2579
|
}
|
|
1670
2580
|
};
|
|
2581
|
+
function noop() {}
|
|
2582
|
+
/** What a write that failed with `error` did with the numbers it named. */
|
|
2583
|
+
function outcomeOf(error) {
|
|
2584
|
+
return _embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.ObjectNumberUnavailable) ? "refused" : "failed";
|
|
2585
|
+
}
|
|
2586
|
+
/** What an answered change did with the numbers it named, as a single write's outcome. */
|
|
2587
|
+
function settledOutcomeOf(answer) {
|
|
2588
|
+
if (answer.status === "applied") return "committed";
|
|
2589
|
+
return answer.error.code === _embedpdf_engine_core_runtime.EngineErrorCode.ObjectNumberUnavailable ? "refused" : "failed";
|
|
2590
|
+
}
|
|
2591
|
+
/** `before`, or an `AbortError` once `signal` aborts. */
|
|
2592
|
+
function untilTurn(before, signal) {
|
|
2593
|
+
if (signal.aborted) return Promise.reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason ?? "aborted"));
|
|
2594
|
+
return new Promise((resolve, reject) => {
|
|
2595
|
+
const onAbort = () => reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason ?? "aborted"));
|
|
2596
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2597
|
+
before.then(() => {
|
|
2598
|
+
signal.removeEventListener("abort", onAbort);
|
|
2599
|
+
resolve();
|
|
2600
|
+
});
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
1671
2603
|
//#endregion
|
|
1672
|
-
//#region src/realtime/
|
|
2604
|
+
//#region src/realtime/auditRowToEvents.ts
|
|
1673
2605
|
/**
|
|
1674
|
-
* Translate a remote audit row into
|
|
1675
|
-
* exactly-once and verbatim-payload invariants are unit-testable
|
|
1676
|
-
* server.
|
|
1677
|
-
*
|
|
2606
|
+
* Translate a remote audit row into the `DocumentEvent`s it records — pure,
|
|
2607
|
+
* so the exactly-once and verbatim-payload invariants are unit-testable
|
|
2608
|
+
* without a server. Most rows are one fact; a change is the events of its
|
|
2609
|
+
* items, a form reset is one `forms.valueSet` per field it changed, an
|
|
2610
|
+
* import is one `annotations.created` per annotation, and a completed
|
|
2611
|
+
* signing is the signature and the new version. An undo's events name the
|
|
2612
|
+
* change it undid (`origin.undoOf`). Every event of a row shares `origin.tx`,
|
|
2613
|
+
* its id the request's `Idempotency-Key`. Returns none for an own echo and for kinds
|
|
2614
|
+
* this engine version doesn't know (a newer server's events degrade to
|
|
2615
|
+
* "ignored", never to a crash).
|
|
1678
2616
|
*
|
|
1679
|
-
*
|
|
1680
|
-
*
|
|
1681
|
-
*
|
|
1682
|
-
*
|
|
1683
|
-
*
|
|
1684
|
-
* records the resulting order, so `pageObjectNumbers` is the full new
|
|
1685
|
-
* order and `destIndex` is absent (remote consumers use `layout`).
|
|
2617
|
+
* A page name write's row is a `pages.reorder` that moved no page: its
|
|
2618
|
+
* payload is the result, as the originator got it, with the new `layout`.
|
|
2619
|
+
*
|
|
2620
|
+
* The audit row keys pages by object number (its storage identity); the
|
|
2621
|
+
* event carries them as `PageRef` addresses, like every other event.
|
|
1686
2622
|
*/
|
|
1687
|
-
function
|
|
1688
|
-
if (row.originSessionId === mySessionId) return
|
|
2623
|
+
function auditRowToEvents(row, mySessionId) {
|
|
2624
|
+
if (row.originSessionId === mySessionId) return [];
|
|
2625
|
+
if (row.withheld) return [];
|
|
1689
2626
|
const origin = {
|
|
1690
2627
|
kind: "remote",
|
|
1691
2628
|
sessionId: row.originSessionId ?? `unknown:${row.sub}`,
|
|
1692
2629
|
sub: row.sub,
|
|
1693
2630
|
ts: row.ts,
|
|
1694
|
-
serverId: row.id
|
|
2631
|
+
serverId: row.id,
|
|
2632
|
+
...row.undoOf ? { undoOf: row.undoOf } : {}
|
|
1695
2633
|
};
|
|
2634
|
+
const id = row.idempotencyKey ?? `audit:${row.id}`;
|
|
2635
|
+
const events = factsOf(row, origin);
|
|
2636
|
+
return events.map((event, index) => ({
|
|
2637
|
+
...event,
|
|
2638
|
+
origin: {
|
|
2639
|
+
...origin,
|
|
2640
|
+
tx: {
|
|
2641
|
+
id,
|
|
2642
|
+
index,
|
|
2643
|
+
count: events.length
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
}));
|
|
2647
|
+
}
|
|
2648
|
+
/** The events a row records, in order, or none for a kind this version doesn't know. */
|
|
2649
|
+
function factsOf(row, origin) {
|
|
2650
|
+
if (row.kind === "change") return (0, _embedpdf_engine_core_runtime.changeEvents)(row.payload).map((event) => ({
|
|
2651
|
+
...event,
|
|
2652
|
+
origin
|
|
2653
|
+
}));
|
|
2654
|
+
if (row.kind === "annot.import") return (0, _embedpdf_engine_core_runtime.annotationImportFacts)(row.payload).map((fact) => ({
|
|
2655
|
+
type: "annotations.created",
|
|
2656
|
+
origin,
|
|
2657
|
+
...fact
|
|
2658
|
+
}));
|
|
2659
|
+
if (row.kind === "form.import") return (0, _embedpdf_engine_core_runtime.formImportFacts)(row.payload).map((fact) => ({
|
|
2660
|
+
type: "forms.created",
|
|
2661
|
+
origin,
|
|
2662
|
+
...fact
|
|
2663
|
+
}));
|
|
2664
|
+
if (row.kind === "form.importValues") return (0, _embedpdf_engine_core_runtime.formValuesImportFacts)(row.payload).map((fact) => ({
|
|
2665
|
+
type: "forms.valueSet",
|
|
2666
|
+
origin,
|
|
2667
|
+
...fact
|
|
2668
|
+
}));
|
|
2669
|
+
if (row.kind === "form.reset") return (0, _embedpdf_engine_core_runtime.formResetFacts)(row.payload).map((fact) => ({
|
|
2670
|
+
type: "forms.valueSet",
|
|
2671
|
+
origin,
|
|
2672
|
+
...fact
|
|
2673
|
+
}));
|
|
2674
|
+
if (row.kind === "signature.complete") {
|
|
2675
|
+
const { signingId, ...result } = row.payload;
|
|
2676
|
+
return [{
|
|
2677
|
+
type: "signatures.completed",
|
|
2678
|
+
origin,
|
|
2679
|
+
signingId,
|
|
2680
|
+
...result
|
|
2681
|
+
}, {
|
|
2682
|
+
type: "document.versioned",
|
|
2683
|
+
origin,
|
|
2684
|
+
version: result.version
|
|
2685
|
+
}];
|
|
2686
|
+
}
|
|
2687
|
+
const event = eventOf(row, origin);
|
|
2688
|
+
return event ? [event] : [];
|
|
2689
|
+
}
|
|
2690
|
+
/** The one event a single-fact row records, or `null` for a kind this version doesn't know. */
|
|
2691
|
+
function eventOf(row, origin) {
|
|
2692
|
+
const rowPage = () => (0, _embedpdf_engine_core_runtime.toPageRef)(row.pageObjectNumber ?? row.affectedPages[0] ?? 0);
|
|
1696
2693
|
switch (row.kind) {
|
|
1697
2694
|
case "measure.setScale": return {
|
|
1698
|
-
type: "
|
|
2695
|
+
type: "pages.scaleSet",
|
|
1699
2696
|
origin,
|
|
1700
2697
|
...row.payload
|
|
1701
2698
|
};
|
|
1702
2699
|
case "annot.create": return {
|
|
1703
|
-
type: "
|
|
1704
|
-
|
|
2700
|
+
type: "annotations.created",
|
|
2701
|
+
page: rowPage(),
|
|
1705
2702
|
origin,
|
|
1706
2703
|
...row.payload
|
|
1707
2704
|
};
|
|
1708
2705
|
case "annot.update": return {
|
|
1709
|
-
type: "
|
|
1710
|
-
|
|
2706
|
+
type: "annotations.updated",
|
|
2707
|
+
page: rowPage(),
|
|
1711
2708
|
origin,
|
|
1712
2709
|
...row.payload
|
|
1713
2710
|
};
|
|
1714
2711
|
case "annot.delete": return {
|
|
1715
|
-
type: "
|
|
1716
|
-
|
|
1717
|
-
origin,
|
|
1718
|
-
...row.payload
|
|
1719
|
-
};
|
|
1720
|
-
case "annot.move": return {
|
|
1721
|
-
type: "annotation.moved",
|
|
1722
|
-
pageObjectNumber: row.pageObjectNumber ?? row.affectedPages[0] ?? 0,
|
|
2712
|
+
type: "annotations.deleted",
|
|
2713
|
+
page: rowPage(),
|
|
1723
2714
|
origin,
|
|
2715
|
+
deleted: (0, _embedpdf_engine_core_runtime.deletedAnnotationsOf)(row.payload),
|
|
1724
2716
|
...row.payload
|
|
1725
2717
|
};
|
|
1726
|
-
case "
|
|
1727
|
-
type: "
|
|
1728
|
-
|
|
2718
|
+
case "annot.reorder": return {
|
|
2719
|
+
type: "annotations.reordered",
|
|
2720
|
+
page: rowPage(),
|
|
1729
2721
|
origin,
|
|
1730
2722
|
...row.payload
|
|
1731
2723
|
};
|
|
1732
|
-
case "pages.
|
|
1733
|
-
|
|
1734
|
-
const rotation = payload.layout.pages.find((page) => page.pageObjectNumber === row.affectedPages[0])?.rotation ?? 0;
|
|
1735
|
-
return {
|
|
1736
|
-
type: "pages.rotated",
|
|
1737
|
-
pageObjectNumbers: row.affectedPages,
|
|
1738
|
-
rotation,
|
|
1739
|
-
origin,
|
|
1740
|
-
...payload
|
|
1741
|
-
};
|
|
1742
|
-
}
|
|
1743
|
-
case "pages.delete": return {
|
|
1744
|
-
type: "pages.deleted",
|
|
1745
|
-
pageObjectNumbers: row.affectedPages,
|
|
1746
|
-
origin,
|
|
1747
|
-
...row.payload
|
|
1748
|
-
};
|
|
1749
|
-
case "pages.insert":
|
|
1750
|
-
case "pages.insertBlank": return {
|
|
1751
|
-
type: "pages.inserted",
|
|
2724
|
+
case "pages.reorder": return {
|
|
2725
|
+
type: "pages.reordered",
|
|
1752
2726
|
origin,
|
|
1753
2727
|
...row.payload
|
|
1754
2728
|
};
|
|
@@ -1762,63 +2736,85 @@ function auditRowToEvent(row, mySessionId) {
|
|
|
1762
2736
|
origin,
|
|
1763
2737
|
...row.payload
|
|
1764
2738
|
};
|
|
1765
|
-
case "
|
|
1766
|
-
type: "
|
|
2739
|
+
case "metadata.updateCustom": return {
|
|
2740
|
+
type: "metadata.customUpdated",
|
|
1767
2741
|
origin,
|
|
1768
2742
|
...row.payload
|
|
1769
2743
|
};
|
|
1770
|
-
case "
|
|
1771
|
-
type: "
|
|
2744
|
+
case "form.setValue": return {
|
|
2745
|
+
type: "forms.valueSet",
|
|
1772
2746
|
origin,
|
|
1773
2747
|
...row.payload
|
|
1774
2748
|
};
|
|
1775
|
-
case "form.
|
|
1776
|
-
|
|
1777
|
-
type: "form.valueChanged",
|
|
2749
|
+
case "form.repair": return {
|
|
2750
|
+
type: "forms.repaired",
|
|
1778
2751
|
origin,
|
|
1779
2752
|
...row.payload
|
|
1780
2753
|
};
|
|
1781
|
-
case "form.
|
|
1782
|
-
type: "
|
|
2754
|
+
case "form.createField": return {
|
|
2755
|
+
type: "forms.created",
|
|
1783
2756
|
origin,
|
|
1784
2757
|
...row.payload
|
|
1785
2758
|
};
|
|
1786
|
-
case "form.
|
|
1787
|
-
|
|
2759
|
+
case "form.updateField":
|
|
2760
|
+
case "form.setSignatureAppearance": return {
|
|
2761
|
+
type: "forms.updated",
|
|
1788
2762
|
origin,
|
|
1789
2763
|
...row.payload
|
|
1790
2764
|
};
|
|
1791
|
-
case "form.
|
|
1792
|
-
|
|
2765
|
+
case "form.deleteField": {
|
|
2766
|
+
const result = row.payload;
|
|
2767
|
+
return {
|
|
2768
|
+
type: "forms.deleted",
|
|
2769
|
+
origin,
|
|
2770
|
+
deleted: (0, _embedpdf_engine_core_runtime.deletedFieldOf)(result),
|
|
2771
|
+
...result
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2774
|
+
case "form.addWidget": return {
|
|
2775
|
+
type: "forms.widgetAdded",
|
|
1793
2776
|
origin,
|
|
1794
2777
|
...row.payload
|
|
1795
2778
|
};
|
|
1796
|
-
case "form.
|
|
1797
|
-
|
|
1798
|
-
type: "form.fieldUpdated",
|
|
2779
|
+
case "form.detachWidget": return {
|
|
2780
|
+
type: "forms.widgetRemoved",
|
|
1799
2781
|
origin,
|
|
1800
2782
|
...row.payload
|
|
1801
2783
|
};
|
|
1802
|
-
case "form.
|
|
1803
|
-
type: "
|
|
2784
|
+
case "form.updateWidget": return {
|
|
2785
|
+
type: "forms.widgetUpdated",
|
|
1804
2786
|
origin,
|
|
1805
2787
|
...row.payload
|
|
1806
2788
|
};
|
|
1807
|
-
case "form.
|
|
1808
|
-
type: "
|
|
2789
|
+
case "form.deleteWidget": return {
|
|
2790
|
+
type: "forms.widgetDeleted",
|
|
1809
2791
|
origin,
|
|
1810
2792
|
...row.payload
|
|
1811
2793
|
};
|
|
1812
|
-
case "form.
|
|
1813
|
-
type: "
|
|
2794
|
+
case "form.reorderWidgets": return {
|
|
2795
|
+
type: "forms.widgetsReordered",
|
|
1814
2796
|
origin,
|
|
1815
2797
|
...row.payload
|
|
1816
2798
|
};
|
|
1817
|
-
case "form.
|
|
1818
|
-
type: "
|
|
2799
|
+
case "form.reorderCalculations": return {
|
|
2800
|
+
type: "forms.calculationsReordered",
|
|
1819
2801
|
origin,
|
|
1820
2802
|
...row.payload
|
|
1821
2803
|
};
|
|
2804
|
+
case "signature.prepare": {
|
|
2805
|
+
const { field, ...wire } = row.payload;
|
|
2806
|
+
return {
|
|
2807
|
+
type: "signatures.prepared",
|
|
2808
|
+
origin,
|
|
2809
|
+
field,
|
|
2810
|
+
...(0, _embedpdf_engine_core_wire.decodePrepared)(wire)
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
case "signature.cancel": return {
|
|
2814
|
+
type: "signatures.cancelled",
|
|
2815
|
+
origin,
|
|
2816
|
+
signingId: row.payload.signingId
|
|
2817
|
+
};
|
|
1822
2818
|
default: return null;
|
|
1823
2819
|
}
|
|
1824
2820
|
}
|
|
@@ -1874,7 +2870,12 @@ var SseClient = class {
|
|
|
1874
2870
|
return;
|
|
1875
2871
|
}
|
|
1876
2872
|
if (!res.ok || !res.body) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Network, `events stream HTTP ${res.status}`);
|
|
1877
|
-
|
|
2873
|
+
this.opts.onConnected?.(true);
|
|
2874
|
+
try {
|
|
2875
|
+
await this.consume(res.body);
|
|
2876
|
+
} finally {
|
|
2877
|
+
this.opts.onConnected?.(false);
|
|
2878
|
+
}
|
|
1878
2879
|
if (Date.now() - startedAt >= 3e4) this.retryDelayMs = 500;
|
|
1879
2880
|
} catch {}
|
|
1880
2881
|
if (this.closed) return;
|
|
@@ -1911,7 +2912,11 @@ var SseClient = class {
|
|
|
1911
2912
|
if (event === "mutation") try {
|
|
1912
2913
|
this.opts.onRow(JSON.parse(data.join("\n")));
|
|
1913
2914
|
} catch {}
|
|
1914
|
-
else if (event === "
|
|
2915
|
+
else if (event === "session") {
|
|
2916
|
+
const parsed = safeJson(data.join("\n"));
|
|
2917
|
+
const status = _embedpdf_engine_core_wire.EditSessionStatusSchema.safeParse(parsed);
|
|
2918
|
+
if (status.success) this.opts.onSession?.(status.data);
|
|
2919
|
+
} else if (event === "full-refresh") this.opts.onFullRefresh();
|
|
1915
2920
|
else if (event === "auth-revoked") {
|
|
1916
2921
|
this.closed = true;
|
|
1917
2922
|
this.abort?.abort();
|
|
@@ -1919,29 +2924,39 @@ var SseClient = class {
|
|
|
1919
2924
|
}
|
|
1920
2925
|
}
|
|
1921
2926
|
};
|
|
2927
|
+
function safeJson(text) {
|
|
2928
|
+
try {
|
|
2929
|
+
return JSON.parse(text);
|
|
2930
|
+
} catch {
|
|
2931
|
+
return null;
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
1922
2934
|
//#endregion
|
|
1923
2935
|
//#region src/document/CloudDocumentHandle.ts
|
|
1924
2936
|
var CloudDocumentHandle = class {
|
|
1925
|
-
constructor(http, id, layerName = _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME, initialHead, initialToken = null,
|
|
2937
|
+
constructor(http, id, layerName = _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME, initialHead, initialToken = null, onClose = () => {}) {
|
|
1926
2938
|
this.http = http;
|
|
1927
2939
|
this.layerName = layerName;
|
|
2940
|
+
this.onClose = onClose;
|
|
1928
2941
|
_defineProperty(this, "id", void 0);
|
|
1929
|
-
_defineProperty(this, "capabilities", {
|
|
1930
|
-
weakAnnotationEditSessions: "required",
|
|
1931
|
-
pageEditSessions: "unsupported"
|
|
1932
|
-
});
|
|
1933
2942
|
_defineProperty(this, "metadata", void 0);
|
|
1934
2943
|
_defineProperty(this, "annotations", void 0);
|
|
1935
2944
|
_defineProperty(this, "actions", void 0);
|
|
2945
|
+
_defineProperty(this, "outline", void 0);
|
|
1936
2946
|
_defineProperty(this, "attachments", void 0);
|
|
1937
2947
|
_defineProperty(this, "signatures", void 0);
|
|
1938
2948
|
_defineProperty(this, "forms", void 0);
|
|
1939
2949
|
_defineProperty(this, "search", void 0);
|
|
1940
2950
|
_defineProperty(this, "pages", void 0);
|
|
2951
|
+
_defineProperty(this, "pageReads", void 0);
|
|
1941
2952
|
_defineProperty(this, "redaction", void 0);
|
|
1942
2953
|
_defineProperty(this, "security", void 0);
|
|
1943
2954
|
_defineProperty(this, "render", void 0);
|
|
1944
2955
|
_defineProperty(this, "events", void 0);
|
|
2956
|
+
_defineProperty(this, "objectNumbers", void 0);
|
|
2957
|
+
_defineProperty(this, "pool", void 0);
|
|
2958
|
+
_defineProperty(this, "writes", void 0);
|
|
2959
|
+
_defineProperty(this, "changes", void 0);
|
|
1945
2960
|
_defineProperty(this, "publisher", void 0);
|
|
1946
2961
|
_defineProperty(this, "hub", void 0);
|
|
1947
2962
|
_defineProperty(this, "sessionId", void 0);
|
|
@@ -1953,11 +2968,20 @@ var CloudDocumentHandle = class {
|
|
|
1953
2968
|
_defineProperty(this, "manifestFloorVersion", 0);
|
|
1954
2969
|
_defineProperty(this, "pendingInitialHead", void 0);
|
|
1955
2970
|
_defineProperty(this, "manifestAccessor", void 0);
|
|
2971
|
+
_defineProperty(this, "cloudSecurity", void 0);
|
|
1956
2972
|
this.id = id;
|
|
1957
2973
|
this.pendingInitialHead = initialHead ?? null;
|
|
1958
|
-
const
|
|
2974
|
+
const pool = new CloudObjectNumberPool({ reserve: (count, signal) => http.postJson(_embedpdf_engine_core_wire.wirePaths.objectNumbers(id, layerName), { count }, (raw) => _embedpdf_engine_core_wire.ObjectNumberReservationResponseSchema.parse(raw), signal, { session: true }) });
|
|
2975
|
+
this.pool = pool;
|
|
2976
|
+
this.objectNumbers = pool;
|
|
2977
|
+
this.writes = new CloudWrites(pool, changeSender(http, id, layerName));
|
|
2978
|
+
const security = new CloudDocumentSecurityService(http, id, layerName, initialHead ?? fallbackUnknownHead(id), { isClosed: () => this.closed }, initialToken, {
|
|
2979
|
+
wanted: () => pool.wantedAtAccess(),
|
|
2980
|
+
opened: (edit) => pool.opened(edit)
|
|
2981
|
+
});
|
|
1959
2982
|
this.security = security;
|
|
1960
|
-
this.
|
|
2983
|
+
this.cloudSecurity = security;
|
|
2984
|
+
this.render = { getPolicy: () => _embedpdf_engine_core_runtime.AbortablePromise.run(async () => {
|
|
1961
2985
|
const advertised = (security.currentAccess ?? (await security.establishAccess()).access ?? null)?.renderPolicy;
|
|
1962
2986
|
if (!advertised) return _embedpdf_engine_core_runtime.CONTINUOUS_RENDER_POLICY;
|
|
1963
2987
|
return {
|
|
@@ -1970,52 +2994,73 @@ var CloudDocumentHandle = class {
|
|
|
1970
2994
|
background: advertised.background,
|
|
1971
2995
|
enforced: advertised.enforced
|
|
1972
2996
|
};
|
|
1973
|
-
} };
|
|
1974
|
-
const hub = new
|
|
2997
|
+
}) };
|
|
2998
|
+
const hub = new _embedpdf_engine_core_runtime.EventHub();
|
|
1975
2999
|
this.hub = hub;
|
|
3000
|
+
const sessionId = http.sessionId ?? `cloud:anon:${id}`;
|
|
1976
3001
|
this.sessionId = sessionId;
|
|
3002
|
+
const subscribe = (listener) => {
|
|
3003
|
+
const unsubscribe = hub.subscribe(listener);
|
|
3004
|
+
this.retainRemoteStream();
|
|
3005
|
+
let released = false;
|
|
3006
|
+
return () => {
|
|
3007
|
+
unsubscribe();
|
|
3008
|
+
if (!released) {
|
|
3009
|
+
released = true;
|
|
3010
|
+
this.releaseRemoteStream();
|
|
3011
|
+
}
|
|
3012
|
+
};
|
|
3013
|
+
};
|
|
1977
3014
|
this.events = {
|
|
1978
|
-
subscribe
|
|
1979
|
-
|
|
1980
|
-
this.retainRemoteStream();
|
|
1981
|
-
let released = false;
|
|
1982
|
-
return () => {
|
|
1983
|
-
unsubscribe();
|
|
1984
|
-
if (!released) {
|
|
1985
|
-
released = true;
|
|
1986
|
-
this.releaseRemoteStream();
|
|
1987
|
-
}
|
|
1988
|
-
};
|
|
1989
|
-
},
|
|
3015
|
+
subscribe,
|
|
3016
|
+
on: (type, listener) => (0, _embedpdf_engine_core_runtime.subscribeToType)(subscribe, type, listener),
|
|
1990
3017
|
lastServerId: () => hub.lastServerId()
|
|
1991
3018
|
};
|
|
1992
|
-
this.publisher = new
|
|
3019
|
+
this.publisher = new _embedpdf_engine_core_runtime.SessionEventPublisher(hub, sessionId);
|
|
3020
|
+
hub.subscribe((event) => {
|
|
3021
|
+
if (event.type === "document.versioned") pool.versioned(event.version.sha256);
|
|
3022
|
+
});
|
|
1993
3023
|
this.manifestAccessor = {
|
|
1994
3024
|
get: (signal) => this.getManifest(signal),
|
|
1995
3025
|
refresh: (signal) => this.refreshManifest(signal),
|
|
1996
3026
|
apply: (meta, owns) => this.absorbMutation(meta, owns),
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
applyPageInsert: (cache) => this.absorbPageInsert(cache),
|
|
2000
|
-
applyMetadata: (cache) => this.absorbMetadata(cache),
|
|
2001
|
-
applyAttachments: (cache) => this.absorbAttachments(cache)
|
|
3027
|
+
applyPageDelete: (meta, deletedPages) => this.absorbPageDelete(meta, deletedPages),
|
|
3028
|
+
applyPageInsert: (meta) => this.absorbPageInsert(meta)
|
|
2002
3029
|
};
|
|
2003
|
-
this.
|
|
2004
|
-
this.
|
|
3030
|
+
this.changes = new CloudDocumentChanges(() => this.closed, this.manifestAccessor, this.publisher, this.writes);
|
|
3031
|
+
this.metadata = new CloudMetadataService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
|
|
3032
|
+
const importLimits = async () => (security.currentAccess ?? (await security.establishAccess()).access)?.bundleLimits ?? _embedpdf_engine_core_runtime.DEFAULT_BUNDLE_LIMITS;
|
|
3033
|
+
this.annotations = new CloudDocumentAnnotationsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, importLimits);
|
|
2005
3034
|
this.actions = new CloudDocumentActionsService(http, id, layerName, () => this.closed, this.manifestAccessor);
|
|
2006
|
-
this.
|
|
2007
|
-
this.
|
|
2008
|
-
this.
|
|
3035
|
+
this.outline = (0, _embedpdf_engine_core_runtime.outlineVerbs)(() => readCloudOutline(http, id, layerName, () => this.closed, this.manifestAccessor), (change, options) => this.apply(change, options));
|
|
3036
|
+
this.attachments = (0, _embedpdf_engine_core_runtime.attachmentVerbs)(new CloudDocumentAttachmentsService(http, id, layerName, () => this.closed, this.manifestAccessor), (change, options) => this.apply(change, options));
|
|
3037
|
+
this.signatures = new CloudDocumentSignaturesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
|
|
3038
|
+
this.forms = new CloudDocumentFormsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, importLimits);
|
|
2009
3039
|
this.search = new CloudDocumentSearchService(http, id, layerName, () => this.closed, this.manifestAccessor);
|
|
2010
|
-
this.
|
|
2011
|
-
this.
|
|
3040
|
+
this.pageReads = new CloudDocumentPagesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
|
|
3041
|
+
this.pages = (0, _embedpdf_engine_core_runtime.pageVerbs)(this.pageReads, (change, options) => this.apply(change, options));
|
|
3042
|
+
this.redaction = new CloudDocumentRedactionService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
|
|
3043
|
+
}
|
|
3044
|
+
page(ref) {
|
|
3045
|
+
return new CloudPageHandle(ref, this.http, this.id, this.layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, (signal) => this.pageReads.pageLayout(ref, signal), () => this.cloudSecurity.pageLayerRights(), () => this.cloudSecurity.pagePrintRights());
|
|
3046
|
+
}
|
|
3047
|
+
/**
|
|
3048
|
+
* This handle: the cloud engine sends every call as it's made, in
|
|
3049
|
+
* parallel, and the server runs them in arrival order, so a call's facts
|
|
3050
|
+
* change nothing here.
|
|
3051
|
+
*/
|
|
3052
|
+
with(_facts) {
|
|
3053
|
+
return this;
|
|
2012
3054
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
3055
|
+
/** One change as one transaction, or the undo of one (see `DocumentHandle.apply`). */
|
|
3056
|
+
apply(change, options) {
|
|
3057
|
+
return this.changes.apply(change, options);
|
|
2015
3058
|
}
|
|
2016
|
-
|
|
3059
|
+
/** Ignored: the server learns what's needed from what is asked of it. */
|
|
3060
|
+
setWorkingSet(_view, _pages) {}
|
|
3061
|
+
download(options = {}) {
|
|
2017
3062
|
if (this.closed) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.id} is closed`));
|
|
2018
|
-
const mode =
|
|
3063
|
+
const mode = options.mode ?? _embedpdf_engine_core_runtime.DEFAULT_PDF_SAVE_MODE;
|
|
2019
3064
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
2020
3065
|
const buildPath = async () => {
|
|
2021
3066
|
const manifest = await this.getManifest(signal);
|
|
@@ -2034,6 +3079,20 @@ var CloudDocumentHandle = class {
|
|
|
2034
3079
|
});
|
|
2035
3080
|
}
|
|
2036
3081
|
/**
|
|
3082
|
+
* Learn what the document's signatures forbid before the caller's first
|
|
3083
|
+
* question: the manifest carries it, so `open()` fetches it up front and
|
|
3084
|
+
* `security.allows()` answers as the server would from the start. A locked
|
|
3085
|
+
* document, which the server won't describe yet, learns it from `unlock()`.
|
|
3086
|
+
*/
|
|
3087
|
+
async learnProtection(signal) {
|
|
3088
|
+
try {
|
|
3089
|
+
await this.getManifest(signal);
|
|
3090
|
+
} catch (error) {
|
|
3091
|
+
if (_embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.DocPasswordRequired)) return;
|
|
3092
|
+
throw error;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
2037
3096
|
* Return the cached manifest, fetching cold-cache once if needed.
|
|
2038
3097
|
* Concurrent callers share a single inflight request (singleflight)
|
|
2039
3098
|
* so an N-page handle that opens N services in parallel still
|
|
@@ -2055,9 +3114,9 @@ var CloudDocumentHandle = class {
|
|
|
2055
3114
|
return awaitSignal(this.startManifestFetch({ allowInitialHead: false }), signal);
|
|
2056
3115
|
}
|
|
2057
3116
|
/**
|
|
2058
|
-
* Monotone plane-scope flip: mark the planes a mutation
|
|
3117
|
+
* Monotone plane-scope flip: mark the planes a mutation owns as layer-scoped in
|
|
2059
3118
|
* the cached manifest. Scopes only ever move base → layer (no revert op
|
|
2060
|
-
* exists), so flipping is safe under
|
|
3119
|
+
* exists), so flipping is safe under any event ordering — a duplicate or
|
|
2061
3120
|
* out-of-order event still proves the plane diverged at some point — and
|
|
2062
3121
|
* the manifest fetch stays the authoritative source (the 404 → refresh
|
|
2063
3122
|
* rail heals any miss, e.g. a future mutation kind this client doesn't
|
|
@@ -2088,17 +3147,10 @@ var CloudDocumentHandle = class {
|
|
|
2088
3147
|
return;
|
|
2089
3148
|
}
|
|
2090
3149
|
}
|
|
2091
|
-
const byPageObjectNumber = new Map(this.manifestCache.pages.map((page) => [page.
|
|
2092
|
-
for (const pageState of meta.affectedPages) {
|
|
2093
|
-
const existing = byPageObjectNumber.get(pageState.pageObjectNumber);
|
|
2094
|
-
if (existing) byPageObjectNumber.set(pageState.pageObjectNumber, {
|
|
2095
|
-
...existing,
|
|
2096
|
-
state: pageState
|
|
2097
|
-
});
|
|
2098
|
-
}
|
|
3150
|
+
const byPageObjectNumber = new Map(this.manifestCache.pages.map((page) => [page.page.objectNumber, page]));
|
|
2099
3151
|
if (delta) for (const page of delta.pages) {
|
|
2100
|
-
const existing = byPageObjectNumber.get(page.
|
|
2101
|
-
if (existing) byPageObjectNumber.set(page.
|
|
3152
|
+
const existing = byPageObjectNumber.get(page.page.objectNumber);
|
|
3153
|
+
if (existing) byPageObjectNumber.set(page.page.objectNumber, {
|
|
2102
3154
|
...existing,
|
|
2103
3155
|
cache: page.cache
|
|
2104
3156
|
});
|
|
@@ -2107,140 +3159,69 @@ var CloudDocumentHandle = class {
|
|
|
2107
3159
|
...this.manifestCache,
|
|
2108
3160
|
docVersion: delta?.docVersion ?? this.manifestCache.docVersion,
|
|
2109
3161
|
...delta?.annotationsVersion !== void 0 ? { annotationsVersion: delta.annotationsVersion } : {},
|
|
3162
|
+
...delta?.formsVersion !== void 0 ? { formsVersion: delta.formsVersion } : {},
|
|
3163
|
+
...delta?.layoutVersion !== void 0 ? { layoutVersion: delta.layoutVersion } : {},
|
|
3164
|
+
...delta?.metadataVersion !== void 0 ? { metadataVersion: delta.metadataVersion } : {},
|
|
3165
|
+
...delta?.attachmentsVersion !== void 0 ? { attachmentsVersion: delta.attachmentsVersion } : {},
|
|
3166
|
+
...delta?.outlineVersion !== void 0 ? { outlineVersion: delta.outlineVersion } : {},
|
|
2110
3167
|
...delta?.layerVersion !== void 0 ? { layerVersion: delta.layerVersion } : {},
|
|
2111
3168
|
...delta?.working !== void 0 ? { working: delta.working } : {},
|
|
2112
|
-
pages: Array.from(byPageObjectNumber.values()).sort((a, b) => a.
|
|
2113
|
-
};
|
|
2114
|
-
}
|
|
2115
|
-
/**
|
|
2116
|
-
* Patch the cached manifest after a set-preserving page-structure op (move,
|
|
2117
|
-
* rotate). Both are purely structural: they advance `docVersion` (so leaf
|
|
2118
|
-
* URLs re-resolve) and `layoutVersion` (so the /layout leaf re-fetches),
|
|
2119
|
-
* but leave every per-page pin untouched — a rotate renders the SAME
|
|
2120
|
-
* normalized bitmaps, a move the same pages. We raise the floor
|
|
2121
|
-
* unconditionally and, when our cache is exactly one version behind,
|
|
2122
|
-
* advance it in place; otherwise we drop it and refetch lazily.
|
|
2123
|
-
*/
|
|
2124
|
-
absorbPageStructure(cache) {
|
|
2125
|
-
this.flipScopes(["layout"]);
|
|
2126
|
-
this.manifestFloorVersion = Math.max(this.manifestFloorVersion, cache.docVersion);
|
|
2127
|
-
this.inflightManifest = null;
|
|
2128
|
-
if (!this.manifestCache) return;
|
|
2129
|
-
if (cache.docVersion <= this.manifestCache.docVersion) return;
|
|
2130
|
-
if (cache.previousDocVersion !== this.manifestCache.docVersion) {
|
|
2131
|
-
this.manifestCache = null;
|
|
2132
|
-
return;
|
|
2133
|
-
}
|
|
2134
|
-
this.manifestCache = {
|
|
2135
|
-
...this.manifestCache,
|
|
2136
|
-
docVersion: cache.docVersion,
|
|
2137
|
-
layoutVersion: cache.layoutVersion
|
|
3169
|
+
pages: Array.from(byPageObjectNumber.values()).sort((a, b) => a.page.objectNumber - b.page.objectNumber)
|
|
2138
3170
|
};
|
|
2139
3171
|
}
|
|
2140
3172
|
/**
|
|
2141
|
-
* Patch the cached manifest after a page delete: the shared
|
|
2142
|
-
*
|
|
2143
|
-
*
|
|
2144
|
-
* 404 anyway — this keeps the failure local and instant).
|
|
3173
|
+
* Patch the cached manifest after a page delete: the shared advance plus
|
|
3174
|
+
* dropping the deleted pages' manifest rows, so no leaf URL for a retired
|
|
3175
|
+
* page object number can be built from the cache (a stale request would
|
|
3176
|
+
* 404 anyway — this keeps the failure local and instant). Delete changes
|
|
3177
|
+
* the page set: a view that removed content must never resolve base
|
|
3178
|
+
* artifacts again, so content, annotations and forms flip with layout.
|
|
2145
3179
|
*/
|
|
2146
|
-
absorbPageDelete(
|
|
2147
|
-
this.
|
|
3180
|
+
absorbPageDelete(meta, deletedPages) {
|
|
3181
|
+
this.absorbMutation(meta, [
|
|
2148
3182
|
"layout",
|
|
2149
3183
|
"content",
|
|
2150
|
-
"annotations"
|
|
3184
|
+
"annotations",
|
|
3185
|
+
"forms"
|
|
2151
3186
|
]);
|
|
2152
|
-
this.manifestFloorVersion = Math.max(this.manifestFloorVersion, cache.docVersion);
|
|
2153
|
-
this.inflightManifest = null;
|
|
2154
3187
|
if (!this.manifestCache) return;
|
|
2155
|
-
|
|
2156
|
-
if (cache.previousDocVersion !== this.manifestCache.docVersion) {
|
|
2157
|
-
this.manifestCache = null;
|
|
2158
|
-
return;
|
|
2159
|
-
}
|
|
2160
|
-
const deleted = new Set(deletedPages);
|
|
3188
|
+
const deleted = new Set(deletedPages.map((page) => page.objectNumber));
|
|
2161
3189
|
this.manifestCache = {
|
|
2162
3190
|
...this.manifestCache,
|
|
2163
|
-
|
|
2164
|
-
layoutVersion: cache.layoutVersion,
|
|
2165
|
-
pages: this.manifestCache.pages.filter((page) => !deleted.has(page.state.pageObjectNumber))
|
|
3191
|
+
pages: this.manifestCache.pages.filter((page) => !deleted.has(page.page.objectNumber))
|
|
2166
3192
|
};
|
|
2167
3193
|
}
|
|
2168
3194
|
/**
|
|
2169
3195
|
* Patch bookkeeping after a page insert — the mirror of
|
|
2170
3196
|
* {@link absorbPageDelete}, with one asymmetry: delete can patch the
|
|
2171
|
-
* cached manifest losslessly (it only
|
|
2172
|
-
* manifest rows for the fresh
|
|
2173
|
-
* this absorb flips the planes (insert changes the page
|
|
2174
|
-
* delete it owns content
|
|
2175
|
-
* version floor, and
|
|
3197
|
+
* cached manifest losslessly (it only removes rows), but an insert needs
|
|
3198
|
+
* manifest rows for the fresh page object numbers and the result doesn't carry them. So
|
|
3199
|
+
* this absorb flips the planes (insert changes the page set, so like
|
|
3200
|
+
* delete it owns content, annotations and forms alongside layout), raises the
|
|
3201
|
+
* version floor, and drops the cache — the next read refetches a manifest
|
|
2176
3202
|
* that includes the new pages' rows. The UI never waits on that refetch:
|
|
2177
3203
|
* the mutation result / event already carries the full new layout.
|
|
2178
3204
|
*/
|
|
2179
|
-
absorbPageInsert(
|
|
3205
|
+
absorbPageInsert(meta) {
|
|
2180
3206
|
this.flipScopes([
|
|
2181
3207
|
"layout",
|
|
2182
3208
|
"content",
|
|
2183
|
-
"annotations"
|
|
3209
|
+
"annotations",
|
|
3210
|
+
"forms"
|
|
2184
3211
|
]);
|
|
2185
|
-
this.manifestFloorVersion = Math.max(this.manifestFloorVersion,
|
|
3212
|
+
if (meta.cacheDelta) this.manifestFloorVersion = Math.max(this.manifestFloorVersion, meta.cacheDelta.docVersion);
|
|
2186
3213
|
this.inflightManifest = null;
|
|
2187
3214
|
this.manifestCache = null;
|
|
2188
3215
|
}
|
|
2189
|
-
/**
|
|
2190
|
-
* Patch the cached manifest after a metadata write. Symmetric with
|
|
2191
|
-
* {@link absorbPageMove}: a metadata edit advances `docVersion` (so leaf
|
|
2192
|
-
* URLs re-resolve) and `metadataVersion` (so the /metadata leaf re-fetches),
|
|
2193
|
-
* but leaves `layoutVersion` and every per-page pin untouched. Raise the
|
|
2194
|
-
* floor unconditionally and, when our cache is exactly one version behind,
|
|
2195
|
-
* advance it in place; otherwise drop it and refetch lazily.
|
|
2196
|
-
*/
|
|
2197
|
-
absorbMetadata(cache) {
|
|
2198
|
-
this.flipScopes(["metadata"]);
|
|
2199
|
-
this.manifestFloorVersion = Math.max(this.manifestFloorVersion, cache.docVersion);
|
|
2200
|
-
this.inflightManifest = null;
|
|
2201
|
-
if (!this.manifestCache) return;
|
|
2202
|
-
if (cache.docVersion <= this.manifestCache.docVersion) return;
|
|
2203
|
-
if (cache.previousDocVersion !== this.manifestCache.docVersion) {
|
|
2204
|
-
this.manifestCache = null;
|
|
2205
|
-
return;
|
|
2206
|
-
}
|
|
2207
|
-
this.manifestCache = {
|
|
2208
|
-
...this.manifestCache,
|
|
2209
|
-
docVersion: cache.docVersion,
|
|
2210
|
-
metadataVersion: cache.metadataVersion
|
|
2211
|
-
};
|
|
2212
|
-
}
|
|
2213
|
-
/**
|
|
2214
|
-
* Patch the cached manifest after an attachment create/delete. Symmetric
|
|
2215
|
-
* with {@link absorbMetadata}: an attachment write advances `docVersion`
|
|
2216
|
-
* (so leaf URLs re-resolve) and `attachmentsVersion` (so the /attachments
|
|
2217
|
-
* listing and /attachment-files leaves re-fetch), but leaves
|
|
2218
|
-
* `layoutVersion` and every per-page pin untouched. Raise the floor
|
|
2219
|
-
* unconditionally and, when our cache is exactly one version behind,
|
|
2220
|
-
* advance it in place; otherwise drop it and refetch lazily.
|
|
2221
|
-
*/
|
|
2222
|
-
absorbAttachments(cache) {
|
|
2223
|
-
this.flipScopes(["attachments"]);
|
|
2224
|
-
this.manifestFloorVersion = Math.max(this.manifestFloorVersion, cache.docVersion);
|
|
2225
|
-
this.inflightManifest = null;
|
|
2226
|
-
if (!this.manifestCache) return;
|
|
2227
|
-
if (cache.docVersion <= this.manifestCache.docVersion) return;
|
|
2228
|
-
if (cache.previousDocVersion !== this.manifestCache.docVersion) {
|
|
2229
|
-
this.manifestCache = null;
|
|
2230
|
-
return;
|
|
2231
|
-
}
|
|
2232
|
-
this.manifestCache = {
|
|
2233
|
-
...this.manifestCache,
|
|
2234
|
-
docVersion: cache.docVersion,
|
|
2235
|
-
attachmentsVersion: cache.attachmentsVersion
|
|
2236
|
-
};
|
|
2237
|
-
}
|
|
2238
3216
|
startManifestFetch(opts) {
|
|
2239
3217
|
const ctrl = new AbortController();
|
|
2240
3218
|
const promise = this.fetchManifest(ctrl.signal, opts);
|
|
2241
3219
|
this.inflightManifest = promise;
|
|
2242
3220
|
promise.then((manifest) => {
|
|
2243
|
-
if (manifest.docVersion >= this.manifestFloorVersion && (!this.manifestCache || manifest.docVersion >= this.manifestCache.docVersion))
|
|
3221
|
+
if (manifest.docVersion >= this.manifestFloorVersion && (!this.manifestCache || manifest.docVersion >= this.manifestCache.docVersion)) {
|
|
3222
|
+
this.manifestCache = manifest;
|
|
3223
|
+
this.cloudSecurity.setProtection(manifest.protection);
|
|
3224
|
+
}
|
|
2244
3225
|
}).catch(() => void 0).finally(() => {
|
|
2245
3226
|
if (this.inflightManifest === promise) this.inflightManifest = null;
|
|
2246
3227
|
});
|
|
@@ -2272,6 +3253,7 @@ var CloudDocumentHandle = class {
|
|
|
2272
3253
|
this.sseClient = null;
|
|
2273
3254
|
this.manifestCache = null;
|
|
2274
3255
|
this.inflightManifest = null;
|
|
3256
|
+
this.onClose();
|
|
2275
3257
|
return _embedpdf_engine_core_runtime.AbortablePromise.resolveValue(void 0);
|
|
2276
3258
|
}
|
|
2277
3259
|
retainRemoteStream() {
|
|
@@ -2286,10 +3268,19 @@ var CloudDocumentHandle = class {
|
|
|
2286
3268
|
...this.manifestCache,
|
|
2287
3269
|
auditHead: row.id
|
|
2288
3270
|
};
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
3271
|
+
if (row.withheld) {
|
|
3272
|
+
const meta = row.payload?.meta;
|
|
3273
|
+
if (meta && row.originSessionId !== this.sessionId) this.absorbMutation(meta, ["annotations", "forms"]);
|
|
3274
|
+
return;
|
|
3275
|
+
}
|
|
3276
|
+
if (row.kind === "signature.complete") {
|
|
3277
|
+
const sha = row.payload.version?.sha256;
|
|
3278
|
+
if (typeof sha === "string") this.pool.versioned(sha);
|
|
3279
|
+
}
|
|
3280
|
+
for (const event of auditRowToEvents(row, this.sessionId)) {
|
|
3281
|
+
this.absorbRemoteEvent(event);
|
|
3282
|
+
this.hub.publish(event);
|
|
3283
|
+
}
|
|
2293
3284
|
},
|
|
2294
3285
|
onFullRefresh: () => {
|
|
2295
3286
|
this.manifestCache = null;
|
|
@@ -2302,7 +3293,9 @@ var CloudDocumentHandle = class {
|
|
|
2302
3293
|
},
|
|
2303
3294
|
onAuthLost: () => {
|
|
2304
3295
|
this.sseClient = null;
|
|
2305
|
-
}
|
|
3296
|
+
},
|
|
3297
|
+
onSession: (status) => this.pool.sync(status),
|
|
3298
|
+
onConnected: (connected) => this.pool.connected(connected)
|
|
2306
3299
|
});
|
|
2307
3300
|
this.sseClient.open();
|
|
2308
3301
|
}
|
|
@@ -2313,56 +3306,90 @@ var CloudDocumentHandle = class {
|
|
|
2313
3306
|
this.sseClient = null;
|
|
2314
3307
|
}
|
|
2315
3308
|
}
|
|
2316
|
-
/** Patch the cached manifest from a
|
|
3309
|
+
/** Patch the cached manifest from a remote event's coherence pins — the
|
|
2317
3310
|
* same absorb rails local mutations use, so reads stay warm no matter
|
|
2318
|
-
* whose hand caused the change.
|
|
3311
|
+
* whose hand caused the change. Every event kind is named below, so a new
|
|
3312
|
+
* one can't be forgotten: a missed one leaves this client reading the
|
|
3313
|
+
* old version from an immutable, cached URL. */
|
|
2319
3314
|
absorbRemoteEvent(event) {
|
|
2320
3315
|
switch (event.type) {
|
|
2321
|
-
case "
|
|
3316
|
+
case "pages.scaleSet":
|
|
2322
3317
|
this.absorbMutation(event.meta, []);
|
|
2323
3318
|
return;
|
|
2324
|
-
case "
|
|
2325
|
-
case "
|
|
2326
|
-
case "
|
|
2327
|
-
case "
|
|
3319
|
+
case "annotations.created":
|
|
3320
|
+
case "annotations.updated":
|
|
3321
|
+
case "annotations.deleted":
|
|
3322
|
+
case "annotations.restored":
|
|
3323
|
+
case "annotations.reordered":
|
|
2328
3324
|
this.absorbMutation(event.meta, ["annotations"]);
|
|
2329
3325
|
return;
|
|
2330
|
-
case "
|
|
3326
|
+
case "outline.created":
|
|
3327
|
+
case "outline.updated":
|
|
3328
|
+
case "outline.moved":
|
|
3329
|
+
case "outline.deleted":
|
|
3330
|
+
case "outline.restored":
|
|
3331
|
+
this.absorbMutation(event.meta, ["outline"]);
|
|
3332
|
+
return;
|
|
3333
|
+
case "pages.reordered":
|
|
2331
3334
|
case "pages.rotated":
|
|
2332
|
-
|
|
3335
|
+
case "pages.named":
|
|
3336
|
+
this.absorbMutation(event.meta, ["layout"]);
|
|
2333
3337
|
return;
|
|
2334
3338
|
case "pages.deleted":
|
|
2335
|
-
|
|
3339
|
+
this.absorbPageDelete(event.meta, event.pages);
|
|
2336
3340
|
return;
|
|
2337
3341
|
case "pages.inserted":
|
|
2338
|
-
|
|
3342
|
+
case "pages.restored":
|
|
3343
|
+
this.absorbPageInsert(event.meta);
|
|
2339
3344
|
return;
|
|
2340
3345
|
case "metadata.updated":
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
case "attachment.created":
|
|
2344
|
-
case "attachment.deleted":
|
|
2345
|
-
if (event.cache) this.absorbAttachments(event.cache);
|
|
3346
|
+
case "metadata.customUpdated":
|
|
3347
|
+
this.absorbMutation(event.meta, ["metadata"]);
|
|
2346
3348
|
return;
|
|
2347
|
-
case "
|
|
2348
|
-
case "
|
|
2349
|
-
case "
|
|
2350
|
-
|
|
2351
|
-
case "form.fieldUpdated":
|
|
2352
|
-
case "form.fieldDeleted":
|
|
2353
|
-
case "form.widgetAttached":
|
|
2354
|
-
case "form.widgetDetached":
|
|
2355
|
-
this.absorbMutation(event.meta, ["annotations"]);
|
|
3349
|
+
case "attachments.created":
|
|
3350
|
+
case "attachments.deleted":
|
|
3351
|
+
case "attachments.restored":
|
|
3352
|
+
this.absorbMutation(event.meta, ["attachments"]);
|
|
2356
3353
|
return;
|
|
2357
|
-
case "
|
|
2358
|
-
|
|
3354
|
+
case "forms.valueSet":
|
|
3355
|
+
case "forms.repaired":
|
|
3356
|
+
case "forms.created":
|
|
3357
|
+
case "forms.updated":
|
|
3358
|
+
case "forms.deleted":
|
|
3359
|
+
case "forms.restored":
|
|
3360
|
+
case "forms.widgetAdded":
|
|
3361
|
+
case "forms.widgetRemoved":
|
|
3362
|
+
case "forms.widgetUpdated":
|
|
3363
|
+
case "forms.widgetDeleted":
|
|
3364
|
+
case "forms.widgetRestored":
|
|
3365
|
+
case "forms.widgetsReordered":
|
|
3366
|
+
case "forms.calculationsReordered":
|
|
3367
|
+
this.absorbMutation(event.meta, ["forms"]);
|
|
2359
3368
|
return;
|
|
2360
3369
|
case "pages.flattened":
|
|
2361
|
-
|
|
3370
|
+
case "annotations.flattened":
|
|
3371
|
+
this.absorbMutation(event.meta, [
|
|
3372
|
+
"content",
|
|
3373
|
+
"annotations",
|
|
3374
|
+
"forms"
|
|
3375
|
+
]);
|
|
3376
|
+
return;
|
|
3377
|
+
case "signatures.prepared":
|
|
3378
|
+
case "document.versioned":
|
|
3379
|
+
this.manifestCache = null;
|
|
3380
|
+
this.inflightManifest = null;
|
|
2362
3381
|
return;
|
|
2363
3382
|
case "redaction.applied":
|
|
2364
|
-
|
|
3383
|
+
this.absorbMutation(event.meta, [
|
|
3384
|
+
"content",
|
|
3385
|
+
"annotations",
|
|
3386
|
+
"forms"
|
|
3387
|
+
]);
|
|
2365
3388
|
return;
|
|
3389
|
+
case "signatures.completed": return;
|
|
3390
|
+
case "signatures.cancelled": return;
|
|
3391
|
+
case "stream.desynced": return;
|
|
3392
|
+
default:
|
|
2366
3393
|
}
|
|
2367
3394
|
}
|
|
2368
3395
|
};
|
|
@@ -2392,22 +3419,6 @@ function fallbackUnknownHead(id) {
|
|
|
2392
3419
|
}
|
|
2393
3420
|
};
|
|
2394
3421
|
}
|
|
2395
|
-
function awaitSignal(promise, signal) {
|
|
2396
|
-
if (signal.aborted) return Promise.reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason));
|
|
2397
|
-
return new Promise((resolve, reject) => {
|
|
2398
|
-
const onAbort = () => {
|
|
2399
|
-
reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason));
|
|
2400
|
-
};
|
|
2401
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
2402
|
-
promise.then((value) => {
|
|
2403
|
-
signal.removeEventListener("abort", onAbort);
|
|
2404
|
-
resolve(value);
|
|
2405
|
-
}, (reason) => {
|
|
2406
|
-
signal.removeEventListener("abort", onAbort);
|
|
2407
|
-
reject(reason);
|
|
2408
|
-
});
|
|
2409
|
-
});
|
|
2410
|
-
}
|
|
2411
3422
|
//#endregion
|
|
2412
3423
|
//#region src/share.ts
|
|
2413
3424
|
/**
|
|
@@ -2491,8 +3502,11 @@ function shareSessionSource(baseUrl, shareToken, opts = {}) {
|
|
|
2491
3502
|
}
|
|
2492
3503
|
//#endregion
|
|
2493
3504
|
//#region src/transport/HttpClient.ts
|
|
3505
|
+
/** CloudPDF SaaS: where an engine connects when it names no server of its own. */
|
|
3506
|
+
const CLOUDPDF_SAAS_URL = "https://engine.cloudpdf.com";
|
|
2494
3507
|
var HttpClient = class HttpClient {
|
|
2495
|
-
constructor(
|
|
3508
|
+
constructor(options) {
|
|
3509
|
+
this.options = options;
|
|
2496
3510
|
_defineProperty(this, "base", void 0);
|
|
2497
3511
|
_defineProperty(this, "tokenFn", void 0);
|
|
2498
3512
|
_defineProperty(this, "fetchFn", void 0);
|
|
@@ -2500,13 +3514,13 @@ var HttpClient = class HttpClient {
|
|
|
2500
3514
|
_defineProperty(this, "onRetry", void 0);
|
|
2501
3515
|
_defineProperty(this, "sessionId", void 0);
|
|
2502
3516
|
_defineProperty(this, "cdnBinding", null);
|
|
2503
|
-
this.base =
|
|
2504
|
-
const token =
|
|
3517
|
+
this.base = (options.baseUrl ?? "https://engine.cloudpdf.com").replace(/\/$/, "");
|
|
3518
|
+
const token = options.token;
|
|
2505
3519
|
this.tokenFn = token === void 0 ? null : typeof token === "function" ? token : () => token;
|
|
2506
|
-
this.sessionId =
|
|
2507
|
-
this.fetchFn =
|
|
2508
|
-
this.docAffinityHeader =
|
|
2509
|
-
this.onRetry =
|
|
3520
|
+
this.sessionId = options.sessionId ?? null;
|
|
3521
|
+
this.fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
3522
|
+
this.docAffinityHeader = options.docAffinityHeader ?? true;
|
|
3523
|
+
this.onRetry = options.onRetry;
|
|
2510
3524
|
}
|
|
2511
3525
|
/** Normalized server base URL (no trailing slash). */
|
|
2512
3526
|
get baseUrl() {
|
|
@@ -2522,18 +3536,17 @@ var HttpClient = class HttpClient {
|
|
|
2522
3536
|
return this.fetchFn;
|
|
2523
3537
|
}
|
|
2524
3538
|
/**
|
|
2525
|
-
*
|
|
2526
|
-
*
|
|
2527
|
-
*
|
|
2528
|
-
*
|
|
2529
|
-
*
|
|
3539
|
+
* A client for one open document: this client's settings, the document's
|
|
3540
|
+
* own editing session, its own CDN binding, and `token` as its bearer
|
|
3541
|
+
* when the open names one (else this client's). Two open documents never
|
|
3542
|
+
* share object numbers, event echoes or a CDN binding, even two opens of
|
|
3543
|
+
* the same document on one engine.
|
|
2530
3544
|
*/
|
|
2531
|
-
|
|
3545
|
+
forDocument({ sessionId, token }) {
|
|
2532
3546
|
return new HttpClient({
|
|
2533
|
-
|
|
2534
|
-
token,
|
|
2535
|
-
|
|
2536
|
-
fetch: this.fetchFn
|
|
3547
|
+
...this.options,
|
|
3548
|
+
...token !== void 0 ? { token } : {},
|
|
3549
|
+
sessionId
|
|
2537
3550
|
});
|
|
2538
3551
|
}
|
|
2539
3552
|
/**
|
|
@@ -2548,20 +3561,20 @@ var HttpClient = class HttpClient {
|
|
|
2548
3561
|
...headers
|
|
2549
3562
|
}),
|
|
2550
3563
|
signal
|
|
2551
|
-
});
|
|
3564
|
+
}, { session: true });
|
|
2552
3565
|
}
|
|
2553
3566
|
/**
|
|
2554
3567
|
* Install (or clear) the per-session CDN binding. Pass `null` to
|
|
2555
3568
|
* route everything to origin again — useful on session close or
|
|
2556
3569
|
* when an adapter swap happens mid-session (rare).
|
|
2557
3570
|
*
|
|
2558
|
-
* The HttpClient stays dumb about
|
|
3571
|
+
* The HttpClient stays dumb about when to do this; the cloud
|
|
2559
3572
|
* document handle pushes the binding in after /access succeeds.
|
|
2560
3573
|
*
|
|
2561
3574
|
* Side effect: in the browser, signedCookies are written to
|
|
2562
3575
|
* `document.cookie` once per setCdnAccess call so the CDN edge
|
|
2563
|
-
* receives them on subsequent fetches.
|
|
2564
|
-
*
|
|
3576
|
+
* receives them on subsequent fetches. Outside the browser signed
|
|
3577
|
+
* cookies are ignored: this client keeps no cookie jar.
|
|
2565
3578
|
*/
|
|
2566
3579
|
setCdnAccess(binding) {
|
|
2567
3580
|
this.cdnBinding = binding;
|
|
@@ -2716,29 +3729,43 @@ var HttpClient = class HttpClient {
|
|
|
2716
3729
|
return await this.getFile(retryPath, signal);
|
|
2717
3730
|
}
|
|
2718
3731
|
}
|
|
2719
|
-
async postMultipartJson(path, body, parser, signal) {
|
|
3732
|
+
async postMultipartJson(path, body, parser, signal, options = {}) {
|
|
2720
3733
|
const res = await this.request(path, {
|
|
2721
3734
|
method: "POST",
|
|
2722
3735
|
body,
|
|
2723
3736
|
signal
|
|
2724
|
-
});
|
|
3737
|
+
}, options);
|
|
2725
3738
|
return await this.parseJsonResponse(res, parser);
|
|
2726
3739
|
}
|
|
2727
3740
|
/** PATCH counterpart of {@link postMultipartJson} (binary-carrying mutations). */
|
|
2728
|
-
async patchMultipartJson(path, body, parser, signal) {
|
|
3741
|
+
async patchMultipartJson(path, body, parser, signal, options = {}) {
|
|
2729
3742
|
const res = await this.request(path, {
|
|
2730
3743
|
method: "PATCH",
|
|
2731
3744
|
body,
|
|
2732
3745
|
signal
|
|
2733
|
-
});
|
|
3746
|
+
}, options);
|
|
2734
3747
|
return await this.parseJsonResponse(res, parser);
|
|
2735
3748
|
}
|
|
2736
|
-
async postJson(path, body, parser, signal) {
|
|
2737
|
-
const res = await this.requestJson(path, "POST", body, signal);
|
|
3749
|
+
async postJson(path, body, parser, signal, options = {}) {
|
|
3750
|
+
const res = await this.requestJson(path, "POST", body, signal, options);
|
|
2738
3751
|
return await this.parseJsonResponse(res, parser);
|
|
2739
3752
|
}
|
|
2740
|
-
async putJson(path, body, parser, signal) {
|
|
2741
|
-
return this.parseJsonResponse(await this.requestJson(path, "PUT", body, signal), parser);
|
|
3753
|
+
async putJson(path, body, parser, signal, options = {}) {
|
|
3754
|
+
return this.parseJsonResponse(await this.requestJson(path, "PUT", body, signal, options), parser);
|
|
3755
|
+
}
|
|
3756
|
+
/** POST a JSON body and parse a `multipart/form-data` response (a large annotation export). */
|
|
3757
|
+
async postJsonFormData(path, body, signal) {
|
|
3758
|
+
const res = await this.request(path, {
|
|
3759
|
+
method: "POST",
|
|
3760
|
+
body: JSON.stringify(body),
|
|
3761
|
+
headers: new Headers({
|
|
3762
|
+
"Content-Type": "application/json",
|
|
3763
|
+
Accept: "multipart/form-data"
|
|
3764
|
+
}),
|
|
3765
|
+
signal
|
|
3766
|
+
});
|
|
3767
|
+
if (!res.ok) await this.throwFromBody(res);
|
|
3768
|
+
return await res.formData();
|
|
2742
3769
|
}
|
|
2743
3770
|
/** POST a JSON body and return the raw binary response (pages.extract). */
|
|
2744
3771
|
async postJsonBytes(path, body, signal) {
|
|
@@ -2747,7 +3774,7 @@ var HttpClient = class HttpClient {
|
|
|
2747
3774
|
return new Uint8Array(await res.arrayBuffer());
|
|
2748
3775
|
}
|
|
2749
3776
|
/** POST a raw binary body (FDF/XFDF import) and parse the JSON response. */
|
|
2750
|
-
async postBytesJson(path, bytes, contentType, parser, signal) {
|
|
3777
|
+
async postBytesJson(path, bytes, contentType, parser, signal, options = {}) {
|
|
2751
3778
|
const headers = new Headers({ "Content-Type": contentType });
|
|
2752
3779
|
const body = new Uint8Array(bytes).buffer;
|
|
2753
3780
|
const res = await this.request(path, {
|
|
@@ -2755,61 +3782,73 @@ var HttpClient = class HttpClient {
|
|
|
2755
3782
|
body,
|
|
2756
3783
|
headers,
|
|
2757
3784
|
signal
|
|
2758
|
-
});
|
|
3785
|
+
}, options);
|
|
2759
3786
|
return await this.parseJsonResponse(res, parser);
|
|
2760
3787
|
}
|
|
2761
|
-
async patchJson(path, body, parser, signal) {
|
|
2762
|
-
const res = await this.requestJson(path, "PATCH", body, signal);
|
|
3788
|
+
async patchJson(path, body, parser, signal, options = {}) {
|
|
3789
|
+
const res = await this.requestJson(path, "PATCH", body, signal, options);
|
|
2763
3790
|
return await this.parseJsonResponse(res, parser);
|
|
2764
3791
|
}
|
|
2765
|
-
async deleteJson(path, parser, signal) {
|
|
3792
|
+
async deleteJson(path, parser, signal, options = {}) {
|
|
2766
3793
|
const res = await this.request(path, {
|
|
2767
3794
|
method: "DELETE",
|
|
2768
3795
|
signal
|
|
2769
|
-
});
|
|
3796
|
+
}, options);
|
|
2770
3797
|
return await this.parseJsonResponse(res, parser);
|
|
2771
3798
|
}
|
|
2772
|
-
|
|
2773
|
-
const res = await this.request(path, {
|
|
2774
|
-
method: "DELETE",
|
|
2775
|
-
signal
|
|
2776
|
-
});
|
|
2777
|
-
if (res.status === 204) return;
|
|
2778
|
-
if (!res.ok) await this.throwFromBody(res);
|
|
2779
|
-
}
|
|
2780
|
-
async postEmpty(path, body, signal) {
|
|
2781
|
-
const res = await this.requestJson(path, "POST", body, signal);
|
|
2782
|
-
if (res.status === 204) return;
|
|
2783
|
-
if (!res.ok) await this.throwFromBody(res);
|
|
2784
|
-
}
|
|
2785
|
-
requestJson(path, method, body, signal) {
|
|
3799
|
+
requestJson(path, method, body, signal, options = {}) {
|
|
2786
3800
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
2787
3801
|
return this.request(path, {
|
|
2788
3802
|
method,
|
|
2789
3803
|
body: JSON.stringify(body ?? {}),
|
|
2790
3804
|
headers,
|
|
2791
3805
|
signal
|
|
2792
|
-
});
|
|
3806
|
+
}, options);
|
|
2793
3807
|
}
|
|
2794
|
-
async request(path, init) {
|
|
2795
|
-
const { url, extraHeader, routedToCdn } = this.resolveOutgoing(path);
|
|
3808
|
+
async request(path, init, options = {}) {
|
|
3809
|
+
const { url, extraHeader, routedToCdn } = this.resolveOutgoing(path, init.method ?? "GET");
|
|
2796
3810
|
const headers = new Headers(init.headers ?? {});
|
|
2797
3811
|
if (this.tokenFn) {
|
|
2798
3812
|
const token = await this.tokenFn();
|
|
2799
3813
|
headers.set("Authorization", `Bearer ${token}`);
|
|
2800
3814
|
}
|
|
2801
3815
|
if (!headers.has("Accept")) headers.set("Accept", "application/json");
|
|
2802
|
-
|
|
3816
|
+
const { write } = options;
|
|
3817
|
+
if (this.sessionId && (write || options.session)) headers.set("X-Engine-Session-Id", this.sessionId);
|
|
3818
|
+
if (write) {
|
|
3819
|
+
if (write.opId !== void 0) headers.set("Idempotency-Key", write.opId);
|
|
3820
|
+
if (write.reserveObjectNumbers && write.reserveObjectNumbers > 0) headers.set("EmbedPDF-Reserve-Object-Numbers", String(write.reserveObjectNumbers));
|
|
3821
|
+
}
|
|
2803
3822
|
if (extraHeader) headers.set(extraHeader.name, extraHeader.value);
|
|
2804
3823
|
if (this.docAffinityHeader && !routedToCdn) {
|
|
2805
3824
|
const doc = /^\/v1\/docs\/([^/]+)\//.exec(path);
|
|
2806
3825
|
if (doc) headers.set("X-CloudPDF-Doc", decodeURIComponent(doc[1]));
|
|
2807
3826
|
}
|
|
2808
|
-
for (let attempt = 0;; attempt++) {
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
3827
|
+
for (let attempt = 0, networkFailures = 0;; attempt++) {
|
|
3828
|
+
let res;
|
|
3829
|
+
try {
|
|
3830
|
+
res = await this.execute(url, {
|
|
3831
|
+
...init,
|
|
3832
|
+
headers
|
|
3833
|
+
});
|
|
3834
|
+
} catch (err) {
|
|
3835
|
+
const wait = HttpClient.NETWORK_RETRY_MS[networkFailures];
|
|
3836
|
+
if (!write || wait === void 0 || !_embedpdf_engine_core_runtime.EngineError.is(err, _embedpdf_engine_core_runtime.EngineErrorCode.Network) || isStreamBody(init.body ?? null)) throw err;
|
|
3837
|
+
networkFailures++;
|
|
3838
|
+
const waitMs = wait * (.9 + .2 * Math.random());
|
|
3839
|
+
this.onRetry?.({
|
|
3840
|
+
path,
|
|
3841
|
+
code: _embedpdf_engine_core_runtime.EngineErrorCode.Network,
|
|
3842
|
+
attempt,
|
|
3843
|
+
waitMs
|
|
3844
|
+
});
|
|
3845
|
+
await sleepRacingAbort(waitMs, init.signal ?? null);
|
|
3846
|
+
continue;
|
|
3847
|
+
}
|
|
3848
|
+
if (res.ok && write?.onObjectNumbers) {
|
|
3849
|
+
const ranges = (0, _embedpdf_engine_core_runtime.parseObjectNumberRanges)(res.headers.get("EmbedPDF-Object-Numbers"));
|
|
3850
|
+
if (ranges.length > 0) write.onObjectNumbers(ranges);
|
|
3851
|
+
}
|
|
2813
3852
|
if (res.status !== 503 || attempt >= HttpClient.MAX_RETRIES || isStreamBody(init.body ?? null)) return res;
|
|
2814
3853
|
const code = await peekErrorCode(res);
|
|
2815
3854
|
if (code === null || !RETRYABLE_503.has(code)) return res;
|
|
@@ -2844,13 +3883,15 @@ var HttpClient = class HttpClient {
|
|
|
2844
3883
|
}
|
|
2845
3884
|
/**
|
|
2846
3885
|
* Decide where this request actually goes. With no CDN binding (or
|
|
2847
|
-
* binding's `cdn` is null) every request stays on origin
|
|
3886
|
+
* binding's `cdn` is null) every request stays on origin, and so does
|
|
3887
|
+
* every request that isn't a read (`GET`, `HEAD`): a write must reach the
|
|
3888
|
+
* server itself, whatever read its path looks like. Otherwise
|
|
2848
3889
|
* `applyCdnAccess` decides per-request based on the resource the
|
|
2849
3890
|
* path resolves to (and whether the caller's scope granted CDN
|
|
2850
3891
|
* access to it).
|
|
2851
3892
|
*/
|
|
2852
|
-
resolveOutgoing(path) {
|
|
2853
|
-
if (!this.cdnBinding || !this.cdnBinding.cdn) return {
|
|
3893
|
+
resolveOutgoing(path, method) {
|
|
3894
|
+
if (!(method === "GET" || method === "HEAD") || !this.cdnBinding || !this.cdnBinding.cdn) return {
|
|
2854
3895
|
url: `${this.baseUrl}${path}`,
|
|
2855
3896
|
extraHeader: null,
|
|
2856
3897
|
routedToCdn: false
|
|
@@ -2888,11 +3929,17 @@ var HttpClient = class HttpClient {
|
|
|
2888
3929
|
_defineProperty(HttpClient, "MAX_RETRIES", 2);
|
|
2889
3930
|
_defineProperty(HttpClient, "RETRY_AFTER_CAP_MS", 5e3);
|
|
2890
3931
|
_defineProperty(HttpClient, "RETRY_AFTER_DEFAULT_MS", 1e3);
|
|
3932
|
+
_defineProperty(HttpClient, "NETWORK_RETRY_MS", [
|
|
3933
|
+
250,
|
|
3934
|
+
1e3,
|
|
3935
|
+
3e3
|
|
3936
|
+
]);
|
|
2891
3937
|
function mapStatusToCode(status) {
|
|
2892
3938
|
if (status === 401) return _embedpdf_engine_core_runtime.EngineErrorCode.Unauthenticated;
|
|
2893
3939
|
if (status === 403) return _embedpdf_engine_core_runtime.EngineErrorCode.Forbidden;
|
|
2894
|
-
if (status === 409) return _embedpdf_engine_core_runtime.EngineErrorCode.
|
|
3940
|
+
if (status === 409) return _embedpdf_engine_core_runtime.EngineErrorCode.LayerVersionConflict;
|
|
2895
3941
|
if (status === 404) return _embedpdf_engine_core_runtime.EngineErrorCode.NotFound;
|
|
3942
|
+
if (status === 413) return _embedpdf_engine_core_runtime.EngineErrorCode.PayloadTooLarge;
|
|
2896
3943
|
if (status === 422) return _embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed;
|
|
2897
3944
|
if (status === 499) return _embedpdf_engine_core_runtime.EngineErrorCode.Aborted;
|
|
2898
3945
|
if (status === 400) return _embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg;
|
|
@@ -2941,22 +3988,19 @@ function sleepRacingAbort(ms, signal) {
|
|
|
2941
3988
|
*/
|
|
2942
3989
|
var CloudEngine = class CloudEngine {
|
|
2943
3990
|
static fromOptions(opts) {
|
|
2944
|
-
|
|
2945
|
-
return new CloudEngine(new HttpClient({
|
|
2946
|
-
...opts,
|
|
2947
|
-
sessionId
|
|
2948
|
-
}), sessionId);
|
|
3991
|
+
return new CloudEngine(new HttpClient(opts));
|
|
2949
3992
|
}
|
|
2950
|
-
constructor(http
|
|
3993
|
+
constructor(http) {
|
|
2951
3994
|
this.http = http;
|
|
2952
|
-
this
|
|
3995
|
+
_defineProperty(this, "canOpenFiles", false);
|
|
2953
3996
|
_defineProperty(this, "destroyed", false);
|
|
3997
|
+
_defineProperty(this, "handles", /* @__PURE__ */ new Set());
|
|
2954
3998
|
}
|
|
2955
3999
|
open(input, options) {
|
|
2956
4000
|
if (this.destroyed) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, "engine destroyed"));
|
|
2957
4001
|
options?.scope;
|
|
2958
4002
|
options?.identity;
|
|
2959
|
-
const effectivePassword = options
|
|
4003
|
+
const effectivePassword = options?.password ?? null;
|
|
2960
4004
|
if (input.kind === "share") {
|
|
2961
4005
|
const raw = shareSessionSource(this.http.baseUrl, input.shareToken, {
|
|
2962
4006
|
...input.sharePassword !== void 0 ? { password: input.sharePassword } : {},
|
|
@@ -2971,28 +4015,39 @@ var CloudEngine = class CloudEngine {
|
|
|
2971
4015
|
};
|
|
2972
4016
|
return this.open({
|
|
2973
4017
|
kind: "token",
|
|
2974
|
-
token
|
|
2975
|
-
password: input.password ?? null
|
|
4018
|
+
token
|
|
2976
4019
|
}, options);
|
|
2977
4020
|
}
|
|
2978
4021
|
if (input.kind === "token") {
|
|
2979
4022
|
const tokenSource = input.token;
|
|
2980
4023
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
2981
|
-
const docHttp = this.http.
|
|
4024
|
+
const docHttp = this.http.forDocument({
|
|
4025
|
+
sessionId: newSessionId(),
|
|
4026
|
+
token: tokenSource
|
|
4027
|
+
});
|
|
2982
4028
|
const token = await docHttp.currentToken();
|
|
2983
4029
|
const claims = decodeUnverifiedClaims(token);
|
|
2984
4030
|
const docId = claims.doc_id;
|
|
2985
4031
|
if (typeof docId !== "string" || docId.length === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "cloud engine: token has no doc_id claim — mint a doc-scoped JWT");
|
|
2986
4032
|
const layerName = typeof claims.layer_name === "string" && claims.layer_name.length > 0 ? claims.layer_name : _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME;
|
|
2987
4033
|
const head = await docHttp.getJson(_embedpdf_engine_core_wire.wirePaths.layerHead(docId, layerName), (raw) => _embedpdf_engine_core_wire.DocumentHeadSchema.parse(raw), signal);
|
|
2988
|
-
const handle = new CloudDocumentHandle(docHttp, head.id, layerName, head, token, this.
|
|
2989
|
-
|
|
4034
|
+
const handle = new CloudDocumentHandle(docHttp, head.id, layerName, head, token, () => this.handles.delete(handle));
|
|
4035
|
+
this.handles.add(handle);
|
|
4036
|
+
try {
|
|
4037
|
+
await establishOnOpen(handle, head, signal, effectivePassword, false);
|
|
4038
|
+
} catch (error) {
|
|
4039
|
+
await handle.close();
|
|
4040
|
+
throw error;
|
|
4041
|
+
}
|
|
2990
4042
|
return handle;
|
|
2991
4043
|
});
|
|
2992
4044
|
}
|
|
2993
4045
|
if (input.kind === "id") {
|
|
2994
4046
|
const id = input.id;
|
|
2995
|
-
const docHttp =
|
|
4047
|
+
const docHttp = this.http.forDocument({
|
|
4048
|
+
sessionId: newSessionId(),
|
|
4049
|
+
...input.token ? { token: input.token } : {}
|
|
4050
|
+
});
|
|
2996
4051
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
2997
4052
|
let layerName = input.layerName ?? _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME;
|
|
2998
4053
|
let resolvedToken = null;
|
|
@@ -3006,46 +4061,97 @@ var CloudEngine = class CloudEngine {
|
|
|
3006
4061
|
if (typeof claims.layer_name === "string" && claims.layer_name.length > 0) layerName = claims.layer_name;
|
|
3007
4062
|
}
|
|
3008
4063
|
const head = await docHttp.getJson(_embedpdf_engine_core_wire.wirePaths.layerHead(id, layerName), (raw) => _embedpdf_engine_core_wire.DocumentHeadSchema.parse(raw), signal);
|
|
3009
|
-
const handle = new CloudDocumentHandle(docHttp, head.id, layerName, head, resolvedToken, this.
|
|
3010
|
-
|
|
4064
|
+
const handle = new CloudDocumentHandle(docHttp, head.id, layerName, head, resolvedToken, () => this.handles.delete(handle));
|
|
4065
|
+
this.handles.add(handle);
|
|
4066
|
+
const tenant = resolvedToken !== null && typeof decodeUnverifiedClaims(resolvedToken).doc_id !== "string";
|
|
4067
|
+
try {
|
|
4068
|
+
await establishOnOpen(handle, head, signal, effectivePassword, tenant);
|
|
4069
|
+
} catch (error) {
|
|
4070
|
+
await handle.close();
|
|
4071
|
+
throw error;
|
|
4072
|
+
}
|
|
3011
4073
|
return handle;
|
|
3012
4074
|
});
|
|
3013
4075
|
}
|
|
3014
4076
|
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `cloud engine supports OpenInput.kind === 'token', 'id', or 'share' (got '${input.kind}')`));
|
|
3015
4077
|
}
|
|
4078
|
+
/**
|
|
4079
|
+
* Close every document this engine opened (their event streams stop), then
|
|
4080
|
+
* refuse new opens. As on the local engine, a handle is unusable after.
|
|
4081
|
+
*/
|
|
3016
4082
|
destroy() {
|
|
3017
4083
|
if (this.destroyed) return _embedpdf_engine_core_runtime.AbortablePromise.resolveValue(void 0);
|
|
3018
4084
|
this.destroyed = true;
|
|
3019
|
-
|
|
4085
|
+
const open = [...this.handles];
|
|
4086
|
+
this.handles.clear();
|
|
4087
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async () => {
|
|
4088
|
+
await Promise.all(open.map((handle) => handle.close()));
|
|
4089
|
+
});
|
|
3020
4090
|
}
|
|
3021
4091
|
};
|
|
3022
4092
|
/**
|
|
4093
|
+
* A session id for a document being opened. Every open is its own session,
|
|
4094
|
+
* even a second open of the same document on this engine. The id stamps the
|
|
4095
|
+
* document's own events' origins and travels as `X-Engine-Session-Id`: the
|
|
4096
|
+
* server keeps the session's object numbers under it and marks its audit
|
|
4097
|
+
* rows with it, so the document's stream drops its own echoes and hears
|
|
4098
|
+
* every other open as remote.
|
|
4099
|
+
*/
|
|
4100
|
+
function newSessionId() {
|
|
4101
|
+
return `cloud:${(0, _embedpdf_engine_core_runtime.generateUuid)()}`;
|
|
4102
|
+
}
|
|
4103
|
+
/**
|
|
4104
|
+
* What `open()` learns before handing the document over: access when the
|
|
4105
|
+
* password or a CDN needs it (see {@link maybeAutoEstablishAccess}), then
|
|
4106
|
+
* what the document's signatures forbid (the manifest). A caller that may
|
|
4107
|
+
* create objects calls `/access` anyway, beside the manifest fetch, for its
|
|
4108
|
+
* editing session's first object numbers; on a document locked by a
|
|
4109
|
+
* password, `unlock()` brings them. A failed `/access` doesn't fail the
|
|
4110
|
+
* open: creates then go without numbers until the pool gets some.
|
|
4111
|
+
*/
|
|
4112
|
+
async function establishOnOpen(handle, head, signal, password, tenant) {
|
|
4113
|
+
const accessed = await maybeAutoEstablishAccess(handle, head, signal, password);
|
|
4114
|
+
const security = handle.security;
|
|
4115
|
+
const locked = !accessed && head.access.reasons.includes("password");
|
|
4116
|
+
await Promise.all([!accessed && !locked && (tenant || mayCreateObjects(security)) ? settleLinked(security.establishAccess(), signal) : void 0, handle.learnProtection(signal)]);
|
|
4117
|
+
}
|
|
4118
|
+
/**
|
|
4119
|
+
* Whether the caller may make new objects (annotations as itself, pages,
|
|
4120
|
+
* form fields): the server's own test for handing out object numbers, from
|
|
4121
|
+
* what the client knows before `/access`.
|
|
4122
|
+
*/
|
|
4123
|
+
function mayCreateObjects(security) {
|
|
4124
|
+
return security.allowsAnnotation("create") || security.allows("doc.pages.assemble") || security.allows("doc.forms.modify");
|
|
4125
|
+
}
|
|
4126
|
+
/**
|
|
3023
4127
|
* Post-/head access establishment, in two layers:
|
|
3024
4128
|
*
|
|
3025
|
-
* 1. **Supplied password** — a password given at open is always
|
|
4129
|
+
* 1. **Supplied password** — a password given at open is always tried
|
|
3026
4130
|
* (PDFium parity: the local engine feeds it to the loader no matter
|
|
3027
4131
|
* what). Two triggers, because `/head` only carries the `password`
|
|
3028
|
-
* reason when a password is needed to
|
|
4132
|
+
* reason when a password is needed to read:
|
|
3029
4133
|
* - `reasons` has `'password'` → required-to-read case
|
|
3030
4134
|
* - `head.permissions.canUpgradeToOwner` → permission-only encrypted
|
|
3031
4135
|
* doc; the password can still upgrade to owner. Skipping this arm
|
|
3032
|
-
* would silently drop supplied
|
|
4136
|
+
* would silently drop supplied owner passwords — and diverge from
|
|
3033
4137
|
* the local engine on identical input.
|
|
3034
4138
|
* Outcomes follow "a password failure may only block what the password
|
|
3035
4139
|
* was needed for": a rejection in the required case leaves the handle
|
|
3036
|
-
* locked (the
|
|
4140
|
+
* locked (the prompt says `incorrect`, as on the local engine); in the
|
|
3037
4141
|
* upgrade case the document stays readable and the failure is
|
|
3038
4142
|
* non-fatal. The one /access POST also installs the CDN binding, so a
|
|
3039
4143
|
* successful unlock covers the `password + cdn` combined case.
|
|
3040
4144
|
*
|
|
3041
4145
|
* 2. **CDN-only** — unchanged: `/access` without a password, transparently.
|
|
3042
|
-
* A required password with
|
|
4146
|
+
* A required password with no supplied password never auto-calls; the
|
|
3043
4147
|
* dev prompts and `unlock()` runs the same POST.
|
|
3044
4148
|
*
|
|
3045
4149
|
* Non-password /access failures never break `open()` (the first real
|
|
3046
4150
|
* request still tries origin, where the JWT check produces a regular
|
|
3047
4151
|
* Forbidden) — but they are also never mistaken for a wrong password:
|
|
3048
4152
|
* only a DocPasswordRequired/Incorrect rejection means "wrong password".
|
|
4153
|
+
*
|
|
4154
|
+
* Returns whether `/access` answered.
|
|
3049
4155
|
*/
|
|
3050
4156
|
async function maybeAutoEstablishAccess(handle, head, signal, password) {
|
|
3051
4157
|
const reasons = new Set(head.access.reasons);
|
|
@@ -3054,13 +4160,13 @@ async function maybeAutoEstablishAccess(handle, head, signal, password) {
|
|
|
3054
4160
|
if (await settleLinked(security.unlock({
|
|
3055
4161
|
password,
|
|
3056
4162
|
mode: "any"
|
|
3057
|
-
}), signal)) return;
|
|
3058
|
-
if (reasons.has("password")) return;
|
|
4163
|
+
}), signal)) return true;
|
|
4164
|
+
if (reasons.has("password")) return false;
|
|
3059
4165
|
}
|
|
3060
|
-
if (!head.access.required) return;
|
|
3061
|
-
if (reasons.has("password")) return;
|
|
3062
|
-
if (!reasons.has("cdn")) return;
|
|
3063
|
-
|
|
4166
|
+
if (!head.access.required) return false;
|
|
4167
|
+
if (reasons.has("password")) return false;
|
|
4168
|
+
if (!reasons.has("cdn")) return false;
|
|
4169
|
+
return settleLinked(security.establishAccess(), signal);
|
|
3064
4170
|
}
|
|
3065
4171
|
/** Await an /access attempt with open()-abort linkage. Returns whether it
|
|
3066
4172
|
* succeeded; failures are contained (see maybeAutoEstablishAccess). */
|
|
@@ -3079,9 +4185,6 @@ async function settleLinked(pending, signal) {
|
|
|
3079
4185
|
}
|
|
3080
4186
|
//#endregion
|
|
3081
4187
|
//#region src/index.ts
|
|
3082
|
-
function createCloudEngine(opts) {
|
|
3083
|
-
return CloudEngine.fromOptions(opts);
|
|
3084
|
-
}
|
|
3085
4188
|
/**
|
|
3086
4189
|
* Create a cloud {@link CloudEngine} — the drop-in counterpart of
|
|
3087
4190
|
* `localEngine()`, so swapping local for cloud is a one-import change.
|
|
@@ -3093,59 +4196,46 @@ function createCloudEngine(opts) {
|
|
|
3093
4196
|
* to let a `<Viewer>` own the lifetime instead.
|
|
3094
4197
|
*
|
|
3095
4198
|
* Note the deliberate asymmetry with `localEngine()`: there is no `fonts`
|
|
3096
|
-
* option. Fallback fonts are a
|
|
4199
|
+
* option. Fallback fonts are a server policy on the cloud (`Engine.fonts` is
|
|
3097
4200
|
* `undefined` cloud-side), so they cannot be configured from the client. This
|
|
3098
4201
|
* is the local-vs-cloud split, surfaced in the API.
|
|
3099
4202
|
*
|
|
4203
|
+
* Without a `baseUrl` it connects to CloudPDF SaaS; your own server names itself:
|
|
4204
|
+
*
|
|
3100
4205
|
* ```ts
|
|
3101
|
-
* const engine = cloudEngine(
|
|
4206
|
+
* const engine = cloudEngine(); // CloudPDF SaaS
|
|
4207
|
+
* const own = cloudEngine({ baseUrl: 'https://pdf.example.com' }); // your @cloudpdf/server
|
|
3102
4208
|
* <Viewer engine={engine} plugins={[stagePlugin(), renderPlugin()]} />
|
|
3103
4209
|
* ```
|
|
3104
4210
|
*/
|
|
3105
|
-
function cloudEngine(opts) {
|
|
4211
|
+
function cloudEngine(opts = {}) {
|
|
3106
4212
|
return CloudEngine.fromOptions(opts);
|
|
3107
4213
|
}
|
|
3108
4214
|
//#endregion
|
|
3109
|
-
|
|
3110
|
-
enumerable: true,
|
|
3111
|
-
get: function() {
|
|
3112
|
-
return _embedpdf_engine_core_runtime.AbortError;
|
|
3113
|
-
}
|
|
3114
|
-
});
|
|
3115
|
-
Object.defineProperty(exports, "AbortablePromise", {
|
|
3116
|
-
enumerable: true,
|
|
3117
|
-
get: function() {
|
|
3118
|
-
return _embedpdf_engine_core_runtime.AbortablePromise;
|
|
3119
|
-
}
|
|
3120
|
-
});
|
|
4215
|
+
exports.CLOUDPDF_SAAS_URL = CLOUDPDF_SAAS_URL;
|
|
3121
4216
|
exports.CloudDocumentAnnotationsService = CloudDocumentAnnotationsService;
|
|
3122
4217
|
exports.CloudDocumentHandle = CloudDocumentHandle;
|
|
3123
4218
|
exports.CloudDocumentPagesService = CloudDocumentPagesService;
|
|
3124
4219
|
exports.CloudEngine = CloudEngine;
|
|
3125
4220
|
exports.CloudMetadataService = CloudMetadataService;
|
|
3126
4221
|
exports.CloudPageAnnotationsService = CloudPageAnnotationsService;
|
|
3127
|
-
exports.CloudPageGeometryService = CloudPageGeometryService;
|
|
3128
4222
|
exports.CloudPageHandle = CloudPageHandle;
|
|
3129
4223
|
exports.CloudPageRenderService = CloudPageRenderService;
|
|
3130
|
-
Object.defineProperty(exports, "EngineError", {
|
|
3131
|
-
enumerable: true,
|
|
3132
|
-
get: function() {
|
|
3133
|
-
return _embedpdf_engine_core_runtime.EngineError;
|
|
3134
|
-
}
|
|
3135
|
-
});
|
|
3136
|
-
Object.defineProperty(exports, "EngineErrorCode", {
|
|
3137
|
-
enumerable: true,
|
|
3138
|
-
get: function() {
|
|
3139
|
-
return _embedpdf_engine_core_runtime.EngineErrorCode;
|
|
3140
|
-
}
|
|
3141
|
-
});
|
|
3142
4224
|
exports.HttpClient = HttpClient;
|
|
3143
4225
|
exports.ShareExchangeError = ShareExchangeError;
|
|
3144
4226
|
exports.cloudEngine = cloudEngine;
|
|
3145
|
-
exports.createCloudEngine = createCloudEngine;
|
|
3146
4227
|
exports.decodeUnverifiedClaims = decodeUnverifiedClaims;
|
|
3147
4228
|
exports.engineErrorFromShareExchange = engineErrorFromShareExchange;
|
|
3148
4229
|
exports.exchangeShareToken = exchangeShareToken;
|
|
3149
4230
|
exports.shareSessionSource = shareSessionSource;
|
|
4231
|
+
var _embedpdf_engine_core_public = require("@embedpdf/engine-core/public");
|
|
4232
|
+
Object.keys(_embedpdf_engine_core_public).forEach(function(k) {
|
|
4233
|
+
if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
4234
|
+
enumerable: true,
|
|
4235
|
+
get: function() {
|
|
4236
|
+
return _embedpdf_engine_core_public[k];
|
|
4237
|
+
}
|
|
4238
|
+
});
|
|
4239
|
+
});
|
|
3150
4240
|
|
|
3151
4241
|
//# sourceMappingURL=index.cjs.map
|