@l.x/sessions 1.0.3 → 1.0.5
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/.depcheckrc +20 -0
- package/.eslintrc.js +21 -0
- package/LICENSE +122 -0
- package/README.md +1 -0
- package/env.d.ts +12 -0
- package/package.json +49 -1
- package/project.json +36 -0
- package/src/challenge-solvers/createChallengeSolverService.ts +64 -0
- package/src/challenge-solvers/createHashcashMockSolver.ts +39 -0
- package/src/challenge-solvers/createHashcashSolver.test.ts +385 -0
- package/src/challenge-solvers/createHashcashSolver.ts +270 -0
- package/src/challenge-solvers/createNoneMockSolver.ts +11 -0
- package/src/challenge-solvers/createTurnstileMockSolver.ts +30 -0
- package/src/challenge-solvers/createTurnstileSolver.ts +357 -0
- package/src/challenge-solvers/hashcash/core.native.ts +34 -0
- package/src/challenge-solvers/hashcash/core.test.ts +314 -0
- package/src/challenge-solvers/hashcash/core.ts +35 -0
- package/src/challenge-solvers/hashcash/core.web.ts +123 -0
- package/src/challenge-solvers/hashcash/createWorkerHashcashSolver.test.ts +195 -0
- package/src/challenge-solvers/hashcash/createWorkerHashcashSolver.ts +120 -0
- package/src/challenge-solvers/hashcash/shared.ts +70 -0
- package/src/challenge-solvers/hashcash/worker/createHashcashMultiWorkerChannel.native.ts +22 -0
- package/src/challenge-solvers/hashcash/worker/createHashcashMultiWorkerChannel.ts +22 -0
- package/src/challenge-solvers/hashcash/worker/createHashcashMultiWorkerChannel.web.ts +212 -0
- package/src/challenge-solvers/hashcash/worker/createHashcashWorkerChannel.native.ts +16 -0
- package/src/challenge-solvers/hashcash/worker/createHashcashWorkerChannel.ts +16 -0
- package/src/challenge-solvers/hashcash/worker/createHashcashWorkerChannel.web.ts +97 -0
- package/src/challenge-solvers/hashcash/worker/hashcash.worker.ts +91 -0
- package/src/challenge-solvers/hashcash/worker/types.ts +69 -0
- package/src/challenge-solvers/turnstileErrors.ts +49 -0
- package/src/challenge-solvers/turnstileScriptLoader.ts +325 -0
- package/src/challenge-solvers/turnstileSolver.integration.test.ts +331 -0
- package/src/challenge-solvers/types.ts +55 -0
- package/src/challengeFlow.integration.test.ts +627 -0
- package/src/device-id/createDeviceIdService.ts +19 -0
- package/src/device-id/types.ts +11 -0
- package/src/index.ts +139 -0
- package/src/lx-identifier/createLXIdentifierService.ts +1 -0
- package/src/lx-identifier/createUniswapIdentifierService.ts +19 -0
- package/src/lx-identifier/lxIdentifierQuery.ts +1 -0
- package/src/lx-identifier/types.ts +11 -0
- package/src/lx-identifier/uniswapIdentifierQuery.ts +20 -0
- package/src/oauth-service/createOAuthService.ts +125 -0
- package/src/oauth-service/types.ts +104 -0
- package/src/performance/createNoopPerformanceTracker.ts +12 -0
- package/src/performance/createPerformanceTracker.ts +43 -0
- package/src/performance/index.ts +7 -0
- package/src/performance/types.ts +11 -0
- package/src/session-initialization/createSessionInitializationService.test.ts +557 -0
- package/src/session-initialization/createSessionInitializationService.ts +184 -0
- package/src/session-initialization/sessionErrors.ts +32 -0
- package/src/session-repository/createSessionClient.ts +10 -0
- package/src/session-repository/createSessionRepository.test.ts +313 -0
- package/src/session-repository/createSessionRepository.ts +242 -0
- package/src/session-repository/errors.ts +22 -0
- package/src/session-repository/types.ts +289 -0
- package/src/session-service/createNoopSessionService.ts +24 -0
- package/src/session-service/createSessionService.test.ts +388 -0
- package/src/session-service/createSessionService.ts +61 -0
- package/src/session-service/types.ts +59 -0
- package/src/session-storage/createSessionStorage.ts +28 -0
- package/src/session-storage/types.ts +15 -0
- package/src/session.integration.test.ts +516 -0
- package/src/sessionLifecycle.integration.test.ts +264 -0
- package/src/test-utils/createLocalCookieTransport.ts +52 -0
- package/src/test-utils/createLocalHeaderTransport.ts +45 -0
- package/src/test-utils/mocks.ts +122 -0
- package/src/test-utils.ts +200 -0
- package/src/uniswap-identifier/createUniswapIdentifierService.ts +19 -0
- package/src/uniswap-identifier/types.ts +11 -0
- package/src/uniswap-identifier/uniswapIdentifierQuery.ts +20 -0
- package/tsconfig.json +26 -0
- package/tsconfig.lint.json +8 -0
- package/tsconfig.spec.json +8 -0
- package/vitest.config.ts +20 -0
- package/vitest.integration.config.ts +14 -0
- package/index.d.ts +0 -1
- package/index.js +0 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-worker channel factory for parallel hashcash proof-of-work.
|
|
3
|
+
*
|
|
4
|
+
* Spawns multiple Web Workers to search different counter ranges in parallel.
|
|
5
|
+
* Uses Promise.race() - first worker to find a valid proof wins.
|
|
6
|
+
*
|
|
7
|
+
* This provides significant speedup on multi-core systems:
|
|
8
|
+
* - 4 workers ~= 4x speedup
|
|
9
|
+
* - 8 workers ~= 8x speedup (diminishing returns beyond core count)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ProofResult } from '@l.x/sessions/src/challenge-solvers/hashcash/core'
|
|
13
|
+
import type {
|
|
14
|
+
FindProofParams,
|
|
15
|
+
HashcashWorkerAPI,
|
|
16
|
+
HashcashWorkerChannel,
|
|
17
|
+
} from '@l.x/sessions/src/challenge-solvers/hashcash/worker/types'
|
|
18
|
+
import { createChannel } from 'bidc'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Configuration for multi-worker hashcash channel.
|
|
22
|
+
*/
|
|
23
|
+
interface MultiWorkerConfig {
|
|
24
|
+
/**
|
|
25
|
+
* Number of workers to spawn.
|
|
26
|
+
* Defaults to navigator.hardwareConcurrency or 4.
|
|
27
|
+
*/
|
|
28
|
+
workerCount?: number
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Factory function to create a Worker instance.
|
|
32
|
+
*/
|
|
33
|
+
getWorker: () => Worker
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Internal worker state for tracking individual workers.
|
|
38
|
+
*/
|
|
39
|
+
interface WorkerState {
|
|
40
|
+
worker: Worker
|
|
41
|
+
channel: ReturnType<typeof createChannel>
|
|
42
|
+
cancelled: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates a multi-worker channel for parallel hashcash proof-of-work.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const channel = createHashcashMultiWorkerChannel({
|
|
51
|
+
* workerCount: 4,
|
|
52
|
+
* getWorker: () => new Worker(
|
|
53
|
+
* new URL('./hashcash.worker.ts', import.meta.url),
|
|
54
|
+
* { type: 'module' }
|
|
55
|
+
* ),
|
|
56
|
+
* })
|
|
57
|
+
*
|
|
58
|
+
* const proof = await channel.api.findProof({ challenge })
|
|
59
|
+
* channel.terminate()
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
function createHashcashMultiWorkerChannel(config: MultiWorkerConfig): HashcashWorkerChannel {
|
|
63
|
+
const workerCount = config.workerCount ?? (typeof navigator !== 'undefined' ? navigator.hardwareConcurrency : 4)
|
|
64
|
+
const workers: WorkerState[] = []
|
|
65
|
+
|
|
66
|
+
// Track if we've been terminated
|
|
67
|
+
let terminated = false
|
|
68
|
+
|
|
69
|
+
// Initialize workers lazily on first findProof call
|
|
70
|
+
const initWorkers = (): void => {
|
|
71
|
+
if (workers.length > 0 || terminated) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < workerCount; i++) {
|
|
76
|
+
const worker = config.getWorker()
|
|
77
|
+
const channel = createChannel(worker)
|
|
78
|
+
workers.push({ worker, channel, cancelled: false })
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const api: HashcashWorkerAPI = {
|
|
83
|
+
async findProof(params: FindProofParams): Promise<ProofResult | null> {
|
|
84
|
+
if (terminated) {
|
|
85
|
+
throw new Error('Multi-worker channel has been terminated')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
initWorkers()
|
|
89
|
+
|
|
90
|
+
// Reset cancelled state for all workers at start of new search
|
|
91
|
+
workers.forEach((state) => {
|
|
92
|
+
state.cancelled = false
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const { challenge, rangeStart = 0, rangeSize = challenge.max_proof_length } = params
|
|
96
|
+
const rangeEnd = rangeStart + rangeSize
|
|
97
|
+
|
|
98
|
+
// Divide range across workers
|
|
99
|
+
const rangePerWorker = Math.ceil(rangeSize / workerCount)
|
|
100
|
+
|
|
101
|
+
// Track completion state
|
|
102
|
+
let foundResult: ProofResult | null = null
|
|
103
|
+
let completedCount = 0
|
|
104
|
+
|
|
105
|
+
// Pre-calculate expected worker count before starting any workers.
|
|
106
|
+
// This avoids a subtle dependency on JS event loop microtask ordering:
|
|
107
|
+
// if we counted inline, a fast-completing worker's .then() could
|
|
108
|
+
// theoretically race with the loop incrementing the started count.
|
|
109
|
+
let expectedCount = 0
|
|
110
|
+
workers.forEach((state, index) => {
|
|
111
|
+
if (state.cancelled || terminated) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
const workerRangeStart = rangeStart + index * rangePerWorker
|
|
115
|
+
const workerRangeEnd = Math.min(workerRangeStart + rangePerWorker, rangeEnd)
|
|
116
|
+
if (workerRangeEnd - workerRangeStart > 0) {
|
|
117
|
+
expectedCount++
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
if (expectedCount === 0) {
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
// Start all workers in parallel
|
|
127
|
+
workers.forEach((state, index) => {
|
|
128
|
+
if (terminated) {
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const workerRangeStart = rangeStart + index * rangePerWorker
|
|
133
|
+
const workerRangeEnd = Math.min(workerRangeStart + rangePerWorker, rangeEnd)
|
|
134
|
+
const workerRangeSize = workerRangeEnd - workerRangeStart
|
|
135
|
+
|
|
136
|
+
if (workerRangeSize <= 0) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Start worker search (don't await - let them race)
|
|
141
|
+
state.channel
|
|
142
|
+
.send({
|
|
143
|
+
type: 'findProof',
|
|
144
|
+
params: {
|
|
145
|
+
challenge,
|
|
146
|
+
rangeStart: workerRangeStart,
|
|
147
|
+
rangeSize: workerRangeSize,
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
.then((result: unknown) => {
|
|
151
|
+
// Check if worker returned busy response
|
|
152
|
+
if (result && typeof result === 'object' && 'busy' in result) {
|
|
153
|
+
return null
|
|
154
|
+
}
|
|
155
|
+
return result as ProofResult | null
|
|
156
|
+
})
|
|
157
|
+
.then((result) => {
|
|
158
|
+
completedCount++
|
|
159
|
+
|
|
160
|
+
// First worker to find a valid proof wins
|
|
161
|
+
if (result && !foundResult && !terminated) {
|
|
162
|
+
foundResult = result
|
|
163
|
+
// Cancel all other workers immediately
|
|
164
|
+
api.cancel().catch(() => {})
|
|
165
|
+
resolve(result)
|
|
166
|
+
} else if (completedCount === expectedCount && !foundResult) {
|
|
167
|
+
// All expected workers done, no result found
|
|
168
|
+
resolve(null)
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
.catch(() => {
|
|
172
|
+
completedCount++
|
|
173
|
+
if (completedCount === expectedCount && !foundResult) {
|
|
174
|
+
resolve(null)
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
async cancel(): Promise<void> {
|
|
182
|
+
// Cancel all workers - fire and forget for speed
|
|
183
|
+
workers.forEach((state) => {
|
|
184
|
+
if (!state.cancelled) {
|
|
185
|
+
state.cancelled = true
|
|
186
|
+
state.channel.send({ type: 'cancel' }).catch(() => {
|
|
187
|
+
// Ignore cancel errors
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
api,
|
|
196
|
+
terminate(): void {
|
|
197
|
+
terminated = true
|
|
198
|
+
|
|
199
|
+
// Terminate all workers
|
|
200
|
+
for (const state of workers) {
|
|
201
|
+
state.cancelled = true
|
|
202
|
+
state.channel.cleanup()
|
|
203
|
+
state.worker.terminate()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
workers.length = 0
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export { createHashcashMultiWorkerChannel }
|
|
212
|
+
export type { MultiWorkerConfig }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native implementation of hashcash worker channel factory.
|
|
3
|
+
* Web Workers are not available in React Native.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
CreateHashcashWorkerChannelContext,
|
|
8
|
+
HashcashWorkerChannel,
|
|
9
|
+
} from '@l.x/sessions/src/challenge-solvers/hashcash/worker/types'
|
|
10
|
+
import { NotImplementedError } from 'utilities/src/errors'
|
|
11
|
+
|
|
12
|
+
function createHashcashWorkerChannel(_ctx: CreateHashcashWorkerChannelContext): HashcashWorkerChannel {
|
|
13
|
+
throw new NotImplementedError('createHashcashWorkerChannel')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { createHashcashWorkerChannel }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base stub for hashcash worker channel factory.
|
|
3
|
+
* Platform-specific implementations override this file.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
CreateHashcashWorkerChannelContext,
|
|
8
|
+
HashcashWorkerChannel,
|
|
9
|
+
} from '@l.x/sessions/src/challenge-solvers/hashcash/worker/types'
|
|
10
|
+
import { PlatformSplitStubError } from 'utilities/src/errors'
|
|
11
|
+
|
|
12
|
+
function createHashcashWorkerChannel(_ctx: CreateHashcashWorkerChannelContext): HashcashWorkerChannel {
|
|
13
|
+
throw new PlatformSplitStubError('createHashcashWorkerChannel')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { createHashcashWorkerChannel }
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Worker channel factory for hashcash proof-of-work.
|
|
3
|
+
*
|
|
4
|
+
* Creates a Web Worker and establishes a BIDC channel for
|
|
5
|
+
* bidirectional async communication.
|
|
6
|
+
*
|
|
7
|
+
* This is the web platform implementation used by both web app and extension.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ProofResult } from '@l.x/sessions/src/challenge-solvers/hashcash/core'
|
|
11
|
+
import type {
|
|
12
|
+
CreateHashcashWorkerChannelContext,
|
|
13
|
+
FindProofParams,
|
|
14
|
+
HashcashWorkerAPI,
|
|
15
|
+
HashcashWorkerChannel,
|
|
16
|
+
} from '@l.x/sessions/src/challenge-solvers/hashcash/worker/types'
|
|
17
|
+
import { createChannel } from 'bidc'
|
|
18
|
+
|
|
19
|
+
// Singleton worker instance for reuse
|
|
20
|
+
let sharedWorker: Worker | null = null
|
|
21
|
+
let sharedChannel: ReturnType<typeof createChannel> | null = null
|
|
22
|
+
let referenceCount = 0
|
|
23
|
+
// Track pending operations to reject on terminate
|
|
24
|
+
const pendingOperations = new Set<(err: Error) => void>()
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates (or reuses) a channel to the hashcash worker.
|
|
28
|
+
*
|
|
29
|
+
* Uses a shared worker instance to avoid creation overhead.
|
|
30
|
+
* The worker is only terminated when all channels are closed.
|
|
31
|
+
*
|
|
32
|
+
* @param ctx - Context containing getWorker function to create the Worker instance
|
|
33
|
+
*/
|
|
34
|
+
function createHashcashWorkerChannel(ctx: CreateHashcashWorkerChannelContext): HashcashWorkerChannel {
|
|
35
|
+
// Create worker on first use
|
|
36
|
+
if (!sharedWorker) {
|
|
37
|
+
sharedWorker = ctx.getWorker()
|
|
38
|
+
sharedChannel = createChannel(sharedWorker)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
referenceCount++
|
|
42
|
+
|
|
43
|
+
const channel = sharedChannel
|
|
44
|
+
if (!channel) {
|
|
45
|
+
throw new Error('Worker channel not initialized')
|
|
46
|
+
}
|
|
47
|
+
const { send } = channel
|
|
48
|
+
|
|
49
|
+
const api: HashcashWorkerAPI = {
|
|
50
|
+
findProof(params: FindProofParams): Promise<ProofResult | null> {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
pendingOperations.add(reject)
|
|
53
|
+
|
|
54
|
+
send({ type: 'findProof', params })
|
|
55
|
+
.then((result: unknown) => {
|
|
56
|
+
// Check if worker returned busy response
|
|
57
|
+
// Worker returns { busy: true } when another operation is in progress
|
|
58
|
+
if (result && typeof result === 'object' && 'busy' in result) {
|
|
59
|
+
reject(new Error('Worker is busy - another findProof operation is in progress'))
|
|
60
|
+
} else {
|
|
61
|
+
resolve(result as ProofResult | null)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
.catch(reject)
|
|
65
|
+
.finally(() => pendingOperations.delete(reject))
|
|
66
|
+
})
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async cancel(): Promise<void> {
|
|
70
|
+
await send({ type: 'cancel' })
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
api,
|
|
76
|
+
terminate(): void {
|
|
77
|
+
referenceCount--
|
|
78
|
+
|
|
79
|
+
// Only terminate when no more references
|
|
80
|
+
if (referenceCount <= 0 && sharedWorker) {
|
|
81
|
+
// Reject any pending operations before terminating
|
|
82
|
+
const error = new Error('Worker terminated while operation in progress')
|
|
83
|
+
for (const reject of pendingOperations) {
|
|
84
|
+
reject(error)
|
|
85
|
+
}
|
|
86
|
+
pendingOperations.clear()
|
|
87
|
+
|
|
88
|
+
sharedWorker.terminate()
|
|
89
|
+
sharedWorker = null
|
|
90
|
+
sharedChannel = null
|
|
91
|
+
referenceCount = 0
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { createHashcashWorkerChannel }
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashcash Web Worker
|
|
3
|
+
*
|
|
4
|
+
* Runs proof-of-work computation off the main thread using Web Crypto.
|
|
5
|
+
* Uses BIDC for bidirectional async communication.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ProofResult } from '@l.x/sessions/src/challenge-solvers/hashcash/core'
|
|
9
|
+
import { findProof } from '@l.x/sessions/src/challenge-solvers/hashcash/core'
|
|
10
|
+
import type { FindProofParams } from '@l.x/sessions/src/challenge-solvers/hashcash/worker/types'
|
|
11
|
+
import type { SerializableValue } from 'bidc'
|
|
12
|
+
import { createChannel } from 'bidc'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Message types sent from main thread to worker
|
|
16
|
+
* These are plain objects at runtime, compatible with SerializableValue
|
|
17
|
+
*/
|
|
18
|
+
type WorkerMessage = { type: 'findProof'; params: FindProofParams } | { type: 'cancel' }
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Response type returned from worker to main thread.
|
|
22
|
+
* Matches ProofResult structure but explicitly typed for serialization.
|
|
23
|
+
*/
|
|
24
|
+
type WorkerResponse = ProofResult | null
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Operation state for single-operation-at-a-time enforcement.
|
|
28
|
+
*
|
|
29
|
+
* This worker is designed for single-operation-at-a-time usage. Concurrent findProof
|
|
30
|
+
* calls will be rejected with an error. This ensures clean cancellation semantics
|
|
31
|
+
* where cancel() only affects the one in-progress operation.
|
|
32
|
+
*/
|
|
33
|
+
let operationInProgress = false
|
|
34
|
+
let cancelled = false
|
|
35
|
+
|
|
36
|
+
// Create BIDC channel - in worker context, connects to parent
|
|
37
|
+
const { receive } = createChannel()
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Response type for busy state - signals to caller that worker is occupied.
|
|
41
|
+
* The channel layer converts this to an appropriate error.
|
|
42
|
+
*/
|
|
43
|
+
type WorkerBusyResponse = { busy: true }
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Async message handler for incoming requests from main thread.
|
|
47
|
+
* BIDC supports async handlers - it awaits the Promise before sending response.
|
|
48
|
+
*/
|
|
49
|
+
const messageHandler = async (data: WorkerMessage): Promise<WorkerResponse | WorkerBusyResponse> => {
|
|
50
|
+
switch (data.type) {
|
|
51
|
+
case 'findProof': {
|
|
52
|
+
// Enforce single-operation-at-a-time
|
|
53
|
+
if (operationInProgress) {
|
|
54
|
+
return { busy: true }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
operationInProgress = true
|
|
58
|
+
// Reset cancellation flag for new search
|
|
59
|
+
cancelled = false
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// findProof is now async (uses Web Crypto)
|
|
63
|
+
const result = await findProof({
|
|
64
|
+
challenge: data.params.challenge,
|
|
65
|
+
rangeStart: data.params.rangeStart,
|
|
66
|
+
rangeSize: data.params.rangeSize,
|
|
67
|
+
// Check cancellation flag during iteration
|
|
68
|
+
shouldStop: () => cancelled,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// Return ProofResult directly - BIDC handles serialization of Uint8Array
|
|
72
|
+
return result
|
|
73
|
+
} finally {
|
|
74
|
+
operationInProgress = false
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case 'cancel': {
|
|
79
|
+
cancelled = true
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
default:
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Register async message handler with BIDC
|
|
89
|
+
// BIDC supports async handlers - the response Promise is awaited before sending
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
91
|
+
receive(messageHandler as (data: SerializableValue) => Promise<SerializableValue>)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { HashcashChallenge, ProofResult } from '@l.x/sessions/src/challenge-solvers/hashcash/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parameters for finding a hashcash proof
|
|
5
|
+
*/
|
|
6
|
+
interface FindProofParams {
|
|
7
|
+
challenge: HashcashChallenge
|
|
8
|
+
rangeStart?: number
|
|
9
|
+
rangeSize?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Context for creating a hashcash worker channel.
|
|
14
|
+
* Apps inject the Worker instance getter to control Worker instantiation.
|
|
15
|
+
*/
|
|
16
|
+
interface CreateHashcashWorkerChannelContext {
|
|
17
|
+
/**
|
|
18
|
+
* Returns a Worker instance for hashcash proof-of-work.
|
|
19
|
+
* Called once on first channel creation (singleton pattern).
|
|
20
|
+
*/
|
|
21
|
+
getWorker: () => Worker
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The API exposed by the hashcash worker.
|
|
26
|
+
* This is the contract between main thread and worker.
|
|
27
|
+
*/
|
|
28
|
+
interface HashcashWorkerAPI {
|
|
29
|
+
/**
|
|
30
|
+
* Find a proof-of-work solution for the given challenge.
|
|
31
|
+
* Returns null if no solution found within range or if cancelled.
|
|
32
|
+
*/
|
|
33
|
+
findProof(params: FindProofParams): Promise<ProofResult | null>
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Cancel any in-progress proof search.
|
|
37
|
+
*/
|
|
38
|
+
cancel(): Promise<void>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A channel to communicate with a hashcash worker.
|
|
43
|
+
* Platform-specific implementations create these.
|
|
44
|
+
*/
|
|
45
|
+
interface HashcashWorkerChannel {
|
|
46
|
+
/**
|
|
47
|
+
* The worker API - call methods to execute on worker thread
|
|
48
|
+
*/
|
|
49
|
+
api: HashcashWorkerAPI
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Terminate the worker and clean up resources
|
|
53
|
+
*/
|
|
54
|
+
terminate(): void
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Factory function that creates a HashcashWorkerChannel.
|
|
59
|
+
* Injected into the solver to enable platform-specific implementations.
|
|
60
|
+
*/
|
|
61
|
+
type HashcashWorkerChannelFactory = () => HashcashWorkerChannel
|
|
62
|
+
|
|
63
|
+
export type {
|
|
64
|
+
CreateHashcashWorkerChannelContext,
|
|
65
|
+
FindProofParams,
|
|
66
|
+
HashcashWorkerAPI,
|
|
67
|
+
HashcashWorkerChannel,
|
|
68
|
+
HashcashWorkerChannelFactory,
|
|
69
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { SessionError } from '@l.x/sessions/src/session-initialization/sessionErrors'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Error thrown when Turnstile script fails to load
|
|
5
|
+
*/
|
|
6
|
+
export class TurnstileScriptLoadError extends SessionError {
|
|
7
|
+
constructor(message: string, cause?: unknown) {
|
|
8
|
+
super(`Turnstile script load error: ${message}`, 'TurnstileScriptLoadError')
|
|
9
|
+
if (cause) {
|
|
10
|
+
this.cause = cause
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Error thrown when Turnstile API is not available
|
|
17
|
+
*/
|
|
18
|
+
export class TurnstileApiNotAvailableError extends SessionError {
|
|
19
|
+
constructor() {
|
|
20
|
+
super('Turnstile API not available', 'TurnstileApiNotAvailableError')
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Error thrown when Turnstile challenge times out
|
|
26
|
+
*/
|
|
27
|
+
export class TurnstileTimeoutError extends SessionError {
|
|
28
|
+
constructor(timeoutMs: number) {
|
|
29
|
+
super(`Turnstile challenge timed out after ${timeoutMs}ms`, 'TurnstileTimeoutError')
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Error thrown when Turnstile returns an error
|
|
35
|
+
*/
|
|
36
|
+
export class TurnstileError extends SessionError {
|
|
37
|
+
constructor(errorCode: string) {
|
|
38
|
+
super(`Turnstile error: ${errorCode}`, 'TurnstileError')
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Error thrown when Turnstile token expires
|
|
44
|
+
*/
|
|
45
|
+
export class TurnstileTokenExpiredError extends SessionError {
|
|
46
|
+
constructor() {
|
|
47
|
+
super('Turnstile token expired', 'TurnstileTokenExpiredError')
|
|
48
|
+
}
|
|
49
|
+
}
|