@calimero-network/mero-js 25.3.1 → 25.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
- function parseAuthCallback(url2) {
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", opts.callbackUrl);
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
  }
@@ -9423,12 +9501,12 @@ function createMeroClient(config) {
9423
9501
  }
9424
9502
 
9425
9503
  // src/account/session.ts
9426
- var KEY = "calimero.delegated.connection";
9504
+ var KEY2 = "calimero.delegated.connection";
9427
9505
  var CREDENTIAL_KEY = "calimero.delegated.credential";
9428
9506
  function readDelegatedSession() {
9429
9507
  if (typeof sessionStorage === "undefined") return null;
9430
9508
  try {
9431
- const raw = sessionStorage.getItem(KEY);
9509
+ const raw = sessionStorage.getItem(KEY2);
9432
9510
  return raw ? JSON.parse(raw) : null;
9433
9511
  } catch {
9434
9512
  return null;
@@ -9436,13 +9514,13 @@ function readDelegatedSession() {
9436
9514
  }
9437
9515
  function saveDelegatedSession(s) {
9438
9516
  try {
9439
- sessionStorage.setItem(KEY, JSON.stringify(s));
9517
+ sessionStorage.setItem(KEY2, JSON.stringify(s));
9440
9518
  } catch {
9441
9519
  }
9442
9520
  }
9443
9521
  function clearDelegatedSession() {
9444
9522
  try {
9445
- sessionStorage.removeItem(KEY);
9523
+ sessionStorage.removeItem(KEY2);
9446
9524
  } catch {
9447
9525
  }
9448
9526
  }
@@ -10240,64 +10318,6 @@ function base643(bytes) {
10240
10318
  return btoa(binary);
10241
10319
  }
10242
10320
 
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
10321
  // src/cloud/link-redirect.ts
10302
10322
  var DEFAULT_CLOUD_WEB_URL = "https://cloud.calimero.network";
10303
10323
  function cloudLinkUrl(options) {
@@ -10354,7 +10374,8 @@ async function completeCloudLink(options) {
10354
10374
  var DEVICE_PARAM = "enrol-device";
10355
10375
  var KEM_PARAM = "enrol-kem";
10356
10376
  var CALLBACK_PARAM = "callback-url";
10357
- var STATE_PARAM2 = "state";
10377
+ var STATE_PARAM3 = "state";
10378
+ var PENDING_KEY2 = "calimero.device.enrol.pending";
10358
10379
  function deviceEnrolmentUrl(options) {
10359
10380
  assertHex(options.devicePublicKey, "devicePublicKey");
10360
10381
  assertHex(options.kemPublicKey, "kemPublicKey");
@@ -10364,7 +10385,7 @@ function deviceEnrolmentUrl(options) {
10364
10385
  url2.searchParams.set(DEVICE_PARAM, options.devicePublicKey);
10365
10386
  url2.searchParams.set(KEM_PARAM, options.kemPublicKey);
10366
10387
  url2.searchParams.set(CALLBACK_PARAM, options.returnTo);
10367
- if (options.state) url2.searchParams.set(STATE_PARAM2, options.state);
10388
+ url2.searchParams.set(STATE_PARAM3, beginState(PENDING_KEY2, {}));
10368
10389
  return url2.toString();
10369
10390
  }
10370
10391
  function beginDeviceEnrolment(options) {
@@ -10375,6 +10396,7 @@ function readEnrolmentCallback(location2 = window.location) {
10375
10396
  const params = new URLSearchParams(location2.hash.replace(/^#/, ""));
10376
10397
  const error = params.get("error");
10377
10398
  if (error) {
10399
+ takeState(PENDING_KEY2, params.get(STATE_PARAM3));
10378
10400
  stripFragment2();
10379
10401
  throw new Error(
10380
10402
  error === "denied" || error === "cancelled" ? "the device was not approved at the wallet" : `the wallet refused this enrolment: ${error}`
@@ -10393,9 +10415,9 @@ function stripFragment2() {
10393
10415
  history.replaceState(null, "", location.pathname + location.search);
10394
10416
  }
10395
10417
  async function completeDeviceEnrolment(options) {
10396
- if (options.expectState !== void 0 && options.expectState !== options.state) {
10418
+ if (!takeState(PENDING_KEY2, options.state)) {
10397
10419
  throw new Error(
10398
- "this enrolment answers a request this app did not make \u2014 the state it carries is not the one sent"
10420
+ "this enrolment answers a request this app did not make - start the enrolment again from here"
10399
10421
  );
10400
10422
  }
10401
10423
  const certificate = await verifyDeviceCredential(options.credential);
@@ -12788,8 +12810,8 @@ var MeroJs = class {
12788
12810
  /**
12789
12811
  * Parse an auth callback URL hash fragment (static utility)
12790
12812
  */
12791
- static parseAuthCallback(url2) {
12792
- return parseAuthCallback(url2);
12813
+ static parseAuthCallback(url2, options) {
12814
+ return parseAuthCallback(url2, options);
12793
12815
  }
12794
12816
  /**
12795
12817
  * Build an auth login URL (static utility)