@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.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
|
-
|
|
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",
|
|
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
|
}
|
|
@@ -2950,7 +3028,8 @@ var SseClient = class {
|
|
|
2950
3028
|
return;
|
|
2951
3029
|
}
|
|
2952
3030
|
this.closed = false;
|
|
2953
|
-
|
|
3031
|
+
const controller = new AbortController();
|
|
3032
|
+
this.abortController = controller;
|
|
2954
3033
|
try {
|
|
2955
3034
|
const url2 = `${this.baseUrl}/sse`;
|
|
2956
3035
|
const headers = {
|
|
@@ -2960,7 +3039,7 @@ var SseClient = class {
|
|
|
2960
3039
|
const proofAttached = carriesProof(headers);
|
|
2961
3040
|
const response = await this.fetchImpl(url2, {
|
|
2962
3041
|
headers,
|
|
2963
|
-
signal:
|
|
3042
|
+
signal: controller.signal,
|
|
2964
3043
|
...proofAttached && { redirect: "manual" }
|
|
2965
3044
|
});
|
|
2966
3045
|
if (proofAttached) refuseProofRedirect(response, url2);
|
|
@@ -2988,21 +3067,31 @@ var SseClient = class {
|
|
|
2988
3067
|
if (!response.body) {
|
|
2989
3068
|
throw new Error("SSE response has no body");
|
|
2990
3069
|
}
|
|
2991
|
-
this.readStream(response.body).catch((err) => {
|
|
2992
|
-
if (this.
|
|
3070
|
+
this.readStream(response.body, controller).catch((err) => {
|
|
3071
|
+
if (this.superseded(controller)) return;
|
|
2993
3072
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
2994
3073
|
this.emit("error", error);
|
|
2995
3074
|
this.scheduleReconnect();
|
|
2996
3075
|
});
|
|
2997
3076
|
} catch (err) {
|
|
2998
|
-
if (this.
|
|
3077
|
+
if (this.superseded(controller)) return;
|
|
2999
3078
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
3000
3079
|
this.emit("error", error);
|
|
3001
3080
|
if (error instanceof AuthRevokedError) return;
|
|
3002
3081
|
this.scheduleReconnect();
|
|
3003
3082
|
}
|
|
3004
3083
|
}
|
|
3005
|
-
|
|
3084
|
+
/**
|
|
3085
|
+
* Whether `controller`'s connection is no longer the one this client holds:
|
|
3086
|
+
* closed, or replaced by a later connect. Its abort lands asynchronously,
|
|
3087
|
+
* after a `close()` + `connect()` has already cleared `closed`, so `closed`
|
|
3088
|
+
* alone cannot tell; reporting it, and reconnecting for it, would abort the
|
|
3089
|
+
* live connection, whose own abort would do the same, without end.
|
|
3090
|
+
*/
|
|
3091
|
+
superseded(controller) {
|
|
3092
|
+
return this.closed || controller.signal.aborted || this.abortController !== controller;
|
|
3093
|
+
}
|
|
3094
|
+
async readStream(body, controller) {
|
|
3006
3095
|
const reader = body.getReader();
|
|
3007
3096
|
const decoder = new TextDecoder();
|
|
3008
3097
|
let buffer = "";
|
|
@@ -3030,11 +3119,11 @@ var SseClient = class {
|
|
|
3030
3119
|
}
|
|
3031
3120
|
}
|
|
3032
3121
|
} catch (err) {
|
|
3033
|
-
if (this.
|
|
3122
|
+
if (this.superseded(controller)) return;
|
|
3034
3123
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
3035
3124
|
this.emit("error", error);
|
|
3036
3125
|
}
|
|
3037
|
-
if (!this.
|
|
3126
|
+
if (!this.superseded(controller)) {
|
|
3038
3127
|
this.scheduleReconnect();
|
|
3039
3128
|
}
|
|
3040
3129
|
}
|
|
@@ -9123,12 +9212,12 @@ function createMeroClient(config) {
|
|
|
9123
9212
|
}
|
|
9124
9213
|
|
|
9125
9214
|
// src/account/session.ts
|
|
9126
|
-
var
|
|
9215
|
+
var KEY2 = "calimero.delegated.connection";
|
|
9127
9216
|
var CREDENTIAL_KEY = "calimero.delegated.credential";
|
|
9128
9217
|
function readDelegatedSession() {
|
|
9129
9218
|
if (typeof sessionStorage === "undefined") return null;
|
|
9130
9219
|
try {
|
|
9131
|
-
const raw = sessionStorage.getItem(
|
|
9220
|
+
const raw = sessionStorage.getItem(KEY2);
|
|
9132
9221
|
return raw ? JSON.parse(raw) : null;
|
|
9133
9222
|
} catch {
|
|
9134
9223
|
return null;
|
|
@@ -9136,13 +9225,13 @@ function readDelegatedSession() {
|
|
|
9136
9225
|
}
|
|
9137
9226
|
function saveDelegatedSession(s) {
|
|
9138
9227
|
try {
|
|
9139
|
-
sessionStorage.setItem(
|
|
9228
|
+
sessionStorage.setItem(KEY2, JSON.stringify(s));
|
|
9140
9229
|
} catch {
|
|
9141
9230
|
}
|
|
9142
9231
|
}
|
|
9143
9232
|
function clearDelegatedSession() {
|
|
9144
9233
|
try {
|
|
9145
|
-
sessionStorage.removeItem(
|
|
9234
|
+
sessionStorage.removeItem(KEY2);
|
|
9146
9235
|
} catch {
|
|
9147
9236
|
}
|
|
9148
9237
|
}
|
|
@@ -9940,64 +10029,6 @@ function base643(bytes) {
|
|
|
9940
10029
|
return btoa(binary);
|
|
9941
10030
|
}
|
|
9942
10031
|
|
|
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
10032
|
// src/cloud/link-redirect.ts
|
|
10002
10033
|
var DEFAULT_CLOUD_WEB_URL = "https://cloud.calimero.network";
|
|
10003
10034
|
function cloudLinkUrl(options) {
|
|
@@ -10054,7 +10085,8 @@ async function completeCloudLink(options) {
|
|
|
10054
10085
|
var DEVICE_PARAM = "enrol-device";
|
|
10055
10086
|
var KEM_PARAM = "enrol-kem";
|
|
10056
10087
|
var CALLBACK_PARAM = "callback-url";
|
|
10057
|
-
var
|
|
10088
|
+
var STATE_PARAM3 = "state";
|
|
10089
|
+
var PENDING_KEY2 = "calimero.device.enrol.pending";
|
|
10058
10090
|
function deviceEnrolmentUrl(options) {
|
|
10059
10091
|
assertHex(options.devicePublicKey, "devicePublicKey");
|
|
10060
10092
|
assertHex(options.kemPublicKey, "kemPublicKey");
|
|
@@ -10064,7 +10096,7 @@ function deviceEnrolmentUrl(options) {
|
|
|
10064
10096
|
url2.searchParams.set(DEVICE_PARAM, options.devicePublicKey);
|
|
10065
10097
|
url2.searchParams.set(KEM_PARAM, options.kemPublicKey);
|
|
10066
10098
|
url2.searchParams.set(CALLBACK_PARAM, options.returnTo);
|
|
10067
|
-
|
|
10099
|
+
url2.searchParams.set(STATE_PARAM3, beginState(PENDING_KEY2, {}));
|
|
10068
10100
|
return url2.toString();
|
|
10069
10101
|
}
|
|
10070
10102
|
function beginDeviceEnrolment(options) {
|
|
@@ -10075,6 +10107,7 @@ function readEnrolmentCallback(location2 = window.location) {
|
|
|
10075
10107
|
const params = new URLSearchParams(location2.hash.replace(/^#/, ""));
|
|
10076
10108
|
const error = params.get("error");
|
|
10077
10109
|
if (error) {
|
|
10110
|
+
takeState(PENDING_KEY2, params.get(STATE_PARAM3));
|
|
10078
10111
|
stripFragment2();
|
|
10079
10112
|
throw new Error(
|
|
10080
10113
|
error === "denied" || error === "cancelled" ? "the device was not approved at the wallet" : `the wallet refused this enrolment: ${error}`
|
|
@@ -10093,9 +10126,9 @@ function stripFragment2() {
|
|
|
10093
10126
|
history.replaceState(null, "", location.pathname + location.search);
|
|
10094
10127
|
}
|
|
10095
10128
|
async function completeDeviceEnrolment(options) {
|
|
10096
|
-
if (
|
|
10129
|
+
if (!takeState(PENDING_KEY2, options.state)) {
|
|
10097
10130
|
throw new Error(
|
|
10098
|
-
"this enrolment answers a request this app did not make
|
|
10131
|
+
"this enrolment answers a request this app did not make - start the enrolment again from here"
|
|
10099
10132
|
);
|
|
10100
10133
|
}
|
|
10101
10134
|
const certificate = await verifyDeviceCredential(options.credential);
|
|
@@ -12488,8 +12521,8 @@ var MeroJs = class {
|
|
|
12488
12521
|
/**
|
|
12489
12522
|
* Parse an auth callback URL hash fragment (static utility)
|
|
12490
12523
|
*/
|
|
12491
|
-
static parseAuthCallback(url2) {
|
|
12492
|
-
return parseAuthCallback(url2);
|
|
12524
|
+
static parseAuthCallback(url2, options) {
|
|
12525
|
+
return parseAuthCallback(url2, options);
|
|
12493
12526
|
}
|
|
12494
12527
|
/**
|
|
12495
12528
|
* Build an auth login URL (static utility)
|