@verentis/sdk 0.1.1 → 0.2.2
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 +85 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -3
- package/dist/index.d.ts +58 -3
- package/dist/index.js +85 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,53 @@ npm install @verentis/sdk
|
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
|
+
### Hosted header actions (SDK 0.2)
|
|
14
|
+
|
|
15
|
+
Applications register buttons after `await client.whenReady()`. The workspace owns their
|
|
16
|
+
presentation; handlers execute inside the application with its existing delegated token.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
const save = client.bridge!.registerAction({
|
|
20
|
+
id: 'document.save',
|
|
21
|
+
label: 'Save',
|
|
22
|
+
icon: 'lucide:save',
|
|
23
|
+
variant: 'default',
|
|
24
|
+
scopes: ['node.file.read', 'node.node.create', 'node.node.update', 'node.journal.create'],
|
|
25
|
+
enabled: false,
|
|
26
|
+
}, async () => {
|
|
27
|
+
await persistDocument()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Pass the complete descriptor when document state changes.
|
|
31
|
+
save.update({ id: 'document.save', label: 'Save', scopes: [
|
|
32
|
+
'node.file.read', 'node.node.create', 'node.node.update', 'node.journal.create',
|
|
33
|
+
], enabled: true })
|
|
34
|
+
save.dispose()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use stable app-local IDs, up to 16 actions, Lucide icon identifiers, and explicit `scopes`
|
|
38
|
+
(`[]` for client-only operations). `enabled`, `visible`, `busy`, and `disabledReason` communicate
|
|
39
|
+
state without serializing handlers. The host checks required scopes against the delegated grant;
|
|
40
|
+
registration never expands permissions. Throw on failure so the host can display it.
|
|
41
|
+
|
|
42
|
+
For clipboard actions, set `kind: 'clipboard'` and return `{ clipboardText: source }`. The host starts
|
|
43
|
+
the clipboard write in the user's click gesture while awaiting the text, preserving browser
|
|
44
|
+
user-activation requirements. For operations that navigate after completion, return
|
|
45
|
+
`{ navigate: { path: '/applications', newTab: false } }` instead of navigating before the result.
|
|
46
|
+
|
|
47
|
+
The protocol uses session-bound `verentis:actions:set`, `verentis:action:invoke`, and
|
|
48
|
+
`verentis:action:result` messages. Reload/navigation disposes pending work; an unanswered invocation
|
|
49
|
+
times out as **outcome unknown**, never an automatic retry. App handlers must not report successful
|
|
50
|
+
completion before their mutation finishes.
|
|
51
|
+
|
|
52
|
+
Migrated actions are hosted only: there is no legacy-host or standalone-toolbar fallback.
|
|
53
|
+
Fullscreen apps can hide the host header only when they register no actions. Keep specialized
|
|
54
|
+
controls (zoom, editing modes, contextual forms) within the app.
|
|
55
|
+
|
|
56
|
+
Release the 0.2 SDK before deploying apps that require `^0.2.0`, together with the matching host.
|
|
57
|
+
Local sibling-repository development can link the built SDK; published applications must resolve
|
|
58
|
+
the released SDK from npm.
|
|
59
|
+
|
|
13
60
|
### Standalone mode (local development)
|
|
14
61
|
|
|
15
62
|
```ts
|
package/dist/index.cjs
CHANGED
|
@@ -745,6 +745,13 @@ var SettingsModule = class {
|
|
|
745
745
|
}
|
|
746
746
|
};
|
|
747
747
|
|
|
748
|
+
// src/transport/header-actions.ts
|
|
749
|
+
function isHeaderAction(value) {
|
|
750
|
+
if (!value || typeof value !== "object") return false;
|
|
751
|
+
const a = value;
|
|
752
|
+
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");
|
|
753
|
+
}
|
|
754
|
+
|
|
748
755
|
// src/transport/bridge.transport.ts
|
|
749
756
|
var Bridge = class {
|
|
750
757
|
listeners = /* @__PURE__ */ new Map();
|
|
@@ -753,6 +760,9 @@ var Bridge = class {
|
|
|
753
760
|
_hostOrigin = null;
|
|
754
761
|
_messageHandler;
|
|
755
762
|
_destroyed = false;
|
|
763
|
+
sessionId = null;
|
|
764
|
+
actions = /* @__PURE__ */ new Map();
|
|
765
|
+
requests = /* @__PURE__ */ new Set();
|
|
756
766
|
constructor() {
|
|
757
767
|
this._initPromise = new Promise((resolve) => {
|
|
758
768
|
this._initResolve = resolve;
|
|
@@ -782,6 +792,7 @@ var Bridge = class {
|
|
|
782
792
|
}
|
|
783
793
|
/** Send a message to the host */
|
|
784
794
|
postToHost(message) {
|
|
795
|
+
if (this._destroyed) throw new Error("Bridge has been destroyed.");
|
|
785
796
|
if (!this._hostOrigin) {
|
|
786
797
|
throw new Error("Bridge not initialized: host origin unknown. Wait for init first.");
|
|
787
798
|
}
|
|
@@ -816,6 +827,69 @@ var Bridge = class {
|
|
|
816
827
|
setDirty(isDirty) {
|
|
817
828
|
this.postToHost({ type: "verentis:dirty", version: 1, isDirty });
|
|
818
829
|
}
|
|
830
|
+
registerAction(descriptor, handler) {
|
|
831
|
+
if (!this.sessionId || this._destroyed) throw new Error("Header actions require an initialized workspace host.");
|
|
832
|
+
if (!isHeaderAction(descriptor)) throw new Error("Invalid header action.");
|
|
833
|
+
if (this.actions.has(descriptor.id)) throw new Error(`Action '${descriptor.id}' is already registered.`);
|
|
834
|
+
if (this.actions.size >= 16) throw new Error("At most 16 header actions may be registered.");
|
|
835
|
+
const action = { descriptor: { ...descriptor, scopes: [...descriptor.scopes] }, handler, busy: false };
|
|
836
|
+
this.actions.set(descriptor.id, action);
|
|
837
|
+
this.publishActions();
|
|
838
|
+
return {
|
|
839
|
+
update: (next) => {
|
|
840
|
+
if (this.actions.get(descriptor.id) !== action) throw new Error("Action has been disposed.");
|
|
841
|
+
if (!isHeaderAction(next) || next.id !== descriptor.id) throw new Error("Invalid header action update.");
|
|
842
|
+
action.descriptor = { ...next, scopes: [...next.scopes] };
|
|
843
|
+
this.publishActions();
|
|
844
|
+
},
|
|
845
|
+
dispose: () => {
|
|
846
|
+
if (this.actions.get(descriptor.id) !== action) return;
|
|
847
|
+
this.actions.delete(descriptor.id);
|
|
848
|
+
this.publishActions();
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
publishActions() {
|
|
853
|
+
if (!this.sessionId || this._destroyed) return;
|
|
854
|
+
this.postToHost({
|
|
855
|
+
type: "verentis:actions:set",
|
|
856
|
+
version: 1,
|
|
857
|
+
sessionId: this.sessionId,
|
|
858
|
+
actions: [...this.actions.values()].map((a) => ({ ...a.descriptor, busy: a.busy || a.descriptor.busy }))
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
async invokeAction(message) {
|
|
862
|
+
if (message.sessionId !== this.sessionId || typeof message.requestId !== "string" || message.requestId.length > 128 || !message.requestId || this.requests.has(message.requestId)) return;
|
|
863
|
+
this.requests.add(message.requestId);
|
|
864
|
+
if (this.requests.size > 1e3) this.requests.delete(this.requests.values().next().value);
|
|
865
|
+
const action = this.actions.get(message.actionId);
|
|
866
|
+
const reply = {
|
|
867
|
+
type: "verentis:action:result",
|
|
868
|
+
version: 1,
|
|
869
|
+
sessionId: message.sessionId,
|
|
870
|
+
requestId: message.requestId,
|
|
871
|
+
actionId: message.actionId
|
|
872
|
+
};
|
|
873
|
+
if (!action || action.busy || action.descriptor.busy || action.descriptor.enabled === false || action.descriptor.visible === false) {
|
|
874
|
+
this.postToHost({ ...reply, error: "Action is unavailable." });
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
action.busy = true;
|
|
878
|
+
this.publishActions();
|
|
879
|
+
try {
|
|
880
|
+
const result = await action.handler();
|
|
881
|
+
if (!this._destroyed && this.sessionId === message.sessionId) {
|
|
882
|
+
this.postToHost({ ...reply, result: result || void 0 });
|
|
883
|
+
}
|
|
884
|
+
} catch (error) {
|
|
885
|
+
if (!this._destroyed && this.sessionId === message.sessionId) {
|
|
886
|
+
this.postToHost({ ...reply, error: (error instanceof Error ? error.message : "Action failed.").slice(0, 500) });
|
|
887
|
+
}
|
|
888
|
+
} finally {
|
|
889
|
+
action.busy = false;
|
|
890
|
+
this.publishActions();
|
|
891
|
+
}
|
|
892
|
+
}
|
|
819
893
|
/** Subscribe to a specific message type from the host */
|
|
820
894
|
on(type, listener) {
|
|
821
895
|
if (!this.listeners.has(type)) {
|
|
@@ -829,6 +903,8 @@ var Bridge = class {
|
|
|
829
903
|
/** Clean up event listeners */
|
|
830
904
|
destroy() {
|
|
831
905
|
this._destroyed = true;
|
|
906
|
+
this.actions.clear();
|
|
907
|
+
this.requests.clear();
|
|
832
908
|
this.listeners.clear();
|
|
833
909
|
if (typeof window !== "undefined") {
|
|
834
910
|
window.removeEventListener("message", this._messageHandler);
|
|
@@ -837,9 +913,12 @@ var Bridge = class {
|
|
|
837
913
|
handleMessage(event) {
|
|
838
914
|
if (this._destroyed) return;
|
|
839
915
|
const data = event.data;
|
|
840
|
-
if (
|
|
916
|
+
if (event.source !== window.parent || data?.version !== 1 || typeof data?.type !== "string" || !data.type.startsWith("verentis:")) return;
|
|
917
|
+
if (this._hostOrigin && event.origin !== this._hostOrigin) return;
|
|
841
918
|
if (data.type === "verentis:init") {
|
|
919
|
+
if (this._hostOrigin) return;
|
|
842
920
|
this._hostOrigin = event.origin;
|
|
921
|
+
this.sessionId = typeof data.sessionId === "string" ? data.sessionId : null;
|
|
843
922
|
console.info("[Verentis SDK] Received verentis:init from host", {
|
|
844
923
|
origin: event.origin,
|
|
845
924
|
workspaceId: data.context.workspace.id,
|
|
@@ -848,6 +927,10 @@ var Bridge = class {
|
|
|
848
927
|
const initData = data;
|
|
849
928
|
this._initResolve({ context: initData.context, token: initData.token });
|
|
850
929
|
}
|
|
930
|
+
if (data.type === "verentis:action:invoke") {
|
|
931
|
+
void this.invokeAction(data);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
851
934
|
const typeListeners = this.listeners.get(data.type);
|
|
852
935
|
if (typeListeners) {
|
|
853
936
|
for (const listener of typeListeners) {
|
|
@@ -1116,6 +1199,7 @@ exports.TokenAuth = TokenAuth;
|
|
|
1116
1199
|
exports.VerentisClient = VerentisClient;
|
|
1117
1200
|
exports.createVerentisClient = createVerentisClient;
|
|
1118
1201
|
exports.findBestMatch = findBestMatch;
|
|
1202
|
+
exports.isHeaderAction = isHeaderAction;
|
|
1119
1203
|
exports.isTerminalStatus = isTerminalStatus;
|
|
1120
1204
|
exports.matchMimeType = matchMimeType;
|
|
1121
1205
|
//# sourceMappingURL=index.cjs.map
|