@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,218 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { Attestation, Payload } from '@0xsequence/wallet-primitives'
|
|
3
|
+
import { Signers } from '@0xsequence/wallet-core'
|
|
4
|
+
import { Address, Hex } from 'ox'
|
|
5
|
+
import type { TypedData } from 'ox/TypedData'
|
|
6
|
+
|
|
7
|
+
// --- Public Interfaces and Constants ---
|
|
8
|
+
|
|
9
|
+
export const RequestActionType = {
|
|
10
|
+
CREATE_NEW_SESSION: 'createNewSession',
|
|
11
|
+
ADD_EXPLICIT_SESSION: 'addExplicitSession',
|
|
12
|
+
MODIFY_EXPLICIT_SESSION: 'modifyExplicitSession',
|
|
13
|
+
SIGN_MESSAGE: 'signMessage',
|
|
14
|
+
SIGN_TYPED_DATA: 'signTypedData',
|
|
15
|
+
SEND_WALLET_TRANSACTION: 'sendWalletTransaction',
|
|
16
|
+
} as const
|
|
17
|
+
|
|
18
|
+
export type LoginMethod = 'google' | 'apple' | 'email' | 'passkey' | 'mnemonic'
|
|
19
|
+
|
|
20
|
+
export interface GuardConfig {
|
|
21
|
+
url: string
|
|
22
|
+
address: Address.Address
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// --- Payloads for Transport ---
|
|
26
|
+
|
|
27
|
+
export interface CreateNewSessionPayload {
|
|
28
|
+
sessionAddress: Address.Address
|
|
29
|
+
origin: string
|
|
30
|
+
permissions?: Signers.Session.ExplicitParams
|
|
31
|
+
includeImplicitSession?: boolean
|
|
32
|
+
preferredLoginMethod?: LoginMethod
|
|
33
|
+
email?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AddExplicitSessionPayload {
|
|
37
|
+
sessionAddress: Address.Address
|
|
38
|
+
permissions: Signers.Session.ExplicitParams
|
|
39
|
+
preferredLoginMethod?: LoginMethod
|
|
40
|
+
email?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ModifySessionPayload {
|
|
44
|
+
walletAddress: Address.Address
|
|
45
|
+
sessionAddress: Address.Address
|
|
46
|
+
permissions: Signers.Session.ExplicitParams
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface SignMessagePayload {
|
|
50
|
+
address: Address.Address
|
|
51
|
+
message: string
|
|
52
|
+
chainId: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface SignTypedDataPayload {
|
|
56
|
+
address: Address.Address
|
|
57
|
+
typedData: TypedData
|
|
58
|
+
chainId: number
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type TransactionRequest = {
|
|
62
|
+
to: Address.Address
|
|
63
|
+
value?: bigint
|
|
64
|
+
data?: Hex.Hex
|
|
65
|
+
gasLimit?: bigint
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SendWalletTransactionPayload {
|
|
69
|
+
address: Address.Address
|
|
70
|
+
transactionRequest: TransactionRequest
|
|
71
|
+
chainId: number
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ConnectSuccessResponsePayload {
|
|
75
|
+
walletAddress: string
|
|
76
|
+
attestation?: Attestation.Attestation
|
|
77
|
+
signature?: Hex.Hex
|
|
78
|
+
userEmail?: string
|
|
79
|
+
loginMethod?: LoginMethod
|
|
80
|
+
guard?: GuardConfig
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ModifySessionSuccessResponsePayload {
|
|
84
|
+
walletAddress: string
|
|
85
|
+
sessionAddress: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface SignatureSuccessResponse {
|
|
89
|
+
signature: Hex.Hex
|
|
90
|
+
walletAddress: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface SendWalletTransactionSuccessResponse {
|
|
94
|
+
transactionHash: Hex.Hex
|
|
95
|
+
walletAddress: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type WalletActionResponse = SignatureSuccessResponse | SendWalletTransactionSuccessResponse
|
|
99
|
+
|
|
100
|
+
export interface ExplicitSessionResponsePayload {
|
|
101
|
+
walletAddress: string
|
|
102
|
+
sessionAddress: string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// --- Dapp-facing Types ---
|
|
106
|
+
|
|
107
|
+
export type RandomPrivateKeyFn = () => Hex.Hex | Promise<Hex.Hex>
|
|
108
|
+
|
|
109
|
+
type RequiredKeys = 'to' | 'data' | 'value'
|
|
110
|
+
|
|
111
|
+
export type Transaction =
|
|
112
|
+
// Required properties from Payload.Call
|
|
113
|
+
Pick<Payload.Call, RequiredKeys> &
|
|
114
|
+
// All other properties from Payload.Call, but optional
|
|
115
|
+
Partial<Omit<Payload.Call, RequiredKeys>>
|
|
116
|
+
|
|
117
|
+
export type Session = {
|
|
118
|
+
address: Address.Address
|
|
119
|
+
isImplicit: boolean
|
|
120
|
+
permissions?: Signers.Session.ExplicitParams
|
|
121
|
+
chainId?: number
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// --- Event Types ---
|
|
125
|
+
|
|
126
|
+
export type ChainSessionManagerEvent = 'sessionsUpdated' | 'explicitSessionResponse'
|
|
127
|
+
|
|
128
|
+
export type ExplicitSessionEventListener = (data: {
|
|
129
|
+
action: (typeof RequestActionType)['ADD_EXPLICIT_SESSION' | 'MODIFY_EXPLICIT_SESSION']
|
|
130
|
+
response?: ExplicitSessionResponsePayload
|
|
131
|
+
error?: any
|
|
132
|
+
}) => void
|
|
133
|
+
|
|
134
|
+
// A generic listener for events from the DappClient
|
|
135
|
+
export type DappClientEventListener = (data?: any) => void
|
|
136
|
+
|
|
137
|
+
export type DappClientWalletActionEventListener = (data: {
|
|
138
|
+
action: (typeof RequestActionType)['SIGN_MESSAGE' | 'SIGN_TYPED_DATA' | 'SEND_WALLET_TRANSACTION']
|
|
139
|
+
response?: WalletActionResponse
|
|
140
|
+
error?: any
|
|
141
|
+
chainId: number
|
|
142
|
+
}) => void
|
|
143
|
+
|
|
144
|
+
export type DappClientExplicitSessionEventListener = (data: {
|
|
145
|
+
action: (typeof RequestActionType)['ADD_EXPLICIT_SESSION' | 'MODIFY_EXPLICIT_SESSION']
|
|
146
|
+
response?: ExplicitSessionResponsePayload
|
|
147
|
+
error?: any
|
|
148
|
+
chainId: number
|
|
149
|
+
}) => void
|
|
150
|
+
|
|
151
|
+
// --- DappTransport Types ---
|
|
152
|
+
|
|
153
|
+
export interface SequenceSessionStorage {
|
|
154
|
+
getItem(key: string): string | null | Promise<string | null>
|
|
155
|
+
setItem(key: string, value: string): void | Promise<void>
|
|
156
|
+
removeItem(key: string): void | Promise<void>
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export enum MessageType {
|
|
160
|
+
WALLET_OPENED = 'WALLET_OPENED',
|
|
161
|
+
INIT = 'INIT',
|
|
162
|
+
REQUEST = 'REQUEST',
|
|
163
|
+
RESPONSE = 'RESPONSE',
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export enum TransportMode {
|
|
167
|
+
POPUP = 'popup',
|
|
168
|
+
REDIRECT = 'redirect',
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface PopupModeOptions {
|
|
172
|
+
requestTimeoutMs?: number
|
|
173
|
+
handshakeTimeoutMs?: number
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface TransportMessage<T = any> {
|
|
177
|
+
id: string
|
|
178
|
+
type: MessageType
|
|
179
|
+
sessionId?: string
|
|
180
|
+
action?: string
|
|
181
|
+
payload?: T
|
|
182
|
+
error?: any
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface BaseRequest {
|
|
186
|
+
type: string
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface MessageSignatureRequest extends BaseRequest {
|
|
190
|
+
type: 'message_signature'
|
|
191
|
+
message: string
|
|
192
|
+
address: Address.Address
|
|
193
|
+
chainId: number
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface TypedDataSignatureRequest extends BaseRequest {
|
|
197
|
+
type: 'typed_data_signature'
|
|
198
|
+
typedData: unknown
|
|
199
|
+
address: Address.Address
|
|
200
|
+
chainId: number
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export const WalletSize = {
|
|
204
|
+
width: 380,
|
|
205
|
+
height: 600,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface PendingRequest {
|
|
209
|
+
resolve: (payload: any) => void
|
|
210
|
+
reject: (error: any) => void
|
|
211
|
+
timer: number
|
|
212
|
+
action: string
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface SendRequestOptions {
|
|
216
|
+
timeout?: number
|
|
217
|
+
path?: string
|
|
218
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const CACHE_DB_NAME = 'sequence-cache'
|
|
2
|
+
export const NODES_URL = 'https://nodes.sequence.app/{network}'
|
|
3
|
+
export const RELAYER_URL = 'https://dev-{network}-relayer.sequence.app'
|
|
4
|
+
export const KEYMACHINE_URL = 'https://v3-keymachine.sequence-dev.app'
|
|
5
|
+
export const VALUE_FORWARDER_ADDRESS = '0xABAAd93EeE2a569cF0632f39B10A9f5D734777ca'
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export class InitializationError extends Error {
|
|
2
|
+
constructor(message: string) {
|
|
3
|
+
super(message)
|
|
4
|
+
this.name = 'InitializationError'
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class SigningError extends Error {
|
|
9
|
+
constructor(message: string) {
|
|
10
|
+
super(message)
|
|
11
|
+
this.name = 'SigningError'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class TransactionError extends Error {
|
|
16
|
+
constructor(message: string) {
|
|
17
|
+
super(message)
|
|
18
|
+
this.name = 'TransactionError'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ModifyExplicitSessionError extends Error {
|
|
23
|
+
constructor(message: string) {
|
|
24
|
+
super(message)
|
|
25
|
+
this.name = 'ModifyExplicitSessionError'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class ConnectionError extends Error {
|
|
30
|
+
constructor(message: string) {
|
|
31
|
+
super(message)
|
|
32
|
+
this.name = 'ConnectionError'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class AddExplicitSessionError extends Error {
|
|
37
|
+
constructor(message: string) {
|
|
38
|
+
super(message)
|
|
39
|
+
this.name = 'AddExplicitSessionError'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class FeeOptionError extends Error {
|
|
44
|
+
constructor(message: string) {
|
|
45
|
+
super(message)
|
|
46
|
+
this.name = 'FeeOptionError'
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class WalletRedirectError extends Error {
|
|
51
|
+
constructor(message: string) {
|
|
52
|
+
super(message)
|
|
53
|
+
this.name = 'WalletRedirectError'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class SessionConfigError extends Error {
|
|
58
|
+
constructor(message: string) {
|
|
59
|
+
super(message)
|
|
60
|
+
this.name = 'SessionConfigError'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { Network } from '@0xsequence/wallet-primitives'
|
|
3
|
+
import { Bytes, Hex } from 'ox'
|
|
4
|
+
import { NODES_URL, RELAYER_URL } from './constants.js'
|
|
5
|
+
|
|
6
|
+
type JsonReplacer = (key: string, value: any) => any
|
|
7
|
+
type JsonReviver = (key: string, value: any) => any
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a single JSON replacer by chaining multiple replacers.
|
|
11
|
+
* The first replacer to transform a value wins.
|
|
12
|
+
*/
|
|
13
|
+
function chainReplacers(replacers: JsonReplacer[]): JsonReplacer {
|
|
14
|
+
return function (key: string, value: any): any {
|
|
15
|
+
for (const replacer of replacers) {
|
|
16
|
+
const replacedValue = replacer(key, value)
|
|
17
|
+
if (replacedValue !== value) {
|
|
18
|
+
return replacedValue
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return value
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a single JSON reviver by chaining multiple revivers.
|
|
27
|
+
* The output of one reviver becomes the input for the next.
|
|
28
|
+
*/
|
|
29
|
+
function chainRevivers(revivers: JsonReviver[]): JsonReviver {
|
|
30
|
+
return function (key: string, value: any): any {
|
|
31
|
+
let currentValue = value
|
|
32
|
+
for (const reviver of revivers) {
|
|
33
|
+
currentValue = reviver(key, currentValue)
|
|
34
|
+
}
|
|
35
|
+
return currentValue
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A JSON replacer that serializes BigInt values into a structured object.
|
|
41
|
+
*/
|
|
42
|
+
const bigIntReplacer: JsonReplacer = (_key, value) => {
|
|
43
|
+
if (typeof value === 'bigint') {
|
|
44
|
+
return {
|
|
45
|
+
_isBigInt: true,
|
|
46
|
+
data: value.toString(),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return value
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A JSON replacer that serializes Uint8Array values into a structured object.
|
|
54
|
+
*/
|
|
55
|
+
const uint8ArrayReplacer: JsonReplacer = (_key, value) => {
|
|
56
|
+
if (value instanceof Uint8Array) {
|
|
57
|
+
return {
|
|
58
|
+
_isUint8Array: true,
|
|
59
|
+
data: Hex.from(value),
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return value
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A JSON reviver that deserializes a structured object back into a BigInt.
|
|
67
|
+
*/
|
|
68
|
+
const bigIntReviver: JsonReviver = (key, value) => {
|
|
69
|
+
if (value !== null && typeof value === 'object' && value._isBigInt === true && typeof value.data === 'string') {
|
|
70
|
+
try {
|
|
71
|
+
return BigInt(value.data)
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.error(`Failed to revive BigInt for key "${key}":`, e)
|
|
74
|
+
return value // Return original object if revival fails
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return value
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A JSON reviver that deserializes a structured object back into a Uint8Array.
|
|
82
|
+
*/
|
|
83
|
+
const uint8ArrayReviver: JsonReviver = (key, value) => {
|
|
84
|
+
if (value !== null && typeof value === 'object' && value._isUint8Array === true && typeof value.data === 'string') {
|
|
85
|
+
try {
|
|
86
|
+
return Bytes.from(value.data)
|
|
87
|
+
} catch (e) {
|
|
88
|
+
console.error(`Failed to revive Uint8Array for key "${key}":`, e)
|
|
89
|
+
return value // Return original object if revival fails
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return value
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const jsonReplacers = chainReplacers([bigIntReplacer, uint8ArrayReplacer])
|
|
96
|
+
|
|
97
|
+
export const jsonRevivers = chainRevivers([bigIntReviver, uint8ArrayReviver])
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Apply a template to a string.
|
|
101
|
+
*
|
|
102
|
+
* Example:
|
|
103
|
+
* applyTemplate('https://v3-{network}-relayer.sequence.app', { network: 'arbitrum' })
|
|
104
|
+
* returns 'https://v3-arbitrum-relayer.sequence.app'
|
|
105
|
+
*
|
|
106
|
+
* @param template - The template to apply.
|
|
107
|
+
* @param values - The values to apply to the template.
|
|
108
|
+
* @returns The template with the values applied.
|
|
109
|
+
*/
|
|
110
|
+
function applyTemplate(template: string, values: Record<string, string>) {
|
|
111
|
+
return template.replace(/{(.*?)}/g, (_, key) => {
|
|
112
|
+
const value = values[key]
|
|
113
|
+
if (value === undefined) {
|
|
114
|
+
throw new Error(`Missing template value for ${template}: ${key}`)
|
|
115
|
+
}
|
|
116
|
+
return value
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const getNetwork = (chainId: Network.ChainId | bigint | number) => {
|
|
121
|
+
const network = Network.getNetworkFromChainId(chainId)
|
|
122
|
+
|
|
123
|
+
if (!network) {
|
|
124
|
+
throw new Error(`Network with chainId ${chainId} not found`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return network
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const getRpcUrl = (chainId: Network.ChainId | bigint | number, nodesUrl: string, projectAccessKey: string) => {
|
|
131
|
+
const network = getNetwork(chainId)
|
|
132
|
+
|
|
133
|
+
let url = applyTemplate(nodesUrl, { network: network.name })
|
|
134
|
+
|
|
135
|
+
if (nodesUrl.includes('sequence')) {
|
|
136
|
+
url = `${url}/${projectAccessKey}`
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return url
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const getRelayerUrl = (chainId: Network.ChainId | bigint | number, relayerUrl: string) => {
|
|
143
|
+
const network = getNetwork(chainId)
|
|
144
|
+
|
|
145
|
+
const url = applyTemplate(relayerUrl, { network: network.name })
|
|
146
|
+
|
|
147
|
+
return url
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const getExplorerUrl = (chainId: Network.ChainId | bigint | number, txHash: string) => {
|
|
151
|
+
const network = getNetwork(chainId)
|
|
152
|
+
const explorerUrl = network.blockExplorer?.url
|
|
153
|
+
if (!explorerUrl) {
|
|
154
|
+
throw new Error(`Explorer URL not found for chainId ${chainId}`)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return `${explorerUrl}/tx/${txHash}`
|
|
158
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { Attestation } from '@0xsequence/wallet-primitives'
|
|
2
|
+
import { Address, Hex } from 'ox'
|
|
3
|
+
import { jsonReplacers, jsonRevivers } from './index.js'
|
|
4
|
+
import {
|
|
5
|
+
AddExplicitSessionPayload,
|
|
6
|
+
CreateNewSessionPayload,
|
|
7
|
+
ModifySessionPayload,
|
|
8
|
+
LoginMethod,
|
|
9
|
+
SignMessagePayload,
|
|
10
|
+
SignTypedDataPayload,
|
|
11
|
+
GuardConfig,
|
|
12
|
+
SendWalletTransactionPayload,
|
|
13
|
+
} from '../types/index.js'
|
|
14
|
+
|
|
15
|
+
export interface ExplicitSessionData {
|
|
16
|
+
pk: Hex.Hex
|
|
17
|
+
walletAddress: Address.Address
|
|
18
|
+
chainId: number
|
|
19
|
+
loginMethod?: LoginMethod
|
|
20
|
+
userEmail?: string
|
|
21
|
+
guard?: GuardConfig
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ImplicitSessionData {
|
|
25
|
+
pk: Hex.Hex
|
|
26
|
+
walletAddress: Address.Address
|
|
27
|
+
attestation: Attestation.Attestation
|
|
28
|
+
identitySignature: Hex.Hex
|
|
29
|
+
chainId: number
|
|
30
|
+
loginMethod?: LoginMethod
|
|
31
|
+
userEmail?: string
|
|
32
|
+
guard?: GuardConfig
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type PendingPayload =
|
|
36
|
+
| CreateNewSessionPayload
|
|
37
|
+
| AddExplicitSessionPayload
|
|
38
|
+
| ModifySessionPayload
|
|
39
|
+
| SignMessagePayload
|
|
40
|
+
| SignTypedDataPayload
|
|
41
|
+
| SendWalletTransactionPayload
|
|
42
|
+
|
|
43
|
+
export interface PendingRequestContext {
|
|
44
|
+
chainId: number
|
|
45
|
+
action: string
|
|
46
|
+
payload: PendingPayload
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface SequenceStorage {
|
|
50
|
+
setPendingRedirectRequest(isPending: boolean): Promise<void>
|
|
51
|
+
isRedirectRequestPending(): Promise<boolean>
|
|
52
|
+
|
|
53
|
+
saveTempSessionPk(pk: Hex.Hex): Promise<void>
|
|
54
|
+
getAndClearTempSessionPk(): Promise<Hex.Hex | null>
|
|
55
|
+
|
|
56
|
+
savePendingRequest(context: PendingRequestContext): Promise<void>
|
|
57
|
+
getAndClearPendingRequest(): Promise<PendingRequestContext | null>
|
|
58
|
+
peekPendingRequest(): Promise<PendingRequestContext | null>
|
|
59
|
+
|
|
60
|
+
saveExplicitSession(sessionData: ExplicitSessionData): Promise<void>
|
|
61
|
+
getExplicitSessions(): Promise<ExplicitSessionData[]>
|
|
62
|
+
clearExplicitSessions(): Promise<void>
|
|
63
|
+
|
|
64
|
+
saveImplicitSession(sessionData: ImplicitSessionData): Promise<void>
|
|
65
|
+
getImplicitSession(): Promise<ImplicitSessionData | null>
|
|
66
|
+
clearImplicitSession(): Promise<void>
|
|
67
|
+
|
|
68
|
+
clearAllData(): Promise<void>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const DB_NAME = 'SequenceDappStorage'
|
|
72
|
+
const DB_VERSION = 1
|
|
73
|
+
const STORE_NAME = 'userKeys'
|
|
74
|
+
const IMPLICIT_SESSIONS_IDB_KEY = 'SequenceImplicitSession'
|
|
75
|
+
const EXPLICIT_SESSIONS_IDB_KEY = 'SequenceExplicitSession'
|
|
76
|
+
|
|
77
|
+
const PENDING_REDIRECT_REQUEST_KEY = 'SequencePendingRedirect'
|
|
78
|
+
const TEMP_SESSION_PK_KEY = 'SequencePendingTempSessionPk'
|
|
79
|
+
const PENDING_REQUEST_CONTEXT_KEY = 'SequencePendingRequestContext'
|
|
80
|
+
|
|
81
|
+
export class WebStorage implements SequenceStorage {
|
|
82
|
+
private openDB(): Promise<IDBDatabase> {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION)
|
|
85
|
+
request.onerror = (event) => reject(`IndexedDB error: ${(event.target as IDBRequest).error}`)
|
|
86
|
+
request.onsuccess = (event) => resolve((event.target as IDBRequest).result as IDBDatabase)
|
|
87
|
+
request.onupgradeneeded = (event) => {
|
|
88
|
+
const db = (event.target as IDBRequest).result as IDBDatabase
|
|
89
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
90
|
+
db.createObjectStore(STORE_NAME)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private async getIDBItem<T>(key: IDBValidKey): Promise<T | undefined> {
|
|
97
|
+
const db = await this.openDB()
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const request = db.transaction(STORE_NAME, 'readonly').objectStore(STORE_NAME).get(key)
|
|
100
|
+
request.onerror = (event) => reject(`Failed to retrieve item: ${(event.target as IDBRequest).error}`)
|
|
101
|
+
request.onsuccess = (event) => resolve((event.target as IDBRequest).result as T | undefined)
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private async setIDBItem(key: IDBValidKey, value: unknown): Promise<void> {
|
|
106
|
+
const db = await this.openDB()
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const request = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME).put(value, key)
|
|
109
|
+
request.onerror = (event) => reject(`Failed to save item: ${(event.target as IDBRequest).error}`)
|
|
110
|
+
request.onsuccess = () => resolve()
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private async deleteIDBItem(key: IDBValidKey): Promise<void> {
|
|
115
|
+
const db = await this.openDB()
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const request = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME).delete(key)
|
|
118
|
+
request.onerror = (event) => reject(`Failed to delete item: ${(event.target as IDBRequest).error}`)
|
|
119
|
+
request.onsuccess = () => resolve()
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async setPendingRedirectRequest(isPending: boolean): Promise<void> {
|
|
124
|
+
try {
|
|
125
|
+
if (isPending) {
|
|
126
|
+
sessionStorage.setItem(PENDING_REDIRECT_REQUEST_KEY, 'true')
|
|
127
|
+
} else {
|
|
128
|
+
sessionStorage.removeItem(PENDING_REDIRECT_REQUEST_KEY)
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('Failed to set pending redirect flag:', error)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async isRedirectRequestPending(): Promise<boolean> {
|
|
136
|
+
try {
|
|
137
|
+
return sessionStorage.getItem(PENDING_REDIRECT_REQUEST_KEY) === 'true'
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('Failed to check pending redirect flag:', error)
|
|
140
|
+
return false
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async saveTempSessionPk(pk: Hex.Hex): Promise<void> {
|
|
145
|
+
try {
|
|
146
|
+
sessionStorage.setItem(TEMP_SESSION_PK_KEY, pk)
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('Failed to save temp session PK:', error)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async getAndClearTempSessionPk(): Promise<Hex.Hex | null> {
|
|
153
|
+
try {
|
|
154
|
+
const pk = sessionStorage.getItem(TEMP_SESSION_PK_KEY)
|
|
155
|
+
sessionStorage.removeItem(TEMP_SESSION_PK_KEY)
|
|
156
|
+
return pk as Hex.Hex | null
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error('Failed to retrieve temp session PK:', error)
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async savePendingRequest(context: PendingRequestContext): Promise<void> {
|
|
164
|
+
try {
|
|
165
|
+
sessionStorage.setItem(PENDING_REQUEST_CONTEXT_KEY, JSON.stringify(context, jsonReplacers))
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('Failed to save pending request context:', error)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async getAndClearPendingRequest(): Promise<PendingRequestContext | null> {
|
|
172
|
+
try {
|
|
173
|
+
const context = sessionStorage.getItem(PENDING_REQUEST_CONTEXT_KEY)
|
|
174
|
+
if (!context) return null
|
|
175
|
+
sessionStorage.removeItem(PENDING_REQUEST_CONTEXT_KEY)
|
|
176
|
+
return JSON.parse(context, jsonRevivers)
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error('Failed to retrieve pending request context:', error)
|
|
179
|
+
return null
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async peekPendingRequest(): Promise<PendingRequestContext | null> {
|
|
184
|
+
try {
|
|
185
|
+
const context = sessionStorage.getItem(PENDING_REQUEST_CONTEXT_KEY)
|
|
186
|
+
if (!context) return null
|
|
187
|
+
return JSON.parse(context, jsonRevivers)
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error('Failed to peek at pending request context:', error)
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async saveExplicitSession(sessionData: ExplicitSessionData): Promise<void> {
|
|
195
|
+
try {
|
|
196
|
+
const existingSessions = (await this.getExplicitSessions()).filter(
|
|
197
|
+
(s) =>
|
|
198
|
+
!(
|
|
199
|
+
Address.isEqual(s.walletAddress, sessionData.walletAddress) &&
|
|
200
|
+
s.pk === sessionData.pk &&
|
|
201
|
+
s.chainId === sessionData.chainId
|
|
202
|
+
),
|
|
203
|
+
)
|
|
204
|
+
await this.setIDBItem(EXPLICIT_SESSIONS_IDB_KEY, [...existingSessions, sessionData])
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error('Failed to save explicit session:', error)
|
|
207
|
+
throw error
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async getExplicitSessions(): Promise<ExplicitSessionData[]> {
|
|
212
|
+
try {
|
|
213
|
+
const sessions = await this.getIDBItem<ExplicitSessionData[]>(EXPLICIT_SESSIONS_IDB_KEY)
|
|
214
|
+
return sessions && Array.isArray(sessions) ? sessions : []
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error('Failed to retrieve explicit sessions:', error)
|
|
217
|
+
return []
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async clearExplicitSessions(): Promise<void> {
|
|
222
|
+
try {
|
|
223
|
+
await this.deleteIDBItem(EXPLICIT_SESSIONS_IDB_KEY)
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error('Failed to clear explicit sessions:', error)
|
|
226
|
+
throw error
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async saveImplicitSession(sessionData: ImplicitSessionData): Promise<void> {
|
|
231
|
+
try {
|
|
232
|
+
await this.setIDBItem(IMPLICIT_SESSIONS_IDB_KEY, sessionData)
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error('Failed to save implicit session:', error)
|
|
235
|
+
throw error
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async getImplicitSession(): Promise<ImplicitSessionData | null> {
|
|
240
|
+
try {
|
|
241
|
+
return (await this.getIDBItem<ImplicitSessionData>(IMPLICIT_SESSIONS_IDB_KEY)) ?? null
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error('Failed to retrieve implicit session:', error)
|
|
244
|
+
return null
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async clearImplicitSession(): Promise<void> {
|
|
249
|
+
try {
|
|
250
|
+
await this.deleteIDBItem(IMPLICIT_SESSIONS_IDB_KEY)
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.error('Failed to clear implicit session:', error)
|
|
253
|
+
throw error
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async clearAllData(): Promise<void> {
|
|
258
|
+
try {
|
|
259
|
+
// Clear all session storage items
|
|
260
|
+
sessionStorage.removeItem(PENDING_REDIRECT_REQUEST_KEY)
|
|
261
|
+
sessionStorage.removeItem(TEMP_SESSION_PK_KEY)
|
|
262
|
+
sessionStorage.removeItem(PENDING_REQUEST_CONTEXT_KEY)
|
|
263
|
+
|
|
264
|
+
// Clear all IndexedDB items
|
|
265
|
+
await this.clearExplicitSessions()
|
|
266
|
+
await this.clearImplicitSession()
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error('Failed to clear all data:', error)
|
|
269
|
+
throw error
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|