@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.d.ts CHANGED
@@ -5,7 +5,7 @@ export * from "./auth-api/index.js";
5
5
  export * from "./admin-api/index.js";
6
6
  export * from "./errors/index.js";
7
7
  export { parseAuthCallback, buildAuthLoginUrl } from "./auth/index.js";
8
- export type { AuthCallbackResult, AuthLoginOptions } from "./auth/index.js";
8
+ export type { AuthCallbackResult, AuthLoginOptions, ParseAuthCallbackOptions, } from "./auth/index.js";
9
9
  export { MemoryTokenStore, LocalStorageTokenStore, } from "./token-store/index.js";
10
10
  export type { TokenStore } from "./token-store/index.js";
11
11
  export { RpcClient, RpcError } from "./rpc/index.js";
package/dist/index.mjs CHANGED
@@ -2684,8 +2684,73 @@ function createAdminApiClientFromHttpClient(httpClient, _config) {
2684
2684
  return new AdminApiClient(httpClient);
2685
2685
  }
2686
2686
 
2687
+ // src/cloud/link-state.ts
2688
+ var KEY = "calimero.cloud.link.pending";
2689
+ var STATE_PARAM = "link_state";
2690
+ var LIFETIME_MS = 10 * 60 * 1e3;
2691
+ var ACCOUNT_HEX = /^[0-9a-f]{64}$/;
2692
+ var MAX_PENDING = 20;
2693
+ function readPending(key, now) {
2694
+ try {
2695
+ const raw = localStorage.getItem(key);
2696
+ const pending = raw ? JSON.parse(raw) : {};
2697
+ return Object.fromEntries(
2698
+ Object.entries(pending).filter(
2699
+ ([, p]) => p && typeof p.expiresAt === "number" && p.expiresAt > now
2700
+ )
2701
+ );
2702
+ } catch {
2703
+ return {};
2704
+ }
2705
+ }
2706
+ function writePending(key, pending) {
2707
+ try {
2708
+ localStorage.setItem(key, JSON.stringify(pending));
2709
+ } catch {
2710
+ }
2711
+ }
2712
+ function beginState(key, payload) {
2713
+ const now = Date.now();
2714
+ const pending = readPending(key, now);
2715
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
2716
+ const state = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
2717
+ ""
2718
+ );
2719
+ pending[state] = { ...payload, expiresAt: now + LIFETIME_MS };
2720
+ writePending(key, Object.fromEntries(Object.entries(pending).slice(-MAX_PENDING)));
2721
+ return state;
2722
+ }
2723
+ function takeState(key, state) {
2724
+ const pending = readPending(key, Date.now());
2725
+ if (!state || !Object.prototype.hasOwnProperty.call(pending, state))
2726
+ return null;
2727
+ const entry = pending[state];
2728
+ delete pending[state];
2729
+ writePending(key, pending);
2730
+ return entry;
2731
+ }
2732
+ function beginLinkState(account, returnTo) {
2733
+ if (!ACCOUNT_HEX.test(account)) {
2734
+ throw new Error("accountId must be 64 lowercase hex characters");
2735
+ }
2736
+ const url2 = new URL(returnTo);
2737
+ url2.searchParams.set(STATE_PARAM, beginState(KEY, { account }));
2738
+ return url2.toString();
2739
+ }
2740
+ function takeLinkState(search) {
2741
+ return takeState(KEY, new URLSearchParams(search).get(STATE_PARAM));
2742
+ }
2743
+ function withoutLinkState(search) {
2744
+ const params = new URLSearchParams(search);
2745
+ params.delete(STATE_PARAM);
2746
+ const rest = params.toString();
2747
+ return rest ? `?${rest}` : "";
2748
+ }
2749
+
2687
2750
  // src/auth/index.ts
