Skip to content

Commit e619e1e

Browse files
authored
Merge pull request #7 from Outblock/feat/erc4337
fix: persist SDK connection across dapp refresh
2 parents b116b6b + 4114b2c commit e619e1e

1 file changed

Lines changed: 79 additions & 19 deletions

File tree

packages/sdk/src/provider.ts

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ interface PendingRequest {
2121
reject: (error: any) => void
2222
}
2323

24+
interface PersistedSession {
25+
connectedAddress: string
26+
chainId: number | null
27+
}
28+
2429
const SIGNING_METHODS = new Set([
2530
"eth_sendTransaction",
2631
"eth_signTransaction",
@@ -38,19 +43,44 @@ export function createFlowDevWalletProvider(config: FlowDevWalletConfig = {}) {
3843
} = config
3944

4045
let popup: Window | null = null
41-
let connectedAddress: string | null = null
42-
let chainId: number | null = null
46+
const storageKey = getSessionStorageKey(walletUrl)
47+
const initialSession = readPersistedSession(storageKey)
48+
let connectedAddress: string | null = initialSession?.connectedAddress ?? null
49+
let chainId: number | null = initialSession?.chainId ?? null
4350
let popupReady = false
4451
let requestId = 0
4552
const pending = new Map<number, PendingRequest>()
4653
const listeners = new Map<EventName, Set<Handler>>()
54+
let connectWaiters: Array<{ resolve: (accounts: string[]) => void; reject: (error: Error) => void }> = []
4755

4856
function emit(event: EventName, ...args: any[]) {
4957
listeners.get(event)?.forEach((fn) => fn(...args))
5058
}
5159

5260
let readyResolvers: Array<() => void> = []
5361

62+
function persistSession() {
63+
if (typeof window === "undefined") return
64+
if (!connectedAddress) {
65+
window.localStorage.removeItem(storageKey)
66+
return
67+
}
68+
const session: PersistedSession = { connectedAddress, chainId }
69+
window.localStorage.setItem(storageKey, JSON.stringify(session))
70+
}
71+
72+
function resolveConnectWaiters(accounts: string[]) {
73+
const waiters = connectWaiters
74+
connectWaiters = []
75+
waiters.forEach(({ resolve }) => resolve(accounts))
76+
}
77+
78+
function rejectConnectWaiters(error: Error) {
79+
const waiters = connectWaiters
80+
connectWaiters = []
81+
waiters.forEach(({ reject }) => reject(error))
82+
}
83+
5484
function onMessage(event: MessageEvent) {
5585
const { data } = event
5686
if (!data?.type?.startsWith("flowindex_")) return
@@ -60,18 +90,22 @@ export function createFlowDevWalletProvider(config: FlowDevWalletConfig = {}) {
6090
if (data.address) {
6191
connectedAddress = data.address
6292
chainId = data.chainId
93+
persistSession()
6394
}
6495
const resolvers = readyResolvers
6596
readyResolvers = []
6697
resolvers.forEach((r) => r())
6798
}
6899

69100
if (data.type === "flowindex_connected") {
70-
connectedAddress = data.address
101+
const address = data.address as string
102+
connectedAddress = address
71103
chainId = data.chainId
72104
popupReady = true
105+
persistSession()
73106
emit("connect", { chainId: `0x${chainId!.toString(16)}` })
74-
emit("accountsChanged", [connectedAddress])
107+
emit("accountsChanged", [address])
108+
resolveConnectWaiters([address])
75109
const resolvers = readyResolvers
76110
readyResolvers = []
77111
resolvers.forEach((r) => r())
@@ -81,6 +115,8 @@ export function createFlowDevWalletProvider(config: FlowDevWalletConfig = {}) {
81115
connectedAddress = null
82116
chainId = null
83117
popupReady = false
118+
persistSession()
119+
rejectConnectWaiters(new Error("User rejected connection"))
84120
emit("disconnect", { code: 4900, message: "Disconnected" })
85121
emit("accountsChanged", [])
86122
}
@@ -144,26 +180,25 @@ export function createFlowDevWalletProvider(config: FlowDevWalletConfig = {}) {
144180
if (method === "eth_requestAccounts") {
145181
if (connectedAddress) return [connectedAddress]
146182

147-
openPopup()
148-
149183
return new Promise<string[]>((resolve, reject) => {
184+
const waiter = {
185+
resolve: (accounts: string[]) => {
186+
clearTimeout(timeout)
187+
resolve(accounts)
188+
},
189+
reject: (error: Error) => {
190+
clearTimeout(timeout)
191+
reject(error)
192+
},
193+
}
150194
const timeout = setTimeout(() => {
195+
connectWaiters = connectWaiters.filter((candidate) => candidate !== waiter)
151196
reject(new Error("Connection timed out"))
152197
}, 120_000)
153198

154-
const handler = (event: MessageEvent) => {
155-
if (event.data?.type === "flowindex_connected") {
156-
clearTimeout(timeout)
157-
window.removeEventListener("message", handler)
158-
resolve([event.data.address])
159-
}
160-
if (event.data?.type === "flowindex_disconnected") {
161-
clearTimeout(timeout)
162-
window.removeEventListener("message", handler)
163-
reject(new Error("User rejected connection"))
164-
}
165-
}
166-
window.addEventListener("message", handler)
199+
connectWaiters.push(waiter)
200+
201+
openPopup()
167202
})
168203
}
169204

@@ -205,6 +240,7 @@ export function createFlowDevWalletProvider(config: FlowDevWalletConfig = {}) {
205240
connectedAddress = null
206241
chainId = null
207242
popupReady = false
243+
persistSession()
208244
if (popup && !popup.closed) popup.close()
209245
popup = null
210246
emit("disconnect", { code: 4900, message: "Disconnected" })
@@ -216,3 +252,27 @@ export function createFlowDevWalletProvider(config: FlowDevWalletConfig = {}) {
216252
}
217253

218254
export type FlowDevWalletProvider = ReturnType<typeof createFlowDevWalletProvider>
255+
256+
function getSessionStorageKey(walletUrl: string): string {
257+
const normalizedWalletUrl = typeof window !== "undefined"
258+
? new URL(walletUrl, window.location.href).toString()
259+
: walletUrl
260+
return `flow-dev-wallet:session:${normalizedWalletUrl}`
261+
}
262+
263+
function readPersistedSession(storageKey: string): PersistedSession | null {
264+
if (typeof window === "undefined") return null
265+
266+
try {
267+
const raw = window.localStorage.getItem(storageKey)
268+
if (!raw) return null
269+
const parsed = JSON.parse(raw) as PersistedSession
270+
if (!parsed?.connectedAddress) return null
271+
return {
272+
connectedAddress: parsed.connectedAddress,
273+
chainId: typeof parsed.chainId === "number" ? parsed.chainId : null,
274+
}
275+
} catch {
276+
return null
277+
}
278+
}

0 commit comments

Comments
 (0)