@luxexchange/sessions 1.0.2 → 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 +1 -7
- package/src/challenge-solvers/createChallengeSolverService.ts +2 -2
- package/src/challenge-solvers/createHashcashMockSolver.ts +2 -2
- package/src/challenge-solvers/createHashcashSolver.test.ts +6 -6
- package/src/challenge-solvers/createHashcashSolver.ts +18 -3
- package/src/challenge-solvers/createNoneMockSolver.ts +1 -1
- package/src/challenge-solvers/createTurnstileMockSolver.ts +2 -2
- package/src/challenge-solvers/createTurnstileSolver.ts +9 -5
- package/src/challenge-solvers/hashcash/core.native.ts +1 -1
- package/src/challenge-solvers/hashcash/core.test.ts +10 -10
- package/src/challenge-solvers/hashcash/core.ts +1 -1
- package/src/challenge-solvers/hashcash/createWorkerHashcashSolver.test.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/createHashcashWorkerChannel.native.ts +1 -1
- package/src/challenge-solvers/hashcash/worker/createHashcashWorkerChannel.ts +1 -1
- package/src/challenge-solvers/hashcash/worker/hashcash.worker.ts +3 -3
- package/src/challenge-solvers/turnstileSolver.integration.test.ts +1 -1
- package/src/challengeFlow.integration.test.ts +41 -41
- package/src/device-id/createDeviceIdService.ts +1 -1
- package/src/index.ts +8 -6
- package/src/oauth-service/types.ts +1 -1
- package/src/session-initialization/createSessionInitializationService.ts +29 -38
- package/src/session-repository/createSessionClient.ts +1 -1
- package/src/session-repository/createSessionRepository.test.ts +2 -2
- package/src/session-repository/createSessionRepository.ts +11 -11
- 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 +4 -4
- package/src/session-service/types.ts +1 -1
- package/src/session-storage/createSessionStorage.ts +1 -1
- package/src/session.integration.test.ts +116 -80
- 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,16 +1,16 @@
|
|
|
1
|
-
import type { DeviceIdService } from '@
|
|
2
|
-
import type { SessionRepository } from '@
|
|
3
|
-
import { createSessionService } from '@
|
|
4
|
-
import type { SessionService } from '@
|
|
5
|
-
import type { SessionStorage } from '@
|
|
6
|
-
import type {
|
|
1
|
+
import type { DeviceIdService } from '@luxexchange/sessions/src/device-id/types'
|
|
2
|
+
import type { SessionRepository } from '@luxexchange/sessions/src/session-repository/types'
|
|
3
|
+
import { createSessionService } from '@luxexchange/sessions/src/session-service/createSessionService'
|
|
4
|
+
import type { SessionService } from '@luxexchange/sessions/src/session-service/types'
|
|
5
|
+
import type { SessionStorage } from '@luxexchange/sessions/src/session-storage/types'
|
|
6
|
+
import type { LxIdentifierService } from '@luxexchange/sessions/src/uniswap-identifier/types'
|
|
7
7
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
8
8
|
|
|
9
9
|
describe('createSessionService', () => {
|
|
10
10
|
let storage: SessionStorage
|
|
11
11
|
let repository: SessionRepository
|
|
12
12
|
let deviceIdService: DeviceIdService
|
|
13
|
-
let
|
|
13
|
+
let lxIdentifierService: LxIdentifierService
|
|
14
14
|
let service: SessionService
|
|
15
15
|
|
|
16
16
|
beforeEach(() => {
|
|
@@ -37,14 +37,14 @@ describe('createSessionService', () => {
|
|
|
37
37
|
},
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
let
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
let lxIdentifierData: string | null = null
|
|
41
|
+
lxIdentifierService = {
|
|
42
|
+
getLxIdentifier: async (): Promise<string | null> => lxIdentifierData,
|
|
43
|
+
setLxIdentifier: async (identifier: string): Promise<void> => {
|
|
44
|
+
lxIdentifierData = identifier
|
|
45
45
|
},
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
removeLxIdentifier: async (): Promise<void> => {
|
|
47
|
+
lxIdentifierData = null
|
|
48
48
|
},
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -68,7 +68,7 @@ describe('createSessionService', () => {
|
|
|
68
68
|
sessionStorage: storage,
|
|
69
69
|
sessionRepository: repository,
|
|
70
70
|
deviceIdService,
|
|
71
|
-
|
|
71
|
+
lxIdentifierService,
|
|
72
72
|
})
|
|
73
73
|
})
|
|
74
74
|
|
|
@@ -103,7 +103,7 @@ describe('createSessionService', () => {
|
|
|
103
103
|
sessionStorage: storage,
|
|
104
104
|
sessionRepository: repository,
|
|
105
105
|
deviceIdService,
|
|
106
|
-
|
|
106
|
+
lxIdentifierService,
|
|
107
107
|
})
|
|
108
108
|
|
|
109
109
|
await service.initSession()
|
|
@@ -178,7 +178,7 @@ describe('createSessionService', () => {
|
|
|
178
178
|
sessionStorage: storage,
|
|
179
179
|
sessionRepository: repository,
|
|
180
180
|
deviceIdService,
|
|
181
|
-
|
|
181
|
+
lxIdentifierService,
|
|
182
182
|
})
|
|
183
183
|
|
|
184
184
|
expect(await service2.getSessionState()).toEqual({ sessionId: 'test-session-123' })
|
|
@@ -201,7 +201,7 @@ describe('createSessionService', () => {
|
|
|
201
201
|
sessionStorage: storage2,
|
|
202
202
|
sessionRepository: repository,
|
|
203
203
|
deviceIdService,
|
|
204
|
-
|
|
204
|
+
lxIdentifierService,
|
|
205
205
|
})
|
|
206
206
|
|
|
207
207
|
await service.initSession()
|
|
@@ -326,8 +326,8 @@ describe('createSessionService', () => {
|
|
|
326
326
|
})
|
|
327
327
|
})
|
|
328
328
|
|
|
329
|
-
describe('
|
|
330
|
-
it('persists
|
|
329
|
+
describe('uniswap identifier handling', () => {
|
|
330
|
+
it('persists lxIdentifier when provided in extra', async () => {
|
|
331
331
|
repository.initSession = async (): Promise<{
|
|
332
332
|
sessionId?: string
|
|
333
333
|
needChallenge: boolean
|
|
@@ -335,14 +335,14 @@ describe('createSessionService', () => {
|
|
|
335
335
|
}> => ({
|
|
336
336
|
sessionId: 'test-session-123',
|
|
337
337
|
needChallenge: false,
|
|
338
|
-
extra: {
|
|
338
|
+
extra: { lxIdentifier: '71cef16f-4d99-4082-987c-a6f810f9ca7f' },
|
|
339
339
|
})
|
|
340
340
|
|
|
341
341
|
await service.initSession()
|
|
342
|
-
expect(await
|
|
342
|
+
expect(await lxIdentifierService.getLxIdentifier()).toBe('71cef16f-4d99-4082-987c-a6f810f9ca7f')
|
|
343
343
|
})
|
|
344
344
|
|
|
345
|
-
it('does not persist
|
|
345
|
+
it('does not persist lxIdentifier when not provided', async () => {
|
|
346
346
|
repository.initSession = async (): Promise<{
|
|
347
347
|
sessionId?: string
|
|
348
348
|
needChallenge: boolean
|
|
@@ -354,10 +354,10 @@ describe('createSessionService', () => {
|
|
|
354
354
|
})
|
|
355
355
|
|
|
356
356
|
await service.initSession()
|
|
357
|
-
expect(await
|
|
357
|
+
expect(await lxIdentifierService.getLxIdentifier()).toBeNull()
|
|
358
358
|
})
|
|
359
359
|
|
|
360
|
-
it('updates
|
|
360
|
+
it('updates lxIdentifier on subsequent initSession calls', async () => {
|
|
361
361
|
repository.initSession = async (): Promise<{
|
|
362
362
|
sessionId?: string
|
|
363
363
|
needChallenge: boolean
|
|
@@ -365,11 +365,11 @@ describe('createSessionService', () => {
|
|
|
365
365
|
}> => ({
|
|
366
366
|
sessionId: 'test-session-123',
|
|
367
367
|
needChallenge: false,
|
|
368
|
-
extra: {
|
|
368
|
+
extra: { lxIdentifier: 'first-identifier' },
|
|
369
369
|
})
|
|
370
370
|
|
|
371
371
|
await service.initSession()
|
|
372
|
-
expect(await
|
|
372
|
+
expect(await lxIdentifierService.getLxIdentifier()).toBe('first-identifier')
|
|
373
373
|
|
|
374
374
|
repository.initSession = async (): Promise<{
|
|
375
375
|
sessionId?: string
|
|
@@ -378,11 +378,11 @@ describe('createSessionService', () => {
|
|
|
378
378
|
}> => ({
|
|
379
379
|
sessionId: 'test-session-456',
|
|
380
380
|
needChallenge: false,
|
|
381
|
-
extra: {
|
|
381
|
+
extra: { lxIdentifier: 'second-identifier' },
|
|
382
382
|
})
|
|
383
383
|
|
|
384
384
|
await service.initSession()
|
|
385
|
-
expect(await
|
|
385
|
+
expect(await lxIdentifierService.getLxIdentifier()).toBe('second-identifier')
|
|
386
386
|
})
|
|
387
387
|
})
|
|
388
388
|
})
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
VerifySessionResponse,
|
|
10
10
|
} from '@luxexchange/sessions/src/session-service/types'
|
|
11
11
|
import type { SessionStorage } from '@luxexchange/sessions/src/session-storage/types'
|
|
12
|
-
import type {
|
|
12
|
+
import type { LxIdentifierService } from '@luxexchange/sessions/src/uniswap-identifier/types'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Creates a Session Service instance.
|
|
@@ -18,7 +18,7 @@ import type { LuxIdentifierService } from '@luxexchange/sessions/src/lux-identif
|
|
|
18
18
|
export function createSessionService(ctx: {
|
|
19
19
|
sessionStorage: SessionStorage
|
|
20
20
|
deviceIdService: DeviceIdService
|
|
21
|
-
|
|
21
|
+
lxIdentifierService: LxIdentifierService
|
|
22
22
|
sessionRepository: SessionRepository
|
|
23
23
|
}): SessionService {
|
|
24
24
|
async function initSession(): Promise<InitSessionResponse> {
|
|
@@ -29,8 +29,8 @@ export function createSessionService(ctx: {
|
|
|
29
29
|
if (result.deviceId) {
|
|
30
30
|
await ctx.deviceIdService.setDeviceId(result.deviceId)
|
|
31
31
|
}
|
|
32
|
-
if (result.extra['
|
|
33
|
-
await ctx.
|
|
32
|
+
if (result.extra['lxIdentifier']) {
|
|
33
|
+
await ctx.lxIdentifierService.setLxIdentifier(result.extra['lxIdentifier'])
|
|
34
34
|
}
|
|
35
35
|
return result
|
|
36
36
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChallengeType } from '@
|
|
1
|
+
import { ChallengeType } from '@luxamm/client-platform-service/dist/uniswap/platformservice/v1/sessionService_pb'
|
|
2
2
|
import type { TypedChallengeData } from '@luxexchange/sessions/src/session-repository/types'
|
|
3
3
|
import { SessionState } from '@luxexchange/sessions/src/session-storage/types'
|
|
4
4
|
|
|
@@ -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
|
/**
|