2688
- function parseAuthCallback(url2) {
2751
+ var PENDING_KEY = "calimero.auth.login.pending";
2752
+ var STATE_PARAM2 = "auth_state";
2753
+ function parseAuthCallback(url2, options = {}) {
2689
2754
  try {
2690
2755
  const hashIndex = url2.indexOf("#");
2691
2756
  if (hashIndex === -1) return null;
@@ -2693,6 +2758,12 @@ function parseAuthCallback(url2) {
2693
2758
  const params = new URLSearchParams(hash);
2694
2759
  const accessToken = params.get("access_token");
2695
2760
  if (!accessToken) return null;
2761
+ if (!options.allowUnsolicited && !takeState(PENDING_KEY, stateOf(url2, hashIndex))) {
2762
+ console.warn(
2763
+ "[mero-js] ignoring auth callback tokens: they answer no login begun in this browser"
2764
+ );
2765
+ return null;
2766
+ }
2696
2767
  return {
2697
2768
  accessToken,
2698
2769
  refreshToken: params.get("refresh_token") ?? "",
@@ -2705,9 +2776,16 @@ function parseAuthCallback(url2) {
2705
2776
  return null;
2706
2777
  }
2707
2778
  }
2779
+ function stateOf(url2, hashIndex) {
2780
+ const queryIndex = url2.indexOf("?");
2781
+ if (queryIndex === -1 || queryIndex > hashIndex) return null;
2782
+ return new URLSearchParams(url2.substring(queryIndex + 1, hashIndex)).get(STATE_PARAM2);
2783
+ }
2708
2784
  function buildAuthLoginUrl(nodeUrl, opts) {
2785
+ const callback = new URL(opts.callbackUrl);
2786
+ callback.searchParams.set(STATE_PARAM2, beginState(PENDING_KEY, {}));
2709
2787
  const params = new URLSearchParams();
2710
- params.set("callback-url", opts.callbackUrl);
2788
+ params.set("callback-url", callback.toString());
2711
2789
  if (opts.permissions && opts.permissions.length > 0) {
2712
2790
  params.set("permissions", opts.permissions.join(","));
2713
2791
  }
@@ -9123,12 +9201,12 @@ function createMeroClient(config) {
9123
9201
  }
9124
9202
 
9125
9203
  // src/account/session.ts
9126
- var KEY = "calimero.delegated.connection";
9204
+ var KEY2 = "calimero.delegated.connection";
9127
9205
  var CREDENTIAL_KEY = "calimero.delegated.credential";
9128
9206
  function readDelegatedSession() {
9129
9207
  if (typeof sessionStorage === "undefined") return null;
9130
9208
  try {
9131
- const raw = sessionStorage.getItem(KEY);
9209
+ const raw = sessionStorage.getItem(KEY2);
9132
9210
  return raw ? JSON.parse(raw) : null;
9133
9211
  } catch {
9134
9212
  return null;
@@ -9136,13 +9214,13 @@ function readDelegatedSession() {
9136
9214
  }
9137
9215
  function saveDelegatedSession(s) {
9138
9216
  try {
9139
- sessionStorage.setItem(KEY, JSON.stringify(s));
9217
+ sessionStorage.setItem(KEY2, JSON.stringify(s));
9140
9218
  } catch {
9141
9219
  }
9142
9220
  }
9143
9221
  function clearDelegatedSession() {
9144
9222
  try {
9145
- sessionStorage.removeItem(KEY);
9223
+ sessionStorage.removeItem(KEY2);
9146
9224
  } catch {
9147
9225
  }
9148
9226
  }
@@ -9940,64 +10018,6 @@ function base643(bytes) {
9940
10018
  return btoa(binary);
9941
10019
  }
9942
10020
 
9943
- // src/cloud/link-state.ts
9944
- var KEY2 = "calimero.cloud.link.pending";
9945
- var STATE_PARAM = "link_state";
9946
- var LIFETIME_MS = 10 * 60 * 1e3;
9947
- var ACCOUNT_HEX = /^[0-9a-f]{64}$/;
9948
- var MAX_PENDING = 20;
9949
- function readPending(now) {
9950
- try {
9951
- const raw = localStorage.getItem(KEY2);
9952
- const pending = raw ? JSON.parse(raw) : {};
9953
- return Object.fromEntries(
9954
- Object.entries(pending).filter(
9955
- ([, p]) => p && typeof p.expiresAt === "number" && p.expiresAt > now
9956
- )
9957
- );
9958
- } catch {
9959
- return {};
9960
- }
9961
- }
9962
- function writePending(pending) {
9963
- try {
9964
- localStorage.setItem(KEY2, JSON.stringify(pending));
9965
- } catch {
9966
- }
9967
- }
9968
- function beginLinkState(account, returnTo) {
9969
- if (!ACCOUNT_HEX.test(account)) {
9970
- throw new Error("accountId must be 64 lowercase hex characters");
9971
- }
9972
- const url2 = new URL(returnTo);
9973
- const now = Date.now();
9974
- const pending = readPending(now);
9975
- const bytes = crypto.getRandomValues(new Uint8Array(16));
9976
- const state = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
9977
- ""
9978
- );
9979
- pending[state] = { account, expiresAt: now + LIFETIME_MS };
9980
- writePending(Object.fromEntries(Object.entries(pending).slice(-MAX_PENDING)));
9981
- url2.searchParams.set(STATE_PARAM, state);
9982
- return url2.toString();
9983
- }
9984
- function takeLinkState(search) {
9985
- const state = new URLSearchParams(search).get(STATE_PARAM);
9986
- const pending = readPending(Date.now());
9987
- if (!state || !Object.prototype.hasOwnProperty.call(pending, state))
9988
- return null;
9989
- const entry = pending[state];
9990
- delete pending[state];
9991
- writePending(pending);
9992
- return entry;
9993
- }
9994
- function withoutLinkState(search) {
9995
- const params = new URLSearchParams(search);
9996
- params.delete(STATE_PARAM);
9997
- const rest = params.toString();
9998
- return rest ? `?${rest}` : "";
9999
- }
10000
-
10001
10021
  // src/cloud/link-redirect.ts
10002
10022
  var DEFAULT_CLOUD_WEB_URL = "https://cloud.calimero.network";
10003
10023
  function cloudLinkUrl(options) {
@@ -10054,7 +10074,8 @@ async function completeCloudLink(options) {
10054
10074
  var DEVICE_PARAM = "enrol-device";
10055
10075
  var KEM_PARAM = "enrol-kem";
10056
10076
  var CALLBACK_PARAM = "callback-url";
10057
- var STATE_PARAM2 = "state";
10077
+ var STATE_PARAM3 = "state";
10078
+ var PENDING_KEY2 = "calimero.device.enrol.pending";
10058
10079
  function deviceEnrolmentUrl(options) {
10059
10080
  assertHex(options.devicePublicKey, "devicePublicKey");
10060
10081
  assertHex(options.kemPublicKey, "kemPublicKey");
@@ -10064,7 +10085,7 @@ function deviceEnrolmentUrl(options) {
10064
10085
  url2.searchParams.set(DEVICE_PARAM, options.devicePublicKey);
10065
10086
  url2.searchParams.set(KEM_PARAM, options.kemPublicKey);
10066
10087
  url2.searchParams.set(CALLBACK_PARAM, options.returnTo);
10067
- if (options.state) url2.searchParams.set(STATE_PARAM2, options.state);
10088
+ url2.searchParams.set(STATE_PARAM3, beginState(PENDING_KEY2, {}));
10068
10089
  return url2.toString();
10069
10090
  }
10070
10091
  function beginDeviceEnrolment(options) {
@@ -10075,6 +10096,7 @@ function readEnrolmentCallback(location2 = window.location) {
10075
10096
  const params = new URLSearchParams(location2.hash.replace(/^#/, ""));
10076
10097
  const error = params.get("error");
10077
10098
  if (error) {
10099
+ takeState(PENDING_KEY2, params.get(STATE_PARAM3));
10078
10100
  stripFragment2();
10079
10101
  throw new Error(
10080
10102
  error === "denied" || error === "cancelled" ? "the device was not approved at the wallet" : `the wallet refused this enrolment: ${error}`
@@ -10093,9 +10115,9 @@ function stripFragment2() {
10093
10115
  history.replaceState(null, "", location.pathname + location.search);
10094
10116
  }
10095
10117
  async function completeDeviceEnrolment(options) {
10096
- if (options.expectState !== void 0 && options.expectState !== options.state) {
10118
+ if (!takeState(PENDING_KEY2, options.state)) {
10097
10119
  throw new Error(
10098
- "this enrolment answers a request this app did not make \u2014 the state it carries is not the one sent"
10120
+ "this enrolment answers a request this app did not make - start the enrolment again from here"
10099
10121
  );
10100
10122
  }
10101
10123
  const certificate = await verifyDeviceCredential(options.credential);
@@ -12488,8 +12510,8 @@ var MeroJs = class {
12488
12510
  /**
12489
12511
  * Parse an auth callback URL hash fragment (static utility)
12490
12512
  */
12491
- static parseAuthCallback(url2) {
12492
- return parseAuthCallback(url2);
12513
+ static parseAuthCallback(url2, options) {
12514
+ return parseAuthCallback(url2, options);
12493
12515
  }
12494
12516
  /**
12495
12517
  * Build an auth login URL (static utility)