@verentis/sdk 0.2.4 → 0.2.6
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 +138 -11
- package/dist/index.cjs +614 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +642 -88
- package/dist/index.d.ts +642 -88
- package/dist/index.js +602 -66
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -167,6 +167,9 @@ var ContextModule = class {
|
|
|
167
167
|
_file = null;
|
|
168
168
|
_entryPoint = null;
|
|
169
169
|
_apiUrl = null;
|
|
170
|
+
_application = null;
|
|
171
|
+
_installation = null;
|
|
172
|
+
_grants = null;
|
|
170
173
|
/** The current workspace */
|
|
171
174
|
get workspace() {
|
|
172
175
|
if (!this._workspace) {
|
|
@@ -207,13 +210,39 @@ var ContextModule = class {
|
|
|
207
210
|
get hasEntryPoint() {
|
|
208
211
|
return this._entryPoint !== null;
|
|
209
212
|
}
|
|
213
|
+
get application() {
|
|
214
|
+
return this._application ? { ...this._application } : null;
|
|
215
|
+
}
|
|
216
|
+
get installation() {
|
|
217
|
+
return this._installation ? { ...this._installation } : null;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* The boundaries of a brokered token (canvas documents), as the host sent them with the token; `null` when the
|
|
221
|
+
* token is not boundary-confined by a broker (or in standalone mode).
|
|
222
|
+
*/
|
|
223
|
+
get grants() {
|
|
224
|
+
return this._grants ? this._grants.map((grant) => ({ ...grant })) : null;
|
|
225
|
+
}
|
|
226
|
+
/** Update the token's grants from the host's init or token refresh message */
|
|
227
|
+
setGrants(grants) {
|
|
228
|
+
if (!Array.isArray(grants)) {
|
|
229
|
+
this._grants = null;
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
this._grants = grants.filter((grant) => typeof grant === "object" && grant !== null && typeof grant.type === "string" && typeof grant.identifier === "string" && typeof grant.path === "string" && grant.path.startsWith("/") && (grant.mode === "read" || grant.mode === "write") && typeof grant.source === "string").map((grant) => ({ type: grant.type, identifier: grant.identifier, path: grant.path, mode: grant.mode, source: grant.source }));
|
|
233
|
+
}
|
|
210
234
|
/** Update context from the host's init message */
|
|
211
235
|
setFromAppContext(ctx) {
|
|
236
|
+
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))) {
|
|
237
|
+
throw new Error("Invalid application launch metadata");
|
|
238
|
+
}
|
|
212
239
|
this._workspace = ctx.workspace;
|
|
213
240
|
this._surface = ctx.surface ?? "file";
|
|
214
241
|
this._file = ctx.file ?? null;
|
|
215
242
|
this._entryPoint = ctx.entryPoint ?? null;
|
|
216
243
|
this._apiUrl = ctx.apiUrl;
|
|
244
|
+
this._application = ctx.application ? { ...ctx.application } : null;
|
|
245
|
+
this._installation = ctx.installation ? { ...ctx.installation } : null;
|
|
217
246
|
}
|
|
218
247
|
/** Set workspace info directly (standalone mode) */
|
|
219
248
|
setWorkspace(workspace) {
|
|
@@ -441,6 +470,49 @@ var FilesModule = class {
|
|
|
441
470
|
headers: this.workspaceHeaders()
|
|
442
471
|
});
|
|
443
472
|
}
|
|
473
|
+
/** Read metadata by stable node identity on an explicit branch. */
|
|
474
|
+
async getNode(nodeId, options) {
|
|
475
|
+
requireNonBlank(nodeId, "Node ID");
|
|
476
|
+
requireNonBlank(options.branch, "Branch");
|
|
477
|
+
return this.http.request("GET", `/nodes/${encodeURIComponent(nodeId)}/file`, {
|
|
478
|
+
headers: { ...this.workspaceHeaders(), "X-Branch": options.branch }
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
/** Read bytes and their matching content revision in the same response. */
|
|
482
|
+
async readNodeContent(nodeId, options) {
|
|
483
|
+
requireNonBlank(nodeId, "Node ID");
|
|
484
|
+
requireNonBlank(options.branch, "Branch");
|
|
485
|
+
const response = await this.http.requestRaw("GET", `/nodes/${encodeURIComponent(nodeId)}/content`, {
|
|
486
|
+
headers: { ...this.workspaceHeaders(), "X-Branch": options.branch, Accept: "*/*" }
|
|
487
|
+
});
|
|
488
|
+
await this.http.ensureSuccess(response);
|
|
489
|
+
const revision = response.headers.get("ETag");
|
|
490
|
+
if (!revision || !/^"[a-fA-F0-9]{32}"$/.test(revision)) {
|
|
491
|
+
throw new Error("Node-content response did not include a valid strong revision ETag.");
|
|
492
|
+
}
|
|
493
|
+
return { content: await response.blob(), revision };
|
|
494
|
+
}
|
|
495
|
+
/** Replace existing node bytes conditionally; never creates or changes path/MIME. */
|
|
496
|
+
async writeNodeContent(nodeId, options) {
|
|
497
|
+
requireNonBlank(nodeId, "Node ID");
|
|
498
|
+
requireNonBlank(options.branch, "Branch");
|
|
499
|
+
requireNonBlank(options.idempotencyKey, "Idempotency key");
|
|
500
|
+
const revision = /^[a-fA-F0-9]{32}$/.test(options.revision) ? `"${options.revision}"` : options.revision;
|
|
501
|
+
if (!/^"[a-fA-F0-9]{32}"$/.test(revision)) {
|
|
502
|
+
throw new Error("A single strong node-content revision is required.");
|
|
503
|
+
}
|
|
504
|
+
const content = typeof options.content === "string" ? new Blob([options.content]) : options.content instanceof ArrayBuffer ? new Blob([options.content]) : options.content;
|
|
505
|
+
return this.http.request("PUT", `/nodes/${encodeURIComponent(nodeId)}/content`, {
|
|
506
|
+
body: content,
|
|
507
|
+
headers: {
|
|
508
|
+
...this.workspaceHeaders(),
|
|
509
|
+
"X-Branch": options.branch,
|
|
510
|
+
"If-Match": revision,
|
|
511
|
+
"Idempotency-Key": options.idempotencyKey,
|
|
512
|
+
"Content-Type": "application/octet-stream"
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
444
516
|
/**
|
|
445
517
|
* Read the raw content of a file as a Blob.
|
|
446
518
|
*/
|
|
@@ -532,11 +604,18 @@ var FilesModule = class {
|
|
|
532
604
|
if (options.contentType) metadata.contentType = options.contentType;
|
|
533
605
|
if (options.branch) metadata.branch = options.branch;
|
|
534
606
|
formData.append("metadata", JSON.stringify(metadata));
|
|
535
|
-
await this.http.request("POST", `/files/${normalizedPath}`, {
|
|
607
|
+
await this.http.request("POST", `/files/${normalizedPath.split("/").map(encodeURIComponent).join("/")}`, {
|
|
536
608
|
body: formData,
|
|
537
|
-
headers:
|
|
609
|
+
headers: {
|
|
610
|
+
...this.workspaceHeaders(),
|
|
611
|
+
...options.createOnly ? { "If-None-Match": "*" } : {}
|
|
612
|
+
}
|
|
538
613
|
});
|
|
539
614
|
}
|
|
615
|
+
/** Create without a read-before-write race or overwrite of an existing live file. */
|
|
616
|
+
async create(path, options) {
|
|
617
|
+
await this.write(path, { ...options, createOnly: true });
|
|
618
|
+
}
|
|
540
619
|
/**
|
|
541
620
|
* Move or rename a file.
|
|
542
621
|
*/
|
|
@@ -557,14 +636,19 @@ var FilesModule = class {
|
|
|
557
636
|
* Delete a file.
|
|
558
637
|
*/
|
|
559
638
|
async remove(options) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
639
|
+
const path = normalizePath(options.path);
|
|
640
|
+
requireNonBlank(path, "File path");
|
|
641
|
+
const branch = options.branch ?? "main";
|
|
642
|
+
requireNonBlank(branch, "Branch");
|
|
643
|
+
if (options.headStamp !== void 0) requireNonBlank(options.headStamp, "Path head stamp");
|
|
644
|
+
if (options.idempotencyKey !== void 0) requireNonBlank(options.idempotencyKey, "Idempotency key");
|
|
645
|
+
await this.http.request("DELETE", `/files/${path.split("/").map(encodeURIComponent).join("/")}`, {
|
|
646
|
+
params: { branch },
|
|
565
647
|
headers: {
|
|
566
648
|
...this.workspaceHeaders(),
|
|
567
|
-
"
|
|
649
|
+
...options.headStamp === void 0 ? {} : { "If-Match": options.headStamp },
|
|
650
|
+
...options.idempotencyKey === void 0 ? {} : { "Idempotency-Key": options.idempotencyKey },
|
|
651
|
+
...options.mode === void 0 ? {} : { "X-Delete-Mode": options.mode === "deep" ? "Deep" : "Shallow" }
|
|
568
652
|
}
|
|
569
653
|
});
|
|
570
654
|
}
|
|
@@ -576,6 +660,9 @@ var FilesModule = class {
|
|
|
576
660
|
return headers;
|
|
577
661
|
}
|
|
578
662
|
};
|
|
663
|
+
function requireNonBlank(value, name) {
|
|
664
|
+
if (value.trim().length === 0) throw new Error(`${name} is required.`);
|
|
665
|
+
}
|
|
579
666
|
function normalizePath(path) {
|
|
580
667
|
return path.replace(/\/+/g, "/").replace(/^\//, "").replace(/\/$/, "");
|
|
581
668
|
}
|
|
@@ -592,6 +679,52 @@ var InstallationsModule = class {
|
|
|
592
679
|
}
|
|
593
680
|
http;
|
|
594
681
|
workspaceId;
|
|
682
|
+
/** Resolve canonical publisher/package identity, never the installation's runtime display name. */
|
|
683
|
+
async resolvePackageId(identity, options) {
|
|
684
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9]+(?:-[a-z0-9]+)*$/.test(identity)) {
|
|
685
|
+
throw new Error("A canonical publisher/package identity is required");
|
|
686
|
+
}
|
|
687
|
+
const result = await this.http.request("GET", `/packages/by-package-id/${identity}`, {
|
|
688
|
+
params: { workspaceId: this.requireWorkspaceId(options?.workspaceId) }
|
|
689
|
+
});
|
|
690
|
+
if (!result || typeof result.id !== "string" || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(result.id)) {
|
|
691
|
+
throw new Error("The catalog response omitted its stable package identity");
|
|
692
|
+
}
|
|
693
|
+
return result.id;
|
|
694
|
+
}
|
|
695
|
+
/** Resolves the complete required graph without installing anything or granting capabilities. */
|
|
696
|
+
async preview(packageId, options) {
|
|
697
|
+
return this.http.request("POST", "/installations/graphs/preview", {
|
|
698
|
+
body: JSON.stringify({
|
|
699
|
+
workspaceId: this.requireWorkspaceId(options?.workspaceId),
|
|
700
|
+
packageId,
|
|
701
|
+
packageVersion: options?.packageVersion,
|
|
702
|
+
installPath: options?.installPath,
|
|
703
|
+
branch: options?.branch,
|
|
704
|
+
installMode: options?.installMode
|
|
705
|
+
}),
|
|
706
|
+
headers: { "Content-Type": "application/json" }
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
/** Explicit, single-use consent to a displayed preview. Never automatically retries accepted installs. */
|
|
710
|
+
async install(preview, consent) {
|
|
711
|
+
if (consent !== true) throw new Error("Explicit consent to the installation graph is required");
|
|
712
|
+
return this.http.request("POST", "/installations/graphs", {
|
|
713
|
+
body: JSON.stringify({
|
|
714
|
+
workspaceId: preview.workspaceId,
|
|
715
|
+
operationId: preview.operationId,
|
|
716
|
+
fingerprint: preview.fingerprint,
|
|
717
|
+
consent
|
|
718
|
+
}),
|
|
719
|
+
headers: { "Content-Type": "application/json" }
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
/** Inspect durable outcomes after a failure or lost response instead of resubmitting consent. */
|
|
723
|
+
async graphResult(operationId, options) {
|
|
724
|
+
return this.http.request("GET", `/installations/graphs/${encodeURIComponent(operationId)}`, {
|
|
725
|
+
params: { workspaceId: this.requireWorkspaceId(options?.workspaceId) }
|
|
726
|
+
});
|
|
727
|
+
}
|
|
595
728
|
async list(options) {
|
|
596
729
|
return this.http.request("GET", "/installations", {
|
|
597
730
|
params: {
|
|
@@ -772,6 +905,176 @@ var SettingsModule = class {
|
|
|
772
905
|
}
|
|
773
906
|
};
|
|
774
907
|
|
|
908
|
+
// src/modules/email.ts
|
|
909
|
+
var EmailModule = class {
|
|
910
|
+
constructor(http, workspaceId) {
|
|
911
|
+
this.http = http;
|
|
912
|
+
this.workspaceId = workspaceId;
|
|
913
|
+
}
|
|
914
|
+
http;
|
|
915
|
+
workspaceId;
|
|
916
|
+
async preview(installationId, options) {
|
|
917
|
+
return this.http.request("GET", `/installations/${encodeURIComponent(installationId)}/email-consent-preview`, {
|
|
918
|
+
params: { workspaceId: this.workspace(options) }
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
async consents(options) {
|
|
922
|
+
return this.http.request("GET", "/email/consents", { params: { workspaceId: this.workspace(options) } });
|
|
923
|
+
}
|
|
924
|
+
/** Approve exactly the reviewed signed candidate; tracking stays off unless explicitly opted in. */
|
|
925
|
+
async consent(preview, approval, options) {
|
|
926
|
+
if (approval !== true) throw new Error("Explicit email consent is required");
|
|
927
|
+
if (!options?.senderGrants?.length) throw new Error("Explicit purpose/sender grants are required");
|
|
928
|
+
return this.http.request("PUT", `/email/consents/${encodeURIComponent(preview.installationId)}`, {
|
|
929
|
+
params: { workspaceId: preview.workspaceId },
|
|
930
|
+
body: JSON.stringify({
|
|
931
|
+
candidateDigest: preview.candidateDigest,
|
|
932
|
+
enabled: true,
|
|
933
|
+
allowOpenTracking: options?.allowOpenTracking ?? false,
|
|
934
|
+
allowClickTracking: options?.allowClickTracking ?? false,
|
|
935
|
+
senderGrants: options.senderGrants.map((grant) => ({ purpose: grant.purpose, senderId: grant.senderId }))
|
|
936
|
+
}),
|
|
937
|
+
headers: { "Content-Type": "application/json" }
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
async revoke(consent, options) {
|
|
941
|
+
return this.http.request("PUT", `/email/consents/${encodeURIComponent(consent.installationId)}`, {
|
|
942
|
+
params: { workspaceId: this.workspace(options) },
|
|
943
|
+
body: JSON.stringify({ candidateDigest: consent.candidateDigest, enabled: false, allowOpenTracking: false, allowClickTracking: false }),
|
|
944
|
+
headers: { "Content-Type": "application/json" }
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
async senders(options) {
|
|
948
|
+
return this.http.request("GET", "/email/senders", { params: { workspaceId: this.workspace(options) } });
|
|
949
|
+
}
|
|
950
|
+
async invoicePdf(recordId, installationId, options) {
|
|
951
|
+
this.mainBranch(options);
|
|
952
|
+
return this.http.request("POST", "/email/artifacts/invoice", {
|
|
953
|
+
body: JSON.stringify({ workspaceId: this.workspace(options), recordId, installationId }),
|
|
954
|
+
headers: { "Content-Type": "application/json" }
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
/** Authenticated immutable bytes; no public URL. The caller owns any local object URL it creates. */
|
|
958
|
+
async artifactContent(artifactId, options) {
|
|
959
|
+
this.mainBranch(options);
|
|
960
|
+
return this.http.request("GET", `/email/artifacts/${encodeURIComponent(artifactId)}/content`, {
|
|
961
|
+
params: { workspaceId: this.workspace(options), branch: "main" }
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
/** A stable key binds this exact request. After a lost response inspect status; never automatically resend. */
|
|
965
|
+
async dispatch(options) {
|
|
966
|
+
this.mainBranch(options);
|
|
967
|
+
return this.http.request("POST", "/email/dispatches", {
|
|
968
|
+
body: JSON.stringify({
|
|
969
|
+
workspaceId: this.workspace(options),
|
|
970
|
+
installationId: options.installationId,
|
|
971
|
+
recordId: options.recordId,
|
|
972
|
+
purpose: options.purpose,
|
|
973
|
+
templateId: options.templateId,
|
|
974
|
+
senderId: options.senderId,
|
|
975
|
+
recipients: options.recipients,
|
|
976
|
+
artifactIds: options.artifactIds,
|
|
977
|
+
idempotencyKey: options.idempotencyKey,
|
|
978
|
+
replyTo: options.replyTo
|
|
979
|
+
}),
|
|
980
|
+
headers: { "Content-Type": "application/json" }
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
async outcome(id, options) {
|
|
984
|
+
this.mainBranch(options);
|
|
985
|
+
return this.http.request("GET", `/email/dispatches/${encodeURIComponent(id)}`, {
|
|
986
|
+
params: { workspaceId: this.workspace(options) }
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
async history(options) {
|
|
990
|
+
this.mainBranch(options);
|
|
991
|
+
return this.http.request("GET", "/email/dispatches", {
|
|
992
|
+
params: { workspaceId: this.workspace(options), recordId: options?.recordId, installationId: options?.installationId }
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
async status(id, options) {
|
|
996
|
+
return this.outcome(id, options);
|
|
997
|
+
}
|
|
998
|
+
workspace(options) {
|
|
999
|
+
const value = options?.workspaceId ?? this.workspaceId;
|
|
1000
|
+
if (!value) throw new Error("A workspace is required for transactional email");
|
|
1001
|
+
return value;
|
|
1002
|
+
}
|
|
1003
|
+
mainBranch(options) {
|
|
1004
|
+
if (options?.branch !== void 0 && options.branch !== "main")
|
|
1005
|
+
throw new Error("Transactional email currently supports only the main business branch");
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
// src/modules/business.ts
|
|
1010
|
+
var BusinessModule = class _BusinessModule {
|
|
1011
|
+
constructor(http, branch = "main") {
|
|
1012
|
+
this.http = http;
|
|
1013
|
+
this.branch = branch;
|
|
1014
|
+
}
|
|
1015
|
+
http;
|
|
1016
|
+
branch;
|
|
1017
|
+
withBranch(branch) {
|
|
1018
|
+
return new _BusinessModule(this.http, branch);
|
|
1019
|
+
}
|
|
1020
|
+
async context() {
|
|
1021
|
+
return this.http.request("GET", "/business/context", { headers: this.headers() });
|
|
1022
|
+
}
|
|
1023
|
+
async command(command) {
|
|
1024
|
+
if (!command.idempotencyKey?.trim() || command.idempotencyKey.length > 200 || /[\u0000-\u001f\u007f-\u009f]/.test(command.idempotencyKey)) {
|
|
1025
|
+
throw new Error("A bounded printable idempotency key is required");
|
|
1026
|
+
}
|
|
1027
|
+
if (command.expectedRevision !== void 0 && !/^"[a-f0-9]{32}"$/.test(command.expectedRevision)) {
|
|
1028
|
+
throw new Error("An exact strong quoted canonical revision is required");
|
|
1029
|
+
}
|
|
1030
|
+
return this.http.request("POST", "/business/commands", {
|
|
1031
|
+
headers: this.headers(),
|
|
1032
|
+
body: JSON.stringify(command)
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
async read(id) {
|
|
1036
|
+
return this.http.request("GET", `/business/records/${encodeURIComponent(id)}`, { headers: this.headers() });
|
|
1037
|
+
}
|
|
1038
|
+
async list(kind) {
|
|
1039
|
+
return this.http.request("GET", "/business/records", {
|
|
1040
|
+
headers: this.headers(),
|
|
1041
|
+
params: kind ? { kind } : {}
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
/** null means unresolved, not success; do not invent a new operation key. */
|
|
1045
|
+
async outcome(request) {
|
|
1046
|
+
return this.http.request("POST", "/business/outcomes", {
|
|
1047
|
+
headers: this.headers(),
|
|
1048
|
+
body: JSON.stringify(request)
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
/** Recover using the exact original command, without reproducing server fingerprint encoding. */
|
|
1052
|
+
async outcomeFor(command) {
|
|
1053
|
+
return this.outcome({ operation: command.operation, idempotencyKey: command.idempotencyKey, command });
|
|
1054
|
+
}
|
|
1055
|
+
async report(report, range = {}) {
|
|
1056
|
+
return this.http.request("GET", `/business/reports/${report}`, {
|
|
1057
|
+
headers: this.headers(),
|
|
1058
|
+
params: range
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
/** Retained historical expense receipt bytes, even after the original native file is removed. */
|
|
1062
|
+
async receipt(nodeId, revision) {
|
|
1063
|
+
const response = await this.http.requestRaw("GET", `/business/receipts/${encodeURIComponent(nodeId)}/content`, {
|
|
1064
|
+
headers: this.headers(),
|
|
1065
|
+
params: { revision }
|
|
1066
|
+
});
|
|
1067
|
+
await this.http.ensureSuccess(response);
|
|
1068
|
+
return response.blob();
|
|
1069
|
+
}
|
|
1070
|
+
headers() {
|
|
1071
|
+
if (!this.branch || this.branch.length > 128 || this.branch !== this.branch.trim().toLowerCase() || /[\u0000-\u001f\u007f-\u009f]/.test(this.branch)) {
|
|
1072
|
+
throw new Error("Business operations require a canonical branch");
|
|
1073
|
+
}
|
|
1074
|
+
return { "X-Branch": this.branch, "Content-Type": "application/json" };
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
|
|
775
1078
|
// src/transport/header-actions.ts
|
|
776
1079
|
function isHeaderAction(value) {
|
|
777
1080
|
if (!value || typeof value !== "object") return false;
|
|
@@ -779,7 +1082,113 @@ function isHeaderAction(value) {
|
|
|
779
1082
|
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
1083
|
}
|
|
781
1084
|
|
|
1085
|
+
// src/collaboration/native.ts
|
|
1086
|
+
var NativeCollaborationError = class extends Error {
|
|
1087
|
+
constructor(message, code) {
|
|
1088
|
+
super(message);
|
|
1089
|
+
this.code = code;
|
|
1090
|
+
this.name = "NativeCollaborationError";
|
|
1091
|
+
}
|
|
1092
|
+
code;
|
|
1093
|
+
};
|
|
1094
|
+
function record(value) {
|
|
1095
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1096
|
+
}
|
|
1097
|
+
function natural(value) {
|
|
1098
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1099
|
+
}
|
|
1100
|
+
function identifier(value) {
|
|
1101
|
+
return typeof value === "string" && value.length > 0 && value.length <= 256;
|
|
1102
|
+
}
|
|
1103
|
+
function pointer(value) {
|
|
1104
|
+
return record(value) && typeof value.x === "number" && Number.isFinite(value.x) && typeof value.y === "number" && Number.isFinite(value.y);
|
|
1105
|
+
}
|
|
1106
|
+
function element(value) {
|
|
1107
|
+
return record(value) && identifier(value.id) && natural(value.version) && (value.versionNonce === void 0 || natural(value.versionNonce)) && (value.isDeleted === void 0 || typeof value.isDeleted === "boolean");
|
|
1108
|
+
}
|
|
1109
|
+
function files(value) {
|
|
1110
|
+
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)));
|
|
1111
|
+
}
|
|
1112
|
+
function isNativeSnapshot(value) {
|
|
1113
|
+
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)
|
|
1114
|
+
return false;
|
|
1115
|
+
const ids = value.scene.elements.map((item) => item.id);
|
|
1116
|
+
if (new Set(ids).size !== ids.length) return false;
|
|
1117
|
+
const connectionIds = value.participants.map((item) => record(item) ? item.id : void 0);
|
|
1118
|
+
if (new Set(connectionIds).size !== connectionIds.length) return false;
|
|
1119
|
+
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"));
|
|
1120
|
+
}
|
|
1121
|
+
function isNativeSaveResult(value) {
|
|
1122
|
+
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;
|
|
1123
|
+
}
|
|
1124
|
+
function validateNativeCommand(command) {
|
|
1125
|
+
if (!record(command)) throw new Error("Invalid collaboration command.");
|
|
1126
|
+
switch (command.operation) {
|
|
1127
|
+
case "join":
|
|
1128
|
+
case "snapshot":
|
|
1129
|
+
case "close":
|
|
1130
|
+
break;
|
|
1131
|
+
case "presence":
|
|
1132
|
+
if (command.pointer !== null && !pointer(command.pointer))
|
|
1133
|
+
throw new Error("Invalid collaboration pointer.");
|
|
1134
|
+
break;
|
|
1135
|
+
case "save":
|
|
1136
|
+
if (!natural(command.generation) || !identifier(command.correlation))
|
|
1137
|
+
throw new Error("Invalid collaboration save frontier.");
|
|
1138
|
+
break;
|
|
1139
|
+
case "apply": {
|
|
1140
|
+
const changes = command.changes;
|
|
1141
|
+
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))
|
|
1142
|
+
throw new Error("Invalid collaboration changes.");
|
|
1143
|
+
const ids = changes.changes.map((change) => change.id);
|
|
1144
|
+
if (new Set(ids).size !== ids.length) throw new Error("Duplicate collaboration element changes.");
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
default:
|
|
1148
|
+
throw new Error("Unsupported collaboration command.");
|
|
1149
|
+
}
|
|
1150
|
+
if (new TextEncoder().encode(JSON.stringify(command)).byteLength > 10 * 1024 * 1024)
|
|
1151
|
+
throw new Error("Collaboration request exceeds the document limit.");
|
|
1152
|
+
}
|
|
1153
|
+
var NativeCollaborationClient = class {
|
|
1154
|
+
constructor(bridge) {
|
|
1155
|
+
this.bridge = bridge;
|
|
1156
|
+
}
|
|
1157
|
+
bridge;
|
|
1158
|
+
async snapshotRequest(command) {
|
|
1159
|
+
if (!this.bridge) throw new Error("Native collaboration requires an installed workspace host.");
|
|
1160
|
+
const result = await this.bridge.requestNativeCollaboration(command);
|
|
1161
|
+
if (!isNativeSnapshot(result)) throw new Error("Invalid native collaboration snapshot.");
|
|
1162
|
+
return result;
|
|
1163
|
+
}
|
|
1164
|
+
join() {
|
|
1165
|
+
return this.snapshotRequest({ operation: "join" });
|
|
1166
|
+
}
|
|
1167
|
+
snapshot() {
|
|
1168
|
+
return this.snapshotRequest({ operation: "snapshot" });
|
|
1169
|
+
}
|
|
1170
|
+
apply(changes) {
|
|
1171
|
+
return this.snapshotRequest({ operation: "apply", changes });
|
|
1172
|
+
}
|
|
1173
|
+
presence(value) {
|
|
1174
|
+
return this.snapshotRequest({ operation: "presence", pointer: value });
|
|
1175
|
+
}
|
|
1176
|
+
async save(generation, correlation) {
|
|
1177
|
+
if (!this.bridge) throw new Error("Native collaboration requires an installed workspace host.");
|
|
1178
|
+
const result = await this.bridge.requestNativeCollaboration({ operation: "save", generation, correlation });
|
|
1179
|
+
if (!isNativeSaveResult(result) || result.receipt.generation !== generation || result.receipt.correlation !== correlation)
|
|
1180
|
+
throw new Error("Invalid native collaboration save receipt.");
|
|
1181
|
+
return result;
|
|
1182
|
+
}
|
|
1183
|
+
async close() {
|
|
1184
|
+
if (!this.bridge) throw new Error("Native collaboration requires an installed workspace host.");
|
|
1185
|
+
const result = await this.bridge.requestNativeCollaboration({ operation: "close" });
|
|
1186
|
+
if (result !== null) throw new Error("Invalid native collaboration close response.");
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
|
|
782
1190
|
// src/transport/bridge.transport.ts
|
|
1191
|
+
var HOST_MESSAGE_CHANNEL_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
783
1192
|
var Bridge = class {
|
|
784
1193
|
constructor(expectedHostOrigin) {
|
|
785
1194
|
this.expectedHostOrigin = expectedHostOrigin;
|
|
@@ -806,8 +1215,10 @@ var Bridge = class {
|
|
|
806
1215
|
sessionId = null;
|
|
807
1216
|
actions = /* @__PURE__ */ new Map();
|
|
808
1217
|
requests = /* @__PURE__ */ new Set();
|
|
809
|
-
|
|
1218
|
+
hostMessageHandlers = /* @__PURE__ */ new Map();
|
|
1219
|
+
hostMessageRequests = /* @__PURE__ */ new Set();
|
|
810
1220
|
wopiRequests = /* @__PURE__ */ new Map();
|
|
1221
|
+
collaborationRequests = /* @__PURE__ */ new Map();
|
|
811
1222
|
/** Post `verentis:ready` to the host to start the handshake */
|
|
812
1223
|
sendReady(capabilities) {
|
|
813
1224
|
console.info("[Verentis SDK] Sending verentis:ready to host");
|
|
@@ -869,28 +1280,6 @@ var Bridge = class {
|
|
|
869
1280
|
setDirty(isDirty) {
|
|
870
1281
|
this.postToHost({ type: "verentis:dirty", version: 1, isDirty });
|
|
871
1282
|
}
|
|
872
|
-
/** The host, not the app, selects the installation, file, branch and permission ceiling. */
|
|
873
|
-
requestBackendCredential() {
|
|
874
|
-
if (this._destroyed || !this.sessionId || !this._hostOrigin)
|
|
875
|
-
return Promise.reject(new Error("An initialized workspace host is required."));
|
|
876
|
-
if (this.backendRequests.size >= 4)
|
|
877
|
-
return Promise.reject(new Error("A backend credential request is already pending."));
|
|
878
|
-
const requestId = crypto.randomUUID();
|
|
879
|
-
return new Promise((resolve, reject) => {
|
|
880
|
-
const timer = setTimeout(() => {
|
|
881
|
-
this.backendRequests.delete(requestId);
|
|
882
|
-
reject(new Error("Backend authorization timed out."));
|
|
883
|
-
}, 2e4);
|
|
884
|
-
this.backendRequests.set(requestId, { resolve, reject, timer });
|
|
885
|
-
try {
|
|
886
|
-
this.postToHost({ type: "verentis:backend:request", version: 1, requestId, sessionId: this.sessionId });
|
|
887
|
-
} catch {
|
|
888
|
-
clearTimeout(timer);
|
|
889
|
-
this.backendRequests.delete(requestId);
|
|
890
|
-
reject(new Error("The workspace host is unavailable."));
|
|
891
|
-
}
|
|
892
|
-
});
|
|
893
|
-
}
|
|
894
1283
|
/** The trusted parent selects the approved installation, document and action. */
|
|
895
1284
|
requestWopiLaunch() {
|
|
896
1285
|
return this.requestWopi("launch");
|
|
@@ -938,6 +1327,38 @@ var Bridge = class {
|
|
|
938
1327
|
}
|
|
939
1328
|
});
|
|
940
1329
|
}
|
|
1330
|
+
requestNativeCollaboration(command) {
|
|
1331
|
+
if (this._destroyed || !this.sessionId || !this._hostOrigin)
|
|
1332
|
+
return Promise.reject(new Error("An initialized workspace host is required."));
|
|
1333
|
+
if (this.collaborationRequests.size >= 4)
|
|
1334
|
+
return Promise.reject(new Error("Too many native collaboration requests."));
|
|
1335
|
+
try {
|
|
1336
|
+
validateNativeCommand(command);
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
return Promise.reject(error);
|
|
1339
|
+
}
|
|
1340
|
+
const requestId = crypto.randomUUID();
|
|
1341
|
+
return new Promise((resolve, reject) => {
|
|
1342
|
+
const timer = setTimeout(() => {
|
|
1343
|
+
this.collaborationRequests.delete(requestId);
|
|
1344
|
+
reject(new NativeCollaborationError("Native collaboration timed out.", "unavailable"));
|
|
1345
|
+
}, 3e4);
|
|
1346
|
+
this.collaborationRequests.set(requestId, { resolve, reject, timer });
|
|
1347
|
+
try {
|
|
1348
|
+
this.postToHost({
|
|
1349
|
+
type: "verentis:collaboration:request",
|
|
1350
|
+
version: 1,
|
|
1351
|
+
requestId,
|
|
1352
|
+
sessionId: this.sessionId,
|
|
1353
|
+
command
|
|
1354
|
+
});
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
clearTimeout(timer);
|
|
1357
|
+
this.collaborationRequests.delete(requestId);
|
|
1358
|
+
reject(error);
|
|
1359
|
+
}
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
941
1362
|
registerAction(descriptor, handler) {
|
|
942
1363
|
if (!this.sessionId || this._destroyed) throw new Error("Header actions require an initialized workspace host.");
|
|
943
1364
|
if (!isHeaderAction(descriptor)) throw new Error("Invalid header action.");
|
|
@@ -1001,6 +1422,49 @@ var Bridge = class {
|
|
|
1001
1422
|
this.publishActions();
|
|
1002
1423
|
}
|
|
1003
1424
|
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Handle messages the workspace sends on a channel declared in the manifest `spec.messages` — for
|
|
1427
|
+
* example an agent asking the open app to select a record. The handler's return value is the reply;
|
|
1428
|
+
* a thrown error is reported back as the failure. May be called before the init handshake.
|
|
1429
|
+
*/
|
|
1430
|
+
onHostMessage(channel, handler) {
|
|
1431
|
+
if (this._destroyed) throw new Error("Bridge has been destroyed.");
|
|
1432
|
+
if (!HOST_MESSAGE_CHANNEL_PATTERN.test(channel)) throw new Error(`Invalid host message channel '${channel}'.`);
|
|
1433
|
+
if (this.hostMessageHandlers.has(channel)) throw new Error(`Channel '${channel}' already has a handler.`);
|
|
1434
|
+
this.hostMessageHandlers.set(channel, handler);
|
|
1435
|
+
return () => {
|
|
1436
|
+
if (this.hostMessageHandlers.get(channel) === handler) this.hostMessageHandlers.delete(channel);
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
async receiveHostMessage(message) {
|
|
1440
|
+
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;
|
|
1441
|
+
this.hostMessageRequests.add(message.requestId);
|
|
1442
|
+
if (this.hostMessageRequests.size > 1e3) this.hostMessageRequests.delete(this.hostMessageRequests.values().next().value);
|
|
1443
|
+
const reply = {
|
|
1444
|
+
type: "verentis:host:reply",
|
|
1445
|
+
version: 1,
|
|
1446
|
+
sessionId: message.sessionId,
|
|
1447
|
+
requestId: message.requestId,
|
|
1448
|
+
channel: message.channel
|
|
1449
|
+
};
|
|
1450
|
+
const handler = this.hostMessageHandlers.get(message.channel);
|
|
1451
|
+
if (!handler) {
|
|
1452
|
+
this.postToHost({ ...reply, error: `The application does not handle '${message.channel.slice(0, 64)}' messages.` });
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
let response;
|
|
1456
|
+
try {
|
|
1457
|
+
response = { result: await handler(message.payload) };
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
response = { error: (error instanceof Error ? error.message : "Message failed.").slice(0, 500) || "Message failed." };
|
|
1460
|
+
}
|
|
1461
|
+
if (this._destroyed || this.sessionId !== message.sessionId) return;
|
|
1462
|
+
try {
|
|
1463
|
+
this.postToHost({ ...reply, ...response });
|
|
1464
|
+
} catch {
|
|
1465
|
+
this.postToHost({ ...reply, error: "The reply could not be sent to the workspace." });
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1004
1468
|
/** Subscribe to a specific message type from the host */
|
|
1005
1469
|
on(type, listener) {
|
|
1006
1470
|
if (!this.listeners.has(type)) {
|
|
@@ -1019,17 +1483,19 @@ var Bridge = class {
|
|
|
1019
1483
|
if (!this._hostOrigin) this._initReject(new Error("Bridge has been destroyed."));
|
|
1020
1484
|
this.actions.clear();
|
|
1021
1485
|
this.requests.clear();
|
|
1486
|
+
this.hostMessageHandlers.clear();
|
|
1487
|
+
this.hostMessageRequests.clear();
|
|
1022
1488
|
this.listeners.clear();
|
|
1023
|
-
for (const pending of this.
|
|
1489
|
+
for (const pending of this.wopiRequests.values()) {
|
|
1024
1490
|
clearTimeout(pending.timer);
|
|
1025
1491
|
pending.reject(new Error("Bridge has been destroyed."));
|
|
1026
1492
|
}
|
|
1027
|
-
this.
|
|
1028
|
-
for (const pending of this.
|
|
1493
|
+
this.wopiRequests.clear();
|
|
1494
|
+
for (const pending of this.collaborationRequests.values()) {
|
|
1029
1495
|
clearTimeout(pending.timer);
|
|
1030
1496
|
pending.reject(new Error("Bridge has been destroyed."));
|
|
1031
1497
|
}
|
|
1032
|
-
this.
|
|
1498
|
+
this.collaborationRequests.clear();
|
|
1033
1499
|
if (typeof window !== "undefined") {
|
|
1034
1500
|
window.removeEventListener("message", this._messageHandler);
|
|
1035
1501
|
}
|
|
@@ -1055,26 +1521,28 @@ var Bridge = class {
|
|
|
1055
1521
|
const initData = data;
|
|
1056
1522
|
this._initResolve({ context: initData.context, token: initData.token });
|
|
1057
1523
|
}
|
|
1058
|
-
if (data.type === "verentis:
|
|
1524
|
+
if (data.type === "verentis:wopi:response") {
|
|
1059
1525
|
if (!this._hostOrigin || data.sessionId !== this.sessionId) return;
|
|
1060
|
-
const pending = this.
|
|
1526
|
+
const pending = this.wopiRequests.get(data.requestId);
|
|
1061
1527
|
if (!pending) return;
|
|
1062
1528
|
clearTimeout(pending.timer);
|
|
1063
|
-
this.
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
else pending.resolve(value);
|
|
1529
|
+
this.wopiRequests.delete(data.requestId);
|
|
1530
|
+
if (data.error || !Object.hasOwn(data, "result"))
|
|
1531
|
+
pending.reject(new Error("Collaboration authorization is unavailable."));
|
|
1532
|
+
else pending.resolve(data.result);
|
|
1068
1533
|
return;
|
|
1069
1534
|
}
|
|
1070
|
-
if (data.type === "verentis:
|
|
1535
|
+
if (data.type === "verentis:collaboration:response") {
|
|
1071
1536
|
if (!this._hostOrigin || data.sessionId !== this.sessionId) return;
|
|
1072
|
-
const pending = this.
|
|
1537
|
+
const pending = this.collaborationRequests.get(data.requestId);
|
|
1073
1538
|
if (!pending) return;
|
|
1074
1539
|
clearTimeout(pending.timer);
|
|
1075
|
-
this.
|
|
1540
|
+
this.collaborationRequests.delete(data.requestId);
|
|
1076
1541
|
if (data.error || !Object.hasOwn(data, "result"))
|
|
1077
|
-
pending.reject(new
|
|
1542
|
+
pending.reject(new NativeCollaborationError(
|
|
1543
|
+
typeof data.error === "string" ? data.error.slice(0, 500) : "Native collaboration is unavailable.",
|
|
1544
|
+
typeof data.code === "string" ? data.code.slice(0, 64) : "unavailable"
|
|
1545
|
+
));
|
|
1078
1546
|
else pending.resolve(data.result);
|
|
1079
1547
|
return;
|
|
1080
1548
|
}
|
|
@@ -1082,6 +1550,10 @@ var Bridge = class {
|
|
|
1082
1550
|
void this.invokeAction(data);
|
|
1083
1551
|
return;
|
|
1084
1552
|
}
|
|
1553
|
+
if (data.type === "verentis:host:message") {
|
|
1554
|
+
void this.receiveHostMessage(data);
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1085
1557
|
const typeListeners = this.listeners.get(data.type);
|
|
1086
1558
|
if (typeListeners) {
|
|
1087
1559
|
for (const listener of typeListeners) {
|
|
@@ -1093,16 +1565,18 @@ var Bridge = class {
|
|
|
1093
1565
|
|
|
1094
1566
|
// src/types.ts
|
|
1095
1567
|
var ApiError = class extends Error {
|
|
1096
|
-
constructor(status, detail, title) {
|
|
1568
|
+
constructor(status, detail, title, code) {
|
|
1097
1569
|
super(title ?? `API error ${status}: ${detail}`);
|
|
1098
1570
|
this.status = status;
|
|
1099
1571
|
this.detail = detail;
|
|
1100
1572
|
this.title = title;
|
|
1573
|
+
this.code = code;
|
|
1101
1574
|
this.name = "ApiError";
|
|
1102
1575
|
}
|
|
1103
1576
|
status;
|
|
1104
1577
|
detail;
|
|
1105
1578
|
title;
|
|
1579
|
+
code;
|
|
1106
1580
|
};
|
|
1107
1581
|
|
|
1108
1582
|
// src/transport/http.transport.ts
|
|
@@ -1131,24 +1605,7 @@ var HttpTransport = class {
|
|
|
1131
1605
|
headers,
|
|
1132
1606
|
body: options?.body
|
|
1133
1607
|
});
|
|
1134
|
-
|
|
1135
|
-
let detail = "";
|
|
1136
|
-
let title;
|
|
1137
|
-
try {
|
|
1138
|
-
const body = await response.json();
|
|
1139
|
-
detail = body.detail ?? body.message ?? JSON.stringify(body);
|
|
1140
|
-
title = body.title;
|
|
1141
|
-
} catch {
|
|
1142
|
-
detail = await response.text().catch(() => response.statusText);
|
|
1143
|
-
}
|
|
1144
|
-
console.error("[Verentis SDK] Request failed", {
|
|
1145
|
-
method,
|
|
1146
|
-
path,
|
|
1147
|
-
status: response.status,
|
|
1148
|
-
detail
|
|
1149
|
-
});
|
|
1150
|
-
throw new ApiError(response.status, detail, title);
|
|
1151
|
-
}
|
|
1608
|
+
await this.ensureSuccess(response);
|
|
1152
1609
|
console.info("[Verentis SDK] Request succeeded", {
|
|
1153
1610
|
method,
|
|
1154
1611
|
path,
|
|
@@ -1182,6 +1639,26 @@ var HttpTransport = class {
|
|
|
1182
1639
|
});
|
|
1183
1640
|
return response;
|
|
1184
1641
|
}
|
|
1642
|
+
async ensureSuccess(response) {
|
|
1643
|
+
if (response.ok) return;
|
|
1644
|
+
const raw = await response.text();
|
|
1645
|
+
let detail = raw || response.statusText;
|
|
1646
|
+
let title;
|
|
1647
|
+
let code;
|
|
1648
|
+
try {
|
|
1649
|
+
const body = JSON.parse(raw);
|
|
1650
|
+
if (body !== null && typeof body === "object") {
|
|
1651
|
+
if ("detail" in body && typeof body.detail === "string") detail = body.detail;
|
|
1652
|
+
else if ("message" in body && typeof body.message === "string") detail = body.message;
|
|
1653
|
+
if ("title" in body && typeof body.title === "string") title = body.title;
|
|
1654
|
+
if ("code" in body && typeof body.code === "string") code = body.code;
|
|
1655
|
+
}
|
|
1656
|
+
} catch (error) {
|
|
1657
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
1658
|
+
}
|
|
1659
|
+
console.error("[Verentis SDK] Request failed", { status: response.status, code });
|
|
1660
|
+
throw new ApiError(response.status, detail, title, code);
|
|
1661
|
+
}
|
|
1185
1662
|
buildUrl(path, params) {
|
|
1186
1663
|
const base = `${this.baseUrl}/${this.apiVersion}${path}`;
|
|
1187
1664
|
if (!params) return base;
|
|
@@ -1211,8 +1688,11 @@ var VerentisClient = class {
|
|
|
1211
1688
|
installations;
|
|
1212
1689
|
resources;
|
|
1213
1690
|
settings;
|
|
1691
|
+
email;
|
|
1692
|
+
business;
|
|
1214
1693
|
context;
|
|
1215
1694
|
bridge;
|
|
1695
|
+
collaboration;
|
|
1216
1696
|
_auth;
|
|
1217
1697
|
_http;
|
|
1218
1698
|
_ready;
|
|
@@ -1221,11 +1701,13 @@ var VerentisClient = class {
|
|
|
1221
1701
|
const environment = detectEnvironment();
|
|
1222
1702
|
if (environment === "iframe" && !options?.auth) {
|
|
1223
1703
|
this.bridge = new Bridge();
|
|
1704
|
+
this.collaboration = new NativeCollaborationClient(this.bridge);
|
|
1224
1705
|
const bridgeAuth = new BridgeAuth("*");
|
|
1225
1706
|
this._auth = bridgeAuth;
|
|
1226
1707
|
this._ready = this.initFromBridge(bridgeAuth);
|
|
1227
1708
|
} else {
|
|
1228
1709
|
this.bridge = null;
|
|
1710
|
+
this.collaboration = new NativeCollaborationClient(null);
|
|
1229
1711
|
this._auth = createAuthStrategy(options);
|
|
1230
1712
|
const apiUrl = options?.apiUrl;
|
|
1231
1713
|
if (!apiUrl) {
|
|
@@ -1242,6 +1724,8 @@ var VerentisClient = class {
|
|
|
1242
1724
|
this.installations = new InstallationsModule(this._http, options?.workspaceId);
|
|
1243
1725
|
this.resources = new ResourcesModule(this._http, options?.workspaceId);
|
|
1244
1726
|
this.settings = new SettingsModule(this._http, options?.workspaceId);
|
|
1727
|
+
this.email = new EmailModule(this._http, options?.workspaceId);
|
|
1728
|
+
this.business = new BusinessModule(this._http);
|
|
1245
1729
|
this._ready = Promise.resolve();
|
|
1246
1730
|
return;
|
|
1247
1731
|
}
|
|
@@ -1254,6 +1738,14 @@ var VerentisClient = class {
|
|
|
1254
1738
|
whenReady() {
|
|
1255
1739
|
return this._ready;
|
|
1256
1740
|
}
|
|
1741
|
+
/**
|
|
1742
|
+
* The current `Authorization` header value, refreshed when near expiry.
|
|
1743
|
+
* Forward it to the app's own backend, which exchanges it at Security's
|
|
1744
|
+
* `/connect/token` (RFC 8693) as the installation's confidential client.
|
|
1745
|
+
*/
|
|
1746
|
+
getAuthorizationHeader() {
|
|
1747
|
+
return this._auth.getAuthHeader();
|
|
1748
|
+
}
|
|
1257
1749
|
/** Clean up resources (timers, event listeners) */
|
|
1258
1750
|
destroy() {
|
|
1259
1751
|
this._auth.destroy?.();
|
|
@@ -1266,6 +1758,10 @@ var VerentisClient = class {
|
|
|
1266
1758
|
bridgeAuth.setHostOrigin(hostOrigin);
|
|
1267
1759
|
bridgeAuth.setToken(token);
|
|
1268
1760
|
this.context.setFromAppContext(context);
|
|
1761
|
+
this.context.setGrants(token.grants);
|
|
1762
|
+
this.bridge.on("verentis:token:refreshed", (message) => {
|
|
1763
|
+
if (message.type === "verentis:token:refreshed") this.context.setGrants(message.token?.grants);
|
|
1764
|
+
});
|
|
1269
1765
|
const http = new HttpTransport(
|
|
1270
1766
|
context.apiUrl,
|
|
1271
1767
|
bridgeAuth,
|
|
@@ -1278,6 +1774,8 @@ var VerentisClient = class {
|
|
|
1278
1774
|
this.installations = new InstallationsModule(http, context.workspace.id);
|
|
1279
1775
|
this.resources = new ResourcesModule(http, context.workspace.id);
|
|
1280
1776
|
this.settings = new SettingsModule(http, context.workspace.id);
|
|
1777
|
+
this.email = new EmailModule(http, context.workspace.id);
|
|
1778
|
+
this.business = new BusinessModule(http);
|
|
1281
1779
|
}
|
|
1282
1780
|
};
|
|
1283
1781
|
function detectEnvironment() {
|
|
@@ -1330,11 +1828,49 @@ function stripParams(mime) {
|
|
|
1330
1828
|
return idx === -1 ? mime.trim() : mime.slice(0, idx).trim();
|
|
1331
1829
|
}
|
|
1332
1830
|
|
|
1831
|
+
// src/collaboration/lifecycle.ts
|
|
1832
|
+
function isRecord(value) {
|
|
1833
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1834
|
+
}
|
|
1835
|
+
function exactHttpsOrigin(value) {
|
|
1836
|
+
if (typeof value !== "string" || value.length > 256)
|
|
1837
|
+
throw new Error("Invalid framing origin.");
|
|
1838
|
+
const origin = new URL(value);
|
|
1839
|
+
if (origin.protocol !== "https:" || origin.origin !== value || origin.href !== `${value}/` || !/^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/.test(origin.hostname))
|
|
1840
|
+
throw new Error("Invalid framing origin.");
|
|
1841
|
+
return value;
|
|
1842
|
+
}
|
|
1843
|
+
function isTrustedMessage(event, source, origin) {
|
|
1844
|
+
return Boolean(source && origin && event.source === source && event.origin === origin);
|
|
1845
|
+
}
|
|
1846
|
+
function validateCollaborationDocument(value, context, now = Date.now()) {
|
|
1847
|
+
const branch = context.file?.branch;
|
|
1848
|
+
const node = context.file?.nodeId;
|
|
1849
|
+
const workspace = context.workspace?.id;
|
|
1850
|
+
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")
|
|
1851
|
+
throw new Error("Invalid live launch.");
|
|
1852
|
+
return {
|
|
1853
|
+
sessionId: value.sessionId,
|
|
1854
|
+
workspaceId: value.workspaceId,
|
|
1855
|
+
branch: value.branch,
|
|
1856
|
+
nodeId: value.nodeId,
|
|
1857
|
+
fileId: value.fileId,
|
|
1858
|
+
name: value.name,
|
|
1859
|
+
readOnly: value.readOnly,
|
|
1860
|
+
accessTokenTtl: value.accessTokenTtl
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
function isDurableSaveConfirmed(result, request, currentGeneration, editorModified) {
|
|
1864
|
+
if (!isRecord(request) || !isRecord(result) || !isRecord(result.receipt))
|
|
1865
|
+
return false;
|
|
1866
|
+
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;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1333
1869
|
// src/index.ts
|
|
1334
1870
|
function createVerentisClient(options) {
|
|
1335
1871
|
return new VerentisClient(options);
|
|
1336
1872
|
}
|
|
1337
1873
|
|
|
1338
|
-
export { ApiKeyAuth, Bridge, BridgeAuth, ContextModule, EnginesModule, ExecutionsModule, FilesModule, HttpTransport, InstallationsModule, ResourcesModule, SettingsModule, TokenAuth, VerentisClient, createVerentisClient, findBestMatch, isHeaderAction, isTerminalStatus, matchMimeType };
|
|
1874
|
+
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 };
|
|
1339
1875
|
//# sourceMappingURL=index.js.map
|
|
1340
1876
|
//# sourceMappingURL=index.js.map
|