@0xsequence/wallet-wdk 3.0.0-beta.8 → 3.0.0
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 +2 -2
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +182 -0
- package/dist/dbs/auth-commitments.d.ts.map +1 -1
- package/dist/dbs/auth-keys.d.ts +3 -1
- package/dist/dbs/auth-keys.d.ts.map +1 -1
- package/dist/dbs/auth-keys.js +16 -4
- package/dist/dbs/messages.d.ts.map +1 -1
- package/dist/dbs/passkey-credentials.d.ts.map +1 -1
- package/dist/dbs/recovery.d.ts.map +1 -1
- package/dist/dbs/signatures.d.ts.map +1 -1
- package/dist/dbs/transactions.d.ts.map +1 -1
- package/dist/dbs/wallets.d.ts.map +1 -1
- package/dist/env.d.ts +22 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +30 -0
- package/dist/identity/signer.d.ts +5 -4
- package/dist/identity/signer.d.ts.map +1 -1
- package/dist/identity/signer.js +11 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/sequence/cron.d.ts +3 -0
- package/dist/sequence/cron.d.ts.map +1 -1
- package/dist/sequence/cron.js +72 -39
- package/dist/sequence/handlers/authcode-pkce.d.ts +2 -1
- package/dist/sequence/handlers/authcode-pkce.d.ts.map +1 -1
- package/dist/sequence/handlers/authcode-pkce.js +4 -4
- package/dist/sequence/handlers/authcode.d.ts +4 -1
- package/dist/sequence/handlers/authcode.d.ts.map +1 -1
- package/dist/sequence/handlers/authcode.js +23 -6
- package/dist/sequence/handlers/devices.d.ts +1 -1
- package/dist/sequence/handlers/devices.d.ts.map +1 -1
- package/dist/sequence/handlers/devices.js +1 -1
- package/dist/sequence/handlers/guard.d.ts +1 -1
- package/dist/sequence/handlers/guard.d.ts.map +1 -1
- package/dist/sequence/handlers/guard.js +22 -19
- package/dist/sequence/handlers/identity.d.ts +3 -1
- package/dist/sequence/handlers/identity.d.ts.map +1 -1
- package/dist/sequence/handlers/identity.js +14 -7
- package/dist/sequence/handlers/mnemonic.d.ts.map +1 -1
- package/dist/sequence/handlers/mnemonic.js +21 -18
- package/dist/sequence/handlers/otp.d.ts +2 -1
- package/dist/sequence/handlers/otp.d.ts.map +1 -1
- package/dist/sequence/handlers/otp.js +4 -3
- package/dist/sequence/handlers/passkeys.d.ts +6 -4
- package/dist/sequence/handlers/passkeys.d.ts.map +1 -1
- package/dist/sequence/handlers/passkeys.js +8 -5
- package/dist/sequence/handlers/recovery.js +1 -1
- package/dist/sequence/index.d.ts +2 -0
- package/dist/sequence/index.d.ts.map +1 -1
- package/dist/sequence/index.js +1 -0
- package/dist/sequence/manager.d.ts +67 -55
- package/dist/sequence/manager.d.ts.map +1 -1
- package/dist/sequence/manager.js +77 -17
- package/dist/sequence/messages.js +1 -1
- package/dist/sequence/passkeys-provider.d.ts +24 -0
- package/dist/sequence/passkeys-provider.d.ts.map +1 -0
- package/dist/sequence/passkeys-provider.js +15 -0
- package/dist/sequence/recovery.d.ts +2 -0
- package/dist/sequence/recovery.d.ts.map +1 -1
- package/dist/sequence/recovery.js +100 -34
- package/dist/sequence/signers.d.ts.map +1 -1
- package/dist/sequence/signers.js +3 -1
- package/dist/sequence/transactions.d.ts.map +1 -1
- package/dist/sequence/transactions.js +5 -2
- package/dist/sequence/wallets.d.ts +2 -1
- package/dist/sequence/wallets.d.ts.map +1 -1
- package/dist/sequence/wallets.js +32 -22
- package/eslint.config.js +12 -0
- package/package.json +16 -14
- package/src/dbs/auth-commitments.ts +1 -1
- package/src/dbs/auth-keys.ts +20 -6
- package/src/dbs/messages.ts +1 -1
- package/src/dbs/passkey-credentials.ts +1 -1
- package/src/dbs/recovery.ts +1 -1
- package/src/dbs/signatures.ts +1 -1
- package/src/dbs/transactions.ts +1 -1
- package/src/dbs/wallets.ts +1 -1
- package/src/env.ts +58 -0
- package/src/identity/signer.ts +13 -7
- package/src/index.ts +1 -0
- package/src/sequence/cron.ts +75 -42
- package/src/sequence/handlers/authcode-pkce.ts +6 -4
- package/src/sequence/handlers/authcode.ts +26 -5
- package/src/sequence/handlers/devices.ts +1 -1
- package/src/sequence/handlers/guard.ts +6 -4
- package/src/sequence/handlers/identity.ts +18 -8
- package/src/sequence/handlers/mnemonic.ts +5 -3
- package/src/sequence/handlers/otp.ts +5 -3
- package/src/sequence/handlers/passkeys.ts +13 -13
- package/src/sequence/handlers/recovery.ts +1 -1
- package/src/sequence/index.ts +2 -0
- package/src/sequence/manager.ts +168 -14
- package/src/sequence/messages.ts +1 -1
- package/src/sequence/passkeys-provider.ts +55 -0
- package/src/sequence/recovery.ts +165 -56
- package/src/sequence/signers.ts +3 -1
- package/src/sequence/transactions.ts +6 -2
- package/src/sequence/wallets.ts +39 -25
- package/test/authcode-pkce.test.ts +2 -3
- package/test/authcode.test.ts +6 -8
- package/test/constants.ts +4 -2
- package/test/guard.test.ts +5 -5
- package/test/identity-signer.test.ts +1 -1
- package/test/otp.test.ts +1 -1
- package/test/passkeys.test.ts +1 -1
- package/test/recovery.test.ts +3 -3
- package/test/sessions.test.ts +1 -1
- package/test/{test-ssr-safety.mjs → test-ssr-safety.js} +143 -137
- package/test/transactions.test.ts +3 -3
- package/test/wallets.test.ts +5 -5
package/src/env.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { CoreEnv } from '@0xsequence/wallet-core'
|
|
2
|
+
import { resolveCoreEnv } from '@0xsequence/wallet-core'
|
|
3
|
+
|
|
4
|
+
export type TimersLike = {
|
|
5
|
+
setTimeout: typeof setTimeout
|
|
6
|
+
clearTimeout: typeof clearTimeout
|
|
7
|
+
setInterval: typeof setInterval
|
|
8
|
+
clearInterval: typeof clearInterval
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type LockManagerLike = {
|
|
12
|
+
request: (name: string, callback: (lock: Lock | null) => Promise<void> | void) => Promise<void>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type NavigationLike = {
|
|
16
|
+
getPathname: () => string
|
|
17
|
+
redirect: (url: string) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type WdkEnv = CoreEnv & {
|
|
21
|
+
timers?: TimersLike
|
|
22
|
+
locks?: LockManagerLike
|
|
23
|
+
navigation?: NavigationLike
|
|
24
|
+
urlSearchParams?: typeof URLSearchParams
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveWdkEnv(env?: WdkEnv): WdkEnv {
|
|
28
|
+
const core = resolveCoreEnv(env)
|
|
29
|
+
const globalObj = globalThis as any
|
|
30
|
+
const windowObj = typeof window !== 'undefined' ? window : (globalObj.window ?? {})
|
|
31
|
+
const location = windowObj.location ?? globalObj.location
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
...core,
|
|
35
|
+
timers:
|
|
36
|
+
env?.timers ??
|
|
37
|
+
(typeof globalObj.setTimeout === 'function'
|
|
38
|
+
? {
|
|
39
|
+
setTimeout: globalObj.setTimeout.bind(globalObj),
|
|
40
|
+
clearTimeout: globalObj.clearTimeout.bind(globalObj),
|
|
41
|
+
setInterval: globalObj.setInterval.bind(globalObj),
|
|
42
|
+
clearInterval: globalObj.clearInterval.bind(globalObj),
|
|
43
|
+
}
|
|
44
|
+
: undefined),
|
|
45
|
+
locks: env?.locks ?? globalObj.navigator?.locks ?? windowObj.navigator?.locks,
|
|
46
|
+
navigation:
|
|
47
|
+
env?.navigation ??
|
|
48
|
+
(location
|
|
49
|
+
? {
|
|
50
|
+
getPathname: () => location.pathname,
|
|
51
|
+
redirect: (url: string) => {
|
|
52
|
+
location.href = url
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
: undefined),
|
|
56
|
+
urlSearchParams: env?.urlSearchParams ?? globalObj.URLSearchParams ?? windowObj.URLSearchParams,
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/identity/signer.ts
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import { Address, Signature, Hex, Bytes
|
|
2
|
-
import { Signers, State } from '@0xsequence/wallet-core'
|
|
3
|
-
import { IdentityInstrument
|
|
1
|
+
import { Address, Signature, Hex, Bytes } from 'ox'
|
|
2
|
+
import { Signers, State, type CryptoLike } from '@0xsequence/wallet-core'
|
|
3
|
+
import { IdentityInstrument } from '@0xsequence/identity-instrument'
|
|
4
4
|
import { AuthKey } from '../dbs/auth-keys.js'
|
|
5
5
|
import { Payload, Signature as SequenceSignature } from '@0xsequence/wallet-primitives'
|
|
6
6
|
import * as Identity from '@0xsequence/identity-instrument'
|
|
7
7
|
|
|
8
|
-
export function toIdentityAuthKey(authKey: AuthKey): Identity.AuthKey {
|
|
8
|
+
export function toIdentityAuthKey(authKey: AuthKey, crypto?: CryptoLike): Identity.AuthKey {
|
|
9
|
+
const globalObj = globalThis as any
|
|
10
|
+
const resolvedCrypto = crypto ?? globalObj.window?.crypto ?? globalObj.crypto
|
|
11
|
+
if (!resolvedCrypto?.subtle) {
|
|
12
|
+
throw new Error('crypto.subtle is not available')
|
|
13
|
+
}
|
|
9
14
|
return {
|
|
10
15
|
address: authKey.address,
|
|
11
16
|
keyType: Identity.KeyType.WebCrypto_Secp256r1,
|
|
12
17
|
signer: authKey.identitySigner,
|
|
13
18
|
async sign(digest: Bytes.Bytes) {
|
|
14
|
-
const authKeySignature = await
|
|
19
|
+
const authKeySignature = await resolvedCrypto.subtle.sign(
|
|
15
20
|
{
|
|
16
21
|
name: 'ECDSA',
|
|
17
22
|
hash: 'SHA-256',
|
|
@@ -28,6 +33,7 @@ export class IdentitySigner implements Signers.Signer {
|
|
|
28
33
|
constructor(
|
|
29
34
|
readonly identityInstrument: IdentityInstrument,
|
|
30
35
|
readonly authKey: AuthKey,
|
|
36
|
+
private readonly crypto?: CryptoLike,
|
|
31
37
|
) {}
|
|
32
38
|
|
|
33
39
|
get address(): Address.Address {
|
|
@@ -47,7 +53,7 @@ export class IdentitySigner implements Signers.Signer {
|
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
async signDigest(digest: Bytes.Bytes): Promise<SequenceSignature.SignatureOfSignerLeafHash> {
|
|
50
|
-
const sigHex = await this.identityInstrument.sign(toIdentityAuthKey(this.authKey), digest)
|
|
56
|
+
const sigHex = await this.identityInstrument.sign(toIdentityAuthKey(this.authKey, this.crypto), digest)
|
|
51
57
|
const sig = Signature.fromHex(sigHex)
|
|
52
58
|
return {
|
|
53
59
|
type: 'hash',
|
|
@@ -55,7 +61,7 @@ export class IdentitySigner implements Signers.Signer {
|
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
async witness(stateWriter: State.Writer, wallet: Address.Address, extra?:
|
|
64
|
+
async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: object): Promise<void> {
|
|
59
65
|
const payload = Payload.fromMessage(
|
|
60
66
|
Hex.fromString(
|
|
61
67
|
JSON.stringify({
|
package/src/index.ts
CHANGED
package/src/sequence/cron.ts
CHANGED
|
@@ -17,12 +17,14 @@ export class Cron {
|
|
|
17
17
|
private readonly STORAGE_KEY = 'sequence-cron-jobs'
|
|
18
18
|
private isStopping: boolean = false
|
|
19
19
|
private currentCheckJobsPromise: Promise<void> = Promise.resolve()
|
|
20
|
+
private readonly env: Shared['env']
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Initializes the Cron scheduler and starts the periodic job checker.
|
|
23
24
|
* @param shared Shared context for modules and logging.
|
|
24
25
|
*/
|
|
25
26
|
constructor(private readonly shared: Shared) {
|
|
27
|
+
this.env = shared.env
|
|
26
28
|
this.start()
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -33,7 +35,11 @@ export class Cron {
|
|
|
33
35
|
private start() {
|
|
34
36
|
if (this.isStopping) return
|
|
35
37
|
this.executeCheckJobsChain()
|
|
36
|
-
|
|
38
|
+
const setIntervalFn = this.env.timers?.setInterval ?? (globalThis as any).setInterval
|
|
39
|
+
if (!setIntervalFn) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
this.checkInterval = setIntervalFn(() => this.executeCheckJobsChain(), 60 * 1000)
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
/**
|
|
@@ -58,7 +64,10 @@ export class Cron {
|
|
|
58
64
|
this.isStopping = true
|
|
59
65
|
|
|
60
66
|
if (this.checkInterval) {
|
|
61
|
-
clearInterval(
|
|
67
|
+
const clearIntervalFn = this.env.timers?.clearInterval ?? (globalThis as any).clearInterval
|
|
68
|
+
if (clearIntervalFn) {
|
|
69
|
+
clearIntervalFn(this.checkInterval)
|
|
70
|
+
}
|
|
62
71
|
this.checkInterval = undefined
|
|
63
72
|
this.shared.modules.logger.log('Cron: Interval cleared.')
|
|
64
73
|
}
|
|
@@ -104,48 +113,22 @@ export class Cron {
|
|
|
104
113
|
}
|
|
105
114
|
|
|
106
115
|
try {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
if (!lock) {
|
|
112
|
-
return
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const now = Date.now()
|
|
116
|
-
const storage = await this.getStorageState()
|
|
117
|
-
|
|
118
|
-
for (const [id, job] of this.jobs) {
|
|
116
|
+
const locks = this.env.locks ?? (globalThis as any).navigator?.locks
|
|
117
|
+
if (locks?.request) {
|
|
118
|
+
await locks.request('sequence-cron-jobs', async (lock: Lock | null) => {
|
|
119
119
|
if (this.isStopping) {
|
|
120
|
-
|
|
120
|
+
return
|
|
121
121
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const timeSinceLastRun = now - lastRun
|
|
125
|
-
|
|
126
|
-
if (timeSinceLastRun >= job.interval) {
|
|
127
|
-
try {
|
|
128
|
-
await job.handler()
|
|
129
|
-
if (!this.isStopping) {
|
|
130
|
-
job.lastRun = now
|
|
131
|
-
storage.set(id, { lastRun: now })
|
|
132
|
-
}
|
|
133
|
-
} catch (error) {
|
|
134
|
-
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
135
|
-
this.shared.modules.logger.log(`Cron: Job ${id} was aborted.`)
|
|
136
|
-
} else {
|
|
137
|
-
console.error(`Cron job ${id} failed:`, error)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
122
|
+
if (!lock) {
|
|
123
|
+
return
|
|
140
124
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
})
|
|
125
|
+
await this.runJobs()
|
|
126
|
+
})
|
|
127
|
+
} else {
|
|
128
|
+
await this.runJobs()
|
|
129
|
+
}
|
|
147
130
|
} catch (error) {
|
|
148
|
-
if (error
|
|
131
|
+
if (this.isAbortError(error)) {
|
|
149
132
|
this.shared.modules.logger.log('Cron: navigator.locks.request was aborted.')
|
|
150
133
|
} else {
|
|
151
134
|
console.error('Cron: Error in navigator.locks.request:', error)
|
|
@@ -153,13 +136,51 @@ export class Cron {
|
|
|
153
136
|
}
|
|
154
137
|
}
|
|
155
138
|
|
|
139
|
+
private async runJobs(): Promise<void> {
|
|
140
|
+
const now = Date.now()
|
|
141
|
+
const storage = await this.getStorageState()
|
|
142
|
+
|
|
143
|
+
for (const [id, job] of this.jobs) {
|
|
144
|
+
if (this.isStopping) {
|
|
145
|
+
break
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const lastRun = storage.get(id)?.lastRun ?? job.lastRun
|
|
149
|
+
const timeSinceLastRun = now - lastRun
|
|
150
|
+
|
|
151
|
+
if (timeSinceLastRun >= job.interval) {
|
|
152
|
+
try {
|
|
153
|
+
await job.handler()
|
|
154
|
+
if (!this.isStopping) {
|
|
155
|
+
job.lastRun = now
|
|
156
|
+
storage.set(id, { lastRun: now })
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (this.isAbortError(error)) {
|
|
160
|
+
this.shared.modules.logger.log(`Cron: Job ${id} was aborted.`)
|
|
161
|
+
} else {
|
|
162
|
+
console.error(`Cron job ${id} failed:`, error)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!this.isStopping) {
|
|
169
|
+
await this.syncWithStorage()
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
156
173
|
/**
|
|
157
174
|
* Loads the persisted last run times for jobs from localStorage.
|
|
158
175
|
* @returns Map of job IDs to their last run times.
|
|
159
176
|
*/
|
|
160
177
|
private async getStorageState(): Promise<Map<string, { lastRun: number }>> {
|
|
161
178
|
if (this.isStopping) return new Map()
|
|
162
|
-
const
|
|
179
|
+
const storage = this.env.storage
|
|
180
|
+
if (!storage) {
|
|
181
|
+
return new Map()
|
|
182
|
+
}
|
|
183
|
+
const state = storage.getItem(this.STORAGE_KEY)
|
|
163
184
|
return new Map(state ? JSON.parse(state) : [])
|
|
164
185
|
}
|
|
165
186
|
|
|
@@ -168,7 +189,19 @@ export class Cron {
|
|
|
168
189
|
*/
|
|
169
190
|
private async syncWithStorage() {
|
|
170
191
|
if (this.isStopping) return
|
|
192
|
+
const storage = this.env.storage
|
|
193
|
+
if (!storage) {
|
|
194
|
+
return
|
|
195
|
+
}
|
|
171
196
|
const state = Array.from(this.jobs.entries()).map(([id, job]) => [id, { lastRun: job.lastRun }])
|
|
172
|
-
|
|
197
|
+
storage.setItem(this.STORAGE_KEY, JSON.stringify(state))
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private isAbortError(error: unknown): boolean {
|
|
201
|
+
const domException = (globalThis as any).DOMException
|
|
202
|
+
if (domException && error instanceof domException) {
|
|
203
|
+
return (error as DOMException).name === 'AbortError'
|
|
204
|
+
}
|
|
205
|
+
return (error as any)?.name === 'AbortError'
|
|
173
206
|
}
|
|
174
207
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Hex,
|
|
1
|
+
import { Hex, Bytes } from 'ox'
|
|
2
2
|
import { Handler } from './handler.js'
|
|
3
3
|
import * as Db from '../../dbs/index.js'
|
|
4
4
|
import { Signatures } from '../signatures.js'
|
|
5
5
|
import * as Identity from '@0xsequence/identity-instrument'
|
|
6
6
|
import { IdentitySigner } from '../../identity/signer.js'
|
|
7
7
|
import { AuthCodeHandler } from './authcode.js'
|
|
8
|
+
import type { WdkEnv } from '../../env.js'
|
|
8
9
|
|
|
9
10
|
export class AuthCodePkceHandler extends AuthCodeHandler implements Handler {
|
|
10
11
|
constructor(
|
|
@@ -16,8 +17,9 @@ export class AuthCodePkceHandler extends AuthCodeHandler implements Handler {
|
|
|
16
17
|
signatures: Signatures,
|
|
17
18
|
commitments: Db.AuthCommitments,
|
|
18
19
|
authKeys: Db.AuthKeys,
|
|
20
|
+
env?: WdkEnv,
|
|
19
21
|
) {
|
|
20
|
-
super(signupKind, issuer, oauthUrl, audience, nitro, signatures, commitments, authKeys)
|
|
22
|
+
super(signupKind, issuer, oauthUrl, audience, nitro, signatures, commitments, authKeys, env)
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
public async commitAuth(target: string, isSignUp: boolean, state?: string, signer?: string) {
|
|
@@ -40,7 +42,7 @@ export class AuthCodePkceHandler extends AuthCodeHandler implements Handler {
|
|
|
40
42
|
isSignUp,
|
|
41
43
|
})
|
|
42
44
|
|
|
43
|
-
const searchParams =
|
|
45
|
+
const searchParams = this.serializeQuery({
|
|
44
46
|
code_challenge: codeChallenge,
|
|
45
47
|
code_challenge_method: 'S256',
|
|
46
48
|
client_id: this.audience,
|
|
@@ -51,7 +53,7 @@ export class AuthCodePkceHandler extends AuthCodeHandler implements Handler {
|
|
|
51
53
|
state,
|
|
52
54
|
})
|
|
53
55
|
|
|
54
|
-
return `${this.oauthUrl}?${searchParams
|
|
56
|
+
return `${this.oauthUrl}?${searchParams}`
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
public async completeAuth(
|
|
@@ -6,6 +6,7 @@ import * as Identity from '@0xsequence/identity-instrument'
|
|
|
6
6
|
import { SignerUnavailable, SignerReady, SignerActionable, BaseSignatureRequest } from '../types/signature-request.js'
|
|
7
7
|
import { IdentitySigner } from '../../identity/signer.js'
|
|
8
8
|
import { IdentityHandler } from './identity.js'
|
|
9
|
+
import type { NavigationLike, WdkEnv } from '../../env.js'
|
|
9
10
|
|
|
10
11
|
export class AuthCodeHandler extends IdentityHandler implements Handler {
|
|
11
12
|
protected redirectUri: string = ''
|
|
@@ -19,8 +20,9 @@ export class AuthCodeHandler extends IdentityHandler implements Handler {
|
|
|
19
20
|
signatures: Signatures,
|
|
20
21
|
protected readonly commitments: Db.AuthCommitments,
|
|
21
22
|
authKeys: Db.AuthKeys,
|
|
23
|
+
env?: WdkEnv,
|
|
22
24
|
) {
|
|
23
|
-
super(nitro, authKeys, signatures, Identity.IdentityType.OIDC)
|
|
25
|
+
super(nitro, authKeys, signatures, Identity.IdentityType.OIDC, env)
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
public get kind() {
|
|
@@ -45,7 +47,7 @@ export class AuthCodeHandler extends IdentityHandler implements Handler {
|
|
|
45
47
|
isSignUp,
|
|
46
48
|
})
|
|
47
49
|
|
|
48
|
-
const searchParams =
|
|
50
|
+
const searchParams = this.serializeQuery({
|
|
49
51
|
client_id: this.audience,
|
|
50
52
|
redirect_uri: this.redirectUri,
|
|
51
53
|
response_type: 'code',
|
|
@@ -53,7 +55,7 @@ export class AuthCodeHandler extends IdentityHandler implements Handler {
|
|
|
53
55
|
...(this.signupKind === 'apple' ? {} : { scope: 'openid profile email' }),
|
|
54
56
|
})
|
|
55
57
|
|
|
56
|
-
return `${this.oauthUrl}?${searchParams
|
|
58
|
+
return `${this.oauthUrl}?${searchParams}`
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
public async completeAuth(
|
|
@@ -94,10 +96,29 @@ export class AuthCodeHandler extends IdentityHandler implements Handler {
|
|
|
94
96
|
status: 'actionable',
|
|
95
97
|
message: 'request-redirect',
|
|
96
98
|
handle: async () => {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
+
const navigation = this.getNavigation()
|
|
100
|
+
const url = await this.commitAuth(navigation.getPathname(), false, request.id, address)
|
|
101
|
+
navigation.redirect(url)
|
|
99
102
|
return true
|
|
100
103
|
},
|
|
101
104
|
}
|
|
102
105
|
}
|
|
106
|
+
|
|
107
|
+
protected serializeQuery(params: Record<string, string>): string {
|
|
108
|
+
const searchParamsCtor = this.env.urlSearchParams ?? (globalThis as any).URLSearchParams
|
|
109
|
+
if (searchParamsCtor) {
|
|
110
|
+
return new searchParamsCtor(params).toString()
|
|
111
|
+
}
|
|
112
|
+
return Object.entries(params)
|
|
113
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
114
|
+
.join('&')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private getNavigation(): NavigationLike {
|
|
118
|
+
const navigation = this.env.navigation
|
|
119
|
+
if (!navigation) {
|
|
120
|
+
throw new Error('navigation is not available')
|
|
121
|
+
}
|
|
122
|
+
return navigation
|
|
123
|
+
}
|
|
103
124
|
}
|
|
@@ -35,7 +35,7 @@ export class GuardHandler implements Handler {
|
|
|
35
35
|
this.onPromptCode = undefined
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
onStatusChange(
|
|
38
|
+
onStatusChange(_cb: () => void): () => void {
|
|
39
39
|
return () => {}
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -87,8 +87,9 @@ export class GuardHandler implements Handler {
|
|
|
87
87
|
address,
|
|
88
88
|
handler: this,
|
|
89
89
|
status: 'ready',
|
|
90
|
-
handle: () =>
|
|
91
|
-
|
|
90
|
+
handle: () => {
|
|
91
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
92
|
+
return new Promise(async (resolve, reject) => {
|
|
92
93
|
try {
|
|
93
94
|
const signature = await guard.signEnvelope(request.envelope)
|
|
94
95
|
await this.signatures.addSignature(request.id, signature)
|
|
@@ -106,7 +107,8 @@ export class GuardHandler implements Handler {
|
|
|
106
107
|
reject(e)
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
|
-
})
|
|
110
|
+
})
|
|
111
|
+
},
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
114
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Hex
|
|
1
|
+
import { Hex } from 'ox'
|
|
2
2
|
import * as Db from '../../dbs/index.js'
|
|
3
3
|
import * as Identity from '@0xsequence/identity-instrument'
|
|
4
4
|
import { Signatures } from '../signatures.js'
|
|
5
5
|
import { BaseSignatureRequest } from '../types/signature-request.js'
|
|
6
6
|
import { IdentitySigner, toIdentityAuthKey } from '../../identity/signer.js'
|
|
7
|
+
import { resolveWdkEnv, type WdkEnv } from '../../env.js'
|
|
7
8
|
|
|
8
9
|
export const identityTypeToHex = (identityType?: Identity.IdentityType): Hex.Hex => {
|
|
9
10
|
// Bytes4
|
|
@@ -19,12 +20,17 @@ export const identityTypeToHex = (identityType?: Identity.IdentityType): Hex.Hex
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export class IdentityHandler {
|
|
23
|
+
protected readonly env: WdkEnv
|
|
24
|
+
|
|
22
25
|
constructor(
|
|
23
26
|
private readonly nitro: Identity.IdentityInstrument,
|
|
24
27
|
private readonly authKeys: Db.AuthKeys,
|
|
25
28
|
private readonly signatures: Signatures,
|
|
26
29
|
public readonly identityType: Identity.IdentityType,
|
|
27
|
-
|
|
30
|
+
env?: WdkEnv,
|
|
31
|
+
) {
|
|
32
|
+
this.env = resolveWdkEnv(env)
|
|
33
|
+
}
|
|
28
34
|
|
|
29
35
|
public onStatusChange(cb: () => void): () => void {
|
|
30
36
|
return this.authKeys.addListener(cb)
|
|
@@ -37,7 +43,7 @@ export class IdentityHandler {
|
|
|
37
43
|
throw new Error('no-auth-key')
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
const res = await this.nitro.commitVerifier(toIdentityAuthKey(authKey), challenge)
|
|
46
|
+
const res = await this.nitro.commitVerifier(toIdentityAuthKey(authKey, this.env.crypto), challenge)
|
|
41
47
|
return res
|
|
42
48
|
}
|
|
43
49
|
|
|
@@ -47,7 +53,7 @@ export class IdentityHandler {
|
|
|
47
53
|
throw new Error('no-auth-key')
|
|
48
54
|
}
|
|
49
55
|
|
|
50
|
-
const res = await this.nitro.completeAuth(toIdentityAuthKey(authKey), challenge)
|
|
56
|
+
const res = await this.nitro.completeAuth(toIdentityAuthKey(authKey, this.env.crypto), challenge)
|
|
51
57
|
|
|
52
58
|
authKey.identitySigner = res.signer.address
|
|
53
59
|
authKey.expiresAt = new Date(Date.now() + 1000 * 60 * 3) // 3 minutes
|
|
@@ -55,7 +61,7 @@ export class IdentityHandler {
|
|
|
55
61
|
await this.authKeys.delBySigner(authKey.identitySigner)
|
|
56
62
|
await this.authKeys.set(authKey)
|
|
57
63
|
|
|
58
|
-
const signer = new IdentitySigner(this.nitro, authKey)
|
|
64
|
+
const signer = new IdentitySigner(this.nitro, authKey, this.env.crypto)
|
|
59
65
|
return { signer, email: res.identity.email }
|
|
60
66
|
}
|
|
61
67
|
|
|
@@ -72,13 +78,17 @@ export class IdentityHandler {
|
|
|
72
78
|
if (!authKey) {
|
|
73
79
|
return undefined
|
|
74
80
|
}
|
|
75
|
-
return new IdentitySigner(this.nitro, authKey)
|
|
81
|
+
return new IdentitySigner(this.nitro, authKey, this.env.crypto)
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
private async getAuthKey(signer: string): Promise<Db.AuthKey | undefined> {
|
|
79
85
|
let authKey = await this.authKeys.getBySigner(signer)
|
|
80
86
|
if (!signer && !authKey) {
|
|
81
|
-
const
|
|
87
|
+
const crypto = this.env.crypto ?? (globalThis as any).crypto
|
|
88
|
+
if (!crypto?.subtle) {
|
|
89
|
+
throw new Error('crypto.subtle is not available')
|
|
90
|
+
}
|
|
91
|
+
const keyPair = await crypto.subtle.generateKey(
|
|
82
92
|
{
|
|
83
93
|
name: 'ECDSA',
|
|
84
94
|
namedCurve: 'P-256',
|
|
@@ -86,7 +96,7 @@ export class IdentityHandler {
|
|
|
86
96
|
false,
|
|
87
97
|
['sign', 'verify'],
|
|
88
98
|
)
|
|
89
|
-
const publicKey = await
|
|
99
|
+
const publicKey = await crypto.subtle.exportKey('raw', keyPair.publicKey)
|
|
90
100
|
authKey = {
|
|
91
101
|
address: Hex.fromBytes(new Uint8Array(publicKey)),
|
|
92
102
|
identitySigner: '',
|
|
@@ -93,8 +93,9 @@ export class MnemonicHandler implements Handler {
|
|
|
93
93
|
handler: this,
|
|
94
94
|
status: 'actionable',
|
|
95
95
|
message: 'enter-mnemonic',
|
|
96
|
-
handle: () =>
|
|
97
|
-
|
|
96
|
+
handle: () => {
|
|
97
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
98
|
+
return new Promise(async (resolve, reject) => {
|
|
98
99
|
const respond: RespondFn = async (mnemonic) => {
|
|
99
100
|
const signer = MnemonicHandler.toSigner(mnemonic)
|
|
100
101
|
if (!signer) {
|
|
@@ -117,7 +118,8 @@ export class MnemonicHandler implements Handler {
|
|
|
117
118
|
resolve(true)
|
|
118
119
|
}
|
|
119
120
|
await onPromptMnemonic(respond)
|
|
120
|
-
})
|
|
121
|
+
})
|
|
122
|
+
},
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
125
|
}
|
|
@@ -8,6 +8,7 @@ import { SignerUnavailable, SignerReady, SignerActionable, BaseSignatureRequest
|
|
|
8
8
|
import { Kinds } from '../types/signer.js'
|
|
9
9
|
import { IdentityHandler } from './identity.js'
|
|
10
10
|
import { AnswerIncorrectError, ChallengeExpiredError, TooManyAttemptsError } from '../errors.js'
|
|
11
|
+
import type { WdkEnv } from '../../env.js'
|
|
11
12
|
|
|
12
13
|
type RespondFn = (otp: string) => Promise<void>
|
|
13
14
|
|
|
@@ -18,8 +19,8 @@ export class OtpHandler extends IdentityHandler implements Handler {
|
|
|
18
19
|
|
|
19
20
|
private onPromptOtp: undefined | PromptOtpHandler
|
|
20
21
|
|
|
21
|
-
constructor(nitro: Identity.IdentityInstrument, signatures: Signatures, authKeys: Db.AuthKeys) {
|
|
22
|
-
super(nitro, authKeys, signatures, Identity.IdentityType.Email)
|
|
22
|
+
constructor(nitro: Identity.IdentityInstrument, signatures: Signatures, authKeys: Db.AuthKeys, env?: WdkEnv) {
|
|
23
|
+
super(nitro, authKeys, signatures, Identity.IdentityType.Email, env)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
public registerUI(onPromptOtp: PromptOtpHandler) {
|
|
@@ -84,7 +85,7 @@ export class OtpHandler extends IdentityHandler implements Handler {
|
|
|
84
85
|
try {
|
|
85
86
|
await this.handleAuth(challenge, onPromptOtp)
|
|
86
87
|
return true
|
|
87
|
-
} catch
|
|
88
|
+
} catch {
|
|
88
89
|
return false
|
|
89
90
|
}
|
|
90
91
|
},
|
|
@@ -95,6 +96,7 @@ export class OtpHandler extends IdentityHandler implements Handler {
|
|
|
95
96
|
challenge: Identity.OtpChallenge,
|
|
96
97
|
onPromptOtp: PromptOtpHandler,
|
|
97
98
|
): Promise<{ signer: Signers.Signer & Signers.Witnessable; email: string }> {
|
|
99
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
98
100
|
return new Promise(async (resolve, reject) => {
|
|
99
101
|
try {
|
|
100
102
|
const { loginHint, challenge: codeChallenge } = await this.nitroCommitVerifier(challenge)
|
|
@@ -1,33 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { State } from '@0xsequence/wallet-core'
|
|
2
2
|
import { Address, Hex } from 'ox'
|
|
3
3
|
import { Kinds } from '../types/signer.js'
|
|
4
4
|
import { Signatures } from '../signatures.js'
|
|
5
|
-
import { Extensions } from '@0xsequence/wallet-primitives'
|
|
5
|
+
import { Config, Extensions } from '@0xsequence/wallet-primitives'
|
|
6
6
|
import { Handler } from './handler.js'
|
|
7
7
|
import { SignerActionable, SignerUnavailable, BaseSignatureRequest } from '../types/index.js'
|
|
8
|
+
import type { PasskeyProvider, PasskeySigner } from '../passkeys-provider.js'
|
|
8
9
|
|
|
9
10
|
export class PasskeysHandler implements Handler {
|
|
10
11
|
kind = Kinds.LoginPasskey
|
|
11
|
-
private readySigners = new Map<string,
|
|
12
|
+
private readySigners = new Map<string, PasskeySigner>()
|
|
12
13
|
|
|
13
14
|
constructor(
|
|
14
15
|
private readonly signatures: Signatures,
|
|
15
16
|
private readonly extensions: Pick<Extensions.Extensions, 'passkeys'>,
|
|
16
17
|
private readonly stateReader: State.Reader,
|
|
18
|
+
private readonly passkeyProvider: PasskeyProvider,
|
|
17
19
|
) {}
|
|
18
20
|
|
|
19
|
-
onStatusChange(
|
|
21
|
+
onStatusChange(_cb: () => void): () => void {
|
|
20
22
|
return () => {}
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
public addReadySigner(signer:
|
|
25
|
+
public addReadySigner(signer: PasskeySigner) {
|
|
24
26
|
// Use credentialId as key to match specific passkey instances
|
|
25
27
|
this.readySigners.set(signer.credentialId, signer)
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
private async loadPasskey(wallet: Address.Address, imageHash: Hex.Hex): Promise<
|
|
30
|
+
private async loadPasskey(wallet: Address.Address, imageHash: Hex.Hex): Promise<PasskeySigner | undefined> {
|
|
29
31
|
try {
|
|
30
|
-
return await
|
|
32
|
+
return await this.passkeyProvider.loadFromWitness(this.stateReader, this.extensions, wallet, imageHash)
|
|
31
33
|
} catch (e) {
|
|
32
34
|
console.warn('Failed to load passkey:', e)
|
|
33
35
|
return undefined
|
|
@@ -55,7 +57,7 @@ export class PasskeysHandler implements Handler {
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
// First check if we have a ready signer that matches the imageHash
|
|
58
|
-
let passkey:
|
|
60
|
+
let passkey: PasskeySigner | undefined
|
|
59
61
|
|
|
60
62
|
// Look for a ready signer with matching imageHash
|
|
61
63
|
for (const readySigner of this.readySigners.values()) {
|
|
@@ -91,12 +93,10 @@ export class PasskeysHandler implements Handler {
|
|
|
91
93
|
message: 'request-interaction-with-passkey',
|
|
92
94
|
imageHash: imageHash,
|
|
93
95
|
handle: async () => {
|
|
94
|
-
const
|
|
95
|
-
request.envelope.wallet,
|
|
96
|
-
request.envelope.chainId,
|
|
97
|
-
request.envelope.payload,
|
|
98
|
-
imageHash,
|
|
96
|
+
const normalized = Config.normalizeSignerSignature(
|
|
97
|
+
passkey.signSapient(request.envelope.wallet, request.envelope.chainId, request.envelope.payload, imageHash),
|
|
99
98
|
)
|
|
99
|
+
const signature = await normalized.signature
|
|
100
100
|
await this.signatures.addSignature(request.id, {
|
|
101
101
|
address,
|
|
102
102
|
imageHash,
|
package/src/sequence/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { Network as Networks }
|
|
|
3
3
|
|
|
4
4
|
export type { ManagerOptions, Databases, Sequence, Modules, Shared } from './manager.js'
|
|
5
5
|
export { ManagerOptionsDefaults, CreateWalletOptionsDefaults, applyManagerOptionsDefaults, Manager } from './manager.js'
|
|
6
|
+
export { defaultPasskeyProvider } from './passkeys-provider.js'
|
|
7
|
+
export type { PasskeyProvider, PasskeySigner } from './passkeys-provider.js'
|
|
6
8
|
export { Sessions } from './sessions.js'
|
|
7
9
|
export { Signatures } from './signatures.js'
|
|
8
10
|
export type {
|