@verentis/sdk 0.2.3 → 0.2.5
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/README.md +159 -8
- package/dist/index.cjs +641 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +662 -74
- package/dist/index.d.ts +662 -74
- package/dist/index.js +629 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -167,6 +167,8 @@ var ContextModule = class {
|
|
|
167
167
|
_file = null;
|
|
168
168
|
_entryPoint = null;
|
|
169
169
|
_apiUrl = null;
|
|
170
|
+
_application = null;
|
|
171
|
+
_installation = null;
|
|
170
172
|
/** The current workspace */
|
|
171
173
|
get workspace() {
|
|
172
174
|
if (!this._workspace) {
|
|
@@ -207,13 +209,24 @@ var ContextModule = class {
|
|
|
207
209
|
get hasEntryPoint() {
|
|
208
210
|
return this._entryPoint !== null;
|
|
209
211
|
}
|
|
212
|
+
get application() {
|
|
213
|
+
return this._application ? { ...this._application } : null;
|
|
214
|
+
}
|
|
215
|
+
get installation() {
|
|
216
|
+
return this._installation ? { ...this._installation } : null;
|
|
217
|
+
}
|
|
210
218
|
/** Update context from the host's init message */
|
|
211
219
|
setFromAppContext(ctx) {
|
|
220
|
+
if (ctx.application !== void 0 && (typeof ctx.application !== "object" || ctx.application === null || typeof ctx.application.manifestPath !== "string" || !ctx.application.manifestPath.startsWith("/") || typeof ctx.application.manifestNodeId !== "string" || !ctx.application.manifestNodeId.trim() || typeof ctx.application.branch !== "string" || !ctx.application.branch || ctx.application.branch !== ctx.application.branch.trim().toLowerCase() || /[\u0000-\u001f\u007f]/.test(ctx.application.branch))) {
|
|
221
|
+
throw new Error("Invalid application launch metadata");
|
|
222
|
+
}
|
|
212
223
|
this._workspace = ctx.workspace;
|
|
213
224
|
this._surface = ctx.surface ?? "file";
|
|
214
225
|
this._file = ctx.file ?? null;
|
|
215
226
|
this._entryPoint = ctx.entryPoint ?? null;
|
|
216
227
|
this._apiUrl = ctx.apiUrl;
|
|
228
|
+
this._application = ctx.application ? { ...ctx.application } : null;
|
|
229
|
+
this._installation = ctx.installation ? { ...ctx.installation } : null;
|
|
217
230
|
}
|
|
218
231
|
/** Set workspace info directly (standalone mode) */
|
|
219
232
|
setWorkspace(workspace) {
|
|
@@ -441,6 +454,49 @@ var FilesModule = class {
|
|
|
441
454
|
headers: this.workspaceHeaders()
|
|
442
455
|
});
|
|
443
456
|
}
|
|
457
|
+
/** Read metadata by stable node identity on an explicit branch. */
|
|
458
|
+
async getNode(nodeId, options) {
|
|
459
|
+
requireNonBlank(nodeId, "Node ID");
|
|
460
|
+
requireNonBlank(options.branch, "Branch");
|
|
461
|
+
return this.http.request("GET", `/nodes/${encodeURIComponent(nodeId)}/file`, {
|
|
462
|
+
headers: { ...this.workspaceHeaders(), "X-Branch": options.branch }
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
/** Read bytes and their matching content revision in the same response. */
|
|
466
|
+
async readNodeContent(nodeId, options) {
|
|
467
|
+
requireNonBlank(nodeId, "Node ID");
|
|
468
|
+
requireNonBlank(options.branch, "Branch");
|
|
469
|
+
const response = await this.http.requestRaw("GET", `/nodes/${encodeURIComponent(nodeId)}/content`, {
|
|
470
|
+
headers: { ...this.workspaceHeaders(), "X-Branch": options.branch, Accept: "*/*" }
|
|
471
|
+
});
|
|
472
|
+
await this.http.ensureSuccess(response);
|
|
473
|
+
const revision = response.headers.get("ETag");
|
|
474
|
+
if (!revision || !/^"[a-fA-F0-9]{32}"$/.test(revision)) {
|
|
475
|
+
throw new Error("Node-content response did not include a valid strong revision ETag.");
|
|
476
|
+
}
|
|
477
|
+
return { content: await response.blob(), revision };
|
|
478
|
+
}
|
|
479
|
+
/** Replace existing node bytes conditionally; never creates or changes path/MIME. */
|
|
480
|
+
async writeNodeContent(nodeId, options) {
|
|
481
|
+
requireNonBlank(nodeId, "Node ID");
|
|
482
|
+
requireNonBlank(options.branch, "Branch");
|
|
483
|
+
requireNonBlank(options.idempotencyKey, "Idempotency key");
|
|
484
|
+
const revision = /^[a-fA-F0-9]{32}$/.test(options.revision) ? `"${options.revision}"` : options.revision;
|
|
485
|
+
if (!/^"[a-fA-F0-9]{32}"$/.test(revision)) {
|
|
486
|
+
throw new Error("A single strong node-content revision is required.");
|
|
487
|
+
}
|
|
488
|
+
const content = typeof options.content === "string" ? new Blob([options.content]) : options.content instanceof ArrayBuffer ? new Blob([options.content]) : options.content;
|
|
489
|
+
return this.http.request("PUT", `/nodes/${encodeURIComponent(nodeId)}/content`, {
|
|
490
|
+
body: content,
|
|
491
|
+
headers: {
|
|
492
|
+
...this.workspaceHeaders(),
|
|
493
|
+
"X-Branch": options.branch,
|
|
494
|
+
"If-Match": revision,
|
|
495
|
+
"Idempotency-Key": options.idempotencyKey,
|
|
496
|
+
"Content-Type": "application/octet-stream"
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
444
500
|
/**
|
|
445
501
|
* Read the raw content of a file as a Blob.
|
|
446
502
|
*/
|
|
@@ -532,11 +588,18 @@ var FilesModule = class {
|
|
|
532
588
|
if (options.contentType) metadata.contentType = options.contentType;
|
|
533
589
|
if (options.branch) metadata.branch = options.branch;
|
|
534
590
|
formData.append("metadata", JSON.stringify(metadata));
|
|
535
|
-
await this.http.request("POST", `/files/${normalizedPath}`, {
|
|
591
|
+
await this.http.request("POST", `/files/${normalizedPath.split("/").map(encodeURIComponent).join("/")}`, {
|
|
536
592
|
body: formData,
|
|
537
|
-
headers:
|
|
593
|
+
headers: {
|
|
594
|
+
...this.workspaceHeaders(),
|
|
595
|
+
...options.createOnly ? { "If-None-Match": "*" } : {}
|
|
596
|
+
}
|
|
538
597
|
});
|
|
539
598
|
}
|
|
599
|
+
/** Create without a read-before-write race or overwrite of an existing live file. */
|
|
600
|
+
async create(path, options) {
|
|
601
|
+
await this.write(path, { ...options, createOnly: true });
|
|
602
|
+
}
|
|
540
603
|
/**
|
|
541
604
|
* Move or rename a file.
|
|
542
605
|
*/
|
|
@@ -557,14 +620,19 @@ var FilesModule = class {
|
|
|
557
620
|
* Delete a file.
|
|
558
621
|
*/
|
|
559
622
|
async remove(options) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
623
|
+
const path = normalizePath(options.path);
|
|
624
|
+
requireNonBlank(path, "File path");
|
|
625
|
+
const branch = options.branch ?? "main";
|
|
626
|
+
requireNonBlank(branch, "Branch");
|
|
627
|
+
if (options.headStamp !== void 0) requireNonBlank(options.headStamp, "Path head stamp");
|
|
628
|
+
if (options.idempotencyKey !== void 0) requireNonBlank(options.idempotencyKey, "Idempotency key");
|
|
629
|
+
await this.http.request("DELETE", `/files/${path.split("/").map(encodeURIComponent).join("/")}`, {
|
|
630
|
+
params: { branch },
|
|
565
631
|
headers: {
|
|
566
632
|
...this.workspaceHeaders(),
|
|
567
|
-
"
|
|
633
|
+
...options.headStamp === void 0 ? {} : { "If-Match": options.headStamp },
|
|
634
|
+
...options.idempotencyKey === void 0 ? {} : { "Idempotency-Key": options.idempotencyKey },
|
|
635
|
+
...options.mode === void 0 ? {} : { "X-Delete-Mode": options.mode === "deep" ? "Deep" : "Shallow" }
|
|
568
636
|
}
|
|
569
637
|
});
|
|
570
638
|
}
|
|
@@ -576,6 +644,9 @@ var FilesModule = class {
|
|
|
576
644
|
return headers;
|
|
577
645
|
}
|
|
578
646
|
};
|
|
647
|
+
function requireNonBlank(value, name) {
|
|
648
|
+
if (value.trim().length === 0) throw new Error(`${name} is required.`);
|
|
649
|
+
}
|
|
579
650
|
function normalizePath(path) {
|
|
580
651
|
return path.replace(/\/+/g, "/").replace(/^\//, "").replace(/\/$/, "");
|
|
581
652
|
}
|
|
@@ -592,6 +663,52 @@ var InstallationsModule = class {
|
|
|
592
663
|
}
|
|
593
664
|
http;
|
|
594
665
|
workspaceId;
|
|
666
|
+
/** Resolve canonical publisher/package identity, never the installation's runtime display name. */
|
|
667
|
+
async resolvePackageId(identity, options) {
|
|
668
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9]+(?:-[a-z0-9]+)*$/.test(identity)) {
|
|
669
|
+
throw new Error("A canonical publisher/package identity is required");
|
|
670
|
+
}
|
|
671
|
+
const result = await this.http.request("GET", `/packages/by-package-id/${identity}`, {
|
|
672
|
+
params: { workspaceId: this.requireWorkspaceId(options?.workspaceId) }
|
|
673
|
+
});
|
|
674
|
+
if (!result || typeof result.id !== "string" || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(result.id)) {
|
|
675
|
+
throw new Error("The catalog response omitted its stable package identity");
|
|
676
|
+
}
|
|
677
|
+
return result.id;
|
|
678
|
+
}
|
|
679
|
+
/** Resolves the complete required graph without installing anything or granting capabilities. */
|
|
680
|
+
async preview(packageId, options) {
|
|
681
|
+
return this.http.request("POST", "/installations/graphs/preview", {
|
|
682
|
+
body: JSON.stringify({
|
|
683
|
+
workspaceId: this.requireWorkspaceId(options?.workspaceId),
|
|
684
|
+
packageId,
|
|
685
|
+
packageVersion: options?.packageVersion,
|
|
686
|
+
installPath: options?.installPath,
|
|
687
|
+
branch: options?.branch,
|
|
688
|
+
installMode: options?.installMode
|
|
689
|
+
}),
|
|
690
|
+
headers: { "Content-Type": "application/json" }
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
/** Explicit, single-use consent to a displayed preview. Never automatically retries accepted installs. */
|
|
694
|
+
async install(preview, consent) {
|
|
695
|
+
if (consent !== true) throw new Error("Explicit consent to the installation graph is required");
|
|
696
|
+
return this.http.request("POST", "/installations/graphs", {
|
|
697
|
+
body: JSON.stringify({
|
|
698
|
+
workspaceId: preview.workspaceId,
|
|
699
|
+
operationId: preview.operationId,
|
|
700
|
+
fingerprint: preview.fingerprint,
|
|
701
|
+
consent
|
|
702
|
+
}),
|
|
703
|
+
headers: { "Content-Type": "application/json" }
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
/** Inspect durable outcomes after a failure or lost response instead of resubmitting consent. */
|
|
707
|
+
async graphResult(operationId, options) {
|
|
708
|
+
return this.http.request("GET", `/installations/graphs/${encodeURIComponent(operationId)}`, {
|
|
709
|
+
params: { workspaceId: this.requireWorkspaceId(options?.workspaceId) }
|
|
710
|
+
});
|
|
711
|
+
}
|
|
595
712
|
async list(options) {
|
|
596
713
|
return this.http.request("GET", "/installations", {
|
|
597
714
|
params: {
|
|
@@ -772,6 +889,176 @@ var SettingsModule = class {
|
|
|
772
889
|
}
|
|
773
890
|
};
|
|
774
891
|
|
|
892
|
+
// src/modules/email.ts
|
|
893
|
+
var EmailModule = class {
|
|
894
|
+
constructor(http, workspaceId) {
|
|
895
|
+
this.http = http;
|
|
896
|
+
this.workspaceId = workspaceId;
|
|
897
|
+
}
|
|
898
|
+
http;
|
|
899
|
+
workspaceId;
|
|
900
|
+
async preview(installationId, options) {
|
|
901
|
+
return this.http.request("GET", `/installations/${encodeURIComponent(installationId)}/email-consent-preview`, {
|
|
902
|
+
params: { workspaceId: this.workspace(options) }
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
async consents(options) {
|
|
906
|
+
return this.http.request("GET", "/email/consents", { params: { workspaceId: this.workspace(options) } });
|
|
907
|
+
}
|
|
908
|
+
/** Approve exactly the reviewed signed candidate; tracking stays off unless explicitly opted in. */
|
|
909
|
+
async consent(preview, approval, options) {
|
|
910
|
+
if (approval !== true) throw new Error("Explicit email consent is required");
|
|
911
|
+
if (!options?.senderGrants?.length) throw new Error("Explicit purpose/sender grants are required");
|
|
912
|
+
return this.http.request("PUT", `/email/consents/${encodeURIComponent(preview.installationId)}`, {
|
|
913
|
+
params: { workspaceId: preview.workspaceId },
|
|
914
|
+
body: JSON.stringify({
|
|
915
|
+
candidateDigest: preview.candidateDigest,
|
|
916
|
+
enabled: true,
|
|
917
|
+
allowOpenTracking: options?.allowOpenTracking ?? false,
|
|
918
|
+
allowClickTracking: options?.allowClickTracking ?? false,
|
|
919
|
+
senderGrants: options.senderGrants.map((grant) => ({ purpose: grant.purpose, senderId: grant.senderId }))
|
|
920
|
+
}),
|
|
921
|
+
headers: { "Content-Type": "application/json" }
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
async revoke(consent, options) {
|
|
925
|
+
return this.http.request("PUT", `/email/consents/${encodeURIComponent(consent.installationId)}`, {
|
|
926
|
+
params: { workspaceId: this.workspace(options) },
|
|
927
|
+
body: JSON.stringify({ candidateDigest: consent.candidateDigest, enabled: false, allowOpenTracking: false, allowClickTracking: false }),
|
|
928
|
+
headers: { "Content-Type": "application/json" }
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
async senders(options) {
|
|
932
|
+
return this.http.request("GET", "/email/senders", { params: { workspaceId: this.workspace(options) } });
|
|
933
|
+
}
|
|
934
|
+
async invoicePdf(recordId, installationId, options) {
|
|
935
|
+
this.mainBranch(options);
|
|
936
|
+
return this.http.request("POST", "/email/artifacts/invoice", {
|
|
937
|
+
body: JSON.stringify({ workspaceId: this.workspace(options), recordId, installationId }),
|
|
938
|
+
headers: { "Content-Type": "application/json" }
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
/** Authenticated immutable bytes; no public URL. The caller owns any local object URL it creates. */
|
|
942
|
+
async artifactContent(artifactId, options) {
|
|
943
|
+
this.mainBranch(options);
|
|
944
|
+
return this.http.request("GET", `/email/artifacts/${encodeURIComponent(artifactId)}/content`, {
|
|
945
|
+
params: { workspaceId: this.workspace(options), branch: "main" }
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
/** A stable key binds this exact request. After a lost response inspect status; never automatically resend. */
|
|
949
|
+
async dispatch(options) {
|
|
950
|
+
this.mainBranch(options);
|
|
951
|
+
return this.http.request("POST", "/email/dispatches", {
|
|
952
|
+
body: JSON.stringify({
|
|
953
|
+
workspaceId: this.workspace(options),
|
|
954
|
+
installationId: options.installationId,
|
|
955
|
+
recordId: options.recordId,
|
|
956
|
+
purpose: options.purpose,
|
|
957
|
+
templateId: options.templateId,
|
|
958
|
+
senderId: options.senderId,
|
|
959
|
+
recipients: options.recipients,
|
|
960
|
+
artifactIds: options.artifactIds,
|
|
961
|
+
idempotencyKey: options.idempotencyKey,
|
|
962
|
+
replyTo: options.replyTo
|
|
963
|
+
}),
|
|
964
|
+
headers: { "Content-Type": "application/json" }
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
async outcome(id, options) {
|
|
968
|
+
this.mainBranch(options);
|
|
969
|
+
return this.http.request("GET", `/email/dispatches/${encodeURIComponent(id)}`, {
|
|
970
|
+
params: { workspaceId: this.workspace(options) }
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
async history(options) {
|
|
974
|
+
this.mainBranch(options);
|
|
975
|
+
return this.http.request("GET", "/email/dispatches", {
|
|
976
|
+
params: { workspaceId: this.workspace(options), recordId: options?.recordId, installationId: options?.installationId }
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
async status(id, options) {
|
|
980
|
+
return this.outcome(id, options);
|
|
981
|
+
}
|
|
982
|
+
workspace(options) {
|
|
983
|
+
const value = options?.workspaceId ?? this.workspaceId;
|
|
984
|
+
if (!value) throw new Error("A workspace is required for transactional email");
|
|
985
|
+
return value;
|
|
986
|
+
}
|
|
987
|
+
mainBranch(options) {
|
|
988
|
+
if (options?.branch !== void 0 && options.branch !== "main")
|
|
989
|
+
throw new Error("Transactional email currently supports only the main business branch");
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
// src/modules/business.ts
|
|
994
|
+
var BusinessModule = class _BusinessModule {
|
|
995
|
+
constructor(http, branch = "main") {
|
|
996
|
+
this.http = http;
|
|
997
|
+
this.branch = branch;
|
|
998
|
+
}
|
|
999
|
+
http;
|
|
1000
|
+
branch;
|
|
1001
|
+
withBranch(branch) {
|
|
1002
|
+
return new _BusinessModule(this.http, branch);
|
|
1003
|
+
}
|
|
1004
|
+
async context() {
|
|
1005
|
+
return this.http.request("GET", "/business/context", { headers: this.headers() });
|
|
1006
|
+
}
|
|
1007
|
+
async command(command) {
|
|
1008
|
+
if (!command.idempotencyKey?.trim() || command.idempotencyKey.length > 200 || /[\u0000-\u001f\u007f-\u009f]/.test(command.idempotencyKey)) {
|
|
1009
|
+
throw new Error("A bounded printable idempotency key is required");
|
|
1010
|
+
}
|
|
1011
|
+
if (command.expectedRevision !== void 0 && !/^"[a-f0-9]{32}"$/.test(command.expectedRevision)) {
|
|
1012
|
+
throw new Error("An exact strong quoted canonical revision is required");
|
|
1013
|
+
}
|
|
1014
|
+
return this.http.request("POST", "/business/commands", {
|
|
1015
|
+
headers: this.headers(),
|
|
1016
|
+
body: JSON.stringify(command)
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
async read(id) {
|
|
1020
|
+
return this.http.request("GET", `/business/records/${encodeURIComponent(id)}`, { headers: this.headers() });
|
|
1021
|
+
}
|
|
1022
|
+
async list(kind) {
|
|
1023
|
+
return this.http.request("GET", "/business/records", {
|
|
1024
|
+
headers: this.headers(),
|
|
1025
|
+
params: kind ? { kind } : {}
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
/** null means unresolved, not success; do not invent a new operation key. */
|
|
1029
|
+
async outcome(request) {
|
|
1030
|
+
return this.http.request("POST", "/business/outcomes", {
|
|
1031
|
+
headers: this.headers(),
|
|
1032
|
+
body: JSON.stringify(request)
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
/** Recover using the exact original command, without reproducing server fingerprint encoding. */
|
|
1036
|
+
async outcomeFor(command) {
|
|
1037
|
+
return this.outcome({ operation: command.operation, idempotencyKey: command.idempotencyKey, command });
|
|
1038
|
+
}
|
|
1039
|
+
async report(report, range = {}) {
|
|
1040
|
+
return this.http.request("GET", `/business/reports/${report}`, {
|
|
1041
|
+
headers: this.headers(),
|
|
1042
|
+
params: range
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
/** Retained historical expense receipt bytes, even after the original native file is removed. */
|
|
1046
|
+
async receipt(nodeId, revision) {
|
|
1047
|
+
const response = await this.http.requestRaw("GET", `/business/receipts/${encodeURIComponent(nodeId)}/content`, {
|
|
1048
|
+
headers: this.headers(),
|
|
1049
|
+
params: { revision }
|
|
1050
|
+
});
|
|
1051
|
+
await this.http.ensureSuccess(response);
|
|
1052
|
+
return response.blob();
|
|
1053
|
+
}
|
|
1054
|
+
headers() {
|
|
1055
|
+
if (!this.branch || this.branch.length > 128 || this.branch !== this.branch.trim().toLowerCase() || /[\u0000-\u001f\u007f-\u009f]/.test(this.branch)) {
|
|
1056
|
+
throw new Error("Business operations require a canonical branch");
|
|
1057
|
+
}
|
|
1058
|
+
return { "X-Branch": this.branch, "Content-Type": "application/json" };
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
|
|
775
1062
|
// src/transport/header-actions.ts
|
|
776
1063
|
function isHeaderAction(value) {
|
|
777
1064
|
if (!value || typeof value !== "object") return false;
|
|
@@ -779,7 +1066,113 @@ function isHeaderAction(value) {
|
|
|
779
1066
|
return typeof a.id === "string" && /^[a-z][a-z0-9.-]{0,63}$/.test(a.id) && typeof a.label === "string" && a.label.trim().length > 0 && a.label.length <= 100 && Array.isArray(a.scopes) && a.scopes.length <= 32 && a.scopes.every((s) => typeof s === "string" && /^[a-zA-Z0-9._:-]{1,128}$/.test(s)) && (a.icon === void 0 || typeof a.icon === "string" && /^lucide:[a-z0-9-]{1,64}$/.test(a.icon)) && (a.variant === void 0 || ["default", "outline", "destructive"].includes(String(a.variant))) && ["enabled", "visible", "busy"].every((key) => a[key] === void 0 || typeof a[key] === "boolean") && (a.disabledReason === void 0 || typeof a.disabledReason === "string" && a.disabledReason.length <= 200) && (a.kind === void 0 || a.kind === "clipboard");
|
|
780
1067
|
}
|
|
781
1068
|
|
|
1069
|
+
// src/collaboration/native.ts
|
|
1070
|
+
var NativeCollaborationError = class extends Error {
|
|
1071
|
+
constructor(message, code) {
|
|
1072
|
+
super(message);
|
|
1073
|
+
this.code = code;
|
|
1074
|
+
this.name = "NativeCollaborationError";
|
|
1075
|
+
}
|
|
1076
|
+
code;
|
|
1077
|
+
};
|
|
1078
|
+
function record(value) {
|
|
1079
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1080
|
+
}
|
|
1081
|
+
function natural(value) {
|
|
1082
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1083
|
+
}
|
|
1084
|
+
function identifier(value) {
|
|
1085
|
+
return typeof value === "string" && value.length > 0 && value.length <= 256;
|
|
1086
|
+
}
|
|
1087
|
+
function pointer(value) {
|
|
1088
|
+
return record(value) && typeof value.x === "number" && Number.isFinite(value.x) && typeof value.y === "number" && Number.isFinite(value.y);
|
|
1089
|
+
}
|
|
1090
|
+
function element(value) {
|
|
1091
|
+
return record(value) && identifier(value.id) && natural(value.version) && (value.versionNonce === void 0 || natural(value.versionNonce)) && (value.isDeleted === void 0 || typeof value.isDeleted === "boolean");
|
|
1092
|
+
}
|
|
1093
|
+
function files(value) {
|
|
1094
|
+
return record(value) && Object.keys(value).length <= 1e4 && Object.entries(value).every(([id, file]) => record(file) && identifier(id) && file.id === id && typeof file.dataURL === "string" && file.dataURL.length <= 10 * 1024 * 1024 && typeof file.mimeType === "string" && file.mimeType.length > 0 && file.mimeType.length <= 128 && natural(file.created) && (file.lastRetrieved === void 0 || natural(file.lastRetrieved)));
|
|
1095
|
+
}
|
|
1096
|
+
function isNativeSnapshot(value) {
|
|
1097
|
+
if (!record(value) || !natural(value.sequence) || !natural(value.savedSequence) || value.savedSequence > value.sequence || typeof value.revision !== "string" || !value.revision || value.revision.length > 1024 || typeof value.readOnly !== "boolean" || !record(value.scene) || !Array.isArray(value.scene.elements) || value.scene.elements.length > 1e4 || !value.scene.elements.every(element) || !files(value.scene.files) || !record(value.scene.appState) || !Array.isArray(value.participants) || value.participants.length > 20)
|
|
1098
|
+
return false;
|
|
1099
|
+
const ids = value.scene.elements.map((item) => item.id);
|
|
1100
|
+
if (new Set(ids).size !== ids.length) return false;
|
|
1101
|
+
const connectionIds = value.participants.map((item) => record(item) ? item.id : void 0);
|
|
1102
|
+
if (new Set(connectionIds).size !== connectionIds.length) return false;
|
|
1103
|
+
return value.participants.every((item) => record(item) && identifier(item.id) && typeof item.name === "string" && item.name.length <= 1024 && typeof item.readOnly === "boolean" && (item.pointer === null || pointer(item.pointer)) && (item.kind === void 0 || item.kind === "user" || item.kind === "agent"));
|
|
1104
|
+
}
|
|
1105
|
+
function isNativeSaveResult(value) {
|
|
1106
|
+
return isNativeSnapshot(value) && record(value) && record(value.receipt) && natural(value.receipt.generation) && identifier(value.receipt.correlation) && typeof value.receipt.revision === "string" && value.receipt.revision.length > 0 && value.receipt.revision.length <= 1024;
|
|
1107
|
+
}
|
|
1108
|
+
function validateNativeCommand(command) {
|
|
1109
|
+
if (!record(command)) throw new Error("Invalid collaboration command.");
|
|
1110
|
+
switch (command.operation) {
|
|
1111
|
+
case "join":
|
|
1112
|
+
case "snapshot":
|
|
1113
|
+
case "close":
|
|
1114
|
+
break;
|
|
1115
|
+
case "presence":
|
|
1116
|
+
if (command.pointer !== null && !pointer(command.pointer))
|
|
1117
|
+
throw new Error("Invalid collaboration pointer.");
|
|
1118
|
+
break;
|
|
1119
|
+
case "save":
|
|
1120
|
+
if (!natural(command.generation) || !identifier(command.correlation))
|
|
1121
|
+
throw new Error("Invalid collaboration save frontier.");
|
|
1122
|
+
break;
|
|
1123
|
+
case "apply": {
|
|
1124
|
+
const changes = command.changes;
|
|
1125
|
+
if (!record(changes) || !identifier(changes.operationId) || !Array.isArray(changes.changes) || changes.changes.length > 1e4 || !changes.changes.every((change) => record(change) && identifier(change.id) && (change.expectedVersion === null || natural(change.expectedVersion)) && (change.element === null || element(change.element) && change.element.id === change.id)) || changes.files !== void 0 && !files(changes.files) || (changes.appState !== void 0 ? !record(changes.appState) || !natural(changes.appStateExpectedSequence) : changes.appStateExpectedSequence !== void 0))
|
|
1126
|
+
throw new Error("Invalid collaboration changes.");
|
|
1127
|
+
const ids = changes.changes.map((change) => change.id);
|
|
1128
|
+
if (new Set(ids).size !== ids.length) throw new Error("Duplicate collaboration element changes.");
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
default:
|
|
1132
|
+
throw new Error("Unsupported collaboration command.");
|
|
1133
|
+
}
|
|
1134
|
+
if (new TextEncoder().encode(JSON.stringify(command)).byteLength > 10 * 1024 * 1024)
|
|
1135
|
+
throw new Error("Collaboration request exceeds the document limit.");
|
|
1136
|
+
}
|
|
1137
|
+
var NativeCollaborationClient = class {
|
|
1138
|
+
constructor(bridge) {
|
|
1139
|
+
this.bridge = bridge;
|
|
1140
|
+
}
|
|
1141
|
+
bridge;
|
|
1142
|
+
async snapshotRequest(command) {
|
|
1143
|
+
if (!this.bridge) throw new Error("Native collaboration requires an installed workspace host.");
|
|
1144
|
+
const result = await this.bridge.requestNativeCollaboration(command);
|
|
1145
|
+
if (!isNativeSnapshot(result)) throw new Error("Invalid native collaboration snapshot.");
|
|
1146
|
+
return result;
|
|
1147
|
+
}
|
|
1148
|
+
join() {
|
|
1149
|
+
return this.snapshotRequest({ operation: "join" });
|
|
1150
|
+
}
|
|
1151
|
+
snapshot() {
|
|
1152
|
+
return this.snapshotRequest({ operation: "snapshot" });
|
|
1153
|
+
}
|
|
1154
|
+
apply(changes) {
|
|
1155
|
+
return this.snapshotRequest({ operation: "apply", changes });
|
|
1156
|
+
}
|
|
1157
|
+
presence(value) {
|
|
1158
|
+
return this.snapshotRequest({ operation: "presence", pointer: value });
|
|
1159
|
+
}
|
|
1160
|
+
async save(generation, correlation) {
|
|
1161
|
+
if (!this.bridge) throw new Error("Native collaboration requires an installed workspace host.");
|
|
1162
|
+
const result = await this.bridge.requestNativeCollaboration({ operation: "save", generation, correlation });
|
|
1163
|
+
if (!isNativeSaveResult(result) || result.receipt.generation !== generation || result.receipt.correlation !== correlation)
|
|
1164
|
+
throw new Error("Invalid native collaboration save receipt.");
|
|
1165
|
+
return result;
|
|
1166
|
+
}
|
|
1167
|
+
async close() {
|
|
1168
|
+
if (!this.bridge) throw new Error("Native collaboration requires an installed workspace host.");
|
|
1169
|
+
const result = await this.bridge.requestNativeCollaboration({ operation: "close" });
|
|
1170
|
+
if (result !== null) throw new Error("Invalid native collaboration close response.");
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
|
|
782
1174
|
// src/transport/bridge.transport.ts
|
|
1175
|
+
var HOST_MESSAGE_CHANNEL_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
783
1176
|
var Bridge = class {
|
|
784
1177
|
constructor(expectedHostOrigin) {
|
|
785
1178
|
this.expectedHostOrigin = expectedHostOrigin;
|
|
@@ -806,7 +1199,10 @@ var Bridge = class {
|
|
|
806
1199
|
sessionId = null;
|
|
807
1200
|
actions = /* @__PURE__ */ new Map();
|
|
808
1201
|
requests = /* @__PURE__ */ new Set();
|
|
809
|
-
|
|
1202
|
+
hostMessageHandlers = /* @__PURE__ */ new Map();
|
|
1203
|
+
hostMessageRequests = /* @__PURE__ */ new Set();
|
|
1204
|
+
wopiRequests = /* @__PURE__ */ new Map();
|
|
1205
|
+
collaborationRequests = /* @__PURE__ */ new Map();
|
|
810
1206
|
/** Post `verentis:ready` to the host to start the handshake */
|
|
811
1207
|
sendReady(capabilities) {
|
|
812
1208
|
console.info("[Verentis SDK] Sending verentis:ready to host");
|
|
@@ -815,7 +1211,7 @@ var Bridge = class {
|
|
|
815
1211
|
version: 1,
|
|
816
1212
|
capabilities
|
|
817
1213
|
};
|
|
818
|
-
window.parent.postMessage(message, "*");
|
|
1214
|
+
window.parent.postMessage(message, this.expectedHostOrigin ?? "*");
|
|
819
1215
|
}
|
|
820
1216
|
/** Wait for the host to send `verentis:init` with context and token */
|
|
821
1217
|
waitForInit() {
|
|
@@ -868,28 +1264,85 @@ var Bridge = class {
|
|
|
868
1264
|
setDirty(isDirty) {
|
|
869
1265
|
this.postToHost({ type: "verentis:dirty", version: 1, isDirty });
|
|
870
1266
|
}
|
|
871
|
-
/** The
|
|
872
|
-
|
|
1267
|
+
/** The trusted parent selects the approved installation, document and action. */
|
|
1268
|
+
requestWopiLaunch() {
|
|
1269
|
+
return this.requestWopi("launch");
|
|
1270
|
+
}
|
|
1271
|
+
/** Recover only the target selected by a completed server-owned derived operation. */
|
|
1272
|
+
requestWopiContinuation() {
|
|
1273
|
+
return this.requestWopi("continue");
|
|
1274
|
+
}
|
|
1275
|
+
getWopiStatus() {
|
|
1276
|
+
return this.requestWopi("status");
|
|
1277
|
+
}
|
|
1278
|
+
requestWopiSave(generation) {
|
|
1279
|
+
if (!Number.isSafeInteger(generation) || generation < 0)
|
|
1280
|
+
return Promise.reject(new Error("Invalid edit generation."));
|
|
1281
|
+
return this.requestWopi("save", generation);
|
|
1282
|
+
}
|
|
1283
|
+
closeWopiSession() {
|
|
1284
|
+
return this.requestWopi("close");
|
|
1285
|
+
}
|
|
1286
|
+
requestWopi(operation, generation) {
|
|
873
1287
|
if (this._destroyed || !this.sessionId || !this._hostOrigin)
|
|
874
1288
|
return Promise.reject(new Error("An initialized workspace host is required."));
|
|
875
|
-
if (this.
|
|
876
|
-
return Promise.reject(new Error("
|
|
1289
|
+
if (this.wopiRequests.size >= 4)
|
|
1290
|
+
return Promise.reject(new Error("Too many collaboration requests."));
|
|
877
1291
|
const requestId = crypto.randomUUID();
|
|
878
1292
|
return new Promise((resolve, reject) => {
|
|
879
1293
|
const timer = setTimeout(() => {
|
|
880
|
-
this.
|
|
881
|
-
reject(new Error("
|
|
882
|
-
},
|
|
883
|
-
this.
|
|
1294
|
+
this.wopiRequests.delete(requestId);
|
|
1295
|
+
reject(new Error("Collaboration authorization timed out."));
|
|
1296
|
+
}, 3e4);
|
|
1297
|
+
this.wopiRequests.set(requestId, { resolve, reject, timer });
|
|
884
1298
|
try {
|
|
885
|
-
this.postToHost({
|
|
1299
|
+
this.postToHost({
|
|
1300
|
+
type: "verentis:wopi:request",
|
|
1301
|
+
version: 1,
|
|
1302
|
+
requestId,
|
|
1303
|
+
sessionId: this.sessionId,
|
|
1304
|
+
operation,
|
|
1305
|
+
...generation === void 0 ? {} : { generation }
|
|
1306
|
+
});
|
|
886
1307
|
} catch {
|
|
887
1308
|
clearTimeout(timer);
|
|
888
|
-
this.
|
|
1309
|
+
this.wopiRequests.delete(requestId);
|
|
889
1310
|
reject(new Error("The workspace host is unavailable."));
|
|
890
1311
|
}
|
|
891
1312
|
});
|
|
892
1313
|
}
|
|
1314
|
+
requestNativeCollaboration(command) {
|
|
1315
|
+
if (this._destroyed || !this.sessionId || !this._hostOrigin)
|
|
1316
|
+
return Promise.reject(new Error("An initialized workspace host is required."));
|
|
1317
|
+
if (this.collaborationRequests.size >= 4)
|
|
1318
|
+
return Promise.reject(new Error("Too many native collaboration requests."));
|
|
1319
|
+
try {
|
|
1320
|
+
validateNativeCommand(command);
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
return Promise.reject(error);
|
|
1323
|
+
}
|
|
1324
|
+
const requestId = crypto.randomUUID();
|
|
1325
|
+
return new Promise((resolve, reject) => {
|
|
1326
|
+
const timer = setTimeout(() => {
|
|
1327
|
+
this.collaborationRequests.delete(requestId);
|
|
1328
|
+
reject(new NativeCollaborationError("Native collaboration timed out.", "unavailable"));
|
|
1329
|
+
}, 3e4);
|
|
1330
|
+
this.collaborationRequests.set(requestId, { resolve, reject, timer });
|
|
1331
|
+
try {
|
|
1332
|
+
this.postToHost({
|
|
1333
|
+
type: "verentis:collaboration:request",
|
|
1334
|
+
version: 1,
|
|
1335
|
+
requestId,
|
|
1336
|
+
sessionId: this.sessionId,
|
|
1337
|
+
command
|
|
1338
|
+
});
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
clearTimeout(timer);
|
|
1341
|
+
this.collaborationRequests.delete(requestId);
|
|
1342
|
+
reject(error);
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
893
1346
|
registerAction(descriptor, handler) {
|
|
894
1347
|
if (!this.sessionId || this._destroyed) throw new Error("Header actions require an initialized workspace host.");
|
|
895
1348
|
if (!isHeaderAction(descriptor)) throw new Error("Invalid header action.");
|
|
@@ -953,6 +1406,49 @@ var Bridge = class {
|
|
|
953
1406
|
this.publishActions();
|
|
954
1407
|
}
|
|
955
1408
|
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Handle messages the workspace sends on a channel declared in the manifest `spec.messages` — for
|
|
1411
|
+
* example an agent asking the open app to select a record. The handler's return value is the reply;
|
|
1412
|
+
* a thrown error is reported back as the failure. May be called before the init handshake.
|
|
1413
|
+
*/
|
|
1414
|
+
onHostMessage(channel, handler) {
|
|
1415
|
+
if (this._destroyed) throw new Error("Bridge has been destroyed.");
|
|
1416
|
+
if (!HOST_MESSAGE_CHANNEL_PATTERN.test(channel)) throw new Error(`Invalid host message channel '${channel}'.`);
|
|
1417
|
+
if (this.hostMessageHandlers.has(channel)) throw new Error(`Channel '${channel}' already has a handler.`);
|
|
1418
|
+
this.hostMessageHandlers.set(channel, handler);
|
|
1419
|
+
return () => {
|
|
1420
|
+
if (this.hostMessageHandlers.get(channel) === handler) this.hostMessageHandlers.delete(channel);
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
async receiveHostMessage(message) {
|
|
1424
|
+
if (!this._hostOrigin || message.sessionId !== this.sessionId || typeof message.channel !== "string" || typeof message.requestId !== "string" || !message.requestId || message.requestId.length > 128 || this.hostMessageRequests.has(message.requestId)) return;
|
|
1425
|
+
this.hostMessageRequests.add(message.requestId);
|
|
1426
|
+
if (this.hostMessageRequests.size > 1e3) this.hostMessageRequests.delete(this.hostMessageRequests.values().next().value);
|
|
1427
|
+
const reply = {
|
|
1428
|
+
type: "verentis:host:reply",
|
|
1429
|
+
version: 1,
|
|
1430
|
+
sessionId: message.sessionId,
|
|
1431
|
+
requestId: message.requestId,
|
|
1432
|
+
channel: message.channel
|
|
1433
|
+
};
|
|
1434
|
+
const handler = this.hostMessageHandlers.get(message.channel);
|
|
1435
|
+
if (!handler) {
|
|
1436
|
+
this.postToHost({ ...reply, error: `The application does not handle '${message.channel.slice(0, 64)}' messages.` });
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
let response;
|
|
1440
|
+
try {
|
|
1441
|
+
response = { result: await handler(message.payload) };
|
|
1442
|
+
} catch (error) {
|
|
1443
|
+
response = { error: (error instanceof Error ? error.message : "Message failed.").slice(0, 500) || "Message failed." };
|
|
1444
|
+
}
|
|
1445
|
+
if (this._destroyed || this.sessionId !== message.sessionId) return;
|
|
1446
|
+
try {
|
|
1447
|
+
this.postToHost({ ...reply, ...response });
|
|
1448
|
+
} catch {
|
|
1449
|
+
this.postToHost({ ...reply, error: "The reply could not be sent to the workspace." });
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
956
1452
|
/** Subscribe to a specific message type from the host */
|
|
957
1453
|
on(type, listener) {
|
|
958
1454
|
if (!this.listeners.has(type)) {
|
|
@@ -971,12 +1467,19 @@ var Bridge = class {
|
|
|
971
1467
|
if (!this._hostOrigin) this._initReject(new Error("Bridge has been destroyed."));
|
|
972
1468
|
this.actions.clear();
|
|
973
1469
|
this.requests.clear();
|
|
1470
|
+
this.hostMessageHandlers.clear();
|
|
1471
|
+
this.hostMessageRequests.clear();
|
|
974
1472
|
this.listeners.clear();
|
|
975
|
-
for (const pending of this.
|
|
1473
|
+
for (const pending of this.wopiRequests.values()) {
|
|
976
1474
|
clearTimeout(pending.timer);
|
|
977
1475
|
pending.reject(new Error("Bridge has been destroyed."));
|
|
978
1476
|
}
|
|
979
|
-
this.
|
|
1477
|
+
this.wopiRequests.clear();
|
|
1478
|
+
for (const pending of this.collaborationRequests.values()) {
|
|
1479
|
+
clearTimeout(pending.timer);
|
|
1480
|
+
pending.reject(new Error("Bridge has been destroyed."));
|
|
1481
|
+
}
|
|
1482
|
+
this.collaborationRequests.clear();
|
|
980
1483
|
if (typeof window !== "undefined") {
|
|
981
1484
|
window.removeEventListener("message", this._messageHandler);
|
|
982
1485
|
}
|
|
@@ -1002,22 +1505,39 @@ var Bridge = class {
|
|
|
1002
1505
|
const initData = data;
|
|
1003
1506
|
this._initResolve({ context: initData.context, token: initData.token });
|
|
1004
1507
|
}
|
|
1005
|
-
if (data.type === "verentis:
|
|
1508
|
+
if (data.type === "verentis:wopi:response") {
|
|
1006
1509
|
if (!this._hostOrigin || data.sessionId !== this.sessionId) return;
|
|
1007
|
-
const pending = this.
|
|
1510
|
+
const pending = this.wopiRequests.get(data.requestId);
|
|
1008
1511
|
if (!pending) return;
|
|
1009
1512
|
clearTimeout(pending.timer);
|
|
1010
|
-
this.
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1513
|
+
this.wopiRequests.delete(data.requestId);
|
|
1514
|
+
if (data.error || !Object.hasOwn(data, "result"))
|
|
1515
|
+
pending.reject(new Error("Collaboration authorization is unavailable."));
|
|
1516
|
+
else pending.resolve(data.result);
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
if (data.type === "verentis:collaboration:response") {
|
|
1520
|
+
if (!this._hostOrigin || data.sessionId !== this.sessionId) return;
|
|
1521
|
+
const pending = this.collaborationRequests.get(data.requestId);
|
|
1522
|
+
if (!pending) return;
|
|
1523
|
+
clearTimeout(pending.timer);
|
|
1524
|
+
this.collaborationRequests.delete(data.requestId);
|
|
1525
|
+
if (data.error || !Object.hasOwn(data, "result"))
|
|
1526
|
+
pending.reject(new NativeCollaborationError(
|
|
1527
|
+
typeof data.error === "string" ? data.error.slice(0, 500) : "Native collaboration is unavailable.",
|
|
1528
|
+
typeof data.code === "string" ? data.code.slice(0, 64) : "unavailable"
|
|
1529
|
+
));
|
|
1530
|
+
else pending.resolve(data.result);
|
|
1015
1531
|
return;
|
|
1016
1532
|
}
|
|
1017
1533
|
if (data.type === "verentis:action:invoke") {
|
|
1018
1534
|
void this.invokeAction(data);
|
|
1019
1535
|
return;
|
|
1020
1536
|
}
|
|
1537
|
+
if (data.type === "verentis:host:message") {
|
|
1538
|
+
void this.receiveHostMessage(data);
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1021
1541
|
const typeListeners = this.listeners.get(data.type);
|
|
1022
1542
|
if (typeListeners) {
|
|
1023
1543
|
for (const listener of typeListeners) {
|
|
@@ -1029,16 +1549,18 @@ var Bridge = class {
|
|
|
1029
1549
|
|
|
1030
1550
|
// src/types.ts
|
|
1031
1551
|
var ApiError = class extends Error {
|
|
1032
|
-
constructor(status, detail, title) {
|
|
1552
|
+
constructor(status, detail, title, code) {
|
|
1033
1553
|
super(title ?? `API error ${status}: ${detail}`);
|
|
1034
1554
|
this.status = status;
|
|
1035
1555
|
this.detail = detail;
|
|
1036
1556
|
this.title = title;
|
|
1557
|
+
this.code = code;
|
|
1037
1558
|
this.name = "ApiError";
|
|
1038
1559
|
}
|
|
1039
1560
|
status;
|
|
1040
1561
|
detail;
|
|
1041
1562
|
title;
|
|
1563
|
+
code;
|
|
1042
1564
|
};
|
|
1043
1565
|
|
|
1044
1566
|
// src/transport/http.transport.ts
|
|
@@ -1067,24 +1589,7 @@ var HttpTransport = class {
|
|
|
1067
1589
|
headers,
|
|
1068
1590
|
body: options?.body
|
|
1069
1591
|
});
|
|
1070
|
-
|
|
1071
|
-
let detail = "";
|
|
1072
|
-
let title;
|
|
1073
|
-
try {
|
|
1074
|
-
const body = await response.json();
|
|
1075
|
-
detail = body.detail ?? body.message ?? JSON.stringify(body);
|
|
1076
|
-
title = body.title;
|
|
1077
|
-
} catch {
|
|
1078
|
-
detail = await response.text().catch(() => response.statusText);
|
|
1079
|
-
}
|
|
1080
|
-
console.error("[Verentis SDK] Request failed", {
|
|
1081
|
-
method,
|
|
1082
|
-
path,
|
|
1083
|
-
status: response.status,
|
|
1084
|
-
detail
|
|
1085
|
-
});
|
|
1086
|
-
throw new ApiError(response.status, detail, title);
|
|
1087
|
-
}
|
|
1592
|
+
await this.ensureSuccess(response);
|
|
1088
1593
|
console.info("[Verentis SDK] Request succeeded", {
|
|
1089
1594
|
method,
|
|
1090
1595
|
path,
|
|
@@ -1118,6 +1623,26 @@ var HttpTransport = class {
|
|
|
1118
1623
|
});
|
|
1119
1624
|
return response;
|
|
1120
1625
|
}
|
|
1626
|
+
async ensureSuccess(response) {
|
|
1627
|
+
if (response.ok) return;
|
|
1628
|
+
const raw = await response.text();
|
|
1629
|
+
let detail = raw || response.statusText;
|
|
1630
|
+
let title;
|
|
1631
|
+
let code;
|
|
1632
|
+
try {
|
|
1633
|
+
const body = JSON.parse(raw);
|
|
1634
|
+
if (body !== null && typeof body === "object") {
|
|
1635
|
+
if ("detail" in body && typeof body.detail === "string") detail = body.detail;
|
|
1636
|
+
else if ("message" in body && typeof body.message === "string") detail = body.message;
|
|
1637
|
+
if ("title" in body && typeof body.title === "string") title = body.title;
|
|
1638
|
+
if ("code" in body && typeof body.code === "string") code = body.code;
|
|
1639
|
+
}
|
|
1640
|
+
} catch (error) {
|
|
1641
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
1642
|
+
}
|
|
1643
|
+
console.error("[Verentis SDK] Request failed", { status: response.status, code });
|
|
1644
|
+
throw new ApiError(response.status, detail, title, code);
|
|
1645
|
+
}
|
|
1121
1646
|
buildUrl(path, params) {
|
|
1122
1647
|
const base = `${this.baseUrl}/${this.apiVersion}${path}`;
|
|
1123
1648
|
if (!params) return base;
|
|
@@ -1147,8 +1672,11 @@ var VerentisClient = class {
|
|
|
1147
1672
|
installations;
|
|
1148
1673
|
resources;
|
|
1149
1674
|
settings;
|
|
1675
|
+
email;
|
|
1676
|
+
business;
|
|
1150
1677
|
context;
|
|
1151
1678
|
bridge;
|
|
1679
|
+
collaboration;
|
|
1152
1680
|
_auth;
|
|
1153
1681
|
_http;
|
|
1154
1682
|
_ready;
|
|
@@ -1157,11 +1685,13 @@ var VerentisClient = class {
|
|
|
1157
1685
|
const environment = detectEnvironment();
|
|
1158
1686
|
if (environment === "iframe" && !options?.auth) {
|
|
1159
1687
|
this.bridge = new Bridge();
|
|
1688
|
+
this.collaboration = new NativeCollaborationClient(this.bridge);
|
|
1160
1689
|
const bridgeAuth = new BridgeAuth("*");
|
|
1161
1690
|
this._auth = bridgeAuth;
|
|
1162
1691
|
this._ready = this.initFromBridge(bridgeAuth);
|
|
1163
1692
|
} else {
|
|
1164
1693
|
this.bridge = null;
|
|
1694
|
+
this.collaboration = new NativeCollaborationClient(null);
|
|
1165
1695
|
this._auth = createAuthStrategy(options);
|
|
1166
1696
|
const apiUrl = options?.apiUrl;
|
|
1167
1697
|
if (!apiUrl) {
|
|
@@ -1178,6 +1708,8 @@ var VerentisClient = class {
|
|
|
1178
1708
|
this.installations = new InstallationsModule(this._http, options?.workspaceId);
|
|
1179
1709
|
this.resources = new ResourcesModule(this._http, options?.workspaceId);
|
|
1180
1710
|
this.settings = new SettingsModule(this._http, options?.workspaceId);
|
|
1711
|
+
this.email = new EmailModule(this._http, options?.workspaceId);
|
|
1712
|
+
this.business = new BusinessModule(this._http);
|
|
1181
1713
|
this._ready = Promise.resolve();
|
|
1182
1714
|
return;
|
|
1183
1715
|
}
|
|
@@ -1190,6 +1722,14 @@ var VerentisClient = class {
|
|
|
1190
1722
|
whenReady() {
|
|
1191
1723
|
return this._ready;
|
|
1192
1724
|
}
|
|
1725
|
+
/**
|
|
1726
|
+
* The current `Authorization` header value, refreshed when near expiry.
|
|
1727
|
+
* Forward it to the app's own backend, which exchanges it at Security's
|
|
1728
|
+
* `/connect/token` (RFC 8693) as the installation's confidential client.
|
|
1729
|
+
*/
|
|
1730
|
+
getAuthorizationHeader() {
|
|
1731
|
+
return this._auth.getAuthHeader();
|
|
1732
|
+
}
|
|
1193
1733
|
/** Clean up resources (timers, event listeners) */
|
|
1194
1734
|
destroy() {
|
|
1195
1735
|
this._auth.destroy?.();
|
|
@@ -1214,6 +1754,8 @@ var VerentisClient = class {
|
|
|
1214
1754
|
this.installations = new InstallationsModule(http, context.workspace.id);
|
|
1215
1755
|
this.resources = new ResourcesModule(http, context.workspace.id);
|
|
1216
1756
|
this.settings = new SettingsModule(http, context.workspace.id);
|
|
1757
|
+
this.email = new EmailModule(http, context.workspace.id);
|
|
1758
|
+
this.business = new BusinessModule(http);
|
|
1217
1759
|
}
|
|
1218
1760
|
};
|
|
1219
1761
|
function detectEnvironment() {
|
|
@@ -1266,11 +1808,49 @@ function stripParams(mime) {
|
|
|
1266
1808
|
return idx === -1 ? mime.trim() : mime.slice(0, idx).trim();
|
|
1267
1809
|
}
|
|
1268
1810
|
|
|
1811
|
+
// src/collaboration/lifecycle.ts
|
|
1812
|
+
function isRecord(value) {
|
|
1813
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1814
|
+
}
|
|
1815
|
+
function exactHttpsOrigin(value) {
|
|
1816
|
+
if (typeof value !== "string" || value.length > 256)
|
|
1817
|
+
throw new Error("Invalid framing origin.");
|
|
1818
|
+
const origin = new URL(value);
|
|
1819
|
+
if (origin.protocol !== "https:" || origin.origin !== value || origin.href !== `${value}/` || !/^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/.test(origin.hostname))
|
|
1820
|
+
throw new Error("Invalid framing origin.");
|
|
1821
|
+
return value;
|
|
1822
|
+
}
|
|
1823
|
+
function isTrustedMessage(event, source, origin) {
|
|
1824
|
+
return Boolean(source && origin && event.source === source && event.origin === origin);
|
|
1825
|
+
}
|
|
1826
|
+
function validateCollaborationDocument(value, context, now = Date.now()) {
|
|
1827
|
+
const branch = context.file?.branch;
|
|
1828
|
+
const node = context.file?.nodeId;
|
|
1829
|
+
const workspace = context.workspace?.id;
|
|
1830
|
+
if (!branch || !node || !workspace || !isRecord(value) || value.nodeId !== node || value.workspaceId !== workspace || value.branch !== branch.trim().toLowerCase() || typeof value.fileId !== "string" || !/^[a-f0-9]{32}$/.test(value.fileId) || typeof value.sessionId !== "string" || !/^(?:[a-f0-9]{32}|[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12})$/.test(value.sessionId) || typeof value.accessTokenTtl !== "number" || !Number.isSafeInteger(value.accessTokenTtl) || value.accessTokenTtl <= now || typeof value.name !== "string" || !value.name || value.name.length > 1024 || typeof value.readOnly !== "boolean")
|
|
1831
|
+
throw new Error("Invalid live launch.");
|
|
1832
|
+
return {
|
|
1833
|
+
sessionId: value.sessionId,
|
|
1834
|
+
workspaceId: value.workspaceId,
|
|
1835
|
+
branch: value.branch,
|
|
1836
|
+
nodeId: value.nodeId,
|
|
1837
|
+
fileId: value.fileId,
|
|
1838
|
+
name: value.name,
|
|
1839
|
+
readOnly: value.readOnly,
|
|
1840
|
+
accessTokenTtl: value.accessTokenTtl
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
function isDurableSaveConfirmed(result, request, currentGeneration, editorModified) {
|
|
1844
|
+
if (!isRecord(request) || !isRecord(result) || !isRecord(result.receipt))
|
|
1845
|
+
return false;
|
|
1846
|
+
return editorModified === false && result.state === "ready" && result.continuationAvailable !== true && Number.isSafeInteger(currentGeneration) && currentGeneration >= 0 && currentGeneration === request.generation && typeof request.correlation === "string" && request.correlation.length > 0 && result.receipt.generation === request.generation && result.receipt.correlation === request.correlation && typeof result.receipt.revision === "string" && result.receipt.revision.length > 0;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1269
1849
|
// src/index.ts
|
|
1270
1850
|
function createVerentisClient(options) {
|
|
1271
1851
|
return new VerentisClient(options);
|
|
1272
1852
|
}
|
|
1273
1853
|
|
|
1274
|
-
export { ApiKeyAuth, Bridge, BridgeAuth, ContextModule, EnginesModule, ExecutionsModule, FilesModule, HttpTransport, InstallationsModule, ResourcesModule, SettingsModule, TokenAuth, VerentisClient, createVerentisClient, findBestMatch, isHeaderAction, isTerminalStatus, matchMimeType };
|
|
1854
|
+
export { ApiError, ApiKeyAuth, Bridge, BridgeAuth, BusinessModule, ContextModule, EmailModule, EnginesModule, ExecutionsModule, FilesModule, HOST_MESSAGE_CHANNEL_PATTERN, HttpTransport, InstallationsModule, NativeCollaborationClient, NativeCollaborationError, ResourcesModule, SettingsModule, TokenAuth, VerentisClient, createVerentisClient, exactHttpsOrigin, findBestMatch, isDurableSaveConfirmed, isHeaderAction, isNativeSaveResult, isNativeSnapshot, isTerminalStatus, isTrustedMessage, matchMimeType, validateCollaborationDocument, validateNativeCommand };
|
|
1275
1855
|
//# sourceMappingURL=index.js.map
|
|
1276
1856
|
//# sourceMappingURL=index.js.map
|