@0xsequence/provider 0.0.0-2021823181734
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/CHANGELOG.md +909 -0
- package/README.md +4 -0
- package/dist/0xsequence-provider.cjs.d.ts +1 -0
- package/dist/0xsequence-provider.cjs.dev.js +3540 -0
- package/dist/0xsequence-provider.cjs.js +7 -0
- package/dist/0xsequence-provider.cjs.prod.js +3540 -0
- package/dist/0xsequence-provider.esm.js +3508 -0
- package/dist/declarations/src/commands/index.d.ts +20 -0
- package/dist/declarations/src/index.d.ts +5 -0
- package/dist/declarations/src/provider.d.ts +59 -0
- package/dist/declarations/src/transports/base-provider-transport.d.ts +40 -0
- package/dist/declarations/src/transports/base-wallet-transport.d.ts +38 -0
- package/dist/declarations/src/transports/extension-transport/base-injected-transport.d.ts +18 -0
- package/dist/declarations/src/transports/extension-transport/extension-message-handler.d.ts +12 -0
- package/dist/declarations/src/transports/extension-transport/extension-message-provider.d.ts +11 -0
- package/dist/declarations/src/transports/extension-transport/index.d.ts +3 -0
- package/dist/declarations/src/transports/index.d.ts +7 -0
- package/dist/declarations/src/transports/mux-transport/index.d.ts +1 -0
- package/dist/declarations/src/transports/mux-transport/mux-message-provider.d.ts +22 -0
- package/dist/declarations/src/transports/proxy-transport/index.d.ts +3 -0
- package/dist/declarations/src/transports/proxy-transport/proxy-message-channel.d.ts +15 -0
- package/dist/declarations/src/transports/proxy-transport/proxy-message-handler.d.ts +12 -0
- package/dist/declarations/src/transports/proxy-transport/proxy-message-provider.d.ts +12 -0
- package/dist/declarations/src/transports/wallet-request-handler.d.ts +51 -0
- package/dist/declarations/src/transports/window-transport/index.d.ts +2 -0
- package/dist/declarations/src/transports/window-transport/window-message-handler.d.ts +15 -0
- package/dist/declarations/src/transports/window-transport/window-message-provider.d.ts +13 -0
- package/dist/declarations/src/types.d.ts +170 -0
- package/dist/declarations/src/utils.d.ts +14 -0
- package/dist/declarations/src/wallet.d.ts +99 -0
- package/package.json +37 -0
- package/src/commands/index.ts +146 -0
- package/src/index.ts +5 -0
- package/src/provider.ts +494 -0
- package/src/transports/base-provider-transport.ts +411 -0
- package/src/transports/base-wallet-transport.ts +441 -0
- package/src/transports/extension-transport/base-injected-transport.ts +100 -0
- package/src/transports/extension-transport/extension-message-handler.ts +25 -0
- package/src/transports/extension-transport/extension-message-provider.ts +41 -0
- package/src/transports/extension-transport/index.ts +3 -0
- package/src/transports/index.ts +7 -0
- package/src/transports/mux-transport/index.ts +1 -0
- package/src/transports/mux-transport/mux-message-provider.ts +160 -0
- package/src/transports/proxy-transport/index.ts +3 -0
- package/src/transports/proxy-transport/proxy-message-channel.ts +58 -0
- package/src/transports/proxy-transport/proxy-message-handler.ts +46 -0
- package/src/transports/proxy-transport/proxy-message-provider.ts +81 -0
- package/src/transports/wallet-request-handler.ts +702 -0
- package/src/transports/window-transport/index.ts +2 -0
- package/src/transports/window-transport/window-message-handler.ts +166 -0
- package/src/transports/window-transport/window-message-provider.ts +183 -0
- package/src/types.ts +252 -0
- package/src/utils.ts +76 -0
- package/src/wallet.ts +692 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { ethers } from 'ethers'
|
|
2
|
+
import {
|
|
3
|
+
WalletTransport,
|
|
4
|
+
ProviderMessage,
|
|
5
|
+
ProviderMessageRequest,
|
|
6
|
+
EventType,
|
|
7
|
+
ProviderMessageResponse,
|
|
8
|
+
ProviderMessageTransport,
|
|
9
|
+
ProviderRpcError,
|
|
10
|
+
InitState,
|
|
11
|
+
ConnectDetails,
|
|
12
|
+
OpenWalletIntent,
|
|
13
|
+
WalletSession,
|
|
14
|
+
TransportSession
|
|
15
|
+
} from '../types'
|
|
16
|
+
|
|
17
|
+
import { WalletRequestHandler } from './wallet-request-handler'
|
|
18
|
+
|
|
19
|
+
import { NetworkConfig, WalletContext, JsonRpcRequest, JsonRpcResponseCallback } from '@0xsequence/network'
|
|
20
|
+
import { logger, sanitizeAlphanumeric, sanitizeHost, sanitizeNumberString } from '@0xsequence/utils'
|
|
21
|
+
import { AuthorizationOptions } from '@0xsequence/auth'
|
|
22
|
+
|
|
23
|
+
import { PROVIDER_OPEN_TIMEOUT } from './base-provider-transport'
|
|
24
|
+
import { isBrowserExtension } from '../utils'
|
|
25
|
+
|
|
26
|
+
const TRANSPORT_SESSION_LS_KEY = '@sequence.transportSession'
|
|
27
|
+
|
|
28
|
+
export abstract class BaseWalletTransport implements WalletTransport {
|
|
29
|
+
protected walletRequestHandler: WalletRequestHandler
|
|
30
|
+
protected _sessionId: string
|
|
31
|
+
protected _registered: boolean
|
|
32
|
+
|
|
33
|
+
protected _init: InitState
|
|
34
|
+
protected _initNonce: string
|
|
35
|
+
protected _initCallback?: (error?: string) => void
|
|
36
|
+
|
|
37
|
+
// appOrigin identifies the dapp's origin which opened the app. A transport
|
|
38
|
+
// will auto-detect and set this value if it can. This is determined
|
|
39
|
+
// as the parent app/window which opened the wallet.
|
|
40
|
+
protected appOrigin?: string
|
|
41
|
+
|
|
42
|
+
constructor(walletRequestHandler: WalletRequestHandler) {
|
|
43
|
+
this.walletRequestHandler = walletRequestHandler
|
|
44
|
+
this._init = InitState.NIL
|
|
45
|
+
|
|
46
|
+
this.walletRequestHandler.on('connect', (connectDetails: ConnectDetails) => {
|
|
47
|
+
if (!this.registered) return
|
|
48
|
+
// means user has logged in and wallet is connected to the app
|
|
49
|
+
this.notifyConnect(connectDetails)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
this.walletRequestHandler.on('disconnect', (error?: ProviderRpcError) => {
|
|
53
|
+
if (!this.registered) return
|
|
54
|
+
// means user has logged out the app / disconnected wallet from the app
|
|
55
|
+
this.notifyDisconnect(error)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
this.walletRequestHandler.on('accountsChanged', (accounts: string[]) => {
|
|
59
|
+
if (!this.registered) return
|
|
60
|
+
this.notifyAccountsChanged(accounts)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
this.walletRequestHandler.on('networks', (networks: NetworkConfig[]) => {
|
|
64
|
+
if (!this.registered) return
|
|
65
|
+
this.notifyNetworks(networks)
|
|
66
|
+
if (!networks || networks.length === 0) {
|
|
67
|
+
this.notifyChainChanged('0x0')
|
|
68
|
+
} else {
|
|
69
|
+
this.notifyChainChanged(ethers.utils.hexlify(networks[0].chainId))
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
this.walletRequestHandler.on('walletContext', (walletContext: WalletContext) => {
|
|
74
|
+
if (!this.registered || !walletContext) return
|
|
75
|
+
this.notifyWalletContext(walletContext)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
this.walletRequestHandler.on('close', (error?: ProviderRpcError) => {
|
|
79
|
+
if (!this.registered) return
|
|
80
|
+
this.notifyClose(error)
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get registered(): boolean {
|
|
85
|
+
return this._registered
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
register() {
|
|
89
|
+
throw new Error('abstract method')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
unregister() {
|
|
93
|
+
throw new Error('abstract method')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
sendAsync = async (request: JsonRpcRequest, callback: JsonRpcResponseCallback, chainId?: number) => {
|
|
97
|
+
throw new Error('abstract method')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
handleMessage = async (message: ProviderMessage<any>) => {
|
|
101
|
+
const request = message
|
|
102
|
+
|
|
103
|
+
// ensure initial handshake is complete before accepting
|
|
104
|
+
// other kinds of messages.
|
|
105
|
+
if (this._init !== InitState.OK) {
|
|
106
|
+
if (request.type === EventType.INIT) {
|
|
107
|
+
if (this.isValidInitAck(message)) {
|
|
108
|
+
// successful init
|
|
109
|
+
if (this._initCallback) this._initCallback()
|
|
110
|
+
} else {
|
|
111
|
+
// failed init
|
|
112
|
+
if (this._initCallback) this._initCallback('invalid init')
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
// we expect init message first. do nothing here.
|
|
117
|
+
}
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// handle request
|
|
122
|
+
switch (request.type) {
|
|
123
|
+
case EventType.OPEN: {
|
|
124
|
+
if (this._init !== InitState.OK) return
|
|
125
|
+
const session: TransportSession = {
|
|
126
|
+
sessionId: request.data.sessionId,
|
|
127
|
+
intent: request.data.intent,
|
|
128
|
+
networkId: request.data.networkId
|
|
129
|
+
}
|
|
130
|
+
await this.open(session)
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
case EventType.CLOSE: {
|
|
135
|
+
if (this._init !== InitState.OK) return
|
|
136
|
+
// noop. just here to capture the message so event emitters may be notified
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
case EventType.MESSAGE: {
|
|
141
|
+
const response = await this.walletRequestHandler.sendMessageRequest(request)
|
|
142
|
+
this.sendMessage(response)
|
|
143
|
+
|
|
144
|
+
if (response.data.error) {
|
|
145
|
+
// TODO: for certain errors, whenever we want to render something to the UI
|
|
146
|
+
// we should throw
|
|
147
|
+
}
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
default: {
|
|
152
|
+
logger.error(`unexpected payload type ${request.type}`)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// sendMessageRequest sends a ProviderMessageRequest to the wallet post-message transport
|
|
158
|
+
sendMessageRequest = async (message: ProviderMessageRequest): Promise<ProviderMessageResponse> => {
|
|
159
|
+
return this.walletRequestHandler.sendMessageRequest(message)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
sendMessage(message: ProviderMessage<any>) {
|
|
163
|
+
throw new Error('abstract method')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
notifyOpen(openInfo: { chainId?: string; sessionId?: string; session?: WalletSession; error?: string }) {
|
|
167
|
+
const { chainId, sessionId, session, error } = openInfo
|
|
168
|
+
this.sendMessage({
|
|
169
|
+
idx: -1,
|
|
170
|
+
type: EventType.OPEN,
|
|
171
|
+
data: {
|
|
172
|
+
chainId,
|
|
173
|
+
sessionId,
|
|
174
|
+
session,
|
|
175
|
+
error
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
notifyClose(error?: ProviderRpcError) {
|
|
181
|
+
this.sendMessage({
|
|
182
|
+
idx: -1,
|
|
183
|
+
type: EventType.CLOSE,
|
|
184
|
+
data: error ? { error } : null
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
notifyConnect(connectDetails: ConnectDetails) {
|
|
189
|
+
this.sendMessage({
|
|
190
|
+
idx: -1,
|
|
191
|
+
type: EventType.CONNECT,
|
|
192
|
+
data: connectDetails
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
notifyDisconnect(error?: ProviderRpcError) {
|
|
197
|
+
this.sendMessage({
|
|
198
|
+
idx: -1,
|
|
199
|
+
type: EventType.DISCONNECT,
|
|
200
|
+
data: error ? { error } : null
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
notifyAccountsChanged(accounts: string[]) {
|
|
205
|
+
this.sendMessage({
|
|
206
|
+
idx: -1,
|
|
207
|
+
type: EventType.ACCOUNTS_CHANGED,
|
|
208
|
+
data: accounts
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
notifyChainChanged(chainIdHex: string) {
|
|
213
|
+
this.sendMessage({
|
|
214
|
+
idx: -1,
|
|
215
|
+
type: EventType.CHAIN_CHANGED,
|
|
216
|
+
data: chainIdHex
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
notifyNetworks(networks: NetworkConfig[]) {
|
|
221
|
+
this.sendMessage({
|
|
222
|
+
idx: -1,
|
|
223
|
+
type: EventType.NETWORKS,
|
|
224
|
+
data: networks
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
notifyWalletContext(walletContext: WalletContext) {
|
|
229
|
+
this.sendMessage({
|
|
230
|
+
idx: -1,
|
|
231
|
+
type: EventType.WALLET_CONTEXT,
|
|
232
|
+
data: walletContext
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
protected isValidInitAck(message: ProviderMessage<any>): boolean {
|
|
237
|
+
if (this._init === InitState.OK) {
|
|
238
|
+
// we're already in init state, we shouldn't handle this message
|
|
239
|
+
logger.warn("isValidInitAck, already in init'd state, so inquiry is invalid.")
|
|
240
|
+
return false
|
|
241
|
+
}
|
|
242
|
+
if (message.type !== EventType.INIT) {
|
|
243
|
+
logger.warn('isValidInitAck, invalid message type, expecting init')
|
|
244
|
+
return false
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const { sessionId, nonce } = (message.data as any) as { sessionId: string; nonce: string }
|
|
248
|
+
if (!sessionId || sessionId.length === 0 || !nonce || nonce.length === 0) {
|
|
249
|
+
logger.error('invalid init ack')
|
|
250
|
+
return false
|
|
251
|
+
}
|
|
252
|
+
if (sessionId !== this._sessionId || nonce !== this._initNonce) {
|
|
253
|
+
logger.error('invalid init ack match')
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// all checks pass, its true
|
|
258
|
+
return true
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private init(): Promise<void> {
|
|
262
|
+
return new Promise<void>((resolve, reject) => {
|
|
263
|
+
// avoid re-init`ing, or if there is a transport which doesn't require
|
|
264
|
+
// it, then it may set this._init to OK in its constructor.
|
|
265
|
+
if (this._init === InitState.OK) {
|
|
266
|
+
resolve()
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
if (this._init !== InitState.NIL || this._initCallback) {
|
|
270
|
+
reject('transport init is in progress')
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// start init timeout, if we don't receive confirmation
|
|
275
|
+
// from provider within this amount of time, then we timeout
|
|
276
|
+
const initTimeout = setTimeout(() => {
|
|
277
|
+
logger.warn('transport init timed out')
|
|
278
|
+
if (this._initCallback) {
|
|
279
|
+
this._initCallback('transport init timed out')
|
|
280
|
+
}
|
|
281
|
+
}, PROVIDER_OPEN_TIMEOUT / 2)
|
|
282
|
+
|
|
283
|
+
// setup callback as we receive the init message async in the handleMessage function
|
|
284
|
+
this._initCallback = (error?: string) => {
|
|
285
|
+
this._initCallback = undefined // reset
|
|
286
|
+
clearTimeout(initTimeout)
|
|
287
|
+
if (error) {
|
|
288
|
+
reject(error)
|
|
289
|
+
} else {
|
|
290
|
+
this._init = InitState.OK
|
|
291
|
+
resolve()
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// send init request with random nonce to the provider, where we expect
|
|
296
|
+
// for the provider to echo it back to us as complete handshake
|
|
297
|
+
this._initNonce = `${performance.now()}`
|
|
298
|
+
this.sendMessage({
|
|
299
|
+
idx: -1,
|
|
300
|
+
type: EventType.INIT,
|
|
301
|
+
data: { nonce: this._initNonce }
|
|
302
|
+
})
|
|
303
|
+
this._init = InitState.SENT_NONCE
|
|
304
|
+
|
|
305
|
+
// NOTE: the promise will resolve in the _initCallback method
|
|
306
|
+
// which will be called from either handleMessage or the initTimeout
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
protected open = async ({ sessionId, intent, networkId }: TransportSession): Promise<boolean> => {
|
|
311
|
+
if (sessionId) {
|
|
312
|
+
this._sessionId = sanitizeNumberString(sessionId)
|
|
313
|
+
// persist transport session in localstorage for restoring after redirect/reload
|
|
314
|
+
this.saveTransportSession({ sessionId, intent, networkId })
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// init handshake for certain transports, before we can open the communication.
|
|
318
|
+
//
|
|
319
|
+
// for example, with the window-transport, we have to exchange messages to determine the
|
|
320
|
+
// origin host of the dapp.
|
|
321
|
+
await this.init()
|
|
322
|
+
|
|
323
|
+
// Prepare connect options from intent
|
|
324
|
+
if (intent && intent.type === 'connect' && intent.options) {
|
|
325
|
+
const connectOptions = intent.options
|
|
326
|
+
const authorizeOptions: AuthorizationOptions = connectOptions // overlapping types
|
|
327
|
+
|
|
328
|
+
// Sanity/integrity check the intent payload, and set authorization origin
|
|
329
|
+
// if its been determined as part of the init handshake from earlier.
|
|
330
|
+
if (this.appOrigin && authorizeOptions?.origin) {
|
|
331
|
+
if (!isBrowserExtension()) {
|
|
332
|
+
if (authorizeOptions.origin !== this.appOrigin) {
|
|
333
|
+
throw new Error('origin is invalid')
|
|
334
|
+
} else {
|
|
335
|
+
// request origin and derived origins match, lets carry on
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} else if (!this.appOrigin && authorizeOptions?.origin) {
|
|
339
|
+
// ie. when we can't determine the origin in our transport, but dapp provides it to us.
|
|
340
|
+
// we just sanitize the origin host.
|
|
341
|
+
connectOptions.origin = sanitizeHost(authorizeOptions.origin)
|
|
342
|
+
} else if (this.appOrigin) {
|
|
343
|
+
// ie. when we auto-determine the origin such as in window-transport
|
|
344
|
+
connectOptions.origin = this.appOrigin
|
|
345
|
+
}
|
|
346
|
+
if (connectOptions.app) {
|
|
347
|
+
connectOptions.app = sanitizeAlphanumeric(connectOptions.app)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Set connect options on the walletRequestHandler as our primary
|
|
351
|
+
// wallet controller
|
|
352
|
+
this.walletRequestHandler.setConnectOptions(connectOptions)
|
|
353
|
+
if (connectOptions.networkId) {
|
|
354
|
+
networkId = connectOptions.networkId
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
this.walletRequestHandler.setConnectOptions(undefined)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Notify open and proceed to prompt for connection if intended
|
|
361
|
+
if (!this.walletRequestHandler.isSignedIn()) {
|
|
362
|
+
// open wallet without a specific connected chainId, as the user is not signed in
|
|
363
|
+
this.notifyOpen({
|
|
364
|
+
sessionId: this._sessionId
|
|
365
|
+
})
|
|
366
|
+
return true
|
|
367
|
+
} else {
|
|
368
|
+
// Set default network, in case of error chainId will be undefined or 0
|
|
369
|
+
let chainId: number | undefined = undefined
|
|
370
|
+
try {
|
|
371
|
+
if (networkId) {
|
|
372
|
+
chainId = await this.walletRequestHandler.setDefaultNetwork(networkId, false)
|
|
373
|
+
} else {
|
|
374
|
+
chainId = await this.walletRequestHandler.getChainId()
|
|
375
|
+
}
|
|
376
|
+
} catch (err) {
|
|
377
|
+
console.error(err)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Failed to set default network on open -- quit + close
|
|
381
|
+
if (!chainId || chainId <= 0) {
|
|
382
|
+
this.notifyOpen({
|
|
383
|
+
sessionId: this._sessionId,
|
|
384
|
+
error: `failed to open wallet on network ${networkId}`
|
|
385
|
+
})
|
|
386
|
+
return false
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// prompt user with a connect request. the options will be used as previously set above.
|
|
390
|
+
// upon success, the walletRequestHandler will notify the dapp with the ConnectDetails.
|
|
391
|
+
// upon cancellation by user, the walletRequestHandler will throw an error
|
|
392
|
+
|
|
393
|
+
if (intent && intent.type === 'connect') {
|
|
394
|
+
// notify wallet is opened, without session details
|
|
395
|
+
this.notifyOpen({
|
|
396
|
+
sessionId: this._sessionId
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
const connectDetails = await this.walletRequestHandler.promptConnect(intent.options)
|
|
401
|
+
if (connectDetails.connected) {
|
|
402
|
+
this.walletRequestHandler.notifyConnect(connectDetails)
|
|
403
|
+
}
|
|
404
|
+
} catch (err) {
|
|
405
|
+
logger.warn('promptConnect not connected:', err)
|
|
406
|
+
} finally {
|
|
407
|
+
// auto-close by default, unless intent is to keep open
|
|
408
|
+
if (!intent.options || intent.options.keepWalletOpened !== true) {
|
|
409
|
+
this.notifyClose()
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
// user is already connected, notify session details.
|
|
414
|
+
// TODO: in future, keep list if 'connected' dapps / sessions in the session
|
|
415
|
+
// controller, and only sync with allowed apps
|
|
416
|
+
this.notifyOpen({
|
|
417
|
+
sessionId: this._sessionId,
|
|
418
|
+
chainId: `${chainId}`,
|
|
419
|
+
session: await this.walletRequestHandler.walletSession()
|
|
420
|
+
})
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return true
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
private saveTransportSession = (session: TransportSession) => {
|
|
428
|
+
window.localStorage.setItem(TRANSPORT_SESSION_LS_KEY, JSON.stringify(session))
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
protected getCachedTransportSession = (): TransportSession | null => {
|
|
432
|
+
const session = window.localStorage.getItem(TRANSPORT_SESSION_LS_KEY)
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
return session ? (JSON.parse(session) as TransportSession) : null
|
|
436
|
+
} catch (err) {
|
|
437
|
+
console.error(`unable to parse transport session: ${session}`)
|
|
438
|
+
return null
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { JsonRpcRequest, JsonRpcResponse } from '@0xsequence/network'
|
|
2
|
+
import { EventEmitter2 as EventEmitter } from 'eventemitter2'
|
|
3
|
+
import {
|
|
4
|
+
ProviderMessageResponseCallback,
|
|
5
|
+
ProviderMessage,
|
|
6
|
+
EventType,
|
|
7
|
+
ProviderMessageRequest,
|
|
8
|
+
ProviderMessageResponse
|
|
9
|
+
} from '../../types'
|
|
10
|
+
|
|
11
|
+
interface Stream {
|
|
12
|
+
on(ev: string | symbol, fn: (...args: any[]) => void): void
|
|
13
|
+
writable: boolean
|
|
14
|
+
write(chunk: any, cb?: (error: Error | null | undefined) => void): boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// to be used on injected window.ethereum EIP1193 proxy
|
|
18
|
+
export abstract class BaseInjectedTransport extends EventEmitter {
|
|
19
|
+
protected responseCallbacks = new Map<number, ProviderMessageResponseCallback>()
|
|
20
|
+
|
|
21
|
+
private _messageIdx = 0
|
|
22
|
+
protected nextMessageIdx = () => ++this._messageIdx
|
|
23
|
+
|
|
24
|
+
constructor(private stream: Stream) {
|
|
25
|
+
super()
|
|
26
|
+
|
|
27
|
+
this.stream.on('data', this.handleMessage)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private handleMessage = (message: ProviderMessage<JsonRpcResponse>) => {
|
|
31
|
+
if (!message.type || !message.data) {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log('[received message]', message)
|
|
36
|
+
|
|
37
|
+
const requestIdx = message.idx
|
|
38
|
+
const responseCallback = this.responseCallbacks.get(requestIdx)
|
|
39
|
+
if (requestIdx) {
|
|
40
|
+
this.responseCallbacks.delete(requestIdx)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
switch (message.type) {
|
|
44
|
+
case EventType.MESSAGE:
|
|
45
|
+
if (responseCallback) {
|
|
46
|
+
this.emit(EventType.MESSAGE, message)
|
|
47
|
+
responseCallback(message.data.error, message)
|
|
48
|
+
} else {
|
|
49
|
+
// NOTE: this would occur if 'idx' isn't set, which should never happen
|
|
50
|
+
// or when we register two handler, or duplicate messages with the same idx are sent,
|
|
51
|
+
// all of which should be prevented prior to getting to this point
|
|
52
|
+
throw new Error('impossible state')
|
|
53
|
+
}
|
|
54
|
+
break
|
|
55
|
+
case EventType.DISCONNECT:
|
|
56
|
+
case EventType.ACCOUNTS_CHANGED:
|
|
57
|
+
case EventType.CHAIN_CHANGED:
|
|
58
|
+
this.emit(message.type, message.data)
|
|
59
|
+
break
|
|
60
|
+
default:
|
|
61
|
+
console.error('unknown message type', message)
|
|
62
|
+
break
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
protected sendMessageRequest = async (message: ProviderMessageRequest): Promise<ProviderMessageResponse> => {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
if (!message.idx || message.idx <= 0) {
|
|
69
|
+
reject(new Error('message idx not set'))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const responseCallback: ProviderMessageResponseCallback = (error: any, response?: ProviderMessageResponse) => {
|
|
73
|
+
if (error) {
|
|
74
|
+
reject(error)
|
|
75
|
+
} else if (response) {
|
|
76
|
+
resolve(response)
|
|
77
|
+
} else {
|
|
78
|
+
throw new Error('no valid response to return')
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const { idx } = message
|
|
83
|
+
if (!this.responseCallbacks.get(idx)) {
|
|
84
|
+
this.responseCallbacks.set(idx, responseCallback)
|
|
85
|
+
} else {
|
|
86
|
+
reject(new Error('duplicate message idx, should never happen'))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.sendMessage(message)
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private sendMessage(message: ProviderMessage<JsonRpcRequest>) {
|
|
94
|
+
if (!this.stream.writable) {
|
|
95
|
+
console.error('window post message stream is not writable')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.stream.write(message)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { WalletRequestHandler } from '../wallet-request-handler'
|
|
2
|
+
import { BaseWalletTransport } from '../base-wallet-transport'
|
|
3
|
+
import { InitState, ProviderMessage } from '../../types'
|
|
4
|
+
import { Runtime } from 'webextension-polyfill-ts'
|
|
5
|
+
|
|
6
|
+
export const CHANNEL_ID = 'sequence-extension-message-handler'
|
|
7
|
+
|
|
8
|
+
export class ExtensionMessageHandler extends BaseWalletTransport {
|
|
9
|
+
private port: any
|
|
10
|
+
|
|
11
|
+
constructor(walletRequestHandler: WalletRequestHandler, public runtime: Runtime.Static) {
|
|
12
|
+
super(walletRequestHandler)
|
|
13
|
+
this._init = InitState.OK
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
register() {
|
|
17
|
+
this._registered = true
|
|
18
|
+
this.port = this.runtime.connect({ name: CHANNEL_ID })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
sendMessage(message: ProviderMessage<any>) {
|
|
22
|
+
console.log('[ExtensionMessageHandler send]', message)
|
|
23
|
+
this.port.postMessage(message)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { InitState, OpenWalletIntent, ProviderMessage } from '../../types'
|
|
2
|
+
import { BaseProviderTransport } from '../base-provider-transport'
|
|
3
|
+
import { CHANNEL_ID } from './extension-message-handler'
|
|
4
|
+
|
|
5
|
+
import { Runtime } from 'webextension-polyfill-ts'
|
|
6
|
+
|
|
7
|
+
export class ExtensionMessageProvider extends BaseProviderTransport {
|
|
8
|
+
constructor(runtime: Runtime.Static) {
|
|
9
|
+
super()
|
|
10
|
+
|
|
11
|
+
runtime.onConnect.addListener(port => {
|
|
12
|
+
if (port.name === CHANNEL_ID) {
|
|
13
|
+
this._init = InitState.OK
|
|
14
|
+
|
|
15
|
+
port.onMessage.addListener((message: ProviderMessage<any>) => {
|
|
16
|
+
this.handleMessage(message)
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
register = () => {
|
|
23
|
+
this._registered = true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
sendMessage(message: ProviderMessage<any>) {
|
|
27
|
+
//noop
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
unregister() {
|
|
31
|
+
//noop
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
openWallet(path?: string, intent?: OpenWalletIntent, networkId?: string | number) {
|
|
35
|
+
//noop
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
closeWallet() {
|
|
39
|
+
//noop
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './base-provider-transport'
|
|
2
|
+
export * from './base-wallet-transport'
|
|
3
|
+
export * from './proxy-transport'
|
|
4
|
+
export * from './mux-transport'
|
|
5
|
+
export * from './window-transport'
|
|
6
|
+
export * from './wallet-request-handler'
|
|
7
|
+
export * from './extension-transport'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './mux-message-provider'
|