@cloudpdf/engine 3.0.0-next.14 → 3.0.0-next.15
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 +2304 -1091
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +524 -249
- package/dist/index.d.ts +524 -249
- package/dist/index.js +2430 -1200
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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,90 +134,120 @@ 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
244
|
//#region src/document/buildMutationForm.ts
|
|
196
245
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* the document-level `attachments.create` — one envelope, one parser on
|
|
203
|
-
* the server side.
|
|
246
|
+
* The multipart envelope of a write that carries files, in the shape every
|
|
247
|
+
* multipart message has: the `body` part holds the JSON the plain request
|
|
248
|
+
* would have been, which names each file by role
|
|
249
|
+
* (`resources: { <role>: <key> }`), and each file is the part
|
|
250
|
+
* `resource:<key>`.
|
|
204
251
|
*/
|
|
205
252
|
function buildMutationForm(body, resources) {
|
|
206
253
|
const form = new FormData();
|
|
@@ -208,21 +255,46 @@ function buildMutationForm(body, resources) {
|
|
|
208
255
|
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
256
|
return form;
|
|
210
257
|
}
|
|
211
|
-
|
|
212
|
-
|
|
258
|
+
/**
|
|
259
|
+
* The envelope of a write with at most one file per role (an annotation's
|
|
260
|
+
* drawing or attached file, a page insert's source PDF, a signature's
|
|
261
|
+
* mark): each file's key is its role's name, and the body names it so,
|
|
262
|
+
* `resources: { <role>: '<role>' }`.
|
|
263
|
+
*/
|
|
264
|
+
function buildRoleMutationForm(body, files) {
|
|
265
|
+
const parts = {};
|
|
266
|
+
const resources = {};
|
|
267
|
+
for (const [role, file] of Object.entries(files)) {
|
|
268
|
+
if (!file) continue;
|
|
269
|
+
parts[role] = file;
|
|
270
|
+
resources[role] = role;
|
|
271
|
+
}
|
|
272
|
+
return buildMutationForm(Object.keys(resources).length > 0 ? {
|
|
273
|
+
...body,
|
|
274
|
+
resources
|
|
275
|
+
} : body, parts);
|
|
276
|
+
}
|
|
277
|
+
/** An annotation write's envelope: its drawing and attached file, by role. */
|
|
278
|
+
function buildAnnotationMutationForm(body, resources) {
|
|
279
|
+
return buildRoleMutationForm(body, {
|
|
280
|
+
appearance: resources.appearance && { bytes: resources.appearance },
|
|
281
|
+
file: resources.file && { bytes: resources.file }
|
|
282
|
+
});
|
|
213
283
|
}
|
|
214
284
|
//#endregion
|
|
215
285
|
//#region src/document/parseAttachmentContent.ts
|
|
216
286
|
/** Header carrying the file's name, token-text encoded (names are
|
|
217
287
|
* arbitrary UTF-8; HTTP header values are not). */
|
|
218
288
|
const FILE_NAME_HEADER = "X-EmbedPDF-File-Name";
|
|
289
|
+
/** Header carrying the file's declared type; absent when it has none. */
|
|
290
|
+
const FILE_TYPE_HEADER = "X-EmbedPDF-File-Type";
|
|
219
291
|
/**
|
|
220
292
|
* Project an attachment-file response into `AttachmentContent`. The body
|
|
221
|
-
*
|
|
222
|
-
* for the declared
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
293
|
+
* is the decoded bytes; the metadata rides as headers — `X-EmbedPDF-File-Type`
|
|
294
|
+
* for the declared type (`Content-Type` is only what HTTP needs), and
|
|
295
|
+
* `X-EmbedPDF-File-Name` for the file name.
|
|
296
|
+
* Used by the document-level `attachments.download()`; the annotation-level
|
|
297
|
+
* `annotations.downloadResource(ref, 'file')` reads the same response's bytes.
|
|
226
298
|
*/
|
|
227
299
|
function parseAttachmentContent(file) {
|
|
228
300
|
const encodedName = file.headers.get(FILE_NAME_HEADER);
|
|
@@ -233,17 +305,15 @@ function parseAttachmentContent(file) {
|
|
|
233
305
|
} catch (err) {
|
|
234
306
|
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `malformed ${FILE_NAME_HEADER} header: ${err?.message ?? err}`, { cause: err });
|
|
235
307
|
}
|
|
236
|
-
const mimeType = file.headers.get("Content-Type");
|
|
237
308
|
return {
|
|
238
309
|
bytes: file.bytes,
|
|
239
310
|
name,
|
|
240
|
-
|
|
311
|
+
mimeType: file.headers.get(FILE_TYPE_HEADER)
|
|
241
312
|
};
|
|
242
313
|
}
|
|
243
314
|
//#endregion
|
|
244
315
|
//#region src/document/CloudDocumentAttachmentsService.ts
|
|
245
316
|
/** The `/attachments@…` listing leaf is a bare array of name-tree entries. */
|
|
246
|
-
const EmbeddedFileListSchema = _embedpdf_engine_core_wire.EmbeddedFileItemSchema.array();
|
|
247
317
|
/**
|
|
248
318
|
* Cloud-side document-level attachments (the catalog's `/EmbeddedFiles`
|
|
249
319
|
* name tree). Reads use immutable leaves pinned by the manifest's
|
|
@@ -254,13 +324,14 @@ const EmbeddedFileListSchema = _embedpdf_engine_core_wire.EmbeddedFileItemSchema
|
|
|
254
324
|
* never cached.
|
|
255
325
|
*/
|
|
256
326
|
var CloudDocumentAttachmentsService = class {
|
|
257
|
-
constructor(http, docId, layerName, isClosed, manifest, publisher) {
|
|
327
|
+
constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
|
|
258
328
|
this.http = http;
|
|
259
329
|
this.docId = docId;
|
|
260
330
|
this.layerName = layerName;
|
|
261
331
|
this.isClosed = isClosed;
|
|
262
332
|
this.manifest = manifest;
|
|
263
333
|
this.publisher = publisher;
|
|
334
|
+
this.writes = writes;
|
|
264
335
|
}
|
|
265
336
|
/**
|
|
266
337
|
* Snapshot of the name tree, in tree (key-sorted) order. Pulls
|
|
@@ -276,7 +347,7 @@ var CloudDocumentAttachmentsService = class {
|
|
|
276
347
|
const manifest = await this.manifest.get(s);
|
|
277
348
|
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
349
|
};
|
|
279
|
-
return this.http.getJsonWithRefresh(buildPath, (raw) =>
|
|
350
|
+
return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.AttachmentListSchema.parse(raw), async (s) => {
|
|
280
351
|
await this.manifest.refresh(s);
|
|
281
352
|
}, signal);
|
|
282
353
|
});
|
|
@@ -300,195 +371,585 @@ var CloudDocumentAttachmentsService = class {
|
|
|
300
371
|
});
|
|
301
372
|
}
|
|
302
373
|
/**
|
|
303
|
-
* Create an embedded file in the name tree
|
|
304
|
-
* file
|
|
305
|
-
*
|
|
306
|
-
* standard multipart mutation envelope.
|
|
374
|
+
* Create an embedded file in the name tree: metadata into the `body`
|
|
375
|
+
* part, naming its file `resources: { file: 'file' }`, and the bytes in
|
|
376
|
+
* the part `resource:file`.
|
|
307
377
|
*/
|
|
308
|
-
create(file) {
|
|
378
|
+
create(file, options) {
|
|
309
379
|
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`));
|
|
310
380
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
381
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
382
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
383
|
+
const { wireFile, resource } = await (0, _embedpdf_engine_core_runtime.normalizeAttachmentFileSource)(file, "file");
|
|
384
|
+
const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerAttachmentsCollection(this.docId, this.layerName), buildMutationForm(wireFile, { file: resource }), (raw) => _embedpdf_engine_core_wire.AttachmentCreateResultSchema.parse(raw), signal, sent));
|
|
385
|
+
this.manifest.apply(result.meta, ["attachments"]);
|
|
386
|
+
this.publisher.publishWrite(opId, {
|
|
387
|
+
type: "attachments.created",
|
|
388
|
+
...result
|
|
389
|
+
});
|
|
390
|
+
return result;
|
|
317
391
|
});
|
|
318
|
-
return result;
|
|
319
392
|
});
|
|
320
393
|
}
|
|
321
394
|
/** Delete an embedded file from the name tree by its durable key. */
|
|
322
|
-
delete(ref) {
|
|
395
|
+
delete(ref, options) {
|
|
323
396
|
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`));
|
|
324
397
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
398
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
399
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
400
|
+
const result = await write.send((sent) => this.http.deleteJson(_embedpdf_engine_core_wire.wirePaths.layerAttachmentItem(this.docId, this.layerName, ref.key), (raw) => _embedpdf_engine_core_wire.AttachmentDeleteResultSchema.parse(raw), signal, sent));
|
|
401
|
+
this.manifest.apply(result.meta, ["attachments"]);
|
|
402
|
+
this.publisher.publishWrite(opId, {
|
|
403
|
+
type: "attachments.deleted",
|
|
404
|
+
deleted: (0, _embedpdf_engine_core_runtime.deletedAttachmentOf)(result),
|
|
405
|
+
...result
|
|
406
|
+
});
|
|
407
|
+
return result;
|
|
330
408
|
});
|
|
331
|
-
return result;
|
|
332
409
|
});
|
|
333
410
|
}
|
|
334
411
|
};
|
|
335
412
|
//#endregion
|
|
336
|
-
//#region
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
413
|
+
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/typeof.js
|
|
414
|
+
function _typeof(o) {
|
|
415
|
+
"@babel/helpers - typeof";
|
|
416
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
417
|
+
return typeof o;
|
|
418
|
+
} : function(o) {
|
|
419
|
+
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
420
|
+
}, _typeof(o);
|
|
421
|
+
}
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPrimitive.js
|
|
424
|
+
function toPrimitive(t, r) {
|
|
425
|
+
if ("object" != _typeof(t) || !t) return t;
|
|
426
|
+
var e = t[Symbol.toPrimitive];
|
|
427
|
+
if (void 0 !== e) {
|
|
428
|
+
var i = e.call(t, r || "default");
|
|
429
|
+
if ("object" != _typeof(i)) return i;
|
|
430
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
431
|
+
}
|
|
432
|
+
return ("string" === r ? String : Number)(t);
|
|
433
|
+
}
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPropertyKey.js
|
|
436
|
+
function toPropertyKey(t) {
|
|
437
|
+
var i = toPrimitive(t, "string");
|
|
438
|
+
return "symbol" == _typeof(i) ? i : i + "";
|
|
439
|
+
}
|
|
440
|
+
//#endregion
|
|
441
|
+
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/defineProperty.js
|
|
442
|
+
function _defineProperty(e, r, t) {
|
|
443
|
+
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
444
|
+
value: t,
|
|
445
|
+
enumerable: !0,
|
|
446
|
+
configurable: !0,
|
|
447
|
+
writable: !0
|
|
448
|
+
}) : e[r] = t, e;
|
|
449
|
+
}
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/document/CloudDocumentChanges.ts
|
|
452
|
+
/** How many `opId`s a handle remembers having published: far more than any retry spans. */
|
|
453
|
+
const PUBLISHED_MEMORY = 4096;
|
|
454
|
+
/**
|
|
455
|
+
* `doc.apply` on the cloud: each change takes its place in the document's
|
|
456
|
+
* line of writes, and goes out with the changes called right after it, as
|
|
457
|
+
* one `POST …/changes` (see {@link CloudWrites.change}). The server checks
|
|
458
|
+
* every op against the caller inside the write, so nothing about the ops is
|
|
459
|
+
* checked here beyond what the request must hold.
|
|
460
|
+
*
|
|
461
|
+
* A change's events are published once its answer arrives, all sharing its
|
|
462
|
+
* `opId`. A change asked again gets the first answer and publishes nothing:
|
|
463
|
+
* this handle remembers the `opId`s it published.
|
|
464
|
+
*/
|
|
465
|
+
var CloudDocumentChanges = class {
|
|
466
|
+
constructor(isClosed, manifest, publisher, writes) {
|
|
467
|
+
this.isClosed = isClosed;
|
|
468
|
+
this.manifest = manifest;
|
|
469
|
+
this.publisher = publisher;
|
|
470
|
+
this.writes = writes;
|
|
471
|
+
_defineProperty(this, "published", /* @__PURE__ */ new Set());
|
|
472
|
+
}
|
|
473
|
+
apply(change, options) {
|
|
474
|
+
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"));
|
|
475
|
+
let opId;
|
|
476
|
+
try {
|
|
477
|
+
opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
478
|
+
} catch (err) {
|
|
479
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
|
|
480
|
+
}
|
|
481
|
+
if (!(0, _embedpdf_engine_core_runtime.isUndoChange)(change) && change.ops.length === 0) return _embedpdf_engine_core_runtime.AbortablePromise.resolveValue({
|
|
482
|
+
items: [],
|
|
483
|
+
meta: {
|
|
484
|
+
affectedPages: [],
|
|
485
|
+
cacheDelta: null,
|
|
486
|
+
opId,
|
|
487
|
+
undoable: false
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
491
|
+
const answer = await this.writes.change(signal, sentChangeOf(opId, change), {
|
|
492
|
+
named: (0, _embedpdf_engine_core_runtime.objectNumbersNamedBy)(change),
|
|
493
|
+
alone: carriesBytes(change)
|
|
494
|
+
});
|
|
495
|
+
if (answer.status === "refused") throw (0, _embedpdf_engine_core_runtime.deserializeError)(answer.error);
|
|
496
|
+
this.absorb(opId, change, answer.result);
|
|
497
|
+
return answer.result;
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Patch the cached manifest, then publish the change's events (in that
|
|
502
|
+
* order: listeners reading the manifest see the change), unless this
|
|
503
|
+
* handle published them already.
|
|
504
|
+
*/
|
|
505
|
+
absorb(opId, change, result) {
|
|
506
|
+
this.manifest.apply(result.meta, planesOf(result));
|
|
507
|
+
if (this.published.has(opId)) return;
|
|
508
|
+
this.published.add(opId);
|
|
509
|
+
if (this.published.size > PUBLISHED_MEMORY) this.published.delete(this.published.values().next().value);
|
|
510
|
+
const undoOf = (0, _embedpdf_engine_core_runtime.isUndoChange)(change) ? change.undoOf : void 0;
|
|
511
|
+
this.publisher.publishChange(opId, (0, _embedpdf_engine_core_runtime.changeEvents)(result), undoOf);
|
|
512
|
+
}
|
|
342
513
|
};
|
|
343
514
|
/**
|
|
515
|
+
* The sender of a document's requests of changes: JSON, or multipart when
|
|
516
|
+
* any change carries bytes, each op naming its parts by key.
|
|
517
|
+
*/
|
|
518
|
+
function changeSender(http, docId, layerName) {
|
|
519
|
+
const path = _embedpdf_engine_core_wire.wirePaths.layerChanges(docId, layerName);
|
|
520
|
+
const parse = (raw) => _embedpdf_engine_core_wire.ChangeResponseSchema.parse(raw).changes;
|
|
521
|
+
return (changes, options) => {
|
|
522
|
+
const { body, parts } = changeRequestOf(changes);
|
|
523
|
+
const signal = new AbortController().signal;
|
|
524
|
+
return Object.keys(parts).length > 0 ? http.postMultipartJson(path, buildMutationForm(body, parts), parse, signal, options) : http.postJson(path, body, parse, signal, options);
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* A change ready to send: its bytes read, and checked against what a request
|
|
529
|
+
* may hold, so a malformed change is refused alone rather than refusing the
|
|
530
|
+
* request it would share.
|
|
531
|
+
*/
|
|
532
|
+
async function sentChangeOf(opId, change) {
|
|
533
|
+
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: {
|
|
534
|
+
limit: "ops",
|
|
535
|
+
max: _embedpdf_engine_core_wire.CHANGE_REQUEST_LIMITS.ops,
|
|
536
|
+
value: change.ops.length
|
|
537
|
+
} });
|
|
538
|
+
const sent = {
|
|
539
|
+
opId,
|
|
540
|
+
change: (await (0, _embedpdf_engine_core_runtime.resolveChangeResources)(change)).change
|
|
541
|
+
};
|
|
542
|
+
const { body } = changeRequestOf([sent]);
|
|
543
|
+
const checked = _embedpdf_engine_core_wire.ChangeRequestSchema.safeParse(JSON.parse(JSON.stringify(body)));
|
|
544
|
+
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 } });
|
|
545
|
+
return sent;
|
|
546
|
+
}
|
|
547
|
+
/** A request's JSON and its parts: each op's bytes become a part the op names by key. */
|
|
548
|
+
function changeRequestOf(changes) {
|
|
549
|
+
const parts = {};
|
|
550
|
+
const part = (bytes) => {
|
|
551
|
+
const key = `r${Object.keys(parts).length}`;
|
|
552
|
+
parts[key] = { bytes };
|
|
553
|
+
return key;
|
|
554
|
+
};
|
|
555
|
+
const opOf = (op) => {
|
|
556
|
+
switch (op.type) {
|
|
557
|
+
case "annotations.create":
|
|
558
|
+
case "annotations.update": {
|
|
559
|
+
if (!op.resources) return op;
|
|
560
|
+
const { appearance, file } = op.resources;
|
|
561
|
+
return {
|
|
562
|
+
...op,
|
|
563
|
+
resources: {
|
|
564
|
+
...appearance ? { appearance: part(appearance) } : {},
|
|
565
|
+
...file ? { file: part(file) } : {}
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
case "forms.setSignatureAppearance": {
|
|
570
|
+
const { appearance, ...rest } = op;
|
|
571
|
+
return {
|
|
572
|
+
...rest,
|
|
573
|
+
resources: { appearance: part(appearance.pdf.slice().buffer) }
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
default: return op;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
return {
|
|
580
|
+
body: { changes: changes.map(({ opId, change }) => (0, _embedpdf_engine_core_runtime.isUndoChange)(change) ? {
|
|
581
|
+
opId,
|
|
582
|
+
undoOf: change.undoOf
|
|
583
|
+
} : {
|
|
584
|
+
opId,
|
|
585
|
+
ops: change.ops.map(opOf)
|
|
586
|
+
}) },
|
|
587
|
+
parts
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
/** Whether a change carries bytes: a drawing, an attached file, a signature's artwork. */
|
|
591
|
+
function carriesBytes(change) {
|
|
592
|
+
if ((0, _embedpdf_engine_core_runtime.isUndoChange)(change)) return false;
|
|
593
|
+
return change.ops.some((op) => op.type === "forms.setSignatureAppearance" || (op.type === "annotations.create" || op.type === "annotations.update") && op.resources !== void 0 && Object.values(op.resources).some((resource) => resource !== void 0));
|
|
594
|
+
}
|
|
595
|
+
/** The planes a change's writes took over from the base, each op by its family. */
|
|
596
|
+
function planesOf(result) {
|
|
597
|
+
const planes = /* @__PURE__ */ new Set();
|
|
598
|
+
for (const item of result.items) {
|
|
599
|
+
if ((0, _embedpdf_engine_core_runtime.isSkippedItem)(item)) continue;
|
|
600
|
+
if (item.type.startsWith("metadata.")) planes.add("metadata");
|
|
601
|
+
else if (item.type.startsWith("forms.")) planes.add("forms");
|
|
602
|
+
else planes.add("annotations");
|
|
603
|
+
}
|
|
604
|
+
return [...planes];
|
|
605
|
+
}
|
|
606
|
+
//#endregion
|
|
607
|
+
//#region src/shared/withObjectNumbers.ts
|
|
608
|
+
/**
|
|
609
|
+
* `path` with the object numbers a create names, in its query string: one
|
|
610
|
+
* (`objectNumber=42`) or a comma list (`objectNumbers=42,43`). The body
|
|
611
|
+
* stays the data. Names whose value is absent are left out.
|
|
612
|
+
*/
|
|
613
|
+
function withObjectNumbers(path, numbers) {
|
|
614
|
+
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)}`);
|
|
615
|
+
if (query.length === 0) return path;
|
|
616
|
+
return `${path}${path.includes("?") ? "&" : "?"}${query.join("&")}`;
|
|
617
|
+
}
|
|
618
|
+
//#endregion
|
|
619
|
+
//#region src/document/CloudDocumentFormsService.ts
|
|
620
|
+
/**
|
|
344
621
|
* Cloud-side document forms service. Mirrors the local wiring: each call
|
|
345
622
|
* produces an `AbortablePromise` that propagates `signal.abort()` down to
|
|
346
623
|
* `fetch` and validates the JSON response with the wire-stable Zod schema.
|
|
347
624
|
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
625
|
+
* The form is its own read family: `list()` reads the immutable `form@`
|
|
626
|
+
* leaf at the manifest's `formsVersion`. Mutation results carry the
|
|
627
|
+
* `cacheDelta` of the form's pins (`formsVersion`, and each changed widget
|
|
628
|
+
* page's `widgetVersion`); `absorbMutation` folds it into the cached
|
|
629
|
+
* manifest and owns the `forms` plane.
|
|
353
630
|
*/
|
|
354
631
|
var CloudDocumentFormsService = class {
|
|
355
|
-
constructor(http, docId, layerName, isClosed, manifest, publisher) {
|
|
632
|
+
constructor(http, docId, layerName, isClosed, manifest, publisher, writes, importLimits) {
|
|
356
633
|
this.http = http;
|
|
357
634
|
this.docId = docId;
|
|
358
635
|
this.layerName = layerName;
|
|
359
636
|
this.isClosed = isClosed;
|
|
360
637
|
this.manifest = manifest;
|
|
361
638
|
this.publisher = publisher;
|
|
639
|
+
this.writes = writes;
|
|
640
|
+
this.importLimits = importLimits;
|
|
362
641
|
}
|
|
642
|
+
/**
|
|
643
|
+
* The form: one read of the immutable `form@formsVersion=N` leaf at the
|
|
644
|
+
* manifest's pin, at the base's URL while the layer inherits the `forms`
|
|
645
|
+
* plane, CDN-cacheable because the pin moves only when the form does. A
|
|
646
|
+
* stale pin (a write landed → 404) refreshes the manifest and reads again,
|
|
647
|
+
* once.
|
|
648
|
+
*/
|
|
363
649
|
list() {
|
|
364
650
|
const rejected = this.rejectIfClosed();
|
|
365
651
|
if (rejected) return rejected;
|
|
366
|
-
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.
|
|
652
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.getJsonWithRefresh(async (s) => {
|
|
653
|
+
const manifest = await this.manifest.get(s);
|
|
654
|
+
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);
|
|
655
|
+
}, (raw) => _embedpdf_engine_core_wire.FormSnapshotSchema.parse(raw), async (s) => {
|
|
656
|
+
await this.manifest.refresh(s);
|
|
657
|
+
}, signal));
|
|
367
658
|
}
|
|
368
659
|
get(ref) {
|
|
369
660
|
const rejected = this.rejectIfClosed();
|
|
370
661
|
if (rejected) return rejected;
|
|
371
662
|
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
663
|
}
|
|
373
|
-
setValue(ref, value) {
|
|
664
|
+
setValue(ref, value, options) {
|
|
374
665
|
const rejected = this.rejectIfClosed();
|
|
375
666
|
if (rejected) return rejected;
|
|
376
667
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
377
|
-
const
|
|
378
|
-
return this.
|
|
668
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
669
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
670
|
+
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));
|
|
671
|
+
return this.absorbMutation(opId, result, "forms.valueSet");
|
|
672
|
+
});
|
|
379
673
|
});
|
|
380
674
|
}
|
|
381
|
-
reset(
|
|
675
|
+
reset(fields, options) {
|
|
382
676
|
const rejected = this.rejectIfClosed();
|
|
383
677
|
if (rejected) return rejected;
|
|
678
|
+
const refs = fields === void 0 ? void 0 : Array.isArray(fields) ? fields : [fields];
|
|
384
679
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
385
|
-
const
|
|
386
|
-
return this.
|
|
680
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
681
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
682
|
+
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));
|
|
683
|
+
this.manifest.apply(result.meta, ["forms"]);
|
|
684
|
+
this.publisher.publishWrite(opId, ...(0, _embedpdf_engine_core_runtime.formResetFacts)(result).map((fact) => ({
|
|
685
|
+
type: "forms.valueSet",
|
|
686
|
+
...fact
|
|
687
|
+
})));
|
|
688
|
+
return result;
|
|
689
|
+
});
|
|
387
690
|
});
|
|
388
691
|
}
|
|
389
|
-
|
|
692
|
+
/**
|
|
693
|
+
* One versioned, CDN-cacheable read at the manifest's form and layout
|
|
694
|
+
* pins, with the stale-pin retry (404 → refresh the manifest → once). The
|
|
695
|
+
* response is the bundle without its bytes and one part per resource;
|
|
696
|
+
* every part is checked against its id before it is returned.
|
|
697
|
+
*/
|
|
698
|
+
export(selection = {}) {
|
|
390
699
|
const rejected = this.rejectIfClosed();
|
|
391
700
|
if (rejected) return rejected;
|
|
392
701
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
702
|
+
const read = async (s) => {
|
|
703
|
+
const manifest = await this.manifest.get(s);
|
|
704
|
+
const path = this.exportPathAt(manifest, selection);
|
|
705
|
+
if (path) return this.http.getFormData(path, s);
|
|
706
|
+
return this.http.postJsonFormData(_embedpdf_engine_core_wire.wirePaths.layerFormExportRequest(this.docId, this.layerName), {
|
|
707
|
+
formsVersion: manifest.formsVersion,
|
|
708
|
+
layoutVersion: manifest.layoutVersion,
|
|
709
|
+
selection
|
|
710
|
+
}, s);
|
|
711
|
+
};
|
|
712
|
+
let form;
|
|
713
|
+
try {
|
|
714
|
+
form = await read(signal);
|
|
715
|
+
} catch (error) {
|
|
716
|
+
if (!_embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.NotFound)) throw error;
|
|
717
|
+
await this.manifest.refresh(signal);
|
|
718
|
+
form = await read(signal);
|
|
719
|
+
}
|
|
720
|
+
const { body, resources } = await readBundleParts("form", form);
|
|
721
|
+
const bundle = {
|
|
722
|
+
...body,
|
|
723
|
+
resources
|
|
724
|
+
};
|
|
725
|
+
await (0, _embedpdf_engine_core_runtime.assertFormBundle)(bundle, NO_BUNDLE_LIMITS);
|
|
726
|
+
return bundle;
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* One request: the bundle's manifest and each resource once, as parts of
|
|
731
|
+
* one multipart POST under the import's `opId` as `Idempotency-Key`, so a
|
|
732
|
+
* retry applies once. The server holds the limits; the same numbers are
|
|
733
|
+
* checked here first, so a bundle past one fails before its bytes move.
|
|
734
|
+
*/
|
|
735
|
+
import(bundle, options = {}) {
|
|
736
|
+
const rejected = this.rejectIfClosed();
|
|
737
|
+
if (rejected) return rejected;
|
|
738
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
739
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
740
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
741
|
+
const body = {
|
|
742
|
+
bundle: await this.checkedManifest(bundle),
|
|
743
|
+
options: {
|
|
744
|
+
...options.pages !== void 0 ? { pages: options.pages } : {},
|
|
745
|
+
...options.attribution !== void 0 ? { attribution: options.attribution } : {},
|
|
746
|
+
...options.values !== void 0 ? { values: options.values } : {}
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
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));
|
|
750
|
+
this.manifest.apply(result.meta, ["forms"]);
|
|
751
|
+
this.publisher.publishWrite(opId, ...(0, _embedpdf_engine_core_runtime.formImportFacts)(result).map((fact) => ({
|
|
752
|
+
type: "forms.created",
|
|
753
|
+
...fact
|
|
754
|
+
})));
|
|
755
|
+
return result;
|
|
399
756
|
});
|
|
400
|
-
return result;
|
|
401
757
|
});
|
|
402
758
|
}
|
|
403
|
-
|
|
759
|
+
/** One request, as {@link import} sends it. */
|
|
760
|
+
importValues(bundle, options = {}) {
|
|
404
761
|
const rejected = this.rejectIfClosed();
|
|
405
762
|
if (rejected) return rejected;
|
|
406
763
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
764
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
765
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
766
|
+
const body = {
|
|
767
|
+
bundle: await this.checkedManifest(bundle),
|
|
768
|
+
options: options.attribution !== void 0 ? { attribution: options.attribution } : {}
|
|
769
|
+
};
|
|
770
|
+
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));
|
|
771
|
+
this.manifest.apply(result.meta, ["forms"]);
|
|
772
|
+
this.publisher.publishWrite(opId, ...(0, _embedpdf_engine_core_runtime.formValuesImportFacts)(result).map((fact) => ({
|
|
773
|
+
type: "forms.valueSet",
|
|
774
|
+
...fact
|
|
775
|
+
})));
|
|
776
|
+
return result;
|
|
777
|
+
});
|
|
411
778
|
});
|
|
412
779
|
}
|
|
413
|
-
|
|
780
|
+
create(draft, options = {}) {
|
|
414
781
|
const rejected = this.rejectIfClosed();
|
|
415
782
|
if (rejected) return rejected;
|
|
416
|
-
const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
417
783
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
418
|
-
const
|
|
419
|
-
|
|
784
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
785
|
+
const { objectNumber, widgetObjectNumbers } = options;
|
|
786
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
787
|
+
const result = await write.send((sent) => this.http.postJson(withObjectNumbers(_embedpdf_engine_core_wire.wirePaths.layerFormFields(this.docId, this.layerName), {
|
|
788
|
+
objectNumber,
|
|
789
|
+
widgetObjectNumbers
|
|
790
|
+
}), draft, (raw) => _embedpdf_engine_core_wire.FormFieldCreateResultSchema.parse(raw), signal, sent));
|
|
791
|
+
return this.absorbMutation(opId, result, "forms.created");
|
|
792
|
+
}, [objectNumber, ...widgetObjectNumbers ?? []]);
|
|
420
793
|
});
|
|
421
794
|
}
|
|
422
|
-
|
|
795
|
+
update(ref, patch, options) {
|
|
423
796
|
const rejected = this.rejectIfClosed();
|
|
424
797
|
if (rejected) return rejected;
|
|
425
798
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
426
|
-
const
|
|
427
|
-
return this.
|
|
799
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
800
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
801
|
+
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));
|
|
802
|
+
return this.absorbMutation(opId, result, "forms.updated");
|
|
803
|
+
});
|
|
428
804
|
});
|
|
429
805
|
}
|
|
430
|
-
|
|
806
|
+
setSignatureAppearance(ref, appearance, options) {
|
|
431
807
|
const rejected = this.rejectIfClosed();
|
|
432
808
|
if (rejected) return rejected;
|
|
433
809
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
434
|
-
const
|
|
435
|
-
return this.
|
|
810
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
811
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
812
|
+
const bytes = new ArrayBuffer(appearance.pdf.byteLength);
|
|
813
|
+
new Uint8Array(bytes).set(appearance.pdf);
|
|
814
|
+
const form = buildRoleMutationForm({}, { appearance: {
|
|
815
|
+
bytes,
|
|
816
|
+
mimeType: "application/pdf",
|
|
817
|
+
name: "appearance.pdf"
|
|
818
|
+
} });
|
|
819
|
+
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));
|
|
820
|
+
return this.absorbMutation(opId, result, "forms.updated");
|
|
821
|
+
});
|
|
436
822
|
});
|
|
437
823
|
}
|
|
438
|
-
|
|
824
|
+
delete(ref, options) {
|
|
439
825
|
const rejected = this.rejectIfClosed();
|
|
440
826
|
if (rejected) return rejected;
|
|
441
827
|
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");
|
|
828
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
829
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
830
|
+
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));
|
|
831
|
+
this.manifest.apply(result.meta, ["forms"]);
|
|
832
|
+
this.publisher.publishWrite(opId, {
|
|
833
|
+
type: "forms.deleted",
|
|
834
|
+
deleted: (0, _embedpdf_engine_core_runtime.deletedFieldOf)(result),
|
|
835
|
+
...result
|
|
836
|
+
});
|
|
837
|
+
return result;
|
|
838
|
+
});
|
|
454
839
|
});
|
|
455
840
|
}
|
|
456
|
-
|
|
841
|
+
addWidget(ref, placement, options = {}) {
|
|
457
842
|
const rejected = this.rejectIfClosed();
|
|
458
843
|
if (rejected) return rejected;
|
|
459
844
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
460
|
-
const
|
|
461
|
-
|
|
845
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
846
|
+
const { objectNumber, splitObjectNumber } = options;
|
|
847
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
848
|
+
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)), {
|
|
849
|
+
objectNumber,
|
|
850
|
+
splitObjectNumber
|
|
851
|
+
}), placement, (raw) => _embedpdf_engine_core_wire.FormWidgetLinkResultSchema.parse(raw), signal, sent));
|
|
852
|
+
return this.absorbMutation(opId, result, "forms.widgetAdded");
|
|
853
|
+
}, [objectNumber, splitObjectNumber]);
|
|
462
854
|
});
|
|
463
855
|
}
|
|
464
|
-
|
|
856
|
+
removeWidget(ref, widget, options) {
|
|
465
857
|
const rejected = this.rejectIfClosed();
|
|
466
858
|
if (rejected) return rejected;
|
|
467
|
-
const onState = options?.onState;
|
|
468
859
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
860
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
861
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
862
|
+
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));
|
|
863
|
+
return this.absorbMutation(opId, result, "forms.widgetRemoved");
|
|
864
|
+
});
|
|
474
865
|
});
|
|
475
866
|
}
|
|
476
|
-
|
|
867
|
+
updateWidget(widget, patch, options) {
|
|
477
868
|
const rejected = this.rejectIfClosed();
|
|
478
869
|
if (rejected) return rejected;
|
|
479
870
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
480
|
-
const
|
|
481
|
-
return this.
|
|
871
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
872
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
873
|
+
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));
|
|
874
|
+
return this.absorbMutation(opId, result, "forms.widgetUpdated");
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
deleteWidget(widget, options) {
|
|
879
|
+
const rejected = this.rejectIfClosed();
|
|
880
|
+
if (rejected) return rejected;
|
|
881
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
882
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
883
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
884
|
+
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));
|
|
885
|
+
return this.absorbMutation(opId, result, "forms.widgetDeleted");
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
reorderCalculations(fields, position, options) {
|
|
890
|
+
const rejected = this.rejectIfClosed();
|
|
891
|
+
if (rejected) return rejected;
|
|
892
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
893
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
894
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
895
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormCalculationsReorder(this.docId, this.layerName), {
|
|
896
|
+
fields,
|
|
897
|
+
position
|
|
898
|
+
}, (raw) => _embedpdf_engine_core_wire.FormCalculationsReorderResultSchema.parse(raw), signal, sent));
|
|
899
|
+
return this.absorbMutation(opId, result, "forms.calculationsReordered");
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
reorderWidgets(widgets, position, options) {
|
|
904
|
+
const rejected = this.rejectIfClosed();
|
|
905
|
+
if (rejected) return rejected;
|
|
906
|
+
const page = widgets[0]?.page;
|
|
907
|
+
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" } }));
|
|
908
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
909
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
910
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
911
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormWidgetsReorder(this.docId, this.layerName, page), {
|
|
912
|
+
widgets,
|
|
913
|
+
position
|
|
914
|
+
}, (raw) => _embedpdf_engine_core_wire.FormWidgetsReorderResultSchema.parse(raw), signal, sent));
|
|
915
|
+
return this.absorbMutation(opId, result, "forms.widgetsReordered");
|
|
916
|
+
});
|
|
482
917
|
});
|
|
483
918
|
}
|
|
484
919
|
repair(options) {
|
|
485
920
|
const rejected = this.rejectIfClosed();
|
|
486
921
|
if (rejected) return rejected;
|
|
487
922
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
488
|
-
const
|
|
489
|
-
return this.
|
|
923
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
924
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
925
|
+
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));
|
|
926
|
+
return this.absorbMutation(opId, result, "forms.repaired");
|
|
927
|
+
});
|
|
490
928
|
});
|
|
491
929
|
}
|
|
930
|
+
/** The bundle without its bytes, checked against the server's limits first. */
|
|
931
|
+
async checkedManifest(bundle) {
|
|
932
|
+
const { resources, ...manifest } = bundle;
|
|
933
|
+
(0, _embedpdf_engine_core_runtime.assertFormBundleManifest)(bundle, new Map(Object.entries(resources).map(([id, bytes]) => [id, bytes.length])), await this.importLimits());
|
|
934
|
+
return manifest;
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* The cacheable export URL for `selection` at the manifest's pins — the
|
|
938
|
+
* base leaf while the layer inherits both planes the bundle depends on —
|
|
939
|
+
* or `null` when a URL can't carry it.
|
|
940
|
+
*/
|
|
941
|
+
exportPathAt(manifest, selection) {
|
|
942
|
+
const token = {
|
|
943
|
+
formsVersion: manifest.formsVersion,
|
|
944
|
+
layoutVersion: manifest.layoutVersion,
|
|
945
|
+
selection
|
|
946
|
+
};
|
|
947
|
+
try {
|
|
948
|
+
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);
|
|
949
|
+
} catch {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
492
953
|
rejectIfClosed() {
|
|
493
954
|
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
955
|
return null;
|
|
@@ -498,9 +959,9 @@ var CloudDocumentFormsService = class {
|
|
|
498
959
|
* event stream (in that order — listeners reading the manifest in their
|
|
499
960
|
* callback must see post-mutation state). Same rails as annotations.
|
|
500
961
|
*/
|
|
501
|
-
absorbMutation(result, type) {
|
|
502
|
-
this.manifest.apply(result.meta, ["
|
|
503
|
-
this.publisher.
|
|
962
|
+
absorbMutation(opId, result, type) {
|
|
963
|
+
this.manifest.apply(result.meta, ["forms"]);
|
|
964
|
+
this.publisher.publishWrite(opId, {
|
|
504
965
|
type,
|
|
505
966
|
...result
|
|
506
967
|
});
|
|
@@ -508,6 +969,28 @@ var CloudDocumentFormsService = class {
|
|
|
508
969
|
}
|
|
509
970
|
};
|
|
510
971
|
//#endregion
|
|
972
|
+
//#region src/shared/awaitSignal.ts
|
|
973
|
+
/**
|
|
974
|
+
* Wait for a shared promise under one caller's signal: an abort rejects
|
|
975
|
+
* this caller only and leaves the shared work running for the others.
|
|
976
|
+
*/
|
|
977
|
+
function awaitSignal(promise, signal) {
|
|
978
|
+
if (signal.aborted) return Promise.reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason));
|
|
979
|
+
return new Promise((resolve, reject) => {
|
|
980
|
+
const onAbort = () => {
|
|
981
|
+
reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason));
|
|
982
|
+
};
|
|
983
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
984
|
+
promise.then((value) => {
|
|
985
|
+
signal.removeEventListener("abort", onAbort);
|
|
986
|
+
resolve(value);
|
|
987
|
+
}, (reason) => {
|
|
988
|
+
signal.removeEventListener("abort", onAbort);
|
|
989
|
+
reject(reason);
|
|
990
|
+
});
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
//#endregion
|
|
511
994
|
//#region src/document/CloudDocumentPagesService.ts
|
|
512
995
|
/** Detach a Uint8Array view into a standalone ArrayBuffer (the resource-map
|
|
513
996
|
* shape) without disturbing a larger buffer the caller still owns. */
|
|
@@ -518,26 +1001,28 @@ function copyToExactBuffer(view) {
|
|
|
518
1001
|
}
|
|
519
1002
|
/**
|
|
520
1003
|
* Cloud-side document pages service. Mirrors `LocalDocumentPagesService`
|
|
521
|
-
* over HTTP: GET /pages for `list`, POST /pages/
|
|
1004
|
+
* over HTTP: GET /pages for `list`, POST /pages/reorder for the reorder.
|
|
522
1005
|
*
|
|
523
1006
|
* 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
|
-
*
|
|
1007
|
+
* - Pages are addressed exclusively by `PageRef` (their indirect
|
|
1008
|
+
* `pageObjectNumber`); mutation bodies ship `{ pages: PageRef[] }`.
|
|
1009
|
+
* The wire never sends a page index for a mutation. This keeps
|
|
1010
|
+
* multi-call client logic from having to account for index drift
|
|
1011
|
+
* between requests.
|
|
1012
|
+
* - Successful `reorder()` returns the new `layout` (order + geometry) plus
|
|
1013
|
+
* cloud coherence pins. A page reorder bumps only `docVersion` +
|
|
1014
|
+
* `layoutVersion`; every per-page pin stays warm.
|
|
532
1015
|
*/
|
|
533
1016
|
var CloudDocumentPagesService = class {
|
|
534
|
-
constructor(http, docId, layerName, isClosed, manifest, publisher) {
|
|
1017
|
+
constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
|
|
535
1018
|
this.http = http;
|
|
536
1019
|
this.docId = docId;
|
|
537
1020
|
this.layerName = layerName;
|
|
538
1021
|
this.isClosed = isClosed;
|
|
539
1022
|
this.manifest = manifest;
|
|
540
1023
|
this.publisher = publisher;
|
|
1024
|
+
this.writes = writes;
|
|
1025
|
+
_defineProperty(this, "layoutMemo", null);
|
|
541
1026
|
}
|
|
542
1027
|
/**
|
|
543
1028
|
* Page-geometry list. The geometry bytes live at the content-addressed
|
|
@@ -551,142 +1036,195 @@ var CloudDocumentPagesService = class {
|
|
|
551
1036
|
*/
|
|
552
1037
|
list() {
|
|
553
1038
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
1039
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.fetchLayout(signal));
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* One page's layout, for renders to size their image without a round
|
|
1043
|
+
* trip per tile. The layout leaf never changes at a given URL, so the
|
|
1044
|
+
* last one read is kept and shared until the manifest points elsewhere.
|
|
1045
|
+
*/
|
|
1046
|
+
async pageLayout(ref, signal) {
|
|
1047
|
+
const path = await this.layoutPath(signal);
|
|
1048
|
+
let memo = this.layoutMemo;
|
|
1049
|
+
if (memo?.path !== path) {
|
|
1050
|
+
const snapshot = this.fetchLayout(new AbortController().signal);
|
|
1051
|
+
const next = {
|
|
1052
|
+
path,
|
|
1053
|
+
snapshot
|
|
558
1054
|
};
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
|
|
1055
|
+
snapshot.catch(() => {
|
|
1056
|
+
if (this.layoutMemo === next) this.layoutMemo = null;
|
|
1057
|
+
});
|
|
1058
|
+
this.layoutMemo = memo = next;
|
|
1059
|
+
}
|
|
1060
|
+
const page = (await awaitSignal(memo.snapshot, signal)).pages.find((p) => (0, _embedpdf_engine_core_runtime.pageRefsEqual)(p.ref, ref));
|
|
1061
|
+
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}`);
|
|
1062
|
+
return page;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Plane-scope rule: the layout leaf depends on the `layout` plane —
|
|
1066
|
+
* while inherited (no reorder/rotate/insert/delete ever ran), every
|
|
1067
|
+
* visitor's page list is one doc-level URL served from the base
|
|
1068
|
+
* session; the SDK open sequence creates no layer session.
|
|
1069
|
+
*/
|
|
1070
|
+
async layoutPath(signal) {
|
|
1071
|
+
const manifest = await this.manifest.get(signal);
|
|
1072
|
+
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);
|
|
563
1073
|
}
|
|
564
|
-
|
|
1074
|
+
fetchLayout(signal) {
|
|
1075
|
+
return this.http.getJsonWithRefresh((s) => this.layoutPath(s), (raw) => _embedpdf_engine_core_wire.PageListSnapshotSchema.parse(raw), async (s) => {
|
|
1076
|
+
await this.manifest.refresh(s);
|
|
1077
|
+
}, signal);
|
|
1078
|
+
}
|
|
1079
|
+
reorder(pages, position, options) {
|
|
565
1080
|
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`));
|
|
566
1081
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
1082
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1083
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1084
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesReorder(this.docId, this.layerName), {
|
|
1085
|
+
pages,
|
|
1086
|
+
position
|
|
1087
|
+
}, (raw) => _embedpdf_engine_core_wire.PageReorderResultSchema.parse(raw), signal, sent));
|
|
1088
|
+
this.manifest.apply(result.meta, ["layout"]);
|
|
1089
|
+
this.publisher.publishWrite(opId, {
|
|
1090
|
+
type: "pages.reordered",
|
|
1091
|
+
...result
|
|
1092
|
+
});
|
|
1093
|
+
return result;
|
|
577
1094
|
});
|
|
578
|
-
return result;
|
|
579
1095
|
});
|
|
580
1096
|
}
|
|
581
|
-
setName(input) {
|
|
582
|
-
return this.runNameMutation(_embedpdf_engine_core_wire.wirePaths.layerPagesNames(this.docId, this.layerName), input, input.name, input.
|
|
1097
|
+
setName(input, options) {
|
|
1098
|
+
return this.runNameMutation(_embedpdf_engine_core_wire.wirePaths.layerPagesNames(this.docId, this.layerName), input, input.name, input.page, options);
|
|
583
1099
|
}
|
|
584
|
-
removeName(input) {
|
|
585
|
-
return this.runNameMutation(_embedpdf_engine_core_wire.wirePaths.layerPagesNamesDelete(this.docId, this.layerName), input, input.name, null);
|
|
1100
|
+
removeName(input, options) {
|
|
1101
|
+
return this.runNameMutation(_embedpdf_engine_core_wire.wirePaths.layerPagesNamesDelete(this.docId, this.layerName), input, input.name, null, options);
|
|
586
1102
|
}
|
|
587
1103
|
/**
|
|
588
|
-
* Named pages are
|
|
1104
|
+
* Named pages are layout: both verbs share the page-reorder patch exactly —
|
|
589
1105
|
* docVersion + layoutVersion advance, no per-page pin changes, so the
|
|
590
1106
|
* cached manifest is patched in place and the fresh layout is published.
|
|
591
1107
|
*/
|
|
592
|
-
runNameMutation(path, body, name,
|
|
1108
|
+
runNameMutation(path, body, name, page, options) {
|
|
593
1109
|
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`));
|
|
594
1110
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
rotate(pageObjectNumbers, rotation) {
|
|
607
|
-
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`));
|
|
608
|
-
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
609
|
-
const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesRotate(this.docId, this.layerName), {
|
|
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
|
|
1111
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1112
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1113
|
+
const result = await write.send((sent) => this.http.postJson(path, body, (raw) => _embedpdf_engine_core_wire.PageNameResultSchema.parse(raw), signal, sent));
|
|
1114
|
+
this.manifest.apply(result.meta, ["layout"]);
|
|
1115
|
+
this.publisher.publishWrite(opId, {
|
|
1116
|
+
type: "pages.named",
|
|
1117
|
+
name,
|
|
1118
|
+
page,
|
|
1119
|
+
...result
|
|
1120
|
+
});
|
|
1121
|
+
return result;
|
|
619
1122
|
});
|
|
620
|
-
return result;
|
|
621
1123
|
});
|
|
622
1124
|
}
|
|
623
|
-
|
|
1125
|
+
rotate(pages, rotation, options) {
|
|
624
1126
|
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`));
|
|
625
1127
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
1128
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1129
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1130
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesRotate(this.docId, this.layerName), {
|
|
1131
|
+
pages,
|
|
1132
|
+
rotation
|
|
1133
|
+
}, (raw) => _embedpdf_engine_core_wire.PageRotateResultSchema.parse(raw), signal, sent));
|
|
1134
|
+
this.manifest.apply(result.meta, ["layout"]);
|
|
1135
|
+
this.publisher.publishWrite(opId, {
|
|
1136
|
+
type: "pages.rotated",
|
|
1137
|
+
pages,
|
|
1138
|
+
rotation,
|
|
1139
|
+
...result
|
|
1140
|
+
});
|
|
1141
|
+
return result;
|
|
632
1142
|
});
|
|
633
|
-
return result;
|
|
634
1143
|
});
|
|
635
1144
|
}
|
|
636
|
-
|
|
1145
|
+
delete(pages, options) {
|
|
637
1146
|
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`));
|
|
638
1147
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
destIndex,
|
|
650
|
-
...result
|
|
1148
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1149
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1150
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesDelete(this.docId, this.layerName), { pages }, (raw) => _embedpdf_engine_core_wire.PageDeleteResultSchema.parse(raw), signal, sent));
|
|
1151
|
+
this.manifest.applyPageDelete(result.meta, pages);
|
|
1152
|
+
this.publisher.publishWrite(opId, {
|
|
1153
|
+
type: "pages.deleted",
|
|
1154
|
+
pages,
|
|
1155
|
+
...result
|
|
1156
|
+
});
|
|
1157
|
+
return result;
|
|
651
1158
|
});
|
|
652
|
-
return result;
|
|
653
1159
|
});
|
|
654
1160
|
}
|
|
655
|
-
|
|
1161
|
+
insert(bytes, position = "end", options) {
|
|
656
1162
|
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
1163
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
1164
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1165
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1166
|
+
const buffer = bytes instanceof ArrayBuffer ? bytes : copyToExactBuffer(bytes);
|
|
1167
|
+
const form = buildRoleMutationForm({ position }, { source: {
|
|
1168
|
+
bytes: buffer,
|
|
1169
|
+
mimeType: "application/pdf",
|
|
1170
|
+
name: "source.pdf"
|
|
1171
|
+
} });
|
|
1172
|
+
const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerPagesInsert(this.docId, this.layerName), form, (raw) => _embedpdf_engine_core_wire.PageInsertResultSchema.parse(raw), signal, sent));
|
|
1173
|
+
this.manifest.applyPageInsert(result.meta);
|
|
1174
|
+
this.publisher.publishWrite(opId, {
|
|
1175
|
+
type: "pages.inserted",
|
|
1176
|
+
...result
|
|
1177
|
+
});
|
|
1178
|
+
return result;
|
|
668
1179
|
});
|
|
669
|
-
return result;
|
|
670
1180
|
});
|
|
671
1181
|
}
|
|
672
|
-
|
|
1182
|
+
insertBlank(spec, position = "end", options = {}) {
|
|
1183
|
+
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`));
|
|
1184
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1185
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1186
|
+
const { objectNumbers } = options;
|
|
1187
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1188
|
+
const result = await write.send((sent) => this.http.postJson(withObjectNumbers(_embedpdf_engine_core_wire.wirePaths.layerPagesInsertBlank(this.docId, this.layerName), { objectNumbers }), {
|
|
1189
|
+
size: spec.size,
|
|
1190
|
+
...spec.count !== void 0 ? { count: spec.count } : {},
|
|
1191
|
+
position
|
|
1192
|
+
}, (raw) => _embedpdf_engine_core_wire.PageInsertResultSchema.parse(raw), signal, sent));
|
|
1193
|
+
this.manifest.applyPageInsert(result.meta);
|
|
1194
|
+
this.publisher.publishWrite(opId, {
|
|
1195
|
+
type: "pages.inserted",
|
|
1196
|
+
...result
|
|
1197
|
+
});
|
|
1198
|
+
return result;
|
|
1199
|
+
}, objectNumbers ?? []);
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
extract(pages) {
|
|
673
1203
|
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
|
-
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.postJsonBytes(_embedpdf_engine_core_wire.wirePaths.layerPagesExtract(this.docId, this.layerName), {
|
|
1204
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.postJsonBytes(_embedpdf_engine_core_wire.wirePaths.layerPagesExtract(this.docId, this.layerName), { pages }, signal));
|
|
675
1205
|
}
|
|
676
|
-
flatten(
|
|
1206
|
+
flatten(pages, options) {
|
|
1207
|
+
const usage = options?.usage ?? "display";
|
|
677
1208
|
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
1209
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1210
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1211
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1212
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesFlatten(this.docId, this.layerName), {
|
|
1213
|
+
pages,
|
|
1214
|
+
usage
|
|
1215
|
+
}, (raw) => _embedpdf_engine_core_wire.PageFlattenResultSchema.parse(raw), signal, sent));
|
|
1216
|
+
if (result.meta.cacheDelta === null) return result;
|
|
1217
|
+
this.manifest.apply(result.meta, [
|
|
1218
|
+
"content",
|
|
1219
|
+
"annotations",
|
|
1220
|
+
"forms"
|
|
1221
|
+
]);
|
|
1222
|
+
this.publisher.publishWrite(opId, {
|
|
1223
|
+
type: "pages.flattened",
|
|
1224
|
+
...result
|
|
1225
|
+
});
|
|
1226
|
+
return result;
|
|
688
1227
|
});
|
|
689
|
-
return result;
|
|
690
1228
|
});
|
|
691
1229
|
}
|
|
692
1230
|
};
|
|
@@ -697,29 +1235,37 @@ var CloudDocumentPagesService = class {
|
|
|
697
1235
|
* HTTP (POST /redactions/apply); the server enforces the capability gate
|
|
698
1236
|
* (`doc.pages.modify` + `doc.annotate.modify` + `doc.redact`) and persists
|
|
699
1237
|
* the rewritten layer artifact. See `DocumentRedactionService` for the
|
|
700
|
-
* two-stage model and the layer trust boundary — an apply rewrites
|
|
701
|
-
*
|
|
1238
|
+
* two-stage model and the layer trust boundary — an apply rewrites this
|
|
1239
|
+
* layer's bytes; the immutable base keeps the original.
|
|
702
1240
|
*/
|
|
703
1241
|
var CloudDocumentRedactionService = class {
|
|
704
|
-
constructor(http, docId, layerName, isClosed, manifest, publisher) {
|
|
1242
|
+
constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
|
|
705
1243
|
this.http = http;
|
|
706
1244
|
this.docId = docId;
|
|
707
1245
|
this.layerName = layerName;
|
|
708
1246
|
this.isClosed = isClosed;
|
|
709
1247
|
this.manifest = manifest;
|
|
710
1248
|
this.publisher = publisher;
|
|
1249
|
+
this.writes = writes;
|
|
711
1250
|
}
|
|
712
|
-
apply(scope) {
|
|
1251
|
+
apply(scope, options) {
|
|
713
1252
|
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
1253
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1254
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1255
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1256
|
+
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));
|
|
1257
|
+
if (result.meta.cacheDelta === null) return result;
|
|
1258
|
+
this.manifest.apply(result.meta, [
|
|
1259
|
+
"content",
|
|
1260
|
+
"annotations",
|
|
1261
|
+
"forms"
|
|
1262
|
+
]);
|
|
1263
|
+
this.publisher.publishWrite(opId, {
|
|
1264
|
+
type: "redaction.applied",
|
|
1265
|
+
...result
|
|
1266
|
+
});
|
|
1267
|
+
return result;
|
|
721
1268
|
});
|
|
722
|
-
return result;
|
|
723
1269
|
});
|
|
724
1270
|
}
|
|
725
1271
|
};
|
|
@@ -730,14 +1276,14 @@ var CloudDocumentRedactionService = class {
|
|
|
730
1276
|
*
|
|
731
1277
|
* Every slice URL is a canonical cache key — the search token pins the
|
|
732
1278
|
* layer's content epoch (computed from the cached manifest), the
|
|
733
|
-
*
|
|
1279
|
+
* canonical query (default-fold literals collapse case/diacritic
|
|
734
1280
|
* variants into one entry), and the resume position. Identical searches
|
|
735
1281
|
* from any reader of the document hit the same CDN entry; matches are
|
|
736
1282
|
* all that ever crosses the trust boundary, and the rects/full endpoint
|
|
737
1283
|
* split keeps permission tiers in disjoint cache namespaces.
|
|
738
1284
|
*
|
|
739
1285
|
* Staleness rides the standard versioned-read signal: a first slice
|
|
740
|
-
* answered `NotFound` means
|
|
1286
|
+
* answered `NotFound` means our manifest is stale → refresh and retry
|
|
741
1287
|
* once; a continuation answered `NotFound` means the document changed
|
|
742
1288
|
* mid-search → `InvalidArg`, same contract as the local engine's cursor.
|
|
743
1289
|
*/
|
|
@@ -752,9 +1298,10 @@ var CloudDocumentSearchService = class {
|
|
|
752
1298
|
query(request) {
|
|
753
1299
|
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
1300
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
755
|
-
const path =
|
|
1301
|
+
const path = request.snippets ? _embedpdf_engine_core_wire.wirePaths.layerSearchFull : _embedpdf_engine_core_wire.wirePaths.layerSearchRects;
|
|
756
1302
|
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
|
|
1303
|
+
const query = (0, _embedpdf_engine_core_runtime.canonicalSearchQuery)(request);
|
|
1304
|
+
const from = request.from?.objectNumber;
|
|
758
1305
|
if (request.cursor !== void 0) {
|
|
759
1306
|
let token;
|
|
760
1307
|
try {
|
|
@@ -763,7 +1310,7 @@ var CloudDocumentSearchService = class {
|
|
|
763
1310
|
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "malformed search cursor");
|
|
764
1311
|
}
|
|
765
1312
|
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 (
|
|
1313
|
+
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
1314
|
try {
|
|
768
1315
|
return await get(request.cursor);
|
|
769
1316
|
} catch (err) {
|
|
@@ -774,9 +1321,9 @@ var CloudDocumentSearchService = class {
|
|
|
774
1321
|
const mint = (epoch) => (0, _embedpdf_engine_core_wire.encodeSearchToken)({
|
|
775
1322
|
epoch,
|
|
776
1323
|
query,
|
|
777
|
-
...
|
|
1324
|
+
...from !== void 0 ? { from } : {},
|
|
778
1325
|
skip: 0,
|
|
779
|
-
...request.
|
|
1326
|
+
...request.limit !== void 0 ? { limit: request.limit } : {}
|
|
780
1327
|
});
|
|
781
1328
|
try {
|
|
782
1329
|
return await get(mint((0, _embedpdf_engine_core_runtime.searchContentEpoch)(await this.manifest.get(signal))));
|
|
@@ -842,22 +1389,39 @@ function base64UrlDecode(b64url) {
|
|
|
842
1389
|
//#endregion
|
|
843
1390
|
//#region src/document/CloudDocumentSecurityService.ts
|
|
844
1391
|
var CloudDocumentSecurityService = class {
|
|
845
|
-
constructor(http, docId, layerName, initialHead, view, initialToken = null) {
|
|
1392
|
+
constructor(http, docId, layerName, initialHead, view, initialToken = null, editing = null) {
|
|
846
1393
|
this.http = http;
|
|
847
1394
|
this.docId = docId;
|
|
848
1395
|
this.layerName = layerName;
|
|
849
1396
|
this.view = view;
|
|
850
|
-
|
|
1397
|
+
this.editing = editing;
|
|
1398
|
+
_defineProperty(this, "securityState", void 0);
|
|
851
1399
|
_defineProperty(this, "access", null);
|
|
852
1400
|
_defineProperty(this, "tokenScope", void 0);
|
|
853
1401
|
_defineProperty(this, "tokenIdentity", void 0);
|
|
854
|
-
this
|
|
1402
|
+
_defineProperty(this, "tokenHasDocScope", void 0);
|
|
1403
|
+
_defineProperty(this, "protection", null);
|
|
1404
|
+
_defineProperty(this, "passwordRejected", false);
|
|
1405
|
+
this.securityState = (0, _embedpdf_engine_core_runtime.securityStateFromHead)(initialHead);
|
|
855
1406
|
const claims = initialToken ? safeDecodeClaims(initialToken) : null;
|
|
856
1407
|
this.tokenScope = Array.isArray(claims?.scope) ? claims.scope : [];
|
|
857
1408
|
this.tokenIdentity = claims ? identityFromClaims(claims) : null;
|
|
1409
|
+
this.tokenHasDocScope = typeof claims?.doc_id === "string" && Array.isArray(claims?.scope);
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* What the caller may read of a page picture, when the client can know it:
|
|
1413
|
+
* once `/access` answered, or from a document token's scope. Null without
|
|
1414
|
+
* either (no token, a tenant or API token): the server decides.
|
|
1415
|
+
*/
|
|
1416
|
+
pageLayerRights() {
|
|
1417
|
+
if (!this.access && !this.tokenHasDocScope) return null;
|
|
1418
|
+
return {
|
|
1419
|
+
annotations: this.allows("doc.annotate.read"),
|
|
1420
|
+
formFields: this.allows("doc.forms.read")
|
|
1421
|
+
};
|
|
858
1422
|
}
|
|
859
|
-
get
|
|
860
|
-
return this.
|
|
1423
|
+
get state() {
|
|
1424
|
+
return this.securityState;
|
|
861
1425
|
}
|
|
862
1426
|
get currentAccess() {
|
|
863
1427
|
return this.access;
|
|
@@ -865,52 +1429,60 @@ var CloudDocumentSecurityService = class {
|
|
|
865
1429
|
/**
|
|
866
1430
|
* Expanded capability set. Cloud-canonical post-/access; otherwise
|
|
867
1431
|
* computed locally from the JWT scope + /head's pdf bits using the
|
|
868
|
-
*
|
|
1432
|
+
* same `expandRawScope` helper engine-local calls — so the value
|
|
869
1433
|
* matches across engines bit-for-bit on the same inputs.
|
|
870
1434
|
*/
|
|
871
|
-
get
|
|
1435
|
+
get scope() {
|
|
872
1436
|
if (this.access) return this.access.effectiveScope;
|
|
873
|
-
|
|
874
|
-
return Array.from((0, _embedpdf_engine_core_runtime.expandRawScope)(this.tokenScope, bits)).sort();
|
|
1437
|
+
return Array.from((0, _embedpdf_engine_core_runtime.expandRawScope)(this.tokenScope, this.pdfBits(), this.protection)).sort();
|
|
875
1438
|
}
|
|
876
1439
|
/**
|
|
877
|
-
*
|
|
878
|
-
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
1440
|
+
* The server route guards' own predicate over the same inputs: what the
|
|
1441
|
+
* document's signatures forbid first, then the scope (the server's copy
|
|
1442
|
+
* once `/access` answered, else the JWT's), wildcard included, against the
|
|
1443
|
+
* PDF bits. `scope` alone can't gate UI: it enumerates concrete grants and
|
|
1444
|
+
* drops the `*` admin wildcard.
|
|
882
1445
|
*/
|
|
883
1446
|
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);
|
|
1447
|
+
return (0, _embedpdf_engine_core_runtime.checkCapability)(cap, this.rawScope(), this.pdfBits(), this.protection);
|
|
887
1448
|
}
|
|
888
1449
|
/**
|
|
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`.
|
|
1450
|
+
* Cloud-internal: what the document's signatures forbid, from the latest
|
|
1451
|
+
* manifest (it changes only when a signature publishes a new version).
|
|
897
1452
|
*/
|
|
898
|
-
|
|
1453
|
+
setProtection(protection) {
|
|
1454
|
+
this.protection = protection;
|
|
1455
|
+
}
|
|
1456
|
+
allowsAnnotation(action, target) {
|
|
899
1457
|
const id = this.identity ?? {};
|
|
900
|
-
|
|
1458
|
+
const annotationsProtected = (0, _embedpdf_engine_core_runtime.protectedCapabilities)(this.protection).has("doc.annotate.modify");
|
|
1459
|
+
switch (action) {
|
|
1460
|
+
case "create":
|
|
1461
|
+
if (annotationsProtected) return false;
|
|
1462
|
+
return (0, _embedpdf_engine_core_runtime.checkCollab)("create", selfTarget(id), this.rawScope(), id, this.pdfBits());
|
|
1463
|
+
case "set-group": return (0, _embedpdf_engine_core_runtime.checkSetGroup)("annotations", target.groupId, id.groupId, this.rawScope());
|
|
1464
|
+
default: {
|
|
1465
|
+
const annotation = target ?? {};
|
|
1466
|
+
if (annotation.subtype === "widget") return this.allows("doc.forms.modify");
|
|
1467
|
+
if (annotationsProtected) return false;
|
|
1468
|
+
return (0, _embedpdf_engine_core_runtime.checkCollab)(action, (0, _embedpdf_engine_core_runtime.collabTargetOf)(annotation), this.rawScope(), id, this.pdfBits());
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
901
1471
|
}
|
|
902
|
-
|
|
903
|
-
return (0, _embedpdf_engine_core_runtime.
|
|
1472
|
+
allowsField(action, target) {
|
|
1473
|
+
if (action === "set-group") return (0, _embedpdf_engine_core_runtime.checkSetGroup)("fields", target.groupId ?? "", this.identity?.groupId, this.rawScope());
|
|
1474
|
+
return (0, _embedpdf_engine_core_runtime.checkFieldAction)(action, target.groupId, this.rawScope(), this.pdfBits(), this.protection);
|
|
904
1475
|
}
|
|
905
|
-
|
|
906
|
-
|
|
1476
|
+
/** The same inputs as `allowsField`, as lists. */
|
|
1477
|
+
fieldRights() {
|
|
1478
|
+
return (0, _embedpdf_engine_core_runtime.fieldRightsOf)(this.rawScope(), this.pdfBits(), this.protection, this.identity?.groupId);
|
|
907
1479
|
}
|
|
908
1480
|
/** Raw scope for the collab resolver: server-canonical post-/access, else the JWT claim. */
|
|
909
1481
|
rawScope() {
|
|
910
1482
|
return this.access?.scope ?? this.tokenScope;
|
|
911
1483
|
}
|
|
912
1484
|
pdfBits() {
|
|
913
|
-
return (0, _embedpdf_engine_core_runtime.decodePdfBits)(this.
|
|
1485
|
+
return (0, _embedpdf_engine_core_runtime.decodePdfBits)(this.securityState.permissions.bits);
|
|
914
1486
|
}
|
|
915
1487
|
/**
|
|
916
1488
|
* Identity of the current caller, or null when anonymous.
|
|
@@ -930,21 +1502,28 @@ var CloudDocumentSecurityService = class {
|
|
|
930
1502
|
* security state. See `passwordPromptFromState` for the rules.
|
|
931
1503
|
*/
|
|
932
1504
|
get passwordPrompt() {
|
|
933
|
-
return (0, _embedpdf_engine_core_runtime.passwordPromptFromState)(this.
|
|
1505
|
+
return (0, _embedpdf_engine_core_runtime.passwordPromptFromState)(this.securityState, this.passwordRejected);
|
|
934
1506
|
}
|
|
935
1507
|
unlock(input) {
|
|
936
1508
|
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
1509
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1510
|
+
try {
|
|
1511
|
+
const result = await this.postAccess(signal, {
|
|
1512
|
+
password: input.password,
|
|
1513
|
+
mode: input.mode ?? "any"
|
|
1514
|
+
});
|
|
1515
|
+
this.passwordRejected = false;
|
|
1516
|
+
return result;
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
if (_embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.DocPasswordIncorrect)) this.passwordRejected = true;
|
|
1519
|
+
throw error;
|
|
1520
|
+
}
|
|
942
1521
|
});
|
|
943
1522
|
}
|
|
944
1523
|
/**
|
|
945
1524
|
* Cloud-internal: call /v1/access with no password to establish
|
|
946
1525
|
* a CDN-credentialed session. Used by `CloudEngine.open` when
|
|
947
|
-
* /head's `access.reasons` includes 'cdn' but
|
|
1526
|
+
* /head's `access.reasons` includes 'cdn' but not 'password' —
|
|
948
1527
|
* the server accepts an authenticated /access POST without a
|
|
949
1528
|
* password and returns the signed-URL block.
|
|
950
1529
|
*
|
|
@@ -963,7 +1542,9 @@ var CloudDocumentSecurityService = class {
|
|
|
963
1542
|
* password) and `establishAccess()` (no password). Updates cached
|
|
964
1543
|
* security state, caches the access block, and pushes the CDN
|
|
965
1544
|
* binding into the HttpClient so subsequent fetches apply CDN
|
|
966
|
-
* tokens via `applyCdnAccess`.
|
|
1545
|
+
* tokens via `applyCdnAccess`. It goes as the editing session, and a
|
|
1546
|
+
* caller that may create gets the session's object numbers with it
|
|
1547
|
+
* (`edit`).
|
|
967
1548
|
*
|
|
968
1549
|
* `none` CDN adapter → the access block has null overrides/policies
|
|
969
1550
|
* and `applyCdnAccess` short-circuits to origin; safe to call always.
|
|
@@ -971,27 +1552,33 @@ var CloudDocumentSecurityService = class {
|
|
|
971
1552
|
async postAccess(signal, body) {
|
|
972
1553
|
const response = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.access(this.docId, this.layerName), {
|
|
973
1554
|
...body.password ? { password: body.password } : {},
|
|
974
|
-
mode: body.mode
|
|
975
|
-
|
|
976
|
-
|
|
1555
|
+
mode: body.mode,
|
|
1556
|
+
...this.editing ? { objectNumbers: this.editing.wanted() } : {}
|
|
1557
|
+
}, (raw) => _embedpdf_engine_core_wire.AccessResponseSchema.parse(raw), signal, { session: true });
|
|
1558
|
+
this.securityState = response.security;
|
|
1559
|
+
this.protection = response.protection;
|
|
977
1560
|
this.access = {
|
|
978
1561
|
cdn: response.cdn,
|
|
979
1562
|
passwordGrant: response.passwordGrant,
|
|
980
1563
|
pdfPermissions: response.pdfPermissions,
|
|
981
1564
|
scope: response.scope,
|
|
982
1565
|
effectiveScope: response.effectiveScope,
|
|
1566
|
+
protection: response.protection,
|
|
983
1567
|
identity: response.identity,
|
|
984
1568
|
originPasswordPolicy: response.originPasswordPolicy,
|
|
985
1569
|
expiresAt: response.expiresAt,
|
|
986
|
-
...response.renderPolicy ? { renderPolicy: response.renderPolicy } : {}
|
|
1570
|
+
...response.renderPolicy ? { renderPolicy: response.renderPolicy } : {},
|
|
1571
|
+
bundleLimits: response.bundleLimits,
|
|
1572
|
+
...response.edit ? { edit: response.edit } : {}
|
|
987
1573
|
};
|
|
1574
|
+
if (response.edit) this.editing?.opened(response.edit);
|
|
988
1575
|
this.http.setCdnAccess({
|
|
989
1576
|
cdn: response.cdn,
|
|
990
1577
|
docId: this.docId,
|
|
991
1578
|
layerName: this.layerName
|
|
992
1579
|
});
|
|
993
1580
|
return {
|
|
994
|
-
security: this.
|
|
1581
|
+
security: this.securityState,
|
|
995
1582
|
access: this.access ?? void 0
|
|
996
1583
|
};
|
|
997
1584
|
}
|
|
@@ -1011,25 +1598,39 @@ function safeDecodeClaims(token) {
|
|
|
1011
1598
|
}
|
|
1012
1599
|
}
|
|
1013
1600
|
/**
|
|
1014
|
-
*
|
|
1601
|
+
* Create's CollabTarget is the caller's own identity — the same
|
|
1015
1602
|
* derivation as engine-local's `ScopeGuard.targetForSelfCreate`, so
|
|
1016
1603
|
* `:self` trivially passes and `:group=X` matches the caller's
|
|
1017
1604
|
* default group.
|
|
1018
1605
|
*/
|
|
1019
1606
|
function selfTarget(id) {
|
|
1020
1607
|
return {
|
|
1021
|
-
...id.
|
|
1022
|
-
...id.
|
|
1608
|
+
...id.userId !== void 0 ? { userId: id.userId } : {},
|
|
1609
|
+
...id.groupId !== void 0 ? { groupId: id.groupId } : {}
|
|
1023
1610
|
};
|
|
1024
1611
|
}
|
|
1612
|
+
const IDENTITY_STRING_FIELDS = [
|
|
1613
|
+
"userId",
|
|
1614
|
+
"displayName",
|
|
1615
|
+
"email",
|
|
1616
|
+
"title",
|
|
1617
|
+
"organization",
|
|
1618
|
+
"organizationalUnit",
|
|
1619
|
+
"groupId"
|
|
1620
|
+
];
|
|
1621
|
+
/**
|
|
1622
|
+
* The token's `identity` claim, read the way the server reads it: string
|
|
1623
|
+
* fields, empty values absent. The server rejects a
|
|
1624
|
+
* malformed claim; unverified here, anything else is skipped.
|
|
1625
|
+
*/
|
|
1025
1626
|
function identityFromClaims(claims) {
|
|
1627
|
+
const claim = claims["identity"];
|
|
1628
|
+
if (!claim || typeof claim !== "object" || Array.isArray(claim)) return null;
|
|
1629
|
+
const record = claim;
|
|
1026
1630
|
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;
|
|
1631
|
+
for (const key of IDENTITY_STRING_FIELDS) {
|
|
1632
|
+
const value = record[key];
|
|
1633
|
+
if (typeof value === "string" && value.length > 0) out[key] = value;
|
|
1033
1634
|
}
|
|
1034
1635
|
return Object.keys(out).length > 0 ? out : null;
|
|
1035
1636
|
}
|
|
@@ -1038,11 +1639,11 @@ function identityFromClaims(claims) {
|
|
|
1038
1639
|
/**
|
|
1039
1640
|
* Cloud-side digital signatures. Two kinds of read, two kinds of URL:
|
|
1040
1641
|
*
|
|
1041
|
-
* - The
|
|
1642
|
+
* - The layer view (`list`, and `analyze` of the working copy) is pinned
|
|
1042
1643
|
* by the manifest's `docVersion`, like every other layer read: a
|
|
1043
1644
|
* signature is a layer state change, the immutable URL moves with it.
|
|
1044
|
-
* - Signed
|
|
1045
|
-
* of history) belong to a base
|
|
1645
|
+
* - Signed bytes (`contents`, `digest`, `revisionBytes`, and `analyze`
|
|
1646
|
+
* of history) belong to a base version and are content-addressed by
|
|
1046
1647
|
* the manifest's `baseSha`: immutable forever, shared by every layer
|
|
1047
1648
|
* and every caller.
|
|
1048
1649
|
*
|
|
@@ -1052,13 +1653,14 @@ function identityFromClaims(claims) {
|
|
|
1052
1653
|
* refresh it before returning.
|
|
1053
1654
|
*/
|
|
1054
1655
|
var CloudDocumentSignaturesService = class {
|
|
1055
|
-
constructor(http, docId, layerName, isClosed, manifest, publisher) {
|
|
1656
|
+
constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
|
|
1056
1657
|
this.http = http;
|
|
1057
1658
|
this.docId = docId;
|
|
1058
1659
|
this.layerName = layerName;
|
|
1059
1660
|
this.isClosed = isClosed;
|
|
1060
1661
|
this.manifest = manifest;
|
|
1061
1662
|
this.publisher = publisher;
|
|
1663
|
+
this.writes = writes;
|
|
1062
1664
|
}
|
|
1063
1665
|
list() {
|
|
1064
1666
|
const rejected = this.closedRejection();
|
|
@@ -1070,7 +1672,7 @@ var CloudDocumentSignaturesService = class {
|
|
|
1070
1672
|
await this.manifest.refresh(s);
|
|
1071
1673
|
}, signal));
|
|
1072
1674
|
}
|
|
1073
|
-
|
|
1675
|
+
getContents(field) {
|
|
1074
1676
|
const rejected = this.closedRejection();
|
|
1075
1677
|
if (rejected) return rejected;
|
|
1076
1678
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
@@ -1079,7 +1681,7 @@ var CloudDocumentSignaturesService = class {
|
|
|
1079
1681
|
return this.http.getBytes(_embedpdf_engine_core_wire.wirePaths.docVersionSignatureContents(this.docId, manifest.baseSha, name), signal);
|
|
1080
1682
|
});
|
|
1081
1683
|
}
|
|
1082
|
-
|
|
1684
|
+
getDigest(field, algorithm) {
|
|
1083
1685
|
const rejected = this.closedRejection();
|
|
1084
1686
|
if (rejected) return rejected;
|
|
1085
1687
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
@@ -1088,7 +1690,7 @@ var CloudDocumentSignaturesService = class {
|
|
|
1088
1690
|
return this.http.getBytes(_embedpdf_engine_core_wire.wirePaths.docVersionSignatureDigest(this.docId, manifest.baseSha, name, algorithm), signal);
|
|
1089
1691
|
});
|
|
1090
1692
|
}
|
|
1091
|
-
|
|
1693
|
+
downloadRevision(revisionIndex) {
|
|
1092
1694
|
const rejected = this.closedRejection();
|
|
1093
1695
|
if (rejected) return rejected;
|
|
1094
1696
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
@@ -1097,16 +1699,17 @@ var CloudDocumentSignaturesService = class {
|
|
|
1097
1699
|
});
|
|
1098
1700
|
}
|
|
1099
1701
|
/**
|
|
1100
|
-
* `until: 'working-copy'`
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1702
|
+
* `until: 'working-copy'` judges the layer's pending edits against the
|
|
1703
|
+
* base at the layer URL; anything else, the default `'persisted'`
|
|
1704
|
+
* included, is history of the base version and resolves at the version
|
|
1705
|
+
* URL (the saved file, as the local engine judges by default).
|
|
1103
1706
|
*/
|
|
1104
1707
|
analyze(input) {
|
|
1105
1708
|
const rejected = this.closedRejection();
|
|
1106
1709
|
if (rejected) return rejected;
|
|
1107
1710
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1108
1711
|
const manifest = await this.manifest.get(signal);
|
|
1109
|
-
if (input.until === "working-copy"
|
|
1712
|
+
if (input.until === "working-copy") return this.http.getJsonWithRefresh(async (s) => {
|
|
1110
1713
|
const current = await this.manifest.get(s);
|
|
1111
1714
|
return _embedpdf_engine_core_wire.wirePaths.layerSignaturesAnalysis(this.docId, this.layerName, {
|
|
1112
1715
|
docVersion: current.docVersion,
|
|
@@ -1125,65 +1728,65 @@ var CloudDocumentSignaturesService = class {
|
|
|
1125
1728
|
}), (raw) => _embedpdf_engine_core_wire.ChangeAnalysisSchema.parse(raw), signal);
|
|
1126
1729
|
});
|
|
1127
1730
|
}
|
|
1128
|
-
prepare(input) {
|
|
1731
|
+
prepare(input, options) {
|
|
1129
1732
|
const rejected = this.closedRejection();
|
|
1130
1733
|
if (rejected) return rejected;
|
|
1131
1734
|
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
|
|
1735
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1736
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1737
|
+
const { appearance, ...rest } = input;
|
|
1738
|
+
const form = buildRoleMutationForm(rest, { appearance: appearance && {
|
|
1739
|
+
bytes: appearance.pdf.slice().buffer,
|
|
1740
|
+
mimeType: "application/pdf",
|
|
1741
|
+
name: "appearance.pdf"
|
|
1742
|
+
} });
|
|
1743
|
+
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));
|
|
1744
|
+
await this.manifest.refresh(signal);
|
|
1745
|
+
this.publisher.publishWrite(opId, {
|
|
1746
|
+
type: "signatures.prepared",
|
|
1747
|
+
field: input.field,
|
|
1748
|
+
...prepared
|
|
1749
|
+
});
|
|
1750
|
+
return prepared;
|
|
1150
1751
|
});
|
|
1151
|
-
return prepared;
|
|
1152
1752
|
});
|
|
1153
1753
|
}
|
|
1154
|
-
complete(input) {
|
|
1754
|
+
complete(input, options) {
|
|
1155
1755
|
const rejected = this.closedRejection();
|
|
1156
1756
|
if (rejected) return rejected;
|
|
1157
1757
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
this.
|
|
1165
|
-
|
|
1758
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1759
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1760
|
+
const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerSignatureComplete(this.docId, this.layerName, input.signingId), {
|
|
1761
|
+
cms: (0, _embedpdf_engine_core_wire.toBase64)(input.cms),
|
|
1762
|
+
expectedVersion: input.expectedVersion
|
|
1763
|
+
}, (raw) => _embedpdf_engine_core_wire.SignatureCompleteResultSchema.parse(raw), signal, sent));
|
|
1764
|
+
await this.manifest.refresh(signal);
|
|
1765
|
+
if (result.status === "completed") this.publisher.publishWrite(opId, {
|
|
1766
|
+
type: "signatures.completed",
|
|
1166
1767
|
signingId: input.signingId,
|
|
1167
1768
|
...result
|
|
1168
|
-
}
|
|
1169
|
-
this.publisher.publishLocal({
|
|
1769
|
+
}, {
|
|
1170
1770
|
type: "document.versioned",
|
|
1171
1771
|
version: result.version
|
|
1172
1772
|
});
|
|
1173
|
-
|
|
1174
|
-
|
|
1773
|
+
return result;
|
|
1774
|
+
});
|
|
1175
1775
|
});
|
|
1176
1776
|
}
|
|
1177
|
-
|
|
1777
|
+
cancel(signingId, options) {
|
|
1178
1778
|
const rejected = this.closedRejection();
|
|
1179
1779
|
if (rejected) return rejected;
|
|
1180
1780
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1781
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1782
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
1783
|
+
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));
|
|
1784
|
+
if (result.status === "cancelled") this.publisher.publishWrite(opId, {
|
|
1785
|
+
type: "signatures.cancelled",
|
|
1786
|
+
signingId
|
|
1787
|
+
});
|
|
1788
|
+
return result;
|
|
1185
1789
|
});
|
|
1186
|
-
return result;
|
|
1187
1790
|
});
|
|
1188
1791
|
}
|
|
1189
1792
|
/** Version routes address a field by its fully qualified name; an object-number ref is resolved through the snapshot. */
|
|
@@ -1194,8 +1797,8 @@ var CloudDocumentSignaturesService = class {
|
|
|
1194
1797
|
return _embedpdf_engine_core_wire.wirePaths.layerSignatures(this.docId, this.layerName, manifest.docVersion);
|
|
1195
1798
|
}, (raw) => _embedpdf_engine_core_wire.SignatureSnapshotSchema.parse(raw), async (s) => {
|
|
1196
1799
|
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.
|
|
1800
|
+
}, signal)).signatures.find((s) => s.field.kind === "objectNumber" && s.field.objectNumber === field.objectNumber);
|
|
1801
|
+
if (!match) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no signature field #${field.objectNumber}`);
|
|
1199
1802
|
return match.fieldName;
|
|
1200
1803
|
}
|
|
1201
1804
|
closedRejection() {
|
|
@@ -1205,50 +1808,330 @@ var CloudDocumentSignaturesService = class {
|
|
|
1205
1808
|
};
|
|
1206
1809
|
//#endregion
|
|
1207
1810
|
//#region src/document/CloudMetadataService.ts
|
|
1811
|
+
const STANDARD = {
|
|
1812
|
+
docPath: _embedpdf_engine_core_wire.wirePaths.docMetadata,
|
|
1813
|
+
layerPath: _embedpdf_engine_core_wire.wirePaths.layerMetadata,
|
|
1814
|
+
updatePath: _embedpdf_engine_core_wire.wirePaths.layerMetadataUpdate,
|
|
1815
|
+
parse: (raw) => _embedpdf_engine_core_wire.DocumentMetadataSchema.parse(raw),
|
|
1816
|
+
parseResult: (raw) => _embedpdf_engine_core_wire.MetadataUpdateResultSchema.parse(raw)
|
|
1817
|
+
};
|
|
1818
|
+
const CUSTOM = {
|
|
1819
|
+
docPath: _embedpdf_engine_core_wire.wirePaths.docCustomMetadata,
|
|
1820
|
+
layerPath: _embedpdf_engine_core_wire.wirePaths.layerCustomMetadata,
|
|
1821
|
+
updatePath: _embedpdf_engine_core_wire.wirePaths.layerCustomMetadataUpdate,
|
|
1822
|
+
parse: (raw) => _embedpdf_engine_core_wire.CustomMetadataSchema.parse(raw),
|
|
1823
|
+
parseResult: (raw) => _embedpdf_engine_core_wire.CustomMetadataUpdateResultSchema.parse(raw)
|
|
1824
|
+
};
|
|
1825
|
+
function closedError(docId) {
|
|
1826
|
+
return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${docId} is closed`);
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* The read. The JSON lives at a content-addressed `…@metadataVersion=N` leaf
|
|
1830
|
+
* (not in the manifest); the manifest only publishes the `metadataVersion`
|
|
1831
|
+
* pointer, which both halves of the dict share. So the read pulls it from
|
|
1832
|
+
* the cached manifest, fetches the leaf, and on a 404 (stale pointer)
|
|
1833
|
+
* transparently refreshes the manifest and retries once. `metadataVersion`
|
|
1834
|
+
* bumps only on metadata writes, so the leaf stays cached across page and
|
|
1835
|
+
* annotation edits.
|
|
1836
|
+
*/
|
|
1837
|
+
function readLeaf(deps, leaf) {
|
|
1838
|
+
if (deps.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(closedError(deps.docId));
|
|
1839
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1840
|
+
const buildPath = async (s) => {
|
|
1841
|
+
const manifest = await deps.manifest.get(s);
|
|
1842
|
+
return planesInherited(manifest, ["metadata"]) ? leaf.docPath(deps.docId, manifest.metadataVersion) : leaf.layerPath(deps.docId, deps.layerName, manifest.metadataVersion);
|
|
1843
|
+
};
|
|
1844
|
+
return deps.http.getJsonWithRefresh(buildPath, leaf.parse, async (s) => {
|
|
1845
|
+
await deps.manifest.refresh(s);
|
|
1846
|
+
}, signal);
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* The write. It only advances docVersion + metadataVersion (no per-page pin
|
|
1851
|
+
* changes, no layoutVersion), so the cached manifest is patched in place —
|
|
1852
|
+
* no refetch. `publish` gets the write's `opId`.
|
|
1853
|
+
*/
|
|
1854
|
+
function writeLeaf(deps, leaf, patch, options, publish) {
|
|
1855
|
+
if (deps.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(closedError(deps.docId));
|
|
1856
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1857
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1858
|
+
return deps.writes.run(opId, signal, async (write) => {
|
|
1859
|
+
const result = await write.send((sent) => deps.http.postJson(leaf.updatePath(deps.docId, deps.layerName), patch, leaf.parseResult, signal, sent));
|
|
1860
|
+
deps.manifest.apply(result.meta, ["metadata"]);
|
|
1861
|
+
publish(result, opId);
|
|
1862
|
+
return result;
|
|
1863
|
+
});
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
var CloudCustomMetadataService = class {
|
|
1867
|
+
constructor(deps) {
|
|
1868
|
+
this.deps = deps;
|
|
1869
|
+
}
|
|
1870
|
+
get() {
|
|
1871
|
+
return readLeaf(this.deps, CUSTOM);
|
|
1872
|
+
}
|
|
1873
|
+
update(patch, options) {
|
|
1874
|
+
return writeLeaf(this.deps, CUSTOM, patch, options, (result, opId) => this.deps.publisher.publishWrite(opId, {
|
|
1875
|
+
type: "metadata.customUpdated",
|
|
1876
|
+
...result
|
|
1877
|
+
}));
|
|
1878
|
+
}
|
|
1879
|
+
};
|
|
1208
1880
|
var CloudMetadataService = class {
|
|
1209
|
-
constructor(http, docId, layerName, isClosed, manifest, publisher) {
|
|
1210
|
-
this
|
|
1211
|
-
this
|
|
1212
|
-
this.
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1881
|
+
constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
|
|
1882
|
+
_defineProperty(this, "custom", void 0);
|
|
1883
|
+
_defineProperty(this, "deps", void 0);
|
|
1884
|
+
this.deps = {
|
|
1885
|
+
http,
|
|
1886
|
+
docId,
|
|
1887
|
+
layerName,
|
|
1888
|
+
isClosed,
|
|
1889
|
+
manifest,
|
|
1890
|
+
publisher,
|
|
1891
|
+
writes
|
|
1892
|
+
};
|
|
1893
|
+
this.custom = new CloudCustomMetadataService(this.deps);
|
|
1216
1894
|
}
|
|
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
|
-
});
|
|
1895
|
+
get() {
|
|
1896
|
+
return readLeaf(this.deps, STANDARD);
|
|
1237
1897
|
}
|
|
1238
|
-
update(patch) {
|
|
1239
|
-
|
|
1898
|
+
update(patch, options) {
|
|
1899
|
+
return writeLeaf(this.deps, STANDARD, patch, options, (result, opId) => this.deps.publisher.publishWrite(opId, {
|
|
1900
|
+
type: "metadata.updated",
|
|
1901
|
+
...result
|
|
1902
|
+
}));
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
//#endregion
|
|
1906
|
+
//#region src/document/CloudObjectNumberPool.ts
|
|
1907
|
+
/** Below this many held numbers, a write asks for a top-up… */
|
|
1908
|
+
const LOW_WATER = 8;
|
|
1909
|
+
/** …of up to this many, the most the server tops up by. */
|
|
1910
|
+
const TOP_UP = 32;
|
|
1911
|
+
/** The most one bulk reservation asks for. */
|
|
1912
|
+
const MAX_RESERVATION = 1e3;
|
|
1913
|
+
/** An editing session's lifetime (ms) until the server states one: the protocol's 15 minutes. */
|
|
1914
|
+
const DEFAULT_LIFETIME_MS = 15 * 6e4;
|
|
1915
|
+
/**
|
|
1916
|
+
* The cloud engine's object numbers: what the server handed this editing
|
|
1917
|
+
* session (the engine's `X-Engine-Session-Id` with the token's subject).
|
|
1918
|
+
*
|
|
1919
|
+
* Numbers arrive with `/access`, with a write's response (the top-up a
|
|
1920
|
+
* write asks for when fewer than 8 are held), with the event stream's
|
|
1921
|
+
* `session` events, and from `reserve`'s bulk reservation. A number
|
|
1922
|
+
* `take` hands out is the caller's until a write naming it commits.
|
|
1923
|
+
*
|
|
1924
|
+
* The session lives while the event stream is connected or something
|
|
1925
|
+
* keeps it alive; without that it expires, and its numbers may go to
|
|
1926
|
+
* another session. So once its expiry has passed while the stream is down,
|
|
1927
|
+
* the pool hands out nothing until the server says again what it holds.
|
|
1928
|
+
* The stream's `session` event lists everything the session holds: any
|
|
1929
|
+
* number this pool held or handed out that isn't on it, and that no write
|
|
1930
|
+
* in flight names, is lost (`onLost`, `'reclaimed'`). A new version of the
|
|
1931
|
+
* document numbers objects anew: everything is lost (`'versioned'`).
|
|
1932
|
+
*/
|
|
1933
|
+
var CloudObjectNumberPool = class {
|
|
1934
|
+
constructor(opts) {
|
|
1935
|
+
this.opts = opts;
|
|
1936
|
+
_defineProperty(this, "now", void 0);
|
|
1937
|
+
_defineProperty(this, "available", []);
|
|
1938
|
+
_defineProperty(this, "taken", /* @__PURE__ */ new Set());
|
|
1939
|
+
_defineProperty(this, "writing", /* @__PURE__ */ new Map());
|
|
1940
|
+
_defineProperty(this, "expiresAt", 0);
|
|
1941
|
+
_defineProperty(this, "halfLifetimeMs", DEFAULT_LIFETIME_MS / 2);
|
|
1942
|
+
_defineProperty(this, "streamConnected", false);
|
|
1943
|
+
_defineProperty(this, "listeners", /* @__PURE__ */ new Set());
|
|
1944
|
+
_defineProperty(this, "reserving", Promise.resolve());
|
|
1945
|
+
_defineProperty(this, "version", null);
|
|
1946
|
+
this.now = opts.now ?? Date.now;
|
|
1947
|
+
}
|
|
1948
|
+
get held() {
|
|
1949
|
+
return this.usable() ? this.available.length : 0;
|
|
1950
|
+
}
|
|
1951
|
+
take() {
|
|
1952
|
+
if (!this.usable()) return null;
|
|
1953
|
+
const objectNumber = this.available.shift();
|
|
1954
|
+
if (objectNumber === void 0) return null;
|
|
1955
|
+
this.taken.add(objectNumber);
|
|
1956
|
+
return objectNumber;
|
|
1957
|
+
}
|
|
1958
|
+
reserve(count) {
|
|
1240
1959
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1960
|
+
const turn = this.reserving.then(async () => {
|
|
1961
|
+
while (this.held < count) {
|
|
1962
|
+
const wanted = Math.min(count - this.held, MAX_RESERVATION);
|
|
1963
|
+
const reserved = await this.opts.reserve(wanted, signal);
|
|
1964
|
+
this.receive(reserved.objectNumbers, reserved.expiresIn);
|
|
1965
|
+
}
|
|
1246
1966
|
});
|
|
1247
|
-
|
|
1967
|
+
this.reserving = turn.catch(() => void 0);
|
|
1968
|
+
await turn;
|
|
1248
1969
|
});
|
|
1249
1970
|
}
|
|
1971
|
+
onLost(listener) {
|
|
1972
|
+
this.listeners.add(listener);
|
|
1973
|
+
return () => {
|
|
1974
|
+
this.listeners.delete(listener);
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
/** How many numbers a write should ask the server for: 0 while enough are held. */
|
|
1978
|
+
topUp() {
|
|
1979
|
+
const held = this.held;
|
|
1980
|
+
return held < LOW_WATER ? TOP_UP - held : 0;
|
|
1981
|
+
}
|
|
1982
|
+
/** How many numbers `/access` should ask for: enough to make up the first block. */
|
|
1983
|
+
wantedAtAccess() {
|
|
1984
|
+
return Math.max(0, LOW_WATER - this.held);
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1987
|
+
* `/access` answered for the editing session. A session the server made
|
|
1988
|
+
* anew holds nothing of what this pool had.
|
|
1989
|
+
*/
|
|
1990
|
+
opened(edit) {
|
|
1991
|
+
if (edit.session === "new") this.lose(this.unspent(), "reclaimed");
|
|
1992
|
+
this.receive(edit.objectNumbers, edit.expiresIn);
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Numbers handed to the session: by `/access`, a write's top-up or the
|
|
1996
|
+
* bulk reservation; `expiresIn` (seconds) when the answer told it. Numbers
|
|
1997
|
+
* held from before an expiry the server can't vouch for any more are
|
|
1998
|
+
* dropped: only a `session` event says which survived.
|
|
1999
|
+
*/
|
|
2000
|
+
receive(ranges, expiresIn) {
|
|
2001
|
+
if (expiresIn !== void 0) {
|
|
2002
|
+
if (!this.usable()) this.lose(this.available.splice(0), "reclaimed");
|
|
2003
|
+
this.expiresIn(expiresIn);
|
|
2004
|
+
}
|
|
2005
|
+
this.add((0, _embedpdf_engine_core_runtime.objectNumbersIn)(ranges));
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* The event stream's `session` event: everything the session holds now.
|
|
2009
|
+
* What this pool held or handed out and the list doesn't name, unless a
|
|
2010
|
+
* write in flight names it, is lost.
|
|
2011
|
+
*/
|
|
2012
|
+
sync(status) {
|
|
2013
|
+
this.expiresIn(status.expiresIn);
|
|
2014
|
+
const held = new Set((0, _embedpdf_engine_core_runtime.objectNumbersIn)(status.held));
|
|
2015
|
+
const lost = this.unspent().filter((n) => !held.has(n));
|
|
2016
|
+
for (const n of lost) this.taken.delete(n);
|
|
2017
|
+
this.available = [...held].filter((n) => !this.taken.has(n) && !this.writing.has(n)).sort((a, b) => a - b);
|
|
2018
|
+
this.emit(lost, "reclaimed");
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Version `sha256` was published: it numbers objects anew, so every number
|
|
2022
|
+
* held or handed out is gone. Told once per version (the stream and this
|
|
2023
|
+
* engine's own completion both tell it, in either order).
|
|
2024
|
+
*/
|
|
2025
|
+
versioned(sha256) {
|
|
2026
|
+
if (sha256 === this.version) return;
|
|
2027
|
+
this.version = sha256;
|
|
2028
|
+
this.lose(this.unspent(), "versioned");
|
|
2029
|
+
}
|
|
2030
|
+
/** The event stream connected or dropped: while it's up, the server keeps the session alive. */
|
|
2031
|
+
connected(connected) {
|
|
2032
|
+
this.streamConnected = connected;
|
|
2033
|
+
}
|
|
2034
|
+
/** A write that names `numbers` was called. */
|
|
2035
|
+
beginWrite(numbers) {
|
|
2036
|
+
for (const n of numbers) this.writing.set(n, (this.writing.get(n) ?? 0) + 1);
|
|
2037
|
+
}
|
|
2038
|
+
/**
|
|
2039
|
+
* The write that named `numbers` settled. A committed write also kept the
|
|
2040
|
+
* session alive: at least half its lifetime is left.
|
|
2041
|
+
*/
|
|
2042
|
+
endWrite(numbers, outcome) {
|
|
2043
|
+
for (const n of numbers) {
|
|
2044
|
+
const left = (this.writing.get(n) ?? 1) - 1;
|
|
2045
|
+
if (left > 0) this.writing.set(n, left);
|
|
2046
|
+
else this.writing.delete(n);
|
|
2047
|
+
if (outcome !== "failed") this.taken.delete(n);
|
|
2048
|
+
}
|
|
2049
|
+
if (outcome === "committed") this.expiresAt = Math.max(this.expiresAt, this.now() + this.halfLifetimeMs);
|
|
2050
|
+
}
|
|
2051
|
+
usable() {
|
|
2052
|
+
return this.streamConnected || this.now() < this.expiresAt;
|
|
2053
|
+
}
|
|
2054
|
+
expiresIn(seconds) {
|
|
2055
|
+
const ms = seconds * 1e3;
|
|
2056
|
+
this.expiresAt = this.now() + ms;
|
|
2057
|
+
this.halfLifetimeMs = Math.max(this.halfLifetimeMs, ms / 2);
|
|
2058
|
+
}
|
|
2059
|
+
add(numbers) {
|
|
2060
|
+
const known = /* @__PURE__ */ new Set([...this.available, ...this.taken]);
|
|
2061
|
+
const fresh = numbers.filter((n) => !known.has(n) && !this.writing.has(n));
|
|
2062
|
+
if (fresh.length === 0) return;
|
|
2063
|
+
this.available = [...this.available, ...fresh].sort((a, b) => a - b);
|
|
2064
|
+
}
|
|
2065
|
+
/** Held or handed out, and named by no write in flight. */
|
|
2066
|
+
unspent() {
|
|
2067
|
+
return [...this.available, ...this.taken].filter((n) => !this.writing.has(n));
|
|
2068
|
+
}
|
|
2069
|
+
lose(numbers, reason) {
|
|
2070
|
+
const gone = new Set(numbers);
|
|
2071
|
+
this.available = this.available.filter((n) => !gone.has(n));
|
|
2072
|
+
for (const n of gone) this.taken.delete(n);
|
|
2073
|
+
this.emit([...gone], reason);
|
|
2074
|
+
}
|
|
2075
|
+
emit(numbers, reason) {
|
|
2076
|
+
if (numbers.length === 0) return;
|
|
2077
|
+
const lost = {
|
|
2078
|
+
numbers: [...numbers].sort((a, b) => a - b),
|
|
2079
|
+
reason
|
|
2080
|
+
};
|
|
2081
|
+
for (const listener of [...this.listeners]) listener(lost);
|
|
2082
|
+
}
|
|
1250
2083
|
};
|
|
1251
2084
|
//#endregion
|
|
2085
|
+
//#region src/document/appearanceForm.ts
|
|
2086
|
+
/**
|
|
2087
|
+
* Parse the appearance `multipart/form-data` response into the same
|
|
2088
|
+
* `AnnotationAppearanceImagesResult` shape the local engine produces. The
|
|
2089
|
+
* `body` part is validated against the wire schema; each image (the part
|
|
2090
|
+
* `resource:<key>` its entry names) is wrapped in a `PageImageHandle`
|
|
2091
|
+
* backed by the in-memory blob we already downloaded.
|
|
2092
|
+
*/
|
|
2093
|
+
async function parseAppearanceForm(form) {
|
|
2094
|
+
const raw = form.get("body");
|
|
2095
|
+
if (typeof raw !== "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, "appearance response missing its body part");
|
|
2096
|
+
let parsedJson;
|
|
2097
|
+
try {
|
|
2098
|
+
parsedJson = JSON.parse(raw);
|
|
2099
|
+
} catch (err) {
|
|
2100
|
+
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `appearance body is not valid JSON: ${err?.message ?? err}`);
|
|
2101
|
+
}
|
|
2102
|
+
const body = _embedpdf_engine_core_wire.AnnotationAppearanceBatchSchema.parse(parsedJson);
|
|
2103
|
+
const appearances = await Promise.all(body.appearances.map(async (entry) => {
|
|
2104
|
+
const name = `resource:${entry.resources.image}`;
|
|
2105
|
+
const partValue = form.get(name);
|
|
2106
|
+
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}"`);
|
|
2107
|
+
const blob = partValue;
|
|
2108
|
+
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
2109
|
+
const image = (0, _embedpdf_engine_core_runtime.createPageImageHandle)({
|
|
2110
|
+
width: entry.width,
|
|
2111
|
+
height: entry.height,
|
|
2112
|
+
format: entry.format,
|
|
2113
|
+
contentType: entry.contentType,
|
|
2114
|
+
source: {
|
|
2115
|
+
kind: "bytes",
|
|
2116
|
+
bytes
|
|
2117
|
+
}
|
|
2118
|
+
}, { async blob() {
|
|
2119
|
+
return blob;
|
|
2120
|
+
} });
|
|
2121
|
+
return {
|
|
2122
|
+
ref: entry.ref,
|
|
2123
|
+
mode: entry.mode,
|
|
2124
|
+
state: entry.state,
|
|
2125
|
+
rect: entry.rect,
|
|
2126
|
+
image
|
|
2127
|
+
};
|
|
2128
|
+
}));
|
|
2129
|
+
return {
|
|
2130
|
+
page: body.page,
|
|
2131
|
+
appearances
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
//#endregion
|
|
1252
2135
|
//#region src/document/CloudPageAnnotationsService.ts
|
|
1253
2136
|
/**
|
|
1254
2137
|
* Cloud-side page annotation service. Mirrors the local wiring: each
|
|
@@ -1260,45 +2143,46 @@ var CloudMetadataService = class {
|
|
|
1260
2143
|
* manifest. Mutations use unversioned layer URLs and are never cached.
|
|
1261
2144
|
*/
|
|
1262
2145
|
var CloudPageAnnotationsService = class {
|
|
1263
|
-
constructor(http, docId, layerName,
|
|
2146
|
+
constructor(http, docId, layerName, pageRef, isClosed, manifest, publisher, writes) {
|
|
1264
2147
|
this.http = http;
|
|
1265
2148
|
this.docId = docId;
|
|
1266
2149
|
this.layerName = layerName;
|
|
1267
|
-
this.
|
|
2150
|
+
this.pageRef = pageRef;
|
|
1268
2151
|
this.isClosed = isClosed;
|
|
1269
2152
|
this.manifest = manifest;
|
|
1270
2153
|
this.publisher = publisher;
|
|
2154
|
+
this.writes = writes;
|
|
1271
2155
|
}
|
|
1272
2156
|
list() {
|
|
1273
2157
|
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
2158
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1275
2159
|
const buildPath = async (s) => {
|
|
1276
2160
|
const manifest = await this.manifest.get(s);
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
|
|
2161
|
+
const pageObjectNumber = this.pageRef.objectNumber;
|
|
2162
|
+
const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
|
|
2163
|
+
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}`);
|
|
2164
|
+
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
2165
|
};
|
|
1281
|
-
return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.
|
|
2166
|
+
return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.AnnotationListSchema.parse(raw), async (s) => {
|
|
1282
2167
|
await this.manifest.refresh(s);
|
|
1283
2168
|
}, signal);
|
|
1284
2169
|
});
|
|
1285
2170
|
}
|
|
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 = {}) {
|
|
2171
|
+
renderAppearances(options = {}) {
|
|
1290
2172
|
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
2173
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
2174
|
+
(0, _embedpdf_engine_core_runtime.checkImageQuality)(options.quality);
|
|
1292
2175
|
const format = options.format ?? "webp";
|
|
1293
2176
|
const buildPath = async (s) => {
|
|
1294
2177
|
const manifest = await this.manifest.get(s);
|
|
1295
|
-
const
|
|
1296
|
-
|
|
2178
|
+
const pageObjectNumber = this.pageRef.objectNumber;
|
|
2179
|
+
const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
|
|
2180
|
+
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
2181
|
const wireToken = (0, _embedpdf_engine_core_wire.annotationAppearancesImageOptionsToWire)({
|
|
1298
2182
|
...options,
|
|
1299
2183
|
format
|
|
1300
2184
|
}, { annotationVersion: page.cache.annotationVersion });
|
|
1301
|
-
return planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageAnnotationAppearances(this.docId, this.
|
|
2185
|
+
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
2186
|
};
|
|
1303
2187
|
return parseAppearanceForm(await this.http.getFormDataWithRefresh(buildPath, async (s) => {
|
|
1304
2188
|
await this.manifest.refresh(s);
|
|
@@ -1306,121 +2190,140 @@ var CloudPageAnnotationsService = class {
|
|
|
1306
2190
|
});
|
|
1307
2191
|
}
|
|
1308
2192
|
/**
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1311
|
-
*
|
|
1312
|
-
*
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1315
|
-
* id — the same `:annotKey` routing update()/delete() use.
|
|
2193
|
+
* One of the annotation's resources. `file` is a read over the immutable
|
|
2194
|
+
* `attachment-files` leaf, pinned by the manifest's `attachmentsVersion`
|
|
2195
|
+
* (annotation-level files re-key on the same pin as document-level ones),
|
|
2196
|
+
* with the same stale-404 refresh retry as {@link list}. `appearance` is a
|
|
2197
|
+
* derived read (no-store), like {@link exportAppearance}. Both address the
|
|
2198
|
+
* annotation by the same `:annotKey` update()/delete() use.
|
|
1316
2199
|
*/
|
|
1317
|
-
|
|
2200
|
+
downloadResource(ref, role) {
|
|
1318
2201
|
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
|
-
|
|
2202
|
+
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}`));
|
|
2203
|
+
const annotKey = (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(ref);
|
|
2204
|
+
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
2205
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1323
2206
|
const buildPath = async (s) => {
|
|
1324
2207
|
const manifest = await this.manifest.get(s);
|
|
1325
|
-
return planesInherited(manifest, ["annotations", "attachments"]) ? _embedpdf_engine_core_wire.wirePaths.docAnnotationFile(this.docId, this.
|
|
2208
|
+
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
2209
|
};
|
|
1327
|
-
return
|
|
2210
|
+
return (await this.http.getFileWithRefresh(buildPath, async (s) => {
|
|
1328
2211
|
await this.manifest.refresh(s);
|
|
1329
|
-
}, signal));
|
|
2212
|
+
}, signal)).bytes;
|
|
1330
2213
|
});
|
|
1331
2214
|
}
|
|
1332
|
-
create(draft) {
|
|
2215
|
+
create(draft, options = {}) {
|
|
1333
2216
|
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
|
-
|
|
2217
|
+
let opId;
|
|
2218
|
+
try {
|
|
2219
|
+
opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
2220
|
+
} catch (err) {
|
|
2221
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
|
|
2222
|
+
}
|
|
2223
|
+
const { resources, objectNumber } = options;
|
|
2224
|
+
const data = (0, _embedpdf_engine_core_runtime.withFileFromResource)(draft, resources);
|
|
2225
|
+
const path = withObjectNumbers(_embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsCreate(this.docId, this.layerName, this.pageRef), { objectNumber });
|
|
2226
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
|
|
2227
|
+
const wireResources = await (0, _embedpdf_engine_core_runtime.resolveAnnotationResources)(resources);
|
|
1337
2228
|
const parse = (raw) => _embedpdf_engine_core_wire.AnnotationCreateResultSchema.parse(raw);
|
|
1338
|
-
const result =
|
|
1339
|
-
return this.absorbMutation(result, "
|
|
1340
|
-
});
|
|
2229
|
+
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));
|
|
2230
|
+
return this.absorbMutation(opId, result, "annotations.created");
|
|
2231
|
+
}, [objectNumber]));
|
|
1341
2232
|
}
|
|
1342
|
-
update(ref, patch) {
|
|
2233
|
+
update(ref, patch, options = {}) {
|
|
1343
2234
|
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
|
-
});
|
|
2235
|
+
let opId;
|
|
2236
|
+
try {
|
|
2237
|
+
opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
2238
|
+
} catch (err) {
|
|
2239
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
|
|
1355
2240
|
}
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
const
|
|
1361
|
-
|
|
1362
|
-
|
|
2241
|
+
const { resources } = options;
|
|
2242
|
+
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}`));
|
|
2243
|
+
const path = _embedpdf_engine_core_wire.wirePaths.layerAnnotationByKey(this.docId, this.layerName, this.pageRef, (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(ref));
|
|
2244
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
|
|
2245
|
+
const wireResources = await (0, _embedpdf_engine_core_runtime.resolveAnnotationResources)(resources);
|
|
2246
|
+
const result = await this.patchMutation(write, path, { patch }, wireResources, signal);
|
|
2247
|
+
return this.absorbMutation(opId, result, "annotations.updated");
|
|
2248
|
+
}));
|
|
1363
2249
|
}
|
|
1364
|
-
/** PATCH as plain JSON, or as multipart when the patch
|
|
1365
|
-
patchMutation(path, body, resources, signal) {
|
|
2250
|
+
/** PATCH as plain JSON, or as multipart when resources came with the patch. */
|
|
2251
|
+
patchMutation(write, path, body, resources, signal) {
|
|
1366
2252
|
const parse = (raw) => _embedpdf_engine_core_wire.AnnotationUpdateResultSchema.parse(raw);
|
|
1367
|
-
|
|
1368
|
-
return this.http.patchJson(path, body, parse, signal);
|
|
2253
|
+
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
2254
|
}
|
|
1370
|
-
delete(ref) {
|
|
2255
|
+
delete(ref, options) {
|
|
1371
2256
|
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
|
-
});
|
|
2257
|
+
let opId;
|
|
2258
|
+
try {
|
|
2259
|
+
opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
2260
|
+
} catch (err) {
|
|
2261
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
|
|
1382
2262
|
}
|
|
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
|
-
});
|
|
2263
|
+
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}`));
|
|
2264
|
+
const path = _embedpdf_engine_core_wire.wirePaths.layerAnnotationByKey(this.docId, this.layerName, this.pageRef, (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(ref));
|
|
2265
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
|
|
2266
|
+
const result = await write.send((sent) => this.http.deleteJson(path, (raw) => _embedpdf_engine_core_wire.AnnotationDeleteResultSchema.parse(raw), signal, sent));
|
|
2267
|
+
return this.absorbDelete(opId, result);
|
|
2268
|
+
}));
|
|
1389
2269
|
}
|
|
1390
|
-
|
|
2270
|
+
reorder(refs, position, options) {
|
|
1391
2271
|
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
|
-
|
|
2272
|
+
let opId;
|
|
2273
|
+
try {
|
|
2274
|
+
opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
2275
|
+
} catch (err) {
|
|
2276
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
|
|
2277
|
+
}
|
|
2278
|
+
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}`));
|
|
2279
|
+
const path = _embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsReorder(this.docId, this.layerName, this.pageRef);
|
|
2280
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
|
|
2281
|
+
const result = await write.send((sent) => this.http.postJson(path, {
|
|
1396
2282
|
refs,
|
|
1397
|
-
|
|
1398
|
-
}, (raw) => _embedpdf_engine_core_wire.
|
|
1399
|
-
|
|
1400
|
-
|
|
2283
|
+
position
|
|
2284
|
+
}, (raw) => _embedpdf_engine_core_wire.AnnotationReorderResultSchema.parse(raw), signal, sent));
|
|
2285
|
+
this.manifest.apply(result.meta, ["annotations"]);
|
|
2286
|
+
this.publisher.publishWrite(opId, {
|
|
2287
|
+
type: "annotations.reordered",
|
|
2288
|
+
page: this.pageRef,
|
|
2289
|
+
...result
|
|
2290
|
+
});
|
|
2291
|
+
return result;
|
|
2292
|
+
}));
|
|
1401
2293
|
}
|
|
1402
|
-
flatten(refs,
|
|
2294
|
+
flatten(refs, options) {
|
|
2295
|
+
const usage = options?.usage ?? "display";
|
|
1403
2296
|
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
|
-
|
|
2297
|
+
let opId;
|
|
2298
|
+
try {
|
|
2299
|
+
opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
2300
|
+
} catch (err) {
|
|
2301
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
|
|
2302
|
+
}
|
|
2303
|
+
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}`));
|
|
2304
|
+
const path = _embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsFlatten(this.docId, this.layerName, this.pageRef);
|
|
2305
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
|
|
2306
|
+
const result = await write.send((sent) => this.http.postJson(path, {
|
|
1408
2307
|
refs,
|
|
1409
2308
|
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
|
-
|
|
2309
|
+
}, (raw) => _embedpdf_engine_core_wire.AnnotationFlattenResultSchema.parse(raw), signal, sent));
|
|
2310
|
+
if (result.meta.cacheDelta === null) return result;
|
|
2311
|
+
this.manifest.apply(result.meta, [
|
|
2312
|
+
"content",
|
|
2313
|
+
"annotations",
|
|
2314
|
+
"forms"
|
|
2315
|
+
]);
|
|
2316
|
+
this.publisher.publishWrite(opId, {
|
|
1414
2317
|
type: "annotations.flattened",
|
|
1415
2318
|
...result
|
|
1416
2319
|
});
|
|
1417
2320
|
return result;
|
|
1418
|
-
});
|
|
2321
|
+
}));
|
|
1419
2322
|
}
|
|
1420
2323
|
exportAppearance(refs) {
|
|
1421
2324
|
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.
|
|
2325
|
+
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}`));
|
|
2326
|
+
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
2327
|
}
|
|
1425
2328
|
/**
|
|
1426
2329
|
* Patch the cached manifest, then publish the mutation to the document's
|
|
@@ -1429,148 +2332,130 @@ var CloudPageAnnotationsService = class {
|
|
|
1429
2332
|
* `type` with the matching result by construction; the cast localizes that
|
|
1430
2333
|
* pairing here instead of widening every site.
|
|
1431
2334
|
*/
|
|
1432
|
-
|
|
2335
|
+
/** A delete's event names what went, for listeners that didn't delete it. */
|
|
2336
|
+
absorbDelete(opId, result) {
|
|
1433
2337
|
this.manifest.apply(result.meta, ["annotations"]);
|
|
1434
|
-
this.publisher.
|
|
2338
|
+
this.publisher.publishWrite(opId, {
|
|
2339
|
+
type: "annotations.deleted",
|
|
2340
|
+
page: this.pageRef,
|
|
2341
|
+
deleted: (0, _embedpdf_engine_core_runtime.deletedAnnotationsOf)(result),
|
|
2342
|
+
...result
|
|
2343
|
+
});
|
|
2344
|
+
return result;
|
|
2345
|
+
}
|
|
2346
|
+
absorbMutation(opId, result, type) {
|
|
2347
|
+
this.manifest.apply(result.meta, ["annotations"]);
|
|
2348
|
+
this.publisher.publishWrite(opId, {
|
|
1435
2349
|
type,
|
|
1436
|
-
|
|
2350
|
+
page: this.pageRef,
|
|
1437
2351
|
...result
|
|
1438
2352
|
});
|
|
1439
2353
|
return result;
|
|
1440
2354
|
}
|
|
1441
2355
|
};
|
|
2356
|
+
//#endregion
|
|
2357
|
+
//#region src/document/CloudPageFormsService.ts
|
|
1442
2358
|
/**
|
|
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.
|
|
2359
|
+
* Cloud page form service: the page's widget images, one read of the
|
|
2360
|
+
* immutable `form/pages/{p}/appearances@` leaf at the page's `widgetVersion`,
|
|
2361
|
+
* at the base's URL while the layer inherits the `forms` plane.
|
|
1492
2362
|
*/
|
|
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) {
|
|
2363
|
+
var CloudPageFormsService = class {
|
|
2364
|
+
constructor(http, docId, layerName, pageRef, isClosed, manifest) {
|
|
1507
2365
|
this.http = http;
|
|
1508
2366
|
this.docId = docId;
|
|
1509
2367
|
this.layerName = layerName;
|
|
1510
|
-
this.
|
|
2368
|
+
this.pageRef = pageRef;
|
|
1511
2369
|
this.isClosed = isClosed;
|
|
1512
2370
|
this.manifest = manifest;
|
|
1513
2371
|
}
|
|
1514
|
-
|
|
2372
|
+
renderAppearances(options = {}) {
|
|
1515
2373
|
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
2374
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
2375
|
+
(0, _embedpdf_engine_core_runtime.checkImageQuality)(options.quality);
|
|
2376
|
+
const format = options.format ?? "webp";
|
|
1517
2377
|
const buildPath = async (s) => {
|
|
1518
2378
|
const manifest = await this.manifest.get(s);
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
|
|
2379
|
+
const pageObjectNumber = this.pageRef.objectNumber;
|
|
2380
|
+
const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
|
|
2381
|
+
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}`);
|
|
2382
|
+
const token = (0, _embedpdf_engine_core_wire.widgetAppearancesImageOptionsToWire)({
|
|
2383
|
+
...options,
|
|
2384
|
+
format
|
|
2385
|
+
}, { widgetVersion: page.cache.widgetVersion });
|
|
2386
|
+
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
2387
|
};
|
|
1523
|
-
return this.http.
|
|
2388
|
+
return parseAppearanceForm(await this.http.getFormDataWithRefresh(buildPath, async (s) => {
|
|
1524
2389
|
await this.manifest.refresh(s);
|
|
1525
|
-
}, signal);
|
|
2390
|
+
}, signal));
|
|
1526
2391
|
});
|
|
1527
2392
|
}
|
|
1528
2393
|
};
|
|
1529
2394
|
//#endregion
|
|
1530
2395
|
//#region src/document/CloudPageRenderService.ts
|
|
1531
2396
|
var CloudPageRenderService = class {
|
|
1532
|
-
constructor(http, docId, layerName,
|
|
2397
|
+
constructor(http, docId, layerName, pageRef, isClosed, manifest, layout, layerRights) {
|
|
1533
2398
|
this.http = http;
|
|
1534
2399
|
this.docId = docId;
|
|
1535
2400
|
this.layerName = layerName;
|
|
1536
|
-
this.
|
|
2401
|
+
this.pageRef = pageRef;
|
|
1537
2402
|
this.isClosed = isClosed;
|
|
1538
2403
|
this.manifest = manifest;
|
|
2404
|
+
this.layout = layout;
|
|
2405
|
+
this.layerRights = layerRights;
|
|
1539
2406
|
}
|
|
1540
2407
|
image(options = {}) {
|
|
1541
2408
|
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
2409
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
2410
|
+
(0, _embedpdf_engine_core_runtime.checkImageQuality)(options.quality);
|
|
1543
2411
|
const format = normalizeFormat(options.format);
|
|
1544
|
-
const
|
|
2412
|
+
const family = _embedpdf_engine_core_wire.PAGE_RENDER_FAMILIES[(0, _embedpdf_engine_core_wire.pageRenderFamilyOf)((0, _embedpdf_engine_core_runtime.resolvePageLayers)(options, this.layerRights() ?? {
|
|
2413
|
+
annotations: true,
|
|
2414
|
+
formFields: true
|
|
2415
|
+
}))];
|
|
1545
2416
|
const buildPath = async (s) => {
|
|
1546
2417
|
const manifest = await this.manifest.get(s);
|
|
1547
|
-
const
|
|
1548
|
-
|
|
2418
|
+
const pageObjectNumber = this.pageRef.objectNumber;
|
|
2419
|
+
const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
|
|
2420
|
+
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}`);
|
|
2421
|
+
const pins = { contentVersion: page.cache.contentVersion };
|
|
2422
|
+
for (const pin of family.pins) pins[pin] = page.cache[pin];
|
|
1549
2423
|
const wireToken = (0, _embedpdf_engine_core_wire.renderImageOptionsToWire)({
|
|
1550
2424
|
...options,
|
|
1551
2425
|
format
|
|
1552
|
-
},
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
2426
|
+
}, pins);
|
|
2427
|
+
return planesInherited(manifest, family.planes) ? _embedpdf_engine_core_wire.wirePaths.docPageRender(this.docId, family.family, this.pageRef, wireToken) : _embedpdf_engine_core_wire.wirePaths.layerPageRender(this.docId, this.layerName, family.family, this.pageRef, wireToken);
|
|
2428
|
+
};
|
|
2429
|
+
const [requestPath, transform] = await Promise.all([buildPath(signal), this.transformOf(options, signal)]);
|
|
2430
|
+
return {
|
|
2431
|
+
...createCloudPageImageHandle({
|
|
2432
|
+
width: transform.width,
|
|
2433
|
+
height: transform.height,
|
|
2434
|
+
format,
|
|
2435
|
+
contentType: `image/${format}`,
|
|
2436
|
+
source: {
|
|
2437
|
+
kind: "url",
|
|
2438
|
+
url: this.http.absoluteUrl(requestPath)
|
|
2439
|
+
}
|
|
2440
|
+
}, this.http, buildPath, async (s) => {
|
|
2441
|
+
await this.manifest.refresh(s);
|
|
2442
|
+
}),
|
|
2443
|
+
transform
|
|
1558
2444
|
};
|
|
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
2445
|
});
|
|
1571
2446
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
2447
|
+
/**
|
|
2448
|
+
* The image's transform, computed like the server's render (the same
|
|
2449
|
+
* `renderSize` and matrix), so the handle knows its size and its pixels
|
|
2450
|
+
* before any are fetched. Only a full page needs the page's layout.
|
|
2451
|
+
*/
|
|
2452
|
+
async transformOf(options, signal) {
|
|
2453
|
+
const target = options.target;
|
|
2454
|
+
return (0, _embedpdf_engine_core_runtime.renderAreaTransform)(target?.kind === "rect" ? (0, _embedpdf_engine_core_runtime.renderTargetArea)(target.rect) : {
|
|
2455
|
+
x: 0,
|
|
2456
|
+
y: 0,
|
|
2457
|
+
...(await this.layout(signal)).size
|
|
2458
|
+
}, options);
|
|
1574
2459
|
}
|
|
1575
2460
|
};
|
|
1576
2461
|
function createCloudPageImageHandle(result, http, buildPath, onStaleVersion) {
|
|
@@ -1584,65 +2469,78 @@ function normalizeFormat(format) {
|
|
|
1584
2469
|
//#endregion
|
|
1585
2470
|
//#region src/document/CloudPageTextService.ts
|
|
1586
2471
|
/**
|
|
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.
|
|
2472
|
+
* Cloud-side per-page text service. `get()` fetches the content-addressed
|
|
2473
|
+
* URL `/v1/docs/:id/text/pages/:pon/data@contentVersion=N` and `layout()`
|
|
2474
|
+
* its geometry twin, where `N` is the page's current `contentVersion` from
|
|
2475
|
+
* the cached manifest. On a 404 (stale version) the SDK transparently
|
|
2476
|
+
* refreshes `/head` + `/manifest@docVersion=N`, rebuilds the URL with the
|
|
2477
|
+
* fresh version, and retries exactly once.
|
|
1593
2478
|
*/
|
|
1594
2479
|
var CloudPageTextService = class {
|
|
1595
|
-
constructor(http, docId, layerName,
|
|
2480
|
+
constructor(http, docId, layerName, pageRef, isClosed, manifest) {
|
|
1596
2481
|
this.http = http;
|
|
1597
2482
|
this.docId = docId;
|
|
1598
2483
|
this.layerName = layerName;
|
|
1599
|
-
this.
|
|
2484
|
+
this.pageRef = pageRef;
|
|
1600
2485
|
this.isClosed = isClosed;
|
|
1601
2486
|
this.manifest = manifest;
|
|
1602
2487
|
}
|
|
1603
|
-
|
|
2488
|
+
get() {
|
|
2489
|
+
return this.readLeaf("text", (raw) => _embedpdf_engine_core_wire.PageTextSnapshotSchema.parse(raw));
|
|
2490
|
+
}
|
|
2491
|
+
slice(range) {
|
|
2492
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => (0, _embedpdf_engine_core_runtime.sliceText)(await this.get().abortWith(signal), range));
|
|
2493
|
+
}
|
|
2494
|
+
layout() {
|
|
2495
|
+
return this.readLeaf("geometry", (raw) => (0, _embedpdf_engine_core_runtime.createTextLayout)(_embedpdf_engine_core_wire.PageGeometrySnapshotSchema.parse(raw)));
|
|
2496
|
+
}
|
|
2497
|
+
/** One of the page's content leaves at the manifest's pin, with the stale-pin retry. */
|
|
2498
|
+
readLeaf(leaf, parse) {
|
|
1604
2499
|
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`));
|
|
1605
|
-
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
2500
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.getJsonWithRefresh(async (s) => this.leafPath(leaf, await this.manifest.get(s)), parse, async (s) => {
|
|
2501
|
+
await this.manifest.refresh(s);
|
|
2502
|
+
}, signal));
|
|
2503
|
+
}
|
|
2504
|
+
leafPath(leaf, manifest) {
|
|
2505
|
+
const pageObjectNumber = this.pageRef.objectNumber;
|
|
2506
|
+
const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
|
|
2507
|
+
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}`);
|
|
2508
|
+
const version = page.cache.contentVersion;
|
|
2509
|
+
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);
|
|
2510
|
+
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
2511
|
}
|
|
1617
2512
|
};
|
|
1618
2513
|
//#endregion
|
|
1619
2514
|
//#region src/document/CloudPageMeasureService.ts
|
|
1620
2515
|
var CloudPageMeasureService = class {
|
|
1621
|
-
constructor(http, docId, layerName,
|
|
2516
|
+
constructor(http, docId, layerName, pageRef, isClosed, manifest, publisher, writes) {
|
|
1622
2517
|
this.http = http;
|
|
1623
2518
|
this.docId = docId;
|
|
1624
2519
|
this.layerName = layerName;
|
|
1625
|
-
this.
|
|
2520
|
+
this.pageRef = pageRef;
|
|
1626
2521
|
this.isClosed = isClosed;
|
|
1627
2522
|
this.manifest = manifest;
|
|
1628
2523
|
this.publisher = publisher;
|
|
2524
|
+
this.writes = writes;
|
|
1629
2525
|
}
|
|
1630
|
-
|
|
2526
|
+
listViewports() {
|
|
1631
2527
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1632
2528
|
this.check();
|
|
1633
|
-
return this.http.getJson(_embedpdf_engine_core_wire.wirePaths.layerPageViewports(this.docId, this.layerName, this.
|
|
2529
|
+
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
2530
|
});
|
|
1635
2531
|
}
|
|
1636
|
-
setScale(measure) {
|
|
2532
|
+
setScale(measure, options) {
|
|
1637
2533
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
1638
|
-
|
|
1639
|
-
signal.throwIfAborted();
|
|
2534
|
+
const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
|
|
1640
2535
|
this.check();
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
2536
|
+
return this.writes.run(opId, signal, async (write) => {
|
|
2537
|
+
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));
|
|
2538
|
+
this.manifest.apply(result.meta, []);
|
|
2539
|
+
this.publisher.publishWrite(opId, {
|
|
2540
|
+
type: "pages.scaleSet",
|
|
2541
|
+
...result
|
|
2542
|
+
});
|
|
2543
|
+
return result;
|
|
1646
2544
|
});
|
|
1647
2545
|
});
|
|
1648
2546
|
}
|
|
@@ -1652,89 +2550,298 @@ var CloudPageMeasureService = class {
|
|
|
1652
2550
|
};
|
|
1653
2551
|
//#endregion
|
|
1654
2552
|
//#region src/document/CloudPageHandle.ts
|
|
2553
|
+
/**
|
|
2554
|
+
* Cloud page handle, keyed on `ref` — the page's durable address (its
|
|
2555
|
+
* object number). Every service below addresses the wire by it
|
|
2556
|
+
* (`/pages/obj:N/…`), keys the manifest by its number, and publishes
|
|
2557
|
+
* that number in events.
|
|
2558
|
+
*/
|
|
1655
2559
|
var CloudPageHandle = class {
|
|
1656
|
-
constructor(
|
|
1657
|
-
this.
|
|
1658
|
-
this.pageIndex = pageIndex;
|
|
2560
|
+
constructor(ref, http, docId, layerName, isClosed, manifest, publisher, writes, layout, layerRights) {
|
|
2561
|
+
this.ref = ref;
|
|
1659
2562
|
_defineProperty(this, "annotations", void 0);
|
|
2563
|
+
_defineProperty(this, "forms", void 0);
|
|
1660
2564
|
_defineProperty(this, "text", void 0);
|
|
1661
|
-
_defineProperty(this, "geometry", void 0);
|
|
1662
2565
|
_defineProperty(this, "render", void 0);
|
|
1663
2566
|
_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,
|
|
2567
|
+
this.measure = new CloudPageMeasureService(http, docId, layerName, ref, isClosed, manifest, publisher, writes);
|
|
2568
|
+
this.annotations = new CloudPageAnnotationsService(http, docId, layerName, ref, isClosed, manifest, publisher, writes);
|
|
2569
|
+
this.forms = new CloudPageFormsService(http, docId, layerName, ref, isClosed, manifest);
|
|
2570
|
+
this.text = new CloudPageTextService(http, docId, layerName, ref, isClosed, manifest);
|
|
2571
|
+
this.render = new CloudPageRenderService(http, docId, layerName, ref, isClosed, manifest, layout, layerRights);
|
|
2572
|
+
}
|
|
2573
|
+
};
|
|
2574
|
+
//#endregion
|
|
2575
|
+
//#region src/document/CloudWrites.ts
|
|
2576
|
+
/**
|
|
2577
|
+
* A document's writes, sent one at a time in the order they were called,
|
|
2578
|
+
* so the server applies them in that order: a create, then an update of
|
|
2579
|
+
* what it made, never the other way round. Reads don't wait.
|
|
2580
|
+
*
|
|
2581
|
+
* A write takes its place when it is called: `run` must be the first thing
|
|
2582
|
+
* a write does, before anything it awaits. What it does before `send`
|
|
2583
|
+
* (reading a file to upload, say) happens meanwhile; its turn ends when it
|
|
2584
|
+
* settles, after the event it publishes.
|
|
2585
|
+
*
|
|
2586
|
+
* Changes (`doc.apply`) called one after another share a place: while the
|
|
2587
|
+
* write before them is in flight they wait together, and go out as one
|
|
2588
|
+
* request when their turn comes, each answered on its own.
|
|
2589
|
+
*/
|
|
2590
|
+
var CloudWrites = class {
|
|
2591
|
+
constructor(pool, sendChanges = () => {
|
|
2592
|
+
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "this line sends no changes");
|
|
2593
|
+
}) {
|
|
2594
|
+
this.pool = pool;
|
|
2595
|
+
this.sendChanges = sendChanges;
|
|
2596
|
+
_defineProperty(this, "last", Promise.resolve());
|
|
2597
|
+
_defineProperty(this, "open", null);
|
|
2598
|
+
}
|
|
2599
|
+
run(opId, signal, body, named = []) {
|
|
2600
|
+
this.open = null;
|
|
2601
|
+
const before = this.last;
|
|
2602
|
+
const numbers = named.filter((n) => n !== void 0);
|
|
2603
|
+
this.pool.beginWrite(numbers);
|
|
2604
|
+
const settled = body({ send: async (request) => {
|
|
2605
|
+
await untilTurn(before, signal);
|
|
2606
|
+
const reserve = this.pool.topUp();
|
|
2607
|
+
return request({ write: {
|
|
2608
|
+
opId,
|
|
2609
|
+
...reserve > 0 ? { reserveObjectNumbers: reserve } : {},
|
|
2610
|
+
onObjectNumbers: (ranges) => this.pool.receive(ranges)
|
|
2611
|
+
} });
|
|
2612
|
+
} }).then((result) => {
|
|
2613
|
+
this.pool.endWrite(numbers, "committed");
|
|
2614
|
+
return result;
|
|
2615
|
+
}, (error) => {
|
|
2616
|
+
this.pool.endWrite(numbers, outcomeOf(error));
|
|
2617
|
+
throw error;
|
|
2618
|
+
});
|
|
2619
|
+
this.last = before.then(() => settled.then(noop, noop));
|
|
2620
|
+
return settled;
|
|
2621
|
+
}
|
|
2622
|
+
/**
|
|
2623
|
+
* Take a change's place in the line: it joins the changes waiting for
|
|
2624
|
+
* their turn, and goes out with them. `ready` is what the request sends
|
|
2625
|
+
* for it, which it may still be preparing (reading bytes). A change aborted
|
|
2626
|
+
* before its request goes out is left out of it; once out, it is answered.
|
|
2627
|
+
*/
|
|
2628
|
+
change(signal, ready, { named = [], alone = false } = {}) {
|
|
2629
|
+
this.pool.beginWrite(named);
|
|
2630
|
+
ready.catch(noop);
|
|
2631
|
+
return new Promise((resolve, reject) => {
|
|
2632
|
+
const lined = {
|
|
2633
|
+
ready,
|
|
2634
|
+
signal,
|
|
2635
|
+
numbers: named,
|
|
2636
|
+
resolve,
|
|
2637
|
+
reject
|
|
2638
|
+
};
|
|
2639
|
+
(!alone && this.open !== null && this.open.changes.length < _embedpdf_engine_core_wire.CHANGE_REQUEST_LIMITS.changes ? this.open : this.queue()).changes.push(lined);
|
|
2640
|
+
if (alone) this.open = null;
|
|
2641
|
+
}).then((settled) => {
|
|
2642
|
+
this.pool.endWrite(named, settledOutcomeOf(settled));
|
|
2643
|
+
return settled;
|
|
2644
|
+
}, (error) => {
|
|
2645
|
+
this.pool.endWrite(named, outcomeOf(error));
|
|
2646
|
+
throw error;
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
/** A new batch at the end of the line, open for the changes that follow. */
|
|
2650
|
+
queue() {
|
|
2651
|
+
const before = this.last;
|
|
2652
|
+
const batch = { changes: [] };
|
|
2653
|
+
this.open = batch;
|
|
2654
|
+
const sent = before.then(() => this.send(batch));
|
|
2655
|
+
this.last = sent.then(noop, noop);
|
|
2656
|
+
return batch;
|
|
2657
|
+
}
|
|
2658
|
+
/** A batch's turn: nothing joins it any more, and what's ready goes out. */
|
|
2659
|
+
async send(batch) {
|
|
2660
|
+
if (this.open === batch) this.open = null;
|
|
2661
|
+
const prepared = await Promise.allSettled(batch.changes.map((lined) => lined.ready));
|
|
2662
|
+
const going = [];
|
|
2663
|
+
batch.changes.forEach((lined, index) => {
|
|
2664
|
+
const ready = prepared[index];
|
|
2665
|
+
if (lined.signal.aborted) lined.reject(new _embedpdf_engine_core_runtime.AbortError(lined.signal.reason ?? "aborted"));
|
|
2666
|
+
else if (ready.status === "rejected") lined.reject(ready.reason);
|
|
2667
|
+
else going.push({
|
|
2668
|
+
lined,
|
|
2669
|
+
sent: ready.value
|
|
2670
|
+
});
|
|
2671
|
+
});
|
|
2672
|
+
if (going.length === 0) return;
|
|
2673
|
+
const reserve = this.pool.topUp();
|
|
2674
|
+
try {
|
|
2675
|
+
const answers = await this.sendChanges(going.map(({ sent }) => sent), { write: {
|
|
2676
|
+
...reserve > 0 ? { reserveObjectNumbers: reserve } : {},
|
|
2677
|
+
onObjectNumbers: (ranges) => this.pool.receive(ranges)
|
|
2678
|
+
} });
|
|
2679
|
+
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`);
|
|
2680
|
+
going.forEach(({ lined }, index) => lined.resolve(answers[index]));
|
|
2681
|
+
} catch (error) {
|
|
2682
|
+
for (const { lined } of going) lined.reject(error);
|
|
2683
|
+
}
|
|
1669
2684
|
}
|
|
1670
2685
|
};
|
|
2686
|
+
function noop() {}
|
|
2687
|
+
/** What a write that failed with `error` did with the numbers it named. */
|
|
2688
|
+
function outcomeOf(error) {
|
|
2689
|
+
return _embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.ObjectNumberUnavailable) ? "refused" : "failed";
|
|
2690
|
+
}
|
|
2691
|
+
/** What an answered change did with the numbers it named, as a single write's outcome. */
|
|
2692
|
+
function settledOutcomeOf(answer) {
|
|
2693
|
+
if (answer.status === "applied") return "committed";
|
|
2694
|
+
return answer.error.code === _embedpdf_engine_core_runtime.EngineErrorCode.ObjectNumberUnavailable ? "refused" : "failed";
|
|
2695
|
+
}
|
|
2696
|
+
/** `before`, or an `AbortError` once `signal` aborts. */
|
|
2697
|
+
function untilTurn(before, signal) {
|
|
2698
|
+
if (signal.aborted) return Promise.reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason ?? "aborted"));
|
|
2699
|
+
return new Promise((resolve, reject) => {
|
|
2700
|
+
const onAbort = () => reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason ?? "aborted"));
|
|
2701
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2702
|
+
before.then(() => {
|
|
2703
|
+
signal.removeEventListener("abort", onAbort);
|
|
2704
|
+
resolve();
|
|
2705
|
+
});
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
1671
2708
|
//#endregion
|
|
1672
|
-
//#region src/realtime/
|
|
2709
|
+
//#region src/realtime/auditRowToEvents.ts
|
|
1673
2710
|
/**
|
|
1674
|
-
* Translate a remote audit row into
|
|
1675
|
-
* exactly-once and verbatim-payload invariants are unit-testable
|
|
1676
|
-
* server.
|
|
1677
|
-
*
|
|
2711
|
+
* Translate a remote audit row into the `DocumentEvent`s it records — pure,
|
|
2712
|
+
* so the exactly-once and verbatim-payload invariants are unit-testable
|
|
2713
|
+
* without a server. Most rows are one fact; a change is the events of its
|
|
2714
|
+
* items, a form reset is one `forms.valueSet` per field it changed, an
|
|
2715
|
+
* import is one `annotations.created` per annotation, and a completed
|
|
2716
|
+
* signing is the signature and the new version. An undo's events name the
|
|
2717
|
+
* change it undid (`origin.undoOf`). Every event of a row shares `origin.tx`,
|
|
2718
|
+
* its id the request's `Idempotency-Key`. Returns none for an own echo and for kinds
|
|
2719
|
+
* this engine version doesn't know (a newer server's events degrade to
|
|
2720
|
+
* "ignored", never to a crash).
|
|
1678
2721
|
*
|
|
1679
2722
|
* Context-field fidelity differs by op, by design of the audit row:
|
|
1680
2723
|
* - rotate/delete: `affectedPages` is exactly the op's page set; rotation
|
|
1681
2724
|
* is recovered from the layout (it's absolute — every affected page
|
|
1682
2725
|
* carries the value).
|
|
1683
|
-
* -
|
|
1684
|
-
*
|
|
1685
|
-
*
|
|
2726
|
+
* - reorder: the payload is the result, as the originator got it: the
|
|
2727
|
+
* pages that moved and the new `layout`.
|
|
2728
|
+
*
|
|
2729
|
+
* The audit row keys pages by object number (its storage identity); the
|
|
2730
|
+
* event carries them as `PageRef` addresses, like every other event.
|
|
1686
2731
|
*/
|
|
1687
|
-
function
|
|
1688
|
-
if (row.originSessionId === mySessionId) return
|
|
2732
|
+
function auditRowToEvents(row, mySessionId) {
|
|
2733
|
+
if (row.originSessionId === mySessionId) return [];
|
|
2734
|
+
if (row.withheld) return [];
|
|
1689
2735
|
const origin = {
|
|
1690
2736
|
kind: "remote",
|
|
1691
2737
|
sessionId: row.originSessionId ?? `unknown:${row.sub}`,
|
|
1692
2738
|
sub: row.sub,
|
|
1693
2739
|
ts: row.ts,
|
|
1694
|
-
serverId: row.id
|
|
2740
|
+
serverId: row.id,
|
|
2741
|
+
...row.undoOf ? { undoOf: row.undoOf } : {}
|
|
1695
2742
|
};
|
|
2743
|
+
const id = row.idempotencyKey ?? `audit:${row.id}`;
|
|
2744
|
+
const events = factsOf(row, origin);
|
|
2745
|
+
return events.map((event, index) => ({
|
|
2746
|
+
...event,
|
|
2747
|
+
origin: {
|
|
2748
|
+
...origin,
|
|
2749
|
+
tx: {
|
|
2750
|
+
id,
|
|
2751
|
+
index,
|
|
2752
|
+
count: events.length
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
}));
|
|
2756
|
+
}
|
|
2757
|
+
/** The events a row records, in order, or none for a kind this version doesn't know. */
|
|
2758
|
+
function factsOf(row, origin) {
|
|
2759
|
+
if (row.kind === "change") return (0, _embedpdf_engine_core_runtime.changeEvents)(row.payload).map((event) => ({
|
|
2760
|
+
...event,
|
|
2761
|
+
origin
|
|
2762
|
+
}));
|
|
2763
|
+
if (row.kind === "annot.import") return (0, _embedpdf_engine_core_runtime.annotationImportFacts)(row.payload).map((fact) => ({
|
|
2764
|
+
type: "annotations.created",
|
|
2765
|
+
origin,
|
|
2766
|
+
...fact
|
|
2767
|
+
}));
|
|
2768
|
+
if (row.kind === "form.import") return (0, _embedpdf_engine_core_runtime.formImportFacts)(row.payload).map((fact) => ({
|
|
2769
|
+
type: "forms.created",
|
|
2770
|
+
origin,
|
|
2771
|
+
...fact
|
|
2772
|
+
}));
|
|
2773
|
+
if (row.kind === "form.importValues") return (0, _embedpdf_engine_core_runtime.formValuesImportFacts)(row.payload).map((fact) => ({
|
|
2774
|
+
type: "forms.valueSet",
|
|
2775
|
+
origin,
|
|
2776
|
+
...fact
|
|
2777
|
+
}));
|
|
2778
|
+
if (row.kind === "form.reset") return (0, _embedpdf_engine_core_runtime.formResetFacts)(row.payload).map((fact) => ({
|
|
2779
|
+
type: "forms.valueSet",
|
|
2780
|
+
origin,
|
|
2781
|
+
...fact
|
|
2782
|
+
}));
|
|
2783
|
+
if (row.kind === "signature.complete") {
|
|
2784
|
+
const { signingId, ...result } = row.payload;
|
|
2785
|
+
return [{
|
|
2786
|
+
type: "signatures.completed",
|
|
2787
|
+
origin,
|
|
2788
|
+
signingId,
|
|
2789
|
+
...result
|
|
2790
|
+
}, {
|
|
2791
|
+
type: "document.versioned",
|
|
2792
|
+
origin,
|
|
2793
|
+
version: result.version
|
|
2794
|
+
}];
|
|
2795
|
+
}
|
|
2796
|
+
const event = eventOf(row, origin);
|
|
2797
|
+
return event ? [event] : [];
|
|
2798
|
+
}
|
|
2799
|
+
/** The one event a single-fact row records, or `null` for a kind this version doesn't know. */
|
|
2800
|
+
function eventOf(row, origin) {
|
|
2801
|
+
const rowPage = () => (0, _embedpdf_engine_core_runtime.toPageRef)(row.pageObjectNumber ?? row.affectedPages[0] ?? 0);
|
|
2802
|
+
const affectedPages = () => row.affectedPages.map((pageObjectNumber) => (0, _embedpdf_engine_core_runtime.toPageRef)(pageObjectNumber));
|
|
1696
2803
|
switch (row.kind) {
|
|
1697
2804
|
case "measure.setScale": return {
|
|
1698
|
-
type: "
|
|
2805
|
+
type: "pages.scaleSet",
|
|
1699
2806
|
origin,
|
|
1700
2807
|
...row.payload
|
|
1701
2808
|
};
|
|
1702
2809
|
case "annot.create": return {
|
|
1703
|
-
type: "
|
|
1704
|
-
|
|
2810
|
+
type: "annotations.created",
|
|
2811
|
+
page: rowPage(),
|
|
1705
2812
|
origin,
|
|
1706
2813
|
...row.payload
|
|
1707
2814
|
};
|
|
1708
2815
|
case "annot.update": return {
|
|
1709
|
-
type: "
|
|
1710
|
-
|
|
2816
|
+
type: "annotations.updated",
|
|
2817
|
+
page: rowPage(),
|
|
1711
2818
|
origin,
|
|
1712
2819
|
...row.payload
|
|
1713
2820
|
};
|
|
1714
2821
|
case "annot.delete": return {
|
|
1715
|
-
type: "
|
|
1716
|
-
|
|
2822
|
+
type: "annotations.deleted",
|
|
2823
|
+
page: rowPage(),
|
|
1717
2824
|
origin,
|
|
2825
|
+
deleted: (0, _embedpdf_engine_core_runtime.deletedAnnotationsOf)(row.payload),
|
|
1718
2826
|
...row.payload
|
|
1719
2827
|
};
|
|
1720
|
-
case "annot.
|
|
1721
|
-
type: "
|
|
1722
|
-
|
|
2828
|
+
case "annot.reorder": return {
|
|
2829
|
+
type: "annotations.reordered",
|
|
2830
|
+
page: rowPage(),
|
|
1723
2831
|
origin,
|
|
1724
2832
|
...row.payload
|
|
1725
2833
|
};
|
|
1726
|
-
case "pages.
|
|
1727
|
-
type: "pages.
|
|
1728
|
-
pageObjectNumbers: row.affectedPages,
|
|
2834
|
+
case "pages.reorder": return {
|
|
2835
|
+
type: "pages.reordered",
|
|
1729
2836
|
origin,
|
|
1730
2837
|
...row.payload
|
|
1731
2838
|
};
|
|
1732
2839
|
case "pages.rotate": {
|
|
1733
2840
|
const payload = row.payload;
|
|
1734
|
-
const rotation = payload.layout.pages.find((page) => page.
|
|
2841
|
+
const rotation = payload.layout.pages.find((page) => page.ref.objectNumber === row.affectedPages[0])?.rotation ?? 0;
|
|
1735
2842
|
return {
|
|
1736
2843
|
type: "pages.rotated",
|
|
1737
|
-
|
|
2844
|
+
pages: affectedPages(),
|
|
1738
2845
|
rotation,
|
|
1739
2846
|
origin,
|
|
1740
2847
|
...payload
|
|
@@ -1742,7 +2849,7 @@ function auditRowToEvent(row, mySessionId) {
|
|
|
1742
2849
|
}
|
|
1743
2850
|
case "pages.delete": return {
|
|
1744
2851
|
type: "pages.deleted",
|
|
1745
|
-
|
|
2852
|
+
pages: affectedPages(),
|
|
1746
2853
|
origin,
|
|
1747
2854
|
...row.payload
|
|
1748
2855
|
};
|
|
@@ -1762,63 +2869,96 @@ function auditRowToEvent(row, mySessionId) {
|
|
|
1762
2869
|
origin,
|
|
1763
2870
|
...row.payload
|
|
1764
2871
|
};
|
|
1765
|
-
case "
|
|
1766
|
-
type: "
|
|
2872
|
+
case "metadata.updateCustom": return {
|
|
2873
|
+
type: "metadata.customUpdated",
|
|
1767
2874
|
origin,
|
|
1768
2875
|
...row.payload
|
|
1769
2876
|
};
|
|
1770
|
-
case "attachment.
|
|
1771
|
-
type: "
|
|
2877
|
+
case "attachment.create": return {
|
|
2878
|
+
type: "attachments.created",
|
|
1772
2879
|
origin,
|
|
1773
2880
|
...row.payload
|
|
1774
2881
|
};
|
|
1775
|
-
case "
|
|
1776
|
-
|
|
1777
|
-
type: "form.valueChanged",
|
|
2882
|
+
case "attachment.delete": return {
|
|
2883
|
+
type: "attachments.deleted",
|
|
1778
2884
|
origin,
|
|
2885
|
+
deleted: (0, _embedpdf_engine_core_runtime.deletedAttachmentOf)(row.payload),
|
|
1779
2886
|
...row.payload
|
|
1780
2887
|
};
|
|
1781
|
-
case "form.
|
|
1782
|
-
type: "
|
|
2888
|
+
case "form.setValue": return {
|
|
2889
|
+
type: "forms.valueSet",
|
|
1783
2890
|
origin,
|
|
1784
2891
|
...row.payload
|
|
1785
2892
|
};
|
|
1786
2893
|
case "form.repair": return {
|
|
1787
|
-
type: "
|
|
2894
|
+
type: "forms.repaired",
|
|
1788
2895
|
origin,
|
|
1789
2896
|
...row.payload
|
|
1790
2897
|
};
|
|
1791
2898
|
case "form.createField": return {
|
|
1792
|
-
type: "
|
|
2899
|
+
type: "forms.created",
|
|
1793
2900
|
origin,
|
|
1794
2901
|
...row.payload
|
|
1795
2902
|
};
|
|
1796
2903
|
case "form.updateField":
|
|
1797
2904
|
case "form.setSignatureAppearance": return {
|
|
1798
|
-
type: "
|
|
2905
|
+
type: "forms.updated",
|
|
2906
|
+
origin,
|
|
2907
|
+
...row.payload
|
|
2908
|
+
};
|
|
2909
|
+
case "form.deleteField": {
|
|
2910
|
+
const result = row.payload;
|
|
2911
|
+
return {
|
|
2912
|
+
type: "forms.deleted",
|
|
2913
|
+
origin,
|
|
2914
|
+
deleted: (0, _embedpdf_engine_core_runtime.deletedFieldOf)(result),
|
|
2915
|
+
...result
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
case "form.addWidget": return {
|
|
2919
|
+
type: "forms.widgetAdded",
|
|
1799
2920
|
origin,
|
|
1800
2921
|
...row.payload
|
|
1801
2922
|
};
|
|
1802
|
-
case "form.
|
|
1803
|
-
type: "
|
|
2923
|
+
case "form.detachWidget": return {
|
|
2924
|
+
type: "forms.widgetRemoved",
|
|
1804
2925
|
origin,
|
|
1805
2926
|
...row.payload
|
|
1806
2927
|
};
|
|
1807
|
-
case "form.
|
|
1808
|
-
type: "
|
|
2928
|
+
case "form.updateWidget": return {
|
|
2929
|
+
type: "forms.widgetUpdated",
|
|
1809
2930
|
origin,
|
|
1810
2931
|
...row.payload
|
|
1811
2932
|
};
|
|
1812
|
-
case "form.
|
|
1813
|
-
type: "
|
|
2933
|
+
case "form.deleteWidget": return {
|
|
2934
|
+
type: "forms.widgetDeleted",
|
|
1814
2935
|
origin,
|
|
1815
2936
|
...row.payload
|
|
1816
2937
|
};
|
|
1817
|
-
case "form.
|
|
1818
|
-
type: "
|
|
2938
|
+
case "form.reorderWidgets": return {
|
|
2939
|
+
type: "forms.widgetsReordered",
|
|
1819
2940
|
origin,
|
|
1820
2941
|
...row.payload
|
|
1821
2942
|
};
|
|
2943
|
+
case "form.reorderCalculations": return {
|
|
2944
|
+
type: "forms.calculationsReordered",
|
|
2945
|
+
origin,
|
|
2946
|
+
...row.payload
|
|
2947
|
+
};
|
|
2948
|
+
case "signature.prepare": {
|
|
2949
|
+
const { field, ...wire } = row.payload;
|
|
2950
|
+
return {
|
|
2951
|
+
type: "signatures.prepared",
|
|
2952
|
+
origin,
|
|
2953
|
+
field,
|
|
2954
|
+
...(0, _embedpdf_engine_core_wire.decodePrepared)(wire)
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2957
|
+
case "signature.cancel": return {
|
|
2958
|
+
type: "signatures.cancelled",
|
|
2959
|
+
origin,
|
|
2960
|
+
signingId: row.payload.signingId
|
|
2961
|
+
};
|
|
1822
2962
|
default: return null;
|
|
1823
2963
|
}
|
|
1824
2964
|
}
|
|
@@ -1874,7 +3014,12 @@ var SseClient = class {
|
|
|
1874
3014
|
return;
|
|
1875
3015
|
}
|
|
1876
3016
|
if (!res.ok || !res.body) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Network, `events stream HTTP ${res.status}`);
|
|
1877
|
-
|
|
3017
|
+
this.opts.onConnected?.(true);
|
|
3018
|
+
try {
|
|
3019
|
+
await this.consume(res.body);
|
|
3020
|
+
} finally {
|
|
3021
|
+
this.opts.onConnected?.(false);
|
|
3022
|
+
}
|
|
1878
3023
|
if (Date.now() - startedAt >= 3e4) this.retryDelayMs = 500;
|
|
1879
3024
|
} catch {}
|
|
1880
3025
|
if (this.closed) return;
|
|
@@ -1911,7 +3056,11 @@ var SseClient = class {
|
|
|
1911
3056
|
if (event === "mutation") try {
|
|
1912
3057
|
this.opts.onRow(JSON.parse(data.join("\n")));
|
|
1913
3058
|
} catch {}
|
|
1914
|
-
else if (event === "
|
|
3059
|
+
else if (event === "session") {
|
|
3060
|
+
const parsed = safeJson(data.join("\n"));
|
|
3061
|
+
const status = _embedpdf_engine_core_wire.EditSessionStatusSchema.safeParse(parsed);
|
|
3062
|
+
if (status.success) this.opts.onSession?.(status.data);
|
|
3063
|
+
} else if (event === "full-refresh") this.opts.onFullRefresh();
|
|
1915
3064
|
else if (event === "auth-revoked") {
|
|
1916
3065
|
this.closed = true;
|
|
1917
3066
|
this.abort?.abort();
|
|
@@ -1919,17 +3068,21 @@ var SseClient = class {
|
|
|
1919
3068
|
}
|
|
1920
3069
|
}
|
|
1921
3070
|
};
|
|
3071
|
+
function safeJson(text) {
|
|
3072
|
+
try {
|
|
3073
|
+
return JSON.parse(text);
|
|
3074
|
+
} catch {
|
|
3075
|
+
return null;
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
1922
3078
|
//#endregion
|
|
1923
3079
|
//#region src/document/CloudDocumentHandle.ts
|
|
1924
3080
|
var CloudDocumentHandle = class {
|
|
1925
|
-
constructor(http, id, layerName = _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME, initialHead, initialToken = null,
|
|
3081
|
+
constructor(http, id, layerName = _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME, initialHead, initialToken = null, onClose = () => {}) {
|
|
1926
3082
|
this.http = http;
|
|
1927
3083
|
this.layerName = layerName;
|
|
3084
|
+
this.onClose = onClose;
|
|
1928
3085
|
_defineProperty(this, "id", void 0);
|
|
1929
|
-
_defineProperty(this, "capabilities", {
|
|
1930
|
-
weakAnnotationEditSessions: "required",
|
|
1931
|
-
pageEditSessions: "unsupported"
|
|
1932
|
-
});
|
|
1933
3086
|
_defineProperty(this, "metadata", void 0);
|
|
1934
3087
|
_defineProperty(this, "annotations", void 0);
|
|
1935
3088
|
_defineProperty(this, "actions", void 0);
|
|
@@ -1942,6 +3095,10 @@ var CloudDocumentHandle = class {
|
|
|
1942
3095
|
_defineProperty(this, "security", void 0);
|
|
1943
3096
|
_defineProperty(this, "render", void 0);
|
|
1944
3097
|
_defineProperty(this, "events", void 0);
|
|
3098
|
+
_defineProperty(this, "objectNumbers", void 0);
|
|
3099
|
+
_defineProperty(this, "pool", void 0);
|
|
3100
|
+
_defineProperty(this, "writes", void 0);
|
|
3101
|
+
_defineProperty(this, "changes", void 0);
|
|
1945
3102
|
_defineProperty(this, "publisher", void 0);
|
|
1946
3103
|
_defineProperty(this, "hub", void 0);
|
|
1947
3104
|
_defineProperty(this, "sessionId", void 0);
|
|
@@ -1953,11 +3110,20 @@ var CloudDocumentHandle = class {
|
|
|
1953
3110
|
_defineProperty(this, "manifestFloorVersion", 0);
|
|
1954
3111
|
_defineProperty(this, "pendingInitialHead", void 0);
|
|
1955
3112
|
_defineProperty(this, "manifestAccessor", void 0);
|
|
3113
|
+
_defineProperty(this, "cloudSecurity", void 0);
|
|
1956
3114
|
this.id = id;
|
|
1957
3115
|
this.pendingInitialHead = initialHead ?? null;
|
|
1958
|
-
const
|
|
3116
|
+
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 }) });
|
|
3117
|
+
this.pool = pool;
|
|
3118
|
+
this.objectNumbers = pool;
|
|
3119
|
+
this.writes = new CloudWrites(pool, changeSender(http, id, layerName));
|
|
3120
|
+
const security = new CloudDocumentSecurityService(http, id, layerName, initialHead ?? fallbackUnknownHead(id), { isClosed: () => this.closed }, initialToken, {
|
|
3121
|
+
wanted: () => pool.wantedAtAccess(),
|
|
3122
|
+
opened: (edit) => pool.opened(edit)
|
|
3123
|
+
});
|
|
1959
3124
|
this.security = security;
|
|
1960
|
-
this.
|
|
3125
|
+
this.cloudSecurity = security;
|
|
3126
|
+
this.render = { getPolicy: () => _embedpdf_engine_core_runtime.AbortablePromise.run(async () => {
|
|
1961
3127
|
const advertised = (security.currentAccess ?? (await security.establishAccess()).access ?? null)?.renderPolicy;
|
|
1962
3128
|
if (!advertised) return _embedpdf_engine_core_runtime.CONTINUOUS_RENDER_POLICY;
|
|
1963
3129
|
return {
|
|
@@ -1970,52 +3136,71 @@ var CloudDocumentHandle = class {
|
|
|
1970
3136
|
background: advertised.background,
|
|
1971
3137
|
enforced: advertised.enforced
|
|
1972
3138
|
};
|
|
1973
|
-
} };
|
|
1974
|
-
const hub = new
|
|
3139
|
+
}) };
|
|
3140
|
+
const hub = new _embedpdf_engine_core_runtime.EventHub();
|
|
1975
3141
|
this.hub = hub;
|
|
3142
|
+
const sessionId = http.sessionId ?? `cloud:anon:${id}`;
|
|
1976
3143
|
this.sessionId = sessionId;
|
|
3144
|
+
const subscribe = (listener) => {
|
|
3145
|
+
const unsubscribe = hub.subscribe(listener);
|
|
3146
|
+
this.retainRemoteStream();
|
|
3147
|
+
let released = false;
|
|
3148
|
+
return () => {
|
|
3149
|
+
unsubscribe();
|
|
3150
|
+
if (!released) {
|
|
3151
|
+
released = true;
|
|
3152
|
+
this.releaseRemoteStream();
|
|
3153
|
+
}
|
|
3154
|
+
};
|
|
3155
|
+
};
|
|
1977
3156
|
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
|
-
},
|
|
3157
|
+
subscribe,
|
|
3158
|
+
on: (type, listener) => (0, _embedpdf_engine_core_runtime.subscribeToType)(subscribe, type, listener),
|
|
1990
3159
|
lastServerId: () => hub.lastServerId()
|
|
1991
3160
|
};
|
|
1992
|
-
this.publisher = new
|
|
3161
|
+
this.publisher = new _embedpdf_engine_core_runtime.SessionEventPublisher(hub, sessionId);
|
|
3162
|
+
hub.subscribe((event) => {
|
|
3163
|
+
if (event.type === "document.versioned") pool.versioned(event.version.sha256);
|
|
3164
|
+
});
|
|
1993
3165
|
this.manifestAccessor = {
|
|
1994
3166
|
get: (signal) => this.getManifest(signal),
|
|
1995
3167
|
refresh: (signal) => this.refreshManifest(signal),
|
|
1996
3168
|
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)
|
|
3169
|
+
applyPageDelete: (meta, deletedPages) => this.absorbPageDelete(meta, deletedPages),
|
|
3170
|
+
applyPageInsert: (meta) => this.absorbPageInsert(meta)
|
|
2002
3171
|
};
|
|
2003
|
-
this.
|
|
2004
|
-
this.
|
|
3172
|
+
this.changes = new CloudDocumentChanges(() => this.closed, this.manifestAccessor, this.publisher, this.writes);
|
|
3173
|
+
this.metadata = new CloudMetadataService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
|
|
3174
|
+
const importLimits = async () => (security.currentAccess ?? (await security.establishAccess()).access)?.bundleLimits ?? _embedpdf_engine_core_runtime.DEFAULT_BUNDLE_LIMITS;
|
|
3175
|
+
this.annotations = new CloudDocumentAnnotationsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, importLimits);
|
|
2005
3176
|
this.actions = new CloudDocumentActionsService(http, id, layerName, () => this.closed, this.manifestAccessor);
|
|
2006
|
-
this.attachments = new CloudDocumentAttachmentsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher);
|
|
2007
|
-
this.signatures = new CloudDocumentSignaturesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher);
|
|
2008
|
-
this.forms = new CloudDocumentFormsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher);
|
|
3177
|
+
this.attachments = new CloudDocumentAttachmentsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
|
|
3178
|
+
this.signatures = new CloudDocumentSignaturesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
|
|
3179
|
+
this.forms = new CloudDocumentFormsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, importLimits);
|
|
2009
3180
|
this.search = new CloudDocumentSearchService(http, id, layerName, () => this.closed, this.manifestAccessor);
|
|
2010
|
-
this.pages = new CloudDocumentPagesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher);
|
|
2011
|
-
this.redaction = new CloudDocumentRedactionService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher);
|
|
3181
|
+
this.pages = new CloudDocumentPagesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
|
|
3182
|
+
this.redaction = new CloudDocumentRedactionService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
|
|
3183
|
+
}
|
|
3184
|
+
page(ref) {
|
|
3185
|
+
return new CloudPageHandle(ref, this.http, this.id, this.layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, (signal) => this.pages.pageLayout(ref, signal), () => this.cloudSecurity.pageLayerRights());
|
|
3186
|
+
}
|
|
3187
|
+
/**
|
|
3188
|
+
* This handle: the cloud engine sends every call as it's made, in
|
|
3189
|
+
* parallel, and the server runs them in arrival order, so a call's facts
|
|
3190
|
+
* change nothing here.
|
|
3191
|
+
*/
|
|
3192
|
+
with(_facts) {
|
|
3193
|
+
return this;
|
|
2012
3194
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
3195
|
+
/** One change as one transaction, or the undo of one (see `DocumentHandle.apply`). */
|
|
3196
|
+
apply(change, options) {
|
|
3197
|
+
return this.changes.apply(change, options);
|
|
2015
3198
|
}
|
|
2016
|
-
|
|
3199
|
+
/** Ignored: the server learns what's needed from what is asked of it. */
|
|
3200
|
+
setWorkingSet(_view, _pages) {}
|
|
3201
|
+
download(options = {}) {
|
|
2017
3202
|
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 =
|
|
3203
|
+
const mode = options.mode ?? _embedpdf_engine_core_runtime.DEFAULT_PDF_SAVE_MODE;
|
|
2019
3204
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
2020
3205
|
const buildPath = async () => {
|
|
2021
3206
|
const manifest = await this.getManifest(signal);
|
|
@@ -2034,6 +3219,20 @@ var CloudDocumentHandle = class {
|
|
|
2034
3219
|
});
|
|
2035
3220
|
}
|
|
2036
3221
|
/**
|
|
3222
|
+
* Learn what the document's signatures forbid before the caller's first
|
|
3223
|
+
* question: the manifest carries it, so `open()` fetches it up front and
|
|
3224
|
+
* `security.allows()` answers as the server would from the start. A locked
|
|
3225
|
+
* document, which the server won't describe yet, learns it from `unlock()`.
|
|
3226
|
+
*/
|
|
3227
|
+
async learnProtection(signal) {
|
|
3228
|
+
try {
|
|
3229
|
+
await this.getManifest(signal);
|
|
3230
|
+
} catch (error) {
|
|
3231
|
+
if (_embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.DocPasswordRequired)) return;
|
|
3232
|
+
throw error;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
/**
|
|
2037
3236
|
* Return the cached manifest, fetching cold-cache once if needed.
|
|
2038
3237
|
* Concurrent callers share a single inflight request (singleflight)
|
|
2039
3238
|
* so an N-page handle that opens N services in parallel still
|
|
@@ -2055,9 +3254,9 @@ var CloudDocumentHandle = class {
|
|
|
2055
3254
|
return awaitSignal(this.startManifestFetch({ allowInitialHead: false }), signal);
|
|
2056
3255
|
}
|
|
2057
3256
|
/**
|
|
2058
|
-
* Monotone plane-scope flip: mark the planes a mutation
|
|
3257
|
+
* Monotone plane-scope flip: mark the planes a mutation owns as layer-scoped in
|
|
2059
3258
|
* the cached manifest. Scopes only ever move base → layer (no revert op
|
|
2060
|
-
* exists), so flipping is safe under
|
|
3259
|
+
* exists), so flipping is safe under any event ordering — a duplicate or
|
|
2061
3260
|
* out-of-order event still proves the plane diverged at some point — and
|
|
2062
3261
|
* the manifest fetch stays the authoritative source (the 404 → refresh
|
|
2063
3262
|
* rail heals any miss, e.g. a future mutation kind this client doesn't
|
|
@@ -2088,17 +3287,10 @@ var CloudDocumentHandle = class {
|
|
|
2088
3287
|
return;
|
|
2089
3288
|
}
|
|
2090
3289
|
}
|
|
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
|
-
}
|
|
3290
|
+
const byPageObjectNumber = new Map(this.manifestCache.pages.map((page) => [page.page.objectNumber, page]));
|
|
2099
3291
|
if (delta) for (const page of delta.pages) {
|
|
2100
|
-
const existing = byPageObjectNumber.get(page.
|
|
2101
|
-
if (existing) byPageObjectNumber.set(page.
|
|
3292
|
+
const existing = byPageObjectNumber.get(page.page.objectNumber);
|
|
3293
|
+
if (existing) byPageObjectNumber.set(page.page.objectNumber, {
|
|
2102
3294
|
...existing,
|
|
2103
3295
|
cache: page.cache
|
|
2104
3296
|
});
|
|
@@ -2107,140 +3299,68 @@ var CloudDocumentHandle = class {
|
|
|
2107
3299
|
...this.manifestCache,
|
|
2108
3300
|
docVersion: delta?.docVersion ?? this.manifestCache.docVersion,
|
|
2109
3301
|
...delta?.annotationsVersion !== void 0 ? { annotationsVersion: delta.annotationsVersion } : {},
|
|
3302
|
+
...delta?.formsVersion !== void 0 ? { formsVersion: delta.formsVersion } : {},
|
|
3303
|
+
...delta?.layoutVersion !== void 0 ? { layoutVersion: delta.layoutVersion } : {},
|
|
3304
|
+
...delta?.metadataVersion !== void 0 ? { metadataVersion: delta.metadataVersion } : {},
|
|
3305
|
+
...delta?.attachmentsVersion !== void 0 ? { attachmentsVersion: delta.attachmentsVersion } : {},
|
|
2110
3306
|
...delta?.layerVersion !== void 0 ? { layerVersion: delta.layerVersion } : {},
|
|
2111
3307
|
...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
|
|
3308
|
+
pages: Array.from(byPageObjectNumber.values()).sort((a, b) => a.page.objectNumber - b.page.objectNumber)
|
|
2138
3309
|
};
|
|
2139
3310
|
}
|
|
2140
3311
|
/**
|
|
2141
|
-
* Patch the cached manifest after a page delete: the shared
|
|
2142
|
-
*
|
|
2143
|
-
*
|
|
2144
|
-
* 404 anyway — this keeps the failure local and instant).
|
|
3312
|
+
* Patch the cached manifest after a page delete: the shared advance plus
|
|
3313
|
+
* dropping the deleted pages' manifest rows, so no leaf URL for a retired
|
|
3314
|
+
* page object number can be built from the cache (a stale request would
|
|
3315
|
+
* 404 anyway — this keeps the failure local and instant). Delete changes
|
|
3316
|
+
* the page set: a view that removed content must never resolve base
|
|
3317
|
+
* artifacts again, so content, annotations and forms flip with layout.
|
|
2145
3318
|
*/
|
|
2146
|
-
absorbPageDelete(
|
|
2147
|
-
this.
|
|
3319
|
+
absorbPageDelete(meta, deletedPages) {
|
|
3320
|
+
this.absorbMutation(meta, [
|
|
2148
3321
|
"layout",
|
|
2149
3322
|
"content",
|
|
2150
|
-
"annotations"
|
|
3323
|
+
"annotations",
|
|
3324
|
+
"forms"
|
|
2151
3325
|
]);
|
|
2152
|
-
this.manifestFloorVersion = Math.max(this.manifestFloorVersion, cache.docVersion);
|
|
2153
|
-
this.inflightManifest = null;
|
|
2154
3326
|
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);
|
|
3327
|
+
const deleted = new Set(deletedPages.map((page) => page.objectNumber));
|
|
2161
3328
|
this.manifestCache = {
|
|
2162
3329
|
...this.manifestCache,
|
|
2163
|
-
|
|
2164
|
-
layoutVersion: cache.layoutVersion,
|
|
2165
|
-
pages: this.manifestCache.pages.filter((page) => !deleted.has(page.state.pageObjectNumber))
|
|
3330
|
+
pages: this.manifestCache.pages.filter((page) => !deleted.has(page.page.objectNumber))
|
|
2166
3331
|
};
|
|
2167
3332
|
}
|
|
2168
3333
|
/**
|
|
2169
3334
|
* Patch bookkeeping after a page insert — the mirror of
|
|
2170
3335
|
* {@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
|
|
3336
|
+
* cached manifest losslessly (it only removes rows), but an insert needs
|
|
3337
|
+
* manifest rows for the fresh page object numbers and the result doesn't carry them. So
|
|
3338
|
+
* this absorb flips the planes (insert changes the page set, so like
|
|
3339
|
+
* delete it owns content, annotations and forms alongside layout), raises the
|
|
3340
|
+
* version floor, and drops the cache — the next read refetches a manifest
|
|
2176
3341
|
* that includes the new pages' rows. The UI never waits on that refetch:
|
|
2177
3342
|
* the mutation result / event already carries the full new layout.
|
|
2178
3343
|
*/
|
|
2179
|
-
absorbPageInsert(
|
|
3344
|
+
absorbPageInsert(meta) {
|
|
2180
3345
|
this.flipScopes([
|
|
2181
3346
|
"layout",
|
|
2182
3347
|
"content",
|
|
2183
|
-
"annotations"
|
|
3348
|
+
"annotations",
|
|
3349
|
+
"forms"
|
|
2184
3350
|
]);
|
|
2185
|
-
this.manifestFloorVersion = Math.max(this.manifestFloorVersion,
|
|
3351
|
+
if (meta.cacheDelta) this.manifestFloorVersion = Math.max(this.manifestFloorVersion, meta.cacheDelta.docVersion);
|
|
2186
3352
|
this.inflightManifest = null;
|
|
2187
3353
|
this.manifestCache = null;
|
|
2188
3354
|
}
|
|
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
3355
|
startManifestFetch(opts) {
|
|
2239
3356
|
const ctrl = new AbortController();
|
|
2240
3357
|
const promise = this.fetchManifest(ctrl.signal, opts);
|
|
2241
3358
|
this.inflightManifest = promise;
|
|
2242
3359
|
promise.then((manifest) => {
|
|
2243
|
-
if (manifest.docVersion >= this.manifestFloorVersion && (!this.manifestCache || manifest.docVersion >= this.manifestCache.docVersion))
|
|
3360
|
+
if (manifest.docVersion >= this.manifestFloorVersion && (!this.manifestCache || manifest.docVersion >= this.manifestCache.docVersion)) {
|
|
3361
|
+
this.manifestCache = manifest;
|
|
3362
|
+
this.cloudSecurity.setProtection(manifest.protection);
|
|
3363
|
+
}
|
|
2244
3364
|
}).catch(() => void 0).finally(() => {
|
|
2245
3365
|
if (this.inflightManifest === promise) this.inflightManifest = null;
|
|
2246
3366
|
});
|
|
@@ -2272,6 +3392,7 @@ var CloudDocumentHandle = class {
|
|
|
2272
3392
|
this.sseClient = null;
|
|
2273
3393
|
this.manifestCache = null;
|
|
2274
3394
|
this.inflightManifest = null;
|
|
3395
|
+
this.onClose();
|
|
2275
3396
|
return _embedpdf_engine_core_runtime.AbortablePromise.resolveValue(void 0);
|
|
2276
3397
|
}
|
|
2277
3398
|
retainRemoteStream() {
|
|
@@ -2286,10 +3407,19 @@ var CloudDocumentHandle = class {
|
|
|
2286
3407
|
...this.manifestCache,
|
|
2287
3408
|
auditHead: row.id
|
|
2288
3409
|
};
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
3410
|
+
if (row.withheld) {
|
|
3411
|
+
const meta = row.payload?.meta;
|
|
3412
|
+
if (meta && row.originSessionId !== this.sessionId) this.absorbMutation(meta, ["annotations", "forms"]);
|
|
3413
|
+
return;
|
|
3414
|
+
}
|
|
3415
|
+
if (row.kind === "signature.complete") {
|
|
3416
|
+
const sha = row.payload.version?.sha256;
|
|
3417
|
+
if (typeof sha === "string") this.pool.versioned(sha);
|
|
3418
|
+
}
|
|
3419
|
+
for (const event of auditRowToEvents(row, this.sessionId)) {
|
|
3420
|
+
this.absorbRemoteEvent(event);
|
|
3421
|
+
this.hub.publish(event);
|
|
3422
|
+
}
|
|
2293
3423
|
},
|
|
2294
3424
|
onFullRefresh: () => {
|
|
2295
3425
|
this.manifestCache = null;
|
|
@@ -2302,7 +3432,9 @@ var CloudDocumentHandle = class {
|
|
|
2302
3432
|
},
|
|
2303
3433
|
onAuthLost: () => {
|
|
2304
3434
|
this.sseClient = null;
|
|
2305
|
-
}
|
|
3435
|
+
},
|
|
3436
|
+
onSession: (status) => this.pool.sync(status),
|
|
3437
|
+
onConnected: (connected) => this.pool.connected(connected)
|
|
2306
3438
|
});
|
|
2307
3439
|
this.sseClient.open();
|
|
2308
3440
|
}
|
|
@@ -2313,55 +3445,73 @@ var CloudDocumentHandle = class {
|
|
|
2313
3445
|
this.sseClient = null;
|
|
2314
3446
|
}
|
|
2315
3447
|
}
|
|
2316
|
-
/** Patch the cached manifest from a
|
|
3448
|
+
/** Patch the cached manifest from a remote event's coherence pins — the
|
|
2317
3449
|
* same absorb rails local mutations use, so reads stay warm no matter
|
|
2318
3450
|
* whose hand caused the change. */
|
|
2319
3451
|
absorbRemoteEvent(event) {
|
|
2320
3452
|
switch (event.type) {
|
|
2321
|
-
case "
|
|
3453
|
+
case "pages.scaleSet":
|
|
2322
3454
|
this.absorbMutation(event.meta, []);
|
|
2323
3455
|
return;
|
|
2324
|
-
case "
|
|
2325
|
-
case "
|
|
2326
|
-
case "
|
|
2327
|
-
case "
|
|
3456
|
+
case "annotations.created":
|
|
3457
|
+
case "annotations.updated":
|
|
3458
|
+
case "annotations.deleted":
|
|
3459
|
+
case "annotations.reordered":
|
|
2328
3460
|
this.absorbMutation(event.meta, ["annotations"]);
|
|
2329
3461
|
return;
|
|
2330
|
-
case "pages.
|
|
3462
|
+
case "pages.reordered":
|
|
2331
3463
|
case "pages.rotated":
|
|
2332
|
-
|
|
3464
|
+
case "pages.named":
|
|
3465
|
+
this.absorbMutation(event.meta, ["layout"]);
|
|
2333
3466
|
return;
|
|
2334
3467
|
case "pages.deleted":
|
|
2335
|
-
|
|
3468
|
+
this.absorbPageDelete(event.meta, event.pages);
|
|
2336
3469
|
return;
|
|
2337
3470
|
case "pages.inserted":
|
|
2338
|
-
|
|
3471
|
+
this.absorbPageInsert(event.meta);
|
|
2339
3472
|
return;
|
|
2340
3473
|
case "metadata.updated":
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
case "attachment.created":
|
|
2344
|
-
case "attachment.deleted":
|
|
2345
|
-
if (event.cache) this.absorbAttachments(event.cache);
|
|
3474
|
+
case "metadata.customUpdated":
|
|
3475
|
+
this.absorbMutation(event.meta, ["metadata"]);
|
|
2346
3476
|
return;
|
|
2347
|
-
case "
|
|
2348
|
-
case "
|
|
2349
|
-
|
|
2350
|
-
case "form.fieldCreated":
|
|
2351
|
-
case "form.fieldUpdated":
|
|
2352
|
-
case "form.fieldDeleted":
|
|
2353
|
-
case "form.widgetAttached":
|
|
2354
|
-
case "form.widgetDetached":
|
|
2355
|
-
this.absorbMutation(event.meta, ["annotations"]);
|
|
3477
|
+
case "attachments.created":
|
|
3478
|
+
case "attachments.deleted":
|
|
3479
|
+
this.absorbMutation(event.meta, ["attachments"]);
|
|
2356
3480
|
return;
|
|
2357
|
-
case "
|
|
2358
|
-
|
|
3481
|
+
case "forms.valueSet":
|
|
3482
|
+
case "forms.repaired":
|
|
3483
|
+
case "forms.created":
|
|
3484
|
+
case "forms.updated":
|
|
3485
|
+
case "forms.deleted":
|
|
3486
|
+
case "forms.restored":
|
|
3487
|
+
case "forms.widgetAdded":
|
|
3488
|
+
case "forms.widgetRemoved":
|
|
3489
|
+
case "forms.widgetUpdated":
|
|
3490
|
+
case "forms.widgetDeleted":
|
|
3491
|
+
case "forms.widgetRestored":
|
|
3492
|
+
case "forms.widgetsReordered":
|
|
3493
|
+
case "forms.calculationsReordered":
|
|
3494
|
+
this.absorbMutation(event.meta, ["forms"]);
|
|
2359
3495
|
return;
|
|
2360
3496
|
case "pages.flattened":
|
|
2361
|
-
|
|
3497
|
+
case "annotations.flattened":
|
|
3498
|
+
this.absorbMutation(event.meta, [
|
|
3499
|
+
"content",
|
|
3500
|
+
"annotations",
|
|
3501
|
+
"forms"
|
|
3502
|
+
]);
|
|
3503
|
+
return;
|
|
3504
|
+
case "signatures.prepared":
|
|
3505
|
+
case "document.versioned":
|
|
3506
|
+
this.manifestCache = null;
|
|
3507
|
+
this.inflightManifest = null;
|
|
2362
3508
|
return;
|
|
2363
3509
|
case "redaction.applied":
|
|
2364
|
-
|
|
3510
|
+
this.absorbMutation(event.meta, [
|
|
3511
|
+
"content",
|
|
3512
|
+
"annotations",
|
|
3513
|
+
"forms"
|
|
3514
|
+
]);
|
|
2365
3515
|
return;
|
|
2366
3516
|
}
|
|
2367
3517
|
}
|
|
@@ -2392,22 +3542,6 @@ function fallbackUnknownHead(id) {
|
|
|
2392
3542
|
}
|
|
2393
3543
|
};
|
|
2394
3544
|
}
|
|
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
3545
|
//#endregion
|
|
2412
3546
|
//#region src/share.ts
|
|
2413
3547
|
/**
|
|
@@ -2491,8 +3625,11 @@ function shareSessionSource(baseUrl, shareToken, opts = {}) {
|
|
|
2491
3625
|
}
|
|
2492
3626
|
//#endregion
|
|
2493
3627
|
//#region src/transport/HttpClient.ts
|
|
3628
|
+
/** CloudPDF SaaS: where an engine connects when it names no server of its own. */
|
|
3629
|
+
const CLOUDPDF_SAAS_URL = "https://engine.cloudpdf.com";
|
|
2494
3630
|
var HttpClient = class HttpClient {
|
|
2495
|
-
constructor(
|
|
3631
|
+
constructor(options) {
|
|
3632
|
+
this.options = options;
|
|
2496
3633
|
_defineProperty(this, "base", void 0);
|
|
2497
3634
|
_defineProperty(this, "tokenFn", void 0);
|
|
2498
3635
|
_defineProperty(this, "fetchFn", void 0);
|
|
@@ -2500,13 +3637,13 @@ var HttpClient = class HttpClient {
|
|
|
2500
3637
|
_defineProperty(this, "onRetry", void 0);
|
|
2501
3638
|
_defineProperty(this, "sessionId", void 0);
|
|
2502
3639
|
_defineProperty(this, "cdnBinding", null);
|
|
2503
|
-
this.base =
|
|
2504
|
-
const token =
|
|
3640
|
+
this.base = (options.baseUrl ?? "https://engine.cloudpdf.com").replace(/\/$/, "");
|
|
3641
|
+
const token = options.token;
|
|
2505
3642
|
this.tokenFn = token === void 0 ? null : typeof token === "function" ? token : () => token;
|
|
2506
|
-
this.sessionId =
|
|
2507
|
-
this.fetchFn =
|
|
2508
|
-
this.docAffinityHeader =
|
|
2509
|
-
this.onRetry =
|
|
3643
|
+
this.sessionId = options.sessionId ?? null;
|
|
3644
|
+
this.fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
3645
|
+
this.docAffinityHeader = options.docAffinityHeader ?? true;
|
|
3646
|
+
this.onRetry = options.onRetry;
|
|
2510
3647
|
}
|
|
2511
3648
|
/** Normalized server base URL (no trailing slash). */
|
|
2512
3649
|
get baseUrl() {
|
|
@@ -2522,18 +3659,17 @@ var HttpClient = class HttpClient {
|
|
|
2522
3659
|
return this.fetchFn;
|
|
2523
3660
|
}
|
|
2524
3661
|
/**
|
|
2525
|
-
*
|
|
2526
|
-
*
|
|
2527
|
-
*
|
|
2528
|
-
*
|
|
2529
|
-
*
|
|
3662
|
+
* A client for one open document: this client's settings, the document's
|
|
3663
|
+
* own editing session, its own CDN binding, and `token` as its bearer
|
|
3664
|
+
* when the open names one (else this client's). Two open documents never
|
|
3665
|
+
* share object numbers, event echoes or a CDN binding, even two opens of
|
|
3666
|
+
* the same document on one engine.
|
|
2530
3667
|
*/
|
|
2531
|
-
|
|
3668
|
+
forDocument({ sessionId, token }) {
|
|
2532
3669
|
return new HttpClient({
|
|
2533
|
-
|
|
2534
|
-
token,
|
|
2535
|
-
|
|
2536
|
-
fetch: this.fetchFn
|
|
3670
|
+
...this.options,
|
|
3671
|
+
...token !== void 0 ? { token } : {},
|
|
3672
|
+
sessionId
|
|
2537
3673
|
});
|
|
2538
3674
|
}
|
|
2539
3675
|
/**
|
|
@@ -2548,20 +3684,20 @@ var HttpClient = class HttpClient {
|
|
|
2548
3684
|
...headers
|
|
2549
3685
|
}),
|
|
2550
3686
|
signal
|
|
2551
|
-
});
|
|
3687
|
+
}, { session: true });
|
|
2552
3688
|
}
|
|
2553
3689
|
/**
|
|
2554
3690
|
* Install (or clear) the per-session CDN binding. Pass `null` to
|
|
2555
3691
|
* route everything to origin again — useful on session close or
|
|
2556
3692
|
* when an adapter swap happens mid-session (rare).
|
|
2557
3693
|
*
|
|
2558
|
-
* The HttpClient stays dumb about
|
|
3694
|
+
* The HttpClient stays dumb about when to do this; the cloud
|
|
2559
3695
|
* document handle pushes the binding in after /access succeeds.
|
|
2560
3696
|
*
|
|
2561
3697
|
* Side effect: in the browser, signedCookies are written to
|
|
2562
3698
|
* `document.cookie` once per setCdnAccess call so the CDN edge
|
|
2563
|
-
* receives them on subsequent fetches.
|
|
2564
|
-
*
|
|
3699
|
+
* receives them on subsequent fetches. Outside the browser signed
|
|
3700
|
+
* cookies are ignored: this client keeps no cookie jar.
|
|
2565
3701
|
*/
|
|
2566
3702
|
setCdnAccess(binding) {
|
|
2567
3703
|
this.cdnBinding = binding;
|
|
@@ -2716,29 +3852,43 @@ var HttpClient = class HttpClient {
|
|
|
2716
3852
|
return await this.getFile(retryPath, signal);
|
|
2717
3853
|
}
|
|
2718
3854
|
}
|
|
2719
|
-
async postMultipartJson(path, body, parser, signal) {
|
|
3855
|
+
async postMultipartJson(path, body, parser, signal, options = {}) {
|
|
2720
3856
|
const res = await this.request(path, {
|
|
2721
3857
|
method: "POST",
|
|
2722
3858
|
body,
|
|
2723
3859
|
signal
|
|
2724
|
-
});
|
|
3860
|
+
}, options);
|
|
2725
3861
|
return await this.parseJsonResponse(res, parser);
|
|
2726
3862
|
}
|
|
2727
3863
|
/** PATCH counterpart of {@link postMultipartJson} (binary-carrying mutations). */
|
|
2728
|
-
async patchMultipartJson(path, body, parser, signal) {
|
|
3864
|
+
async patchMultipartJson(path, body, parser, signal, options = {}) {
|
|
2729
3865
|
const res = await this.request(path, {
|
|
2730
3866
|
method: "PATCH",
|
|
2731
3867
|
body,
|
|
2732
3868
|
signal
|
|
2733
|
-
});
|
|
3869
|
+
}, options);
|
|
2734
3870
|
return await this.parseJsonResponse(res, parser);
|
|
2735
3871
|
}
|
|
2736
|
-
async postJson(path, body, parser, signal) {
|
|
2737
|
-
const res = await this.requestJson(path, "POST", body, signal);
|
|
3872
|
+
async postJson(path, body, parser, signal, options = {}) {
|
|
3873
|
+
const res = await this.requestJson(path, "POST", body, signal, options);
|
|
2738
3874
|
return await this.parseJsonResponse(res, parser);
|
|
2739
3875
|
}
|
|
2740
|
-
async putJson(path, body, parser, signal) {
|
|
2741
|
-
return this.parseJsonResponse(await this.requestJson(path, "PUT", body, signal), parser);
|
|
3876
|
+
async putJson(path, body, parser, signal, options = {}) {
|
|
3877
|
+
return this.parseJsonResponse(await this.requestJson(path, "PUT", body, signal, options), parser);
|
|
3878
|
+
}
|
|
3879
|
+
/** POST a JSON body and parse a `multipart/form-data` response (a large annotation export). */
|
|
3880
|
+
async postJsonFormData(path, body, signal) {
|
|
3881
|
+
const res = await this.request(path, {
|
|
3882
|
+
method: "POST",
|
|
3883
|
+
body: JSON.stringify(body),
|
|
3884
|
+
headers: new Headers({
|
|
3885
|
+
"Content-Type": "application/json",
|
|
3886
|
+
Accept: "multipart/form-data"
|
|
3887
|
+
}),
|
|
3888
|
+
signal
|
|
3889
|
+
});
|
|
3890
|
+
if (!res.ok) await this.throwFromBody(res);
|
|
3891
|
+
return await res.formData();
|
|
2742
3892
|
}
|
|
2743
3893
|
/** POST a JSON body and return the raw binary response (pages.extract). */
|
|
2744
3894
|
async postJsonBytes(path, body, signal) {
|
|
@@ -2747,7 +3897,7 @@ var HttpClient = class HttpClient {
|
|
|
2747
3897
|
return new Uint8Array(await res.arrayBuffer());
|
|
2748
3898
|
}
|
|
2749
3899
|
/** POST a raw binary body (FDF/XFDF import) and parse the JSON response. */
|
|
2750
|
-
async postBytesJson(path, bytes, contentType, parser, signal) {
|
|
3900
|
+
async postBytesJson(path, bytes, contentType, parser, signal, options = {}) {
|
|
2751
3901
|
const headers = new Headers({ "Content-Type": contentType });
|
|
2752
3902
|
const body = new Uint8Array(bytes).buffer;
|
|
2753
3903
|
const res = await this.request(path, {
|
|
@@ -2755,61 +3905,73 @@ var HttpClient = class HttpClient {
|
|
|
2755
3905
|
body,
|
|
2756
3906
|
headers,
|
|
2757
3907
|
signal
|
|
2758
|
-
});
|
|
3908
|
+
}, options);
|
|
2759
3909
|
return await this.parseJsonResponse(res, parser);
|
|
2760
3910
|
}
|
|
2761
|
-
async patchJson(path, body, parser, signal) {
|
|
2762
|
-
const res = await this.requestJson(path, "PATCH", body, signal);
|
|
3911
|
+
async patchJson(path, body, parser, signal, options = {}) {
|
|
3912
|
+
const res = await this.requestJson(path, "PATCH", body, signal, options);
|
|
2763
3913
|
return await this.parseJsonResponse(res, parser);
|
|
2764
3914
|
}
|
|
2765
|
-
async deleteJson(path, parser, signal) {
|
|
3915
|
+
async deleteJson(path, parser, signal, options = {}) {
|
|
2766
3916
|
const res = await this.request(path, {
|
|
2767
3917
|
method: "DELETE",
|
|
2768
3918
|
signal
|
|
2769
|
-
});
|
|
3919
|
+
}, options);
|
|
2770
3920
|
return await this.parseJsonResponse(res, parser);
|
|
2771
3921
|
}
|
|
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) {
|
|
3922
|
+
requestJson(path, method, body, signal, options = {}) {
|
|
2786
3923
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
2787
3924
|
return this.request(path, {
|
|
2788
3925
|
method,
|
|
2789
3926
|
body: JSON.stringify(body ?? {}),
|
|
2790
3927
|
headers,
|
|
2791
3928
|
signal
|
|
2792
|
-
});
|
|
3929
|
+
}, options);
|
|
2793
3930
|
}
|
|
2794
|
-
async request(path, init) {
|
|
2795
|
-
const { url, extraHeader, routedToCdn } = this.resolveOutgoing(path);
|
|
3931
|
+
async request(path, init, options = {}) {
|
|
3932
|
+
const { url, extraHeader, routedToCdn } = this.resolveOutgoing(path, init.method ?? "GET");
|
|
2796
3933
|
const headers = new Headers(init.headers ?? {});
|
|
2797
3934
|
if (this.tokenFn) {
|
|
2798
3935
|
const token = await this.tokenFn();
|
|
2799
3936
|
headers.set("Authorization", `Bearer ${token}`);
|
|
2800
3937
|
}
|
|
2801
3938
|
if (!headers.has("Accept")) headers.set("Accept", "application/json");
|
|
2802
|
-
|
|
3939
|
+
const { write } = options;
|
|
3940
|
+
if (this.sessionId && (write || options.session)) headers.set("X-Engine-Session-Id", this.sessionId);
|
|
3941
|
+
if (write) {
|
|
3942
|
+
if (write.opId !== void 0) headers.set("Idempotency-Key", write.opId);
|
|
3943
|
+
if (write.reserveObjectNumbers && write.reserveObjectNumbers > 0) headers.set("EmbedPDF-Reserve-Object-Numbers", String(write.reserveObjectNumbers));
|
|
3944
|
+
}
|
|
2803
3945
|
if (extraHeader) headers.set(extraHeader.name, extraHeader.value);
|
|
2804
3946
|
if (this.docAffinityHeader && !routedToCdn) {
|
|
2805
3947
|
const doc = /^\/v1\/docs\/([^/]+)\//.exec(path);
|
|
2806
3948
|
if (doc) headers.set("X-CloudPDF-Doc", decodeURIComponent(doc[1]));
|
|
2807
3949
|
}
|
|
2808
|
-
for (let attempt = 0;; attempt++) {
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
3950
|
+
for (let attempt = 0, networkFailures = 0;; attempt++) {
|
|
3951
|
+
let res;
|
|
3952
|
+
try {
|
|
3953
|
+
res = await this.execute(url, {
|
|
3954
|
+
...init,
|
|
3955
|
+
headers
|
|
3956
|
+
});
|
|
3957
|
+
} catch (err) {
|
|
3958
|
+
const wait = HttpClient.NETWORK_RETRY_MS[networkFailures];
|
|
3959
|
+
if (!write || wait === void 0 || !_embedpdf_engine_core_runtime.EngineError.is(err, _embedpdf_engine_core_runtime.EngineErrorCode.Network) || isStreamBody(init.body ?? null)) throw err;
|
|
3960
|
+
networkFailures++;
|
|
3961
|
+
const waitMs = wait * (.9 + .2 * Math.random());
|
|
3962
|
+
this.onRetry?.({
|
|
3963
|
+
path,
|
|
3964
|
+
code: _embedpdf_engine_core_runtime.EngineErrorCode.Network,
|
|
3965
|
+
attempt,
|
|
3966
|
+
waitMs
|
|
3967
|
+
});
|
|
3968
|
+
await sleepRacingAbort(waitMs, init.signal ?? null);
|
|
3969
|
+
continue;
|
|
3970
|
+
}
|
|
3971
|
+
if (res.ok && write?.onObjectNumbers) {
|
|
3972
|
+
const ranges = (0, _embedpdf_engine_core_runtime.parseObjectNumberRanges)(res.headers.get("EmbedPDF-Object-Numbers"));
|
|
3973
|
+
if (ranges.length > 0) write.onObjectNumbers(ranges);
|
|
3974
|
+
}
|
|
2813
3975
|
if (res.status !== 503 || attempt >= HttpClient.MAX_RETRIES || isStreamBody(init.body ?? null)) return res;
|
|
2814
3976
|
const code = await peekErrorCode(res);
|
|
2815
3977
|
if (code === null || !RETRYABLE_503.has(code)) return res;
|
|
@@ -2844,13 +4006,15 @@ var HttpClient = class HttpClient {
|
|
|
2844
4006
|
}
|
|
2845
4007
|
/**
|
|
2846
4008
|
* Decide where this request actually goes. With no CDN binding (or
|
|
2847
|
-
* binding's `cdn` is null) every request stays on origin
|
|
4009
|
+
* binding's `cdn` is null) every request stays on origin, and so does
|
|
4010
|
+
* every request that isn't a read (`GET`, `HEAD`): a write must reach the
|
|
4011
|
+
* server itself, whatever read its path looks like. Otherwise
|
|
2848
4012
|
* `applyCdnAccess` decides per-request based on the resource the
|
|
2849
4013
|
* path resolves to (and whether the caller's scope granted CDN
|
|
2850
4014
|
* access to it).
|
|
2851
4015
|
*/
|
|
2852
|
-
resolveOutgoing(path) {
|
|
2853
|
-
if (!this.cdnBinding || !this.cdnBinding.cdn) return {
|
|
4016
|
+
resolveOutgoing(path, method) {
|
|
4017
|
+
if (!(method === "GET" || method === "HEAD") || !this.cdnBinding || !this.cdnBinding.cdn) return {
|
|
2854
4018
|
url: `${this.baseUrl}${path}`,
|
|
2855
4019
|
extraHeader: null,
|
|
2856
4020
|
routedToCdn: false
|
|
@@ -2888,11 +4052,17 @@ var HttpClient = class HttpClient {
|
|
|
2888
4052
|
_defineProperty(HttpClient, "MAX_RETRIES", 2);
|
|
2889
4053
|
_defineProperty(HttpClient, "RETRY_AFTER_CAP_MS", 5e3);
|
|
2890
4054
|
_defineProperty(HttpClient, "RETRY_AFTER_DEFAULT_MS", 1e3);
|
|
4055
|
+
_defineProperty(HttpClient, "NETWORK_RETRY_MS", [
|
|
4056
|
+
250,
|
|
4057
|
+
1e3,
|
|
4058
|
+
3e3
|
|
4059
|
+
]);
|
|
2891
4060
|
function mapStatusToCode(status) {
|
|
2892
4061
|
if (status === 401) return _embedpdf_engine_core_runtime.EngineErrorCode.Unauthenticated;
|
|
2893
4062
|
if (status === 403) return _embedpdf_engine_core_runtime.EngineErrorCode.Forbidden;
|
|
2894
|
-
if (status === 409) return _embedpdf_engine_core_runtime.EngineErrorCode.
|
|
4063
|
+
if (status === 409) return _embedpdf_engine_core_runtime.EngineErrorCode.LayerVersionConflict;
|
|
2895
4064
|
if (status === 404) return _embedpdf_engine_core_runtime.EngineErrorCode.NotFound;
|
|
4065
|
+
if (status === 413) return _embedpdf_engine_core_runtime.EngineErrorCode.PayloadTooLarge;
|
|
2896
4066
|
if (status === 422) return _embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed;
|
|
2897
4067
|
if (status === 499) return _embedpdf_engine_core_runtime.EngineErrorCode.Aborted;
|
|
2898
4068
|
if (status === 400) return _embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg;
|
|
@@ -2941,22 +4111,19 @@ function sleepRacingAbort(ms, signal) {
|
|
|
2941
4111
|
*/
|
|
2942
4112
|
var CloudEngine = class CloudEngine {
|
|
2943
4113
|
static fromOptions(opts) {
|
|
2944
|
-
|
|
2945
|
-
return new CloudEngine(new HttpClient({
|
|
2946
|
-
...opts,
|
|
2947
|
-
sessionId
|
|
2948
|
-
}), sessionId);
|
|
4114
|
+
return new CloudEngine(new HttpClient(opts));
|
|
2949
4115
|
}
|
|
2950
|
-
constructor(http
|
|
4116
|
+
constructor(http) {
|
|
2951
4117
|
this.http = http;
|
|
2952
|
-
this
|
|
4118
|
+
_defineProperty(this, "canOpenFiles", false);
|
|
2953
4119
|
_defineProperty(this, "destroyed", false);
|
|
4120
|
+
_defineProperty(this, "handles", /* @__PURE__ */ new Set());
|
|
2954
4121
|
}
|
|
2955
4122
|
open(input, options) {
|
|
2956
4123
|
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
4124
|
options?.scope;
|
|
2958
4125
|
options?.identity;
|
|
2959
|
-
const effectivePassword = options
|
|
4126
|
+
const effectivePassword = options?.password ?? null;
|
|
2960
4127
|
if (input.kind === "share") {
|
|
2961
4128
|
const raw = shareSessionSource(this.http.baseUrl, input.shareToken, {
|
|
2962
4129
|
...input.sharePassword !== void 0 ? { password: input.sharePassword } : {},
|
|
@@ -2971,28 +4138,39 @@ var CloudEngine = class CloudEngine {
|
|
|
2971
4138
|
};
|
|
2972
4139
|
return this.open({
|
|
2973
4140
|
kind: "token",
|
|
2974
|
-
token
|
|
2975
|
-
password: input.password ?? null
|
|
4141
|
+
token
|
|
2976
4142
|
}, options);
|
|
2977
4143
|
}
|
|
2978
4144
|
if (input.kind === "token") {
|
|
2979
4145
|
const tokenSource = input.token;
|
|
2980
4146
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
2981
|
-
const docHttp = this.http.
|
|
4147
|
+
const docHttp = this.http.forDocument({
|
|
4148
|
+
sessionId: newSessionId(),
|
|
4149
|
+
token: tokenSource
|
|
4150
|
+
});
|
|
2982
4151
|
const token = await docHttp.currentToken();
|
|
2983
4152
|
const claims = decodeUnverifiedClaims(token);
|
|
2984
4153
|
const docId = claims.doc_id;
|
|
2985
4154
|
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
4155
|
const layerName = typeof claims.layer_name === "string" && claims.layer_name.length > 0 ? claims.layer_name : _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME;
|
|
2987
4156
|
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
|
-
|
|
4157
|
+
const handle = new CloudDocumentHandle(docHttp, head.id, layerName, head, token, () => this.handles.delete(handle));
|
|
4158
|
+
this.handles.add(handle);
|
|
4159
|
+
try {
|
|
4160
|
+
await establishOnOpen(handle, head, signal, effectivePassword, false);
|
|
4161
|
+
} catch (error) {
|
|
4162
|
+
await handle.close();
|
|
4163
|
+
throw error;
|
|
4164
|
+
}
|
|
2990
4165
|
return handle;
|
|
2991
4166
|
});
|
|
2992
4167
|
}
|
|
2993
4168
|
if (input.kind === "id") {
|
|
2994
4169
|
const id = input.id;
|
|
2995
|
-
const docHttp =
|
|
4170
|
+
const docHttp = this.http.forDocument({
|
|
4171
|
+
sessionId: newSessionId(),
|
|
4172
|
+
...input.token ? { token: input.token } : {}
|
|
4173
|
+
});
|
|
2996
4174
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
2997
4175
|
let layerName = input.layerName ?? _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME;
|
|
2998
4176
|
let resolvedToken = null;
|
|
@@ -3006,46 +4184,97 @@ var CloudEngine = class CloudEngine {
|
|
|
3006
4184
|
if (typeof claims.layer_name === "string" && claims.layer_name.length > 0) layerName = claims.layer_name;
|
|
3007
4185
|
}
|
|
3008
4186
|
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
|
-
|
|
4187
|
+
const handle = new CloudDocumentHandle(docHttp, head.id, layerName, head, resolvedToken, () => this.handles.delete(handle));
|
|
4188
|
+
this.handles.add(handle);
|
|
4189
|
+
const tenant = resolvedToken !== null && typeof decodeUnverifiedClaims(resolvedToken).doc_id !== "string";
|
|
4190
|
+
try {
|
|
4191
|
+
await establishOnOpen(handle, head, signal, effectivePassword, tenant);
|
|
4192
|
+
} catch (error) {
|
|
4193
|
+
await handle.close();
|
|
4194
|
+
throw error;
|
|
4195
|
+
}
|
|
3011
4196
|
return handle;
|
|
3012
4197
|
});
|
|
3013
4198
|
}
|
|
3014
4199
|
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
4200
|
}
|
|
4201
|
+
/**
|
|
4202
|
+
* Close every document this engine opened (their event streams stop), then
|
|
4203
|
+
* refuse new opens. As on the local engine, a handle is unusable after.
|
|
4204
|
+
*/
|
|
3016
4205
|
destroy() {
|
|
3017
4206
|
if (this.destroyed) return _embedpdf_engine_core_runtime.AbortablePromise.resolveValue(void 0);
|
|
3018
4207
|
this.destroyed = true;
|
|
3019
|
-
|
|
4208
|
+
const open = [...this.handles];
|
|
4209
|
+
this.handles.clear();
|
|
4210
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async () => {
|
|
4211
|
+
await Promise.all(open.map((handle) => handle.close()));
|
|
4212
|
+
});
|
|
3020
4213
|
}
|
|
3021
4214
|
};
|
|
3022
4215
|
/**
|
|
4216
|
+
* A session id for a document being opened. Every open is its own session,
|
|
4217
|
+
* even a second open of the same document on this engine. The id stamps the
|
|
4218
|
+
* document's own events' origins and travels as `X-Engine-Session-Id`: the
|
|
4219
|
+
* server keeps the session's object numbers under it and marks its audit
|
|
4220
|
+
* rows with it, so the document's stream drops its own echoes and hears
|
|
4221
|
+
* every other open as remote.
|
|
4222
|
+
*/
|
|
4223
|
+
function newSessionId() {
|
|
4224
|
+
return `cloud:${(0, _embedpdf_engine_core_runtime.generateUuid)()}`;
|
|
4225
|
+
}
|
|
4226
|
+
/**
|
|
4227
|
+
* What `open()` learns before handing the document over: access when the
|
|
4228
|
+
* password or a CDN needs it (see {@link maybeAutoEstablishAccess}), then
|
|
4229
|
+
* what the document's signatures forbid (the manifest). A caller that may
|
|
4230
|
+
* create objects calls `/access` anyway, beside the manifest fetch, for its
|
|
4231
|
+
* editing session's first object numbers; on a document locked by a
|
|
4232
|
+
* password, `unlock()` brings them. A failed `/access` doesn't fail the
|
|
4233
|
+
* open: creates then go without numbers until the pool gets some.
|
|
4234
|
+
*/
|
|
4235
|
+
async function establishOnOpen(handle, head, signal, password, tenant) {
|
|
4236
|
+
const accessed = await maybeAutoEstablishAccess(handle, head, signal, password);
|
|
4237
|
+
const security = handle.security;
|
|
4238
|
+
const locked = !accessed && head.access.reasons.includes("password");
|
|
4239
|
+
await Promise.all([!accessed && !locked && (tenant || mayCreateObjects(security)) ? settleLinked(security.establishAccess(), signal) : void 0, handle.learnProtection(signal)]);
|
|
4240
|
+
}
|
|
4241
|
+
/**
|
|
4242
|
+
* Whether the caller may make new objects (annotations as itself, pages,
|
|
4243
|
+
* form fields): the server's own test for handing out object numbers, from
|
|
4244
|
+
* what the client knows before `/access`.
|
|
4245
|
+
*/
|
|
4246
|
+
function mayCreateObjects(security) {
|
|
4247
|
+
return security.allowsAnnotation("create") || security.allows("doc.pages.assemble") || security.allows("doc.forms.modify");
|
|
4248
|
+
}
|
|
4249
|
+
/**
|
|
3023
4250
|
* Post-/head access establishment, in two layers:
|
|
3024
4251
|
*
|
|
3025
|
-
* 1. **Supplied password** — a password given at open is always
|
|
4252
|
+
* 1. **Supplied password** — a password given at open is always tried
|
|
3026
4253
|
* (PDFium parity: the local engine feeds it to the loader no matter
|
|
3027
4254
|
* what). Two triggers, because `/head` only carries the `password`
|
|
3028
|
-
* reason when a password is needed to
|
|
4255
|
+
* reason when a password is needed to read:
|
|
3029
4256
|
* - `reasons` has `'password'` → required-to-read case
|
|
3030
4257
|
* - `head.permissions.canUpgradeToOwner` → permission-only encrypted
|
|
3031
4258
|
* doc; the password can still upgrade to owner. Skipping this arm
|
|
3032
|
-
* would silently drop supplied
|
|
4259
|
+
* would silently drop supplied owner passwords — and diverge from
|
|
3033
4260
|
* the local engine on identical input.
|
|
3034
4261
|
* Outcomes follow "a password failure may only block what the password
|
|
3035
4262
|
* was needed for": a rejection in the required case leaves the handle
|
|
3036
|
-
* locked (the
|
|
4263
|
+
* locked (the prompt says `incorrect`, as on the local engine); in the
|
|
3037
4264
|
* upgrade case the document stays readable and the failure is
|
|
3038
4265
|
* non-fatal. The one /access POST also installs the CDN binding, so a
|
|
3039
4266
|
* successful unlock covers the `password + cdn` combined case.
|
|
3040
4267
|
*
|
|
3041
4268
|
* 2. **CDN-only** — unchanged: `/access` without a password, transparently.
|
|
3042
|
-
* A required password with
|
|
4269
|
+
* A required password with no supplied password never auto-calls; the
|
|
3043
4270
|
* dev prompts and `unlock()` runs the same POST.
|
|
3044
4271
|
*
|
|
3045
4272
|
* Non-password /access failures never break `open()` (the first real
|
|
3046
4273
|
* request still tries origin, where the JWT check produces a regular
|
|
3047
4274
|
* Forbidden) — but they are also never mistaken for a wrong password:
|
|
3048
4275
|
* only a DocPasswordRequired/Incorrect rejection means "wrong password".
|
|
4276
|
+
*
|
|
4277
|
+
* Returns whether `/access` answered.
|
|
3049
4278
|
*/
|
|
3050
4279
|
async function maybeAutoEstablishAccess(handle, head, signal, password) {
|
|
3051
4280
|
const reasons = new Set(head.access.reasons);
|
|
@@ -3054,13 +4283,13 @@ async function maybeAutoEstablishAccess(handle, head, signal, password) {
|
|
|
3054
4283
|
if (await settleLinked(security.unlock({
|
|
3055
4284
|
password,
|
|
3056
4285
|
mode: "any"
|
|
3057
|
-
}), signal)) return;
|
|
3058
|
-
if (reasons.has("password")) return;
|
|
4286
|
+
}), signal)) return true;
|
|
4287
|
+
if (reasons.has("password")) return false;
|
|
3059
4288
|
}
|
|
3060
|
-
if (!head.access.required) return;
|
|
3061
|
-
if (reasons.has("password")) return;
|
|
3062
|
-
if (!reasons.has("cdn")) return;
|
|
3063
|
-
|
|
4289
|
+
if (!head.access.required) return false;
|
|
4290
|
+
if (reasons.has("password")) return false;
|
|
4291
|
+
if (!reasons.has("cdn")) return false;
|
|
4292
|
+
return settleLinked(security.establishAccess(), signal);
|
|
3064
4293
|
}
|
|
3065
4294
|
/** Await an /access attempt with open()-abort linkage. Returns whether it
|
|
3066
4295
|
* succeeded; failures are contained (see maybeAutoEstablishAccess). */
|
|
@@ -3079,9 +4308,6 @@ async function settleLinked(pending, signal) {
|
|
|
3079
4308
|
}
|
|
3080
4309
|
//#endregion
|
|
3081
4310
|
//#region src/index.ts
|
|
3082
|
-
function createCloudEngine(opts) {
|
|
3083
|
-
return CloudEngine.fromOptions(opts);
|
|
3084
|
-
}
|
|
3085
4311
|
/**
|
|
3086
4312
|
* Create a cloud {@link CloudEngine} — the drop-in counterpart of
|
|
3087
4313
|
* `localEngine()`, so swapping local for cloud is a one-import change.
|
|
@@ -3093,59 +4319,46 @@ function createCloudEngine(opts) {
|
|
|
3093
4319
|
* to let a `<Viewer>` own the lifetime instead.
|
|
3094
4320
|
*
|
|
3095
4321
|
* Note the deliberate asymmetry with `localEngine()`: there is no `fonts`
|
|
3096
|
-
* option. Fallback fonts are a
|
|
4322
|
+
* option. Fallback fonts are a server policy on the cloud (`Engine.fonts` is
|
|
3097
4323
|
* `undefined` cloud-side), so they cannot be configured from the client. This
|
|
3098
4324
|
* is the local-vs-cloud split, surfaced in the API.
|
|
3099
4325
|
*
|
|
4326
|
+
* Without a `baseUrl` it connects to CloudPDF SaaS; your own server names itself:
|
|
4327
|
+
*
|
|
3100
4328
|
* ```ts
|
|
3101
|
-
* const engine = cloudEngine(
|
|
4329
|
+
* const engine = cloudEngine(); // CloudPDF SaaS
|
|
4330
|
+
* const own = cloudEngine({ baseUrl: 'https://pdf.example.com' }); // your @cloudpdf/server
|
|
3102
4331
|
* <Viewer engine={engine} plugins={[stagePlugin(), renderPlugin()]} />
|
|
3103
4332
|
* ```
|
|
3104
4333
|
*/
|
|
3105
|
-
function cloudEngine(opts) {
|
|
4334
|
+
function cloudEngine(opts = {}) {
|
|
3106
4335
|
return CloudEngine.fromOptions(opts);
|
|
3107
4336
|
}
|
|
3108
4337
|
//#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
|
-
});
|
|
4338
|
+
exports.CLOUDPDF_SAAS_URL = CLOUDPDF_SAAS_URL;
|
|
3121
4339
|
exports.CloudDocumentAnnotationsService = CloudDocumentAnnotationsService;
|
|
3122
4340
|
exports.CloudDocumentHandle = CloudDocumentHandle;
|
|
3123
4341
|
exports.CloudDocumentPagesService = CloudDocumentPagesService;
|
|
3124
4342
|
exports.CloudEngine = CloudEngine;
|
|
3125
4343
|
exports.CloudMetadataService = CloudMetadataService;
|
|
3126
4344
|
exports.CloudPageAnnotationsService = CloudPageAnnotationsService;
|
|
3127
|
-
exports.CloudPageGeometryService = CloudPageGeometryService;
|
|
3128
4345
|
exports.CloudPageHandle = CloudPageHandle;
|
|
3129
4346
|
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
4347
|
exports.HttpClient = HttpClient;
|
|
3143
4348
|
exports.ShareExchangeError = ShareExchangeError;
|
|
3144
4349
|
exports.cloudEngine = cloudEngine;
|
|
3145
|
-
exports.createCloudEngine = createCloudEngine;
|
|
3146
4350
|
exports.decodeUnverifiedClaims = decodeUnverifiedClaims;
|
|
3147
4351
|
exports.engineErrorFromShareExchange = engineErrorFromShareExchange;
|
|
3148
4352
|
exports.exchangeShareToken = exchangeShareToken;
|
|
3149
4353
|
exports.shareSessionSource = shareSessionSource;
|
|
4354
|
+
var _embedpdf_engine_core_public = require("@embedpdf/engine-core/public");
|
|
4355
|
+
Object.keys(_embedpdf_engine_core_public).forEach(function(k) {
|
|
4356
|
+
if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
4357
|
+
enumerable: true,
|
|
4358
|
+
get: function() {
|
|
4359
|
+
return _embedpdf_engine_core_public[k];
|
|
4360
|
+
}
|
|
4361
|
+
});
|
|
4362
|
+
});
|
|
3150
4363
|
|
|
3151
4364
|
//# sourceMappingURL=index.cjs.map
|