@luxexchange/sessions 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.depcheckrc +20 -0
- package/.eslintrc.js +21 -0
- package/README.md +1 -0
- package/env.d.ts +12 -0
- package/package.json +50 -0
- package/project.json +42 -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 +255 -0
- package/src/challenge-solvers/createNoneMockSolver.ts +11 -0
- package/src/challenge-solvers/createTurnstileMockSolver.ts +30 -0
- package/src/challenge-solvers/createTurnstileSolver.ts +353 -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 +137 -0
- package/src/lux-identifier/createLuxIdentifierService.ts +19 -0
- package/src/lux-identifier/luxIdentifierQuery.ts +20 -0
- package/src/lux-identifier/types.ts +11 -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 +193 -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 +480 -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/tsconfig.json +19 -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/src/index.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/** biome-ignore-all assist/source/organizeImports: we want to manually group exports by category */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @luxexchange/sessions
|
|
5
|
+
*
|
|
6
|
+
* This is the ONLY public entry point for the Sessions package.
|
|
7
|
+
* All exports must be explicitly listed here.
|
|
8
|
+
* Deep imports are forbidden and will be blocked by ESLint.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Device ID
|
|
12
|
+
export { createDeviceIdService } from '@luxexchange/sessions/src/device-id/createDeviceIdService'
|
|
13
|
+
export type { DeviceIdService } from '@luxexchange/sessions/src/device-id/types'
|
|
14
|
+
// Lux Identifier
|
|
15
|
+
export { createLuxIdentifierService } from '@luxexchange/sessions/src/lux-identifier/createLuxIdentifierService'
|
|
16
|
+
export { luxIdentifierQuery } from '@luxexchange/sessions/src/lux-identifier/luxIdentifierQuery'
|
|
17
|
+
export type { LuxIdentifierService } from '@luxexchange/sessions/src/lux-identifier/types'
|
|
18
|
+
// Session Repository
|
|
19
|
+
export { createSessionRepository } from '@luxexchange/sessions/src/session-repository/createSessionRepository'
|
|
20
|
+
export { ChallengeRejectedError } from '@luxexchange/sessions/src/session-repository/errors'
|
|
21
|
+
export { ChallengeFailureReason, VerifyFailureReason } from '@luxexchange/sessions/src/session-repository/types'
|
|
22
|
+
export type {
|
|
23
|
+
SessionRepository,
|
|
24
|
+
ChallengeTypeConfig,
|
|
25
|
+
TypedChallengeData,
|
|
26
|
+
TurnstileChallengeData,
|
|
27
|
+
HashCashChallengeData,
|
|
28
|
+
GitHubChallengeData,
|
|
29
|
+
} from '@luxexchange/sessions/src/session-repository/types'
|
|
30
|
+
|
|
31
|
+
// Session Service
|
|
32
|
+
export { createNoopSessionService } from '@luxexchange/sessions/src/session-service/createNoopSessionService'
|
|
33
|
+
export { createSessionService } from '@luxexchange/sessions/src/session-service/createSessionService'
|
|
34
|
+
export type {
|
|
35
|
+
SessionService,
|
|
36
|
+
InitSessionResponse,
|
|
37
|
+
ChallengeRequest,
|
|
38
|
+
ChallengeResponse,
|
|
39
|
+
VerifySessionRequest,
|
|
40
|
+
VerifySessionResponse,
|
|
41
|
+
} from '@luxexchange/sessions/src/session-service/types'
|
|
42
|
+
|
|
43
|
+
// Session Storage
|
|
44
|
+
export { createSessionStorage } from '@luxexchange/sessions/src/session-storage/createSessionStorage'
|
|
45
|
+
export type { SessionStorage, SessionState } from '@luxexchange/sessions/src/session-storage/types'
|
|
46
|
+
|
|
47
|
+
// Session Client
|
|
48
|
+
export { createSessionClient } from '@luxexchange/sessions/src/session-repository/createSessionClient'
|
|
49
|
+
export type { SessionServiceClient } from '@luxexchange/sessions/src/session-repository/createSessionClient'
|
|
50
|
+
|
|
51
|
+
// Session Initialization
|
|
52
|
+
export { createSessionInitializationService } from '@luxexchange/sessions/src/session-initialization/createSessionInitializationService'
|
|
53
|
+
export {
|
|
54
|
+
SessionError,
|
|
55
|
+
MaxChallengeRetriesError,
|
|
56
|
+
NoSolverAvailableError,
|
|
57
|
+
} from '@luxexchange/sessions/src/session-initialization/sessionErrors'
|
|
58
|
+
export type {
|
|
59
|
+
SessionInitializationService,
|
|
60
|
+
SessionInitOptions,
|
|
61
|
+
SessionInitResult,
|
|
62
|
+
SessionInitAnalytics,
|
|
63
|
+
} from '@luxexchange/sessions/src/session-initialization/createSessionInitializationService'
|
|
64
|
+
|
|
65
|
+
// Challenge Solvers
|
|
66
|
+
export { createChallengeSolverService } from '@luxexchange/sessions/src/challenge-solvers/createChallengeSolverService'
|
|
67
|
+
export { createTurnstileMockSolver } from '@luxexchange/sessions/src/challenge-solvers/createTurnstileMockSolver'
|
|
68
|
+
export { createHashcashMockSolver } from '@luxexchange/sessions/src/challenge-solvers/createHashcashMockSolver'
|
|
69
|
+
export { createNoneMockSolver } from '@luxexchange/sessions/src/challenge-solvers/createNoneMockSolver'
|
|
70
|
+
export { createTurnstileSolver } from '@luxexchange/sessions/src/challenge-solvers/createTurnstileSolver'
|
|
71
|
+
export { createHashcashSolver } from '@luxexchange/sessions/src/challenge-solvers/createHashcashSolver'
|
|
72
|
+
export { createWorkerHashcashSolver } from '@luxexchange/sessions/src/challenge-solvers/hashcash/createWorkerHashcashSolver'
|
|
73
|
+
export {
|
|
74
|
+
TurnstileScriptLoadError,
|
|
75
|
+
TurnstileApiNotAvailableError,
|
|
76
|
+
TurnstileTimeoutError,
|
|
77
|
+
TurnstileError,
|
|
78
|
+
TurnstileTokenExpiredError,
|
|
79
|
+
} from '@luxexchange/sessions/src/challenge-solvers/turnstileErrors'
|
|
80
|
+
export type {
|
|
81
|
+
ChallengeSolver,
|
|
82
|
+
ChallengeSolverService,
|
|
83
|
+
ChallengeData,
|
|
84
|
+
TurnstileScriptOptions,
|
|
85
|
+
} from '@luxexchange/sessions/src/challenge-solvers/types'
|
|
86
|
+
export type {
|
|
87
|
+
CreateTurnstileSolverContext,
|
|
88
|
+
TurnstileSolveAnalytics,
|
|
89
|
+
} from '@luxexchange/sessions/src/challenge-solvers/createTurnstileSolver'
|
|
90
|
+
export type {
|
|
91
|
+
CreateHashcashWorkerChannelContext,
|
|
92
|
+
HashcashWorkerChannel,
|
|
93
|
+
HashcashWorkerChannelFactory,
|
|
94
|
+
} from '@luxexchange/sessions/src/challenge-solvers/hashcash/worker/types'
|
|
95
|
+
export { createHashcashWorkerChannel } from '@luxexchange/sessions/src/challenge-solvers/hashcash/worker/createHashcashWorkerChannel'
|
|
96
|
+
export { createHashcashMultiWorkerChannel } from '@luxexchange/sessions/src/challenge-solvers/hashcash/worker/createHashcashMultiWorkerChannel'
|
|
97
|
+
export type { MultiWorkerConfig } from '@luxexchange/sessions/src/challenge-solvers/hashcash/worker/createHashcashMultiWorkerChannel'
|
|
98
|
+
export type { CreateWorkerHashcashSolverContext } from '@luxexchange/sessions/src/challenge-solvers/hashcash/createWorkerHashcashSolver'
|
|
99
|
+
export type {
|
|
100
|
+
CreateHashcashSolverContext,
|
|
101
|
+
HashcashSolveAnalytics,
|
|
102
|
+
} from '@luxexchange/sessions/src/challenge-solvers/createHashcashSolver'
|
|
103
|
+
|
|
104
|
+
export { ChallengeType } from '@luxexchange/sessions/src/session-service/types'
|
|
105
|
+
|
|
106
|
+
// OAuth Service
|
|
107
|
+
export { createOAuthService } from '@luxexchange/sessions/src/oauth-service/createOAuthService'
|
|
108
|
+
export type { CreateOAuthServiceContext } from '@luxexchange/sessions/src/oauth-service/createOAuthService'
|
|
109
|
+
export type {
|
|
110
|
+
OAuthService,
|
|
111
|
+
OAuthInitiationResult,
|
|
112
|
+
OAuthCallbackParams,
|
|
113
|
+
OAuthVerificationResult,
|
|
114
|
+
OAuthInitiateParams,
|
|
115
|
+
OAuthVerifyParams,
|
|
116
|
+
OAuthUserInfo,
|
|
117
|
+
} from '@luxexchange/sessions/src/oauth-service/types'
|
|
118
|
+
|
|
119
|
+
// Performance Tracking
|
|
120
|
+
export type { PerformanceTracker } from '@luxexchange/sessions/src/performance/types'
|
|
121
|
+
export {
|
|
122
|
+
createPerformanceTracker,
|
|
123
|
+
PERFORMANCE_TRACKING_DISABLED,
|
|
124
|
+
} from '@luxexchange/sessions/src/performance/createPerformanceTracker'
|
|
125
|
+
export type { CreatePerformanceTrackerContext } from '@luxexchange/sessions/src/performance/createPerformanceTracker'
|
|
126
|
+
export { createNoopPerformanceTracker } from '@luxexchange/sessions/src/performance/createNoopPerformanceTracker'
|
|
127
|
+
|
|
128
|
+
// Test utilities (for integration testing)
|
|
129
|
+
export {
|
|
130
|
+
InMemorySessionStorage,
|
|
131
|
+
InMemoryDeviceIdService,
|
|
132
|
+
InMemoryLuxIdentifierService,
|
|
133
|
+
} from '@luxexchange/sessions/src/test-utils'
|
|
134
|
+
export {
|
|
135
|
+
createCookieJar,
|
|
136
|
+
createLocalCookieTransport,
|
|
137
|
+
} from '@luxexchange/sessions/src/test-utils/createLocalCookieTransport'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { LuxIdentifierService } from '@luxfi/sessions/src/lux-identifier/types'
|
|
2
|
+
|
|
3
|
+
function createLuxIdentifierService(ctx: {
|
|
4
|
+
getLuxIdentifier: () => Promise<string | null>
|
|
5
|
+
setLuxIdentifier: (identifier: string) => Promise<void>
|
|
6
|
+
removeLuxIdentifier: () => Promise<void>
|
|
7
|
+
}): LuxIdentifierService {
|
|
8
|
+
const getLuxIdentifier = ctx.getLuxIdentifier
|
|
9
|
+
const setLuxIdentifier = ctx.setLuxIdentifier
|
|
10
|
+
const removeLuxIdentifier = ctx.removeLuxIdentifier
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
getLuxIdentifier,
|
|
14
|
+
setLuxIdentifier,
|
|
15
|
+
removeLuxIdentifier,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { createLuxIdentifierService }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { queryOptions } from '@tanstack/react-query'
|
|
2
|
+
import type { LuxIdentifierService } from '@luxexchange/sessions/src/lux-identifier/types'
|
|
3
|
+
import { ReactQueryCacheKey } from '@luxfi/utilities/src/reactQuery/cache'
|
|
4
|
+
import type { QueryOptionsResult } from '@luxfi/utilities/src/reactQuery/queryOptions'
|
|
5
|
+
|
|
6
|
+
type LuxIdentifierQueryOptions = QueryOptionsResult<
|
|
7
|
+
string | null,
|
|
8
|
+
Error,
|
|
9
|
+
string | null,
|
|
10
|
+
[ReactQueryCacheKey.LuxIdentifier]
|
|
11
|
+
>
|
|
12
|
+
|
|
13
|
+
export function luxIdentifierQuery(getService: () => LuxIdentifierService): LuxIdentifierQueryOptions {
|
|
14
|
+
return queryOptions({
|
|
15
|
+
queryKey: [ReactQueryCacheKey.LuxIdentifier],
|
|
16
|
+
queryFn: async () => getService().getLuxIdentifier(),
|
|
17
|
+
staleTime: Infinity,
|
|
18
|
+
gcTime: Infinity,
|
|
19
|
+
})
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lux Identifier provider interface
|
|
3
|
+
* Platform-specific implementations handle lux identifier persistence
|
|
4
|
+
*/
|
|
5
|
+
interface LuxIdentifierService {
|
|
6
|
+
getLuxIdentifier(): Promise<string | null>
|
|
7
|
+
setLuxIdentifier(identifier: string): Promise<void>
|
|
8
|
+
removeLuxIdentifier(): Promise<void>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type { LuxIdentifierService }
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
OAuthCallbackParams,
|
|
3
|
+
OAuthInitiateParams,
|
|
4
|
+
OAuthInitiationResult,
|
|
5
|
+
OAuthService,
|
|
6
|
+
OAuthVerificationResult,
|
|
7
|
+
OAuthVerifyParams,
|
|
8
|
+
} from '@luxexchange/sessions/src/oauth-service/types'
|
|
9
|
+
import type { SessionRepository } from '@luxexchange/sessions/src/session-repository/types'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Context (dependencies) for creating an OAuthService
|
|
13
|
+
*/
|
|
14
|
+
export interface CreateOAuthServiceContext {
|
|
15
|
+
/** Session repository for backend communication */
|
|
16
|
+
sessionRepository: SessionRepository
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates an OAuth Service instance.
|
|
21
|
+
*
|
|
22
|
+
* Handles OAuth redirect-based authentication flows by wrapping
|
|
23
|
+
* the session repository's challenge/verify methods.
|
|
24
|
+
*
|
|
25
|
+
* Unlike ChallengeSolver (synchronous solve), OAuth requires:
|
|
26
|
+
* - External redirect to OAuth provider
|
|
27
|
+
* - User action (authorization)
|
|
28
|
+
* - Callback with authorization code
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const oauthService = createOAuthService({ sessionRepository })
|
|
33
|
+
*
|
|
34
|
+
* // 1. Initiate - get URL and redirect user
|
|
35
|
+
* const { authorizeUrl } = await oauthService.initiate({
|
|
36
|
+
* challengeType: ChallengeType.GITHUB,
|
|
37
|
+
* callbackUrl: 'https://app.com/auth/callback/github',
|
|
38
|
+
* })
|
|
39
|
+
* redirect(authorizeUrl)
|
|
40
|
+
*
|
|
41
|
+
* // 2. In callback route - parse and verify
|
|
42
|
+
* const params = oauthService.parseCallback(new URL(request.url))
|
|
43
|
+
* const result = await oauthService.verify({
|
|
44
|
+
* ...params,
|
|
45
|
+
* challengeType: ChallengeType.GITHUB,
|
|
46
|
+
* })
|
|
47
|
+
* // result.userInfo contains { name?, email? } from OAuth provider
|
|
48
|
+
* redirect('/')
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function createOAuthService(ctx: CreateOAuthServiceContext): OAuthService {
|
|
52
|
+
/**
|
|
53
|
+
* Initiate OAuth flow by requesting authorization URL
|
|
54
|
+
* Note: Callback URL is now configured server-side per OAuth provider
|
|
55
|
+
*/
|
|
56
|
+
async function initiate(params: OAuthInitiateParams): Promise<OAuthInitiationResult> {
|
|
57
|
+
const response = await ctx.sessionRepository.challenge({
|
|
58
|
+
challengeType: params.challengeType,
|
|
59
|
+
// Note: callbackUrl is no longer sent to backend - configured server-side
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
if (!response.authorizeUrl) {
|
|
63
|
+
throw new Error('No authorization URL returned from challenge')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
authorizeUrl: response.authorizeUrl,
|
|
68
|
+
state: response.challengeId,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parse OAuth callback URL to extract parameters
|
|
74
|
+
*/
|
|
75
|
+
function parseCallback(url: URL): OAuthCallbackParams {
|
|
76
|
+
return {
|
|
77
|
+
code: url.searchParams.get('code'),
|
|
78
|
+
state: url.searchParams.get('state'),
|
|
79
|
+
error: url.searchParams.get('error'),
|
|
80
|
+
errorDescription: url.searchParams.get('error_description'),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Verify OAuth callback by submitting authorization code
|
|
86
|
+
*/
|
|
87
|
+
async function verify(params: OAuthVerifyParams): Promise<OAuthVerificationResult> {
|
|
88
|
+
// Handle OAuth error from provider
|
|
89
|
+
if (params.error) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
retry: false,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Validate required params
|
|
97
|
+
if (!params.code || !params.state) {
|
|
98
|
+
return {
|
|
99
|
+
success: false,
|
|
100
|
+
retry: false,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const result = await ctx.sessionRepository.verifySession({
|
|
105
|
+
solution: params.code,
|
|
106
|
+
challengeId: params.state,
|
|
107
|
+
challengeType: params.challengeType,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
success: !result.retry && !result.failureReason,
|
|
112
|
+
retry: result.retry,
|
|
113
|
+
waitSeconds: result.waitSeconds,
|
|
114
|
+
userInfo: result.userInfo,
|
|
115
|
+
failureReason: result.failureReason,
|
|
116
|
+
failureMessage: result.failureMessage,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
initiate,
|
|
122
|
+
parseCallback,
|
|
123
|
+
verify,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ChallengeType } from '@uniswap/client-platform-service/dist/uniswap/platformservice/v1/sessionService_pb'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Result from initiating an OAuth flow
|
|
5
|
+
*/
|
|
6
|
+
export interface OAuthInitiationResult {
|
|
7
|
+
/** URL to redirect user to for OAuth provider authorization */
|
|
8
|
+
authorizeUrl: string
|
|
9
|
+
/** Challenge ID used as OAuth state parameter for CSRF protection */
|
|
10
|
+
state: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parameters extracted from OAuth callback URL
|
|
15
|
+
*/
|
|
16
|
+
export interface OAuthCallbackParams {
|
|
17
|
+
/** Authorization code from OAuth provider */
|
|
18
|
+
code: string | null
|
|
19
|
+
/** State parameter (challenge ID) for CSRF validation */
|
|
20
|
+
state: string | null
|
|
21
|
+
/** OAuth error code if authorization failed */
|
|
22
|
+
error: string | null
|
|
23
|
+
/** Human-readable error description */
|
|
24
|
+
errorDescription: string | null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* User information from OAuth provider
|
|
29
|
+
*/
|
|
30
|
+
export interface OAuthUserInfo {
|
|
31
|
+
name?: string
|
|
32
|
+
email?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Result from verifying OAuth callback
|
|
37
|
+
*/
|
|
38
|
+
export interface OAuthVerificationResult {
|
|
39
|
+
/** Whether verification succeeded */
|
|
40
|
+
success: boolean
|
|
41
|
+
/** Whether to retry the flow */
|
|
42
|
+
retry: boolean
|
|
43
|
+
/** Seconds to wait before retry (for rate limiting) */
|
|
44
|
+
waitSeconds?: number
|
|
45
|
+
/** User information from OAuth provider */
|
|
46
|
+
userInfo?: OAuthUserInfo
|
|
47
|
+
/** Failure reason from backend (e.g., 'REASON_PROVIDER_MISMATCH') */
|
|
48
|
+
failureReason?: string
|
|
49
|
+
/** Failure message with details (e.g., contains 'CHALLENGE_TYPE_GOOGLE') */
|
|
50
|
+
failureMessage?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* OAuth initiation parameters
|
|
55
|
+
*/
|
|
56
|
+
export interface OAuthInitiateParams {
|
|
57
|
+
/** Type of OAuth challenge (GITHUB, GOOGLE, SLACK, etc.) */
|
|
58
|
+
challengeType: ChallengeType
|
|
59
|
+
/** URL where OAuth provider should redirect after authorization */
|
|
60
|
+
callbackUrl: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* OAuth verification parameters
|
|
65
|
+
*/
|
|
66
|
+
export interface OAuthVerifyParams extends OAuthCallbackParams {
|
|
67
|
+
/** Type of OAuth challenge being verified */
|
|
68
|
+
challengeType: ChallengeType
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* OAuth Service interface
|
|
73
|
+
*
|
|
74
|
+
* Handles OAuth redirect-based authentication flows.
|
|
75
|
+
* Unlike ChallengeSolver (which solves challenges synchronously),
|
|
76
|
+
* OAuth requires external user action and callback handling.
|
|
77
|
+
*
|
|
78
|
+
* Flow:
|
|
79
|
+
* 1. initiate() - Get authorization URL and redirect user
|
|
80
|
+
* 2. parseCallback() - Extract params from callback URL
|
|
81
|
+
* 3. verify() - Submit authorization code to backend
|
|
82
|
+
*/
|
|
83
|
+
export interface OAuthService {
|
|
84
|
+
/**
|
|
85
|
+
* Initiate OAuth flow by requesting authorization URL from backend
|
|
86
|
+
* @param params - Challenge type and callback URL
|
|
87
|
+
* @returns Authorization URL to redirect user to
|
|
88
|
+
*/
|
|
89
|
+
initiate(params: OAuthInitiateParams): Promise<OAuthInitiationResult>
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Parse OAuth callback URL to extract parameters
|
|
93
|
+
* @param url - Callback URL with query params from OAuth provider
|
|
94
|
+
* @returns Extracted callback parameters
|
|
95
|
+
*/
|
|
96
|
+
parseCallback(url: URL): OAuthCallbackParams
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Verify OAuth callback by submitting authorization code to backend
|
|
100
|
+
* @param params - Callback params plus challenge type
|
|
101
|
+
* @returns Verification result
|
|
102
|
+
*/
|
|
103
|
+
verify(params: OAuthVerifyParams): Promise<OAuthVerificationResult>
|
|
104
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PERFORMANCE_TRACKING_DISABLED } from '@luxexchange/sessions/src/performance/createPerformanceTracker'
|
|
2
|
+
import type { PerformanceTracker } from '@luxexchange/sessions/src/performance/types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a noop performance tracker that always returns the disabled sentinel.
|
|
6
|
+
* Use this when performance tracking is not needed (e.g., extension, mobile, tests).
|
|
7
|
+
*/
|
|
8
|
+
function createNoopPerformanceTracker(): PerformanceTracker {
|
|
9
|
+
return { now: () => PERFORMANCE_TRACKING_DISABLED }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { createNoopPerformanceTracker }
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { PerformanceTracker } from '@luxexchange/sessions/src/performance/types'
|
|
2
|
+
|
|
3
|
+
/** Sentinel value indicating performance tracking is disabled */
|
|
4
|
+
export const PERFORMANCE_TRACKING_DISABLED = -1
|
|
5
|
+
|
|
6
|
+
interface CreatePerformanceTrackerContext {
|
|
7
|
+
/** Feature flag to enable/disable performance tracking. Required. */
|
|
8
|
+
getIsPerformanceTrackingEnabled: () => boolean
|
|
9
|
+
/**
|
|
10
|
+
* Injected timing function. This is the actual performance API.
|
|
11
|
+
* Allows the caller to pass performance.now, Date.now, or a mock.
|
|
12
|
+
* Required - no implicit dependency on globalThis.performance.
|
|
13
|
+
*/
|
|
14
|
+
getNow: () => number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a performance tracker with feature flag control.
|
|
19
|
+
*
|
|
20
|
+
* Behavior:
|
|
21
|
+
* - If tracking is disabled (feature flag returns false), returns -1 (sentinel)
|
|
22
|
+
* - Otherwise calls the injected getNow() function
|
|
23
|
+
*
|
|
24
|
+
* The sentinel value (-1) allows analytics consumers to distinguish
|
|
25
|
+
* between "0ms duration" and "tracking was disabled".
|
|
26
|
+
*
|
|
27
|
+
* Note: All dependencies are explicitly passed in - no implicit globals.
|
|
28
|
+
*/
|
|
29
|
+
function createPerformanceTracker(ctx: CreatePerformanceTrackerContext): PerformanceTracker {
|
|
30
|
+
function now(): number {
|
|
31
|
+
// If disabled via feature flag, return sentinel (-1)
|
|
32
|
+
if (!ctx.getIsPerformanceTrackingEnabled()) {
|
|
33
|
+
return PERFORMANCE_TRACKING_DISABLED
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return ctx.getNow()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { now }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { createPerformanceTracker }
|
|
43
|
+
export type { CreatePerformanceTrackerContext }
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* eslint-disable check-file/no-index */
|
|
2
|
+
export type { CreatePerformanceTrackerContext } from '@luxexchange/sessions/src/performance/createPerformanceTracker'
|
|
3
|
+
export {
|
|
4
|
+
createPerformanceTracker,
|
|
5
|
+
PERFORMANCE_TRACKING_DISABLED,
|
|
6
|
+
} from '@luxexchange/sessions/src/performance/createPerformanceTracker'
|
|
7
|
+
export type { PerformanceTracker } from '@luxexchange/sessions/src/performance/types'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract for performance timing.
|
|
3
|
+
* Abstraction over performance.now() that can be:
|
|
4
|
+
* - Injected for testing
|
|
5
|
+
* - Disabled via feature flag
|
|
6
|
+
* - Platform-specific (browser vs React Native vs Node)
|
|
7
|
+
*/
|
|
8
|
+
export interface PerformanceTracker {
|
|
9
|
+
/** Returns current high-resolution timestamp in milliseconds */
|
|
10
|
+
now(): number
|
|
11
|
+
}
|