@calimero-network/mero-js 25.3.1 → 25.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/auth/index.d.ts +11 -2
- package/dist/auth/index.js +20 -3
- package/dist/cloud/enrol-redirect.d.ts +10 -24
- package/dist/cloud/enrol-redirect.js +16 -8
- package/dist/cloud/link-state.d.ts +5 -1
- package/dist/cloud/link-state.js +28 -21
- package/dist/events/sse.d.ts +8 -0
- package/dist/events/sse.js +19 -8
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +111 -78
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +111 -78
- package/dist/index.mjs.map +4 -4
- package/dist/mero-js.d.ts +2 -2
- package/dist/mero-js.js +2 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2984,8 +2984,73 @@ function createAdminApiClientFromHttpClient(httpClient, _config) {
|
|
|
2984
2984
|
return new AdminApiClient(httpClient);
|
|
2985
2985
|
}
|
|
2986
2986
|
|
|
2987
|
+
// src/cloud/link-state.ts
|
|
2988
|
+
var KEY = "calimero.cloud.link.pending";
|
|
2989
|
+
var STATE_PARAM = "link_state";
|
|
2990
|
+
var LIFETIME_MS = 10 * 60 * 1e3;
|
|
2991
|
+
var ACCOUNT_HEX = /^[0-9a-f]{64}$/;
|
|
2992
|
+
var MAX_PENDING = 20;
|
|
2993
|
+
function readPending(key, now) {
|
|
2994
|
+
try {
|
|
2995
|
+
const raw = localStorage.getItem(key);
|
|
2996
|
+
const pending = raw ? JSON.parse(raw) : {};
|
|
2997
|
+
return Object.fromEntries(
|
|
2998
|
+
Object.entries(pending).filter(
|
|
2999
|
+
([, p]) => p && typeof p.expiresAt === "number" && p.expiresAt > now
|
|
3000
|
+
)
|
|
3001
|
+
);
|
|
3002
|
+
} catch {
|
|
3003
|
+
return {};
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
function writePending(key, pending) {
|
|
3007
|
+
try {
|
|
3008
|
+
localStorage.setItem(key, JSON.stringify(pending));
|
|
3009
|
+
} catch {
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
function beginState(key, payload) {
|
|
3013
|
+
const now = Date.now();
|
|
3014
|
+
const pending = readPending(key, now);
|
|
3015
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
3016
|
+
const state = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
|
|
3017
|
+
""
|
|
3018
|
+
);
|
|
3019
|
+
pending[state] = { ...payload, expiresAt: now + LIFETIME_MS };
|
|
3020
|
+
writePending(key, Object.fromEntries(Object.entries(pending).slice(-MAX_PENDING)));
|
|
3021
|
+
return state;
|
|
3022
|
+
}
|
|
3023
|
+
function takeState(key, state) {
|
|
3024
|
+
const pending = readPending(key, Date.now());
|
|
3025
|
+
if (!state || !Object.prototype.hasOwnProperty.call(pending, state))
|
|
3026
|
+
return null;
|
|
3027
|
+
const entry = pending[state];
|
|
3028
|
+
delete pending[state];
|
|
3029
|
+
writePending(key, pending);
|
|
3030
|
+
return entry;
|
|
3031
|
+
}
|
|
3032
|
+
function beginLinkState(account, returnTo) {
|
|
3033
|
+
if (!ACCOUNT_HEX.test(account)) {
|
|
3034
|
+
throw new Error("accountId must be 64 lowercase hex characters");
|
|
3035
|
+
}
|
|
3036
|
+
const url2 = new URL(returnTo);
|
|
3037
|
+
url2.searchParams.set(STATE_PARAM, beginState(KEY, { account }));
|
|
3038
|
+
return url2.toString();
|
|
3039
|
+
}
|
|
3040
|
+
function takeLinkState(search) {
|
|
3041
|
+
return takeState(KEY, new URLSearchParams(search).get(STATE_PARAM));
|
|
3042
|
+
}
|
|
3043
|
+
function withoutLinkState(search) {
|
|
3044
|
+
const params = new URLSearchParams(search);
|
|
3045
|
+
params.delete(STATE_PARAM);
|
|
3046
|
+
const rest = params.toString();
|
|
3047
|
+
return rest ? `?${rest}` : "";
|
|
3048
|
+
}
|
|
3049
|
+
|
|
2987
3050
|
// src/auth/index.ts
|
|
2988
|
-
|
|
3051
|
+
var PENDING_KEY = "calimero.auth.login.pending";
|
|
3052
|
+
var STATE_PARAM2 = "auth_state";
|
|
3053
|
+
function parseAuthCallback(url2, options = {}) {
|
|
2989
3054
|
try {
|
|
2990
3055
|
const hashIndex = url2.indexOf("#");
|
|
2991
3056
|
if (hashIndex === -1) return null;
|
|
@@ -2993,6 +3058,12 @@ function parseAuthCallback(url2) {
|
|
|
2993
3058
|
const params = new URLSearchParams(hash);
|
|
2994
3059
|
const accessToken = params.get("access_token");
|
|
2995
3060
|
if (!accessToken) return null;
|
|
3061
|
+
if (!options.allowUnsolicited && !takeState(PENDING_KEY, stateOf(url2, hashIndex))) {
|
|
3062
|
+
console.warn(
|
|
3063
|
+
"[mero-js] ignoring auth callback tokens: they answer no login begun in this browser"
|
|
3064
|
+
);
|
|
3065
|
+
return null;
|
|
3066
|
+
}
|
|
2996
3067
|
return {
|
|
2997
3068
|
accessToken,
|
|
2998
3069
|
refreshToken: params.get("refresh_token") ?? "",
|
|
@@ -3005,9 +3076,16 @@ function parseAuthCallback(url2) {
|
|
|
3005
3076
|
return null;
|
|
3006
3077
|
}
|
|
3007
3078
|
}
|
|
3079
|
+
function stateOf(url2, hashIndex) {
|
|
3080
|
+
const queryIndex = url2.indexOf("?");
|
|
3081
|
+
if (queryIndex === -1 || queryIndex > hashIndex) return null;
|
|
3082
|
+
return new URLSearchParams(url2.substring(queryIndex + 1, hashIndex)).get(STATE_PARAM2);
|
|
3083
|
+
}
|
|
3008
3084
|
function buildAuthLoginUrl(nodeUrl, opts) {
|
|
3085
|
+
const callback = new URL(opts.callbackUrl);
|
|
3086
|
+
callback.searchParams.set(STATE_PARAM2, beginState(PENDING_KEY, {}));
|
|
3009
3087
|
const params = new URLSearchParams();
|
|
3010
|
-
params.set("callback-url",
|
|
3088
|
+
params.set("callback-url", callback.toString());
|
|
3011
3089
|
if (opts.permissions && opts.permissions.length > 0) {
|
|
3012
3090
|
params.set("permissions", opts.permissions.join(","));
|
|
3013
3091
|
}
|
|
@@ -3250,7 +3328,8 @@ var SseClient = class {
|
|
|
3250
3328
|
return;
|
|
3251
3329
|
}
|
|
3252
3330
|
this.closed = false;
|
|
3253
|
-
|
|
3331
|
+
const controller = new AbortController();
|
|
3332
|
+
this.abortController = controller;
|
|
3254
3333
|
try {
|
|
3255
3334
|
const url2 = `${this.baseUrl}/sse`;
|
|
3256
3335
|
const headers = {
|
|
@@ -3260,7 +3339,7 @@ var SseClient = class {
|
|
|
3260
3339
|
const proofAttached = carriesProof(headers);
|
|
3261
3340
|
const response = await this.fetchImpl(url2, {
|
|
3262
3341
|
headers,
|
|
3263
|
-
signal:
|
|
3342
|
+
signal: controller.signal,
|
|
3264
3343
|
...proofAttached && { redirect: "manual" }
|
|
3265
3344
|
});
|
|
3266
3345
|
if (proofAttached) refuseProofRedirect(response, url2);
|
|
@@ -3288,21 +3367,31 @@ var SseClient = class {
|
|
|
3288
3367
|
if (!response.body) {
|
|
3289
3368
|
throw new Error("SSE response has no body");
|
|
3290
3369
|
}
|
|
3291
|
-
this.readStream(response.body).catch((err) => {
|
|
3292
|
-
if (this.
|
|
3370
|
+
this.readStream(response.body, controller).catch((err) => {
|
|
3371
|
+
if (this.superseded(controller)) return;
|
|
3293
3372
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
3294
3373
|
this.emit("error", error);
|
|
3295
3374
|
this.scheduleReconnect();
|
|
3296
3375
|
});
|
|
3297
3376
|
} catch (err) {
|
|
3298
|
-
if (this.
|
|
3377
|
+
if (this.superseded(controller)) return;
|
|
3299
3378
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
3300
3379
|
this.emit("error", error);
|
|
3301
3380
|
if (error instanceof AuthRevokedError) return;
|
|
3302
3381
|
this.scheduleReconnect();
|
|
3303
3382
|
}
|
|
3304
3383
|
}
|
|
3305
|
-
|
|
3384
|
+
/**
|
|
3385
|
+
* Whether `controller`'s connection is no longer the one this client holds:
|
|
3386
|
+
* closed, or replaced by a later connect. Its abort lands asynchronously,
|
|
3387
|
+
* after a `close()` + `connect()` has already cleared `closed`, so `closed`
|
|
3388
|
+
* alone cannot tell; reporting it, and reconnecting for it, would abort the
|
|
3389
|
+
* live connection, whose own abort would do the same, without end.
|
|
3390
|
+
*/
|
|
3391
|
+
superseded(controller) {
|
|
3392
|
+
return this.closed || controller.signal.aborted || this.abortController !== controller;
|
|
3393
|
+
}
|
|
3394
|
+
async readStream(body, controller) {
|
|
3306
3395
|
const reader = body.getReader();
|
|
3307
3396
|
const decoder = new TextDecoder();
|
|
3308
3397
|
let buffer = "";
|
|
@@ -3330,11 +3419,11 @@ var SseClient = class {
|
|
|
3330
3419
|
}
|
|
3331
3420
|
}
|
|
3332
3421
|
} catch (err) {
|
|
3333
|
-
if (this.
|
|
3422
|
+
if (this.superseded(controller)) return;
|
|
3334
3423
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
3335
3424
|
this.emit("error", error);
|
|
3336
3425
|
}
|
|
3337
|
-
if (!this.
|
|
3426
|
+
if (!this.superseded(controller)) {
|
|
3338
3427
|
this.scheduleReconnect();
|
|
3339
3428
|
}
|
|
3340
3429
|
}
|
|
@@ -9423,12 +9512,12 @@ function createMeroClient(config) {
|
|
|
9423
9512
|
}
|
|
9424
9513
|
|
|
9425
9514
|
// src/account/session.ts
|
|
9426
|
-
var
|
|
9515
|
+
var KEY2 = "calimero.delegated.connection";
|
|
9427
9516
|
var CREDENTIAL_KEY = "calimero.delegated.credential";
|
|
9428
9517
|
function readDelegatedSession() {
|
|
9429
9518
|
if (typeof sessionStorage === "undefined") return null;
|
|
9430
9519
|
try {
|
|
9431
|
-
const raw = sessionStorage.getItem(
|
|
9520
|
+
const raw = sessionStorage.getItem(KEY2);
|
|
9432
9521
|
return raw ? JSON.parse(raw) : null;
|
|
9433
9522
|
} catch {
|
|
9434
9523
|
return null;
|
|
@@ -9436,13 +9525,13 @@ function readDelegatedSession() {
|
|
|
9436
9525
|
}
|
|
9437
9526
|
function saveDelegatedSession(s) {
|
|
9438
9527
|
try {
|
|
9439
|
-
sessionStorage.setItem(
|
|
9528
|
+
sessionStorage.setItem(KEY2, JSON.stringify(s));
|
|
9440
9529
|
} catch {
|
|
9441
9530
|
}
|
|
9442
9531
|
}
|
|
9443
9532
|
function clearDelegatedSession() {
|
|
9444
9533
|
try {
|
|
9445
|
-
sessionStorage.removeItem(
|
|
9534
|
+
sessionStorage.removeItem(KEY2);
|
|
9446
9535
|
} catch {
|
|
9447
9536
|
}
|
|
9448
9537
|
}
|
|
@@ -10240,64 +10329,6 @@ function base643(bytes) {
|
|
|
10240
10329
|
return btoa(binary);
|
|
10241
10330
|
}
|
|
10242
10331
|
|
|
10243
|
-
// src/cloud/link-state.ts
|
|
10244
|
-
var KEY2 = "calimero.cloud.link.pending";
|
|
10245
|
-
var STATE_PARAM = "link_state";
|
|
10246
|
-
var LIFETIME_MS = 10 * 60 * 1e3;
|
|
10247
|
-
var ACCOUNT_HEX = /^[0-9a-f]{64}$/;
|
|
10248
|
-
var MAX_PENDING = 20;
|
|
10249
|
-
function readPending(now) {
|
|
10250
|
-
try {
|
|
10251
|
-
const raw = localStorage.getItem(KEY2);
|
|
10252
|
-
const pending = raw ? JSON.parse(raw) : {};
|
|
10253
|
-
return Object.fromEntries(
|
|
10254
|
-
Object.entries(pending).filter(
|
|
10255
|
-
([, p]) => p && typeof p.expiresAt === "number" && p.expiresAt > now
|
|
10256
|
-
)
|
|
10257
|
-
);
|
|
10258
|
-
} catch {
|
|
10259
|
-
return {};
|
|
10260
|
-
}
|
|
10261
|
-
}
|
|
10262
|
-
function writePending(pending) {
|
|
10263
|
-
try {
|
|
10264
|
-
localStorage.setItem(KEY2, JSON.stringify(pending));
|
|
10265
|
-
} catch {
|
|
10266
|
-
}
|
|
10267
|
-
}
|
|
10268
|
-
function beginLinkState(account, returnTo) {
|
|
10269
|
-
if (!ACCOUNT_HEX.test(account)) {
|
|
10270
|
-
throw new Error("accountId must be 64 lowercase hex characters");
|
|
10271
|
-
}
|
|
10272
|
-
const url2 = new URL(returnTo);
|
|
10273
|
-
const now = Date.now();
|
|
10274
|
-
const pending = readPending(now);
|
|
10275
|
-
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
10276
|
-
const state = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
|
|
10277
|
-
""
|
|
10278
|
-
);
|
|
10279
|
-
pending[state] = { account, expiresAt: now + LIFETIME_MS };
|
|
10280
|
-
writePending(Object.fromEntries(Object.entries(pending).slice(-MAX_PENDING)));
|
|
10281
|
-
url2.searchParams.set(STATE_PARAM, state);
|
|
10282
|
-
return url2.toString();
|
|
10283
|
-
}
|
|
10284
|
-
function takeLinkState(search) {
|
|
10285
|
-
const state = new URLSearchParams(search).get(STATE_PARAM);
|
|
10286
|
-
const pending = readPending(Date.now());
|
|
10287
|
-
if (!state || !Object.prototype.hasOwnProperty.call(pending, state))
|
|
10288
|
-
return null;
|
|
10289
|
-
const entry = pending[state];
|
|
10290
|
-
delete pending[state];
|
|
10291
|
-
writePending(pending);
|
|
10292
|
-
return entry;
|
|
10293
|
-
}
|
|
10294
|
-
function withoutLinkState(search) {
|
|
10295
|
-
const params = new URLSearchParams(search);
|
|
10296
|
-
params.delete(STATE_PARAM);
|
|
10297
|
-
const rest = params.toString();
|
|
10298
|
-
return rest ? `?${rest}` : "";
|
|
10299
|
-
}
|
|
10300
|
-
|
|
10301
10332
|
// src/cloud/link-redirect.ts
|
|
10302
10333
|
var DEFAULT_CLOUD_WEB_URL = "https://cloud.calimero.network";
|
|
10303
10334
|
function cloudLinkUrl(options) {
|
|
@@ -10354,7 +10385,8 @@ async function completeCloudLink(options) {
|
|
|
10354
10385
|
var DEVICE_PARAM = "enrol-device";
|
|
10355
10386
|
var KEM_PARAM = "enrol-kem";
|
|
10356
10387
|
var CALLBACK_PARAM = "callback-url";
|
|
10357
|
-
var
|
|
10388
|
+
var STATE_PARAM3 = "state";
|
|
10389
|
+
var PENDING_KEY2 = "calimero.device.enrol.pending";
|
|
10358
10390
|
function deviceEnrolmentUrl(options) {
|
|
10359
10391
|
assertHex(options.devicePublicKey, "devicePublicKey");
|
|
10360
10392
|
assertHex(options.kemPublicKey, "kemPublicKey");
|
|
@@ -10364,7 +10396,7 @@ function deviceEnrolmentUrl(options) {
|
|
|
10364
10396
|
url2.searchParams.set(DEVICE_PARAM, options.devicePublicKey);
|
|
10365
10397
|
url2.searchParams.set(KEM_PARAM, options.kemPublicKey);
|
|
10366
10398
|
url2.searchParams.set(CALLBACK_PARAM, options.returnTo);
|
|
10367
|
-
|
|
10399
|
+
url2.searchParams.set(STATE_PARAM3, beginState(PENDING_KEY2, {}));
|
|
10368
10400
|
return url2.toString();
|
|
10369
10401
|
}
|
|
10370
10402
|
function beginDeviceEnrolment(options) {
|
|
@@ -10375,6 +10407,7 @@ function readEnrolmentCallback(location2 = window.location) {
|
|
|
10375
10407
|
const params = new URLSearchParams(location2.hash.replace(/^#/, ""));
|
|
10376
10408
|
const error = params.get("error");
|
|
10377
10409
|
if (error) {
|
|
10410
|
+
takeState(PENDING_KEY2, params.get(STATE_PARAM3));
|
|
10378
10411
|
stripFragment2();
|
|
10379
10412
|
throw new Error(
|
|
10380
10413
|
error === "denied" || error === "cancelled" ? "the device was not approved at the wallet" : `the wallet refused this enrolment: ${error}`
|
|
@@ -10393,9 +10426,9 @@ function stripFragment2() {
|
|
|
10393
10426
|
history.replaceState(null, "", location.pathname + location.search);
|
|
10394
10427
|
}
|
|
10395
10428
|
async function completeDeviceEnrolment(options) {
|
|
10396
|
-
if (
|
|
10429
|
+
if (!takeState(PENDING_KEY2, options.state)) {
|
|
10397
10430
|
throw new Error(
|
|
10398
|
-
"this enrolment answers a request this app did not make
|
|
10431
|
+
"this enrolment answers a request this app did not make - start the enrolment again from here"
|
|
10399
10432
|
);
|
|
10400
10433
|
}
|
|
10401
10434
|
const certificate = await verifyDeviceCredential(options.credential);
|
|
@@ -12788,8 +12821,8 @@ var MeroJs = class {
|
|
|
12788
12821
|
/**
|
|
12789
12822
|
* Parse an auth callback URL hash fragment (static utility)
|
|
12790
12823
|
*/
|
|
12791
|
-
static parseAuthCallback(url2) {
|
|
12792
|
-
return parseAuthCallback(url2);
|
|
12824
|
+
static parseAuthCallback(url2, options) {
|
|
12825
|
+
return parseAuthCallback(url2, options);
|
|
12793
12826
|
}
|
|
12794
12827
|
/**
|
|
12795
12828
|
* Build an auth login URL (static utility)
|