@calimero-network/mero-js 25.0.3 → 25.0.4
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/cloud/cloud-client.d.ts +5 -1
- package/dist/cloud/cloud-client.js +13 -3
- package/dist/cloud/link-redirect.d.ts +4 -3
- package/dist/cloud/link-redirect.js +11 -8
- package/dist/cloud/link-state.d.ts +13 -0
- package/dist/cloud/link-state.js +60 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +80 -10
- package/dist/index.cjs.map +4 -4
- package/dist/index.mjs +80 -10
- package/dist/index.mjs.map +4 -4
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -9906,15 +9906,70 @@ function base643(bytes) {
|
|
|
9906
9906
|
return btoa(binary);
|
|
9907
9907
|
}
|
|
9908
9908
|
|
|
9909
|
+
// src/cloud/link-state.ts
|
|
9910
|
+
var KEY2 = "calimero.cloud.link.pending";
|
|
9911
|
+
var STATE_PARAM = "link_state";
|
|
9912
|
+
var LIFETIME_MS = 10 * 60 * 1e3;
|
|
9913
|
+
var ACCOUNT_HEX = /^[0-9a-f]{64}$/;
|
|
9914
|
+
var MAX_PENDING = 20;
|
|
9915
|
+
function readPending(now) {
|
|
9916
|
+
try {
|
|
9917
|
+
const raw = localStorage.getItem(KEY2);
|
|
9918
|
+
const pending = raw ? JSON.parse(raw) : {};
|
|
9919
|
+
return Object.fromEntries(
|
|
9920
|
+
Object.entries(pending).filter(
|
|
9921
|
+
([, p]) => p && typeof p.expiresAt === "number" && p.expiresAt > now
|
|
9922
|
+
)
|
|
9923
|
+
);
|
|
9924
|
+
} catch {
|
|
9925
|
+
return {};
|
|
9926
|
+
}
|
|
9927
|
+
}
|
|
9928
|
+
function writePending(pending) {
|
|
9929
|
+
try {
|
|
9930
|
+
localStorage.setItem(KEY2, JSON.stringify(pending));
|
|
9931
|
+
} catch {
|
|
9932
|
+
}
|
|
9933
|
+
}
|
|
9934
|
+
function beginLinkState(account, returnTo) {
|
|
9935
|
+
if (!ACCOUNT_HEX.test(account)) {
|
|
9936
|
+
throw new Error("accountId must be 64 lowercase hex characters");
|
|
9937
|
+
}
|
|
9938
|
+
const url = new URL(returnTo);
|
|
9939
|
+
const now = Date.now();
|
|
9940
|
+
const pending = readPending(now);
|
|
9941
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
9942
|
+
const state = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
|
|
9943
|
+
""
|
|
9944
|
+
);
|
|
9945
|
+
pending[state] = { account, expiresAt: now + LIFETIME_MS };
|
|
9946
|
+
writePending(Object.fromEntries(Object.entries(pending).slice(-MAX_PENDING)));
|
|
9947
|
+
url.searchParams.set(STATE_PARAM, state);
|
|
9948
|
+
return url.toString();
|
|
9949
|
+
}
|
|
9950
|
+
function takeLinkState(search) {
|
|
9951
|
+
const state = new URLSearchParams(search).get(STATE_PARAM);
|
|
9952
|
+
const pending = readPending(Date.now());
|
|
9953
|
+
if (!state || !Object.prototype.hasOwnProperty.call(pending, state))
|
|
9954
|
+
return null;
|
|
9955
|
+
const entry = pending[state];
|
|
9956
|
+
delete pending[state];
|
|
9957
|
+
writePending(pending);
|
|
9958
|
+
return entry;
|
|
9959
|
+
}
|
|
9960
|
+
function withoutLinkState(search) {
|
|
9961
|
+
const params = new URLSearchParams(search);
|
|
9962
|
+
params.delete(STATE_PARAM);
|
|
9963
|
+
const rest = params.toString();
|
|
9964
|
+
return rest ? `?${rest}` : "";
|
|
9965
|
+
}
|
|
9966
|
+
|
|
9909
9967
|
// src/cloud/link-redirect.ts
|
|
9910
9968
|
var DEFAULT_CLOUD_WEB_URL = "https://cloud.calimero.network";
|
|
9911
9969
|
function cloudLinkUrl(options) {
|
|
9912
|
-
if (!/^[0-9a-f]{64}$/.test(options.accountId)) {
|
|
9913
|
-
throw new Error("accountId must be 64 lowercase hex characters");
|
|
9914
|
-
}
|
|
9915
9970
|
const url = new URL(options.cloudWebUrl ?? DEFAULT_CLOUD_WEB_URL);
|
|
9916
9971
|
url.searchParams.set("link-account", options.accountId);
|
|
9917
|
-
url.searchParams.set("callback-url", options.returnTo);
|
|
9972
|
+
url.searchParams.set("callback-url", beginLinkState(options.accountId, options.returnTo));
|
|
9918
9973
|
return url.toString();
|
|
9919
9974
|
}
|
|
9920
9975
|
function beginCloudLink(options) {
|
|
@@ -9925,6 +9980,7 @@ function readCloudLinkCallback(location2 = window.location) {
|
|
|
9925
9980
|
const params = new URLSearchParams(location2.hash.replace(/^#/, ""));
|
|
9926
9981
|
const error = params.get("error");
|
|
9927
9982
|
if (error) {
|
|
9983
|
+
takeLinkState(location2.search ?? "");
|
|
9928
9984
|
stripFragment();
|
|
9929
9985
|
throw new Error(
|
|
9930
9986
|
error === "denied" ? "the account link was declined at the cloud" : `the cloud refused this link: ${error}`
|
|
@@ -9933,12 +9989,18 @@ function readCloudLinkCallback(location2 = window.location) {
|
|
|
9933
9989
|
const grant = params.get("grant");
|
|
9934
9990
|
const account = params.get("account");
|
|
9935
9991
|
if (!grant || !account) return null;
|
|
9992
|
+
if (takeLinkState(location2.search ?? "")?.account !== account) {
|
|
9993
|
+
stripFragment();
|
|
9994
|
+
throw new Error(
|
|
9995
|
+
"this account link answers a request this browser did not make - start the link again from here"
|
|
9996
|
+
);
|
|
9997
|
+
}
|
|
9936
9998
|
stripFragment();
|
|
9937
9999
|
return { grant, account };
|
|
9938
10000
|
}
|
|
9939
10001
|
function stripFragment() {
|
|
9940
10002
|
if (typeof history === "undefined" || !history.replaceState) return;
|
|
9941
|
-
history.replaceState(null, "", location.pathname + location.search);
|
|
10003
|
+
history.replaceState(null, "", location.pathname + withoutLinkState(location.search));
|
|
9942
10004
|
}
|
|
9943
10005
|
async function completeCloudLink(options) {
|
|
9944
10006
|
const root = await resolveRoot(options.root);
|
|
@@ -9958,7 +10020,7 @@ async function completeCloudLink(options) {
|
|
|
9958
10020
|
var DEVICE_PARAM = "enrol-device";
|
|
9959
10021
|
var KEM_PARAM = "enrol-kem";
|
|
9960
10022
|
var CALLBACK_PARAM = "callback-url";
|
|
9961
|
-
var
|
|
10023
|
+
var STATE_PARAM2 = "state";
|
|
9962
10024
|
function deviceEnrolmentUrl(options) {
|
|
9963
10025
|
assertHex(options.devicePublicKey, "devicePublicKey");
|
|
9964
10026
|
assertHex(options.kemPublicKey, "kemPublicKey");
|
|
@@ -9968,7 +10030,7 @@ function deviceEnrolmentUrl(options) {
|
|
|
9968
10030
|
url.searchParams.set(DEVICE_PARAM, options.devicePublicKey);
|
|
9969
10031
|
url.searchParams.set(KEM_PARAM, options.kemPublicKey);
|
|
9970
10032
|
url.searchParams.set(CALLBACK_PARAM, options.returnTo);
|
|
9971
|
-
if (options.state) url.searchParams.set(
|
|
10033
|
+
if (options.state) url.searchParams.set(STATE_PARAM2, options.state);
|
|
9972
10034
|
return url.toString();
|
|
9973
10035
|
}
|
|
9974
10036
|
function beginDeviceEnrolment(options) {
|
|
@@ -11066,7 +11128,6 @@ function joinOf(row) {
|
|
|
11066
11128
|
reportedAt: typeof join.reported_at === "string" ? join.reported_at : null
|
|
11067
11129
|
};
|
|
11068
11130
|
}
|
|
11069
|
-
var ACCOUNT_HEX = /^[0-9a-f]{64}$/;
|
|
11070
11131
|
function accountHexOrNull(value) {
|
|
11071
11132
|
return typeof value === "string" && ACCOUNT_HEX.test(value) ? value : null;
|
|
11072
11133
|
}
|
|
@@ -11794,7 +11855,7 @@ var CloudClient = class {
|
|
|
11794
11855
|
const portal = options.portalUrl.replace(/\/+$/, "");
|
|
11795
11856
|
const query = new URLSearchParams({
|
|
11796
11857
|
"link-account": options.accountId,
|
|
11797
|
-
"callback-url": options.callbackUrl
|
|
11858
|
+
"callback-url": beginLinkState(options.accountId, options.callbackUrl)
|
|
11798
11859
|
});
|
|
11799
11860
|
return { url: `${portal}/?${query.toString()}`, accountId: options.accountId };
|
|
11800
11861
|
}
|
|
@@ -11804,14 +11865,19 @@ var CloudClient = class {
|
|
|
11804
11865
|
* Returns `{}` when there is nothing to read, so it is safe to call on every
|
|
11805
11866
|
* load — which is how a redirected app finds out it has been sent back.
|
|
11806
11867
|
*
|
|
11868
|
+
* A grant is only returned when it answers a handoff this browser began, for the
|
|
11869
|
+
* account that handoff named; anything else comes back as
|
|
11870
|
+
* `error: 'unexpected_callback'`, because the redirect URL can be forwarded.
|
|
11871
|
+
*
|
|
11807
11872
|
* @param href defaults to the current location, when there is one.
|
|
11808
11873
|
*/
|
|
11809
11874
|
static readAccountLinkCallback(href) {
|
|
11810
11875
|
const source = href ?? (typeof window === "undefined" ? "" : window.location.href);
|
|
11811
11876
|
if (!source) return {};
|
|
11812
11877
|
let hash;
|
|
11878
|
+
let search;
|
|
11813
11879
|
try {
|
|
11814
|
-
hash = new URL(source)
|
|
11880
|
+
({ hash, search } = new URL(source));
|
|
11815
11881
|
} catch {
|
|
11816
11882
|
return {};
|
|
11817
11883
|
}
|
|
@@ -11820,6 +11886,10 @@ var CloudClient = class {
|
|
|
11820
11886
|
const accountId = params.get("account");
|
|
11821
11887
|
const error = params.get("error");
|
|
11822
11888
|
const out = {};
|
|
11889
|
+
if (error) takeLinkState(search);
|
|
11890
|
+
if (grant && takeLinkState(search)?.account !== accountId) {
|
|
11891
|
+
return { error: "unexpected_callback" };
|
|
11892
|
+
}
|
|
11823
11893
|
if (grant) out.grant = grant;
|
|
11824
11894
|
if (accountId) out.accountId = accountId;
|
|
11825
11895
|
if (error) out.error = error;
|