@bounded-sh/core 0.0.65 → 0.0.66
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/client/decline.d.ts +2 -0
- package/dist/client/live-effects.d.ts +4 -2
- package/dist/client/operations.d.ts +90 -5
- package/dist/client/reconcile.d.ts +51 -0
- package/dist/client/solana-operations.d.ts +13 -2
- package/dist/{index-C8ocsC_W.js → index-DNZifL5j.js} +801 -91
- package/dist/index-DNZifL5j.js.map +1 -0
- package/dist/{index-BNOU00XY.mjs → index-TyXiW4Uf.mjs} +800 -92
- package/dist/index-TyXiW4Uf.mjs.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{realtime-store-I_bcryPz.js → realtime-store-CINyAkPw.js} +2 -2
- package/dist/{realtime-store-I_bcryPz.js.map → realtime-store-CINyAkPw.js.map} +1 -1
- package/dist/{realtime-store-mzFJ8bHE.mjs → realtime-store-CpbnXM7q.mjs} +2 -2
- package/dist/{realtime-store-mzFJ8bHE.mjs.map → realtime-store-CpbnXM7q.mjs.map} +1 -1
- package/dist/{solana-message-D_CN0H6r.js → solana-message-Bycv-L5n.js} +2 -2
- package/dist/{solana-message-D_CN0H6r.js.map → solana-message-Bycv-L5n.js.map} +1 -1
- package/dist/{solana-message-VZBGvV0S.mjs → solana-message-DXXwC95Z.mjs} +2 -2
- package/dist/{solana-message-VZBGvV0S.mjs.map → solana-message-DXXwC95Z.mjs.map} +1 -1
- package/dist/{solana-operations-BlCYbdiN.mjs → solana-operations-ChHG25O0.mjs} +157 -20
- package/dist/solana-operations-ChHG25O0.mjs.map +1 -0
- package/dist/{solana-operations-CYpu6EU8.js → solana-operations-Dm_4VnlB.js} +158 -19
- package/dist/solana-operations-Dm_4VnlB.js.map +1 -0
- package/dist/types.d.ts +370 -4
- package/dist/utils/core-platform.d.ts +7 -0
- package/package.json +2 -2
- package/dist/index-BNOU00XY.mjs.map +0 -1
- package/dist/index-C8ocsC_W.js.map +0 -1
- package/dist/solana-operations-BlCYbdiN.mjs.map +0 -1
- package/dist/solana-operations-CYpu6EU8.js.map +0 -1
|
@@ -553,22 +553,25 @@ async function createSessionWithSignature(address, message, signature, authMetho
|
|
|
553
553
|
// Deduplicate concurrent refreshSession calls to prevent multiple code paths
|
|
554
554
|
// (WebSocket reconnection, periodic refresh, API 401 retry) from all hitting
|
|
555
555
|
// /session/refresh simultaneously.
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
//
|
|
559
|
-
|
|
560
|
-
|
|
556
|
+
//
|
|
557
|
+
// Keyed PER (issuer, token) in a Map, not one global slot (DS2-0373): the rotating
|
|
558
|
+
// refresh token is single-use, so two concurrent refreshes of the SAME token must
|
|
559
|
+
// share one in-flight request. A single global slot could hold only one token at a
|
|
560
|
+
// time, so an interleaved refresh of a DIFFERENT session's token evicted the first
|
|
561
|
+
// token's in-flight promise; a later second refresh of the first token then missed
|
|
562
|
+
// its own in-flight request and re-POSTed the already-spent token - the issuer sees
|
|
563
|
+
// reuse and revokes the whole family. Keying by token lets unrelated sessions
|
|
564
|
+
// coalesce independently.
|
|
565
|
+
// Note: module-level state is shared across requests in SSR/Node - acceptable since
|
|
566
|
+
// each key is bound to a specific session's token (same pattern as api.ts).
|
|
567
|
+
const refreshInFlightByToken = new Map();
|
|
561
568
|
async function refreshSession(refreshToken, issuer) {
|
|
562
|
-
// Dedupe per (issuer, token): the rotating token is single-use, so two
|
|
563
|
-
// concurrent refreshes of the SAME token must share one in-flight request or
|
|
564
|
-
// the second would look like reuse and revoke the family.
|
|
565
569
|
const dedupeKey = `${issuer || ''}::${refreshToken}`;
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
refreshInFlightToken = dedupeKey;
|
|
570
|
+
const existing = refreshInFlightByToken.get(dedupeKey);
|
|
571
|
+
if (existing)
|
|
572
|
+
return existing;
|
|
570
573
|
let currentFlight;
|
|
571
|
-
currentFlight =
|
|
574
|
+
currentFlight = (async () => {
|
|
572
575
|
try {
|
|
573
576
|
const config = await getConfig();
|
|
574
577
|
const appId = config.appId;
|
|
@@ -583,15 +586,16 @@ async function refreshSession(refreshToken, issuer) {
|
|
|
583
586
|
return response.data;
|
|
584
587
|
}
|
|
585
588
|
finally {
|
|
586
|
-
// Only
|
|
587
|
-
//
|
|
588
|
-
if
|
|
589
|
-
|
|
590
|
-
|
|
589
|
+
// Only remove our own entry, and only if it is still the active flight
|
|
590
|
+
// for this token (a later refresh of the same token cannot have replaced
|
|
591
|
+
// it while we were in flight, but this stays correct if that ever changes).
|
|
592
|
+
if (refreshInFlightByToken.get(dedupeKey) === currentFlight) {
|
|
593
|
+
refreshInFlightByToken.delete(dedupeKey);
|
|
591
594
|
}
|
|
592
595
|
}
|
|
593
596
|
})();
|
|
594
|
-
|
|
597
|
+
refreshInFlightByToken.set(dedupeKey, currentFlight);
|
|
598
|
+
return currentFlight;
|
|
595
599
|
}
|
|
596
600
|
class SessionRevokeError extends Error {
|
|
597
601
|
constructor(message, cause) {
|
|
@@ -1005,7 +1009,12 @@ class WebSessionManager {
|
|
|
1005
1009
|
// hosted login. The idToken carries the canonical identity (@user.id) + exp.
|
|
1006
1010
|
const { idToken } = sessionObj;
|
|
1007
1011
|
const { exp } = JSON.parse(this.decodeBase64Url(idToken.split(".")[1]));
|
|
1008
|
-
|
|
1012
|
+
// Treat a MISSING or non-numeric expiry as EXPIRED (fail closed). A bare
|
|
1013
|
+
// `Date.now() > exp * 1000` is NaN -> false when exp is absent/non-numeric,
|
|
1014
|
+
// so a forged or malformed token with no usable expiry would otherwise be
|
|
1015
|
+
// accepted as valid forever.
|
|
1016
|
+
const expired = typeof exp !== "number" || !Number.isFinite(exp) || Date.now() > exp * 1000;
|
|
1017
|
+
if (expired) {
|
|
1009
1018
|
const { refreshToken } = sessionObj;
|
|
1010
1019
|
// Expired token with no way to refresh — clear the stale session
|
|
1011
1020
|
// (mirrors the refresh-failed branch below) so isAuthenticated()
|
|
@@ -1111,25 +1120,46 @@ class WebSessionManager {
|
|
|
1111
1120
|
/* ------------------------------------------------------------------ */
|
|
1112
1121
|
/* TOKEN HELPERS */
|
|
1113
1122
|
/* ------------------------------------------------------------------ */
|
|
1123
|
+
// These reads are DEFENSIVE (try/catch -> treat a corrupt blob as absent).
|
|
1124
|
+
// logout() reads the refresh token + issuer BEFORE the session is cleared; if a
|
|
1125
|
+
// corrupt (non-JSON) blob threw out of the read, logout would abort with the
|
|
1126
|
+
// credential still in storage while isAuthenticated() kept reporting true, so
|
|
1127
|
+
// sign-out could never complete. A corrupt blob is unusable either way, so
|
|
1128
|
+
// reporting "no token" lets the clear path proceed and finish the sign-out.
|
|
1114
1129
|
static getIdToken() {
|
|
1115
1130
|
if (typeof window === "undefined")
|
|
1116
1131
|
return null;
|
|
1117
|
-
|
|
1118
|
-
|
|
1132
|
+
try {
|
|
1133
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
1134
|
+
return session ? JSON.parse(session).idToken : null;
|
|
1135
|
+
}
|
|
1136
|
+
catch (_a) {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1119
1139
|
}
|
|
1120
1140
|
static getRefreshToken() {
|
|
1121
1141
|
if (typeof window === "undefined")
|
|
1122
1142
|
return null;
|
|
1123
|
-
|
|
1124
|
-
|
|
1143
|
+
try {
|
|
1144
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
1145
|
+
return session ? JSON.parse(session).refreshToken : null;
|
|
1146
|
+
}
|
|
1147
|
+
catch (_a) {
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1125
1150
|
}
|
|
1126
1151
|
/** The issuer base that minted this session (for routing silent refresh). */
|
|
1127
1152
|
static getIssuer() {
|
|
1128
1153
|
var _a;
|
|
1129
1154
|
if (typeof window === "undefined")
|
|
1130
1155
|
return null;
|
|
1131
|
-
|
|
1132
|
-
|
|
1156
|
+
try {
|
|
1157
|
+
const session = localStorage.getItem(this.BOUNDED_SESSION_STORAGE_KEY);
|
|
1158
|
+
return session ? ((_a = JSON.parse(session).issuer) !== null && _a !== void 0 ? _a : null) : null;
|
|
1159
|
+
}
|
|
1160
|
+
catch (_b) {
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1133
1163
|
}
|
|
1134
1164
|
/* ------------------------------------------------------------------ */
|
|
1135
1165
|
/* UPDATE TOKENS */
|
|
@@ -1387,7 +1417,12 @@ class ReactNativeSessionManager {
|
|
|
1387
1417
|
// logins is opaque, so decoding it threw → the catch wiped a valid session.
|
|
1388
1418
|
const { idToken } = sessionObj;
|
|
1389
1419
|
const { exp } = JSON.parse(this.decodeBase64Url(idToken.split(".")[1]));
|
|
1390
|
-
|
|
1420
|
+
// Treat a MISSING or non-numeric expiry as EXPIRED (fail closed). A bare
|
|
1421
|
+
// `Date.now() > exp * 1000` is NaN -> false when exp is absent/non-numeric,
|
|
1422
|
+
// so a forged or malformed token with no usable expiry would otherwise be
|
|
1423
|
+
// accepted as valid forever.
|
|
1424
|
+
const expired = typeof exp !== "number" || !Number.isFinite(exp) || Date.now() > exp * 1000;
|
|
1425
|
+
if (expired) {
|
|
1391
1426
|
const { refreshToken } = sessionObj;
|
|
1392
1427
|
// Expired token with no way to refresh — clear the stale session
|
|
1393
1428
|
// (mirrors the refresh-failed branch below) so isAuthenticated()
|
|
@@ -4010,7 +4045,7 @@ class ServerSessionManager {
|
|
|
4010
4045
|
const sessionConfig = (_a = this.sessionConfig) !== null && _a !== void 0 ? _a : undefined;
|
|
4011
4046
|
/* fetch nonce from auth API (bound to the snapshotted app+issuer) */
|
|
4012
4047
|
const nonce = await genAuthNonce(sessionConfig);
|
|
4013
|
-
const { genSolanaMessage } = await Promise.resolve().then(function () { return require('./solana-message-
|
|
4048
|
+
const { genSolanaMessage } = await Promise.resolve().then(function () { return require('./solana-message-Bycv-L5n.js'); });
|
|
4014
4049
|
const message = await genSolanaMessage(address, nonce, sessionConfig === null || sessionConfig === void 0 ? void 0 : sessionConfig.appId);
|
|
4015
4050
|
/* sign the message */
|
|
4016
4051
|
const sigBytes = nacl.sign.detached(new TextEncoder().encode(message), kp.secretKey);
|
|
@@ -4102,18 +4137,31 @@ ServerSessionManager.instance = new ServerSessionManager();
|
|
|
4102
4137
|
*
|
|
4103
4138
|
* These are used instead of bare `atob()`/`btoa()` so the core package
|
|
4104
4139
|
* works in React Native without requiring the consumer to polyfill globals.
|
|
4140
|
+
*
|
|
4141
|
+
* Both directions are UTF-8-safe: bare btoa / Buffer('binary') are LATIN-1 and
|
|
4142
|
+
* throw (surrogate-pair emoji) or silently mangle (accents, CJK) any code point
|
|
4143
|
+
* > 0xFF, corrupting user text - e.g. an AI-query `prompt` - before it is sent.
|
|
4144
|
+
* safeBtoa base64s the UTF-8 bytes; safeAtob is its exact inverse, and the
|
|
4145
|
+
* realtime worker decodes the same bytes as UTF-8 (src/utf8-base64.ts). ASCII is
|
|
4146
|
+
* byte-identical either way, so JWT (decodeBase64Url) decoding is unaffected.
|
|
4105
4147
|
*/
|
|
4106
4148
|
function safeAtob(input) {
|
|
4107
4149
|
if (typeof globalThis.atob === 'function') {
|
|
4108
|
-
|
|
4150
|
+
const binary = globalThis.atob(input);
|
|
4151
|
+
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
|
|
4152
|
+
return new TextDecoder().decode(bytes);
|
|
4109
4153
|
}
|
|
4110
|
-
return bufferExports.Buffer.from(input, 'base64').toString('
|
|
4154
|
+
return bufferExports.Buffer.from(input, 'base64').toString('utf-8');
|
|
4111
4155
|
}
|
|
4112
4156
|
function safeBtoa(input) {
|
|
4157
|
+
const bytes = new TextEncoder().encode(input);
|
|
4113
4158
|
if (typeof globalThis.btoa === 'function') {
|
|
4114
|
-
|
|
4159
|
+
let binary = '';
|
|
4160
|
+
for (let i = 0; i < bytes.length; i++)
|
|
4161
|
+
binary += String.fromCharCode(bytes[i]);
|
|
4162
|
+
return globalThis.btoa(binary);
|
|
4115
4163
|
}
|
|
4116
|
-
return bufferExports.Buffer.from(
|
|
4164
|
+
return bufferExports.Buffer.from(bytes).toString('base64');
|
|
4117
4165
|
}
|
|
4118
4166
|
/**
|
|
4119
4167
|
* Decode a base64url-encoded string (used by JWT payloads).
|
|
@@ -6729,7 +6777,7 @@ async function doReconnectWithNewAuth() {
|
|
|
6729
6777
|
console.warn('[WS v2] Failed to clear HTTP read cache on auth change:', error);
|
|
6730
6778
|
}
|
|
6731
6779
|
try {
|
|
6732
|
-
const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-
|
|
6780
|
+
const { reconnectRealtimeStoreWithNewAuth } = await Promise.resolve().then(function () { return require('./realtime-store-CINyAkPw.js'); });
|
|
6733
6781
|
await reconnectRealtimeStoreWithNewAuth();
|
|
6734
6782
|
}
|
|
6735
6783
|
catch (error) {
|
|
@@ -7403,17 +7451,6 @@ function assertEvmTransactionMatchesWrite(transaction, context) {
|
|
|
7403
7451
|
}
|
|
7404
7452
|
}
|
|
7405
7453
|
|
|
7406
|
-
var __rest$1 = (undefined && undefined.__rest) || function (s, e) {
|
|
7407
|
-
var t = {};
|
|
7408
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
7409
|
-
t[p] = s[p];
|
|
7410
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7411
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7412
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
7413
|
-
t[p[i]] = s[p[i]];
|
|
7414
|
-
}
|
|
7415
|
-
return t;
|
|
7416
|
-
};
|
|
7417
7454
|
/**
|
|
7418
7455
|
* Thrown when a user's wallet doesn't have enough SOL to cover the transaction.
|
|
7419
7456
|
* Apps can catch this to trigger an onramp/funding flow.
|
|
@@ -7991,7 +8028,11 @@ async function get(path, opts = {}) {
|
|
|
7991
8028
|
}
|
|
7992
8029
|
else {
|
|
7993
8030
|
const path = encodeURIComponent(normalizedPath);
|
|
7994
|
-
|
|
8031
|
+
// encodeURIComponent the base64: safeBtoa emits standard base64 (with '+' '/' '='),
|
|
8032
|
+
// and a raw '+' in a query string decodes to a space on the server
|
|
8033
|
+
// (url.searchParams.get), corrupting the exact non-ASCII prompts DS2-0463 fixed. The
|
|
8034
|
+
// server percent-decodes back to the original base64 before decodeUtf8Base64. (DS2-0463)
|
|
8035
|
+
const promptQueryParam = (opts === null || opts === void 0 ? void 0 : opts.prompt) ? `&prompt=${encodeURIComponent(safeBtoa(opts.prompt))}` : "";
|
|
7995
8036
|
const apiPath = `items?path=${path}${promptQueryParam}${filterParam}${sortParam}${includeSubPathsParam}${shapeParam}${limitParam}${cursorParam}`;
|
|
7996
8037
|
response = await makeApiRequest('GET', apiPath, null, opts._overrides);
|
|
7997
8038
|
}
|
|
@@ -8332,20 +8373,200 @@ async function runExpressionMany(many) {
|
|
|
8332
8373
|
throw error;
|
|
8333
8374
|
}
|
|
8334
8375
|
}
|
|
8376
|
+
/* ===================== WRITE RECEIPT HELPERS (W4) ======================== */
|
|
8377
|
+
/**
|
|
8378
|
+
* Settle delay before the best-effort mirror read on an on-chain write. It is
|
|
8379
|
+
* an optimization, not a correctness guarantee: the receipt is captured first
|
|
8380
|
+
* and returned regardless of what the mirror read reports.
|
|
8381
|
+
*/
|
|
8382
|
+
const MIRROR_SYNC_SETTLE_MS = 1500;
|
|
8383
|
+
const SOLANA_CHAIN_TAGS = new Set(['solana_devnet', 'solana_mainnet']);
|
|
8384
|
+
/**
|
|
8385
|
+
* Read the execution-lane tag the WORKER reported. The SDK never infers it: a
|
|
8386
|
+
* batch touching only unflagged collections in a `realtime_devnet` app commits
|
|
8387
|
+
* offchain, so the app's protocol is not the lane. Several field names are
|
|
8388
|
+
* accepted (batch-level receipt first, then the per-transaction Solana
|
|
8389
|
+
* `network` tag that predates it), but no value is ever manufactured.
|
|
8390
|
+
*/
|
|
8391
|
+
function readWorkerChainTag(...sources) {
|
|
8392
|
+
for (const source of sources) {
|
|
8393
|
+
if (!source || typeof source !== 'object')
|
|
8394
|
+
continue;
|
|
8395
|
+
for (const key of ['chain', 'executionChain', 'network']) {
|
|
8396
|
+
const value = source[key];
|
|
8397
|
+
if (typeof value === 'string' && value.trim().length > 0)
|
|
8398
|
+
return value.trim();
|
|
8399
|
+
}
|
|
8400
|
+
}
|
|
8401
|
+
return null;
|
|
8402
|
+
}
|
|
8403
|
+
/** Path-keyed echo of what the caller asked for. NEVER authoritative state. */
|
|
8404
|
+
function buildRequestedDocuments(documents) {
|
|
8405
|
+
return documents.map(entry => {
|
|
8406
|
+
var _a;
|
|
8407
|
+
return ({
|
|
8408
|
+
path: entry.destinationPath,
|
|
8409
|
+
document: ((_a = entry.document) !== null && _a !== void 0 ? _a : null),
|
|
8410
|
+
});
|
|
8411
|
+
});
|
|
8412
|
+
}
|
|
8413
|
+
/** Look a concrete path up in a path-keyed map, tolerating a leading slash. */
|
|
8414
|
+
function pickPathDocument(source, path) {
|
|
8415
|
+
if (Object.prototype.hasOwnProperty.call(source, path))
|
|
8416
|
+
return source[path];
|
|
8417
|
+
const withSlash = path.startsWith('/') ? path : `/${path}`;
|
|
8418
|
+
if (Object.prototype.hasOwnProperty.call(source, withSlash))
|
|
8419
|
+
return source[withSlash];
|
|
8420
|
+
const withoutSlash = path.startsWith('/') ? path.slice(1) : path;
|
|
8421
|
+
if (Object.prototype.hasOwnProperty.call(source, withoutSlash))
|
|
8422
|
+
return source[withoutSlash];
|
|
8423
|
+
return undefined;
|
|
8424
|
+
}
|
|
8425
|
+
/**
|
|
8426
|
+
* `observed: true` only when EVERY requested concrete path was read back as a
|
|
8427
|
+
* real document. The sync endpoint answers `null` for a document that is
|
|
8428
|
+
* absent, lagging, or read-denied, and a deleted path reads back as `null` too
|
|
8429
|
+
* — none of those are evidence of synchronization.
|
|
8430
|
+
*/
|
|
8431
|
+
function validatePathKeyedDocuments(paths, source) {
|
|
8432
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
8433
|
+
return { observed: false, reason: 'the synchronization response was not a path-keyed document map' };
|
|
8434
|
+
}
|
|
8435
|
+
const map = source;
|
|
8436
|
+
const documents = [];
|
|
8437
|
+
const unobserved = [];
|
|
8438
|
+
for (const path of paths) {
|
|
8439
|
+
const document = pickPathDocument(map, path);
|
|
8440
|
+
if (document == null || typeof document !== 'object' || Array.isArray(document)) {
|
|
8441
|
+
unobserved.push(path);
|
|
8442
|
+
documents.push({ path, document: null });
|
|
8443
|
+
continue;
|
|
8444
|
+
}
|
|
8445
|
+
documents.push({ path, document: document });
|
|
8446
|
+
}
|
|
8447
|
+
if (unobserved.length > 0) {
|
|
8448
|
+
return {
|
|
8449
|
+
observed: false,
|
|
8450
|
+
reason: `no readable document was returned for ${unobserved.join(', ')} ` +
|
|
8451
|
+
'(absent, deleted, lagging, read-denied, or a passthrough write)',
|
|
8452
|
+
};
|
|
8453
|
+
}
|
|
8454
|
+
return { observed: true, documents };
|
|
8455
|
+
}
|
|
8456
|
+
/**
|
|
8457
|
+
* Best-effort mirror read. A confirmed transaction must NEVER be lost to a
|
|
8458
|
+
* synchronization failure, so this reports its outcome in `mirrorSync` and
|
|
8459
|
+
* never rejects. It reads the CONCRETE destination paths, so a collection
|
|
8460
|
+
* insert with a generated id syncs the path that actually exists.
|
|
8461
|
+
*/
|
|
8462
|
+
async function attemptMirrorSync(paths, options, settleMs = MIRROR_SYNC_SETTLE_MS) {
|
|
8463
|
+
try {
|
|
8464
|
+
if (settleMs > 0)
|
|
8465
|
+
await new Promise(resolve => setTimeout(resolve, settleMs));
|
|
8466
|
+
const synced = await syncItems(paths, options);
|
|
8467
|
+
// The sync endpoint answers `{ data: <path-keyed map>, status }`; a document
|
|
8468
|
+
// path always contains a `/`, so a top-level `data` key is never one.
|
|
8469
|
+
const map = (synced && typeof synced === 'object' && !Array.isArray(synced) && 'data' in synced)
|
|
8470
|
+
? synced.data
|
|
8471
|
+
: synced;
|
|
8472
|
+
return validatePathKeyedDocuments(paths, map);
|
|
8473
|
+
}
|
|
8474
|
+
catch (error) {
|
|
8475
|
+
const message = typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' ? error.message : String(error);
|
|
8476
|
+
return { observed: false, reason: `mirror synchronization failed: ${message}` };
|
|
8477
|
+
}
|
|
8478
|
+
}
|
|
8479
|
+
function normalizePathKey(path) {
|
|
8480
|
+
return path.startsWith('/') ? path.slice(1) : path;
|
|
8481
|
+
}
|
|
8482
|
+
/**
|
|
8483
|
+
* Post-hook observation for a DIRECT commit, taken from the worker's own batch
|
|
8484
|
+
* receipt. The worker is the only party that can read every concrete path back
|
|
8485
|
+
* AFTER hooks have run, so when it reports nothing we say so rather than
|
|
8486
|
+
* echoing the request as if it were server state.
|
|
8487
|
+
*
|
|
8488
|
+
* Unlike the mirror read, a `null` document here IS authoritative: the worker
|
|
8489
|
+
* read its own store, so a deleted path genuinely is absent. The worker's
|
|
8490
|
+
* `observed` verdict is still re-validated against the requested paths — a
|
|
8491
|
+
* receipt that skips one of them cannot be treated as complete.
|
|
8492
|
+
*/
|
|
8493
|
+
function observationFromDirectCommit(paths, observation) {
|
|
8494
|
+
var _a;
|
|
8495
|
+
if (!observation || typeof observation !== 'object' || Array.isArray(observation)) {
|
|
8496
|
+
return { observed: false, reason: 'the write receipt carried no post-hook observation' };
|
|
8497
|
+
}
|
|
8498
|
+
const entries = Array.isArray(observation.documents) ? observation.documents : null;
|
|
8499
|
+
if (!entries) {
|
|
8500
|
+
return { observed: false, reason: 'the write receipt carried no path-keyed post-hook documents' };
|
|
8501
|
+
}
|
|
8502
|
+
const byPath = new Map();
|
|
8503
|
+
for (const entry of entries) {
|
|
8504
|
+
if (!entry || typeof entry.path !== 'string')
|
|
8505
|
+
continue;
|
|
8506
|
+
const document = entry.document;
|
|
8507
|
+
byPath.set(normalizePathKey(entry.path), (document && typeof document === 'object' && !Array.isArray(document))
|
|
8508
|
+
? document
|
|
8509
|
+
: null);
|
|
8510
|
+
}
|
|
8511
|
+
const documents = [];
|
|
8512
|
+
const missing = [];
|
|
8513
|
+
for (const path of paths) {
|
|
8514
|
+
const key = normalizePathKey(path);
|
|
8515
|
+
if (!byPath.has(key)) {
|
|
8516
|
+
missing.push(path);
|
|
8517
|
+
documents.push({ path, document: null });
|
|
8518
|
+
continue;
|
|
8519
|
+
}
|
|
8520
|
+
documents.push({ path, document: (_a = byPath.get(key)) !== null && _a !== void 0 ? _a : null });
|
|
8521
|
+
}
|
|
8522
|
+
if (observation.observed !== true || missing.length > 0) {
|
|
8523
|
+
const unobserved = Array.isArray(observation.unobserved) ? observation.unobserved : [];
|
|
8524
|
+
const reasons = [
|
|
8525
|
+
...unobserved
|
|
8526
|
+
.filter((entry) => entry && typeof entry.path === 'string')
|
|
8527
|
+
.map((entry) => { var _a; return `${entry.path}: ${(_a = entry.reason) !== null && _a !== void 0 ? _a : 'not observable'}`; }),
|
|
8528
|
+
...missing.map(path => `${path}: no entry in the receipt observation`),
|
|
8529
|
+
];
|
|
8530
|
+
return {
|
|
8531
|
+
observed: false,
|
|
8532
|
+
reason: reasons.length > 0
|
|
8533
|
+
? reasons.join('; ')
|
|
8534
|
+
: 'the worker could not read every requested path back after hooks',
|
|
8535
|
+
};
|
|
8536
|
+
}
|
|
8537
|
+
return { observed: true, documents };
|
|
8538
|
+
}
|
|
8335
8539
|
/**
|
|
8336
8540
|
* Write a document at `path`. Sugar for a one-element {@link setMany}.
|
|
8337
8541
|
*
|
|
8338
8542
|
* **Delete:** pass `null` as the document to delete it — `set(path, null)` is
|
|
8339
8543
|
* the delete (there is no separate `del`/`remove`). It is routed through the
|
|
8340
|
-
* collection's policy `delete` rule and broadcasts a delete to subscribers
|
|
8544
|
+
* collection's policy `delete` rule and broadcasts a delete to subscribers, and
|
|
8545
|
+
* the deleted entry surfaces as a `null` document under its path.
|
|
8546
|
+
*
|
|
8547
|
+
* Resolves to a discriminated {@link SetResult}; discriminate on `status`.
|
|
8341
8548
|
*/
|
|
8342
8549
|
async function set(path, document, options) {
|
|
8343
8550
|
// setMany already invalidates the read cache for the affected document and
|
|
8344
8551
|
// collection paths — no second sweep needed here.
|
|
8345
8552
|
return setMany([{ path, document }], options);
|
|
8346
8553
|
}
|
|
8554
|
+
/**
|
|
8555
|
+
* Atomically write (or delete, with a `null` document) a batch of documents.
|
|
8556
|
+
*
|
|
8557
|
+
* Resolves to a discriminated {@link SetResult}:
|
|
8558
|
+
* - `committed` — the worker applied the batch directly.
|
|
8559
|
+
* - `confirmed` — a client-signed chain transaction landed successfully.
|
|
8560
|
+
* - `submitted` — broadcast happened but the outcome is unknown. NEVER
|
|
8561
|
+
* resubmit; call `reconcileSetResult`/`waitForSetResult` on the receipt.
|
|
8562
|
+
* - `expired_not_landed` — Solana only, and the one retry-safe terminal state.
|
|
8563
|
+
* - `signed` — `shouldSubmitTx: false`; signed and deliberately not sent.
|
|
8564
|
+
*
|
|
8565
|
+
* `requestedDocuments` is always the caller's own echo; authoritative server
|
|
8566
|
+
* state appears only under `observation`/`mirrorSync` once actually observed.
|
|
8567
|
+
*/
|
|
8347
8568
|
async function setMany(many, options) {
|
|
8348
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
8569
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
8349
8570
|
// Returns the data that was set, or undefined if the document was already set.
|
|
8350
8571
|
try {
|
|
8351
8572
|
// A server WalletClient threads its immutable config snapshot; the default
|
|
@@ -8427,8 +8648,17 @@ async function setMany(many, options) {
|
|
|
8427
8648
|
(hasSerializedSolanaTarget && hasBuiltSolanaTarget)) {
|
|
8428
8649
|
throw new Error('Ambiguous on-chain write response mixes EVM and/or Solana transaction target formats');
|
|
8429
8650
|
}
|
|
8430
|
-
let transactionResult;
|
|
8431
8651
|
const expectedWritePaths = documents.map(d => d.destinationPath);
|
|
8652
|
+
const requestedDocuments = buildRequestedDocuments(documents);
|
|
8653
|
+
const appId = config.appId;
|
|
8654
|
+
// The lane comes verbatim from the worker, and is read BEFORE anything is
|
|
8655
|
+
// signed or submitted: a missing tag fails closed while nothing has been
|
|
8656
|
+
// broadcast, instead of stranding a landed transaction with no receipt.
|
|
8657
|
+
const workerChain = readWorkerChainTag((_k = setResponse.data) === null || _k === void 0 ? void 0 : _k.receipt, setResponse.data, curTx);
|
|
8658
|
+
if (!workerChain) {
|
|
8659
|
+
throw new Error('The write response carried no execution-chain tag, so this receipt could not state where the write ran. ' +
|
|
8660
|
+
'Nothing was signed or submitted.');
|
|
8661
|
+
}
|
|
8432
8662
|
// EVM onchain write: the worker returns a PRE-BUILT EIP-1559 tx on
|
|
8433
8663
|
// `transactions[0].evmTransaction` (the EVM twin of Solana's pre-built
|
|
8434
8664
|
// `serializedTransaction`). WORKER 202 CONTRACT (field name + shape the
|
|
@@ -8443,41 +8673,110 @@ async function setMany(many, options) {
|
|
|
8443
8673
|
assertEvmTransactionMatchesWrite(curTx.evmTransaction, {
|
|
8444
8674
|
appId: config.appId,
|
|
8445
8675
|
documents,
|
|
8446
|
-
policy: (
|
|
8676
|
+
policy: (_o = (_m = (_l = options === null || options === void 0 ? void 0 : options._overrides) === null || _l === void 0 ? void 0 : _l._evmTransactionPolicy) !== null && _m !== void 0 ? _m : config.evmWalletConfig) !== null && _o !== void 0 ? _o : null,
|
|
8447
8677
|
});
|
|
8448
|
-
|
|
8678
|
+
const evmChain = workerChain;
|
|
8679
|
+
const evmOutcome = await handleEvmTransaction(curTx.evmTransaction, authProvider, options);
|
|
8680
|
+
// CAPTURE THE RECEIPT FIRST. Mirror synchronization comes after, and its
|
|
8681
|
+
// failure can never turn a landed transaction into a thrown error that
|
|
8682
|
+
// invites the caller to submit the same write twice.
|
|
8683
|
+
if (evmOutcome.outcome === 'confirmed') {
|
|
8684
|
+
const mirrorSync = await attemptMirrorSync(expectedWritePaths, options);
|
|
8685
|
+
return {
|
|
8686
|
+
status: 'confirmed', appId, requestedDocuments, chain: evmChain,
|
|
8687
|
+
transactionId: evmOutcome.transactionSignature,
|
|
8688
|
+
blockNumber: evmOutcome.blockNumber,
|
|
8689
|
+
mirrorSync,
|
|
8690
|
+
};
|
|
8691
|
+
}
|
|
8692
|
+
// An unknown confirmation outcome is NOT a mirror question: nothing is
|
|
8693
|
+
// known to have landed, so no synchronization is attempted.
|
|
8694
|
+
if (evmOutcome.transactionSignature === null) {
|
|
8695
|
+
return {
|
|
8696
|
+
status: 'submitted', appId, requestedDocuments, chain: evmChain,
|
|
8697
|
+
transactionId: null,
|
|
8698
|
+
confirmation: { state: 'unknown', reason: evmOutcome.reason, reconcilable: false },
|
|
8699
|
+
};
|
|
8700
|
+
}
|
|
8701
|
+
if ('fence' in evmOutcome && evmOutcome.fence) {
|
|
8702
|
+
return {
|
|
8703
|
+
status: 'submitted', appId, requestedDocuments, chain: evmChain,
|
|
8704
|
+
transactionId: evmOutcome.transactionSignature,
|
|
8705
|
+
fence: evmOutcome.fence,
|
|
8706
|
+
confirmation: { state: 'unknown', reason: evmOutcome.reason },
|
|
8707
|
+
};
|
|
8708
|
+
}
|
|
8709
|
+
return {
|
|
8710
|
+
status: 'submitted', appId, requestedDocuments, chain: evmChain,
|
|
8711
|
+
transactionId: evmOutcome.transactionSignature,
|
|
8712
|
+
rebroadcastable: false,
|
|
8713
|
+
confirmation: { state: 'unknown', reason: evmOutcome.reason },
|
|
8714
|
+
};
|
|
8449
8715
|
}
|
|
8450
8716
|
else if (curTx.serializedTransaction) {
|
|
8451
|
-
|
|
8452
|
-
|
|
8717
|
+
if (!SOLANA_CHAIN_TAGS.has(workerChain)) {
|
|
8718
|
+
throw new Error(`The write response reported execution chain "${workerChain}" for a Solana transaction. ` +
|
|
8719
|
+
'Nothing was signed or submitted.');
|
|
8720
|
+
}
|
|
8721
|
+
const solanaChain = workerChain;
|
|
8722
|
+
const { handlePreBuiltTransaction } = await Promise.resolve().then(function () { return require('./solana-operations-Dm_4VnlB.js'); });
|
|
8723
|
+
const solanaOutcome = await handlePreBuiltTransaction(curTx, authProvider, options, expectedWritePaths);
|
|
8724
|
+
// `shouldSubmitTx: false` signed WITHOUT sending — no signature exists,
|
|
8725
|
+
// and there is nothing to mirror, so no synchronization is attempted.
|
|
8726
|
+
if (solanaOutcome.outcome === 'signed') {
|
|
8727
|
+
return {
|
|
8728
|
+
status: 'signed', appId, requestedDocuments, chain: solanaChain,
|
|
8729
|
+
transactionId: null,
|
|
8730
|
+
signedTransaction: solanaOutcome.signedTransaction,
|
|
8731
|
+
fence: solanaOutcome.fence,
|
|
8732
|
+
};
|
|
8733
|
+
}
|
|
8734
|
+
if (solanaOutcome.outcome === 'submitted') {
|
|
8735
|
+
return {
|
|
8736
|
+
status: 'submitted', appId, requestedDocuments, chain: solanaChain,
|
|
8737
|
+
transactionId: solanaOutcome.transactionSignature,
|
|
8738
|
+
signedTransaction: solanaOutcome.signedTransaction,
|
|
8739
|
+
fence: solanaOutcome.fence,
|
|
8740
|
+
confirmation: { state: 'unknown', reason: solanaOutcome.reason },
|
|
8741
|
+
};
|
|
8742
|
+
}
|
|
8743
|
+
// Confirmed: the receipt is already complete, so the mirror read is a
|
|
8744
|
+
// best-effort enrichment reported in `mirrorSync`, never a rejection.
|
|
8745
|
+
const mirrorSync = await attemptMirrorSync(expectedWritePaths, options);
|
|
8746
|
+
return {
|
|
8747
|
+
status: 'confirmed', appId, requestedDocuments, chain: solanaChain,
|
|
8748
|
+
transactionId: solanaOutcome.transactionSignature,
|
|
8749
|
+
signedTransaction: solanaOutcome.signedTransaction,
|
|
8750
|
+
fence: solanaOutcome.fence,
|
|
8751
|
+
confirmationContextSlot: solanaOutcome.confirmationContextSlot,
|
|
8752
|
+
mirrorSync,
|
|
8753
|
+
};
|
|
8453
8754
|
}
|
|
8454
8755
|
else {
|
|
8455
8756
|
throw new Error('Unsupported 202 transaction shape: expected serializedTransaction or evmTransaction');
|
|
8456
8757
|
}
|
|
8457
|
-
// Sync items after all transactions are confirmed
|
|
8458
|
-
// Wait for 1.5 seconds to ensure all transactions are confirmed
|
|
8459
|
-
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
8460
|
-
await syncItems(many.map(m => m.path), options);
|
|
8461
|
-
// TODO: Should we wait here or do the optimistic subscription updates like below?
|
|
8462
|
-
return Object.assign(Object.assign({}, documents.map(d => d.document)), { transactionId: transactionResult.transactionSignature, signedTransaction: transactionResult.signedTransaction });
|
|
8463
8758
|
}
|
|
8464
8759
|
else if (setResponse.status === 200) {
|
|
8465
|
-
//
|
|
8466
|
-
//
|
|
8467
|
-
//
|
|
8468
|
-
//
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8760
|
+
// Direct commit: the worker applied the batch itself. The receipt is
|
|
8761
|
+
// batch-level — `transactionId` comes from the worker's own receipt and is
|
|
8762
|
+
// NEVER lifted out of a result document (a delete returns the PREVIOUS
|
|
8763
|
+
// document, so its stale `_transaction_hash` would misreport the delete).
|
|
8764
|
+
const body = (setResponse.data && typeof setResponse.data === 'object' && !Array.isArray(setResponse.data))
|
|
8765
|
+
? setResponse.data
|
|
8766
|
+
: null;
|
|
8767
|
+
const receipt = ((body === null || body === void 0 ? void 0 : body.receipt) && typeof body.receipt === 'object' && !Array.isArray(body.receipt))
|
|
8768
|
+
? body.receipt
|
|
8769
|
+
: null;
|
|
8770
|
+
const rawTransactionId = receipt === null || receipt === void 0 ? void 0 : receipt.transactionId;
|
|
8771
|
+
return {
|
|
8772
|
+
status: 'committed',
|
|
8773
|
+
requestedDocuments: buildRequestedDocuments(documents),
|
|
8774
|
+
observation: observationFromDirectCommit(documents.map(d => d.destinationPath), receipt === null || receipt === void 0 ? void 0 : receipt.observation),
|
|
8775
|
+
// A worker predating the batch receipt reports no lane. Say so rather
|
|
8776
|
+
// than inventing one — and never discard a committed write's receipt.
|
|
8777
|
+
chain: ((_p = readWorkerChainTag(receipt)) !== null && _p !== void 0 ? _p : 'unknown'),
|
|
8778
|
+
transactionId: typeof rawTransactionId === 'string' && rawTransactionId.length > 0 ? rawTransactionId : null,
|
|
8779
|
+
};
|
|
8481
8780
|
}
|
|
8482
8781
|
else {
|
|
8483
8782
|
throw new Error(`Failed to set document: ${setResponse}`);
|
|
@@ -8492,7 +8791,11 @@ async function setMany(many, options) {
|
|
|
8492
8791
|
// slot of runTransaction (Solana slot undefined). shouldSubmitTx is honored by
|
|
8493
8792
|
// the provider itself.
|
|
8494
8793
|
async function handleEvmTransaction(evmTransaction, authProvider, options) {
|
|
8495
|
-
|
|
8794
|
+
const result = await authProvider.runTransaction(evmTransaction, undefined, options);
|
|
8795
|
+
if ((result === null || result === void 0 ? void 0 : result.lane) !== 'evm') {
|
|
8796
|
+
throw new Error('The EVM auth provider returned a non-EVM transaction outcome');
|
|
8797
|
+
}
|
|
8798
|
+
return result;
|
|
8496
8799
|
}
|
|
8497
8800
|
}
|
|
8498
8801
|
/**
|
|
@@ -8572,8 +8875,27 @@ async function getFiles(path, options) {
|
|
|
8572
8875
|
throw error;
|
|
8573
8876
|
}
|
|
8574
8877
|
}
|
|
8878
|
+
/** Pull the worker's concrete object path (`token.pathId`) out of a response body. */
|
|
8879
|
+
function readWorkerFilePath(source) {
|
|
8880
|
+
if (!source || typeof source !== 'object')
|
|
8881
|
+
return null;
|
|
8882
|
+
for (const key of ['pathId', 'path', 'destinationPath']) {
|
|
8883
|
+
const value = source[key];
|
|
8884
|
+
if (typeof value === 'string' && value.trim().length > 0)
|
|
8885
|
+
return value.trim();
|
|
8886
|
+
}
|
|
8887
|
+
return null;
|
|
8888
|
+
}
|
|
8889
|
+
function readFileVisibility(source) {
|
|
8890
|
+
const value = (source && typeof source === 'object') ? source.visibility : undefined;
|
|
8891
|
+
if (value === 'public' || value === 'private' || value === 'unknown')
|
|
8892
|
+
return value;
|
|
8893
|
+
// The worker did not state it, so neither do we. `unknown` never means the
|
|
8894
|
+
// object is private — it means the read-rule evaluation was not reported.
|
|
8895
|
+
return 'unknown';
|
|
8896
|
+
}
|
|
8575
8897
|
async function setFile(path, file, options) {
|
|
8576
|
-
var _a;
|
|
8898
|
+
var _a, _b, _c, _d;
|
|
8577
8899
|
// 1) Get the presigned URL from your backend
|
|
8578
8900
|
const requestBody = {
|
|
8579
8901
|
destinationPath: path,
|
|
@@ -8590,10 +8912,18 @@ async function setFile(path, file, options) {
|
|
|
8590
8912
|
}
|
|
8591
8913
|
}
|
|
8592
8914
|
const response = await makeApiRequest('POST', 'storage/url', requestBody, options === null || options === void 0 ? void 0 : options._overrides);
|
|
8915
|
+
const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
|
|
8593
8916
|
if (file == null) {
|
|
8594
|
-
|
|
8917
|
+
// A deletion is acknowledged only by a non-404 response. `makeApiRequest`
|
|
8918
|
+
// normalizes 404 into `{ data: null, status: 404 }` rather than throwing, so
|
|
8919
|
+
// the status has to be inspected.
|
|
8920
|
+
const deletePath = (_a = readWorkerFilePath(response === null || response === void 0 ? void 0 : response.data)) !== null && _a !== void 0 ? _a : normalizedPath;
|
|
8921
|
+
if ((response === null || response === void 0 ? void 0 : response.status) === 404) {
|
|
8922
|
+
return { path: deletePath, deleted: false, alreadyAbsent: true };
|
|
8923
|
+
}
|
|
8924
|
+
return { path: deletePath, deleted: true };
|
|
8595
8925
|
}
|
|
8596
|
-
if (!((
|
|
8926
|
+
if (!((_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.s3Url)) {
|
|
8597
8927
|
throw new Error("No s3Url in response");
|
|
8598
8928
|
}
|
|
8599
8929
|
// Client-side size guard using the limit returned by the server
|
|
@@ -8602,20 +8932,62 @@ async function setFile(path, file, options) {
|
|
|
8602
8932
|
throw new Error(`File size ${(file.size / (1024 * 1024)).toFixed(2)} MB exceeds the maximum upload size of ${(maxFileSize / (1024 * 1024)).toFixed(0)} MB.`);
|
|
8603
8933
|
}
|
|
8604
8934
|
const presignedUrl = response.data.s3Url;
|
|
8605
|
-
//
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8935
|
+
// Retain the CONCRETE path BEFORE the PUT. Storage generates a collection
|
|
8936
|
+
// upload's id server-side, so `setFile("metadata", file)` creates
|
|
8937
|
+
// `metadata/<generated-id>`; if the commit succeeds and only its response is
|
|
8938
|
+
// lost, this is the only identifier the object has.
|
|
8939
|
+
const retainedPath = (_c = readWorkerFilePath(response.data)) !== null && _c !== void 0 ? _c : normalizedPath;
|
|
8940
|
+
// 2) Upload the file directly to storage with a PUT request
|
|
8941
|
+
let uploadResponse;
|
|
8942
|
+
try {
|
|
8943
|
+
uploadResponse = await fetch(presignedUrl, {
|
|
8944
|
+
method: 'PUT',
|
|
8945
|
+
headers: {
|
|
8946
|
+
'Content-Type': file.type,
|
|
8947
|
+
},
|
|
8948
|
+
body: file
|
|
8949
|
+
});
|
|
8950
|
+
}
|
|
8951
|
+
catch (error) {
|
|
8952
|
+
// The transport itself failed, so we do not know whether the object
|
|
8953
|
+
// committed. This is NOT a definite failure and NOT retry-safe.
|
|
8954
|
+
const message = typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' ? error.message : String(error);
|
|
8955
|
+
return {
|
|
8956
|
+
status: 'unknown',
|
|
8957
|
+
path: retainedPath,
|
|
8958
|
+
reason: `the upload response was lost before it could be read: ${message}`,
|
|
8959
|
+
retrySafe: false,
|
|
8960
|
+
};
|
|
8961
|
+
}
|
|
8962
|
+
// 3) A non-2xx answer is a definite, unambiguous failure — keep throwing.
|
|
8614
8963
|
if (!uploadResponse.ok) {
|
|
8615
|
-
await uploadResponse.text();
|
|
8964
|
+
await uploadResponse.text().catch(() => '');
|
|
8616
8965
|
throw new Error(`Upload failed with status: ${uploadResponse.status}`);
|
|
8617
8966
|
}
|
|
8618
|
-
|
|
8967
|
+
// 4) The PUT response IS the finalize response: it carries the durable URL
|
|
8968
|
+
// and the anonymous-read verdict, inside the worker's `{ data, status }`
|
|
8969
|
+
// envelope.
|
|
8970
|
+
let finalizeBody = null;
|
|
8971
|
+
try {
|
|
8972
|
+
const parsed = await uploadResponse.json();
|
|
8973
|
+
finalizeBody = (parsed && typeof parsed === 'object' && parsed.data && typeof parsed.data === 'object')
|
|
8974
|
+
? parsed.data
|
|
8975
|
+
: parsed;
|
|
8976
|
+
}
|
|
8977
|
+
catch (_e) {
|
|
8978
|
+
// A worker predating the URL-carrying finalize response answers with
|
|
8979
|
+
// something we cannot parse. The object still committed.
|
|
8980
|
+
finalizeBody = null;
|
|
8981
|
+
}
|
|
8982
|
+
const url = (finalizeBody && typeof finalizeBody.url === 'string' && finalizeBody.url.length > 0)
|
|
8983
|
+
? finalizeBody.url
|
|
8984
|
+
: null;
|
|
8985
|
+
return {
|
|
8986
|
+
status: 'uploaded',
|
|
8987
|
+
path: (_d = readWorkerFilePath(finalizeBody)) !== null && _d !== void 0 ? _d : retainedPath,
|
|
8988
|
+
url,
|
|
8989
|
+
visibility: readFileVisibility(finalizeBody),
|
|
8990
|
+
};
|
|
8619
8991
|
}
|
|
8620
8992
|
async function signMessage(message) {
|
|
8621
8993
|
const config = await getConfig();
|
|
@@ -8652,6 +9024,7 @@ var operations = /*#__PURE__*/Object.freeze({
|
|
|
8652
9024
|
__proto__: null,
|
|
8653
9025
|
InsufficientBalanceError: InsufficientBalanceError,
|
|
8654
9026
|
aggregate: aggregate,
|
|
9027
|
+
attemptMirrorSync: attemptMirrorSync,
|
|
8655
9028
|
clearReadCacheForAuthChange: clearReadCacheForAuthChange,
|
|
8656
9029
|
count: count,
|
|
8657
9030
|
docId: docId,
|
|
@@ -8674,6 +9047,341 @@ var operations = /*#__PURE__*/Object.freeze({
|
|
|
8674
9047
|
syncItems: syncItems
|
|
8675
9048
|
});
|
|
8676
9049
|
|
|
9050
|
+
const DEFAULT_TIMEOUT_MS = 60000;
|
|
9051
|
+
const DEFAULT_MAX_ATTEMPTS = 20;
|
|
9052
|
+
const DEFAULT_POLL_INTERVAL_MS = 3000;
|
|
9053
|
+
const DEFAULT_MIN_REBROADCAST_INTERVAL_MS = 15000;
|
|
9054
|
+
/** Mirror read after a late confirmation. No settle delay: the tx already landed. */
|
|
9055
|
+
const RECONCILE_MIRROR_SETTLE_MS = 0;
|
|
9056
|
+
function errorText(error) {
|
|
9057
|
+
const message = error === null || error === void 0 ? void 0 : error.message;
|
|
9058
|
+
return typeof message === 'string' && message.length > 0 ? message : String(error);
|
|
9059
|
+
}
|
|
9060
|
+
function isSolanaSubmitted(receipt) {
|
|
9061
|
+
return receipt.status === 'submitted' && 'signedTransaction' in receipt;
|
|
9062
|
+
}
|
|
9063
|
+
function isEvmFencedSubmitted(receipt) {
|
|
9064
|
+
return receipt.status === 'submitted' && 'fence' in receipt && !('signedTransaction' in receipt);
|
|
9065
|
+
}
|
|
9066
|
+
function isEvmHashKnownSubmitted(receipt) {
|
|
9067
|
+
return receipt.status === 'submitted' && receipt.rebroadcastable === false;
|
|
9068
|
+
}
|
|
9069
|
+
/**
|
|
9070
|
+
* The Solana execution lane a client config REQUIRES, or `null` when the config
|
|
9071
|
+
* carries no constraint. Only a positive contradiction rejects a receipt — an
|
|
9072
|
+
* unmapped or unset `chain` is not evidence of a mismatch.
|
|
9073
|
+
*/
|
|
9074
|
+
function requiredSolanaChain(config) {
|
|
9075
|
+
switch (config.chain) {
|
|
9076
|
+
case 'realtime_devnet':
|
|
9077
|
+
case 'solana_devnet':
|
|
9078
|
+
return 'solana_devnet';
|
|
9079
|
+
case 'realtime_mainnet':
|
|
9080
|
+
case 'realtime_mainnet_preview':
|
|
9081
|
+
case 'solana_mainnet':
|
|
9082
|
+
return 'solana_mainnet';
|
|
9083
|
+
default:
|
|
9084
|
+
return null;
|
|
9085
|
+
}
|
|
9086
|
+
}
|
|
9087
|
+
/**
|
|
9088
|
+
* Bind the receipt to the app + chain it was issued for BEFORE any RPC or
|
|
9089
|
+
* mirror request. Writes bind to an immutable config, so a bare reconcile after
|
|
9090
|
+
* a global re-`init()` could otherwise poll the wrong app's endpoints entirely.
|
|
9091
|
+
*/
|
|
9092
|
+
function assertReceiptMatchesContext(receipt, config) {
|
|
9093
|
+
var _a;
|
|
9094
|
+
if (!receipt.appId || typeof receipt.appId !== 'string') {
|
|
9095
|
+
throw new Error('This write receipt carries no appId and cannot be reconciled.');
|
|
9096
|
+
}
|
|
9097
|
+
if (receipt.appId !== config.appId) {
|
|
9098
|
+
throw new Error(`This write receipt was issued for app "${receipt.appId}" but the bound client targets "${config.appId}". ` +
|
|
9099
|
+
'Reconcile it through the client it came from.');
|
|
9100
|
+
}
|
|
9101
|
+
if (isSolanaSubmitted(receipt)) {
|
|
9102
|
+
const required = requiredSolanaChain(config);
|
|
9103
|
+
if (required && required !== receipt.chain) {
|
|
9104
|
+
throw new Error(`This write receipt ran on "${receipt.chain}" but the bound client targets "${required}".`);
|
|
9105
|
+
}
|
|
9106
|
+
return;
|
|
9107
|
+
}
|
|
9108
|
+
const fence = receipt.fence;
|
|
9109
|
+
if (fence && typeof fence.chainId === 'number') {
|
|
9110
|
+
const evm = config.evmWalletConfig;
|
|
9111
|
+
const configured = new Set();
|
|
9112
|
+
if (evm && typeof evm.chainId === 'number')
|
|
9113
|
+
configured.add(evm.chainId);
|
|
9114
|
+
for (const id of Object.keys((_a = evm === null || evm === void 0 ? void 0 : evm.runtimeAddresses) !== null && _a !== void 0 ? _a : {})) {
|
|
9115
|
+
const parsed = Number(id);
|
|
9116
|
+
if (Number.isSafeInteger(parsed))
|
|
9117
|
+
configured.add(parsed);
|
|
9118
|
+
}
|
|
9119
|
+
if (configured.size > 0 && !configured.has(fence.chainId)) {
|
|
9120
|
+
throw new Error(`This write receipt ran on EVM chain ${fence.chainId}, which the bound client does not configure.`);
|
|
9121
|
+
}
|
|
9122
|
+
}
|
|
9123
|
+
}
|
|
9124
|
+
function stillSubmitted(receipt, reason) {
|
|
9125
|
+
return Object.assign(Object.assign({}, receipt), { confirmation: Object.assign(Object.assign({}, receipt.confirmation), { reason }) });
|
|
9126
|
+
}
|
|
9127
|
+
async function mirrorFor(receipt, options) {
|
|
9128
|
+
const paths = receipt.requestedDocuments.map(entry => entry.path);
|
|
9129
|
+
if (paths.length === 0)
|
|
9130
|
+
return { observed: false, reason: 'the receipt carried no requested paths' };
|
|
9131
|
+
const setOptions = (options === null || options === void 0 ? void 0 : options._overrides) ? { _overrides: options._overrides } : undefined;
|
|
9132
|
+
return attemptMirrorSync(paths, setOptions, RECONCILE_MIRROR_SETTLE_MS);
|
|
9133
|
+
}
|
|
9134
|
+
async function resolveSolanaRpc(config, options) {
|
|
9135
|
+
var _a, _b;
|
|
9136
|
+
const injected = (_a = options === null || options === void 0 ? void 0 : options._overrides) === null || _a === void 0 ? void 0 : _a._solanaRpc;
|
|
9137
|
+
if (injected)
|
|
9138
|
+
return injected;
|
|
9139
|
+
const rpcUrl = (_b = config.rpcUrl) === null || _b === void 0 ? void 0 : _b.trim();
|
|
9140
|
+
if (!rpcUrl) {
|
|
9141
|
+
throw new Error('Reconciling a Solana write receipt requires init({ rpcUrl }).');
|
|
9142
|
+
}
|
|
9143
|
+
const { Connection } = await import('@solana/web3.js');
|
|
9144
|
+
return new Connection(rpcUrl, 'confirmed');
|
|
9145
|
+
}
|
|
9146
|
+
/**
|
|
9147
|
+
* Solana reconciliation NEVER broadcasts. It reaches `expired_not_landed` only
|
|
9148
|
+
* after the fence is observed expired AND history successfully proves the
|
|
9149
|
+
* signature is absent; a failed or inconclusive lookup stays `submitted`.
|
|
9150
|
+
*/
|
|
9151
|
+
async function reconcileSolana(receipt, config, options) {
|
|
9152
|
+
var _a, _b, _c, _d;
|
|
9153
|
+
const rpc = await resolveSolanaRpc(config, options);
|
|
9154
|
+
let statusValue = null;
|
|
9155
|
+
let contextSlot;
|
|
9156
|
+
try {
|
|
9157
|
+
if (typeof rpc.getSignatureStatuses !== 'function') {
|
|
9158
|
+
return stillSubmitted(receipt, 'the configured Solana RPC cannot report signature statuses');
|
|
9159
|
+
}
|
|
9160
|
+
const statuses = await rpc.getSignatureStatuses([receipt.transactionId]);
|
|
9161
|
+
statusValue = (_b = (_a = statuses === null || statuses === void 0 ? void 0 : statuses.value) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : null;
|
|
9162
|
+
contextSlot = (_c = statuses === null || statuses === void 0 ? void 0 : statuses.context) === null || _c === void 0 ? void 0 : _c.slot;
|
|
9163
|
+
}
|
|
9164
|
+
catch (error) {
|
|
9165
|
+
return stillSubmitted(receipt, `signature status lookup failed: ${errorText(error)}`);
|
|
9166
|
+
}
|
|
9167
|
+
if (statusValue) {
|
|
9168
|
+
// Landed. A landed-but-failed transaction is a genuine failure, not `submitted`.
|
|
9169
|
+
if (statusValue.err) {
|
|
9170
|
+
throw new Error(`Transaction failed: ${JSON.stringify(statusValue.err)}`);
|
|
9171
|
+
}
|
|
9172
|
+
const confirmed = {
|
|
9173
|
+
status: 'confirmed',
|
|
9174
|
+
appId: receipt.appId,
|
|
9175
|
+
requestedDocuments: receipt.requestedDocuments,
|
|
9176
|
+
chain: receipt.chain,
|
|
9177
|
+
transactionId: receipt.transactionId,
|
|
9178
|
+
signedTransaction: receipt.signedTransaction,
|
|
9179
|
+
fence: receipt.fence,
|
|
9180
|
+
confirmationContextSlot: contextSlot !== null && contextSlot !== void 0 ? contextSlot : 0,
|
|
9181
|
+
mirrorSync: await mirrorFor(receipt, options),
|
|
9182
|
+
};
|
|
9183
|
+
return confirmed;
|
|
9184
|
+
}
|
|
9185
|
+
// Absent from the status window. Expiry has to be OBSERVED before absence can
|
|
9186
|
+
// mean anything: before the fence expires the transaction may still land.
|
|
9187
|
+
let blockHeight;
|
|
9188
|
+
try {
|
|
9189
|
+
blockHeight = typeof rpc.getBlockHeight === 'function' ? await rpc.getBlockHeight('confirmed') : undefined;
|
|
9190
|
+
}
|
|
9191
|
+
catch (error) {
|
|
9192
|
+
return stillSubmitted(receipt, `block height lookup failed: ${errorText(error)}`);
|
|
9193
|
+
}
|
|
9194
|
+
if (typeof blockHeight !== 'number') {
|
|
9195
|
+
return stillSubmitted(receipt, 'expiry could not be observed: the RPC reported no block height');
|
|
9196
|
+
}
|
|
9197
|
+
if (blockHeight <= receipt.fence.lastValidBlockHeight) {
|
|
9198
|
+
return stillSubmitted(receipt, `not yet expired: block height ${blockHeight} is within the fence (lastValidBlockHeight ${receipt.fence.lastValidBlockHeight})`);
|
|
9199
|
+
}
|
|
9200
|
+
// Expired. Prove absence from history before declaring the write retry-safe.
|
|
9201
|
+
let history;
|
|
9202
|
+
try {
|
|
9203
|
+
history = await rpc.getTransaction(receipt.transactionId, {
|
|
9204
|
+
maxSupportedTransactionVersion: 0,
|
|
9205
|
+
commitment: 'confirmed',
|
|
9206
|
+
});
|
|
9207
|
+
}
|
|
9208
|
+
catch (error) {
|
|
9209
|
+
return stillSubmitted(receipt, `history lookup failed after expiry: ${errorText(error)}`);
|
|
9210
|
+
}
|
|
9211
|
+
if (history) {
|
|
9212
|
+
if ((_d = history === null || history === void 0 ? void 0 : history.meta) === null || _d === void 0 ? void 0 : _d.err) {
|
|
9213
|
+
throw new Error(`Transaction failed: ${JSON.stringify(history.meta.err)}`);
|
|
9214
|
+
}
|
|
9215
|
+
const confirmed = {
|
|
9216
|
+
status: 'confirmed',
|
|
9217
|
+
appId: receipt.appId,
|
|
9218
|
+
requestedDocuments: receipt.requestedDocuments,
|
|
9219
|
+
chain: receipt.chain,
|
|
9220
|
+
transactionId: receipt.transactionId,
|
|
9221
|
+
signedTransaction: receipt.signedTransaction,
|
|
9222
|
+
fence: receipt.fence,
|
|
9223
|
+
confirmationContextSlot: typeof history.slot === 'number' ? history.slot : (contextSlot !== null && contextSlot !== void 0 ? contextSlot : 0),
|
|
9224
|
+
mirrorSync: await mirrorFor(receipt, options),
|
|
9225
|
+
};
|
|
9226
|
+
return confirmed;
|
|
9227
|
+
}
|
|
9228
|
+
return {
|
|
9229
|
+
status: 'expired_not_landed',
|
|
9230
|
+
appId: receipt.appId,
|
|
9231
|
+
requestedDocuments: receipt.requestedDocuments,
|
|
9232
|
+
chain: receipt.chain,
|
|
9233
|
+
transactionId: receipt.transactionId,
|
|
9234
|
+
fence: receipt.fence,
|
|
9235
|
+
};
|
|
9236
|
+
}
|
|
9237
|
+
/**
|
|
9238
|
+
* EVM reconciliation runs through the provider's own transport, so a wallet
|
|
9239
|
+
* client reconciles with its own identity and RPC. There is deliberately NO
|
|
9240
|
+
* EVM retry-safe terminal state: nonce advancement is not proof the write did
|
|
9241
|
+
* not execute, and plain JSON-RPC offers no transaction-by-sender-and-nonce
|
|
9242
|
+
* lookup, so a `submitted` EVM receipt either confirms or stays `submitted`.
|
|
9243
|
+
*/
|
|
9244
|
+
async function reconcileEvm(receipt, provider, options) {
|
|
9245
|
+
if (!provider || typeof provider.reconcileEvmTransaction !== 'function') {
|
|
9246
|
+
return stillSubmitted(receipt, 'no EVM reconciliation transport is available on the bound auth provider');
|
|
9247
|
+
}
|
|
9248
|
+
const fenced = isEvmFencedSubmitted(receipt) ? receipt.fence : undefined;
|
|
9249
|
+
const allowRebroadcast = Boolean(fenced) && (options === null || options === void 0 ? void 0 : options.allowRebroadcast) !== false;
|
|
9250
|
+
let outcome;
|
|
9251
|
+
try {
|
|
9252
|
+
outcome = await provider.reconcileEvmTransaction({
|
|
9253
|
+
transactionHash: receipt.transactionId,
|
|
9254
|
+
fence: fenced,
|
|
9255
|
+
allowRebroadcast,
|
|
9256
|
+
});
|
|
9257
|
+
}
|
|
9258
|
+
catch (error) {
|
|
9259
|
+
return stillSubmitted(receipt, `EVM reconciliation failed: ${errorText(error)}`);
|
|
9260
|
+
}
|
|
9261
|
+
if (outcome.state === 'failed') {
|
|
9262
|
+
// Landed and reverted: a genuine failure, never `submitted`.
|
|
9263
|
+
throw new Error(outcome.reason);
|
|
9264
|
+
}
|
|
9265
|
+
if (outcome.state === 'confirmed') {
|
|
9266
|
+
if (typeof outcome.blockNumber !== 'number') {
|
|
9267
|
+
return stillSubmitted(receipt, 'the EVM receipt reported success without a block number, so confirmation could not be stated');
|
|
9268
|
+
}
|
|
9269
|
+
const confirmed = {
|
|
9270
|
+
status: 'confirmed',
|
|
9271
|
+
appId: receipt.appId,
|
|
9272
|
+
requestedDocuments: receipt.requestedDocuments,
|
|
9273
|
+
chain: receipt.chain,
|
|
9274
|
+
transactionId: receipt.transactionId,
|
|
9275
|
+
blockNumber: outcome.blockNumber,
|
|
9276
|
+
mirrorSync: await mirrorFor(receipt, options),
|
|
9277
|
+
};
|
|
9278
|
+
return confirmed;
|
|
9279
|
+
}
|
|
9280
|
+
if (outcome.state === 'absent_after_expiry') {
|
|
9281
|
+
// Defensive: no EVM lane may report a retry-safe terminal state.
|
|
9282
|
+
return stillSubmitted(receipt, 'the EVM transport reported absence, which is not proof an EVM write did not execute');
|
|
9283
|
+
}
|
|
9284
|
+
return stillSubmitted(receipt, outcome.reason);
|
|
9285
|
+
}
|
|
9286
|
+
/**
|
|
9287
|
+
* Poll the SAME signature/hash a `submitted` receipt carries and return the
|
|
9288
|
+
* resulting `SetResult`.
|
|
9289
|
+
*
|
|
9290
|
+
* - Terminal receipts (`committed`, `confirmed`, `expired_not_landed`,
|
|
9291
|
+
* `signed`) are returned unchanged; nothing was left open.
|
|
9292
|
+
* - An irreconcilable injected-wallet receipt (`transactionId: null`) is
|
|
9293
|
+
* rejected immediately: no identifier exists to poll.
|
|
9294
|
+
* - A transaction that landed and FAILED throws, exactly as it would have on
|
|
9295
|
+
* the original write.
|
|
9296
|
+
*/
|
|
9297
|
+
async function reconcileSetResult(receipt, options) {
|
|
9298
|
+
var _a, _b, _c, _d, _e;
|
|
9299
|
+
if (!receipt || typeof receipt !== 'object' || typeof receipt.status !== 'string') {
|
|
9300
|
+
throw new Error('reconcileSetResult requires a write receipt returned by set/setMany.');
|
|
9301
|
+
}
|
|
9302
|
+
if (receipt.status !== 'submitted')
|
|
9303
|
+
return receipt;
|
|
9304
|
+
if (receipt.transactionId === null) {
|
|
9305
|
+
throw new Error('This receipt is explicitly irreconcilable: the wallet send response was lost, so no transaction hash exists to poll. ' +
|
|
9306
|
+
'Resolve it out of band — resubmitting could execute the write twice.');
|
|
9307
|
+
}
|
|
9308
|
+
const config = (_b = (_a = options === null || options === void 0 ? void 0 : options._overrides) === null || _a === void 0 ? void 0 : _a._config) !== null && _b !== void 0 ? _b : await getConfig();
|
|
9309
|
+
// Validation happens BEFORE any RPC or mirror request.
|
|
9310
|
+
assertReceiptMatchesContext(receipt, config);
|
|
9311
|
+
if (isSolanaSubmitted(receipt)) {
|
|
9312
|
+
return reconcileSolana(receipt, config, options);
|
|
9313
|
+
}
|
|
9314
|
+
const provider = (_e = (_d = (_c = options === null || options === void 0 ? void 0 : options._overrides) === null || _c === void 0 ? void 0 : _c.authProvider) !== null && _d !== void 0 ? _d : config.authProvider) !== null && _e !== void 0 ? _e : null;
|
|
9315
|
+
if (isEvmFencedSubmitted(receipt) || isEvmHashKnownSubmitted(receipt)) {
|
|
9316
|
+
return reconcileEvm(receipt, provider, options);
|
|
9317
|
+
}
|
|
9318
|
+
return stillSubmitted(receipt, 'this receipt shape has no reconciliation lane');
|
|
9319
|
+
}
|
|
9320
|
+
/**
|
|
9321
|
+
* Reconcile repeatedly until the receipt leaves `submitted` or the bound is hit.
|
|
9322
|
+
*
|
|
9323
|
+
* The bound is mandatory, because some states are permanently non-terminal by
|
|
9324
|
+
* design: an unidentified nonce consumer or a permanently underpriced EVM
|
|
9325
|
+
* transaction never resolves. On exhaustion (or abort) this returns the LATEST
|
|
9326
|
+
* `submitted` receipt with its identifier intact rather than hanging or
|
|
9327
|
+
* throwing the identifier away.
|
|
9328
|
+
*/
|
|
9329
|
+
async function waitForSetResult(receipt, options) {
|
|
9330
|
+
var _a, _b, _c, _d, _e;
|
|
9331
|
+
if (receipt && typeof receipt === 'object' && receipt.status === 'submitted' && receipt.transactionId === null) {
|
|
9332
|
+
// Rejected immediately rather than polled: there is nothing to poll.
|
|
9333
|
+
return reconcileSetResult(receipt, options);
|
|
9334
|
+
}
|
|
9335
|
+
const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : DEFAULT_TIMEOUT_MS;
|
|
9336
|
+
const maxAttempts = (_b = options === null || options === void 0 ? void 0 : options.maxAttempts) !== null && _b !== void 0 ? _b : DEFAULT_MAX_ATTEMPTS;
|
|
9337
|
+
const pollIntervalMs = (_c = options === null || options === void 0 ? void 0 : options.pollIntervalMs) !== null && _c !== void 0 ? _c : DEFAULT_POLL_INTERVAL_MS;
|
|
9338
|
+
const minRebroadcastIntervalMs = (_d = options === null || options === void 0 ? void 0 : options.minRebroadcastIntervalMs) !== null && _d !== void 0 ? _d : DEFAULT_MIN_REBROADCAST_INTERVAL_MS;
|
|
9339
|
+
const rebroadcastPermitted = (options === null || options === void 0 ? void 0 : options.allowRebroadcast) !== false;
|
|
9340
|
+
const startedAt = Date.now();
|
|
9341
|
+
let current = receipt;
|
|
9342
|
+
let attempts = 0;
|
|
9343
|
+
let lastRebroadcastAt = -Infinity;
|
|
9344
|
+
while (attempts < maxAttempts && Date.now() - startedAt < timeoutMs) {
|
|
9345
|
+
if ((_e = options === null || options === void 0 ? void 0 : options.signal) === null || _e === void 0 ? void 0 : _e.aborted)
|
|
9346
|
+
return current;
|
|
9347
|
+
const now = Date.now();
|
|
9348
|
+
const allowRebroadcast = rebroadcastPermitted && (now - lastRebroadcastAt >= minRebroadcastIntervalMs);
|
|
9349
|
+
if (allowRebroadcast)
|
|
9350
|
+
lastRebroadcastAt = now;
|
|
9351
|
+
attempts++;
|
|
9352
|
+
current = await reconcileSetResult(current, Object.assign(Object.assign({}, options), { allowRebroadcast }));
|
|
9353
|
+
if (current.status !== 'submitted')
|
|
9354
|
+
return current;
|
|
9355
|
+
if (attempts >= maxAttempts)
|
|
9356
|
+
break;
|
|
9357
|
+
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
9358
|
+
if (remaining <= 0)
|
|
9359
|
+
break;
|
|
9360
|
+
const slept = await sleepUnlessAborted(Math.min(pollIntervalMs, remaining), options === null || options === void 0 ? void 0 : options.signal);
|
|
9361
|
+
if (!slept)
|
|
9362
|
+
return current;
|
|
9363
|
+
}
|
|
9364
|
+
return current;
|
|
9365
|
+
}
|
|
9366
|
+
/** Resolves `false` when the signal aborted during (or before) the sleep. */
|
|
9367
|
+
function sleepUnlessAborted(ms, signal) {
|
|
9368
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
9369
|
+
return Promise.resolve(false);
|
|
9370
|
+
if (!signal)
|
|
9371
|
+
return new Promise(resolve => setTimeout(() => resolve(true), ms));
|
|
9372
|
+
return new Promise(resolve => {
|
|
9373
|
+
const timer = setTimeout(() => {
|
|
9374
|
+
signal.removeEventListener('abort', onAbort);
|
|
9375
|
+
resolve(true);
|
|
9376
|
+
}, ms);
|
|
9377
|
+
const onAbort = () => {
|
|
9378
|
+
clearTimeout(timer);
|
|
9379
|
+
resolve(false);
|
|
9380
|
+
};
|
|
9381
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
9382
|
+
});
|
|
9383
|
+
}
|
|
9384
|
+
|
|
8677
9385
|
/**
|
|
8678
9386
|
* WebSocket Subscription Module
|
|
8679
9387
|
*
|
|
@@ -9515,7 +10223,7 @@ function toMillis(seconds) {
|
|
|
9515
10223
|
|
|
9516
10224
|
/** SIWS message creation is async already, so its onchain module can be lazy. */
|
|
9517
10225
|
async function genSolanaMessage(address, nonce) {
|
|
9518
|
-
const implementation = await Promise.resolve().then(function () { return require('./solana-message-
|
|
10226
|
+
const implementation = await Promise.resolve().then(function () { return require('./solana-message-Bycv-L5n.js'); });
|
|
9519
10227
|
return implementation.genSolanaMessage(address, nonce);
|
|
9520
10228
|
}
|
|
9521
10229
|
|
|
@@ -10044,6 +10752,7 @@ exports.normalizeEvmAddress = normalizeEvmAddress;
|
|
|
10044
10752
|
exports.now = now;
|
|
10045
10753
|
exports.onSessionCleared = onSessionCleared;
|
|
10046
10754
|
exports.queryAggregate = queryAggregate;
|
|
10755
|
+
exports.reconcileSetResult = reconcileSetResult;
|
|
10047
10756
|
exports.reconnectWithNewAuth = reconnectWithNewAuth;
|
|
10048
10757
|
exports.refreshSession = refreshSession;
|
|
10049
10758
|
exports.revokeSession = revokeSession;
|
|
@@ -10067,5 +10776,6 @@ exports.subscribeView = subscribeView;
|
|
|
10067
10776
|
exports.toLowerEvmAddress = toLowerEvmAddress;
|
|
10068
10777
|
exports.toMillis = toMillis;
|
|
10069
10778
|
exports.toSeconds = toSeconds;
|
|
10779
|
+
exports.waitForSetResult = waitForSetResult;
|
|
10070
10780
|
exports.withEffects = withEffects;
|
|
10071
|
-
//# sourceMappingURL=index-
|
|
10781
|
+
//# sourceMappingURL=index-DNZifL5j.js.map
|