@0xsequence/dapp-client 0.0.0-20250910142613
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/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE +202 -0
- package/README.md +238 -0
- package/dist/ChainSessionManager.d.ts +203 -0
- package/dist/ChainSessionManager.d.ts.map +1 -0
- package/dist/ChainSessionManager.js +742 -0
- package/dist/DappClient.d.ts +409 -0
- package/dist/DappClient.d.ts.map +1 -0
- package/dist/DappClient.js +667 -0
- package/dist/DappTransport.d.ts +47 -0
- package/dist/DappTransport.d.ts.map +1 -0
- package/dist/DappTransport.js +443 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/types/index.d.ts +168 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +25 -0
- package/dist/utils/constants.d.ts +6 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +5 -0
- package/dist/utils/errors.d.ts +28 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +54 -0
- package/dist/utils/index.d.ts +11 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +135 -0
- package/dist/utils/storage.d.ts +64 -0
- package/dist/utils/storage.d.ts.map +1 -0
- package/dist/utils/storage.js +196 -0
- package/eslint.config.mjs +4 -0
- package/package.json +38 -0
- package/src/ChainSessionManager.ts +978 -0
- package/src/DappClient.ts +801 -0
- package/src/DappTransport.ts +518 -0
- package/src/index.ts +47 -0
- package/src/types/index.ts +218 -0
- package/src/utils/constants.ts +5 -0
- package/src/utils/errors.ts +62 -0
- package/src/utils/index.ts +158 -0
- package/src/utils/storage.ts +272 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
import { jsonReplacers, jsonRevivers } from './utils/index.js'
|
|
4
|
+
import {
|
|
5
|
+
MessageType,
|
|
6
|
+
PendingRequest,
|
|
7
|
+
PopupModeOptions,
|
|
8
|
+
SendRequestOptions,
|
|
9
|
+
SequenceSessionStorage,
|
|
10
|
+
TransportMessage,
|
|
11
|
+
TransportMode,
|
|
12
|
+
WalletSize,
|
|
13
|
+
} from './types/index.js'
|
|
14
|
+
|
|
15
|
+
enum ConnectionState {
|
|
16
|
+
DISCONNECTED = 'DISCONNECTED',
|
|
17
|
+
CONNECTING = 'CONNECTING',
|
|
18
|
+
CONNECTED = 'CONNECTED',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const REDIRECT_REQUEST_KEY = 'dapp-redirect-request'
|
|
22
|
+
|
|
23
|
+
export class DappTransport {
|
|
24
|
+
private walletWindow: Window | undefined = undefined
|
|
25
|
+
private connectionState: ConnectionState = ConnectionState.DISCONNECTED
|
|
26
|
+
private readyPromise: Promise<void> | undefined = undefined
|
|
27
|
+
private readyPromiseResolve: (() => void) | undefined = undefined
|
|
28
|
+
private readyPromiseReject: ((reason?: any) => void) | undefined = undefined
|
|
29
|
+
private initId: string | undefined = undefined
|
|
30
|
+
private handshakeTimeoutId: number | undefined = undefined
|
|
31
|
+
private closeCheckIntervalId: number | undefined = undefined
|
|
32
|
+
private sessionId: string | undefined = undefined
|
|
33
|
+
private pendingRequests = new Map<string, PendingRequest>()
|
|
34
|
+
private messageQueue: TransportMessage[] = []
|
|
35
|
+
private readonly requestTimeoutMs: number
|
|
36
|
+
private readonly handshakeTimeoutMs: number
|
|
37
|
+
private readonly sequenceSessionStorage: SequenceSessionStorage
|
|
38
|
+
private readonly redirectActionHandler?: (url: string) => void
|
|
39
|
+
|
|
40
|
+
public readonly walletOrigin: string
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
public readonly walletUrl: string,
|
|
44
|
+
readonly mode: TransportMode = TransportMode.POPUP,
|
|
45
|
+
popupModeOptions: PopupModeOptions = {},
|
|
46
|
+
sequenceSessionStorage?: SequenceSessionStorage,
|
|
47
|
+
redirectActionHandler?: (url: string) => void,
|
|
48
|
+
) {
|
|
49
|
+
try {
|
|
50
|
+
this.walletOrigin = new URL(walletUrl).origin
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error('[DApp] Invalid walletUrl provided:', walletUrl, e)
|
|
53
|
+
throw new Error(`Invalid walletUrl: ${walletUrl}`)
|
|
54
|
+
}
|
|
55
|
+
if (!this.walletOrigin || this.walletOrigin === 'null' || this.walletOrigin === '*') {
|
|
56
|
+
console.error('[DApp] Could not determine a valid wallet origin from the URL:', walletUrl)
|
|
57
|
+
throw new Error('Invalid wallet origin derived from walletUrl.')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (sequenceSessionStorage) {
|
|
61
|
+
this.sequenceSessionStorage = sequenceSessionStorage
|
|
62
|
+
} else if (typeof window !== 'undefined' && window.sessionStorage) {
|
|
63
|
+
this.sequenceSessionStorage = window.sessionStorage
|
|
64
|
+
} else {
|
|
65
|
+
throw new Error('A storage implementation must be provided for non-browser environments.')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.requestTimeoutMs = popupModeOptions.requestTimeoutMs ?? 300000
|
|
69
|
+
this.handshakeTimeoutMs = popupModeOptions.handshakeTimeoutMs ?? 15000
|
|
70
|
+
|
|
71
|
+
if (this.mode === TransportMode.POPUP) {
|
|
72
|
+
window.addEventListener('message', this.handleMessage)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.redirectActionHandler = redirectActionHandler
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get isWalletOpen(): boolean {
|
|
79
|
+
if (this.mode === TransportMode.REDIRECT) return false
|
|
80
|
+
return !!this.walletWindow && !this.walletWindow.closed
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get isReady(): boolean {
|
|
84
|
+
if (this.mode === TransportMode.REDIRECT) return false
|
|
85
|
+
return this.connectionState === ConnectionState.CONNECTED
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async sendRequest<TResponse = any, TRequest = any>(
|
|
89
|
+
action: string,
|
|
90
|
+
redirectUrl: string,
|
|
91
|
+
payload?: TRequest,
|
|
92
|
+
options: SendRequestOptions = {},
|
|
93
|
+
): Promise<TResponse> {
|
|
94
|
+
if (this.mode === TransportMode.REDIRECT) {
|
|
95
|
+
const url = await this.getRequestRedirectUrl(action, payload, redirectUrl, options.path)
|
|
96
|
+
if (this.redirectActionHandler) {
|
|
97
|
+
this.redirectActionHandler(url)
|
|
98
|
+
} else {
|
|
99
|
+
console.info('[DappTransport] No redirectActionHandler provided. Using window.location.href to navigate.')
|
|
100
|
+
window.location.href = url
|
|
101
|
+
}
|
|
102
|
+
return new Promise<TResponse>(() => {})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (this.connectionState !== ConnectionState.CONNECTED) {
|
|
106
|
+
await this.openWallet(options.path)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!this.isWalletOpen || this.connectionState !== ConnectionState.CONNECTED) {
|
|
110
|
+
throw new Error('Wallet connection is not available or failed to establish.')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const id = this.generateId()
|
|
114
|
+
const message: TransportMessage<TRequest> = {
|
|
115
|
+
id,
|
|
116
|
+
type: MessageType.REQUEST,
|
|
117
|
+
action,
|
|
118
|
+
payload,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
const timeout = options.timeout ?? this.requestTimeoutMs
|
|
123
|
+
const timer = window.setTimeout(() => {
|
|
124
|
+
if (this.pendingRequests.has(id)) {
|
|
125
|
+
this.pendingRequests.delete(id)
|
|
126
|
+
reject(new Error(`Request '${action}' (ID: ${id}) timed out after ${timeout}ms.`))
|
|
127
|
+
}
|
|
128
|
+
}, timeout)
|
|
129
|
+
|
|
130
|
+
this.pendingRequests.set(id, { resolve, reject, timer, action })
|
|
131
|
+
this.postMessageToWallet(message)
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public async getRequestRedirectUrl(
|
|
136
|
+
action: string,
|
|
137
|
+
payload: any,
|
|
138
|
+
redirectUrl: string,
|
|
139
|
+
path?: string,
|
|
140
|
+
): Promise<string> {
|
|
141
|
+
const id = this.generateId()
|
|
142
|
+
const request = { id, action, timestamp: Date.now() }
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await this.sequenceSessionStorage.setItem(REDIRECT_REQUEST_KEY, JSON.stringify(request, jsonReplacers))
|
|
146
|
+
} catch (e) {
|
|
147
|
+
console.error('Failed to set redirect request in storage', e)
|
|
148
|
+
throw new Error('Could not save redirect state to storage. Redirect flow is unavailable.')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const serializedPayload = btoa(JSON.stringify(payload || {}, jsonReplacers))
|
|
152
|
+
const fullWalletUrl = path ? `${this.walletUrl}${path}` : this.walletUrl
|
|
153
|
+
const url = new URL(fullWalletUrl)
|
|
154
|
+
url.searchParams.set('action', action)
|
|
155
|
+
url.searchParams.set('payload', serializedPayload)
|
|
156
|
+
url.searchParams.set('id', id)
|
|
157
|
+
url.searchParams.set('redirectUrl', redirectUrl)
|
|
158
|
+
url.searchParams.set('mode', 'redirect')
|
|
159
|
+
|
|
160
|
+
return url.toString()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public async getRedirectResponse<TResponse = any>(
|
|
164
|
+
cleanState: boolean = true,
|
|
165
|
+
url?: string,
|
|
166
|
+
): Promise<{ payload: TResponse; action: string } | { error: any; action: string } | null> {
|
|
167
|
+
const params = new URLSearchParams(url ? new URL(url).search : window.location.search)
|
|
168
|
+
const responseId = params.get('id')
|
|
169
|
+
if (!responseId) return null
|
|
170
|
+
|
|
171
|
+
let originalRequest: { id: string; action: string; timestamp: number }
|
|
172
|
+
try {
|
|
173
|
+
const storedRequest = await this.sequenceSessionStorage.getItem(REDIRECT_REQUEST_KEY)
|
|
174
|
+
if (!storedRequest) {
|
|
175
|
+
return null
|
|
176
|
+
}
|
|
177
|
+
originalRequest = JSON.parse(storedRequest, jsonRevivers)
|
|
178
|
+
} catch (e) {
|
|
179
|
+
console.error('Failed to parse redirect request from storage', e)
|
|
180
|
+
return null
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (originalRequest.id !== responseId) {
|
|
184
|
+
console.error(`Mismatched ID in redirect response. Expected ${originalRequest.id}, got ${responseId}.`)
|
|
185
|
+
if (cleanState) {
|
|
186
|
+
await this.sequenceSessionStorage.removeItem(REDIRECT_REQUEST_KEY)
|
|
187
|
+
}
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const responsePayloadB64 = params.get('payload')
|
|
192
|
+
const responseErrorB64 = params.get('error')
|
|
193
|
+
|
|
194
|
+
const isBrowser = typeof window !== 'undefined' && window.history
|
|
195
|
+
|
|
196
|
+
if (cleanState) {
|
|
197
|
+
await this.sequenceSessionStorage.removeItem(REDIRECT_REQUEST_KEY)
|
|
198
|
+
if (isBrowser && !url) {
|
|
199
|
+
const cleanUrl = new URL(window.location.href)
|
|
200
|
+
;['id', 'payload', 'error', 'mode'].forEach((p) => cleanUrl.searchParams.delete(p))
|
|
201
|
+
history.replaceState({}, document.title, cleanUrl.toString())
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (responseErrorB64) {
|
|
206
|
+
try {
|
|
207
|
+
return {
|
|
208
|
+
error: JSON.parse(atob(responseErrorB64), jsonRevivers),
|
|
209
|
+
action: originalRequest.action,
|
|
210
|
+
}
|
|
211
|
+
} catch (e) {
|
|
212
|
+
console.error('Failed to parse error from redirect response', e)
|
|
213
|
+
return {
|
|
214
|
+
error: 'Failed to parse error from redirect',
|
|
215
|
+
action: originalRequest.action,
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (responsePayloadB64) {
|
|
220
|
+
try {
|
|
221
|
+
return {
|
|
222
|
+
payload: JSON.parse(atob(responsePayloadB64), jsonRevivers),
|
|
223
|
+
action: originalRequest.action,
|
|
224
|
+
}
|
|
225
|
+
} catch (e) {
|
|
226
|
+
console.error('Failed to parse payload from redirect response', e)
|
|
227
|
+
return {
|
|
228
|
+
error: 'Failed to parse payload from redirect',
|
|
229
|
+
action: originalRequest.action,
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
error: "Redirect response missing 'payload' or 'error'",
|
|
235
|
+
action: originalRequest.action,
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public openWallet(path?: string): Promise<void> {
|
|
240
|
+
if (this.mode === TransportMode.REDIRECT) {
|
|
241
|
+
throw new Error("`openWallet` is not available in 'redirect' mode.")
|
|
242
|
+
}
|
|
243
|
+
if (this.connectionState !== ConnectionState.DISCONNECTED) {
|
|
244
|
+
if (this.isWalletOpen) this.walletWindow?.focus()
|
|
245
|
+
return this.readyPromise || Promise.resolve()
|
|
246
|
+
}
|
|
247
|
+
this.connectionState = ConnectionState.CONNECTING
|
|
248
|
+
this.clearPendingRequests(new Error('Wallet connection reset during open.'))
|
|
249
|
+
this.messageQueue = []
|
|
250
|
+
this.clearTimeouts()
|
|
251
|
+
this.readyPromise = new Promise<void>((resolve, reject) => {
|
|
252
|
+
this.readyPromiseResolve = resolve
|
|
253
|
+
this.readyPromiseReject = reject
|
|
254
|
+
})
|
|
255
|
+
this.readyPromise.catch(() => {})
|
|
256
|
+
this.initId = this.generateId()
|
|
257
|
+
const fullWalletUrl = path ? `${this.walletUrl}${path}` : this.walletUrl
|
|
258
|
+
this.sessionId = this.generateId()
|
|
259
|
+
const urlWithParams = new URL(fullWalletUrl)
|
|
260
|
+
urlWithParams.searchParams.set('dappOrigin', window.location.origin)
|
|
261
|
+
urlWithParams.searchParams.set('sessionId', this.sessionId)
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const openedWindow = window.open(
|
|
265
|
+
urlWithParams.toString(),
|
|
266
|
+
'Wallet',
|
|
267
|
+
`width=${WalletSize.width},height=${WalletSize.height},scrollbars=yes,resizable=yes`,
|
|
268
|
+
)
|
|
269
|
+
this.walletWindow = openedWindow || undefined
|
|
270
|
+
} catch (error) {
|
|
271
|
+
const openError = new Error(
|
|
272
|
+
`Failed to open wallet window: ${error instanceof Error ? error.message : String(error)}`,
|
|
273
|
+
)
|
|
274
|
+
this._handlePreConnectionFailure(openError)
|
|
275
|
+
return Promise.reject(openError)
|
|
276
|
+
}
|
|
277
|
+
if (!this.walletWindow) {
|
|
278
|
+
const error = new Error('Failed to open wallet window. Please check your pop-up blocker settings.')
|
|
279
|
+
this._handlePreConnectionFailure(error)
|
|
280
|
+
return Promise.reject(error)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
this.handshakeTimeoutId = window.setTimeout(() => {
|
|
284
|
+
if (this.connectionState === ConnectionState.CONNECTING) {
|
|
285
|
+
const timeoutError = new Error(`Wallet handshake timed out after ${this.handshakeTimeoutMs}ms.`)
|
|
286
|
+
this._handlePreConnectionFailure(timeoutError)
|
|
287
|
+
}
|
|
288
|
+
}, this.handshakeTimeoutMs)
|
|
289
|
+
|
|
290
|
+
this.closeCheckIntervalId = window.setInterval(() => {
|
|
291
|
+
if (!this.isWalletOpen) {
|
|
292
|
+
if (this.connectionState === ConnectionState.CONNECTING)
|
|
293
|
+
this._handlePreConnectionFailure(new Error('Wallet window was closed before becoming ready.'))
|
|
294
|
+
else if (this.connectionState === ConnectionState.CONNECTED) this._handleDetectedClosure()
|
|
295
|
+
}
|
|
296
|
+
}, 500)
|
|
297
|
+
return this.readyPromise
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
public closeWallet(): void {
|
|
301
|
+
if (this.mode === TransportMode.REDIRECT) {
|
|
302
|
+
console.warn(
|
|
303
|
+
"[DApp] `closeWallet` is not available in 'redirect' mode. Use window.location.href to navigate away.",
|
|
304
|
+
)
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
if (this.connectionState === ConnectionState.DISCONNECTED) return
|
|
308
|
+
if (this.isWalletOpen) this.walletWindow?.close()
|
|
309
|
+
this.connectionState = ConnectionState.DISCONNECTED
|
|
310
|
+
this.readyPromise = undefined
|
|
311
|
+
this.readyPromiseResolve = undefined
|
|
312
|
+
this.readyPromiseReject = undefined
|
|
313
|
+
this._resetConnection(new Error('Wallet closed intentionally by DApp.'), 'Wallet closed intentionally by DApp.')
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
destroy(): void {
|
|
317
|
+
if (this.mode === TransportMode.POPUP) {
|
|
318
|
+
window.removeEventListener('message', this.handleMessage)
|
|
319
|
+
if (this.isWalletOpen) {
|
|
320
|
+
this.walletWindow?.close()
|
|
321
|
+
}
|
|
322
|
+
this._resetConnection(new Error('Transport destroyed.'), 'Destroying transport...')
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private handleMessage = (event: MessageEvent): void => {
|
|
327
|
+
if (event.origin !== this.walletOrigin) {
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const isPotentiallyValidSource =
|
|
332
|
+
this.walletWindow && (event.source === this.walletWindow || !this.walletWindow.closed)
|
|
333
|
+
|
|
334
|
+
if (!isPotentiallyValidSource && event.data?.type !== MessageType.WALLET_OPENED) {
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const message = event.data as TransportMessage
|
|
339
|
+
if (
|
|
340
|
+
!message ||
|
|
341
|
+
typeof message !== 'object' ||
|
|
342
|
+
!message.id ||
|
|
343
|
+
!message.type ||
|
|
344
|
+
(message.type === MessageType.WALLET_OPENED && !message.sessionId)
|
|
345
|
+
) {
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
switch (message.type) {
|
|
351
|
+
case MessageType.WALLET_OPENED:
|
|
352
|
+
this.handleWalletReadyMessage(message)
|
|
353
|
+
break
|
|
354
|
+
case MessageType.RESPONSE:
|
|
355
|
+
this.handleResponseMessage(message)
|
|
356
|
+
break
|
|
357
|
+
case MessageType.REQUEST:
|
|
358
|
+
case MessageType.INIT:
|
|
359
|
+
default:
|
|
360
|
+
break
|
|
361
|
+
}
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.error(`[DApp] Error processing received message (Type: ${message.type}, ID: ${message.id}):`, error)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private handleWalletReadyMessage(message: TransportMessage): void {
|
|
368
|
+
if (this.connectionState !== ConnectionState.CONNECTING) {
|
|
369
|
+
return
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (message.sessionId !== this.sessionId) {
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (this.handshakeTimeoutId !== undefined) {
|
|
377
|
+
window.clearTimeout(this.handshakeTimeoutId)
|
|
378
|
+
this.handshakeTimeoutId = undefined
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const initMessage: TransportMessage = {
|
|
382
|
+
id: this.initId!,
|
|
383
|
+
type: MessageType.INIT,
|
|
384
|
+
sessionId: this.sessionId,
|
|
385
|
+
}
|
|
386
|
+
this.postMessageToWallet(initMessage)
|
|
387
|
+
|
|
388
|
+
this.connectionState = ConnectionState.CONNECTED
|
|
389
|
+
|
|
390
|
+
if (this.readyPromiseResolve) {
|
|
391
|
+
this.readyPromiseResolve()
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
this.messageQueue.forEach((queuedMsg) => {
|
|
395
|
+
this.postMessageToWallet(queuedMsg)
|
|
396
|
+
})
|
|
397
|
+
this.messageQueue = []
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private handleResponseMessage(message: TransportMessage): void {
|
|
401
|
+
const pending = this.pendingRequests.get(message.id)
|
|
402
|
+
if (pending) {
|
|
403
|
+
window.clearTimeout(pending.timer)
|
|
404
|
+
this.pendingRequests.delete(message.id)
|
|
405
|
+
if (message.error) {
|
|
406
|
+
const error = new Error(`Wallet responded with error: ${JSON.stringify(message.error)}`)
|
|
407
|
+
pending.reject(error)
|
|
408
|
+
} else {
|
|
409
|
+
pending.resolve(message.payload)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private postMessageToWallet(message: TransportMessage): void {
|
|
415
|
+
if (!this.isWalletOpen) {
|
|
416
|
+
if (
|
|
417
|
+
message.type === MessageType.INIT &&
|
|
418
|
+
this.connectionState === ConnectionState.CONNECTING &&
|
|
419
|
+
message.id === this.initId
|
|
420
|
+
) {
|
|
421
|
+
this._handlePreConnectionFailure(new Error('Failed to send INIT: Wallet window closed unexpectedly.'))
|
|
422
|
+
} else if (message.type === MessageType.REQUEST) {
|
|
423
|
+
const pendingReq = this.pendingRequests.get(message.id)
|
|
424
|
+
if (pendingReq) {
|
|
425
|
+
window.clearTimeout(pendingReq.timer)
|
|
426
|
+
this.pendingRequests.delete(message.id)
|
|
427
|
+
pendingReq.reject(new Error(`Failed to send request '${pendingReq.action}': Wallet window closed.`))
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (this.connectionState !== ConnectionState.CONNECTED && message.type !== MessageType.INIT) {
|
|
434
|
+
this.messageQueue.push(message)
|
|
435
|
+
return
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
this.walletWindow?.postMessage(message, this.walletOrigin)
|
|
440
|
+
} catch (error) {
|
|
441
|
+
const rejectionError =
|
|
442
|
+
error instanceof Error ? error : new Error('Failed to send message to wallet due to unknown error')
|
|
443
|
+
|
|
444
|
+
if (
|
|
445
|
+
message.type === MessageType.INIT &&
|
|
446
|
+
this.connectionState === ConnectionState.CONNECTING &&
|
|
447
|
+
message.id === this.initId
|
|
448
|
+
) {
|
|
449
|
+
this._handlePreConnectionFailure(rejectionError)
|
|
450
|
+
} else if (message.type === MessageType.REQUEST) {
|
|
451
|
+
const pendingReq = this.pendingRequests.get(message.id)
|
|
452
|
+
if (pendingReq) {
|
|
453
|
+
window.clearTimeout(pendingReq.timer)
|
|
454
|
+
this.pendingRequests.delete(message.id)
|
|
455
|
+
pendingReq.reject(rejectionError)
|
|
456
|
+
}
|
|
457
|
+
this._handleDetectedClosure()
|
|
458
|
+
} else {
|
|
459
|
+
this._handleDetectedClosure()
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
private _resetConnection(reason: Error, logMessage: string): void {
|
|
465
|
+
console.log(`[DApp] ${logMessage}`)
|
|
466
|
+
if (this.readyPromiseReject) {
|
|
467
|
+
this.readyPromiseReject(reason)
|
|
468
|
+
}
|
|
469
|
+
this.clearTimeouts()
|
|
470
|
+
this.clearPendingRequests(reason)
|
|
471
|
+
this.connectionState = ConnectionState.DISCONNECTED
|
|
472
|
+
this.walletWindow = undefined
|
|
473
|
+
this.readyPromise = undefined
|
|
474
|
+
this.readyPromiseResolve = undefined
|
|
475
|
+
this.readyPromiseReject = undefined
|
|
476
|
+
this.initId = undefined
|
|
477
|
+
this.sessionId = undefined
|
|
478
|
+
this.messageQueue = []
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
private _handlePreConnectionFailure(error: Error): void {
|
|
482
|
+
this._resetConnection(error, `Connection failure: ${error.message}`)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private _handleDetectedClosure(): void {
|
|
486
|
+
if (this.connectionState === ConnectionState.CONNECTED) {
|
|
487
|
+
const reason = new Error('Wallet connection terminated unexpectedly.')
|
|
488
|
+
this._resetConnection(reason, 'Wallet connection terminated unexpectedly after ready.')
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private clearPendingRequests(reason: Error): void {
|
|
493
|
+
if (this.pendingRequests.size > 0) {
|
|
494
|
+
const requestsToClear = new Map(this.pendingRequests)
|
|
495
|
+
this.pendingRequests.clear()
|
|
496
|
+
requestsToClear.forEach((pending) => {
|
|
497
|
+
window.clearTimeout(pending.timer)
|
|
498
|
+
const errorToSend = reason instanceof Error ? reason : new Error(`Operation failed: ${reason}`)
|
|
499
|
+
pending.reject(errorToSend)
|
|
500
|
+
})
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
private clearTimeouts(): void {
|
|
505
|
+
if (this.handshakeTimeoutId !== undefined) {
|
|
506
|
+
window.clearTimeout(this.handshakeTimeoutId)
|
|
507
|
+
this.handshakeTimeoutId = undefined
|
|
508
|
+
}
|
|
509
|
+
if (this.closeCheckIntervalId !== undefined) {
|
|
510
|
+
window.clearInterval(this.closeCheckIntervalId)
|
|
511
|
+
this.closeCheckIntervalId = undefined
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private generateId(): string {
|
|
516
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}`
|
|
517
|
+
}
|
|
518
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export { DappClient } from './DappClient.js'
|
|
2
|
+
export type { DappClientEventListener } from './DappClient.js'
|
|
3
|
+
export type {
|
|
4
|
+
LoginMethod,
|
|
5
|
+
GuardConfig,
|
|
6
|
+
Transaction,
|
|
7
|
+
SignatureSuccessResponse,
|
|
8
|
+
ChainSessionManagerEvent,
|
|
9
|
+
SequenceSessionStorage,
|
|
10
|
+
RandomPrivateKeyFn,
|
|
11
|
+
Session,
|
|
12
|
+
SignMessagePayload,
|
|
13
|
+
AddExplicitSessionPayload,
|
|
14
|
+
CreateNewSessionPayload,
|
|
15
|
+
SignTypedDataPayload,
|
|
16
|
+
ConnectSuccessResponsePayload,
|
|
17
|
+
ModifySessionSuccessResponsePayload,
|
|
18
|
+
ModifySessionPayload,
|
|
19
|
+
DappClientWalletActionEventListener,
|
|
20
|
+
DappClientExplicitSessionEventListener,
|
|
21
|
+
TransactionRequest,
|
|
22
|
+
SendWalletTransactionPayload,
|
|
23
|
+
SendWalletTransactionSuccessResponse,
|
|
24
|
+
WalletActionResponse,
|
|
25
|
+
} from './types/index.js'
|
|
26
|
+
export { RequestActionType, TransportMode } from './types/index.js'
|
|
27
|
+
export {
|
|
28
|
+
FeeOptionError,
|
|
29
|
+
TransactionError,
|
|
30
|
+
AddExplicitSessionError,
|
|
31
|
+
ConnectionError,
|
|
32
|
+
InitializationError,
|
|
33
|
+
SigningError,
|
|
34
|
+
ModifyExplicitSessionError,
|
|
35
|
+
} from './utils/errors.js'
|
|
36
|
+
export { getExplorerUrl, jsonReplacers, jsonRevivers } from './utils/index.js'
|
|
37
|
+
export type {
|
|
38
|
+
SequenceStorage,
|
|
39
|
+
ExplicitSessionData,
|
|
40
|
+
ImplicitSessionData,
|
|
41
|
+
PendingRequestContext,
|
|
42
|
+
PendingPayload,
|
|
43
|
+
} from './utils/storage.js'
|
|
44
|
+
export { WebStorage } from './utils/storage.js'
|
|
45
|
+
|
|
46
|
+
export { Permission, Extensions, SessionConfig } from '@0xsequence/wallet-primitives'
|
|
47
|
+
export { Signers, Wallet, Utils, Relayer } from '@0xsequence/wallet-core'
|