@gvnrdao/dh-sdk 0.0.308 → 0.0.311
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/browser/dist/browser.js +1 -1
- package/dist/index.js +123 -15
- package/dist/index.mjs +123 -15
- package/dist/interfaces/chunks/config.i.d.ts +8 -0
- package/dist/utils/eip712-login.d.ts +13 -1
- package/dist/utils/server-session-store.d.ts +44 -0
- package/dist/utils/server-session.d.ts +12 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6507,7 +6507,7 @@ async function generateExtendAuthorization(positionId, newTerm, chainId, signerO
|
|
|
6507
6507
|
var import_ethers4 = require("ethers");
|
|
6508
6508
|
function buildLoginDomain(chainId) {
|
|
6509
6509
|
return {
|
|
6510
|
-
name: "Diamond Hands
|
|
6510
|
+
name: "Diamond Hands",
|
|
6511
6511
|
version: "1",
|
|
6512
6512
|
chainId
|
|
6513
6513
|
};
|
|
@@ -6527,12 +6527,12 @@ var LOGIN_TYPES_WITH_AUDIENCE = {
|
|
|
6527
6527
|
{ name: "audience", type: "string" }
|
|
6528
6528
|
]
|
|
6529
6529
|
};
|
|
6530
|
-
async function buildSignedLoginPayload(signer, chainId, audience) {
|
|
6530
|
+
async function buildSignedLoginPayload(signer, chainId, audience, reuse) {
|
|
6531
6531
|
const address = await signer.getAddress();
|
|
6532
6532
|
const message = {
|
|
6533
6533
|
address,
|
|
6534
|
-
issuedAt: Math.floor(Date.now() / 1e3),
|
|
6535
|
-
nonce: (0, import_ethers4.hexlify)((0, import_ethers4.randomBytes)(32)),
|
|
6534
|
+
issuedAt: reuse?.issuedAt ?? Math.floor(Date.now() / 1e3),
|
|
6535
|
+
nonce: reuse?.nonce ?? (0, import_ethers4.hexlify)((0, import_ethers4.randomBytes)(32)),
|
|
6536
6536
|
...audience ? { audience } : {}
|
|
6537
6537
|
};
|
|
6538
6538
|
const signature = await signer.signTypedData(
|
|
@@ -6550,6 +6550,13 @@ var STORE_PREFIX = "dh-server-session:";
|
|
|
6550
6550
|
function sessionStoreKey(address, chainId, endpoint) {
|
|
6551
6551
|
return STORE_PREFIX + `${address.trim().toLowerCase()}:${chainId}:${endpoint.replace(/\/+$/, "")}`;
|
|
6552
6552
|
}
|
|
6553
|
+
var PENDING_ENVELOPE_TTL_SECONDS = 8 * 60;
|
|
6554
|
+
function pendingEnvelopeKey(sessionKey) {
|
|
6555
|
+
return sessionKey + ":pending";
|
|
6556
|
+
}
|
|
6557
|
+
function isPendingEnvelopeShape(value) {
|
|
6558
|
+
return !!value && typeof value === "object" && typeof value.issuedAt === "number" && typeof value.nonce === "string";
|
|
6559
|
+
}
|
|
6553
6560
|
function isPayloadShape(value) {
|
|
6554
6561
|
return !!value && typeof value === "object" && typeof value.chainId === "number" && typeof value.signature === "string" && !!value.message && typeof value.message.address === "string" && typeof value.message.issuedAt === "number" && typeof value.message.nonce === "string";
|
|
6555
6562
|
}
|
|
@@ -6591,9 +6598,48 @@ var LocalStorageSessionStore = class {
|
|
|
6591
6598
|
} catch {
|
|
6592
6599
|
}
|
|
6593
6600
|
}
|
|
6601
|
+
loadPending(key) {
|
|
6602
|
+
let raw;
|
|
6603
|
+
try {
|
|
6604
|
+
raw = window.localStorage.getItem(pendingEnvelopeKey(key));
|
|
6605
|
+
} catch {
|
|
6606
|
+
return null;
|
|
6607
|
+
}
|
|
6608
|
+
if (!raw)
|
|
6609
|
+
return null;
|
|
6610
|
+
let parsed;
|
|
6611
|
+
try {
|
|
6612
|
+
parsed = JSON.parse(raw);
|
|
6613
|
+
} catch {
|
|
6614
|
+
this.clearPending(key);
|
|
6615
|
+
return null;
|
|
6616
|
+
}
|
|
6617
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
6618
|
+
if (!isPendingEnvelopeShape(parsed) || nowSec >= parsed.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
|
|
6619
|
+
this.clearPending(key);
|
|
6620
|
+
return null;
|
|
6621
|
+
}
|
|
6622
|
+
return parsed;
|
|
6623
|
+
}
|
|
6624
|
+
savePending(key, envelope) {
|
|
6625
|
+
try {
|
|
6626
|
+
window.localStorage.setItem(
|
|
6627
|
+
pendingEnvelopeKey(key),
|
|
6628
|
+
JSON.stringify(envelope)
|
|
6629
|
+
);
|
|
6630
|
+
} catch {
|
|
6631
|
+
}
|
|
6632
|
+
}
|
|
6633
|
+
clearPending(key) {
|
|
6634
|
+
try {
|
|
6635
|
+
window.localStorage.removeItem(pendingEnvelopeKey(key));
|
|
6636
|
+
} catch {
|
|
6637
|
+
}
|
|
6638
|
+
}
|
|
6594
6639
|
};
|
|
6595
6640
|
var MemorySessionStore = class {
|
|
6596
6641
|
entries = /* @__PURE__ */ new Map();
|
|
6642
|
+
pending = /* @__PURE__ */ new Map();
|
|
6597
6643
|
load(key) {
|
|
6598
6644
|
return this.entries.get(key) ?? null;
|
|
6599
6645
|
}
|
|
@@ -6603,6 +6649,23 @@ var MemorySessionStore = class {
|
|
|
6603
6649
|
clear(key) {
|
|
6604
6650
|
this.entries.delete(key);
|
|
6605
6651
|
}
|
|
6652
|
+
loadPending(key) {
|
|
6653
|
+
const envelope = this.pending.get(key);
|
|
6654
|
+
if (!envelope)
|
|
6655
|
+
return null;
|
|
6656
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
6657
|
+
if (nowSec >= envelope.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
|
|
6658
|
+
this.pending.delete(key);
|
|
6659
|
+
return null;
|
|
6660
|
+
}
|
|
6661
|
+
return envelope;
|
|
6662
|
+
}
|
|
6663
|
+
savePending(key, envelope) {
|
|
6664
|
+
this.pending.set(key, envelope);
|
|
6665
|
+
}
|
|
6666
|
+
clearPending(key) {
|
|
6667
|
+
this.pending.delete(key);
|
|
6668
|
+
}
|
|
6606
6669
|
};
|
|
6607
6670
|
function localStorageUsable() {
|
|
6608
6671
|
try {
|
|
@@ -6622,6 +6685,8 @@ function createDefaultSessionStore() {
|
|
|
6622
6685
|
|
|
6623
6686
|
// src/utils/server-session.ts
|
|
6624
6687
|
var REFRESH_LEEWAY_SECONDS = 30;
|
|
6688
|
+
var LOGIN_RETRY_BASE_MS = 3e4;
|
|
6689
|
+
var LOGIN_RETRY_MAX_MS = 5 * 6e4;
|
|
6625
6690
|
var ServerLoginError = class extends Error {
|
|
6626
6691
|
constructor(message, status) {
|
|
6627
6692
|
super(message);
|
|
@@ -6638,10 +6703,16 @@ var ServerSession = class {
|
|
|
6638
6703
|
chainId;
|
|
6639
6704
|
store;
|
|
6640
6705
|
onSignaturePrompt;
|
|
6706
|
+
onSignatureResolved;
|
|
6641
6707
|
now;
|
|
6642
6708
|
fetchImpl;
|
|
6643
6709
|
cached = null;
|
|
6644
6710
|
inFlight = null;
|
|
6711
|
+
/**
|
|
6712
|
+
* Set when a prompted login failed; suppresses further prompts until `until`.
|
|
6713
|
+
* `error` is replayed to callers so they still see the real failure reason.
|
|
6714
|
+
*/
|
|
6715
|
+
promptBackoff = null;
|
|
6645
6716
|
/**
|
|
6646
6717
|
* Bumped by `clearPersisted()` (logout / disconnect). An in-flight
|
|
6647
6718
|
* `getOrRefresh()` captures this at start and refuses to write its freshly
|
|
@@ -6662,6 +6733,7 @@ var ServerSession = class {
|
|
|
6662
6733
|
this.chainId = opts.chainId;
|
|
6663
6734
|
this.store = opts.persistSession === false ? null : opts.sessionStore ?? createDefaultSessionStore();
|
|
6664
6735
|
this.onSignaturePrompt = opts.onSignaturePrompt;
|
|
6736
|
+
this.onSignatureResolved = opts.onSignatureResolved;
|
|
6665
6737
|
this.now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
6666
6738
|
this.fetchImpl = opts.fetchImpl ?? ((...a) => fetch(...a));
|
|
6667
6739
|
}
|
|
@@ -6716,6 +6788,7 @@ var ServerSession = class {
|
|
|
6716
6788
|
this.generation += 1;
|
|
6717
6789
|
this.cached = null;
|
|
6718
6790
|
this.inFlight = null;
|
|
6791
|
+
this.promptBackoff = null;
|
|
6719
6792
|
if (!this.store)
|
|
6720
6793
|
return;
|
|
6721
6794
|
try {
|
|
@@ -6812,19 +6885,53 @@ var ServerSession = class {
|
|
|
6812
6885
|
this.inFlight = (async () => {
|
|
6813
6886
|
try {
|
|
6814
6887
|
const existing = await this.tryResolveSilently(gen);
|
|
6815
|
-
if (existing)
|
|
6888
|
+
if (existing) {
|
|
6889
|
+
this.promptBackoff = null;
|
|
6816
6890
|
return existing;
|
|
6891
|
+
}
|
|
6892
|
+
if (this.promptBackoff && this.now() * 1e3 < this.promptBackoff.until) {
|
|
6893
|
+
throw this.promptBackoff.error;
|
|
6894
|
+
}
|
|
6817
6895
|
this.onSignaturePrompt?.();
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
this.
|
|
6821
|
-
this.
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6896
|
+
let session;
|
|
6897
|
+
try {
|
|
6898
|
+
const store = this.store;
|
|
6899
|
+
const storeKey = store ? this.lastStoreKey : null;
|
|
6900
|
+
const reuse = storeKey ? store.loadPending?.(storeKey) ?? void 0 : void 0;
|
|
6901
|
+
const payload = await buildSignedLoginPayload(
|
|
6902
|
+
this.signer,
|
|
6903
|
+
this.chainId,
|
|
6904
|
+
this.serviceEndpoint,
|
|
6905
|
+
reuse
|
|
6906
|
+
);
|
|
6907
|
+
if (storeKey && !reuse) {
|
|
6908
|
+
store.savePending?.(storeKey, {
|
|
6909
|
+
issuedAt: payload.message.issuedAt,
|
|
6910
|
+
nonce: payload.message.nonce
|
|
6911
|
+
});
|
|
6912
|
+
}
|
|
6913
|
+
session = await this.login(payload);
|
|
6914
|
+
if (notCleared()) {
|
|
6915
|
+
this.persist(payload, session);
|
|
6916
|
+
this.cached = session;
|
|
6917
|
+
}
|
|
6918
|
+
if (storeKey)
|
|
6919
|
+
store.clearPending?.(storeKey);
|
|
6920
|
+
this.onSignatureResolved?.(true);
|
|
6921
|
+
} catch (error) {
|
|
6922
|
+
this.onSignatureResolved?.(false);
|
|
6923
|
+
const failures = (this.promptBackoff?.failures ?? 0) + 1;
|
|
6924
|
+
this.promptBackoff = {
|
|
6925
|
+
failures,
|
|
6926
|
+
error,
|
|
6927
|
+
until: this.now() * 1e3 + Math.min(
|
|
6928
|
+
LOGIN_RETRY_BASE_MS * 2 ** (failures - 1),
|
|
6929
|
+
LOGIN_RETRY_MAX_MS
|
|
6930
|
+
)
|
|
6931
|
+
};
|
|
6932
|
+
throw error;
|
|
6827
6933
|
}
|
|
6934
|
+
this.promptBackoff = null;
|
|
6828
6935
|
return session;
|
|
6829
6936
|
} finally {
|
|
6830
6937
|
if (notCleared())
|
|
@@ -16552,7 +16659,8 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
|
|
|
16552
16659
|
chainId: config.chainId,
|
|
16553
16660
|
persistSession: config.sessionPersistence?.enabled,
|
|
16554
16661
|
sessionStore: config.sessionPersistence?.store,
|
|
16555
|
-
onSignaturePrompt: config.onSessionSignaturePrompt
|
|
16662
|
+
onSignaturePrompt: config.onSessionSignaturePrompt,
|
|
16663
|
+
onSignatureResolved: config.onSessionSignatureResolved
|
|
16556
16664
|
});
|
|
16557
16665
|
}
|
|
16558
16666
|
const contractManagerResult = createContractManager({
|
package/dist/index.mjs
CHANGED
|
@@ -6426,7 +6426,7 @@ async function generateExtendAuthorization(positionId, newTerm, chainId, signerO
|
|
|
6426
6426
|
import { hexlify, randomBytes } from "ethers";
|
|
6427
6427
|
function buildLoginDomain(chainId) {
|
|
6428
6428
|
return {
|
|
6429
|
-
name: "Diamond Hands
|
|
6429
|
+
name: "Diamond Hands",
|
|
6430
6430
|
version: "1",
|
|
6431
6431
|
chainId
|
|
6432
6432
|
};
|
|
@@ -6446,12 +6446,12 @@ var LOGIN_TYPES_WITH_AUDIENCE = {
|
|
|
6446
6446
|
{ name: "audience", type: "string" }
|
|
6447
6447
|
]
|
|
6448
6448
|
};
|
|
6449
|
-
async function buildSignedLoginPayload(signer, chainId, audience) {
|
|
6449
|
+
async function buildSignedLoginPayload(signer, chainId, audience, reuse) {
|
|
6450
6450
|
const address = await signer.getAddress();
|
|
6451
6451
|
const message = {
|
|
6452
6452
|
address,
|
|
6453
|
-
issuedAt: Math.floor(Date.now() / 1e3),
|
|
6454
|
-
nonce: hexlify(randomBytes(32)),
|
|
6453
|
+
issuedAt: reuse?.issuedAt ?? Math.floor(Date.now() / 1e3),
|
|
6454
|
+
nonce: reuse?.nonce ?? hexlify(randomBytes(32)),
|
|
6455
6455
|
...audience ? { audience } : {}
|
|
6456
6456
|
};
|
|
6457
6457
|
const signature = await signer.signTypedData(
|
|
@@ -6469,6 +6469,13 @@ var STORE_PREFIX = "dh-server-session:";
|
|
|
6469
6469
|
function sessionStoreKey(address, chainId, endpoint) {
|
|
6470
6470
|
return STORE_PREFIX + `${address.trim().toLowerCase()}:${chainId}:${endpoint.replace(/\/+$/, "")}`;
|
|
6471
6471
|
}
|
|
6472
|
+
var PENDING_ENVELOPE_TTL_SECONDS = 8 * 60;
|
|
6473
|
+
function pendingEnvelopeKey(sessionKey) {
|
|
6474
|
+
return sessionKey + ":pending";
|
|
6475
|
+
}
|
|
6476
|
+
function isPendingEnvelopeShape(value) {
|
|
6477
|
+
return !!value && typeof value === "object" && typeof value.issuedAt === "number" && typeof value.nonce === "string";
|
|
6478
|
+
}
|
|
6472
6479
|
function isPayloadShape(value) {
|
|
6473
6480
|
return !!value && typeof value === "object" && typeof value.chainId === "number" && typeof value.signature === "string" && !!value.message && typeof value.message.address === "string" && typeof value.message.issuedAt === "number" && typeof value.message.nonce === "string";
|
|
6474
6481
|
}
|
|
@@ -6510,9 +6517,48 @@ var LocalStorageSessionStore = class {
|
|
|
6510
6517
|
} catch {
|
|
6511
6518
|
}
|
|
6512
6519
|
}
|
|
6520
|
+
loadPending(key) {
|
|
6521
|
+
let raw;
|
|
6522
|
+
try {
|
|
6523
|
+
raw = window.localStorage.getItem(pendingEnvelopeKey(key));
|
|
6524
|
+
} catch {
|
|
6525
|
+
return null;
|
|
6526
|
+
}
|
|
6527
|
+
if (!raw)
|
|
6528
|
+
return null;
|
|
6529
|
+
let parsed;
|
|
6530
|
+
try {
|
|
6531
|
+
parsed = JSON.parse(raw);
|
|
6532
|
+
} catch {
|
|
6533
|
+
this.clearPending(key);
|
|
6534
|
+
return null;
|
|
6535
|
+
}
|
|
6536
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
6537
|
+
if (!isPendingEnvelopeShape(parsed) || nowSec >= parsed.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
|
|
6538
|
+
this.clearPending(key);
|
|
6539
|
+
return null;
|
|
6540
|
+
}
|
|
6541
|
+
return parsed;
|
|
6542
|
+
}
|
|
6543
|
+
savePending(key, envelope) {
|
|
6544
|
+
try {
|
|
6545
|
+
window.localStorage.setItem(
|
|
6546
|
+
pendingEnvelopeKey(key),
|
|
6547
|
+
JSON.stringify(envelope)
|
|
6548
|
+
);
|
|
6549
|
+
} catch {
|
|
6550
|
+
}
|
|
6551
|
+
}
|
|
6552
|
+
clearPending(key) {
|
|
6553
|
+
try {
|
|
6554
|
+
window.localStorage.removeItem(pendingEnvelopeKey(key));
|
|
6555
|
+
} catch {
|
|
6556
|
+
}
|
|
6557
|
+
}
|
|
6513
6558
|
};
|
|
6514
6559
|
var MemorySessionStore = class {
|
|
6515
6560
|
entries = /* @__PURE__ */ new Map();
|
|
6561
|
+
pending = /* @__PURE__ */ new Map();
|
|
6516
6562
|
load(key) {
|
|
6517
6563
|
return this.entries.get(key) ?? null;
|
|
6518
6564
|
}
|
|
@@ -6522,6 +6568,23 @@ var MemorySessionStore = class {
|
|
|
6522
6568
|
clear(key) {
|
|
6523
6569
|
this.entries.delete(key);
|
|
6524
6570
|
}
|
|
6571
|
+
loadPending(key) {
|
|
6572
|
+
const envelope = this.pending.get(key);
|
|
6573
|
+
if (!envelope)
|
|
6574
|
+
return null;
|
|
6575
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
6576
|
+
if (nowSec >= envelope.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
|
|
6577
|
+
this.pending.delete(key);
|
|
6578
|
+
return null;
|
|
6579
|
+
}
|
|
6580
|
+
return envelope;
|
|
6581
|
+
}
|
|
6582
|
+
savePending(key, envelope) {
|
|
6583
|
+
this.pending.set(key, envelope);
|
|
6584
|
+
}
|
|
6585
|
+
clearPending(key) {
|
|
6586
|
+
this.pending.delete(key);
|
|
6587
|
+
}
|
|
6525
6588
|
};
|
|
6526
6589
|
function localStorageUsable() {
|
|
6527
6590
|
try {
|
|
@@ -6541,6 +6604,8 @@ function createDefaultSessionStore() {
|
|
|
6541
6604
|
|
|
6542
6605
|
// src/utils/server-session.ts
|
|
6543
6606
|
var REFRESH_LEEWAY_SECONDS = 30;
|
|
6607
|
+
var LOGIN_RETRY_BASE_MS = 3e4;
|
|
6608
|
+
var LOGIN_RETRY_MAX_MS = 5 * 6e4;
|
|
6544
6609
|
var ServerLoginError = class extends Error {
|
|
6545
6610
|
constructor(message, status) {
|
|
6546
6611
|
super(message);
|
|
@@ -6557,10 +6622,16 @@ var ServerSession = class {
|
|
|
6557
6622
|
chainId;
|
|
6558
6623
|
store;
|
|
6559
6624
|
onSignaturePrompt;
|
|
6625
|
+
onSignatureResolved;
|
|
6560
6626
|
now;
|
|
6561
6627
|
fetchImpl;
|
|
6562
6628
|
cached = null;
|
|
6563
6629
|
inFlight = null;
|
|
6630
|
+
/**
|
|
6631
|
+
* Set when a prompted login failed; suppresses further prompts until `until`.
|
|
6632
|
+
* `error` is replayed to callers so they still see the real failure reason.
|
|
6633
|
+
*/
|
|
6634
|
+
promptBackoff = null;
|
|
6564
6635
|
/**
|
|
6565
6636
|
* Bumped by `clearPersisted()` (logout / disconnect). An in-flight
|
|
6566
6637
|
* `getOrRefresh()` captures this at start and refuses to write its freshly
|
|
@@ -6581,6 +6652,7 @@ var ServerSession = class {
|
|
|
6581
6652
|
this.chainId = opts.chainId;
|
|
6582
6653
|
this.store = opts.persistSession === false ? null : opts.sessionStore ?? createDefaultSessionStore();
|
|
6583
6654
|
this.onSignaturePrompt = opts.onSignaturePrompt;
|
|
6655
|
+
this.onSignatureResolved = opts.onSignatureResolved;
|
|
6584
6656
|
this.now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
6585
6657
|
this.fetchImpl = opts.fetchImpl ?? ((...a) => fetch(...a));
|
|
6586
6658
|
}
|
|
@@ -6635,6 +6707,7 @@ var ServerSession = class {
|
|
|
6635
6707
|
this.generation += 1;
|
|
6636
6708
|
this.cached = null;
|
|
6637
6709
|
this.inFlight = null;
|
|
6710
|
+
this.promptBackoff = null;
|
|
6638
6711
|
if (!this.store)
|
|
6639
6712
|
return;
|
|
6640
6713
|
try {
|
|
@@ -6731,19 +6804,53 @@ var ServerSession = class {
|
|
|
6731
6804
|
this.inFlight = (async () => {
|
|
6732
6805
|
try {
|
|
6733
6806
|
const existing = await this.tryResolveSilently(gen);
|
|
6734
|
-
if (existing)
|
|
6807
|
+
if (existing) {
|
|
6808
|
+
this.promptBackoff = null;
|
|
6735
6809
|
return existing;
|
|
6810
|
+
}
|
|
6811
|
+
if (this.promptBackoff && this.now() * 1e3 < this.promptBackoff.until) {
|
|
6812
|
+
throw this.promptBackoff.error;
|
|
6813
|
+
}
|
|
6736
6814
|
this.onSignaturePrompt?.();
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
this.
|
|
6740
|
-
this.
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6815
|
+
let session;
|
|
6816
|
+
try {
|
|
6817
|
+
const store = this.store;
|
|
6818
|
+
const storeKey = store ? this.lastStoreKey : null;
|
|
6819
|
+
const reuse = storeKey ? store.loadPending?.(storeKey) ?? void 0 : void 0;
|
|
6820
|
+
const payload = await buildSignedLoginPayload(
|
|
6821
|
+
this.signer,
|
|
6822
|
+
this.chainId,
|
|
6823
|
+
this.serviceEndpoint,
|
|
6824
|
+
reuse
|
|
6825
|
+
);
|
|
6826
|
+
if (storeKey && !reuse) {
|
|
6827
|
+
store.savePending?.(storeKey, {
|
|
6828
|
+
issuedAt: payload.message.issuedAt,
|
|
6829
|
+
nonce: payload.message.nonce
|
|
6830
|
+
});
|
|
6831
|
+
}
|
|
6832
|
+
session = await this.login(payload);
|
|
6833
|
+
if (notCleared()) {
|
|
6834
|
+
this.persist(payload, session);
|
|
6835
|
+
this.cached = session;
|
|
6836
|
+
}
|
|
6837
|
+
if (storeKey)
|
|
6838
|
+
store.clearPending?.(storeKey);
|
|
6839
|
+
this.onSignatureResolved?.(true);
|
|
6840
|
+
} catch (error) {
|
|
6841
|
+
this.onSignatureResolved?.(false);
|
|
6842
|
+
const failures = (this.promptBackoff?.failures ?? 0) + 1;
|
|
6843
|
+
this.promptBackoff = {
|
|
6844
|
+
failures,
|
|
6845
|
+
error,
|
|
6846
|
+
until: this.now() * 1e3 + Math.min(
|
|
6847
|
+
LOGIN_RETRY_BASE_MS * 2 ** (failures - 1),
|
|
6848
|
+
LOGIN_RETRY_MAX_MS
|
|
6849
|
+
)
|
|
6850
|
+
};
|
|
6851
|
+
throw error;
|
|
6746
6852
|
}
|
|
6853
|
+
this.promptBackoff = null;
|
|
6747
6854
|
return session;
|
|
6748
6855
|
} finally {
|
|
6749
6856
|
if (notCleared())
|
|
@@ -16481,7 +16588,8 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
|
|
|
16481
16588
|
chainId: config.chainId,
|
|
16482
16589
|
persistSession: config.sessionPersistence?.enabled,
|
|
16483
16590
|
sessionStore: config.sessionPersistence?.store,
|
|
16484
|
-
onSignaturePrompt: config.onSessionSignaturePrompt
|
|
16591
|
+
onSignaturePrompt: config.onSessionSignaturePrompt,
|
|
16592
|
+
onSignatureResolved: config.onSessionSignatureResolved
|
|
16485
16593
|
});
|
|
16486
16594
|
}
|
|
16487
16595
|
const contractManagerResult = createContractManager({
|
|
@@ -77,6 +77,14 @@ interface BaseSDKConfig {
|
|
|
77
77
|
* "check your wallet" prompt.
|
|
78
78
|
*/
|
|
79
79
|
onSessionSignaturePrompt?: () => void;
|
|
80
|
+
/**
|
|
81
|
+
* Fires once that signature request has concluded — `true` when a session was
|
|
82
|
+
* established, `false` when it failed or was rejected. Pairs with
|
|
83
|
+
* `onSessionSignaturePrompt`, which otherwise has no ending: a UI that shows
|
|
84
|
+
* "check your wallet" on the prompt has no way to stop showing it, and a
|
|
85
|
+
* contract wallet can leave that request open for minutes.
|
|
86
|
+
*/
|
|
87
|
+
onSessionSignatureResolved?: (ok: boolean) => void;
|
|
80
88
|
ethRpcUrl?: string;
|
|
81
89
|
chainId?: number;
|
|
82
90
|
networkOverride?: {
|
|
@@ -31,4 +31,16 @@ export declare const LOGIN_TYPES_WITH_AUDIENCE: Record<string, TypedDataField[]>
|
|
|
31
31
|
* Audit M-8: pass `audience` (the service endpoint URL being logged into) to bind the login to
|
|
32
32
|
* one service. Omit it for legacy compatibility during the migration window.
|
|
33
33
|
*/
|
|
34
|
-
export declare function buildSignedLoginPayload(signer: Signer, chainId: number, audience?: string
|
|
34
|
+
export declare function buildSignedLoginPayload(signer: Signer, chainId: number, audience?: string,
|
|
35
|
+
/**
|
|
36
|
+
* Reuse the envelope of a login already collecting signatures instead of
|
|
37
|
+
* minting a fresh one. Every field is hashed into the EIP-712 message, so a
|
|
38
|
+
* new nonce produces a new hash — which a multi-owner Safe shows as a new
|
|
39
|
+
* message needing its FIRST signature, discarding the ones already collected.
|
|
40
|
+
* Passing the pending envelope back reproduces the same hash, so the next
|
|
41
|
+
* owner adds to it. See `server-session-store.ts`.
|
|
42
|
+
*/
|
|
43
|
+
reuse?: {
|
|
44
|
+
issuedAt: number;
|
|
45
|
+
nonce: string;
|
|
46
|
+
}): Promise<DhServerLoginPayload>;
|
|
@@ -39,18 +39,62 @@ export interface ServerSessionStore {
|
|
|
39
39
|
load(key: string): PersistedServerSession | null;
|
|
40
40
|
save(key: string, session: PersistedServerSession): void;
|
|
41
41
|
clear(key: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Optional pending-envelope persistence (see `PendingLoginEnvelope`). Optional
|
|
44
|
+
* so a custom store written before this existed still satisfies the interface;
|
|
45
|
+
* without it the session simply mints a fresh envelope each attempt, which is
|
|
46
|
+
* the pre-existing behavior and only costs a multi-owner Safe a restart.
|
|
47
|
+
*/
|
|
48
|
+
loadPending?(key: string): PendingLoginEnvelope | null;
|
|
49
|
+
savePending?(key: string, envelope: PendingLoginEnvelope): void;
|
|
50
|
+
clearPending?(key: string): void;
|
|
42
51
|
}
|
|
43
52
|
export declare function sessionStoreKey(address: string, chainId: number, endpoint: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* The UNSIGNED envelope of a login already collecting signatures.
|
|
55
|
+
*
|
|
56
|
+
* A multi-owner Safe signs across sessions — owner 1 now, owner 2 later — and
|
|
57
|
+
* identifies the pending message solely by its EIP-712 hash. Every field is
|
|
58
|
+
* hashed, so minting a fresh `nonce`/`issuedAt` for the next attempt yields a
|
|
59
|
+
* DIFFERENT hash, which Safe presents as a new message needing its FIRST
|
|
60
|
+
* signature. Whatever owner 1 signed is orphaned, and a 2-of-N Safe can never
|
|
61
|
+
* reach threshold: every retry resets the count to zero.
|
|
62
|
+
*
|
|
63
|
+
* Reusing the envelope makes the re-request hash identically, so Safe shows the
|
|
64
|
+
* same pending message and the next owner ADDS to it. Invisible for an EOA,
|
|
65
|
+
* which signs immediately and spends the envelope.
|
|
66
|
+
*/
|
|
67
|
+
export interface PendingLoginEnvelope {
|
|
68
|
+
issuedAt: number;
|
|
69
|
+
nonce: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* How long an unsigned envelope stays reusable. Must stay UNDER the server's
|
|
73
|
+
* contract-wallet first-use skew (`CONTRACT_ISSUED_AT_SKEW_SECONDS`, 10 min):
|
|
74
|
+
* beyond it the first use is rejected as stale, so reuse would guarantee failure
|
|
75
|
+
* instead of preventing a re-sign.
|
|
76
|
+
*/
|
|
77
|
+
export declare const PENDING_ENVELOPE_TTL_SECONDS: number;
|
|
78
|
+
/** Storage key for the pending envelope matching a session key. */
|
|
79
|
+
export declare function pendingEnvelopeKey(sessionKey: string): string;
|
|
80
|
+
export declare function isPendingEnvelopeShape(value: any): value is PendingLoginEnvelope;
|
|
44
81
|
export declare class LocalStorageSessionStore implements ServerSessionStore {
|
|
45
82
|
load(key: string): PersistedServerSession | null;
|
|
46
83
|
save(key: string, session: PersistedServerSession): void;
|
|
47
84
|
clear(key: string): void;
|
|
85
|
+
loadPending(key: string): PendingLoginEnvelope | null;
|
|
86
|
+
savePending(key: string, envelope: PendingLoginEnvelope): void;
|
|
87
|
+
clearPending(key: string): void;
|
|
48
88
|
}
|
|
49
89
|
export declare class MemorySessionStore implements ServerSessionStore {
|
|
50
90
|
private readonly entries;
|
|
91
|
+
private readonly pending;
|
|
51
92
|
load(key: string): PersistedServerSession | null;
|
|
52
93
|
save(key: string, session: PersistedServerSession): void;
|
|
53
94
|
clear(key: string): void;
|
|
95
|
+
loadPending(key: string): PendingLoginEnvelope | null;
|
|
96
|
+
savePending(key: string, envelope: PendingLoginEnvelope): void;
|
|
97
|
+
clearPending(key: string): void;
|
|
54
98
|
}
|
|
55
99
|
/** `localStorage` when usable (browser, not blocked), else per-instance memory. */
|
|
56
100
|
export declare function createDefaultSessionStore(): ServerSessionStore;
|
|
@@ -40,6 +40,12 @@ export interface ServerSessionOptions {
|
|
|
40
40
|
* never on the silent paths. Lets UIs show a "check your wallet" prompt.
|
|
41
41
|
*/
|
|
42
42
|
onSignaturePrompt?: () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Fires once the prompted signature has concluded, with whether a session was
|
|
45
|
+
* established. Without it `onSignaturePrompt` has no ending, so a UI showing
|
|
46
|
+
* "check your wallet" can never take it down.
|
|
47
|
+
*/
|
|
48
|
+
onSignatureResolved?: (ok: boolean) => void;
|
|
43
49
|
/** Override for tests. */
|
|
44
50
|
now?: () => number;
|
|
45
51
|
/** Override for tests. */
|
|
@@ -56,10 +62,16 @@ export declare class ServerSession {
|
|
|
56
62
|
private readonly chainId;
|
|
57
63
|
private readonly store;
|
|
58
64
|
private readonly onSignaturePrompt?;
|
|
65
|
+
private readonly onSignatureResolved?;
|
|
59
66
|
private readonly now;
|
|
60
67
|
private readonly fetchImpl;
|
|
61
68
|
private cached;
|
|
62
69
|
private inFlight;
|
|
70
|
+
/**
|
|
71
|
+
* Set when a prompted login failed; suppresses further prompts until `until`.
|
|
72
|
+
* `error` is replayed to callers so they still see the real failure reason.
|
|
73
|
+
*/
|
|
74
|
+
private promptBackoff;
|
|
63
75
|
/**
|
|
64
76
|
* Bumped by `clearPersisted()` (logout / disconnect). An in-flight
|
|
65
77
|
* `getOrRefresh()` captures this at start and refuses to write its freshly
|
package/package.json
CHANGED