@yieldfabric/wallet 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +41 -2
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +41 -2
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1521,6 +1521,26 @@ var init_tokenStore = __esm({
|
|
|
1521
1521
|
}
|
|
1522
1522
|
});
|
|
1523
1523
|
|
|
1524
|
+
// src/core/crossTabRefresh.ts
|
|
1525
|
+
function withRefreshLock(renew, locks = globalThis.navigator?.locks) {
|
|
1526
|
+
if (!locks?.request) return renew();
|
|
1527
|
+
return locks.request(REFRESH_LOCK, renew);
|
|
1528
|
+
}
|
|
1529
|
+
function renewedElsewhere(spent, stored, chainId, now = Date.now()) {
|
|
1530
|
+
if (!stored.access || !stored.refresh || stored.refresh === spent) return false;
|
|
1531
|
+
const claims = decodeJwtPayload(stored.access);
|
|
1532
|
+
const exp = claims?.exp;
|
|
1533
|
+
return typeof exp === "number" && exp * 1e3 - now > LIVE_FOR_MS && claims?.default_chain_id === chainId;
|
|
1534
|
+
}
|
|
1535
|
+
var REFRESH_LOCK, LIVE_FOR_MS;
|
|
1536
|
+
var init_crossTabRefresh = __esm({
|
|
1537
|
+
"src/core/crossTabRefresh.ts"() {
|
|
1538
|
+
init_jwtPayload();
|
|
1539
|
+
REFRESH_LOCK = "yieldfabric-auth-refresh";
|
|
1540
|
+
LIVE_FOR_MS = 3e4;
|
|
1541
|
+
}
|
|
1542
|
+
});
|
|
1543
|
+
|
|
1524
1544
|
// src/core/onboardingPlanSelectionStorage.ts
|
|
1525
1545
|
function readOnboardingPlanSelectionHint() {
|
|
1526
1546
|
if (typeof window === "undefined") return null;
|
|
@@ -1600,6 +1620,7 @@ var init_authService = __esm({
|
|
|
1600
1620
|
init_tokenManager();
|
|
1601
1621
|
init_jwtClaims();
|
|
1602
1622
|
init_tokenStore();
|
|
1623
|
+
init_crossTabRefresh();
|
|
1603
1624
|
init_onboardingPlanSelectionStorage();
|
|
1604
1625
|
isRefreshing = false;
|
|
1605
1626
|
refreshPromise = null;
|
|
@@ -1786,19 +1807,23 @@ var init_authService = __esm({
|
|
|
1786
1807
|
error.statusCode = 401;
|
|
1787
1808
|
throw error;
|
|
1788
1809
|
}
|
|
1810
|
+
const spent = this.refreshToken;
|
|
1789
1811
|
isRefreshing = true;
|
|
1790
1812
|
refreshOperation = { kind: "regular", chainId };
|
|
1791
|
-
refreshPromise = this.performRefresh(chainId);
|
|
1813
|
+
refreshPromise = withRefreshLock(async () => this.adoptRenewedSession(chainId, spent) ?? this.performRefresh(chainId));
|
|
1792
1814
|
const activePromise = refreshPromise;
|
|
1793
1815
|
try {
|
|
1794
1816
|
const result = await refreshPromise;
|
|
1795
1817
|
return result;
|
|
1796
1818
|
} catch (error) {
|
|
1797
1819
|
switch (classifyAuthFailure(error)) {
|
|
1798
|
-
case "auth":
|
|
1820
|
+
case "auth": {
|
|
1821
|
+
const adopted = this.adoptRenewedSession(chainId, spent);
|
|
1822
|
+
if (adopted) return adopted;
|
|
1799
1823
|
console.warn("Authentication error during token refresh, clearing tokens");
|
|
1800
1824
|
this.clearTokensFromStorage();
|
|
1801
1825
|
break;
|
|
1826
|
+
}
|
|
1802
1827
|
case "network":
|
|
1803
1828
|
console.warn("Network error during token refresh, preserving tokens for retry");
|
|
1804
1829
|
break;
|
|
@@ -1814,6 +1839,20 @@ var init_authService = __esm({
|
|
|
1814
1839
|
}
|
|
1815
1840
|
}
|
|
1816
1841
|
}
|
|
1842
|
+
/** The session another window renewed since this one read the refresh
|
|
1843
|
+
* token `spent`, taken as this window's own; `null` when there is none. */
|
|
1844
|
+
adoptRenewedSession(chainId, spent) {
|
|
1845
|
+
const stored = { access: exports.defaultTokenStore.getAccessToken(), refresh: exports.defaultTokenStore.getRefreshToken() };
|
|
1846
|
+
if (!stored.access || !stored.refresh || !renewedElsewhere(spent, stored, chainId)) return null;
|
|
1847
|
+
this.saveTokensToStorage(stored.access, stored.refresh);
|
|
1848
|
+
const exp = decodeJwtPayload(stored.access)?.exp;
|
|
1849
|
+
return {
|
|
1850
|
+
access_token: stored.access,
|
|
1851
|
+
refresh_token: stored.refresh,
|
|
1852
|
+
token_type: "Bearer",
|
|
1853
|
+
expires_in: Math.max(0, Math.floor(exp - Date.now() / 1e3))
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1817
1856
|
/**
|
|
1818
1857
|
* Perform the actual token refresh.
|
|
1819
1858
|
*
|
package/dist/index.d.cts
CHANGED
|
@@ -741,6 +741,9 @@ declare class AuthService {
|
|
|
741
741
|
* Uses global lock to prevent concurrent refresh attempts
|
|
742
742
|
*/
|
|
743
743
|
refreshAccessToken(targetChainId?: string): Promise<RefreshTokenResponse>;
|
|
744
|
+
/** The session another window renewed since this one read the refresh
|
|
745
|
+
* token `spent`, taken as this window's own; `null` when there is none. */
|
|
746
|
+
private adoptRenewedSession;
|
|
744
747
|
/**
|
|
745
748
|
* Perform the actual token refresh.
|
|
746
749
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -741,6 +741,9 @@ declare class AuthService {
|
|
|
741
741
|
* Uses global lock to prevent concurrent refresh attempts
|
|
742
742
|
*/
|
|
743
743
|
refreshAccessToken(targetChainId?: string): Promise<RefreshTokenResponse>;
|
|
744
|
+
/** The session another window renewed since this one read the refresh
|
|
745
|
+
* token `spent`, taken as this window's own; `null` when there is none. */
|
|
746
|
+
private adoptRenewedSession;
|
|
744
747
|
/**
|
|
745
748
|
* Perform the actual token refresh.
|
|
746
749
|
*
|
package/dist/index.js
CHANGED
|
@@ -1513,6 +1513,26 @@ var init_tokenStore = __esm({
|
|
|
1513
1513
|
}
|
|
1514
1514
|
});
|
|
1515
1515
|
|
|
1516
|
+
// src/core/crossTabRefresh.ts
|
|
1517
|
+
function withRefreshLock(renew, locks = globalThis.navigator?.locks) {
|
|
1518
|
+
if (!locks?.request) return renew();
|
|
1519
|
+
return locks.request(REFRESH_LOCK, renew);
|
|
1520
|
+
}
|
|
1521
|
+
function renewedElsewhere(spent, stored, chainId, now = Date.now()) {
|
|
1522
|
+
if (!stored.access || !stored.refresh || stored.refresh === spent) return false;
|
|
1523
|
+
const claims = decodeJwtPayload(stored.access);
|
|
1524
|
+
const exp = claims?.exp;
|
|
1525
|
+
return typeof exp === "number" && exp * 1e3 - now > LIVE_FOR_MS && claims?.default_chain_id === chainId;
|
|
1526
|
+
}
|
|
1527
|
+
var REFRESH_LOCK, LIVE_FOR_MS;
|
|
1528
|
+
var init_crossTabRefresh = __esm({
|
|
1529
|
+
"src/core/crossTabRefresh.ts"() {
|
|
1530
|
+
init_jwtPayload();
|
|
1531
|
+
REFRESH_LOCK = "yieldfabric-auth-refresh";
|
|
1532
|
+
LIVE_FOR_MS = 3e4;
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1516
1536
|
// src/core/onboardingPlanSelectionStorage.ts
|
|
1517
1537
|
function readOnboardingPlanSelectionHint() {
|
|
1518
1538
|
if (typeof window === "undefined") return null;
|
|
@@ -1592,6 +1612,7 @@ var init_authService = __esm({
|
|
|
1592
1612
|
init_tokenManager();
|
|
1593
1613
|
init_jwtClaims();
|
|
1594
1614
|
init_tokenStore();
|
|
1615
|
+
init_crossTabRefresh();
|
|
1595
1616
|
init_onboardingPlanSelectionStorage();
|
|
1596
1617
|
isRefreshing = false;
|
|
1597
1618
|
refreshPromise = null;
|
|
@@ -1778,19 +1799,23 @@ var init_authService = __esm({
|
|
|
1778
1799
|
error.statusCode = 401;
|
|
1779
1800
|
throw error;
|
|
1780
1801
|
}
|
|
1802
|
+
const spent = this.refreshToken;
|
|
1781
1803
|
isRefreshing = true;
|
|
1782
1804
|
refreshOperation = { kind: "regular", chainId };
|
|
1783
|
-
refreshPromise = this.performRefresh(chainId);
|
|
1805
|
+
refreshPromise = withRefreshLock(async () => this.adoptRenewedSession(chainId, spent) ?? this.performRefresh(chainId));
|
|
1784
1806
|
const activePromise = refreshPromise;
|
|
1785
1807
|
try {
|
|
1786
1808
|
const result = await refreshPromise;
|
|
1787
1809
|
return result;
|
|
1788
1810
|
} catch (error) {
|
|
1789
1811
|
switch (classifyAuthFailure(error)) {
|
|
1790
|
-
case "auth":
|
|
1812
|
+
case "auth": {
|
|
1813
|
+
const adopted = this.adoptRenewedSession(chainId, spent);
|
|
1814
|
+
if (adopted) return adopted;
|
|
1791
1815
|
console.warn("Authentication error during token refresh, clearing tokens");
|
|
1792
1816
|
this.clearTokensFromStorage();
|
|
1793
1817
|
break;
|
|
1818
|
+
}
|
|
1794
1819
|
case "network":
|
|
1795
1820
|
console.warn("Network error during token refresh, preserving tokens for retry");
|
|
1796
1821
|
break;
|
|
@@ -1806,6 +1831,20 @@ var init_authService = __esm({
|
|
|
1806
1831
|
}
|
|
1807
1832
|
}
|
|
1808
1833
|
}
|
|
1834
|
+
/** The session another window renewed since this one read the refresh
|
|
1835
|
+
* token `spent`, taken as this window's own; `null` when there is none. */
|
|
1836
|
+
adoptRenewedSession(chainId, spent) {
|
|
1837
|
+
const stored = { access: defaultTokenStore.getAccessToken(), refresh: defaultTokenStore.getRefreshToken() };
|
|
1838
|
+
if (!stored.access || !stored.refresh || !renewedElsewhere(spent, stored, chainId)) return null;
|
|
1839
|
+
this.saveTokensToStorage(stored.access, stored.refresh);
|
|
1840
|
+
const exp = decodeJwtPayload(stored.access)?.exp;
|
|
1841
|
+
return {
|
|
1842
|
+
access_token: stored.access,
|
|
1843
|
+
refresh_token: stored.refresh,
|
|
1844
|
+
token_type: "Bearer",
|
|
1845
|
+
expires_in: Math.max(0, Math.floor(exp - Date.now() / 1e3))
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1809
1848
|
/**
|
|
1810
1849
|
* Perform the actual token refresh.
|
|
1811
1850
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yieldfabric/wallet",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Reusable YieldFabric wallet — login, signing, profile, and key management",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "YieldFabric Technologies Ltd",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"build": "tsup",
|
|
56
56
|
"dev": "tsup --watch",
|
|
57
57
|
"clean": "rm -rf dist",
|
|
58
|
-
"test:owned-group": "tsup --config tests/tsup.config.ts && node --test tests/ownedGroupSession.test.mjs tests/metaTransactionDigest.test.mjs tests/ownedGroupAuthorization.test.mjs tests/groupOwnershipRelationships.test.mjs tests/ownedGroupDelegationService.test.mjs tests/ownedGroupKeySelection.test.mjs tests/websocketAuthentication.test.mjs tests/withDeadline.test.mjs tests/onboardingCache.test.mjs tests/authFailure.test.mjs tests/jsonEventStream.test.mjs",
|
|
58
|
+
"test:owned-group": "tsup --config tests/tsup.config.ts && node --test tests/ownedGroupSession.test.mjs tests/metaTransactionDigest.test.mjs tests/ownedGroupAuthorization.test.mjs tests/groupOwnershipRelationships.test.mjs tests/ownedGroupDelegationService.test.mjs tests/ownedGroupKeySelection.test.mjs tests/websocketAuthentication.test.mjs tests/withDeadline.test.mjs tests/onboardingCache.test.mjs tests/authFailure.test.mjs tests/jsonEventStream.test.mjs tests/crossTabRefresh.test.mjs tests/twoWindowsRefresh.test.mjs",
|
|
59
59
|
"typecheck": "tsc --noEmit",
|
|
60
60
|
"prepublishOnly": "npm run clean && npm run build"
|
|
61
61
|
},
|