@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.
Files changed (78) hide show
  1. package/.depcheckrc +20 -0
  2. package/.eslintrc.js +21 -0
  3. package/LICENSE +122 -0
  4. package/README.md +1 -0
  5. package/env.d.ts +12 -0
  6. package/package.json +49 -1
  7. package/project.json +36 -0
  8. package/src/challenge-solvers/createChallengeSolverService.ts +64 -0
  9. package/src/challenge-solvers/createHashcashMockSolver.ts +39 -0
  10. package/src/challenge-solvers/createHashcashSolver.test.ts +385 -0
  11. package/src/challenge-solvers/createHashcashSolver.ts +270 -0
  12. package/src/challenge-solvers/createNoneMockSolver.ts +11 -0
  13. package/src/challenge-solvers/createTurnstileMockSolver.ts +30 -0
  14. package/src/challenge-solvers/createTurnstileSolver.ts +357 -0
  15. package/src/challenge-solvers/hashcash/core.native.ts +34 -0
  16. package/src/challenge-solvers/hashcash/core.test.ts +314 -0
  17. package/src/challenge-solvers/hashcash/core.ts +35 -0
  18. package/src/challenge-solvers/hashcash/core.web.ts +123 -0
  19. package/src/challenge-solvers/hashcash/createWorkerHashcashSolver.test.ts +195 -0
  20. package/src/challenge-solvers/hashcash/createWorkerHashcashSolver.ts +120 -0
  21. package/src/challenge-solvers/hashcash/shared.ts +70 -0
  22. package/src/challenge-solvers/hashcash/worker/createHashcashMultiWorkerChannel.native.ts +22 -0
  23. package/src/challenge-solvers/hashcash/worker/createHashcashMultiWorkerChannel.ts +22 -0
  24. package/src/challenge-solvers/hashcash/worker/createHashcashMultiWorkerChannel.web.ts +212 -0
  25. package/src/challenge-solvers/hashcash/worker/createHashcashWorkerChannel.native.ts +16 -0
  26. package/src/challenge-solvers/hashcash/worker/createHashcashWorkerChannel.ts +16 -0
  27. package/src/challenge-solvers/hashcash/worker/createHashcashWorkerChannel.web.ts +97 -0
  28. package/src/challenge-solvers/hashcash/worker/hashcash.worker.ts +91 -0
  29. package/src/challenge-solvers/hashcash/worker/types.ts +69 -0
  30. package/src/challenge-solvers/turnstileErrors.ts +49 -0
  31. package/src/challenge-solvers/turnstileScriptLoader.ts +325 -0
  32. package/src/challenge-solvers/turnstileSolver.integration.test.ts +331 -0
  33. package/src/challenge-solvers/types.ts +55 -0
  34. package/src/challengeFlow.integration.test.ts +627 -0
  35. package/src/device-id/createDeviceIdService.ts +19 -0
  36. package/src/device-id/types.ts +11 -0
  37. package/src/index.ts +139 -0
  38. package/src/lx-identifier/createLXIdentifierService.ts +1 -0
  39. package/src/lx-identifier/createUniswapIdentifierService.ts +19 -0
  40. package/src/lx-identifier/lxIdentifierQuery.ts +1 -0
  41. package/src/lx-identifier/types.ts +11 -0
  42. package/src/lx-identifier/uniswapIdentifierQuery.ts +20 -0
  43. package/src/oauth-service/createOAuthService.ts +125 -0
  44. package/src/oauth-service/types.ts +104 -0
  45. package/src/performance/createNoopPerformanceTracker.ts +12 -0
  46. package/src/performance/createPerformanceTracker.ts +43 -0
  47. package/src/performance/index.ts +7 -0
  48. package/src/performance/types.ts +11 -0
  49. package/src/session-initialization/createSessionInitializationService.test.ts +557 -0
  50. package/src/session-initialization/createSessionInitializationService.ts +184 -0
  51. package/src/session-initialization/sessionErrors.ts +32 -0
  52. package/src/session-repository/createSessionClient.ts +10 -0
  53. package/src/session-repository/createSessionRepository.test.ts +313 -0
  54. package/src/session-repository/createSessionRepository.ts +242 -0
  55. package/src/session-repository/errors.ts +22 -0
  56. package/src/session-repository/types.ts +289 -0
  57. package/src/session-service/createNoopSessionService.ts +24 -0
  58. package/src/session-service/createSessionService.test.ts +388 -0
  59. package/src/session-service/createSessionService.ts +61 -0
  60. package/src/session-service/types.ts +59 -0
  61. package/src/session-storage/createSessionStorage.ts +28 -0
  62. package/src/session-storage/types.ts +15 -0
  63. package/src/session.integration.test.ts +516 -0
  64. package/src/sessionLifecycle.integration.test.ts +264 -0
  65. package/src/test-utils/createLocalCookieTransport.ts +52 -0
  66. package/src/test-utils/createLocalHeaderTransport.ts +45 -0
  67. package/src/test-utils/mocks.ts +122 -0
  68. package/src/test-utils.ts +200 -0
  69. package/src/uniswap-identifier/createUniswapIdentifierService.ts +19 -0
  70. package/src/uniswap-identifier/types.ts +11 -0
  71. package/src/uniswap-identifier/uniswapIdentifierQuery.ts +20 -0
  72. package/tsconfig.json +26 -0
  73. package/tsconfig.lint.json +8 -0
  74. package/tsconfig.spec.json +8 -0
  75. package/vitest.config.ts +20 -0
  76. package/vitest.integration.config.ts +14 -0
  77. package/index.d.ts +0 -1
  78. 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
+ }