@verentis/sdk 0.2.3 → 0.2.4

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 CHANGED
@@ -57,6 +57,30 @@ Release the 0.2 SDK before deploying apps that require `^0.2.0`, together with t
57
57
  Local sibling-repository development can link the built SDK; published applications must resolve
58
58
  the released SDK from npm.
59
59
 
60
+ ### WOPI editor launch
61
+
62
+ Signed WOPI applications use `Bridge.requestWopiLaunch()` after `waitForInit()`.
63
+ The authenticated workspace parent chooses the installed editor, document,
64
+ branch and action; the child cannot request arbitrary document identities.
65
+ The returned token is scoped to WOPI and is not a normal workspace API token.
66
+ Submit it using a form POST to the validated discovery action, never a URL.
67
+
68
+ `requestWopiSave(generation)`, `getWopiStatus()` and `closeWopiSession()` use the
69
+ same exact-parent, origin/session/request-correlated bridge. A matching durable
70
+ save receipt—not a CODE postMessage alone—is required before clearing dirty state.
71
+ When status reports `continuationAvailable`, `requestWopiContinuation()` asks
72
+ the parent to bind the target of a completed server-owned Save As operation.
73
+ It accepts no document or acquisition IDs. Its `WopiContinuation` result contains
74
+ only document metadata and the persisted target `accessTokenTtl`, not a token,
75
+ action URL or private control credential. The parent obtains the exact existing
76
+ control IDs from authenticated `/derived`; it does not reconstruct a launch,
77
+ including after signing-key rotation. Keep the initial launch/form separate from
78
+ active document metadata, retain CODE's iframe and existing token, and preserve
79
+ unverified edits until a matching target save receipt arrives.
80
+ These methods do not replace `requestBackendCredential()` or ordinary OAuth
81
+ authentication for apps that need workspace APIs. WOPI-only packages need no
82
+ per-editor OAuth client secret.
83
+
60
84
  ### Standalone mode (local development)
61
85
 
62
86
  ```ts
package/dist/index.cjs CHANGED
@@ -809,6 +809,7 @@ var Bridge = class {
809
809
  actions = /* @__PURE__ */ new Map();
810
810
  requests = /* @__PURE__ */ new Set();
811
811
  backendRequests = /* @__PURE__ */ new Map();
812
+ wopiRequests = /* @__PURE__ */ new Map();
812
813
  /** Post `verentis:ready` to the host to start the handshake */
813
814
  sendReady(capabilities) {
814
815
  console.info("[Verentis SDK] Sending verentis:ready to host");
@@ -817,7 +818,7 @@ var Bridge = class {
817
818
  version: 1,
818
819
  capabilities
819
820
  };
820
- window.parent.postMessage(message, "*");
821
+ window.parent.postMessage(message, this.expectedHostOrigin ?? "*");
821
822
  }
822
823
  /** Wait for the host to send `verentis:init` with context and token */
823
824
  waitForInit() {
@@ -892,6 +893,53 @@ var Bridge = class {
892
893
  }
893
894
  });
894
895
  }
896
+ /** The trusted parent selects the approved installation, document and action. */
897
+ requestWopiLaunch() {
898
+ return this.requestWopi("launch");
899
+ }
900
+ /** Recover only the target selected by a completed server-owned derived operation. */
901
+ requestWopiContinuation() {
902
+ return this.requestWopi("continue");
903
+ }
904
+ getWopiStatus() {
905
+ return this.requestWopi("status");
906
+ }
907
+ requestWopiSave(generation) {
908
+ if (!Number.isSafeInteger(generation) || generation < 0)
909
+ return Promise.reject(new Error("Invalid edit generation."));
910
+ return this.requestWopi("save", generation);
911
+ }
912
+ closeWopiSession() {
913
+ return this.requestWopi("close");
914
+ }
915
+ requestWopi(operation, generation) {
916
+ if (this._destroyed || !this.sessionId || !this._hostOrigin)
917
+ return Promise.reject(new Error("An initialized workspace host is required."));
918
+ if (this.wopiRequests.size >= 4)
919
+ return Promise.reject(new Error("Too many collaboration requests."));
920
+ const requestId = crypto.randomUUID();
921
+ return new Promise((resolve, reject) => {
922
+ const timer = setTimeout(() => {
923
+ this.wopiRequests.delete(requestId);
924
+ reject(new Error("Collaboration authorization timed out."));
925
+ }, 3e4);
926
+ this.wopiRequests.set(requestId, { resolve, reject, timer });
927
+ try {
928
+ this.postToHost({
929
+ type: "verentis:wopi:request",
930
+ version: 1,
931
+ requestId,
932
+ sessionId: this.sessionId,
933
+ operation,
934
+ ...generation === void 0 ? {} : { generation }
935
+ });
936
+ } catch {
937
+ clearTimeout(timer);
938
+ this.wopiRequests.delete(requestId);
939
+ reject(new Error("The workspace host is unavailable."));
940
+ }
941
+ });
942
+ }
895
943
  registerAction(descriptor, handler) {
896
944
  if (!this.sessionId || this._destroyed) throw new Error("Header actions require an initialized workspace host.");
897
945
  if (!isHeaderAction(descriptor)) throw new Error("Invalid header action.");
@@ -979,6 +1027,11 @@ var Bridge = class {
979
1027
  pending.reject(new Error("Bridge has been destroyed."));
980
1028
  }
981
1029
  this.backendRequests.clear();
1030
+ for (const pending of this.wopiRequests.values()) {
1031
+ clearTimeout(pending.timer);
1032
+ pending.reject(new Error("Bridge has been destroyed."));
1033
+ }
1034
+ this.wopiRequests.clear();
982
1035
  if (typeof window !== "undefined") {
983
1036
  window.removeEventListener("message", this._messageHandler);
984
1037
  }
@@ -1016,6 +1069,17 @@ var Bridge = class {
1016
1069
  else pending.resolve(value);
1017
1070
  return;
1018
1071
  }
1072
+ if (data.type === "verentis:wopi:response") {
1073
+ if (!this._hostOrigin || data.sessionId !== this.sessionId) return;
1074
+ const pending = this.wopiRequests.get(data.requestId);
1075
+ if (!pending) return;
1076
+ clearTimeout(pending.timer);
1077
+ this.wopiRequests.delete(data.requestId);
1078
+ if (data.error || !Object.hasOwn(data, "result"))
1079
+ pending.reject(new Error("Collaboration authorization is unavailable."));
1080
+ else pending.resolve(data.result);
1081
+ return;
1082
+ }
1019
1083
  if (data.type === "verentis:action:invoke") {
1020
1084
  void this.invokeAction(data);
1021
1085
  return;