@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.cjs
CHANGED
|
@@ -10191,15 +10191,70 @@ function base643(bytes) {
|
|
|
10191
10191
|
return btoa(binary);
|
|
10192
10192
|
}
|
|
10193
10193
|
|
|
10194
|
+
// src/cloud/link-state.ts
|
|
10195
|
+
var KEY2 = "calimero.cloud.link.pending";
|
|
10196
|
+
var STATE_PARAM = "link_state";
|
|
10197
|
+
var LIFETIME_MS = 10 * 60 * 1e3;
|
|
10198
|
+
var ACCOUNT_HEX = /^[0-9a-f]{64}$/;
|
|
10199
|
+
var MAX_PENDING = 20;
|
|
10200
|
+
function readPending(now) {
|
|
10201
|
+
try {
|
|
10202
|
+
const raw = localStorage.getItem(KEY2);
|
|
10203
|
+
const pending = raw ? JSON.parse(raw) : {};
|
|
10204
|
+
return Object.fromEntries(
|
|
10205
|
+
Object.entries(pending).filter(
|
|
10206
|
+
([, p]) => p && typeof p.expiresAt === "number" && p.expiresAt > now
|
|
10207
|
+
)
|
|
10208
|
+
);
|
|
10209
|
+
} catch {
|
|
10210
|
+
return {};
|
|
10211
|
+
}
|
|
10212
|
+
}
|
|
10213
|
+
function writePending(pending) {
|
|
10214
|
+
try {
|
|
10215
|
+
localStorage.setItem(KEY2, JSON.stringify(pending));
|
|
10216
|
+
} catch {
|
|
10217
|
+
}
|
|
10218
|
+
}
|
|
10219
|
+
function beginLinkState(account, returnTo) {
|
|
10220
|
+
if (!ACCOUNT_HEX.test(account)) {
|
|
10221
|
+
throw new Error("accountId must be 64 lowercase hex characters");
|
|
10222
|
+
}
|
|
10223
|
+
const url = new URL(returnTo);
|
|
10224
|
+
const now = Date.now();
|
|
10225
|
+
const pending = readPending(now);
|
|
10226
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
10227
|
+
const state = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
|
|
10228
|
+
""
|
|
10229
|
+
);
|
|
10230
|
+
pending[state] = { account, expiresAt: now + LIFETIME_MS };
|
|
10231
|
+
writePending(Object.fromEntries(Object.entries(pending).slice(-MAX_PENDING)));
|
|
10232
|
+
url.searchParams.set(STATE_PARAM, state);
|
|
10233
|
+
return url.toString();
|
|
10234
|
+
}
|
|
10235
|
+
function takeLinkState(search) {
|
|
10236
|
+
const state = new URLSearchParams(search).get(STATE_PARAM);
|
|
10237
|
+
const pending = readPending(Date.now());
|
|
10238
|
+
if (!state || !Object.prototype.hasOwnProperty.call(pending, state))
|
|
10239
|
+
return null;
|
|
10240
|
+
const entry = pending[state];
|
|
10241
|
+
delete pending[state];
|
|
10242
|
+
writePending(pending);
|
|
10243
|
+
return entry;
|
|
10244
|
+
}
|
|
10245
|
+
function withoutLinkState(search) {
|
|
10246
|
+
const params = new URLSearchParams(search);
|
|
10247
|
+
params.delete(STATE_PARAM);
|
|
10248
|
+
const rest = params.toString();
|
|
10249
|
+
return rest ? `?${rest}` : "";
|
|
10250
|
+
}
|
|
10251
|
+
|
|
10194
10252
|
// src/cloud/link-redirect.ts
|
|
10195
10253
|
var DEFAULT_CLOUD_WEB_URL = "https://cloud.calimero.network";
|
|
10196
10254
|
function cloudLinkUrl(options) {
|
|
10197
|
-
if (!/^[0-9a-f]{64}$/.test(options.accountId)) {
|
|
10198
|
-
throw new Error("accountId must be 64 lowercase hex characters");
|
|
10199
|
-
}
|
|
10200
10255
|
const url = new URL(options.cloudWebUrl ?? DEFAULT_CLOUD_WEB_URL);
|
|
10201
10256
|
url.searchParams.set("link-account", options.accountId);
|
|
10202
|
-
url.searchParams.set("callback-url", options.returnTo);
|
|
10257
|
+
url.searchParams.set("callback-url", beginLinkState(options.accountId, options.returnTo));
|
|
10203
10258
|
return url.toString();
|
|
10204
10259
|
}
|
|
10205
10260
|
function beginCloudLink(options) {
|
|
@@ -10210,6 +10265,7 @@ function readCloudLinkCallback(location2 = window.location) {
|
|
|
10210
10265
|
const params = new URLSearchParams(location2.hash.replace(/^#/, ""));
|
|
10211
10266
|
const error = params.get("error");
|
|
10212
10267
|
if (error) {
|
|
10268
|
+
takeLinkState(location2.search ?? "");
|
|
10213
10269
|
stripFragment();
|
|
10214
10270
|
throw new Error(
|
|
10215
10271
|
error === "denied" ? "the account link was declined at the cloud" : `the cloud refused this link: ${error}`
|
|
@@ -10218,12 +10274,18 @@ function readCloudLinkCallback(location2 = window.location) {
|
|
|
10218
10274
|
const grant = params.get("grant");
|
|
10219
10275
|
const account = params.get("account");
|
|
10220
10276
|
if (!grant || !account) return null;
|
|
10277
|
+
if (takeLinkState(location2.search ?? "")?.account !== account) {
|
|
10278
|
+
stripFragment();
|
|
10279
|
+
throw new Error(
|
|
10280
|
+
"this account link answers a request this browser did not make - start the link again from here"
|
|
10281
|
+
);
|
|
10282
|
+
}
|
|
10221
10283
|
stripFragment();
|
|
10222
10284
|
return { grant, account };
|
|
10223
10285
|
}
|
|
10224
10286
|
function stripFragment() {
|
|
10225
10287
|
if (typeof history === "undefined" || !history.replaceState) return;
|
|
10226
|
-
history.replaceState(null, "", location.pathname + location.search);
|
|
10288
|
+
history.replaceState(null, "", location.pathname + withoutLinkState(location.search));
|
|
10227
10289
|
}
|
|
10228
10290
|
async function completeCloudLink(options) {
|
|
10229
10291
|
const root = await resolveRoot(options.root);
|
|
@@ -10243,7 +10305,7 @@ async function completeCloudLink(options) {
|
|
|
10243
10305
|
var DEVICE_PARAM = "enrol-device";
|
|
10244
10306
|
var KEM_PARAM = "enrol-kem";
|
|
10245
10307
|
var CALLBACK_PARAM = "callback-url";
|
|
10246
|
-
var
|
|
10308
|
+
var STATE_PARAM2 = "state";
|
|
10247
10309
|
function deviceEnrolmentUrl(options) {
|
|
10248
10310
|
assertHex(options.devicePublicKey, "devicePublicKey");
|
|
10249
10311
|
assertHex(options.kemPublicKey, "kemPublicKey");
|
|
@@ -10253,7 +10315,7 @@ function deviceEnrolmentUrl(options) {
|
|
|
10253
10315
|
url.searchParams.set(DEVICE_PARAM, options.devicePublicKey);
|
|
10254
10316
|
url.searchParams.set(KEM_PARAM, options.kemPublicKey);
|
|
10255
10317
|
url.searchParams.set(CALLBACK_PARAM, options.returnTo);
|
|
10256
|
-
if (options.state) url.searchParams.set(
|
|
10318
|
+
if (options.state) url.searchParams.set(STATE_PARAM2, options.state);
|
|
10257
10319
|
return url.toString();
|
|
10258
10320
|
}
|
|
10259
10321
|
function beginDeviceEnrolment(options) {
|
|
@@ -11351,7 +11413,6 @@ function joinOf(row) {
|
|
|
11351
11413
|
reportedAt: typeof join.reported_at === "string" ? join.reported_at : null
|
|
11352
11414
|
};
|
|
11353
11415
|
}
|
|
11354
|
-
var ACCOUNT_HEX = /^[0-9a-f]{64}$/;
|
|
11355
11416
|
function accountHexOrNull(value) {
|
|
11356
11417
|
return typeof value === "string" && ACCOUNT_HEX.test(value) ? value : null;
|
|
11357
11418
|
}
|
|
@@ -12079,7 +12140,7 @@ var CloudClient = class {
|
|
|
12079
12140
|
const portal = options.portalUrl.replace(/\/+$/, "");
|
|
12080
12141
|
const query = new URLSearchParams({
|
|
12081
12142
|
"link-account": options.accountId,
|
|
12082
|
-
"callback-url": options.callbackUrl
|
|
12143
|
+
"callback-url": beginLinkState(options.accountId, options.callbackUrl)
|
|
12083
12144
|
});
|
|
12084
12145
|
return { url: `${portal}/?${query.toString()}`, accountId: options.accountId };
|
|
12085
12146
|
}
|
|
@@ -12089,14 +12150,19 @@ var CloudClient = class {
|
|
|
12089
12150
|
* Returns `{}` when there is nothing to read, so it is safe to call on every
|
|
12090
12151
|
* load — which is how a redirected app finds out it has been sent back.
|
|
12091
12152
|
*
|
|
12153
|
+
* A grant is only returned when it answers a handoff this browser began, for the
|
|
12154
|
+
* account that handoff named; anything else comes back as
|
|
12155
|
+
* `error: 'unexpected_callback'`, because the redirect URL can be forwarded.
|
|
12156
|
+
*
|
|
12092
12157
|
* @param href defaults to the current location, when there is one.
|
|
12093
12158
|
*/
|
|
12094
12159
|
static readAccountLinkCallback(href) {
|
|
12095
12160
|
const source = href ?? (typeof window === "undefined" ? "" : window.location.href);
|
|
12096
12161
|
if (!source) return {};
|
|
12097
12162
|
let hash;
|
|
12163
|
+
let search;
|
|
12098
12164
|
try {
|
|
12099
|
-
hash = new URL(source)
|
|
12165
|
+
({ hash, search } = new URL(source));
|
|
12100
12166
|
} catch {
|
|
12101
12167
|
return {};
|
|
12102
12168
|
}
|
|
@@ -12105,6 +12171,10 @@ var CloudClient = class {
|
|
|
12105
12171
|
const accountId = params.get("account");
|
|
12106
12172
|
const error = params.get("error");
|
|
12107
12173
|
const out = {};
|
|
12174
|
+
if (error) takeLinkState(search);
|
|
12175
|
+
if (grant && takeLinkState(search)?.account !== accountId) {
|
|
12176
|
+
return { error: "unexpected_callback" };
|
|
12177
|
+
}
|
|
12108
12178
|
if (grant) out.grant = grant;
|
|
12109
12179
|
if (accountId) out.accountId = accountId;
|
|
12110
12180
|
if (error) out.error = error;
|