@calimero-network/mero-js 26.11.0 → 27.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 +73 -288
- package/dist/cloud-v2/attestation.d.ts +91 -0
- package/dist/cloud-v2/attestation.js +135 -0
- package/dist/cloud-v2/capabilities.d.ts +30 -0
- package/dist/cloud-v2/capabilities.js +31 -0
- package/dist/cloud-v2/client.d.ts +426 -0
- package/dist/cloud-v2/client.js +646 -0
- package/dist/cloud-v2/errors.d.ts +98 -0
- package/dist/cloud-v2/errors.js +165 -0
- package/dist/cloud-v2/index.d.ts +10 -0
- package/dist/cloud-v2/index.js +5 -0
- package/dist/cloud-v2/passkey.d.ts +27 -0
- package/dist/cloud-v2/passkey.js +73 -0
- package/dist/crypto/internal.d.ts +3 -37
- package/dist/crypto/internal.js +3 -72
- package/dist/index.browser.mjs +4 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +1750 -12772
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -56
- package/dist/index.js +14 -75
- package/dist/index.mjs +1750 -12772
- package/dist/index.mjs.map +4 -4
- package/dist/sealed/index.d.ts +4 -4
- package/dist/sealed/index.js +2 -2
- package/dist/sealed/release.d.ts +21 -76
- package/dist/sealed/release.js +24 -108
- package/dist/sealed/sealed.d.ts +8 -56
- package/dist/sealed/sealed.js +11 -89
- package/dist/sealed/verify.d.ts +7 -13
- package/dist/sealed/verify.js +3 -7
- package/package.json +8 -13
- package/dist/account/account-admin.d.ts +0 -80
- package/dist/account/account-admin.js +0 -346
- package/dist/account/account.d.ts +0 -196
- package/dist/account/account.js +0 -269
- package/dist/account/application-id.d.ts +0 -50
- package/dist/account/application-id.js +0 -83
- package/dist/account/bootstrap-from-invitation.d.ts +0 -110
- package/dist/account/bootstrap-from-invitation.js +0 -80
- package/dist/account/create-context.d.ts +0 -103
- package/dist/account/create-context.js +0 -267
- package/dist/account/govern.d.ts +0 -24
- package/dist/account/govern.js +0 -50
- package/dist/account/index.d.ts +0 -26
- package/dist/account/index.js +0 -15
- package/dist/account/join-as-account.d.ts +0 -23
- package/dist/account/join-as-account.js +0 -34
- package/dist/account/join-with-node.d.ts +0 -115
- package/dist/account/join-with-node.js +0 -161
- package/dist/account/node-admin.d.ts +0 -20
- package/dist/account/node-admin.js +0 -33
- package/dist/account/relay-from-invitation.d.ts +0 -137
- package/dist/account/relay-from-invitation.js +0 -180
- package/dist/account/relay-login-error.d.ts +0 -32
- package/dist/account/relay-login-error.js +0 -34
- package/dist/account/session.d.ts +0 -266
- package/dist/account/session.js +0 -915
- package/dist/account/wordlist.d.ts +0 -16
- package/dist/account/wordlist.js +0 -273
- package/dist/admin-api/admin-client.d.ts +0 -688
- package/dist/admin-api/admin-client.js +0 -1230
- package/dist/admin-api/admin-factory.d.ts +0 -14
- package/dist/admin-api/admin-factory.js +0 -22
- package/dist/admin-api/admin-types.d.ts +0 -2046
- package/dist/admin-api/admin-types.js +0 -12
- package/dist/admin-api/index.d.ts +0 -4
- package/dist/admin-api/index.js +0 -5
- package/dist/admin-api/redeem-invitation.d.ts +0 -101
- package/dist/admin-api/redeem-invitation.js +0 -200
- package/dist/auth/index.d.ts +0 -34
- package/dist/auth/index.js +0 -67
- package/dist/auth-api/auth-client.d.ts +0 -31
- package/dist/auth-api/auth-client.js +0 -117
- package/dist/auth-api/auth-factory.d.ts +0 -7
- package/dist/auth-api/auth-factory.js +0 -41
- package/dist/auth-api/auth-types.d.ts +0 -130
- package/dist/auth-api/auth-types.js +0 -2
- package/dist/auth-api/index.d.ts +0 -3
- package/dist/auth-api/index.js +0 -4
- package/dist/capabilities.d.ts +0 -84
- package/dist/capabilities.js +0 -117
- package/dist/cloud/account-ownership.d.ts +0 -63
- package/dist/cloud/account-ownership.js +0 -87
- package/dist/cloud/cloud-client.d.ts +0 -949
- package/dist/cloud/cloud-client.js +0 -1068
- package/dist/cloud/connect.d.ts +0 -213
- package/dist/cloud/connect.js +0 -260
- package/dist/cloud/enrol-redirect.d.ts +0 -181
- package/dist/cloud/enrol-redirect.js +0 -244
- package/dist/cloud/index.d.ts +0 -12
- package/dist/cloud/index.js +0 -10
- package/dist/cloud/link-redirect.d.ts +0 -95
- package/dist/cloud/link-redirect.js +0 -121
- package/dist/cloud/link-state.d.ts +0 -36
- package/dist/cloud/link-state.js +0 -104
- package/dist/cloud/routing-proof.d.ts +0 -113
- package/dist/cloud/routing-proof.js +0 -96
- package/dist/device-cert/device-cert.d.ts +0 -189
- package/dist/device-cert/device-cert.js +0 -239
- package/dist/device-cert/device-scope.d.ts +0 -58
- package/dist/device-cert/device-scope.js +0 -76
- package/dist/device-cert/index.d.ts +0 -4
- package/dist/device-cert/index.js +0 -3
- package/dist/ephemeral/codec.d.ts +0 -4
- package/dist/ephemeral/codec.js +0 -12
- package/dist/ephemeral/index.d.ts +0 -57
- package/dist/ephemeral/index.js +0 -101
- package/dist/ephemeral/types.d.ts +0 -27
- package/dist/ephemeral/types.js +0 -1
- package/dist/errors/classify.d.ts +0 -65
- package/dist/errors/classify.js +0 -117
- package/dist/errors/index.d.ts +0 -2
- package/dist/errors/index.js +0 -1
- package/dist/events/group.d.ts +0 -95
- package/dist/events/group.js +0 -10
- package/dist/events/index.d.ts +0 -5
- package/dist/events/index.js +0 -2
- package/dist/events/sse.d.ts +0 -127
- package/dist/events/sse.js +0 -469
- package/dist/events/ws.d.ts +0 -57
- package/dist/events/ws.js +0 -212
- package/dist/http-client/api-response.d.ts +0 -15
- package/dist/http-client/api-response.js +0 -1
- package/dist/http-client/http-factory.d.ts +0 -71
- package/dist/http-client/http-factory.js +0 -68
- package/dist/http-client/http-types.d.ts +0 -87
- package/dist/http-client/http-types.js +0 -1
- package/dist/http-client/index.d.ts +0 -7
- package/dist/http-client/index.js +0 -11
- package/dist/http-client/path-segment.d.ts +0 -1
- package/dist/http-client/path-segment.js +0 -8
- package/dist/http-client/retry.d.ts +0 -5
- package/dist/http-client/retry.js +0 -82
- package/dist/http-client/signal-utils.d.ts +0 -2
- package/dist/http-client/signal-utils.js +0 -41
- package/dist/http-client/web-client.d.ts +0 -80
- package/dist/http-client/web-client.js +0 -557
- package/dist/invitation/index.d.ts +0 -2
- package/dist/invitation/index.js +0 -2
- package/dist/invitation/invitation.d.ts +0 -106
- package/dist/invitation/invitation.js +0 -104
- package/dist/login/index.d.ts +0 -4
- package/dist/login/index.js +0 -3
- package/dist/login/login.d.ts +0 -111
- package/dist/login/login.js +0 -110
- package/dist/login/session.d.ts +0 -124
- package/dist/login/session.js +0 -141
- package/dist/mero-js.d.ts +0 -231
- package/dist/mero-js.js +0 -469
- package/dist/namespace-op/index.d.ts +0 -2
- package/dist/namespace-op/index.js +0 -3
- package/dist/namespace-op/namespace-op.d.ts +0 -150
- package/dist/namespace-op/namespace-op.js +0 -191
- package/dist/nodeDiscovery.d.ts +0 -47
- package/dist/nodeDiscovery.js +0 -101
- package/dist/owner-op/index.d.ts +0 -2
- package/dist/owner-op/index.js +0 -3
- package/dist/owner-op/owner-op.d.ts +0 -119
- package/dist/owner-op/owner-op.js +0 -214
- package/dist/presence/relay-presence.d.ts +0 -56
- package/dist/presence/relay-presence.js +0 -107
- package/dist/presence/statement.d.ts +0 -12
- package/dist/presence/statement.js +0 -17
- package/dist/relay/index.d.ts +0 -4
- package/dist/relay/index.js +0 -2
- package/dist/relay/nonce-source.d.ts +0 -72
- package/dist/relay/nonce-source.js +0 -111
- package/dist/relay/relay-client.d.ts +0 -472
- package/dist/relay/relay-client.js +0 -518
- package/dist/relay-attestation/attest-node-key.d.ts +0 -66
- package/dist/relay-attestation/attest-node-key.js +0 -110
- package/dist/relay-attestation/index.d.ts +0 -1
- package/dist/relay-attestation/index.js +0 -1
- package/dist/request/caller-proof.d.ts +0 -43
- package/dist/request/caller-proof.js +0 -36
- package/dist/request/index.d.ts +0 -3
- package/dist/request/index.js +0 -3
- package/dist/request/proof-signer.d.ts +0 -49
- package/dist/request/proof-signer.js +0 -59
- package/dist/request/request-sig.d.ts +0 -99
- package/dist/request/request-sig.js +0 -95
- package/dist/request-proof/authorizer.d.ts +0 -41
- package/dist/request-proof/authorizer.js +0 -60
- package/dist/request-proof/index.d.ts +0 -4
- package/dist/request-proof/index.js +0 -2
- package/dist/request-proof/request-proof.d.ts +0 -53
- package/dist/request-proof/request-proof.js +0 -95
- package/dist/rpc/index.d.ts +0 -64
- package/dist/rpc/index.js +0 -78
- package/dist/signer/index.d.ts +0 -2
- package/dist/signer/index.js +0 -3
- package/dist/signer/signer.d.ts +0 -92
- package/dist/signer/signer.js +0 -147
- package/dist/token-store/index.d.ts +0 -27
- package/dist/token-store/index.js +0 -62
- package/dist/transport/index.d.ts +0 -6
- package/dist/transport/index.js +0 -3
- package/dist/transport/mero-client.d.ts +0 -294
- package/dist/transport/mero-client.js +0 -386
- package/dist/transport/relay-observer.d.ts +0 -152
- package/dist/transport/relay-observer.js +0 -173
- package/dist/transport/relay-transport.d.ts +0 -73
- package/dist/transport/relay-transport.js +0 -94
- package/dist/transport/types.d.ts +0 -100
- package/dist/transport/types.js +0 -32
- package/dist/types/api-response.d.ts +0 -15
- package/dist/types/api-response.js +0 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/warrant/creation-warrant.d.ts +0 -163
- package/dist/warrant/creation-warrant.js +0 -194
- package/dist/warrant/governance-op.d.ts +0 -202
- package/dist/warrant/governance-op.js +0 -355
- package/dist/warrant/governance-warrant.d.ts +0 -124
- package/dist/warrant/governance-warrant.js +0 -164
- package/dist/warrant/index.d.ts +0 -8
- package/dist/warrant/index.js +0 -5
- package/dist/warrant/warrant.d.ts +0 -188
- package/dist/warrant/warrant.js +0 -202
package/dist/mero-js.js
DELETED
|
@@ -1,469 +0,0 @@
|
|
|
1
|
-
import { createBrowserHttpClient } from './http-client/index.js';
|
|
2
|
-
import { createAuthApiClientFromHttpClient } from './auth-api/index.js';
|
|
3
|
-
import { createAdminApiClientFromHttpClient } from './admin-api/index.js';
|
|
4
|
-
import { parseAuthCallback, buildAuthLoginUrl } from './auth/index.js';
|
|
5
|
-
import { createProofSigner } from './request/index.js';
|
|
6
|
-
import { RpcClient } from './rpc/index.js';
|
|
7
|
-
import { SseClient } from './events/sse.js';
|
|
8
|
-
import { WsClient } from './events/ws.js';
|
|
9
|
-
import { EphemeralClient } from './ephemeral/index.js';
|
|
10
|
-
import { CloudClient } from './cloud/cloud-client.js';
|
|
11
|
-
/**
|
|
12
|
-
* Accept either the keys or a ready signer.
|
|
13
|
-
*
|
|
14
|
-
* Taking a callback as well as an options object is not a convenience: it is
|
|
15
|
-
* how a caller keeps the signing key out of the SDK entirely when it lives
|
|
16
|
-
* somewhere the SDK cannot reach.
|
|
17
|
-
*/
|
|
18
|
-
function resolveProofSigner(config) {
|
|
19
|
-
if (!config)
|
|
20
|
-
return undefined;
|
|
21
|
-
return typeof config === 'function' ? config : createProofSigner(config);
|
|
22
|
-
}
|
|
23
|
-
/** Name of the Web Lock that serializes token refresh across tabs / webviews. */
|
|
24
|
-
const REFRESH_LOCK_NAME = 'mero-js:token-refresh';
|
|
25
|
-
/** Returns `navigator.locks` when the runtime supports the Web Locks API. */
|
|
26
|
-
function getLockManager() {
|
|
27
|
-
const nav = globalThis.navigator;
|
|
28
|
-
const locks = nav?.locks;
|
|
29
|
-
return locks && typeof locks.request === 'function' ? locks : null;
|
|
30
|
-
}
|
|
31
|
-
/** Key a token bundle to its node: origin plus path prefix, so nodes behind one host stay apart. */
|
|
32
|
-
function nodeKeyOf(baseUrl) {
|
|
33
|
-
try {
|
|
34
|
-
const { origin, pathname } = new URL(baseUrl);
|
|
35
|
-
return origin === 'null' ? baseUrl : origin + pathname.replace(/\/+$/, '');
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return baseUrl;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/** Try to extract `exp` (seconds) from a JWT, return ms timestamp or fallback. */
|
|
42
|
-
function expiresAtFromJwt(token, fallbackMs) {
|
|
43
|
-
try {
|
|
44
|
-
const parts = token.split('.');
|
|
45
|
-
if (parts.length === 3) {
|
|
46
|
-
// JWT uses base64url encoding: replace -/_ with +// and add padding
|
|
47
|
-
let b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
48
|
-
while (b64.length % 4)
|
|
49
|
-
b64 += '=';
|
|
50
|
-
const payload = JSON.parse(atob(b64));
|
|
51
|
-
if (typeof payload.exp === 'number') {
|
|
52
|
-
return payload.exp * 1000;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
// not a JWT or can't parse
|
|
58
|
-
}
|
|
59
|
-
return fallbackMs;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Main MeroJs SDK class that manages all API clients and authentication
|
|
63
|
-
*/
|
|
64
|
-
export class MeroJs {
|
|
65
|
-
constructor(config) {
|
|
66
|
-
this.tokenData = null;
|
|
67
|
-
this.refreshPromise = null;
|
|
68
|
-
// True while the store is known to hold our tokens; a store that cannot persist never sets it.
|
|
69
|
-
this.storeHoldsTokens = false;
|
|
70
|
-
// Bumped when local tokens are dropped, so a refresh already in flight cannot restore them.
|
|
71
|
-
this.tokenGeneration = 0;
|
|
72
|
-
this.onStorage = () => {
|
|
73
|
-
if (this.signedOutElsewhere())
|
|
74
|
-
this.dropLocalTokens();
|
|
75
|
-
};
|
|
76
|
-
this.rpcClient = null;
|
|
77
|
-
this.sseClient = null;
|
|
78
|
-
this.wsClient = null;
|
|
79
|
-
this.wsWarned = false;
|
|
80
|
-
this.config = {
|
|
81
|
-
timeoutMs: 10000,
|
|
82
|
-
...config,
|
|
83
|
-
};
|
|
84
|
-
this.tokenStore = config.tokenStore ?? null;
|
|
85
|
-
this.nodeKey = nodeKeyOf(config.baseUrl);
|
|
86
|
-
// Restore tokens from store if available
|
|
87
|
-
this.tokenData = this.readStoredTokens();
|
|
88
|
-
if (this.tokenStore) {
|
|
89
|
-
this.storeHoldsTokens = this.tokenData !== null;
|
|
90
|
-
if (typeof window !== 'undefined')
|
|
91
|
-
window.addEventListener('storage', this.onStorage);
|
|
92
|
-
}
|
|
93
|
-
// Create HTTP client with token management
|
|
94
|
-
// For Tauri, explicitly set credentials to 'omit' to avoid network errors
|
|
95
|
-
const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window;
|
|
96
|
-
// One signer for the whole connection: `authClient` and `adminClient` are
|
|
97
|
-
// both built FROM this transport, so wiring it here covers every request
|
|
98
|
-
// either of them makes.
|
|
99
|
-
const getProof = resolveProofSigner(this.config.requestProof);
|
|
100
|
-
this.httpClient = createBrowserHttpClient({
|
|
101
|
-
baseUrl: this.config.baseUrl,
|
|
102
|
-
fetch: this.config.fetch,
|
|
103
|
-
getProof,
|
|
104
|
-
getAuthToken: async () => {
|
|
105
|
-
const token = await this.getValidToken();
|
|
106
|
-
return token?.access_token || '';
|
|
107
|
-
},
|
|
108
|
-
refreshToken: async () => {
|
|
109
|
-
const refreshed = await this.performTokenRefresh();
|
|
110
|
-
return refreshed.access_token;
|
|
111
|
-
},
|
|
112
|
-
onTokenRefresh: async (newToken) => {
|
|
113
|
-
if (this.tokenData) {
|
|
114
|
-
this.tokenData.access_token = newToken;
|
|
115
|
-
this.persistTokens();
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
onAuthRevoked: async () => {
|
|
119
|
-
// The refresh-token family is gone (a single-use refresh token was
|
|
120
|
-
// replayed, or the token was revoked). Nothing left to refresh with —
|
|
121
|
-
// drop the bundle so the app can force a re-login, then let the app react.
|
|
122
|
-
this.clearToken();
|
|
123
|
-
try {
|
|
124
|
-
await this.config.onAuthRevoked?.();
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
// A failing app callback must not mask the auth error.
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
timeoutMs: this.config.timeoutMs,
|
|
131
|
-
credentials: this.config.requestCredentials ?? (isTauri ? 'omit' : undefined),
|
|
132
|
-
});
|
|
133
|
-
// Create API clients
|
|
134
|
-
this.authClient = createAuthApiClientFromHttpClient(this.httpClient, {
|
|
135
|
-
baseUrl: this.config.baseUrl,
|
|
136
|
-
getAuthToken: async () => {
|
|
137
|
-
const token = await this.getValidToken();
|
|
138
|
-
return token?.access_token || '';
|
|
139
|
-
},
|
|
140
|
-
timeoutMs: this.config.timeoutMs,
|
|
141
|
-
});
|
|
142
|
-
this.adminClient = createAdminApiClientFromHttpClient(this.httpClient, {
|
|
143
|
-
baseUrl: this.config.baseUrl,
|
|
144
|
-
getAuthToken: async () => {
|
|
145
|
-
const token = await this.getValidToken();
|
|
146
|
-
return token?.access_token || '';
|
|
147
|
-
},
|
|
148
|
-
timeoutMs: this.config.timeoutMs,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Get the Auth API client
|
|
153
|
-
*/
|
|
154
|
-
get auth() {
|
|
155
|
-
return this.authClient;
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Get the Admin API client
|
|
159
|
-
*/
|
|
160
|
-
get admin() {
|
|
161
|
-
return this.adminClient;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Get the RPC client (lazy initialized)
|
|
165
|
-
*/
|
|
166
|
-
get rpc() {
|
|
167
|
-
if (!this.rpcClient) {
|
|
168
|
-
this.rpcClient = new RpcClient({ httpClient: this.httpClient });
|
|
169
|
-
}
|
|
170
|
-
return this.rpcClient;
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Get the SSE event client (lazy initialized)
|
|
174
|
-
*/
|
|
175
|
-
get events() {
|
|
176
|
-
if (!this.sseClient) {
|
|
177
|
-
this.sseClient = new SseClient({
|
|
178
|
-
baseUrl: this.config.baseUrl,
|
|
179
|
-
fetch: this.config.fetch,
|
|
180
|
-
getAuthToken: async () => {
|
|
181
|
-
const token = await this.getValidToken();
|
|
182
|
-
return token?.access_token || '';
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
return this.sseClient;
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Get the ephemeral-presence client (lazy initialized).
|
|
190
|
-
*
|
|
191
|
-
* Shares this instance's SSE client, so `subscribe` reuses the one event
|
|
192
|
-
* stream rather than opening a second connection.
|
|
193
|
-
*/
|
|
194
|
-
get ephemeral() {
|
|
195
|
-
if (!this.ephemeralClient) {
|
|
196
|
-
this.ephemeralClient = new EphemeralClient({
|
|
197
|
-
httpClient: this.httpClient,
|
|
198
|
-
sse: this.events,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
return this.ephemeralClient;
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Calimero Cloud (lazy initialized).
|
|
205
|
-
*
|
|
206
|
-
* Available whether or not `cloud` was configured — an app that signs in
|
|
207
|
-
* later gets an unauthenticated client it can call `signInWithGoogle` on,
|
|
208
|
-
* which is one fewer construction path than making the getter conditional.
|
|
209
|
-
* It is entirely independent of this instance's node credential: the cloud
|
|
210
|
-
* knows nothing about the node's tokens and the node knows nothing about the
|
|
211
|
-
* cloud session.
|
|
212
|
-
*/
|
|
213
|
-
get cloud() {
|
|
214
|
-
if (!this.cloudClient) {
|
|
215
|
-
this.cloudClient = new CloudClient(this.config.cloud ?? {});
|
|
216
|
-
}
|
|
217
|
-
return this.cloudClient;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Get the WebSocket event client (lazy initialized).
|
|
221
|
-
* @experimental Use `events` (SSE) for production. WsClient is experimental.
|
|
222
|
-
*/
|
|
223
|
-
get ws() {
|
|
224
|
-
if (!this.wsWarned) {
|
|
225
|
-
this.wsWarned = true;
|
|
226
|
-
console.warn('[mero-js] WsClient is experimental. Use mero.events (SSE) for production.');
|
|
227
|
-
}
|
|
228
|
-
if (!this.wsClient) {
|
|
229
|
-
this.wsClient = new WsClient({
|
|
230
|
-
baseUrl: this.config.baseUrl,
|
|
231
|
-
getAuthToken: async () => {
|
|
232
|
-
const token = await this.getValidToken();
|
|
233
|
-
return token?.access_token || '';
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
return this.wsClient;
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Authenticate with the provided credentials
|
|
241
|
-
* This will create the root key on first use
|
|
242
|
-
*/
|
|
243
|
-
async authenticate(credentials) {
|
|
244
|
-
const creds = credentials || this.config.credentials;
|
|
245
|
-
if (!creds) {
|
|
246
|
-
throw new Error('No credentials provided for authentication');
|
|
247
|
-
}
|
|
248
|
-
try {
|
|
249
|
-
const requestBody = {
|
|
250
|
-
auth_method: 'user_password',
|
|
251
|
-
public_key: creds.username,
|
|
252
|
-
client_name: 'mero-js-sdk',
|
|
253
|
-
permissions: ['admin'],
|
|
254
|
-
timestamp: Math.floor(Date.now() / 1000),
|
|
255
|
-
provider_data: {
|
|
256
|
-
username: creds.username,
|
|
257
|
-
password: creds.password,
|
|
258
|
-
},
|
|
259
|
-
};
|
|
260
|
-
const response = await this.authClient.generateTokens(requestBody);
|
|
261
|
-
const accessToken = response.data.access_token;
|
|
262
|
-
this.tokenData = {
|
|
263
|
-
access_token: accessToken,
|
|
264
|
-
refresh_token: response.data.refresh_token,
|
|
265
|
-
expires_at: expiresAtFromJwt(accessToken, Date.now() + 3600000),
|
|
266
|
-
};
|
|
267
|
-
this.persistTokens();
|
|
268
|
-
return this.tokenData;
|
|
269
|
-
}
|
|
270
|
-
catch (error) {
|
|
271
|
-
throw new Error(`Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
persistTokens() {
|
|
275
|
-
if (!this.tokenStore || !this.tokenData)
|
|
276
|
-
return;
|
|
277
|
-
this.tokenStore.setTokens({ ...this.tokenData, node_origin: this.nodeKey });
|
|
278
|
-
this.storeHoldsTokens = this.tokenStore.getTokens() !== null;
|
|
279
|
-
}
|
|
280
|
-
/** The stored bundle, only if this node issued it; a legacy or foreign entry reads as empty. */
|
|
281
|
-
readStoredTokens() {
|
|
282
|
-
const stored = this.tokenStore?.getTokens();
|
|
283
|
-
if (!stored || stored.node_origin !== this.nodeKey)
|
|
284
|
-
return null;
|
|
285
|
-
return {
|
|
286
|
-
access_token: stored.access_token,
|
|
287
|
-
refresh_token: stored.refresh_token,
|
|
288
|
-
expires_at: stored.expires_at,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
// Raw store read: an empty store is a logout, another node's bundle is not.
|
|
292
|
-
signedOutElsewhere() {
|
|
293
|
-
return this.storeHoldsTokens && !this.tokenStore?.getTokens();
|
|
294
|
-
}
|
|
295
|
-
dropLocalTokens() {
|
|
296
|
-
this.tokenGeneration++;
|
|
297
|
-
this.refreshPromise = null;
|
|
298
|
-
this.tokenData = null;
|
|
299
|
-
this.storeHoldsTokens = false;
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Get a valid token. Returns the current token as-is.
|
|
303
|
-
* The server rejects refresh attempts while the access token is still valid,
|
|
304
|
-
* so we never proactively refresh. Instead, the WebHttpClient handles 401
|
|
305
|
-
* responses reactively via the refreshToken transport hook.
|
|
306
|
-
*/
|
|
307
|
-
async getValidToken() {
|
|
308
|
-
return this.tokenData;
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Refresh the access token. Single-flight: every caller (including the HTTP
|
|
312
|
-
* transport's 401 hook) goes through here.
|
|
313
|
-
*
|
|
314
|
-
* Refresh tokens are single-use (calimero-network/core#3083): each refresh
|
|
315
|
-
* consumes the presented token and returns a new one, and replaying a consumed
|
|
316
|
-
* token makes the server revoke the whole family. A refresh must therefore
|
|
317
|
-
* happen exactly once per rotation, which needs three layers:
|
|
318
|
-
* - an in-process promise, so concurrent 401s in this instance share one call;
|
|
319
|
-
* - a Web Lock (when available), so other tabs/webviews don't refresh at the
|
|
320
|
-
* same time on the same stored token;
|
|
321
|
-
* - a re-read of the token store inside the lock, so a bundle that another
|
|
322
|
-
* instance already rotated is adopted instead of replaying a stale token.
|
|
323
|
-
*/
|
|
324
|
-
async performTokenRefresh() {
|
|
325
|
-
if (this.refreshPromise) {
|
|
326
|
-
return this.refreshPromise;
|
|
327
|
-
}
|
|
328
|
-
// The access token that triggered this refresh. If the store holds a different
|
|
329
|
-
// one by the time we get the lock, someone else already rotated for us.
|
|
330
|
-
const triggeringAccessToken = this.tokenData?.access_token;
|
|
331
|
-
const promise = this.withRefreshLock(() => this.performTokenRefreshLocked(triggeringAccessToken));
|
|
332
|
-
this.refreshPromise = promise;
|
|
333
|
-
try {
|
|
334
|
-
return await promise;
|
|
335
|
-
}
|
|
336
|
-
finally {
|
|
337
|
-
if (this.refreshPromise === promise) {
|
|
338
|
-
this.refreshPromise = null;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Run `fn` under the refresh lock: the token store's own (shared across
|
|
344
|
-
* processes), else the cross-tab Web Lock, else directly.
|
|
345
|
-
*/
|
|
346
|
-
withRefreshLock(fn) {
|
|
347
|
-
if (this.tokenStore?.withLock)
|
|
348
|
-
return this.tokenStore.withLock(fn);
|
|
349
|
-
const locks = getLockManager();
|
|
350
|
-
return locks ? locks.request(REFRESH_LOCK_NAME, fn) : fn();
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Perform the actual token refresh. Runs with the refresh lock held.
|
|
354
|
-
*/
|
|
355
|
-
async performTokenRefreshLocked(triggeringAccessToken) {
|
|
356
|
-
// Another instance/tab may have rotated the bundle while we were queued behind
|
|
357
|
-
// the lock — the store, not our in-memory copy, is the source of truth.
|
|
358
|
-
if (this.signedOutElsewhere()) {
|
|
359
|
-
// Another holder signed out and emptied the store; refreshing would undo that.
|
|
360
|
-
this.dropLocalTokens();
|
|
361
|
-
throw new Error('Signed out in another tab');
|
|
362
|
-
}
|
|
363
|
-
const stored = this.readStoredTokens();
|
|
364
|
-
if (stored?.refresh_token) {
|
|
365
|
-
this.storeHoldsTokens = true;
|
|
366
|
-
this.tokenData = stored;
|
|
367
|
-
if (triggeringAccessToken && stored.access_token !== triggeringAccessToken) {
|
|
368
|
-
// Someone else already refreshed. Reuse their bundle rather than replaying
|
|
369
|
-
// our (now consumed) refresh token, which would revoke the family.
|
|
370
|
-
return stored;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
if (!this.tokenData?.refresh_token) {
|
|
374
|
-
throw new Error('No refresh token available');
|
|
375
|
-
}
|
|
376
|
-
// The store held the token we present; if it no longer does after the refresh, the
|
|
377
|
-
// session was replaced or signed out meanwhile and our rotated tokens must not be written.
|
|
378
|
-
const generation = this.tokenGeneration;
|
|
379
|
-
const presented = this.tokenData.access_token;
|
|
380
|
-
const storeHeldPresented = this.tokenStore?.getTokens()?.access_token === presented;
|
|
381
|
-
try {
|
|
382
|
-
const response = await this.authClient.refreshToken({
|
|
383
|
-
access_token: this.tokenData.access_token,
|
|
384
|
-
refresh_token: this.tokenData.refresh_token,
|
|
385
|
-
});
|
|
386
|
-
// A local drop already set the state; a store change by another tab is adopted.
|
|
387
|
-
if (generation !== this.tokenGeneration)
|
|
388
|
-
throw new Error('Signed out during token refresh');
|
|
389
|
-
if (storeHeldPresented && this.tokenStore?.getTokens()?.access_token !== presented) {
|
|
390
|
-
this.tokenData = this.tokenStore?.getTokens() ?? null;
|
|
391
|
-
this.storeHoldsTokens = this.tokenData !== null;
|
|
392
|
-
throw new Error('Signed out during token refresh');
|
|
393
|
-
}
|
|
394
|
-
const accessToken = response.data.access_token;
|
|
395
|
-
this.tokenData = {
|
|
396
|
-
access_token: accessToken,
|
|
397
|
-
// The refresh token is rotated on every refresh — persist the new one or
|
|
398
|
-
// the next refresh replays a consumed token.
|
|
399
|
-
refresh_token: response.data.refresh_token,
|
|
400
|
-
expires_at: expiresAtFromJwt(accessToken, Date.now() + 3600000),
|
|
401
|
-
};
|
|
402
|
-
this.persistTokens();
|
|
403
|
-
return this.tokenData;
|
|
404
|
-
}
|
|
405
|
-
catch (error) {
|
|
406
|
-
// Don't clear tokens on refresh failure — the access token may still be
|
|
407
|
-
// valid (server rejects refresh while access token hasn't expired yet).
|
|
408
|
-
// A genuinely dead family arrives as `x-auth-error: token_reuse` and is
|
|
409
|
-
// handled by the onAuthRevoked transport hook. Let the caller handle this.
|
|
410
|
-
throw new Error(`Token refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* Clear the current token
|
|
415
|
-
*/
|
|
416
|
-
clearToken() {
|
|
417
|
-
this.dropLocalTokens();
|
|
418
|
-
this.tokenStore?.clear();
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* Check if the SDK is authenticated
|
|
422
|
-
*/
|
|
423
|
-
isAuthenticated() {
|
|
424
|
-
return this.tokenData !== null;
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Set token data directly (e.g., from auth callback).
|
|
428
|
-
* If `expires_at` is missing or 0, attempts to parse the JWT exp claim,
|
|
429
|
-
* falling back to 1 hour from now.
|
|
430
|
-
*/
|
|
431
|
-
setTokenData(data) {
|
|
432
|
-
const expiresAt = data.expires_at || expiresAtFromJwt(data.access_token, Date.now() + 3600000);
|
|
433
|
-
this.tokenData = { ...data, expires_at: expiresAt };
|
|
434
|
-
this.persistTokens();
|
|
435
|
-
}
|
|
436
|
-
/**
|
|
437
|
-
* Get the current token data (for debugging)
|
|
438
|
-
*/
|
|
439
|
-
getTokenData() {
|
|
440
|
-
return this.tokenData;
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* Close all event connections and clean up resources
|
|
444
|
-
*/
|
|
445
|
-
close() {
|
|
446
|
-
if (typeof window !== 'undefined')
|
|
447
|
-
window.removeEventListener('storage', this.onStorage);
|
|
448
|
-
this.sseClient?.close();
|
|
449
|
-
this.wsClient?.close();
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Parse an auth callback URL hash fragment (static utility)
|
|
453
|
-
*/
|
|
454
|
-
static parseAuthCallback(url, options) {
|
|
455
|
-
return parseAuthCallback(url, options);
|
|
456
|
-
}
|
|
457
|
-
/**
|
|
458
|
-
* Build an auth login URL (static utility)
|
|
459
|
-
*/
|
|
460
|
-
static buildAuthLoginUrl(nodeUrl, opts) {
|
|
461
|
-
return buildAuthLoginUrl(nodeUrl, opts);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Create a new MeroJs SDK instance
|
|
466
|
-
*/
|
|
467
|
-
export function createMeroJs(config) {
|
|
468
|
-
return new MeroJs(config);
|
|
469
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sign a namespace governance op from the SDK — specifically the membership op a
|
|
3
|
-
* keyholder needs to join without running a node.
|
|
4
|
-
*
|
|
5
|
-
* `admitJoin` carries an already-signed op to a node the inviter named as an
|
|
6
|
-
* admitter. Producing that op is the piece a keyholder could not do before: the
|
|
7
|
-
* op must be signed by the device key in the credential it carries, because every
|
|
8
|
-
* peer checks `signer == credential.statement.sign_pk` when applying a join. An
|
|
9
|
-
* admitter can relay it and can never author it.
|
|
10
|
-
*
|
|
11
|
-
* The encoding is borsh, hand-written to match core's structs. Correctness is not
|
|
12
|
-
* asserted against bytes recorded here — a layout transcribed by eye is exactly
|
|
13
|
-
* how a golden ends up confidently wrong. It is asserted by a node accepting the
|
|
14
|
-
* op, which is what the e2e does.
|
|
15
|
-
*/
|
|
16
|
-
import type { SignedGroupOpenInvitation } from '../admin-api/admin-types.js';
|
|
17
|
-
import { type Signer } from '../signer/signer.js';
|
|
18
|
-
/**
|
|
19
|
-
* Core's `SIGNED_NAMESPACE_OP_SCHEMA_VERSION`. A node refuses any other value.
|
|
20
|
-
*
|
|
21
|
-
* 15: core went to 13 (TEE rows locked to the TEE roles, a TEE's relay role
|
|
22
|
-
* read at its namespace root row, inherited read-only roles read-only in Open
|
|
23
|
-
* subgroups), then 14 (`GroupOp::ContextRegisteredOnBehalf`) and 15
|
|
24
|
-
* (`GroupOp::OnBehalf` / `RootOp::OnBehalf`) for delegated context creation and
|
|
25
|
-
* governance through a relay. None of these changes a layout this module
|
|
26
|
-
* signs; the version moves with core's so nodes on different versions never
|
|
27
|
-
* share a namespace.
|
|
28
|
-
*
|
|
29
|
-
* 16: a namespace can be founded through a relay (a delegated
|
|
30
|
-
* `NamespaceCreatedV2`), and `GroupOp::FoundingRelayAttested` lets that relay
|
|
31
|
-
* admit itself as the namespace's first TEE. Again no layout this module signs.
|
|
32
|
-
*
|
|
33
|
-
* 17: `RootOp::GroupCreated` carries a `salt`, and its `group_id` must be the
|
|
34
|
-
* one `createdSubgroupId(admin, parentId, restricted, salt)` derives
|
|
35
|
-
* (calimero-network/core#4244), so two concurrent creates for one id can never
|
|
36
|
-
* name different creators. The layout of that variant changed.
|
|
37
|
-
*
|
|
38
|
-
* 18: owner-level ops need the account ROOT, not only a device of it
|
|
39
|
-
* (calimero-network/core, root-guarded owner ops). `GroupOp::RootGuarded` and
|
|
40
|
-
* `RootOp::RootGuarded` are appended and carry a root-signed proof, and the bare
|
|
41
|
-
* `TransferOwnership`, `AdminChanged`, `GroupDelete` and TEE policy ops are
|
|
42
|
-
* refused. No layout this module signs changes; see `../owner-op` for the proof.
|
|
43
|
-
*
|
|
44
|
-
* 19 is reserved for calimero-network/core#4269.
|
|
45
|
-
*
|
|
46
|
-
* 20: no layout change. A delegated `GroupCreated` whose executor is a TEE at
|
|
47
|
-
* the namespace root now seats that TEE in the new subgroup
|
|
48
|
-
* (calimero-network/core#4276), an apply-time rule older peers disagree on.
|
|
49
|
-
* Core signs and checks at 20, so every op this module signs must too.
|
|
50
|
-
*
|
|
51
|
-
* 21: no layout change. An op concurrent with its signer's removal is void
|
|
52
|
-
* (calimero-network/core#4275), an apply-time rule older peers disagree on.
|
|
53
|
-
* Core signs and checks at 21 from 0.11.0-rc.75. 23.4.0 and 23.4.1 still sign
|
|
54
|
-
* at 20, so against rc.75 every namespace op an account signs with them (a
|
|
55
|
-
* join, a create, any governance op) is refused "schema version mismatch:
|
|
56
|
-
* expected 21, got 20".
|
|
57
|
-
*
|
|
58
|
-
* 22: `GroupOp::SharedWritersRotated` is appended, a `SharedStorage` cell's
|
|
59
|
-
* writer-set step (calimero-network/core#4263), and a capability revoke voids
|
|
60
|
-
* the member's concurrent ops that needed a removed bit
|
|
61
|
-
* (calimero-network/core#4453). No layout this module signs changes.
|
|
62
|
-
*
|
|
63
|
-
* 23: a TEE admission op carries the quote it was admitted on and a TEE
|
|
64
|
-
* authority evidence op the credential it was made for
|
|
65
|
-
* (calimero-network/core#4265). Neither is an op this module signs, so only
|
|
66
|
-
* the version byte moves. Core signs and checks at 23 from 0.11.0-rc.81, the
|
|
67
|
-
* first release with both 22 and 23.
|
|
68
|
-
*
|
|
69
|
-
* 24: no layout change. Only a subgroup's creator may open it - flip it to
|
|
70
|
-
* `Open` (calimero-network/core#4522) - an apply-time rule older peers
|
|
71
|
-
* disagree on, since they let any of its admins open it. A UI that offers
|
|
72
|
-
* "make public" should offer it to the creator alone.
|
|
73
|
-
*
|
|
74
|
-
* 25: no layout change; refused device link/unlink/descope ops are never logged.
|
|
75
|
-
*
|
|
76
|
-
* 26: no layout change. A device link's scope is judged against every
|
|
77
|
-
* application the group may have targeted at the link's cut, not its live
|
|
78
|
-
* target (calimero-network/core#4603), so replicas agree on a link that races
|
|
79
|
-
* a target change; older peers judge it by fold order.
|
|
80
|
-
*
|
|
81
|
-
* 27: no layout change. The namespace founder is seated as an admin by the
|
|
82
|
-
* genesis op itself, so a founder who transfers ownership stays a member
|
|
83
|
-
* (calimero-network/core#4625); older peers seat her from the root's current
|
|
84
|
-
* owner and drop her.
|
|
85
|
-
*/
|
|
86
|
-
export declare const SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 27;
|
|
87
|
-
/**
|
|
88
|
-
* Borsh-encode a `SignedGroupOpenInvitation`.
|
|
89
|
-
*
|
|
90
|
-
* Re-encoded rather than passed through, because it has to go inside the op that
|
|
91
|
-
* gets signed. That makes this function version-locked to core: a field added to
|
|
92
|
-
* the invitation and omitted here produces an op whose inner invitation no longer
|
|
93
|
-
* matches the inviter's signature over it. That exact drift, in client-py, broke
|
|
94
|
-
* 93 scenarios — the failure surfaces as `invalid invitation signature` and points
|
|
95
|
-
* nowhere near the encoder.
|
|
96
|
-
*/
|
|
97
|
-
export declare function encodeSignedInvitation(signed: SignedGroupOpenInvitation): Uint8Array;
|
|
98
|
-
export interface SignMemberJoinInput {
|
|
99
|
-
/** The namespace being joined, 64 hex. */
|
|
100
|
-
readonly namespaceId: string;
|
|
101
|
-
/** The account joining, 64 hex — the subject of the credential. */
|
|
102
|
-
readonly member: string;
|
|
103
|
-
/** The invitation, as the admin API returned it. */
|
|
104
|
-
readonly invitation: SignedGroupOpenInvitation;
|
|
105
|
-
/**
|
|
106
|
-
* The joiner's `AccountProof<DeviceCert>`, hex — from `signDeviceCert`.
|
|
107
|
-
*
|
|
108
|
-
* Its `sign_pk` must be the public key of `deviceSecret`; a peer checks that
|
|
109
|
-
* when applying the join and refuses otherwise.
|
|
110
|
-
*/
|
|
111
|
-
readonly credential: string;
|
|
112
|
-
/**
|
|
113
|
-
* The device signing secret, 64 hex. Must match the credential's `sign_pk`.
|
|
114
|
-
*
|
|
115
|
-
* Mutually exclusive with {@link SignMemberJoinInput.signer}. A browser should
|
|
116
|
-
* prefer the signer: joining is the one governance op a keyholder signs for
|
|
117
|
-
* itself, and it should not need the key in a form script can read.
|
|
118
|
-
*/
|
|
119
|
-
readonly deviceSecret?: string;
|
|
120
|
-
/**
|
|
121
|
-
* The joiner's signer — use instead of
|
|
122
|
-
* {@link SignMemberJoinInput.deviceSecret} when the key cannot be exported.
|
|
123
|
-
*/
|
|
124
|
-
readonly signer?: Signer;
|
|
125
|
-
/**
|
|
126
|
-
* Heads this op is written against. Empty means "signed against an empty head"
|
|
127
|
-
* — which is NOT the same as genesis, and a node with a non-empty head will
|
|
128
|
-
* reject it as stale rather than treat it as a first op.
|
|
129
|
-
*/
|
|
130
|
-
readonly parentOpHashes?: readonly string[];
|
|
131
|
-
/** Anti-replay nonce; a node keeps a window per signer. */
|
|
132
|
-
readonly nonce: number | bigint;
|
|
133
|
-
/**
|
|
134
|
-
* When the join happened, unix seconds. Defaults to now.
|
|
135
|
-
*
|
|
136
|
-
* Carried only by `MemberJoinedAt`, which is the variant used whenever the
|
|
137
|
-
* invitation expires — and since core clamps every invitation to
|
|
138
|
-
* `MAX_INVITATION_VALIDITY_SECS`, that is every invitation. The variant is
|
|
139
|
-
* inferred from the invitation rather than selected by a flag: an expiring
|
|
140
|
-
* invitation joined with plain `MemberJoined` is refused at apply with
|
|
141
|
-
* "expiration is set but joined_at is absent", which is not a mistake worth
|
|
142
|
-
* leaving available.
|
|
143
|
-
*/
|
|
144
|
-
readonly joinedAt?: number | bigint;
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Sign a `MemberJoined` (or `MemberJoinedAt`) namespace op and return it
|
|
148
|
-
* borsh-encoded and hex — the `signedOp` that `admitJoin` takes.
|
|
149
|
-
*/
|
|
150
|
-
export declare function signMemberJoinOp(input: SignMemberJoinInput): Promise<string>;
|