@verentis/sdk 0.1.1 → 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/README.md +47 -0
- package/dist/index.cjs +187 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +98 -5
- package/dist/index.d.ts +98 -5
- package/dist/index.js +187 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28,6 +28,14 @@ var BridgeAuth = class {
|
|
|
28
28
|
_refreshTimer = null;
|
|
29
29
|
_pendingRefresh = null;
|
|
30
30
|
_messageHandler;
|
|
31
|
+
_requestId = null;
|
|
32
|
+
_refreshReject = null;
|
|
33
|
+
_requestTimer = null;
|
|
34
|
+
_destroyed = false;
|
|
35
|
+
setHostOrigin(origin) {
|
|
36
|
+
if (!origin || origin === "*" || origin === "null") throw new Error("A trusted host origin is required.");
|
|
37
|
+
this.hostOrigin = origin;
|
|
38
|
+
}
|
|
31
39
|
/** Set the initial token received from the host's `verentis:init` message */
|
|
32
40
|
setToken(info) {
|
|
33
41
|
this._token = info.accessToken;
|
|
@@ -42,7 +50,9 @@ var BridgeAuth = class {
|
|
|
42
50
|
return `Bearer ${this._token}`;
|
|
43
51
|
}
|
|
44
52
|
destroy() {
|
|
53
|
+
this._destroyed = true;
|
|
45
54
|
if (this._refreshTimer) clearTimeout(this._refreshTimer);
|
|
55
|
+
this.failRefresh("Authentication has been destroyed.");
|
|
46
56
|
if (typeof window !== "undefined") {
|
|
47
57
|
window.removeEventListener("message", this._messageHandler);
|
|
48
58
|
}
|
|
@@ -52,11 +62,16 @@ var BridgeAuth = class {
|
|
|
52
62
|
}
|
|
53
63
|
_refreshResolve = null;
|
|
54
64
|
requestRefresh() {
|
|
65
|
+
if (this._destroyed || this.hostOrigin === "*")
|
|
66
|
+
return Promise.reject(new Error("Authentication is not initialized."));
|
|
55
67
|
if (this._pendingRefresh) return this._pendingRefresh;
|
|
56
68
|
const requestId = crypto.randomUUID();
|
|
57
|
-
this.
|
|
69
|
+
this._requestId = requestId;
|
|
70
|
+
this._pendingRefresh = new Promise((resolve, reject) => {
|
|
58
71
|
this._refreshResolve = resolve;
|
|
72
|
+
this._refreshReject = reject;
|
|
59
73
|
});
|
|
74
|
+
this._requestTimer = setTimeout(() => this.failRefresh("Token refresh timed out."), 2e4);
|
|
60
75
|
const message = {
|
|
61
76
|
type: "verentis:token:refresh",
|
|
62
77
|
version: 1,
|
|
@@ -66,21 +81,35 @@ var BridgeAuth = class {
|
|
|
66
81
|
return this._pendingRefresh;
|
|
67
82
|
}
|
|
68
83
|
handleMessage(event) {
|
|
69
|
-
if (event.origin !== this.hostOrigin) return;
|
|
84
|
+
if (this._destroyed || event.origin !== this.hostOrigin || event.source !== window.parent) return;
|
|
70
85
|
const data = event.data;
|
|
71
|
-
if (data?.type !== "verentis:token:refreshed") return;
|
|
86
|
+
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;
|
|
87
|
+
if (this._requestTimer) clearTimeout(this._requestTimer);
|
|
88
|
+
this._requestTimer = null;
|
|
89
|
+
this._requestId = null;
|
|
72
90
|
this._token = data.token.accessToken;
|
|
73
91
|
this._expiresAt = new Date(data.token.expiresAt).getTime();
|
|
74
92
|
this._pendingRefresh = null;
|
|
75
93
|
this._refreshResolve?.();
|
|
76
94
|
this._refreshResolve = null;
|
|
95
|
+
this._refreshReject = null;
|
|
77
96
|
this.scheduleRefresh();
|
|
78
97
|
}
|
|
79
98
|
scheduleRefresh() {
|
|
80
99
|
if (this._refreshTimer) clearTimeout(this._refreshTimer);
|
|
81
100
|
if (this._expiresAt === 0) return;
|
|
82
101
|
const delay2 = Math.max(0, this._expiresAt - Date.now() - SKEW_MS);
|
|
83
|
-
this._refreshTimer = setTimeout(() => void this.requestRefresh()
|
|
102
|
+
this._refreshTimer = setTimeout(() => void this.requestRefresh().catch(() => {
|
|
103
|
+
}), delay2);
|
|
104
|
+
}
|
|
105
|
+
failRefresh(message) {
|
|
106
|
+
if (this._requestTimer) clearTimeout(this._requestTimer);
|
|
107
|
+
this._requestTimer = null;
|
|
108
|
+
this._requestId = null;
|
|
109
|
+
this._pendingRefresh = null;
|
|
110
|
+
this._refreshReject?.(new Error(message));
|
|
111
|
+
this._refreshReject = null;
|
|
112
|
+
this._refreshResolve = null;
|
|
84
113
|
}
|
|
85
114
|
};
|
|
86
115
|
|
|
@@ -743,23 +772,41 @@ var SettingsModule = class {
|
|
|
743
772
|
}
|
|
744
773
|
};
|
|
745
774
|
|
|
775
|
+
// src/transport/header-actions.ts
|
|
776
|
+
function isHeaderAction(value) {
|
|
777
|
+
if (!value || typeof value !== "object") return false;
|
|
778
|
+
const a = value;
|
|
779
|
+
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
|
+
}
|
|
781
|
+
|
|
746
782
|
// src/transport/bridge.transport.ts
|
|
747
783
|
var Bridge = class {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
_hostOrigin = null;
|
|
752
|
-
_messageHandler;
|
|
753
|
-
_destroyed = false;
|
|
754
|
-
constructor() {
|
|
755
|
-
this._initPromise = new Promise((resolve) => {
|
|
784
|
+
constructor(expectedHostOrigin) {
|
|
785
|
+
this.expectedHostOrigin = expectedHostOrigin;
|
|
786
|
+
this._initPromise = new Promise((resolve, reject) => {
|
|
756
787
|
this._initResolve = resolve;
|
|
788
|
+
this._initReject = reject;
|
|
789
|
+
});
|
|
790
|
+
void this._initPromise.catch(() => {
|
|
757
791
|
});
|
|
758
792
|
this._messageHandler = this.handleMessage.bind(this);
|
|
759
793
|
if (typeof window !== "undefined") {
|
|
760
794
|
window.addEventListener("message", this._messageHandler);
|
|
761
795
|
}
|
|
762
796
|
}
|
|
797
|
+
expectedHostOrigin;
|
|
798
|
+
listeners = /* @__PURE__ */ new Map();
|
|
799
|
+
_initResolve;
|
|
800
|
+
_initReject;
|
|
801
|
+
_initTimer;
|
|
802
|
+
_initPromise;
|
|
803
|
+
_hostOrigin = null;
|
|
804
|
+
_messageHandler;
|
|
805
|
+
_destroyed = false;
|
|
806
|
+
sessionId = null;
|
|
807
|
+
actions = /* @__PURE__ */ new Map();
|
|
808
|
+
requests = /* @__PURE__ */ new Set();
|
|
809
|
+
backendRequests = /* @__PURE__ */ new Map();
|
|
763
810
|
/** Post `verentis:ready` to the host to start the handshake */
|
|
764
811
|
sendReady(capabilities) {
|
|
765
812
|
console.info("[Verentis SDK] Sending verentis:ready to host");
|
|
@@ -772,6 +819,12 @@ var Bridge = class {
|
|
|
772
819
|
}
|
|
773
820
|
/** Wait for the host to send `verentis:init` with context and token */
|
|
774
821
|
waitForInit() {
|
|
822
|
+
if (!this._hostOrigin && !this._destroyed && !this._initTimer) {
|
|
823
|
+
this._initTimer = setTimeout(() => {
|
|
824
|
+
this._initReject(new Error("Workspace handshake timed out."));
|
|
825
|
+
this.destroy();
|
|
826
|
+
}, 2e4);
|
|
827
|
+
}
|
|
775
828
|
return this._initPromise;
|
|
776
829
|
}
|
|
777
830
|
/** Get the host origin (available after init) */
|
|
@@ -780,6 +833,7 @@ var Bridge = class {
|
|
|
780
833
|
}
|
|
781
834
|
/** Send a message to the host */
|
|
782
835
|
postToHost(message) {
|
|
836
|
+
if (this._destroyed) throw new Error("Bridge has been destroyed.");
|
|
783
837
|
if (!this._hostOrigin) {
|
|
784
838
|
throw new Error("Bridge not initialized: host origin unknown. Wait for init first.");
|
|
785
839
|
}
|
|
@@ -814,6 +868,91 @@ var Bridge = class {
|
|
|
814
868
|
setDirty(isDirty) {
|
|
815
869
|
this.postToHost({ type: "verentis:dirty", version: 1, isDirty });
|
|
816
870
|
}
|
|
871
|
+
/** The host, not the app, selects the installation, file, branch and permission ceiling. */
|
|
872
|
+
requestBackendCredential() {
|
|
873
|
+
if (this._destroyed || !this.sessionId || !this._hostOrigin)
|
|
874
|
+
return Promise.reject(new Error("An initialized workspace host is required."));
|
|
875
|
+
if (this.backendRequests.size >= 4)
|
|
876
|
+
return Promise.reject(new Error("A backend credential request is already pending."));
|
|
877
|
+
const requestId = crypto.randomUUID();
|
|
878
|
+
return new Promise((resolve, reject) => {
|
|
879
|
+
const timer = setTimeout(() => {
|
|
880
|
+
this.backendRequests.delete(requestId);
|
|
881
|
+
reject(new Error("Backend authorization timed out."));
|
|
882
|
+
}, 2e4);
|
|
883
|
+
this.backendRequests.set(requestId, { resolve, reject, timer });
|
|
884
|
+
try {
|
|
885
|
+
this.postToHost({ type: "verentis:backend:request", version: 1, requestId, sessionId: this.sessionId });
|
|
886
|
+
} catch {
|
|
887
|
+
clearTimeout(timer);
|
|
888
|
+
this.backendRequests.delete(requestId);
|
|
889
|
+
reject(new Error("The workspace host is unavailable."));
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
registerAction(descriptor, handler) {
|
|
894
|
+
if (!this.sessionId || this._destroyed) throw new Error("Header actions require an initialized workspace host.");
|
|
895
|
+
if (!isHeaderAction(descriptor)) throw new Error("Invalid header action.");
|
|
896
|
+
if (this.actions.has(descriptor.id)) throw new Error(`Action '${descriptor.id}' is already registered.`);
|
|
897
|
+
if (this.actions.size >= 16) throw new Error("At most 16 header actions may be registered.");
|
|
898
|
+
const action = { descriptor: { ...descriptor, scopes: [...descriptor.scopes] }, handler, busy: false };
|
|
899
|
+
this.actions.set(descriptor.id, action);
|
|
900
|
+
this.publishActions();
|
|
901
|
+
return {
|
|
902
|
+
update: (next) => {
|
|
903
|
+
if (this.actions.get(descriptor.id) !== action) throw new Error("Action has been disposed.");
|
|
904
|
+
if (!isHeaderAction(next) || next.id !== descriptor.id) throw new Error("Invalid header action update.");
|
|
905
|
+
action.descriptor = { ...next, scopes: [...next.scopes] };
|
|
906
|
+
this.publishActions();
|
|
907
|
+
},
|
|
908
|
+
dispose: () => {
|
|
909
|
+
if (this.actions.get(descriptor.id) !== action) return;
|
|
910
|
+
this.actions.delete(descriptor.id);
|
|
911
|
+
this.publishActions();
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
publishActions() {
|
|
916
|
+
if (!this.sessionId || this._destroyed) return;
|
|
917
|
+
this.postToHost({
|
|
918
|
+
type: "verentis:actions:set",
|
|
919
|
+
version: 1,
|
|
920
|
+
sessionId: this.sessionId,
|
|
921
|
+
actions: [...this.actions.values()].map((a) => ({ ...a.descriptor, busy: a.busy || a.descriptor.busy }))
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
async invokeAction(message) {
|
|
925
|
+
if (message.sessionId !== this.sessionId || typeof message.requestId !== "string" || message.requestId.length > 128 || !message.requestId || this.requests.has(message.requestId)) return;
|
|
926
|
+
this.requests.add(message.requestId);
|
|
927
|
+
if (this.requests.size > 1e3) this.requests.delete(this.requests.values().next().value);
|
|
928
|
+
const action = this.actions.get(message.actionId);
|
|
929
|
+
const reply = {
|
|
930
|
+
type: "verentis:action:result",
|
|
931
|
+
version: 1,
|
|
932
|
+
sessionId: message.sessionId,
|
|
933
|
+
requestId: message.requestId,
|
|
934
|
+
actionId: message.actionId
|
|
935
|
+
};
|
|
936
|
+
if (!action || action.busy || action.descriptor.busy || action.descriptor.enabled === false || action.descriptor.visible === false) {
|
|
937
|
+
this.postToHost({ ...reply, error: "Action is unavailable." });
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
action.busy = true;
|
|
941
|
+
this.publishActions();
|
|
942
|
+
try {
|
|
943
|
+
const result = await action.handler();
|
|
944
|
+
if (!this._destroyed && this.sessionId === message.sessionId) {
|
|
945
|
+
this.postToHost({ ...reply, result: result || void 0 });
|
|
946
|
+
}
|
|
947
|
+
} catch (error) {
|
|
948
|
+
if (!this._destroyed && this.sessionId === message.sessionId) {
|
|
949
|
+
this.postToHost({ ...reply, error: (error instanceof Error ? error.message : "Action failed.").slice(0, 500) });
|
|
950
|
+
}
|
|
951
|
+
} finally {
|
|
952
|
+
action.busy = false;
|
|
953
|
+
this.publishActions();
|
|
954
|
+
}
|
|
955
|
+
}
|
|
817
956
|
/** Subscribe to a specific message type from the host */
|
|
818
957
|
on(type, listener) {
|
|
819
958
|
if (!this.listeners.has(type)) {
|
|
@@ -827,7 +966,17 @@ var Bridge = class {
|
|
|
827
966
|
/** Clean up event listeners */
|
|
828
967
|
destroy() {
|
|
829
968
|
this._destroyed = true;
|
|
969
|
+
clearTimeout(this._initTimer);
|
|
970
|
+
this._initTimer = void 0;
|
|
971
|
+
if (!this._hostOrigin) this._initReject(new Error("Bridge has been destroyed."));
|
|
972
|
+
this.actions.clear();
|
|
973
|
+
this.requests.clear();
|
|
830
974
|
this.listeners.clear();
|
|
975
|
+
for (const pending of this.backendRequests.values()) {
|
|
976
|
+
clearTimeout(pending.timer);
|
|
977
|
+
pending.reject(new Error("Bridge has been destroyed."));
|
|
978
|
+
}
|
|
979
|
+
this.backendRequests.clear();
|
|
831
980
|
if (typeof window !== "undefined") {
|
|
832
981
|
window.removeEventListener("message", this._messageHandler);
|
|
833
982
|
}
|
|
@@ -835,9 +984,16 @@ var Bridge = class {
|
|
|
835
984
|
handleMessage(event) {
|
|
836
985
|
if (this._destroyed) return;
|
|
837
986
|
const data = event.data;
|
|
838
|
-
if (
|
|
987
|
+
if (event.source !== window.parent || data?.version !== 1 || typeof data?.type !== "string" || !data.type.startsWith("verentis:")) return;
|
|
988
|
+
if (this._hostOrigin && event.origin !== this._hostOrigin) return;
|
|
989
|
+
if (this.expectedHostOrigin && event.origin !== this.expectedHostOrigin) return;
|
|
839
990
|
if (data.type === "verentis:init") {
|
|
991
|
+
if (this._hostOrigin) return;
|
|
992
|
+
if (!data.context?.workspace?.id || !data.token || event.origin === "null") return;
|
|
993
|
+
clearTimeout(this._initTimer);
|
|
994
|
+
this._initTimer = void 0;
|
|
840
995
|
this._hostOrigin = event.origin;
|
|
996
|
+
this.sessionId = typeof data.sessionId === "string" ? data.sessionId : null;
|
|
841
997
|
console.info("[Verentis SDK] Received verentis:init from host", {
|
|
842
998
|
origin: event.origin,
|
|
843
999
|
workspaceId: data.context.workspace.id,
|
|
@@ -846,6 +1002,22 @@ var Bridge = class {
|
|
|
846
1002
|
const initData = data;
|
|
847
1003
|
this._initResolve({ context: initData.context, token: initData.token });
|
|
848
1004
|
}
|
|
1005
|
+
if (data.type === "verentis:backend:response") {
|
|
1006
|
+
if (!this._hostOrigin || data.sessionId !== this.sessionId) return;
|
|
1007
|
+
const pending = this.backendRequests.get(data.requestId);
|
|
1008
|
+
if (!pending) return;
|
|
1009
|
+
clearTimeout(pending.timer);
|
|
1010
|
+
this.backendRequests.delete(data.requestId);
|
|
1011
|
+
const value = data.credential;
|
|
1012
|
+
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())
|
|
1013
|
+
pending.reject(new Error("Backend authorization was denied or is not configured."));
|
|
1014
|
+
else pending.resolve(value);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
if (data.type === "verentis:action:invoke") {
|
|
1018
|
+
void this.invokeAction(data);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
849
1021
|
const typeListeners = this.listeners.get(data.type);
|
|
850
1022
|
if (typeListeners) {
|
|
851
1023
|
for (const listener of typeListeners) {
|
|
@@ -1027,7 +1199,7 @@ var VerentisClient = class {
|
|
|
1027
1199
|
this.bridge.sendReady();
|
|
1028
1200
|
const { context, token } = await this.bridge.waitForInit();
|
|
1029
1201
|
const hostOrigin = this.bridge.hostOrigin;
|
|
1030
|
-
|
|
1202
|
+
bridgeAuth.setHostOrigin(hostOrigin);
|
|
1031
1203
|
bridgeAuth.setToken(token);
|
|
1032
1204
|
this.context.setFromAppContext(context);
|
|
1033
1205
|
const http = new HttpTransport(
|
|
@@ -1099,6 +1271,6 @@ function createVerentisClient(options) {
|
|
|
1099
1271
|
return new VerentisClient(options);
|
|
1100
1272
|
}
|
|
1101
1273
|
|
|
1102
|
-
export { ApiKeyAuth, Bridge, BridgeAuth, ContextModule, EnginesModule, ExecutionsModule, FilesModule, HttpTransport, InstallationsModule, ResourcesModule, SettingsModule, TokenAuth, VerentisClient, createVerentisClient, findBestMatch, isTerminalStatus, matchMimeType };
|
|
1274
|
+
export { ApiKeyAuth, Bridge, BridgeAuth, ContextModule, EnginesModule, ExecutionsModule, FilesModule, HttpTransport, InstallationsModule, ResourcesModule, SettingsModule, TokenAuth, VerentisClient, createVerentisClient, findBestMatch, isHeaderAction, isTerminalStatus, matchMimeType };
|
|
1103
1275
|
//# sourceMappingURL=index.js.map
|
|
1104
1276
|
//# sourceMappingURL=index.js.map
|