@verentis/sdk 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -30,6 +30,14 @@ var BridgeAuth = class {
30
30
  _refreshTimer = null;
31
31
  _pendingRefresh = null;
32
32
  _messageHandler;
33
+ _requestId = null;
34
+ _refreshReject = null;
35
+ _requestTimer = null;
36
+ _destroyed = false;
37
+ setHostOrigin(origin) {
38
+ if (!origin || origin === "*" || origin === "null") throw new Error("A trusted host origin is required.");
39
+ this.hostOrigin = origin;
40
+ }
33
41
  /** Set the initial token received from the host's `verentis:init` message */
34
42
  setToken(info) {
35
43
  this._token = info.accessToken;
@@ -44,7 +52,9 @@ var BridgeAuth = class {
44
52
  return `Bearer ${this._token}`;
45
53
  }
46
54
  destroy() {
55
+ this._destroyed = true;
47
56
  if (this._refreshTimer) clearTimeout(this._refreshTimer);
57
+ this.failRefresh("Authentication has been destroyed.");
48
58
  if (typeof window !== "undefined") {
49
59
  window.removeEventListener("message", this._messageHandler);
50
60
  }
@@ -54,11 +64,16 @@ var BridgeAuth = class {
54
64
  }
55
65
  _refreshResolve = null;
56
66
  requestRefresh() {
67
+ if (this._destroyed || this.hostOrigin === "*")
68
+ return Promise.reject(new Error("Authentication is not initialized."));
57
69
  if (this._pendingRefresh) return this._pendingRefresh;
58
70
  const requestId = crypto.randomUUID();
59
- this._pendingRefresh = new Promise((resolve) => {
71
+ this._requestId = requestId;
72
+ this._pendingRefresh = new Promise((resolve, reject) => {
60
73
  this._refreshResolve = resolve;
74
+ this._refreshReject = reject;
61
75
  });
76
+ this._requestTimer = setTimeout(() => this.failRefresh("Token refresh timed out."), 2e4);
62
77
  const message = {
63
78
  type: "verentis:token:refresh",
64
79
  version: 1,
@@ -68,21 +83,35 @@ var BridgeAuth = class {
68
83
  return this._pendingRefresh;
69
84
  }
70
85
  handleMessage(event) {
71
- if (event.origin !== this.hostOrigin) return;
86
+ if (this._destroyed || event.origin !== this.hostOrigin || event.source !== window.parent) return;
72
87
  const data = event.data;
73
- if (data?.type !== "verentis:token:refreshed") return;
88
+ if (data?.version !== 1 || data?.type !== "verentis:token:refreshed" || !this._requestId || data.requestId !== this._requestId || typeof data.token?.accessToken !== "string" || !Number.isFinite(Date.parse(data.token.expiresAt)) || Date.parse(data.token.expiresAt) <= Date.now() + SKEW_MS) return;
89
+ if (this._requestTimer) clearTimeout(this._requestTimer);
90
+ this._requestTimer = null;
91
+ this._requestId = null;
74
92
  this._token = data.token.accessToken;
75
93
  this._expiresAt = new Date(data.token.expiresAt).getTime();
76
94
  this._pendingRefresh = null;
77
95
  this._refreshResolve?.();
78
96
  this._refreshResolve = null;
97
+ this._refreshReject = null;
79
98
  this.scheduleRefresh();
80
99
  }
81
100
  scheduleRefresh() {
82
101
  if (this._refreshTimer) clearTimeout(this._refreshTimer);
83
102
  if (this._expiresAt === 0) return;
84
103
  const delay2 = Math.max(0, this._expiresAt - Date.now() - SKEW_MS);
85
- this._refreshTimer = setTimeout(() => void this.requestRefresh(), delay2);
104
+ this._refreshTimer = setTimeout(() => void this.requestRefresh().catch(() => {
105
+ }), delay2);
106
+ }
107
+ failRefresh(message) {
108
+ if (this._requestTimer) clearTimeout(this._requestTimer);
109
+ this._requestTimer = null;
110
+ this._requestId = null;
111
+ this._pendingRefresh = null;
112
+ this._refreshReject?.(new Error(message));
113
+ this._refreshReject = null;
114
+ this._refreshResolve = null;
86
115
  }
87
116
  };
88
117
 
@@ -754,8 +783,24 @@ function isHeaderAction(value) {
754
783
 
755
784
  // src/transport/bridge.transport.ts
756
785
  var Bridge = class {
786
+ constructor(expectedHostOrigin) {
787
+ this.expectedHostOrigin = expectedHostOrigin;
788
+ this._initPromise = new Promise((resolve, reject) => {
789
+ this._initResolve = resolve;
790
+ this._initReject = reject;
791
+ });
792
+ void this._initPromise.catch(() => {
793
+ });
794
+ this._messageHandler = this.handleMessage.bind(this);
795
+ if (typeof window !== "undefined") {
796
+ window.addEventListener("message", this._messageHandler);
797
+ }
798
+ }
799
+ expectedHostOrigin;
757
800
  listeners = /* @__PURE__ */ new Map();
758
801
  _initResolve;
802
+ _initReject;
803
+ _initTimer;
759
804
  _initPromise;
760
805
  _hostOrigin = null;
761
806
  _messageHandler;
@@ -763,15 +808,7 @@ var Bridge = class {
763
808
  sessionId = null;
764
809
  actions = /* @__PURE__ */ new Map();
765
810
  requests = /* @__PURE__ */ new Set();
766
- constructor() {
767
- this._initPromise = new Promise((resolve) => {
768
- this._initResolve = resolve;
769
- });
770
- this._messageHandler = this.handleMessage.bind(this);
771
- if (typeof window !== "undefined") {
772
- window.addEventListener("message", this._messageHandler);
773
- }
774
- }
811
+ backendRequests = /* @__PURE__ */ new Map();
775
812
  /** Post `verentis:ready` to the host to start the handshake */
776
813
  sendReady(capabilities) {
777
814
  console.info("[Verentis SDK] Sending verentis:ready to host");
@@ -784,6 +821,12 @@ var Bridge = class {
784
821
  }
785
822
  /** Wait for the host to send `verentis:init` with context and token */
786
823
  waitForInit() {
824
+ if (!this._hostOrigin && !this._destroyed && !this._initTimer) {
825
+ this._initTimer = setTimeout(() => {
826
+ this._initReject(new Error("Workspace handshake timed out."));
827
+ this.destroy();
828
+ }, 2e4);
829
+ }
787
830
  return this._initPromise;
788
831
  }
789
832
  /** Get the host origin (available after init) */
@@ -827,6 +870,28 @@ var Bridge = class {
827
870
  setDirty(isDirty) {
828
871
  this.postToHost({ type: "verentis:dirty", version: 1, isDirty });
829
872
  }
873
+ /** The host, not the app, selects the installation, file, branch and permission ceiling. */
874
+ requestBackendCredential() {
875
+ if (this._destroyed || !this.sessionId || !this._hostOrigin)
876
+ return Promise.reject(new Error("An initialized workspace host is required."));
877
+ if (this.backendRequests.size >= 4)
878
+ return Promise.reject(new Error("A backend credential request is already pending."));
879
+ const requestId = crypto.randomUUID();
880
+ return new Promise((resolve, reject) => {
881
+ const timer = setTimeout(() => {
882
+ this.backendRequests.delete(requestId);
883
+ reject(new Error("Backend authorization timed out."));
884
+ }, 2e4);
885
+ this.backendRequests.set(requestId, { resolve, reject, timer });
886
+ try {
887
+ this.postToHost({ type: "verentis:backend:request", version: 1, requestId, sessionId: this.sessionId });
888
+ } catch {
889
+ clearTimeout(timer);
890
+ this.backendRequests.delete(requestId);
891
+ reject(new Error("The workspace host is unavailable."));
892
+ }
893
+ });
894
+ }
830
895
  registerAction(descriptor, handler) {
831
896
  if (!this.sessionId || this._destroyed) throw new Error("Header actions require an initialized workspace host.");
832
897
  if (!isHeaderAction(descriptor)) throw new Error("Invalid header action.");
@@ -903,9 +968,17 @@ var Bridge = class {
903
968
  /** Clean up event listeners */
904
969
  destroy() {
905
970
  this._destroyed = true;
971
+ clearTimeout(this._initTimer);
972
+ this._initTimer = void 0;
973
+ if (!this._hostOrigin) this._initReject(new Error("Bridge has been destroyed."));
906
974
  this.actions.clear();
907
975
  this.requests.clear();
908
976
  this.listeners.clear();
977
+ for (const pending of this.backendRequests.values()) {
978
+ clearTimeout(pending.timer);
979
+ pending.reject(new Error("Bridge has been destroyed."));
980
+ }
981
+ this.backendRequests.clear();
909
982
  if (typeof window !== "undefined") {
910
983
  window.removeEventListener("message", this._messageHandler);
911
984
  }
@@ -915,8 +988,12 @@ var Bridge = class {
915
988
  const data = event.data;
916
989
  if (event.source !== window.parent || data?.version !== 1 || typeof data?.type !== "string" || !data.type.startsWith("verentis:")) return;
917
990
  if (this._hostOrigin && event.origin !== this._hostOrigin) return;
991
+ if (this.expectedHostOrigin && event.origin !== this.expectedHostOrigin) return;
918
992
  if (data.type === "verentis:init") {
919
993
  if (this._hostOrigin) return;
994
+ if (!data.context?.workspace?.id || !data.token || event.origin === "null") return;
995
+ clearTimeout(this._initTimer);
996
+ this._initTimer = void 0;
920
997
  this._hostOrigin = event.origin;
921
998
  this.sessionId = typeof data.sessionId === "string" ? data.sessionId : null;
922
999
  console.info("[Verentis SDK] Received verentis:init from host", {
@@ -927,6 +1004,18 @@ var Bridge = class {
927
1004
  const initData = data;
928
1005
  this._initResolve({ context: initData.context, token: initData.token });
929
1006
  }
1007
+ if (data.type === "verentis:backend:response") {
1008
+ if (!this._hostOrigin || data.sessionId !== this.sessionId) return;
1009
+ const pending = this.backendRequests.get(data.requestId);
1010
+ if (!pending) return;
1011
+ clearTimeout(pending.timer);
1012
+ this.backendRequests.delete(data.requestId);
1013
+ const value = data.credential;
1014
+ if (data.error || !value || typeof value.launchCredential !== "string" || value.launchCredential.length > 512 || !value.launchCredential || typeof value.delegationId !== "string" || typeof value.backendClientId !== "string" || !Number.isFinite(Date.parse(value.expiresAt)) || Date.parse(value.expiresAt) <= Date.now())
1015
+ pending.reject(new Error("Backend authorization was denied or is not configured."));
1016
+ else pending.resolve(value);
1017
+ return;
1018
+ }
930
1019
  if (data.type === "verentis:action:invoke") {
931
1020
  void this.invokeAction(data);
932
1021
  return;
@@ -1112,7 +1201,7 @@ var VerentisClient = class {
1112
1201
  this.bridge.sendReady();
1113
1202
  const { context, token } = await this.bridge.waitForInit();
1114
1203
  const hostOrigin = this.bridge.hostOrigin;
1115
- Object.assign(bridgeAuth, new BridgeAuth(hostOrigin));
1204
+ bridgeAuth.setHostOrigin(hostOrigin);
1116
1205
  bridgeAuth.setToken(token);
1117
1206
  this.context.setFromAppContext(context);
1118
1207
  const http = new HttpTransport(