@luxexchange/sessions 1.0.1 → 1.0.3
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/README.md +1 -0
- package/env.d.ts +12 -0
- package/package.json +3 -4
- package/project.json +2 -8
- package/src/challenge-solvers/createChallengeSolverService.ts +5 -5
- package/src/challenge-solvers/createHashcashMockSolver.ts +1 -1
- package/src/challenge-solvers/createHashcashSolver.test.ts +10 -10
- package/src/challenge-solvers/createHashcashSolver.ts +22 -7
- package/src/challenge-solvers/createNoneMockSolver.ts +1 -1
- package/src/challenge-solvers/createTurnstileMockSolver.ts +1 -1
- package/src/challenge-solvers/createTurnstileSolver.ts +12 -8
- package/src/challenge-solvers/hashcash/core.native.ts +3 -3
- package/src/challenge-solvers/hashcash/core.test.ts +10 -10
- package/src/challenge-solvers/hashcash/core.ts +3 -3
- package/src/challenge-solvers/hashcash/core.web.ts +4 -4
- package/src/challenge-solvers/hashcash/createWorkerHashcashSolver.test.ts +7 -7
- package/src/challenge-solvers/hashcash/createWorkerHashcashSolver.ts +4 -4
- package/src/challenge-solvers/hashcash/worker/createHashcashMultiWorkerChannel.native.ts +1 -1
- package/src/challenge-solvers/hashcash/worker/createHashcashMultiWorkerChannel.ts +1 -1
- package/src/challenge-solvers/hashcash/worker/createHashcashMultiWorkerChannel.web.ts +2 -2
- package/src/challenge-solvers/hashcash/worker/createHashcashWorkerChannel.native.ts +1 -1
- package/src/challenge-solvers/hashcash/worker/createHashcashWorkerChannel.ts +1 -1
- package/src/challenge-solvers/hashcash/worker/createHashcashWorkerChannel.web.ts +2 -2
- package/src/challenge-solvers/hashcash/worker/hashcash.worker.ts +3 -3
- package/src/challenge-solvers/hashcash/worker/types.ts +1 -1
- package/src/challenge-solvers/turnstileErrors.ts +1 -1
- package/src/challenge-solvers/turnstileScriptLoader.ts +2 -2
- package/src/challenge-solvers/turnstileSolver.integration.test.ts +4 -4
- package/src/challenge-solvers/types.ts +2 -2
- package/src/challengeFlow.integration.test.ts +49 -49
- package/src/device-id/createDeviceIdService.ts +1 -1
- package/src/index.ts +50 -48
- package/src/oauth-service/createOAuthService.ts +2 -2
- package/src/oauth-service/types.ts +1 -1
- package/src/performance/createNoopPerformanceTracker.ts +2 -2
- package/src/performance/createPerformanceTracker.ts +1 -1
- package/src/performance/index.ts +3 -3
- package/src/session-initialization/createSessionInitializationService.test.ts +4 -4
- package/src/session-initialization/createSessionInitializationService.ts +32 -41
- package/src/session-repository/createSessionClient.ts +1 -1
- package/src/session-repository/createSessionRepository.test.ts +5 -5
- package/src/session-repository/createSessionRepository.ts +14 -14
- package/src/session-repository/errors.ts +1 -1
- package/src/session-repository/types.ts +1 -1
- package/src/session-service/createNoopSessionService.ts +2 -2
- package/src/session-service/createSessionService.test.ts +29 -29
- package/src/session-service/createSessionService.ts +8 -8
- package/src/session-service/types.ts +3 -3
- package/src/session-storage/createSessionStorage.ts +1 -1
- package/src/session.integration.test.ts +130 -94
- package/src/sessionLifecycle.integration.test.ts +22 -22
- package/src/test-utils/createLocalCookieTransport.ts +1 -1
- package/src/test-utils/createLocalHeaderTransport.ts +1 -1
- package/src/test-utils/mocks.ts +3 -3
- package/src/test-utils.ts +24 -24
- 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 +10 -3
- 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/src/lux-identifier/createLuxIdentifierService.ts +0 -19
- package/src/lux-identifier/luxIdentifierQuery.ts +0 -20
- package/src/lux-identifier/types.ts +0 -11
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { createChallengeSolverService } from '@
|
|
2
|
-
import { createHashcashSolver } from '@
|
|
3
|
-
import { createNoneMockSolver } from '@
|
|
4
|
-
import { createTurnstileMockSolver } from '@
|
|
5
|
-
import type { PerformanceTracker } from '@
|
|
6
|
-
import { createSessionInitializationService } from '@
|
|
7
|
-
import { createSessionClient } from '@
|
|
8
|
-
import { createSessionRepository } from '@
|
|
9
|
-
import { createSessionService } from '@
|
|
10
|
-
import type { SessionService } from '@
|
|
11
|
-
import { ChallengeType } from '@
|
|
1
|
+
import { createChallengeSolverService } from '@luxexchange/sessions/src/challenge-solvers/createChallengeSolverService'
|
|
2
|
+
import { createHashcashSolver } from '@luxexchange/sessions/src/challenge-solvers/createHashcashSolver'
|
|
3
|
+
import { createNoneMockSolver } from '@luxexchange/sessions/src/challenge-solvers/createNoneMockSolver'
|
|
4
|
+
import { createTurnstileMockSolver } from '@luxexchange/sessions/src/challenge-solvers/createTurnstileMockSolver'
|
|
5
|
+
import type { PerformanceTracker } from '@luxexchange/sessions/src/performance/types'
|
|
6
|
+
import { createSessionInitializationService } from '@luxexchange/sessions/src/session-initialization/createSessionInitializationService'
|
|
7
|
+
import { createSessionClient } from '@luxexchange/sessions/src/session-repository/createSessionClient'
|
|
8
|
+
import { createSessionRepository } from '@luxexchange/sessions/src/session-repository/createSessionRepository'
|
|
9
|
+
import { createSessionService } from '@luxexchange/sessions/src/session-service/createSessionService'
|
|
10
|
+
import type { SessionService } from '@luxexchange/sessions/src/session-service/types'
|
|
11
|
+
import { ChallengeType } from '@luxexchange/sessions/src/session-service/types'
|
|
12
12
|
import {
|
|
13
13
|
InMemoryDeviceIdService,
|
|
14
14
|
InMemorySessionStorage,
|
|
15
15
|
InMemoryLuxIdentifierService,
|
|
16
|
-
} from '@
|
|
16
|
+
} from '@luxexchange/sessions/src/test-utils'
|
|
17
17
|
import {
|
|
18
18
|
createCookieJar,
|
|
19
19
|
createLocalCookieTransport,
|
|
20
|
-
} from '@
|
|
21
|
-
import { createLocalHeaderTransport } from '@
|
|
20
|
+
} from '@luxexchange/sessions/src/test-utils/createLocalCookieTransport'
|
|
21
|
+
import { createLocalHeaderTransport } from '@luxexchange/sessions/src/test-utils/createLocalHeaderTransport'
|
|
22
22
|
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
23
23
|
|
|
24
24
|
// Mock performance tracker for testing
|
|
@@ -39,6 +39,7 @@ const BACKEND_URL = 'https://entry-gateway.backend-staging.api.lux.org'
|
|
|
39
39
|
// Web Platform Tests (Turnstile + Hashcash)
|
|
40
40
|
// =============================================================================
|
|
41
41
|
// Web uses Turnstile (browser CAPTCHA) first, then falls back to Hashcash
|
|
42
|
+
// These tests hit a staging backend that occasionally returns transient 502s.
|
|
42
43
|
describe('Real Backend Integration - Web (Turnstile + Hashcash)', () => {
|
|
43
44
|
let sessionService: SessionService
|
|
44
45
|
let sessionStorage: InMemorySessionStorage
|
|
@@ -76,7 +77,7 @@ describe('Real Backend Integration - Web (Turnstile + Hashcash)', () => {
|
|
|
76
77
|
await sessionStorage.clear()
|
|
77
78
|
})
|
|
78
79
|
|
|
79
|
-
it('initializes session with cookie, empty response sessionId', async () => {
|
|
80
|
+
it('initializes session with cookie, empty response sessionId', { timeout: 30000, retry: 5 }, async () => {
|
|
80
81
|
const manualInitService = createSessionInitializationService({
|
|
81
82
|
getSessionService: () => sessionService,
|
|
82
83
|
challengeSolverService,
|
|
@@ -96,9 +97,9 @@ describe('Real Backend Integration - Web (Turnstile + Hashcash)', () => {
|
|
|
96
97
|
// Session is NOT stored locally yet because challenge is needed
|
|
97
98
|
const sessionState = await sessionService.getSessionState()
|
|
98
99
|
expect(sessionState).toBeNull()
|
|
99
|
-
}
|
|
100
|
+
})
|
|
100
101
|
|
|
101
|
-
it('receives Turnstile challenge first', async () => {
|
|
102
|
+
it('receives Turnstile challenge first', { timeout: 30000, retry: 5 }, async () => {
|
|
102
103
|
const manualInitService = createSessionInitializationService({
|
|
103
104
|
getSessionService: () => sessionService,
|
|
104
105
|
challengeSolverService,
|
|
@@ -113,9 +114,9 @@ describe('Real Backend Integration - Web (Turnstile + Hashcash)', () => {
|
|
|
113
114
|
// Web gets Turnstile first (browser-based CAPTCHA)
|
|
114
115
|
expect(challenge.challengeType).toBe(ChallengeType.TURNSTILE)
|
|
115
116
|
expect(challenge.challengeId).toBeTruthy()
|
|
116
|
-
}
|
|
117
|
+
})
|
|
117
118
|
|
|
118
|
-
it('falls back to Hashcash after Turnstile fails', async () => {
|
|
119
|
+
it('falls back to Hashcash after Turnstile fails', { timeout: 30000, retry: 5 }, async () => {
|
|
119
120
|
const manualInitService = createSessionInitializationService({
|
|
120
121
|
getSessionService: () => sessionService,
|
|
121
122
|
challengeSolverService,
|
|
@@ -151,56 +152,72 @@ describe('Real Backend Integration - Web (Turnstile + Hashcash)', () => {
|
|
|
151
152
|
const hashcashChallenge = await sessionService.requestChallenge()
|
|
152
153
|
expect(hashcashChallenge.challengeType).toBe(ChallengeType.HASHCASH)
|
|
153
154
|
expect(hashcashChallenge.challengeId).toBeTruthy()
|
|
154
|
-
}
|
|
155
|
+
})
|
|
155
156
|
|
|
156
|
-
it('successfully upgrades session with Hashcash after Turnstile fails', { timeout: 60000, retry:
|
|
157
|
-
|
|
158
|
-
getSessionService: () => sessionService,
|
|
159
|
-
challengeSolverService,
|
|
160
|
-
performanceTracker: createMockPerformanceTracker(),
|
|
161
|
-
getIsSessionUpgradeAutoEnabled: () => false,
|
|
162
|
-
})
|
|
157
|
+
it('successfully upgrades session with Hashcash after Turnstile fails', { timeout: 60000, retry: 5 }, async () => {
|
|
158
|
+
let lastError: unknown
|
|
163
159
|
|
|
164
|
-
|
|
160
|
+
// Some staging responses intermittently return HTTP 502 and leave session state invalid.
|
|
161
|
+
// Retry the full flow from a clean state so we don't carry over a broken session cookie.
|
|
162
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
163
|
+
try {
|
|
164
|
+
const manualInitService = createSessionInitializationService({
|
|
165
|
+
getSessionService: () => sessionService,
|
|
166
|
+
challengeSolverService,
|
|
167
|
+
performanceTracker: createMockPerformanceTracker(),
|
|
168
|
+
getIsSessionUpgradeAutoEnabled: () => false,
|
|
169
|
+
})
|
|
165
170
|
|
|
166
|
-
|
|
167
|
-
const turnstileChallenge = await sessionService.requestChallenge()
|
|
168
|
-
const turnstileSolver = challengeSolverService.getSolver(ChallengeType.TURNSTILE)
|
|
169
|
-
const turnstileSolution = await turnstileSolver?.solve({
|
|
170
|
-
challengeId: turnstileChallenge.challengeId,
|
|
171
|
-
challengeType: turnstileChallenge.challengeType,
|
|
172
|
-
extra: turnstileChallenge.extra,
|
|
173
|
-
})
|
|
171
|
+
await manualInitService.initialize()
|
|
174
172
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
173
|
+
// Turnstile attempt (fails)
|
|
174
|
+
const turnstileChallenge = await sessionService.requestChallenge()
|
|
175
|
+
const turnstileSolver = challengeSolverService.getSolver(ChallengeType.TURNSTILE)
|
|
176
|
+
const turnstileSolution = await turnstileSolver?.solve({
|
|
177
|
+
challengeId: turnstileChallenge.challengeId,
|
|
178
|
+
challengeType: turnstileChallenge.challengeType,
|
|
179
|
+
extra: turnstileChallenge.extra,
|
|
180
|
+
})
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
const turnstileResult = await sessionService.verifySession({
|
|
183
|
+
solution: turnstileSolution || '',
|
|
184
|
+
challengeId: turnstileChallenge.challengeId,
|
|
185
|
+
challengeType: turnstileChallenge.challengeType,
|
|
186
|
+
})
|
|
187
|
+
expect(turnstileResult.retry).toBe(true)
|
|
185
188
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
challengeType: hashcashChallenge.challengeType,
|
|
190
|
-
extra: hashcashChallenge.extra,
|
|
191
|
-
})
|
|
189
|
+
// Hashcash attempt
|
|
190
|
+
const hashcashChallenge = await sessionService.requestChallenge()
|
|
191
|
+
expect(hashcashChallenge.challengeType).toBe(ChallengeType.HASHCASH)
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
193
|
+
const hashcashSolver = challengeSolverService.getSolver(ChallengeType.HASHCASH)
|
|
194
|
+
const hashcashSolution = await hashcashSolver?.solve({
|
|
195
|
+
challengeId: hashcashChallenge.challengeId,
|
|
196
|
+
challengeType: hashcashChallenge.challengeType,
|
|
197
|
+
extra: hashcashChallenge.extra,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const hashcashResult = await sessionService.verifySession({
|
|
201
|
+
solution: hashcashSolution || '',
|
|
202
|
+
challengeId: hashcashChallenge.challengeId,
|
|
203
|
+
challengeType: hashcashChallenge.challengeType,
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// Success!
|
|
207
|
+
expect(hashcashResult.retry).toBe(false)
|
|
208
|
+
return
|
|
209
|
+
} catch (error) {
|
|
210
|
+
lastError = error
|
|
211
|
+
await sessionService.removeSession()
|
|
212
|
+
cookieJar.clear()
|
|
213
|
+
await sessionStorage.clear()
|
|
214
|
+
}
|
|
215
|
+
}
|
|
198
216
|
|
|
199
|
-
|
|
200
|
-
expect(hashcashResult.retry).toBe(false)
|
|
217
|
+
throw lastError instanceof Error ? lastError : new Error('Failed to complete Hashcash upgrade flow')
|
|
201
218
|
})
|
|
202
219
|
|
|
203
|
-
it('completes auto-upgrade flow (Turnstile fail → Hashcash success)', { timeout: 60000, retry:
|
|
220
|
+
it('completes auto-upgrade flow (Turnstile fail → Hashcash success)', { timeout: 60000, retry: 5 }, async () => {
|
|
204
221
|
const autoInitService = createSessionInitializationService({
|
|
205
222
|
getSessionService: () => sessionService,
|
|
206
223
|
challengeSolverService,
|
|
@@ -219,38 +236,57 @@ describe('Real Backend Integration - Web (Turnstile + Hashcash)', () => {
|
|
|
219
236
|
|
|
220
237
|
it(
|
|
221
238
|
'calls initSession on reinit - backend handles session reuse via cookie',
|
|
222
|
-
{ timeout: 60000, retry:
|
|
239
|
+
{ timeout: 60000, retry: 5 },
|
|
223
240
|
async () => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
241
|
+
let lastError: unknown
|
|
242
|
+
|
|
243
|
+
// Re-run the full reinit flow from a clean cookie/session state when staging is unstable.
|
|
244
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
245
|
+
try {
|
|
246
|
+
await sessionService.removeSession()
|
|
247
|
+
cookieJar.clear()
|
|
248
|
+
await sessionStorage.clear()
|
|
249
|
+
|
|
250
|
+
const autoInitService = createSessionInitializationService({
|
|
251
|
+
getSessionService: () => sessionService,
|
|
252
|
+
challengeSolverService,
|
|
253
|
+
performanceTracker: createMockPerformanceTracker(),
|
|
254
|
+
getIsSessionUpgradeAutoEnabled: () => true,
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
await autoInitService.initialize()
|
|
258
|
+
|
|
259
|
+
// Cookie should be set from first init
|
|
260
|
+
expect(cookieJar.has('x-session-id')).toBe(true)
|
|
261
|
+
const originalSessionId = cookieJar.get('x-session-id')
|
|
262
|
+
|
|
263
|
+
// Simulate page refresh - call initialize again
|
|
264
|
+
// Backend receives cookie and decides to reuse session
|
|
265
|
+
const reinitService = createSessionInitializationService({
|
|
266
|
+
getSessionService: () => sessionService,
|
|
267
|
+
challengeSolverService,
|
|
268
|
+
performanceTracker: createMockPerformanceTracker(),
|
|
269
|
+
getIsSessionUpgradeAutoEnabled: () => true,
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
await reinitService.initialize()
|
|
273
|
+
|
|
274
|
+
// Backend should reuse session - cookie remains the same
|
|
275
|
+
expect(cookieJar.get('x-session-id')).toBe(originalSessionId)
|
|
276
|
+
return
|
|
277
|
+
} catch (error) {
|
|
278
|
+
lastError = error
|
|
279
|
+
await sessionService.removeSession()
|
|
280
|
+
cookieJar.clear()
|
|
281
|
+
await sessionStorage.clear()
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
throw lastError instanceof Error ? lastError : new Error('Failed to verify cookie-based session reuse flow')
|
|
250
286
|
},
|
|
251
287
|
)
|
|
252
288
|
|
|
253
|
-
it('fires analytics callbacks during auto-upgrade flow', { timeout: 60000, retry:
|
|
289
|
+
it('fires analytics callbacks during auto-upgrade flow', { timeout: 60000, retry: 5 }, async () => {
|
|
254
290
|
const analytics = {
|
|
255
291
|
onInitStarted: vi.fn(),
|
|
256
292
|
onInitCompleted: vi.fn(),
|
|
@@ -351,7 +387,7 @@ describe.each(NON_WEB_PLATFORMS)(
|
|
|
351
387
|
await luxIdentifierService.removeLuxIdentifier()
|
|
352
388
|
})
|
|
353
389
|
|
|
354
|
-
it('initializes session with session ID and device ID stored locally', async () => {
|
|
390
|
+
it('initializes session with session ID and device ID stored locally', { timeout: 30000, retry: 5 }, async () => {
|
|
355
391
|
const manualInitService = createSessionInitializationService({
|
|
356
392
|
getSessionService: () => sessionService,
|
|
357
393
|
challengeSolverService,
|
|
@@ -372,9 +408,9 @@ describe.each(NON_WEB_PLATFORMS)(
|
|
|
372
408
|
// Device ID should also be returned and stored
|
|
373
409
|
const storedDeviceId = await deviceIdService.getDeviceId()
|
|
374
410
|
expect(storedDeviceId).toBeTruthy()
|
|
375
|
-
}
|
|
411
|
+
})
|
|
376
412
|
|
|
377
|
-
it('receives Hashcash challenge directly (no Turnstile)', async () => {
|
|
413
|
+
it('receives Hashcash challenge directly (no Turnstile)', { timeout: 30000, retry: 5 }, async () => {
|
|
378
414
|
const manualInitService = createSessionInitializationService({
|
|
379
415
|
getSessionService: () => sessionService,
|
|
380
416
|
challengeSolverService,
|
|
@@ -389,9 +425,9 @@ describe.each(NON_WEB_PLATFORMS)(
|
|
|
389
425
|
// Non-web gets Hashcash directly (Turnstile is browser-only)
|
|
390
426
|
expect(challenge.challengeType).toBe(ChallengeType.HASHCASH)
|
|
391
427
|
expect(challenge.challengeId).toBeTruthy()
|
|
392
|
-
}
|
|
428
|
+
})
|
|
393
429
|
|
|
394
|
-
it('successfully upgrades session with Hashcash', { timeout: 60000, retry:
|
|
430
|
+
it('successfully upgrades session with Hashcash', { timeout: 60000, retry: 5 }, async () => {
|
|
395
431
|
const manualInitService = createSessionInitializationService({
|
|
396
432
|
getSessionService: () => sessionService,
|
|
397
433
|
challengeSolverService,
|
|
@@ -422,7 +458,7 @@ describe.each(NON_WEB_PLATFORMS)(
|
|
|
422
458
|
expect(hashcashResult.retry).toBe(false)
|
|
423
459
|
})
|
|
424
460
|
|
|
425
|
-
it('completes auto-upgrade flow', { timeout: 60000, retry:
|
|
461
|
+
it('completes auto-upgrade flow', { timeout: 60000, retry: 5 }, async () => {
|
|
426
462
|
const autoInitService = createSessionInitializationService({
|
|
427
463
|
getSessionService: () => sessionService,
|
|
428
464
|
challengeSolverService,
|
|
@@ -440,7 +476,7 @@ describe.each(NON_WEB_PLATFORMS)(
|
|
|
440
476
|
|
|
441
477
|
it(
|
|
442
478
|
'calls initSession on reinit - backend handles session reuse via X-Session-ID header',
|
|
443
|
-
{ timeout: 60000, retry:
|
|
479
|
+
{ timeout: 60000, retry: 5 },
|
|
444
480
|
async () => {
|
|
445
481
|
// First: Complete auto-upgrade flow
|
|
446
482
|
const autoInitService = createSessionInitializationService({
|
|
@@ -8,23 +8,23 @@ import {
|
|
|
8
8
|
SignoutResponse,
|
|
9
9
|
UpdateSessionResponse,
|
|
10
10
|
VerifyResponse,
|
|
11
|
-
} from '@
|
|
12
|
-
import { createSessionRepository } from '@
|
|
13
|
-
import { createSessionService } from '@
|
|
14
|
-
import type { SessionService } from '@
|
|
11
|
+
} from '@luxamm/client-platform-service/dist/uniswap/platformservice/v1/sessionService_pb'
|
|
12
|
+
import { createSessionRepository } from '@luxexchange/sessions/src/session-repository/createSessionRepository'
|
|
13
|
+
import { createSessionService } from '@luxexchange/sessions/src/session-service/createSessionService'
|
|
14
|
+
import type { SessionService } from '@luxexchange/sessions/src/session-service/types'
|
|
15
15
|
import {
|
|
16
16
|
createMockSessionClient,
|
|
17
17
|
InMemoryDeviceIdService,
|
|
18
18
|
InMemorySessionStorage,
|
|
19
|
-
|
|
19
|
+
InMemoryLxIdentifierService,
|
|
20
20
|
type MockEndpoints,
|
|
21
|
-
} from '@
|
|
21
|
+
} from '@luxexchange/sessions/src/test-utils'
|
|
22
22
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
23
23
|
|
|
24
24
|
describe('Session Lifecycle Integration Tests', () => {
|
|
25
25
|
let sessionStorage: InMemorySessionStorage
|
|
26
26
|
let deviceIdService: InMemoryDeviceIdService
|
|
27
|
-
let
|
|
27
|
+
let lxIdentifierService: InMemoryLxIdentifierService
|
|
28
28
|
let sessionService: SessionService
|
|
29
29
|
let mockEndpoints: MockEndpoints
|
|
30
30
|
|
|
@@ -32,42 +32,42 @@ describe('Session Lifecycle Integration Tests', () => {
|
|
|
32
32
|
// Initialize in-memory storage
|
|
33
33
|
sessionStorage = new InMemorySessionStorage()
|
|
34
34
|
deviceIdService = new InMemoryDeviceIdService()
|
|
35
|
-
|
|
35
|
+
lxIdentifierService = new InMemoryLxIdentifierService()
|
|
36
36
|
|
|
37
37
|
// Set up mock endpoints with default responses
|
|
38
38
|
mockEndpoints = {
|
|
39
|
-
'/
|
|
39
|
+
'/uniswap.platformservice.v1.SessionService/InitSession': async (): Promise<InitSessionResponse> => {
|
|
40
40
|
return new InitSessionResponse({
|
|
41
41
|
sessionId: 'test-session-123',
|
|
42
42
|
needChallenge: false,
|
|
43
43
|
extra: {},
|
|
44
44
|
})
|
|
45
45
|
},
|
|
46
|
-
'/
|
|
46
|
+
'/uniswap.platformservice.v1.SessionService/Challenge': async (): Promise<ChallengeResponse> => {
|
|
47
47
|
return new ChallengeResponse({
|
|
48
48
|
challengeId: 'challenge-123',
|
|
49
49
|
challengeType: ChallengeType.TURNSTILE,
|
|
50
50
|
extra: { sitekey: 'test-key' },
|
|
51
51
|
})
|
|
52
52
|
},
|
|
53
|
-
'/
|
|
53
|
+
'/uniswap.platformservice.v1.SessionService/Verify': async (): Promise<VerifyResponse> => {
|
|
54
54
|
return new VerifyResponse({
|
|
55
55
|
retry: false,
|
|
56
56
|
})
|
|
57
57
|
},
|
|
58
|
-
'/
|
|
58
|
+
'/uniswap.platformservice.v1.SessionService/DeleteSession': async (): Promise<DeleteSessionResponse> => {
|
|
59
59
|
return new DeleteSessionResponse({})
|
|
60
60
|
},
|
|
61
|
-
'/
|
|
61
|
+
'/uniswap.platformservice.v1.SessionService/IntrospectSession': async (): Promise<IntrospectSessionResponse> => {
|
|
62
62
|
return new IntrospectSessionResponse({})
|
|
63
63
|
},
|
|
64
|
-
'/
|
|
64
|
+
'/uniswap.platformservice.v1.SessionService/UpdateSession': async (): Promise<UpdateSessionResponse> => {
|
|
65
65
|
return new UpdateSessionResponse({})
|
|
66
66
|
},
|
|
67
|
-
'/
|
|
67
|
+
'/uniswap.platformservice.v1.SessionService/GetChallengeTypes': async (): Promise<GetChallengeTypesResponse> => {
|
|
68
68
|
return new GetChallengeTypesResponse({ challengeTypes: [] })
|
|
69
69
|
},
|
|
70
|
-
'/
|
|
70
|
+
'/uniswap.platformservice.v1.SessionService/Signout': async (): Promise<SignoutResponse> => {
|
|
71
71
|
return new SignoutResponse({})
|
|
72
72
|
},
|
|
73
73
|
}
|
|
@@ -84,7 +84,7 @@ describe('Session Lifecycle Integration Tests', () => {
|
|
|
84
84
|
sessionService = createSessionService({
|
|
85
85
|
sessionStorage,
|
|
86
86
|
deviceIdService,
|
|
87
|
-
|
|
87
|
+
lxIdentifierService,
|
|
88
88
|
sessionRepository,
|
|
89
89
|
})
|
|
90
90
|
})
|
|
@@ -111,7 +111,7 @@ describe('Session Lifecycle Integration Tests', () => {
|
|
|
111
111
|
|
|
112
112
|
it('handles session initialization without challenge requirement', async () => {
|
|
113
113
|
// Override default to return needChallenge: false
|
|
114
|
-
mockEndpoints['/
|
|
114
|
+
mockEndpoints['/uniswap.platformservice.v1.SessionService/InitSession'] =
|
|
115
115
|
async (): Promise<InitSessionResponse> => {
|
|
116
116
|
return new InitSessionResponse({
|
|
117
117
|
sessionId: 'simple-session-123',
|
|
@@ -151,7 +151,7 @@ describe('Session Lifecycle Integration Tests', () => {
|
|
|
151
151
|
})
|
|
152
152
|
|
|
153
153
|
it('stores session ID when provided (mobile/extension behavior)', async () => {
|
|
154
|
-
mockEndpoints['/
|
|
154
|
+
mockEndpoints['/uniswap.platformservice.v1.SessionService/InitSession'] =
|
|
155
155
|
async (): Promise<InitSessionResponse> => {
|
|
156
156
|
return new InitSessionResponse({
|
|
157
157
|
sessionId: 'mobile-session-123',
|
|
@@ -170,7 +170,7 @@ describe('Session Lifecycle Integration Tests', () => {
|
|
|
170
170
|
})
|
|
171
171
|
|
|
172
172
|
it('handles web sessions without storing session ID when undefined', async () => {
|
|
173
|
-
mockEndpoints['/
|
|
173
|
+
mockEndpoints['/uniswap.platformservice.v1.SessionService/InitSession'] =
|
|
174
174
|
async (): Promise<InitSessionResponse> => {
|
|
175
175
|
return new InitSessionResponse({
|
|
176
176
|
sessionId: undefined,
|
|
@@ -216,7 +216,7 @@ describe('Session Lifecycle Integration Tests', () => {
|
|
|
216
216
|
expect(firstSession?.sessionId).toBe('test-session-123')
|
|
217
217
|
|
|
218
218
|
// Update mock to return different session
|
|
219
|
-
mockEndpoints['/
|
|
219
|
+
mockEndpoints['/uniswap.platformservice.v1.SessionService/InitSession'] =
|
|
220
220
|
async (): Promise<InitSessionResponse> => {
|
|
221
221
|
return new InitSessionResponse({
|
|
222
222
|
sessionId: 'new-session-789',
|
|
@@ -233,7 +233,7 @@ describe('Session Lifecycle Integration Tests', () => {
|
|
|
233
233
|
})
|
|
234
234
|
|
|
235
235
|
it('sets and retrieves device ID through init response', async () => {
|
|
236
|
-
mockEndpoints['/
|
|
236
|
+
mockEndpoints['/uniswap.platformservice.v1.SessionService/InitSession'] =
|
|
237
237
|
async (): Promise<InitSessionResponse> => {
|
|
238
238
|
return new InitSessionResponse({
|
|
239
239
|
sessionId: 'device-test-session',
|
|
@@ -14,7 +14,7 @@ export function createLocalCookieTransport(options: { baseUrl: string; cookieJar
|
|
|
14
14
|
interceptors: [
|
|
15
15
|
(next) => async (request) => {
|
|
16
16
|
// Add required headers that backend expects
|
|
17
|
-
request.header.set('x-request-source', '
|
|
17
|
+
request.header.set('x-request-source', 'lx-web')
|
|
18
18
|
|
|
19
19
|
// Simulate browser cookie behavior: add stored cookies to request
|
|
20
20
|
if (cookieJar.size > 0) {
|
|
@@ -3,7 +3,7 @@ import { createConnectTransport } from '@connectrpc/connect-web'
|
|
|
3
3
|
|
|
4
4
|
interface CreateLocalHeaderTransportOptions {
|
|
5
5
|
baseUrl: string
|
|
6
|
-
requestSource: '
|
|
6
|
+
requestSource: 'lx-ios' | 'lx-android' | 'lx-extension'
|
|
7
7
|
getSessionId: () => Promise<string | null>
|
|
8
8
|
getDeviceId: () => Promise<string | null>
|
|
9
9
|
}
|
package/src/test-utils/mocks.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ChallengeSolver, ChallengeSolverService } from '@
|
|
2
|
-
import type { SessionService } from '@
|
|
3
|
-
import { ChallengeType } from '@
|
|
1
|
+
import type { ChallengeSolver, ChallengeSolverService } from '@luxexchange/sessions/src/challenge-solvers/types'
|
|
2
|
+
import type { SessionService } from '@luxexchange/sessions/src/session-service/types'
|
|
3
|
+
import { ChallengeType } from '@luxexchange/sessions/src/session-service/types'
|
|
4
4
|
import { vi } from 'vitest'
|
|
5
5
|
|
|
6
6
|
/**
|
package/src/test-utils.ts
CHANGED
|
@@ -17,30 +17,30 @@ import {
|
|
|
17
17
|
type UpdateSessionResponse,
|
|
18
18
|
type VerifyRequest,
|
|
19
19
|
type VerifyResponse,
|
|
20
|
-
} from '@
|
|
21
|
-
import type { DeviceIdService } from '@
|
|
22
|
-
import type { SessionServiceClient } from '@
|
|
23
|
-
import type { SessionState, SessionStorage } from '@
|
|
24
|
-
import type {
|
|
20
|
+
} from '@luxamm/client-platform-service/dist/uniswap/platformservice/v1/sessionService_pb'
|
|
21
|
+
import type { DeviceIdService } from '@luxexchange/sessions/src/device-id/types'
|
|
22
|
+
import type { SessionServiceClient } from '@luxexchange/sessions/src/session-repository/createSessionClient'
|
|
23
|
+
import type { SessionState, SessionStorage } from '@luxexchange/sessions/src/session-storage/types'
|
|
24
|
+
import type { LxIdentifierService } from '@luxexchange/sessions/src/uniswap-identifier/types'
|
|
25
25
|
// Types for our test transport
|
|
26
26
|
export interface MockEndpointHandler {
|
|
27
27
|
(request: any, headers: Record<string, string>): Promise<any>
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface MockEndpoints {
|
|
31
|
-
'/
|
|
32
|
-
'/
|
|
33
|
-
'/
|
|
34
|
-
'/
|
|
35
|
-
'/
|
|
36
|
-
'/
|
|
37
|
-
'/
|
|
31
|
+
'/uniswap.platformservice.v1.SessionService/InitSession': MockEndpointHandler
|
|
32
|
+
'/uniswap.platformservice.v1.SessionService/Challenge': MockEndpointHandler
|
|
33
|
+
'/uniswap.platformservice.v1.SessionService/Verify': MockEndpointHandler
|
|
34
|
+
'/uniswap.platformservice.v1.SessionService/IntrospectSession': MockEndpointHandler
|
|
35
|
+
'/uniswap.platformservice.v1.SessionService/UpdateSession': MockEndpointHandler
|
|
36
|
+
'/uniswap.platformservice.v1.SessionService/GetChallengeTypes': MockEndpointHandler
|
|
37
|
+
'/uniswap.platformservice.v1.SessionService/Signout': MockEndpointHandler
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// Test transport that intercepts requests and returns mock responses
|
|
41
41
|
export function createTestTransport(mockEndpoints: MockEndpoints): ReturnType<typeof createConnectTransport> {
|
|
42
42
|
return createConnectTransport({
|
|
43
|
-
baseUrl: 'https://test.api.lux.
|
|
43
|
+
baseUrl: 'https://test.api.lux.exchange',
|
|
44
44
|
interceptors: [
|
|
45
45
|
(_next) => async (request) => {
|
|
46
46
|
const url = request.url
|
|
@@ -109,18 +109,18 @@ export class InMemoryDeviceIdService implements DeviceIdService {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
export class
|
|
112
|
+
export class InMemoryLxIdentifierService implements LxIdentifierService {
|
|
113
113
|
private identifier: string | null = null
|
|
114
114
|
|
|
115
|
-
async
|
|
115
|
+
async getLxIdentifier(): Promise<string | null> {
|
|
116
116
|
return this.identifier
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
async
|
|
119
|
+
async setLxIdentifier(id: string): Promise<void> {
|
|
120
120
|
this.identifier = id
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
async
|
|
123
|
+
async removeLxIdentifier(): Promise<void> {
|
|
124
124
|
this.identifier = null
|
|
125
125
|
}
|
|
126
126
|
}
|
|
@@ -137,7 +137,7 @@ export function createMockSessionClient(
|
|
|
137
137
|
request: PartialMessage<InitSessionRequest>,
|
|
138
138
|
_options?: CallOptions,
|
|
139
139
|
): Promise<InitSessionResponse> => {
|
|
140
|
-
const response = await mockEndpoints['/
|
|
140
|
+
const response = await mockEndpoints['/uniswap.platformservice.v1.SessionService/InitSession'](request, {})
|
|
141
141
|
return response as InitSessionResponse
|
|
142
142
|
},
|
|
143
143
|
challenge: async (
|
|
@@ -154,7 +154,7 @@ export function createMockSessionClient(
|
|
|
154
154
|
headers['X-Device-ID'] = deviceId
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
const response = await mockEndpoints['/
|
|
157
|
+
const response = await mockEndpoints['/uniswap.platformservice.v1.SessionService/Challenge'](request, headers)
|
|
158
158
|
return response as ChallengeResponse
|
|
159
159
|
},
|
|
160
160
|
verify: async (request: PartialMessage<VerifyRequest>, _options?: CallOptions): Promise<VerifyResponse> => {
|
|
@@ -168,32 +168,32 @@ export function createMockSessionClient(
|
|
|
168
168
|
headers['X-Device-ID'] = deviceId
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
const response = await mockEndpoints['/
|
|
171
|
+
const response = await mockEndpoints['/uniswap.platformservice.v1.SessionService/Verify'](request, headers)
|
|
172
172
|
return response as VerifyResponse
|
|
173
173
|
},
|
|
174
174
|
introspectSession: async (
|
|
175
175
|
request: PartialMessage<IntrospectSessionRequest>,
|
|
176
176
|
_options?: CallOptions,
|
|
177
177
|
): Promise<IntrospectSessionResponse> => {
|
|
178
|
-
const response = await mockEndpoints['/
|
|
178
|
+
const response = await mockEndpoints['/uniswap.platformservice.v1.SessionService/IntrospectSession'](request, {})
|
|
179
179
|
return response as IntrospectSessionResponse
|
|
180
180
|
},
|
|
181
181
|
updateSession: async (
|
|
182
182
|
request: PartialMessage<UpdateSessionRequest>,
|
|
183
183
|
_options?: CallOptions,
|
|
184
184
|
): Promise<UpdateSessionResponse> => {
|
|
185
|
-
const response = await mockEndpoints['/
|
|
185
|
+
const response = await mockEndpoints['/uniswap.platformservice.v1.SessionService/UpdateSession'](request, {})
|
|
186
186
|
return response as UpdateSessionResponse
|
|
187
187
|
},
|
|
188
188
|
getChallengeTypes: async (
|
|
189
189
|
request: PartialMessage<GetChallengeTypesRequest>,
|
|
190
190
|
_options?: CallOptions,
|
|
191
191
|
): Promise<GetChallengeTypesResponse> => {
|
|
192
|
-
const response = await mockEndpoints['/
|
|
192
|
+
const response = await mockEndpoints['/uniswap.platformservice.v1.SessionService/GetChallengeTypes'](request, {})
|
|
193
193
|
return response as GetChallengeTypesResponse
|
|
194
194
|
},
|
|
195
195
|
signout: async (request: PartialMessage<SignoutRequest>, _options?: CallOptions): Promise<SignoutResponse> => {
|
|
196
|
-
const response = await mockEndpoints['/
|
|
196
|
+
const response = await mockEndpoints['/uniswap.platformservice.v1.SessionService/Signout'](request, {})
|
|
197
197
|
return response as SignoutResponse
|
|
198
198
|
},
|
|
199
199
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { LxIdentifierService } from '@luxexchange/sessions/src/uniswap-identifier/types'
|
|
2
|
+
|
|
3
|
+
function createLxIdentifierService(ctx: {
|
|
4
|
+
getLxIdentifier: () => Promise<string | null>
|
|
5
|
+
setLxIdentifier: (identifier: string) => Promise<void>
|
|
6
|
+
removeLxIdentifier: () => Promise<void>
|
|
7
|
+
}): LxIdentifierService {
|
|
8
|
+
const getLxIdentifier = ctx.getLxIdentifier
|
|
9
|
+
const setLxIdentifier = ctx.setLxIdentifier
|
|
10
|
+
const removeLxIdentifier = ctx.removeLxIdentifier
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
getLxIdentifier,
|
|
14
|
+
setLxIdentifier,
|
|
15
|
+
removeLxIdentifier,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { createLxIdentifierService }
|