@hesohq/sdk 0.1.2-dev.21
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/adapters/ai-sdk.d.ts +43 -0
- package/dist/adapters/ai-sdk.d.ts.map +1 -0
- package/dist/adapters/ai-sdk.js +86 -0
- package/dist/adapters/ai-sdk.js.map +1 -0
- package/dist/adapters/mastra.d.ts +34 -0
- package/dist/adapters/mastra.d.ts.map +1 -0
- package/dist/adapters/mastra.js +84 -0
- package/dist/adapters/mastra.js.map +1 -0
- package/dist/capture.d.ts +230 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +591 -0
- package/dist/capture.js.map +1 -0
- package/dist/cloud.d.ts +188 -0
- package/dist/cloud.d.ts.map +1 -0
- package/dist/cloud.js +196 -0
- package/dist/cloud.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +16 -0
- package/dist/config.js.map +1 -0
- package/dist/delegation.d.ts +68 -0
- package/dist/delegation.d.ts.map +1 -0
- package/dist/delegation.js +139 -0
- package/dist/delegation.js.map +1 -0
- package/dist/gating.d.ts +33 -0
- package/dist/gating.d.ts.map +1 -0
- package/dist/gating.js +96 -0
- package/dist/gating.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy.d.ts +30 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +59 -0
- package/dist/proxy.js.map +1 -0
- package/dist/runtime.d.ts +30 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +70 -0
- package/dist/runtime.js.map +1 -0
- package/dist/types.d.ts +259 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +38 -0
package/dist/cloud.d.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { ActionContent, ActionReceipt } from './types.js';
|
|
2
|
+
/** The org's pinned policy bundle (heso-backend ``PolicyPullResponse``). */
|
|
3
|
+
export interface PolicyPullResult {
|
|
4
|
+
status: 'policy' | 'up_to_date';
|
|
5
|
+
policyId: string;
|
|
6
|
+
policyHash: string;
|
|
7
|
+
/** The policy TOML the engine loads. Empty string == the deny-everything pin. */
|
|
8
|
+
toml: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Pull the org's current pinned policy. The org is resolved from the api-key —
|
|
12
|
+
* there is no team id. An empty ``toml`` is the fail-safe deny pin: the engine
|
|
13
|
+
* routes every action to a human when the org has no resolvable policy.
|
|
14
|
+
*/
|
|
15
|
+
export declare function pullPolicy(): Promise<PolicyPullResult>;
|
|
16
|
+
/** The cloud mirror entry for a pushed receipt (heso-backend ``ReceiptPushResponse``). */
|
|
17
|
+
export interface ReceiptPushResult {
|
|
18
|
+
/** ``appended`` (new mirror row), ``duplicate`` (idempotent re-push), or
|
|
19
|
+
* ``quota_exceeded`` (the monthly mirror soft-cap; the local chain is unaffected). */
|
|
20
|
+
status: 'appended' | 'duplicate' | 'quota_exceeded';
|
|
21
|
+
/** Echoes the receipt's own ``action_hash``. */
|
|
22
|
+
entryHash: string;
|
|
23
|
+
/** The mirror's per-org append position (0 when no row was written). */
|
|
24
|
+
seq: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Push one signed receipt to the cloud outbox. The server recomputes the content
|
|
28
|
+
* hash and rejects a tampered or malformed body before mirroring it.
|
|
29
|
+
*
|
|
30
|
+
* ``supersedesActionHash`` finalizes an approval: the freshly assembled L1 receipt
|
|
31
|
+
* supersedes the suspended L0 entry it was built from (its ``action_hash``). The
|
|
32
|
+
* server replaces that suspended row with the L1 (M5: only suspended/L0 -> L1).
|
|
33
|
+
*/
|
|
34
|
+
export declare function pushReceipt(receipt: ActionReceipt, supersedesActionHash?: string): Promise<ReceiptPushResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Push a batch of receipts. There is no server-side batch route — this loops over
|
|
37
|
+
* ``POST /v1/receipts`` in order, preserving each receipt's outbox result.
|
|
38
|
+
*/
|
|
39
|
+
export declare function pushReceipts(receipts: ActionReceipt[]): Promise<ReceiptPushResult[]>;
|
|
40
|
+
/** The derived state of an approval (heso-backend ``ApprovalView``). */
|
|
41
|
+
export interface ApprovalView {
|
|
42
|
+
approvalId: string;
|
|
43
|
+
actionHash: string;
|
|
44
|
+
status: 'pending' | 'approved' | 'rejected';
|
|
45
|
+
/** The m-of-n requirement. */
|
|
46
|
+
threshold: number;
|
|
47
|
+
/** Distinct verified approvers recorded so far. */
|
|
48
|
+
approvedCount: number;
|
|
49
|
+
resolvedAt: string | null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Poll an approval by its ``action_hash``. Approvals are keyed by the action they
|
|
53
|
+
* gate, not by an opaque approval id. Throws if no approval exists for the hash.
|
|
54
|
+
*/
|
|
55
|
+
export declare function pollApproval(actionHash: string): Promise<ApprovalView>;
|
|
56
|
+
/**
|
|
57
|
+
* The parts the cloud relays for finalizing an approved gate into an L1 receipt.
|
|
58
|
+
* The cloud holds NO signing key (thin-cloud): it stores the approver's verbatim
|
|
59
|
+
* signed record + co-signature and the byte-exact operator-stamped suspended
|
|
60
|
+
* content, and the SDK reassembles the L1 in-core off the IDENTICAL body.
|
|
61
|
+
*/
|
|
62
|
+
export interface L1Parts {
|
|
63
|
+
/** The approver's signed decision record, as the EXACT verbatim JSON TEXT the cloud
|
|
64
|
+
* stored (M2 — never parsed/reserialized; canonicalization is the Rust moat). The SDK
|
|
65
|
+
* threads this string STRAIGHT into the native assemble and only ``JSON.parse``s it
|
|
66
|
+
* read-only for the ``decision`` gate. ``decision`` is asserted ``approved`` before
|
|
67
|
+
* any assemble. */
|
|
68
|
+
record: string;
|
|
69
|
+
/** The approver's raw Ed25519 public key, base64 — the co-signature's verify key. */
|
|
70
|
+
approverPubkeyB64: string;
|
|
71
|
+
/** The approver's detached co-signature over the L1 co-sign payload, base64. */
|
|
72
|
+
coSigB64: string;
|
|
73
|
+
/** The operator-stamped suspended receipt CONTENT the L1 is built from — the same
|
|
74
|
+
* bytes the browser co-signed and the SDK mirror-pushed at suspend time. */
|
|
75
|
+
suspendedContent: ActionContent;
|
|
76
|
+
/** An OPTIONAL relayed RFC-3161 trusted-time anchor, carried VERBATIM and treated
|
|
77
|
+
* as OPAQUE bytes — the SDK NEVER parses or canonicalizes it; it is threaded
|
|
78
|
+
* straight into the native assemble (where the operator signature covers it).
|
|
79
|
+
* Absent ⇒ the unchanged anchorless path (the L1 golden never moves). */
|
|
80
|
+
timeAnchor?: unknown;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Fetch the L1 relay parts for an approved action — a dedicated GET kept OFF the
|
|
84
|
+
* 2s poll loop (``pollApproval``/``waitForApproval`` stay lean; only this call,
|
|
85
|
+
* made once after the status flips to ``approved``, pulls the heavier parts).
|
|
86
|
+
*
|
|
87
|
+
* It hits the SAME unified ``/assembly`` surface as {@link getQuorumParts} — the backend
|
|
88
|
+
* returns ONE shape (a ``legs`` list + threshold/roster/suspended_content). An L1 is
|
|
89
|
+
* exactly ONE approver leg, so this reads ``legs[0]``. Throws if no resolved parts
|
|
90
|
+
* exist for the hash (the cloud 404s an unknown action; an empty ``legs`` list ⇒ no
|
|
91
|
+
* co-sign relay has landed yet).
|
|
92
|
+
*/
|
|
93
|
+
export declare function getL1Parts(actionHash: string): Promise<L1Parts>;
|
|
94
|
+
/** One approver's verbatim relayed leg of a multi-approver QUORUM assembly. The
|
|
95
|
+
* cloud holds NO key: it stored each approver's signed record + detached co-sig
|
|
96
|
+
* VERBATIM and returns them unchanged, and the SDK reassembles the quorum in-core off
|
|
97
|
+
* the IDENTICAL bytes (canonicalization stays the Rust moat — the browser already
|
|
98
|
+
* produced each co-sig over ``quorumCosignPayload`` bytes). */
|
|
99
|
+
export interface QuorumLeg {
|
|
100
|
+
/** This approver's signed decision record, as the EXACT verbatim JSON TEXT the cloud
|
|
101
|
+
* stored (M2 — never parsed/reserialized; canonicalization is the Rust moat). Threaded
|
|
102
|
+
* STRAIGHT into the native assemble; only ``JSON.parse``d read-only for the decision
|
|
103
|
+
* gate. Every leg's ``decision`` is asserted ``approved`` before any quorum is assembled. */
|
|
104
|
+
record: string;
|
|
105
|
+
/** This approver's raw Ed25519 public key, base64 — the co-signature's verify key. */
|
|
106
|
+
approverPubkeyB64: string;
|
|
107
|
+
/** This approver's detached co-signature over the quorum per-approver payload, base64. */
|
|
108
|
+
coSigB64: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The parts the cloud relays for finalizing an approved k-of-n gate into a QUORUM
|
|
112
|
+
* receipt. The multi-approver sibling of {@link L1Parts}: a LIST of ``k`` approver legs
|
|
113
|
+
* plus the gate's ``threshold`` + sorted ``roster`` and the byte-exact operator-stamped
|
|
114
|
+
* ``suspendedContent`` the legs were co-signed against. Stored/relayed VERBATIM —
|
|
115
|
+
* the cloud never re-canonicalizes (it holds no signing key). The assembled receipt
|
|
116
|
+
* embeds ``L1`` WITH a ``multi_approval`` block (not a higher level).
|
|
117
|
+
*/
|
|
118
|
+
export interface QuorumParts {
|
|
119
|
+
/** The ``k`` relayed approver legs (one verbatim record + co-sig per approver). */
|
|
120
|
+
legs: QuorumLeg[];
|
|
121
|
+
/** The k-of-n approval threshold the gate requires (echoed from the operator-
|
|
122
|
+
* signed suspended content; the cloud rejects a disagreeing stored threshold). */
|
|
123
|
+
threshold: number;
|
|
124
|
+
/** The n-roster: base64 approver public keys, sorted ascending — the eligible set. */
|
|
125
|
+
roster: string[];
|
|
126
|
+
/** The operator-stamped suspended receipt CONTENT the quorum is built from — the same
|
|
127
|
+
* bytes each approver co-signed and the SDK mirror-pushed at suspend time. */
|
|
128
|
+
suspendedContent: ActionContent;
|
|
129
|
+
/** An OPTIONAL relayed RFC-3161 trusted-time anchor, carried VERBATIM and treated
|
|
130
|
+
* as OPAQUE bytes — the SDK NEVER parses it; absent ⇒ the anchorless path. */
|
|
131
|
+
timeAnchor?: unknown;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Fetch the QUORUM relay parts for an approved k-of-n action — the multi-approver
|
|
135
|
+
* sibling of {@link getL1Parts}, kept OFF the lean poll loop. Both read the SAME
|
|
136
|
+
* unified ``/assembly`` surface; this consumes the WHOLE ``legs`` list (k approver
|
|
137
|
+
* legs) plus the operator-signed threshold + roster. Relays the legs VERBATIM; the
|
|
138
|
+
* SDK reassembles the quorum in-core ({@link finalizeQuorum}). Throws if no approval
|
|
139
|
+
* exists for the hash (404); an empty ``legs`` list ⇒ no co-sign has landed yet.
|
|
140
|
+
*/
|
|
141
|
+
export declare function getQuorumParts(actionHash: string): Promise<QuorumParts>;
|
|
142
|
+
/** An ``ApprovalView`` enriched with the relayed L1 parts (present only when the
|
|
143
|
+
* approval resolved ``approved``; ``null`` on ``rejected``). */
|
|
144
|
+
export interface ResolvedApproval extends ApprovalView {
|
|
145
|
+
/** The L1 parts, fetched once on approval; ``null`` when the approval was rejected. */
|
|
146
|
+
parts: L1Parts | null;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Poll until an approval resolves (``approved``/``rejected``) or the timeout
|
|
150
|
+
* elapses. On ``approved`` it makes ONE extra GET ({@link getL1Parts}) so the
|
|
151
|
+
* caller can finalize without re-polling — the poll loop itself stays lean.
|
|
152
|
+
* Throws if the timeout elapses while the approval is still ``pending``.
|
|
153
|
+
*/
|
|
154
|
+
export declare function waitForApproval(actionHash: string, options?: {
|
|
155
|
+
pollIntervalMs?: number;
|
|
156
|
+
timeoutMs?: number;
|
|
157
|
+
}): Promise<ResolvedApproval>;
|
|
158
|
+
/** The body of ``POST /v1/approvals/{action_hash}/submit-token`` (``SubmitTokenRequest``). */
|
|
159
|
+
export interface SubmitTokenInput {
|
|
160
|
+
/** The raw approval-token wire bytes, base64. */
|
|
161
|
+
tokenB64: string;
|
|
162
|
+
/** The receipt ``content`` JSON the token signs over (the cloud re-derives the
|
|
163
|
+
* canonical bytes from it; never send a client-supplied canonical form). */
|
|
164
|
+
actionContent: ActionReceipt['content'];
|
|
165
|
+
/** The scope the token must cover — the gated approval rule id (the cloud binds
|
|
166
|
+
* required_scope server-side to the stored rule_id and the wheel does strict
|
|
167
|
+
* byte-equality, so this must equal that rule id, not `${verb}:${decisionPath}`). */
|
|
168
|
+
requiredScope?: string;
|
|
169
|
+
/** The human's decision. This rides INSIDE the token's signed bytes now (one
|
|
170
|
+
* decision byte folded into the Ed25519 payload), so the cloud records the
|
|
171
|
+
* wheel-verified decision and rejects (403 out-of-decision) any token whose
|
|
172
|
+
* signed verdict disagrees with this field. Tokens minted by the browser
|
|
173
|
+
* (`approval-token.ts`); this field declares the human's intent for the edge. */
|
|
174
|
+
decision?: 'approved' | 'rejected';
|
|
175
|
+
/** A one-line reason, persisted server-side. */
|
|
176
|
+
reason?: string;
|
|
177
|
+
/** Client-approval (delegation) path: the operator delegation envelope, base64. */
|
|
178
|
+
delegationEnvelopeB64?: string;
|
|
179
|
+
/** Client-approval (delegation) path: the one-time gate bearer. */
|
|
180
|
+
clientBearer?: string;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Submit a human-signed approval token for an open approval, keyed by the action
|
|
184
|
+
* it gates. The cloud verifies the token against the org's registered approver
|
|
185
|
+
* keys and records it as a vote; the response is the updated ``ApprovalView``.
|
|
186
|
+
*/
|
|
187
|
+
export declare function submitApprovalToken(actionHash: string, input: SubmitTokenInput): Promise<ApprovalView>;
|
|
188
|
+
//# sourceMappingURL=cloud.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"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"}
|
package/dist/cloud.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Cloud client — policy pull, receipt outbox push, approval poll + token submit.
|
|
3
|
+
//
|
|
4
|
+
// Tenancy is resolved from the api-key at the edge (api-key -> org); there is no
|
|
5
|
+
// "teams" path segment. Every shape here mirrors the heso-backend routers:
|
|
6
|
+
// - GET /v1/policy/pull (policy.py -> PolicyPullResponse)
|
|
7
|
+
// - POST /v1/receipts (receipts.py -> ReceiptPushResponse)
|
|
8
|
+
// - GET /v1/approvals/{action_hash} (approvals.py -> ApprovalView)
|
|
9
|
+
// - POST /v1/approvals/{action_hash}/submit-token (approvals.py -> ApprovalView)
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.pullPolicy = pullPolicy;
|
|
12
|
+
exports.pushReceipt = pushReceipt;
|
|
13
|
+
exports.pushReceipts = pushReceipts;
|
|
14
|
+
exports.pollApproval = pollApproval;
|
|
15
|
+
exports.getL1Parts = getL1Parts;
|
|
16
|
+
exports.getQuorumParts = getQuorumParts;
|
|
17
|
+
exports.waitForApproval = waitForApproval;
|
|
18
|
+
exports.submitApprovalToken = submitApprovalToken;
|
|
19
|
+
const config_js_1 = require("./config.js");
|
|
20
|
+
// ─── Internal fetch helper ────────────────────────────────────────────────────
|
|
21
|
+
async function apiFetch(path, init = {}) {
|
|
22
|
+
const { apiKey, endpoint } = (0, config_js_1.getConfig)();
|
|
23
|
+
const url = `${endpoint.replace(/\/$/, '')}${path}`;
|
|
24
|
+
const res = await fetch(url, {
|
|
25
|
+
...init,
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
// The SDK plane authenticates with an api-key; the org is resolved from it.
|
|
29
|
+
'x-api-key': apiKey,
|
|
30
|
+
...(init.headers ?? {}),
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
const body = await res.text().catch(() => '');
|
|
35
|
+
throw new Error(`@hesohq/sdk: ${res.status} ${res.statusText} — ${path}\n${body}`);
|
|
36
|
+
}
|
|
37
|
+
return res.json();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Pull the org's current pinned policy. The org is resolved from the api-key —
|
|
41
|
+
* there is no team id. An empty ``toml`` is the fail-safe deny pin: the engine
|
|
42
|
+
* routes every action to a human when the org has no resolvable policy.
|
|
43
|
+
*/
|
|
44
|
+
async function pullPolicy() {
|
|
45
|
+
const wire = await apiFetch('/v1/policy/pull');
|
|
46
|
+
return {
|
|
47
|
+
status: wire.status,
|
|
48
|
+
policyId: wire.policy_id,
|
|
49
|
+
policyHash: wire.policy_hash,
|
|
50
|
+
toml: wire.toml,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Push one signed receipt to the cloud outbox. The server recomputes the content
|
|
55
|
+
* hash and rejects a tampered or malformed body before mirroring it.
|
|
56
|
+
*
|
|
57
|
+
* ``supersedesActionHash`` finalizes an approval: the freshly assembled L1 receipt
|
|
58
|
+
* supersedes the suspended L0 entry it was built from (its ``action_hash``). The
|
|
59
|
+
* server replaces that suspended row with the L1 (M5: only suspended/L0 -> L1).
|
|
60
|
+
*/
|
|
61
|
+
async function pushReceipt(receipt, supersedesActionHash) {
|
|
62
|
+
const body = { receipt };
|
|
63
|
+
if (supersedesActionHash !== undefined) {
|
|
64
|
+
body.supersedes_action_hash = supersedesActionHash;
|
|
65
|
+
}
|
|
66
|
+
const wire = await apiFetch('/v1/receipts', {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
body: JSON.stringify(body),
|
|
69
|
+
});
|
|
70
|
+
return { status: wire.status, entryHash: wire.entry_hash, seq: wire.seq };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Push a batch of receipts. There is no server-side batch route — this loops over
|
|
74
|
+
* ``POST /v1/receipts`` in order, preserving each receipt's outbox result.
|
|
75
|
+
*/
|
|
76
|
+
async function pushReceipts(receipts) {
|
|
77
|
+
const results = [];
|
|
78
|
+
for (const receipt of receipts) {
|
|
79
|
+
results.push(await pushReceipt(receipt));
|
|
80
|
+
}
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
function mapApprovalView(wire) {
|
|
84
|
+
return {
|
|
85
|
+
approvalId: wire.approval_id,
|
|
86
|
+
actionHash: wire.action_hash,
|
|
87
|
+
status: wire.status,
|
|
88
|
+
threshold: wire.threshold,
|
|
89
|
+
approvedCount: wire.approved_count,
|
|
90
|
+
resolvedAt: wire.resolved_at ?? null,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Poll an approval by its ``action_hash``. Approvals are keyed by the action they
|
|
95
|
+
* gate, not by an opaque approval id. Throws if no approval exists for the hash.
|
|
96
|
+
*/
|
|
97
|
+
async function pollApproval(actionHash) {
|
|
98
|
+
const wire = await apiFetch(`/v1/approvals/${encodeURIComponent(actionHash)}`);
|
|
99
|
+
return mapApprovalView(wire);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Fetch the L1 relay parts for an approved action — a dedicated GET kept OFF the
|
|
103
|
+
* 2s poll loop (``pollApproval``/``waitForApproval`` stay lean; only this call,
|
|
104
|
+
* made once after the status flips to ``approved``, pulls the heavier parts).
|
|
105
|
+
*
|
|
106
|
+
* It hits the SAME unified ``/assembly`` surface as {@link getQuorumParts} — the backend
|
|
107
|
+
* returns ONE shape (a ``legs`` list + threshold/roster/suspended_content). An L1 is
|
|
108
|
+
* exactly ONE approver leg, so this reads ``legs[0]``. Throws if no resolved parts
|
|
109
|
+
* exist for the hash (the cloud 404s an unknown action; an empty ``legs`` list ⇒ no
|
|
110
|
+
* co-sign relay has landed yet).
|
|
111
|
+
*/
|
|
112
|
+
async function getL1Parts(actionHash) {
|
|
113
|
+
const wire = await apiFetch(`/v1/approvals/${encodeURIComponent(actionHash)}/assembly`);
|
|
114
|
+
const leg = wire.legs[0];
|
|
115
|
+
if (leg === undefined) {
|
|
116
|
+
throw new Error(`@hesohq/sdk: no L1 co-sign parts relayed yet for ${actionHash} (assembly legs empty)`);
|
|
117
|
+
}
|
|
118
|
+
const parts = {
|
|
119
|
+
record: leg.record,
|
|
120
|
+
approverPubkeyB64: leg.approver_pubkey_b64,
|
|
121
|
+
coSigB64: leg.co_sig_b64,
|
|
122
|
+
suspendedContent: wire.suspended_content,
|
|
123
|
+
};
|
|
124
|
+
// Carry the anchor only when the cloud relayed one — absent ⇒ anchorless.
|
|
125
|
+
if (wire.time_anchor !== undefined && wire.time_anchor !== null) {
|
|
126
|
+
parts.timeAnchor = wire.time_anchor;
|
|
127
|
+
}
|
|
128
|
+
return parts;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Fetch the QUORUM relay parts for an approved k-of-n action — the multi-approver
|
|
132
|
+
* sibling of {@link getL1Parts}, kept OFF the lean poll loop. Both read the SAME
|
|
133
|
+
* unified ``/assembly`` surface; this consumes the WHOLE ``legs`` list (k approver
|
|
134
|
+
* legs) plus the operator-signed threshold + roster. Relays the legs VERBATIM; the
|
|
135
|
+
* SDK reassembles the quorum in-core ({@link finalizeQuorum}). Throws if no approval
|
|
136
|
+
* exists for the hash (404); an empty ``legs`` list ⇒ no co-sign has landed yet.
|
|
137
|
+
*/
|
|
138
|
+
async function getQuorumParts(actionHash) {
|
|
139
|
+
const wire = await apiFetch(`/v1/approvals/${encodeURIComponent(actionHash)}/assembly`);
|
|
140
|
+
const parts = {
|
|
141
|
+
legs: wire.legs.map((leg) => ({
|
|
142
|
+
record: leg.record,
|
|
143
|
+
approverPubkeyB64: leg.approver_pubkey_b64,
|
|
144
|
+
coSigB64: leg.co_sig_b64,
|
|
145
|
+
})),
|
|
146
|
+
threshold: wire.threshold,
|
|
147
|
+
roster: wire.roster,
|
|
148
|
+
suspendedContent: wire.suspended_content,
|
|
149
|
+
};
|
|
150
|
+
if (wire.time_anchor !== undefined && wire.time_anchor !== null) {
|
|
151
|
+
parts.timeAnchor = wire.time_anchor;
|
|
152
|
+
}
|
|
153
|
+
return parts;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Poll until an approval resolves (``approved``/``rejected``) or the timeout
|
|
157
|
+
* elapses. On ``approved`` it makes ONE extra GET ({@link getL1Parts}) so the
|
|
158
|
+
* caller can finalize without re-polling — the poll loop itself stays lean.
|
|
159
|
+
* Throws if the timeout elapses while the approval is still ``pending``.
|
|
160
|
+
*/
|
|
161
|
+
async function waitForApproval(actionHash, options = {}) {
|
|
162
|
+
const { pollIntervalMs = 2000, timeoutMs = 300000 } = options;
|
|
163
|
+
const deadline = Date.now() + timeoutMs;
|
|
164
|
+
while (Date.now() < deadline) {
|
|
165
|
+
const view = await pollApproval(actionHash);
|
|
166
|
+
if (view.status !== 'pending') {
|
|
167
|
+
const parts = view.status === 'approved' ? await getL1Parts(actionHash) : null;
|
|
168
|
+
return { ...view, parts };
|
|
169
|
+
}
|
|
170
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
171
|
+
}
|
|
172
|
+
throw new Error(`@hesohq/sdk: approval ${actionHash} did not resolve within ${timeoutMs}ms`);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Submit a human-signed approval token for an open approval, keyed by the action
|
|
176
|
+
* it gates. The cloud verifies the token against the org's registered approver
|
|
177
|
+
* keys and records it as a vote; the response is the updated ``ApprovalView``.
|
|
178
|
+
*/
|
|
179
|
+
async function submitApprovalToken(actionHash, input) {
|
|
180
|
+
const body = {
|
|
181
|
+
token_b64: input.tokenB64,
|
|
182
|
+
action_content: input.actionContent,
|
|
183
|
+
required_scope: input.requiredScope ?? '',
|
|
184
|
+
decision: input.decision ?? 'approved',
|
|
185
|
+
reason: input.reason ?? '',
|
|
186
|
+
};
|
|
187
|
+
if (input.delegationEnvelopeB64 !== undefined) {
|
|
188
|
+
body.delegation_envelope_b64 = input.delegationEnvelopeB64;
|
|
189
|
+
}
|
|
190
|
+
if (input.clientBearer !== undefined) {
|
|
191
|
+
body.client_bearer = input.clientBearer;
|
|
192
|
+
}
|
|
193
|
+
const wire = await apiFetch(`/v1/approvals/${encodeURIComponent(actionHash)}/submit-token`, { method: 'POST', body: JSON.stringify(body) });
|
|
194
|
+
return mapApprovalView(wire);
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=cloud.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud.js","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB;AAID,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED,wBAAgB,SAAS,IAAI,cAAc,CAK1C"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SDK configuration — set once at process startup.
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.configure = configure;
|
|
5
|
+
exports.getConfig = getConfig;
|
|
6
|
+
let _config = null;
|
|
7
|
+
function configure(apiKey, endpoint) {
|
|
8
|
+
_config = { apiKey, endpoint };
|
|
9
|
+
}
|
|
10
|
+
function getConfig() {
|
|
11
|
+
if (!_config) {
|
|
12
|
+
throw new Error('@hesohq/sdk: call configure(apiKey, endpoint) before using cloud APIs');
|
|
13
|
+
}
|
|
14
|
+
return _config;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";AAAA,mDAAmD;;AASnD,8BAEC;AAED,8BAKC;AAXD,IAAI,OAAO,GAA0B,IAAI,CAAA;AAEzC,SAAgB,SAAS,CAAC,MAAc,EAAE,QAAgB;IACxD,OAAO,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAA;AAChC,CAAC;AAED,SAAgB,SAAS;IACvB,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAA;IAC1F,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The operator signer — holds the operator PRIVATE key (server-side only). The SDK
|
|
3
|
+
* never sees the private key; it calls `sign` over the canonical bytes and pairs the
|
|
4
|
+
* result with `publicKeyRaw` (the half registered with HESO via registerOperatorKey).
|
|
5
|
+
*/
|
|
6
|
+
export interface OperatorSigner {
|
|
7
|
+
/** raw 32-byte Ed25519 public key — the org's registered operator key */
|
|
8
|
+
publicKeyRaw: Uint8Array;
|
|
9
|
+
/** sign a message, returning the raw 64-byte Ed25519 signature */
|
|
10
|
+
sign: (message: Uint8Array) => Promise<Uint8Array>;
|
|
11
|
+
}
|
|
12
|
+
export interface SignedDelegation {
|
|
13
|
+
/** base64 of the wire envelope — what the gate iframe needs (mountGate's mintEnvelope) */
|
|
14
|
+
envelopeB64: string;
|
|
15
|
+
/** the wire envelope bytes */
|
|
16
|
+
envelopeBytes: Uint8Array;
|
|
17
|
+
/** the operator's PUBLIC key (raw 32 bytes) — the verification target */
|
|
18
|
+
operatorPublicKeyRaw: Uint8Array;
|
|
19
|
+
}
|
|
20
|
+
export interface DelegationInput {
|
|
21
|
+
/** the 32-byte BLAKE3 action digest this delegation authorizes (hex or raw) */
|
|
22
|
+
actionHash: string | Uint8Array;
|
|
23
|
+
/** server-issued 32-byte nonce (replay prevention, single-use per mint) */
|
|
24
|
+
nonce: Uint8Array;
|
|
25
|
+
/** the delegated key K — raw 32-byte Ed25519 public key, published by the gate iframe */
|
|
26
|
+
authorizedKey: Uint8Array;
|
|
27
|
+
/** the scope string the delegated authority is bound to (the policy rule id) */
|
|
28
|
+
scope: string;
|
|
29
|
+
/** subject the operator stamps onto the delegation (e.g. "operator:gate") */
|
|
30
|
+
sub: string;
|
|
31
|
+
/** absolute expiry, Unix seconds */
|
|
32
|
+
expiryUnixSecs: bigint;
|
|
33
|
+
/** absolute not_before, Unix seconds */
|
|
34
|
+
notBeforeUnixSecs: bigint;
|
|
35
|
+
/** the operator signer (holds the operator PRIVATE key, server-side only) */
|
|
36
|
+
signer: OperatorSigner;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Sign a delegation envelope. The wire bytes are byte-exact with what
|
|
40
|
+
* verify_delegation expects (Rust + the wheel), so an envelope minted here verifies
|
|
41
|
+
* offline against the org's registered operator key.
|
|
42
|
+
*/
|
|
43
|
+
export declare function signDelegation(input: DelegationInput): Promise<SignedDelegation>;
|
|
44
|
+
export interface MintDelegationInput {
|
|
45
|
+
/** the 32-byte BLAKE3 action digest this gate authorizes (hex or raw) */
|
|
46
|
+
actionHash: string | Uint8Array;
|
|
47
|
+
/** the end-user's public key K (raw 32 bytes) — published by the gate iframe */
|
|
48
|
+
authorizedKey: Uint8Array;
|
|
49
|
+
/** the scope the gate is bound to (the policy rule id) — must match the co-sign */
|
|
50
|
+
scope: string;
|
|
51
|
+
/** the operator signer (holds the operator PRIVATE key, server-side only) */
|
|
52
|
+
signer: OperatorSigner;
|
|
53
|
+
/** subject stamped onto the delegation (default "operator:gate") */
|
|
54
|
+
sub?: string;
|
|
55
|
+
/** how long the gate stays valid, in seconds (default 300) */
|
|
56
|
+
ttlSecs?: number;
|
|
57
|
+
/** server-issued 32-byte nonce; defaults to a fresh CSPRNG nonce */
|
|
58
|
+
nonce?: Uint8Array;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Mint a delegation envelope for ONE gate, server-side — the one-call helper an
|
|
62
|
+
* operator invokes from mountGate's `mintEnvelope(publicKeyB64)` callback once the
|
|
63
|
+
* gate iframe has published the end-user key K. Fills the nonce, the time window,
|
|
64
|
+
* the version, and the subject so the caller supplies only the action, K, the scope,
|
|
65
|
+
* and the signer. Returns the same `SignedDelegation` as `signDelegation`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function mintDelegationEnvelope(input: MintDelegationInput): Promise<SignedDelegation>;
|
|
68
|
+
//# sourceMappingURL=delegation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegation.d.ts","sourceRoot":"","sources":["../src/delegation.ts"],"names":[],"mappings":"AA6EA;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,YAAY,EAAE,UAAU,CAAA;IACxB,kEAAkE;IAClE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;CACnD;AAED,MAAM,WAAW,gBAAgB;IAC/B,0FAA0F;IAC1F,WAAW,EAAE,MAAM,CAAA;IACnB,8BAA8B;IAC9B,aAAa,EAAE,UAAU,CAAA;IACzB,yEAAyE;IACzE,oBAAoB,EAAE,UAAU,CAAA;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,2EAA2E;IAC3E,KAAK,EAAE,UAAU,CAAA;IACjB,yFAAyF;IACzF,aAAa,EAAE,UAAU,CAAA;IACzB,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAA;IACX,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAA;IACtB,wCAAwC;IACxC,iBAAiB,EAAE,MAAM,CAAA;IACzB,6EAA6E;IAC7E,MAAM,EAAE,cAAc,CAAA;CACvB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8BtF;AAED,MAAM,WAAW,mBAAmB;IAClC,yEAAyE;IACzE,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,gFAAgF;IAChF,aAAa,EAAE,UAAU,CAAA;IACzB,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,MAAM,EAAE,cAAc,CAAA;IACtB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAclG"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @hesohq/sdk — operator delegation-envelope minting.
|
|
3
|
+
//
|
|
4
|
+
// An OPERATOR delegates ONE gate (bound to a single action_hash) to a client key
|
|
5
|
+
// K by signing an Ed25519 delegation envelope. Consistent with this SDK's
|
|
6
|
+
// zero-crypto-in-TS rule: this module assembles the canonical WIRE bytes only; the
|
|
7
|
+
// CALLER injects the signer (their Ed25519 sign over the operator PRIVATE key) — the
|
|
8
|
+
// SDK never holds or implements a key. The wire layout is byte-exact with the Rust
|
|
9
|
+
// core (crates/heso-action/src/delegation.rs) and the Python wheel, pinned by the
|
|
10
|
+
// golden test, so an envelope minted here verifies through verify_delegation
|
|
11
|
+
// unchanged.
|
|
12
|
+
//
|
|
13
|
+
// signed payload =
|
|
14
|
+
// DELEGATION_SIGNING_DOMAIN ++ version(1) ++ action_hash(32) ++ nonce(32)
|
|
15
|
+
// ++ expiry(BE8) ++ not_before(BE8) ++ K(32)
|
|
16
|
+
// ++ sub_len(BE4) ++ sub ++ scope_len(BE4) ++ scope
|
|
17
|
+
// wire envelope =
|
|
18
|
+
// <signed payload, sans domain> ++ signature(64) ++ operator_pubkey(32)
|
|
19
|
+
//
|
|
20
|
+
// The envelope carries NO decision byte by design: the operator pre-mints it
|
|
21
|
+
// before any human verdict exists. The approve/reject decision is bound in the
|
|
22
|
+
// END-USER CO-SIGN TOKEN (browser approval-token.ts), which verify_delegation
|
|
23
|
+
// threads through verify_approval_token. So this wire — and its GOLDEN_WIRE_HEX —
|
|
24
|
+
// stay frozen; do NOT add a decision field here.
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.signDelegation = signDelegation;
|
|
27
|
+
exports.mintDelegationEnvelope = mintDelegationEnvelope;
|
|
28
|
+
// b"heso-delegation/v1\0" — 19 bytes, matches DELEGATION_SIGNING_DOMAIN in Rust.
|
|
29
|
+
const DELEGATION_SIGNING_DOMAIN = new TextEncoder().encode('heso-delegation/v1\0');
|
|
30
|
+
const DELEGATION_VERSION = 0x01;
|
|
31
|
+
function u64be(n) {
|
|
32
|
+
const out = new Uint8Array(8);
|
|
33
|
+
let v = n;
|
|
34
|
+
for (let i = 7; i >= 0; i--) {
|
|
35
|
+
out[i] = Number(v & 0xffn);
|
|
36
|
+
v >>= 8n;
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
function u32be(n) {
|
|
41
|
+
const out = new Uint8Array(4);
|
|
42
|
+
out[0] = (n >>> 24) & 0xff;
|
|
43
|
+
out[1] = (n >>> 16) & 0xff;
|
|
44
|
+
out[2] = (n >>> 8) & 0xff;
|
|
45
|
+
out[3] = n & 0xff;
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
function concat(parts) {
|
|
49
|
+
const total = parts.reduce((s, p) => s + p.length, 0);
|
|
50
|
+
const out = new Uint8Array(total);
|
|
51
|
+
let off = 0;
|
|
52
|
+
for (const p of parts) {
|
|
53
|
+
out.set(p, off);
|
|
54
|
+
off += p.length;
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
function toBase64(bytes) {
|
|
59
|
+
let bin = '';
|
|
60
|
+
for (const b of bytes)
|
|
61
|
+
bin += String.fromCharCode(b);
|
|
62
|
+
return typeof btoa === 'function' ? btoa(bin) : Buffer.from(bytes).toString('base64');
|
|
63
|
+
}
|
|
64
|
+
function fromHex(hex) {
|
|
65
|
+
if (hex.length % 2 !== 0)
|
|
66
|
+
throw new Error(`hex must have an even length, got ${hex.length}`);
|
|
67
|
+
const out = new Uint8Array(hex.length / 2);
|
|
68
|
+
for (let i = 0; i < out.length; i++)
|
|
69
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
function asActionHash(actionHash) {
|
|
73
|
+
const raw = typeof actionHash === 'string' ? fromHex(actionHash) : actionHash;
|
|
74
|
+
if (raw.length !== 32)
|
|
75
|
+
throw new Error(`delegation action_hash must be a 32-byte digest, got ${raw.length}`);
|
|
76
|
+
return raw;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Sign a delegation envelope. The wire bytes are byte-exact with what
|
|
80
|
+
* verify_delegation expects (Rust + the wheel), so an envelope minted here verifies
|
|
81
|
+
* offline against the org's registered operator key.
|
|
82
|
+
*/
|
|
83
|
+
async function signDelegation(input) {
|
|
84
|
+
const actionHash = asActionHash(input.actionHash);
|
|
85
|
+
if (input.nonce.length !== 32)
|
|
86
|
+
throw new Error(`delegation nonce must be 32 bytes, got ${input.nonce.length}`);
|
|
87
|
+
if (input.authorizedKey.length !== 32) {
|
|
88
|
+
throw new Error(`authorized key K must be 32 bytes, got ${input.authorizedKey.length}`);
|
|
89
|
+
}
|
|
90
|
+
const subBytes = new TextEncoder().encode(input.sub);
|
|
91
|
+
const scopeBytes = new TextEncoder().encode(input.scope);
|
|
92
|
+
// The field run shared by the signed payload and the wire envelope: every byte
|
|
93
|
+
// before the signature, in the contract's order.
|
|
94
|
+
const fields = concat([
|
|
95
|
+
new Uint8Array([DELEGATION_VERSION]),
|
|
96
|
+
actionHash,
|
|
97
|
+
input.nonce,
|
|
98
|
+
u64be(input.expiryUnixSecs),
|
|
99
|
+
u64be(input.notBeforeUnixSecs),
|
|
100
|
+
input.authorizedKey,
|
|
101
|
+
u32be(subBytes.length),
|
|
102
|
+
subBytes,
|
|
103
|
+
u32be(scopeBytes.length),
|
|
104
|
+
scopeBytes,
|
|
105
|
+
]);
|
|
106
|
+
const signature = await input.signer.sign(concat([DELEGATION_SIGNING_DOMAIN, fields]));
|
|
107
|
+
if (signature.length !== 64)
|
|
108
|
+
throw new Error(`expected a 64-byte Ed25519 signature, got ${signature.length}`);
|
|
109
|
+
const envelopeBytes = concat([fields, signature, input.signer.publicKeyRaw]);
|
|
110
|
+
return { envelopeB64: toBase64(envelopeBytes), envelopeBytes, operatorPublicKeyRaw: input.signer.publicKeyRaw };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Mint a delegation envelope for ONE gate, server-side — the one-call helper an
|
|
114
|
+
* operator invokes from mountGate's `mintEnvelope(publicKeyB64)` callback once the
|
|
115
|
+
* gate iframe has published the end-user key K. Fills the nonce, the time window,
|
|
116
|
+
* the version, and the subject so the caller supplies only the action, K, the scope,
|
|
117
|
+
* and the signer. Returns the same `SignedDelegation` as `signDelegation`.
|
|
118
|
+
*/
|
|
119
|
+
async function mintDelegationEnvelope(input) {
|
|
120
|
+
const ttl = BigInt(input.ttlSecs ?? 300);
|
|
121
|
+
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
122
|
+
const nonce = input.nonce ?? randomNonce();
|
|
123
|
+
return signDelegation({
|
|
124
|
+
actionHash: input.actionHash,
|
|
125
|
+
nonce,
|
|
126
|
+
authorizedKey: input.authorizedKey,
|
|
127
|
+
scope: input.scope,
|
|
128
|
+
sub: input.sub ?? 'operator:gate',
|
|
129
|
+
notBeforeUnixSecs: now,
|
|
130
|
+
expiryUnixSecs: now + ttl,
|
|
131
|
+
signer: input.signer,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function randomNonce() {
|
|
135
|
+
const n = new Uint8Array(32);
|
|
136
|
+
globalThis.crypto.getRandomValues(n);
|
|
137
|
+
return n;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=delegation.js.map
|