@calimero-network/mero-js 27.4.0 → 28.0.0
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 +34 -5
- package/dist/cloud-v2/client.d.ts +107 -25
- package/dist/cloud-v2/client.js +169 -52
- package/dist/cloud-v2/directory.d.ts +50 -0
- package/dist/cloud-v2/directory.js +115 -0
- package/dist/cloud-v2/index.d.ts +3 -1
- package/dist/cloud-v2/index.js +1 -0
- package/dist/index.browser.mjs +4 -4
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +247 -49
- package/dist/index.cjs.map +4 -4
- package/dist/index.mjs +247 -49
- package/dist/index.mjs.map +4 -4
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1248,6 +1248,85 @@ async function fetchAttestedKey(baseUrl, policy, fetchImpl = (input, init) => fe
|
|
|
1248
1248
|
return transportKey;
|
|
1249
1249
|
}
|
|
1250
1250
|
|
|
1251
|
+
// src/cloud-v2/directory.ts
|
|
1252
|
+
var DEFAULT_DIRECTORY = "https://manager.cloud.calimero.network/api/v2/directory";
|
|
1253
|
+
var DEFAULT_REFRESH_SECONDS = 60;
|
|
1254
|
+
var DirectoryError = class extends Error {
|
|
1255
|
+
constructor(cluster, reason, message, status, cause) {
|
|
1256
|
+
super(`directory for cluster ${cluster}: ${message}`);
|
|
1257
|
+
this.cluster = cluster;
|
|
1258
|
+
this.reason = reason;
|
|
1259
|
+
this.status = status;
|
|
1260
|
+
this.cause = cause;
|
|
1261
|
+
this.name = "DirectoryError";
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
function directoryUrl(directory, cluster) {
|
|
1265
|
+
return `${directory.replace(/\/+$/, "")}/${encodeURIComponent(cluster)}`;
|
|
1266
|
+
}
|
|
1267
|
+
async function readDirectory(directory, cluster, fetcher) {
|
|
1268
|
+
let response;
|
|
1269
|
+
try {
|
|
1270
|
+
response = await fetcher(directoryUrl(directory, cluster), { method: "GET", headers: { accept: "application/json" } });
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
throw new DirectoryError(cluster, "unreachable", `could not be reached: ${String(error)}`, void 0, error);
|
|
1273
|
+
}
|
|
1274
|
+
if (response.status === 404) throw new DirectoryError(cluster, "not-found", "unknown cluster", 404);
|
|
1275
|
+
if (!response.ok) throw new DirectoryError(cluster, "unreachable", `answered ${response.status}`, response.status);
|
|
1276
|
+
let body;
|
|
1277
|
+
try {
|
|
1278
|
+
body = await response.json();
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
throw new DirectoryError(cluster, "bad-response", "answered something other than JSON", response.status, error);
|
|
1281
|
+
}
|
|
1282
|
+
const listing = body;
|
|
1283
|
+
if (!listing || typeof listing !== "object" || !Array.isArray(listing.nodes)) {
|
|
1284
|
+
throw new DirectoryError(cluster, "bad-response", "answered no node list", response.status);
|
|
1285
|
+
}
|
|
1286
|
+
const nodes = [];
|
|
1287
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1288
|
+
for (const entry of listing.nodes) {
|
|
1289
|
+
const node = usable(entry);
|
|
1290
|
+
if (node && !seen.has(node.url)) {
|
|
1291
|
+
seen.add(node.url);
|
|
1292
|
+
nodes.push(node);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
if (nodes.length === 0) throw new DirectoryError(cluster, "empty", "lists no usable node", response.status);
|
|
1296
|
+
const after = listing.refreshAfterSeconds;
|
|
1297
|
+
const refreshAfterSeconds = typeof after === "number" && Number.isFinite(after) && after >= 1 ? after : DEFAULT_REFRESH_SECONDS;
|
|
1298
|
+
return { nodes, refreshAfterSeconds };
|
|
1299
|
+
}
|
|
1300
|
+
function usable(entry) {
|
|
1301
|
+
if (!entry || typeof entry !== "object") return void 0;
|
|
1302
|
+
const { url: url2, zone, weight } = entry;
|
|
1303
|
+
if (typeof url2 !== "string" || typeof weight !== "number" || !Number.isInteger(weight) || weight < 1) return void 0;
|
|
1304
|
+
let parsed;
|
|
1305
|
+
try {
|
|
1306
|
+
parsed = new URL(url2);
|
|
1307
|
+
} catch {
|
|
1308
|
+
return void 0;
|
|
1309
|
+
}
|
|
1310
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return void 0;
|
|
1311
|
+
return { url: url2.replace(/\/+$/, ""), zone: typeof zone === "string" ? zone : void 0, weight };
|
|
1312
|
+
}
|
|
1313
|
+
function orderNodes(nodes, zone, random) {
|
|
1314
|
+
const near = zone === void 0 ? [] : nodes.filter((node) => node.zone === zone);
|
|
1315
|
+
const far = zone === void 0 ? nodes : nodes.filter((node) => node.zone !== zone);
|
|
1316
|
+
return [...weightedOrder(near, random), ...weightedOrder(far, random)];
|
|
1317
|
+
}
|
|
1318
|
+
function weightedOrder(nodes, random) {
|
|
1319
|
+
const left = [...nodes];
|
|
1320
|
+
const order = [];
|
|
1321
|
+
while (left.length > 0) {
|
|
1322
|
+
let roll = random() * left.reduce((sum, node) => sum + node.weight, 0);
|
|
1323
|
+
let pick = 0;
|
|
1324
|
+
while (pick < left.length - 1 && roll >= left[pick].weight) roll -= left[pick++].weight;
|
|
1325
|
+
order.push(left.splice(pick, 1)[0].url);
|
|
1326
|
+
}
|
|
1327
|
+
return order;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1251
1330
|
// src/cloud-v2/errors.ts
|
|
1252
1331
|
var CalimeroError = class extends Error {
|
|
1253
1332
|
constructor(status, code, message, details = {}) {
|
|
@@ -1634,7 +1713,7 @@ function retryDelayMs(retry, options) {
|
|
|
1634
1713
|
function ceremony(path) {
|
|
1635
1714
|
return path === "auth/refresh" || path.startsWith("auth/passkey/");
|
|
1636
1715
|
}
|
|
1637
|
-
var CalimeroClient = class {
|
|
1716
|
+
var CalimeroClient = class _CalimeroClient {
|
|
1638
1717
|
constructor(options) {
|
|
1639
1718
|
/** The one in use: an index into {@link nodeUrls}. */
|
|
1640
1719
|
this.at = 0;
|
|
@@ -1657,10 +1736,40 @@ var CalimeroClient = class {
|
|
|
1657
1736
|
};
|
|
1658
1737
|
this.raw = raw;
|
|
1659
1738
|
}
|
|
1660
|
-
/**
|
|
1739
|
+
/**
|
|
1740
|
+
* A client for the Calimero Cloud cluster named `cluster`: its node list read
|
|
1741
|
+
* from the public node directory, those in `zone` first, each group in
|
|
1742
|
+
* weighted-random order. The directory only picks which nodes to try: each is
|
|
1743
|
+
* attested under `policy` before anything is sent to it. Throws
|
|
1744
|
+
* {@link DirectoryError} when the directory names no usable node.
|
|
1745
|
+
*
|
|
1746
|
+
* The list is read again in the background after a failover, and at the
|
|
1747
|
+
* first request once the directory's `refreshAfterSeconds` has passed.
|
|
1748
|
+
*/
|
|
1749
|
+
static async connect(options) {
|
|
1750
|
+
const directory = options.directory ?? DEFAULT_DIRECTORY;
|
|
1751
|
+
const random = options.random ?? Math.random;
|
|
1752
|
+
const fetcher = options.fetch ?? ((input, init) => fetch(input, init));
|
|
1753
|
+
const listing = await readDirectory(directory, options.cluster, fetcher);
|
|
1754
|
+
const [baseUrl, ...nodes] = orderNodes(listing.nodes, options.zone, random);
|
|
1755
|
+
const client = new _CalimeroClient({ baseUrl, nodes, policy: options.policy, fetch: options.fetch, retry: options.retry });
|
|
1756
|
+
client.source = {
|
|
1757
|
+
directory,
|
|
1758
|
+
cluster: options.cluster,
|
|
1759
|
+
zone: options.zone,
|
|
1760
|
+
random,
|
|
1761
|
+
due: Date.now() + listing.refreshAfterSeconds * 1e3
|
|
1762
|
+
};
|
|
1763
|
+
return client;
|
|
1764
|
+
}
|
|
1765
|
+
/** The node requests go to now: `baseUrl`, until a `421 WRONG_NODE` or a failover moved the client on. */
|
|
1661
1766
|
get nodeUrl() {
|
|
1662
1767
|
return this.nodeUrls[this.at];
|
|
1663
1768
|
}
|
|
1769
|
+
/** Every node this client may use, in the order it moves through them. */
|
|
1770
|
+
get nodes() {
|
|
1771
|
+
return [...this.nodeUrls];
|
|
1772
|
+
}
|
|
1664
1773
|
/** The sealed channel to `url`, attested under the client's policy before its first request. */
|
|
1665
1774
|
channel(url2) {
|
|
1666
1775
|
let sealed = this.channels.get(url2);
|
|
@@ -1681,9 +1790,46 @@ var CalimeroClient = class {
|
|
|
1681
1790
|
*/
|
|
1682
1791
|
moveOn(from) {
|
|
1683
1792
|
if (this.nodeUrls.length < 2) return false;
|
|
1684
|
-
if (this.
|
|
1793
|
+
if (this.nodeUrl === from) this.at = (this.at + 1) % this.nodeUrls.length;
|
|
1685
1794
|
return true;
|
|
1686
1795
|
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Fail over from node `from`, where a request's retries ran out, to the next
|
|
1798
|
+
* node it has not `tried`. False when it has tried them all. A request whose
|
|
1799
|
+
* node another request moved away from already goes to that node, if untried.
|
|
1800
|
+
*/
|
|
1801
|
+
failOver(from, tried) {
|
|
1802
|
+
if (this.nodeUrl !== from && !tried.has(this.nodeUrl)) return true;
|
|
1803
|
+
const count = this.nodeUrls.length;
|
|
1804
|
+
for (let step = 1; step < count; step++) {
|
|
1805
|
+
const next = (this.at + step) % count;
|
|
1806
|
+
if (!tried.has(this.nodeUrls[next])) {
|
|
1807
|
+
this.at = next;
|
|
1808
|
+
this.reread();
|
|
1809
|
+
return true;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
return false;
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Read the node list again from the directory, in the background, for a
|
|
1816
|
+
* client made by {@link connect}: the node in use stays first, then the
|
|
1817
|
+
* directory's order; a node no longer listed is dropped. A failed read is
|
|
1818
|
+
* ignored. One read at a time.
|
|
1819
|
+
*/
|
|
1820
|
+
reread() {
|
|
1821
|
+
const source = this.source;
|
|
1822
|
+
if (!source || source.reading) return;
|
|
1823
|
+
source.reading = readDirectory(source.directory, source.cluster, this.raw).then((listing) => {
|
|
1824
|
+
const current = this.nodeUrl;
|
|
1825
|
+
const listed = orderNodes(listing.nodes, source.zone, source.random);
|
|
1826
|
+
this.nodeUrls = [current, ...listed.filter((url2) => url2 !== current)];
|
|
1827
|
+
this.at = 0;
|
|
1828
|
+
source.due = Date.now() + listing.refreshAfterSeconds * 1e3;
|
|
1829
|
+
}).catch(() => void 0).finally(() => {
|
|
1830
|
+
source.reading = void 0;
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1687
1833
|
/** Authenticate every request with an API key. */
|
|
1688
1834
|
useApiKey(key) {
|
|
1689
1835
|
this.auth = key;
|
|
@@ -1711,6 +1857,7 @@ var CalimeroClient = class {
|
|
|
1711
1857
|
headers["content-type"] = "application/json";
|
|
1712
1858
|
payload = JSON.stringify(body);
|
|
1713
1859
|
}
|
|
1860
|
+
if (this.source && Date.now() >= this.source.due) this.reread();
|
|
1714
1861
|
const url2 = this.nodeUrl;
|
|
1715
1862
|
try {
|
|
1716
1863
|
return await this.channel(url2)(`${url2}/${path}`, { method, headers, body: payload, signal });
|
|
@@ -1742,7 +1889,7 @@ var CalimeroClient = class {
|
|
|
1742
1889
|
let refreshed = false;
|
|
1743
1890
|
let moved = false;
|
|
1744
1891
|
for (; ; ) {
|
|
1745
|
-
const [token, at] = [this.auth, this.
|
|
1892
|
+
const [token, at] = [this.auth, this.nodeUrl];
|
|
1746
1893
|
const response = await this.request(method, path, body, raw, contentType, signal);
|
|
1747
1894
|
if (response.ok) return response;
|
|
1748
1895
|
const error = await refusal(response);
|
|
@@ -1778,25 +1925,40 @@ var CalimeroClient = class {
|
|
|
1778
1925
|
* {@link json} for a call or a view (D70): while the node answers 503 or
|
|
1779
1926
|
* cannot be reached, the same request, with the same idempotency key, is sent
|
|
1780
1927
|
* again after {@link retryDelayMs} (or the pause the node asked for, if
|
|
1781
|
-
* longer), at most `retries` times. Every other answer is final.
|
|
1928
|
+
* longer), at most `retries` times. Every other answer is final.
|
|
1929
|
+
*
|
|
1930
|
+
* When a node's retries run out, the request fails over to the next node the
|
|
1931
|
+
* client knows and its retries start again there, with the same key: the
|
|
1932
|
+
* key is kept in the app's replicated log, so the cluster answers it once
|
|
1933
|
+
* whichever node it reaches. Each node is tried once per request. The last
|
|
1782
1934
|
* failure surfaces as {@link UnavailableError}: the node's 503, or reason
|
|
1783
1935
|
* `network` for a connection that failed, whose `committed` is `'unknown'`.
|
|
1784
|
-
*
|
|
1936
|
+
*
|
|
1937
|
+
* `once` sends the request exactly once, to the node in use (the confirmed
|
|
1938
|
+
* attempt of {@link callConfirmed}, whose challenge is single-use).
|
|
1785
1939
|
*/
|
|
1786
|
-
async retried(method, path, body,
|
|
1787
|
-
|
|
1940
|
+
async retried(method, path, body, once = false) {
|
|
1941
|
+
const retries = once ? 0 : this.retry.retries;
|
|
1942
|
+
const tried = /* @__PURE__ */ new Set();
|
|
1943
|
+
for (; ; ) {
|
|
1788
1944
|
let unavailable;
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
}
|
|
1945
|
+
let node = this.nodeUrl;
|
|
1946
|
+
for (let attempt = 0; ; attempt++) {
|
|
1947
|
+
node = this.nodeUrl;
|
|
1948
|
+
tried.add(node);
|
|
1949
|
+
try {
|
|
1950
|
+
return await this.json(method, path, body);
|
|
1951
|
+
} catch (error) {
|
|
1952
|
+
if (error instanceof UnavailableError) unavailable = error;
|
|
1953
|
+
else if (error instanceof TypeError && this.networkFailures.has(error)) {
|
|
1954
|
+
unavailable = new UnavailableError("NETWORK", `the node could not be reached: ${error.message}`, {}, void 0, error);
|
|
1955
|
+
} else throw error;
|
|
1956
|
+
}
|
|
1957
|
+
if (attempt >= retries) break;
|
|
1958
|
+
const backoff = retryDelayMs(attempt, this.retry);
|
|
1959
|
+
await new Promise((resolve) => setTimeout(resolve, Math.max(backoff, (unavailable.retryAfter ?? 0) * 1e3)));
|
|
1796
1960
|
}
|
|
1797
|
-
if (
|
|
1798
|
-
const backoff = retryDelayMs(attempt, this.retry);
|
|
1799
|
-
await new Promise((resolve) => setTimeout(resolve, Math.max(backoff, (unavailable.retryAfter ?? 0) * 1e3)));
|
|
1961
|
+
if (once || !this.failOver(node, tried)) throw unavailable;
|
|
1800
1962
|
}
|
|
1801
1963
|
}
|
|
1802
1964
|
post(path, body = {}) {
|
|
@@ -1884,19 +2046,25 @@ var CalimeroClient = class {
|
|
|
1884
2046
|
me() {
|
|
1885
2047
|
return this.get("auth/me");
|
|
1886
2048
|
}
|
|
1887
|
-
/**
|
|
1888
|
-
|
|
2049
|
+
/**
|
|
2050
|
+
* Add another passkey (another device, `passkey`) to the signed-in account;
|
|
2051
|
+
* answers its credential id. The node asks for the approval of a passkey
|
|
2052
|
+
* the account already has (`412 CONFIRM_REQUIRED`, never the new one),
|
|
2053
|
+
* which `approver` gives.
|
|
2054
|
+
*/
|
|
2055
|
+
async addPasskey(passkey, approver) {
|
|
1889
2056
|
const begin = await this.post("auth/passkeys/add/begin");
|
|
1890
2057
|
const registration = await passkey.create(begin.options);
|
|
1891
|
-
const { credentialId } = await this.
|
|
1892
|
-
|
|
1893
|
-
registration
|
|
1894
|
-
|
|
2058
|
+
const { credentialId } = await this.postConfirmed(
|
|
2059
|
+
"auth/passkeys/add/finish",
|
|
2060
|
+
{ challengeId: begin.challengeId, registration },
|
|
2061
|
+
approver
|
|
2062
|
+
);
|
|
1895
2063
|
return credentialId;
|
|
1896
2064
|
}
|
|
1897
|
-
/** Remove one of the account's passkeys (not the last). */
|
|
1898
|
-
async removePasskey(credentialId) {
|
|
1899
|
-
await this.
|
|
2065
|
+
/** Remove one of the account's passkeys (not the last), approved with `passkey` (one it has). */
|
|
2066
|
+
async removePasskey(credentialId, passkey) {
|
|
2067
|
+
await this.deleteConfirmed(`auth/passkeys/${seg(credentialId)}`, passkey);
|
|
1900
2068
|
}
|
|
1901
2069
|
/** Name one of the account's passkeys (a device label); `''` clears the name. */
|
|
1902
2070
|
async labelPasskey(credentialId, label) {
|
|
@@ -1924,9 +2092,13 @@ var CalimeroClient = class {
|
|
|
1924
2092
|
async revokeApiKey(keyId) {
|
|
1925
2093
|
await this.delete(`auth/apikeys/${seg(keyId)}`);
|
|
1926
2094
|
}
|
|
1927
|
-
/**
|
|
1928
|
-
|
|
1929
|
-
|
|
2095
|
+
/**
|
|
2096
|
+
* Create an API key for an agent or server, approved with the account's
|
|
2097
|
+
* `passkey` (`412 CONFIRM_REQUIRED`); needs a passkey session (an API key
|
|
2098
|
+
* is refused `403`). The key is returned once.
|
|
2099
|
+
*/
|
|
2100
|
+
createApiKey(options, passkey) {
|
|
2101
|
+
return this.postConfirmed("auth/apikeys", { ...options }, passkey);
|
|
1930
2102
|
}
|
|
1931
2103
|
// ---------------------------------------------------------------- apps
|
|
1932
2104
|
/** Upload a raw WASM bundle (development clusters only); answers its hash. */
|
|
@@ -2034,12 +2206,19 @@ var CalimeroClient = class {
|
|
|
2034
2206
|
}
|
|
2035
2207
|
/**
|
|
2036
2208
|
* POST `body` to `path`, answering a `412 CONFIRM_REQUIRED` with `passkey`'s
|
|
2037
|
-
* assertion over the node's challenge (deleting an org, approving a release,
|
|
2209
|
+
* assertion over the node's challenge (deleting an org, approving a release,
|
|
2210
|
+
* adding a member, ...): the same body again, with `confirm`. Without a
|
|
2211
|
+
* passkey (a caller in plain JavaScript passing none) the `412` reaches the
|
|
2212
|
+
* caller as {@link ConfirmRequired}, carrying the challenge.
|
|
2038
2213
|
*/
|
|
2039
2214
|
postConfirmed(path, body, passkey) {
|
|
2040
2215
|
return this.confirmed("POST", path, body, passkey);
|
|
2041
2216
|
}
|
|
2042
|
-
/**
|
|
2217
|
+
/**
|
|
2218
|
+
* {@link postConfirmed} for a DELETE (deleting an organization, removing a
|
|
2219
|
+
* member or a passkey): `body` (`{}` by default) goes as JSON, and the
|
|
2220
|
+
* confirmed retry carries `confirm` beside it.
|
|
2221
|
+
*/
|
|
2043
2222
|
deleteConfirmed(path, passkey, body = {}) {
|
|
2044
2223
|
return this.confirmed("DELETE", path, body, passkey);
|
|
2045
2224
|
}
|
|
@@ -2047,7 +2226,7 @@ var CalimeroClient = class {
|
|
|
2047
2226
|
try {
|
|
2048
2227
|
return await send(body);
|
|
2049
2228
|
} catch (error) {
|
|
2050
|
-
if (!(error instanceof ConfirmRequired)) throw error;
|
|
2229
|
+
if (!(error instanceof ConfirmRequired) || !passkey) throw error;
|
|
2051
2230
|
const d = error.details;
|
|
2052
2231
|
let assertion;
|
|
2053
2232
|
try {
|
|
@@ -2091,7 +2270,7 @@ var CalimeroClient = class {
|
|
|
2091
2270
|
path,
|
|
2092
2271
|
{ method, args, idempotencyKey: key },
|
|
2093
2272
|
passkey,
|
|
2094
|
-
(body) => this.retried("POST", path, body, body.confirm
|
|
2273
|
+
(body) => this.retried("POST", path, body, body.confirm !== void 0)
|
|
2095
2274
|
);
|
|
2096
2275
|
}
|
|
2097
2276
|
/** Run a read-only method: strong by default, or stale from any replica. */
|
|
@@ -2371,20 +2550,24 @@ var CalimeroClient = class {
|
|
|
2371
2550
|
async liftReleaseDeadline(version) {
|
|
2372
2551
|
await this.delete(`ops/releases/${seg(version)}/deadline`);
|
|
2373
2552
|
}
|
|
2374
|
-
/** Add a member, or change their role
|
|
2375
|
-
async setMember(org, account, role) {
|
|
2376
|
-
await this.
|
|
2553
|
+
/** Add a member, or change their role, approved with the caller's `passkey`. */
|
|
2554
|
+
async setMember(org, account, role, passkey) {
|
|
2555
|
+
await this.postConfirmed(`orgs/${seg(org)}/members`, { account, role }, passkey);
|
|
2377
2556
|
}
|
|
2378
|
-
|
|
2379
|
-
|
|
2557
|
+
/**
|
|
2558
|
+
* Remove a member, or leave (`account` the caller's own), approved with the
|
|
2559
|
+
* caller's `passkey`.
|
|
2560
|
+
*/
|
|
2561
|
+
removeMember(org, account, passkey) {
|
|
2562
|
+
return this.deleteConfirmed(`orgs/${seg(org)}/members/${seg(account)}`, passkey);
|
|
2380
2563
|
}
|
|
2381
2564
|
/**
|
|
2382
2565
|
* Set capability bits ({@link ORG_CAPABILITIES}): for a member (`account`),
|
|
2383
2566
|
* for a member of one team (`account` and `team`), or the organization's
|
|
2384
|
-
* default (neither).
|
|
2567
|
+
* default (neither). Approved with the caller's `passkey`.
|
|
2385
2568
|
*/
|
|
2386
|
-
async setCapabilities(org, capabilities, at = {}) {
|
|
2387
|
-
await this.
|
|
2569
|
+
async setCapabilities(org, capabilities, passkey, at = {}) {
|
|
2570
|
+
await this.postConfirmed(`orgs/${seg(org)}/capabilities`, { capabilities, ...at }, passkey);
|
|
2388
2571
|
}
|
|
2389
2572
|
async createTeam(org, name, join, parent) {
|
|
2390
2573
|
const { teamId } = await this.post(`orgs/${seg(org)}/teams`, { name, join, parent });
|
|
@@ -2404,15 +2587,28 @@ var CalimeroClient = class {
|
|
|
2404
2587
|
async deleteTeam(org, team) {
|
|
2405
2588
|
await this.delete(`orgs/${seg(org)}/teams/${seg(team)}`);
|
|
2406
2589
|
}
|
|
2407
|
-
/**
|
|
2408
|
-
|
|
2409
|
-
|
|
2590
|
+
/**
|
|
2591
|
+
* Add a member to a team, or change their row: role, lead, capability bits.
|
|
2592
|
+
* Approved with the caller's `passkey`.
|
|
2593
|
+
*/
|
|
2594
|
+
async setTeamMember(org, team, account, passkey, row = {}) {
|
|
2595
|
+
await this.postConfirmed(`orgs/${seg(org)}/teams/${seg(team)}/members`, { account, ...row }, passkey);
|
|
2410
2596
|
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2597
|
+
/** Remove a member from a team, approved with the caller's `passkey`. */
|
|
2598
|
+
async removeTeamMember(org, team, account, passkey) {
|
|
2599
|
+
await this.deleteConfirmed(`orgs/${seg(org)}/teams/${seg(team)}/members/${seg(account)}`, passkey);
|
|
2413
2600
|
}
|
|
2414
|
-
|
|
2415
|
-
|
|
2601
|
+
/**
|
|
2602
|
+
* Make an invite into `org` (and `teams`), approved with the caller's
|
|
2603
|
+
* `passkey`; answers its token, which {@link redeemInvite} takes (no
|
|
2604
|
+
* approval there).
|
|
2605
|
+
*/
|
|
2606
|
+
async invite(org, role, passkey, teams = [], options = {}) {
|
|
2607
|
+
const { token } = await this.postConfirmed(
|
|
2608
|
+
`orgs/${seg(org)}/invites`,
|
|
2609
|
+
{ role, teams, ...options },
|
|
2610
|
+
passkey
|
|
2611
|
+
);
|
|
2416
2612
|
return token;
|
|
2417
2613
|
}
|
|
2418
2614
|
redeemInvite(token) {
|
|
@@ -2899,8 +3095,10 @@ export {
|
|
|
2899
3095
|
ConfirmRequired,
|
|
2900
3096
|
ConsentStale,
|
|
2901
3097
|
DEFAULT_COLLATERAL_MIRROR,
|
|
3098
|
+
DEFAULT_DIRECTORY,
|
|
2902
3099
|
DEFAULT_ORG_CAPABILITIES,
|
|
2903
3100
|
DEFAULT_RELEASE_MIRROR,
|
|
3101
|
+
DirectoryError,
|
|
2904
3102
|
GITHUB_RELEASES,
|
|
2905
3103
|
HANDSHAKE_PATH,
|
|
2906
3104
|
IdempotencyMismatch,
|