@showrun/core 0.1.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/LICENSE +21 -0
- package/dist/__tests__/dsl-validation.test.d.ts +2 -0
- package/dist/__tests__/dsl-validation.test.d.ts.map +1 -0
- package/dist/__tests__/dsl-validation.test.js +203 -0
- package/dist/__tests__/pack-versioning.test.d.ts +2 -0
- package/dist/__tests__/pack-versioning.test.d.ts.map +1 -0
- package/dist/__tests__/pack-versioning.test.js +165 -0
- package/dist/__tests__/validator.test.d.ts +2 -0
- package/dist/__tests__/validator.test.d.ts.map +1 -0
- package/dist/__tests__/validator.test.js +149 -0
- package/dist/authResilience.d.ts +146 -0
- package/dist/authResilience.d.ts.map +1 -0
- package/dist/authResilience.js +378 -0
- package/dist/browserLauncher.d.ts +74 -0
- package/dist/browserLauncher.d.ts.map +1 -0
- package/dist/browserLauncher.js +159 -0
- package/dist/browserPersistence.d.ts +49 -0
- package/dist/browserPersistence.d.ts.map +1 -0
- package/dist/browserPersistence.js +143 -0
- package/dist/context.d.ts +10 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +30 -0
- package/dist/dsl/builders.d.ts +340 -0
- package/dist/dsl/builders.d.ts.map +1 -0
- package/dist/dsl/builders.js +416 -0
- package/dist/dsl/conditions.d.ts +33 -0
- package/dist/dsl/conditions.d.ts.map +1 -0
- package/dist/dsl/conditions.js +169 -0
- package/dist/dsl/interpreter.d.ts +24 -0
- package/dist/dsl/interpreter.d.ts.map +1 -0
- package/dist/dsl/interpreter.js +491 -0
- package/dist/dsl/stepHandlers.d.ts +32 -0
- package/dist/dsl/stepHandlers.d.ts.map +1 -0
- package/dist/dsl/stepHandlers.js +787 -0
- package/dist/dsl/target.d.ts +28 -0
- package/dist/dsl/target.d.ts.map +1 -0
- package/dist/dsl/target.js +110 -0
- package/dist/dsl/templating.d.ts +21 -0
- package/dist/dsl/templating.d.ts.map +1 -0
- package/dist/dsl/templating.js +73 -0
- package/dist/dsl/types.d.ts +695 -0
- package/dist/dsl/types.d.ts.map +1 -0
- package/dist/dsl/types.js +7 -0
- package/dist/dsl/validation.d.ts +15 -0
- package/dist/dsl/validation.d.ts.map +1 -0
- package/dist/dsl/validation.js +974 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/jsonPackValidator.d.ts +11 -0
- package/dist/jsonPackValidator.d.ts.map +1 -0
- package/dist/jsonPackValidator.js +61 -0
- package/dist/loader.d.ts +35 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +107 -0
- package/dist/networkCapture.d.ts +107 -0
- package/dist/networkCapture.d.ts.map +1 -0
- package/dist/networkCapture.js +390 -0
- package/dist/packUtils.d.ts +36 -0
- package/dist/packUtils.d.ts.map +1 -0
- package/dist/packUtils.js +97 -0
- package/dist/packVersioning.d.ts +25 -0
- package/dist/packVersioning.d.ts.map +1 -0
- package/dist/packVersioning.js +137 -0
- package/dist/runner.d.ts +62 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +170 -0
- package/dist/types.d.ts +336 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/validator.d.ts +20 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +68 -0
- package/package.json +49 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth resilience module: handles "run once" steps, auth failure detection, and recovery
|
|
3
|
+
*/
|
|
4
|
+
import type { Page } from 'playwright';
|
|
5
|
+
import type { DslStep } from './dsl/types.js';
|
|
6
|
+
import type { AuthPolicy, AuthGuard, Logger } from './types.js';
|
|
7
|
+
import type { NetworkEntrySerializable } from './networkCapture.js';
|
|
8
|
+
/**
|
|
9
|
+
* Outputs produced by a step (vars, collectibles, and network entries)
|
|
10
|
+
*/
|
|
11
|
+
export interface StepOutput {
|
|
12
|
+
vars: Record<string, unknown>;
|
|
13
|
+
collectibles: Record<string, unknown>;
|
|
14
|
+
/** Network entries referenced by this step (for network_find/network_replay caching) */
|
|
15
|
+
networkEntries?: NetworkEntrySerializable[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Cache for tracking executed "once" steps per session/profile.
|
|
19
|
+
* Stores step outputs (vars and collectibles) to restore them when steps are skipped.
|
|
20
|
+
* Can be loaded from and persisted to disk when sessionId/profileId are provided.
|
|
21
|
+
*/
|
|
22
|
+
export declare class OnceCache {
|
|
23
|
+
private sessionCache;
|
|
24
|
+
private profileCache;
|
|
25
|
+
/**
|
|
26
|
+
* Create a cache, optionally loading from disk for the given sessionId and/or profileId.
|
|
27
|
+
*
|
|
28
|
+
* Storage locations:
|
|
29
|
+
* - Session cache: always in temp directory (ephemeral, cleared on server restart)
|
|
30
|
+
* - Profile cache: in profileCacheDir/.once-cache/profile.json if provided, else temp directory
|
|
31
|
+
*/
|
|
32
|
+
static fromDisk(sessionId?: string, profileId?: string, profileCacheDir?: string): OnceCache;
|
|
33
|
+
/**
|
|
34
|
+
* Persist cache to disk for the given sessionId and/or profileId.
|
|
35
|
+
*
|
|
36
|
+
* Storage locations:
|
|
37
|
+
* - Session cache: always in temp directory (ephemeral)
|
|
38
|
+
* - Profile cache: in profileCacheDir/.once-cache/profile.json if provided, else temp directory
|
|
39
|
+
*/
|
|
40
|
+
persist(sessionId?: string, profileId?: string, profileCacheDir?: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Check if a step should be skipped (already executed)
|
|
43
|
+
*/
|
|
44
|
+
isExecuted(stepId: string, scope: 'session' | 'profile' | undefined): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Mark a step as executed with its outputs
|
|
47
|
+
*/
|
|
48
|
+
markExecuted(stepId: string, scope: 'session' | 'profile', outputs?: StepOutput): void;
|
|
49
|
+
/**
|
|
50
|
+
* Get cached outputs for a step
|
|
51
|
+
*/
|
|
52
|
+
getOutputs(stepId: string, scope: 'session' | 'profile'): StepOutput | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Clear cache for a specific scope
|
|
55
|
+
*/
|
|
56
|
+
clear(scope: 'session' | 'profile'): void;
|
|
57
|
+
/**
|
|
58
|
+
* Clear all caches
|
|
59
|
+
*/
|
|
60
|
+
clearAll(): void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Auth failure detection result
|
|
64
|
+
*/
|
|
65
|
+
export interface AuthFailure {
|
|
66
|
+
url: string;
|
|
67
|
+
status: number;
|
|
68
|
+
stepId?: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Auth failure monitor that watches network responses
|
|
72
|
+
*/
|
|
73
|
+
export declare class AuthFailureMonitor {
|
|
74
|
+
private authPolicy;
|
|
75
|
+
private failures;
|
|
76
|
+
private urlRegex?;
|
|
77
|
+
constructor(authPolicy: AuthPolicy);
|
|
78
|
+
/**
|
|
79
|
+
* Check if auth policy is enabled
|
|
80
|
+
*/
|
|
81
|
+
isEnabled(): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Check if a response indicates auth failure
|
|
84
|
+
*/
|
|
85
|
+
isAuthFailure(url: string, status: number): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Record an auth failure
|
|
88
|
+
*/
|
|
89
|
+
recordFailure(failure: AuthFailure): void;
|
|
90
|
+
/**
|
|
91
|
+
* Get the latest auth failure
|
|
92
|
+
*/
|
|
93
|
+
getLatestFailure(): AuthFailure | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Get max recoveries allowed
|
|
96
|
+
*/
|
|
97
|
+
getMaxRecoveries(): number;
|
|
98
|
+
/**
|
|
99
|
+
* Get max step retries after recovery
|
|
100
|
+
*/
|
|
101
|
+
getMaxStepRetries(): number;
|
|
102
|
+
/**
|
|
103
|
+
* Get cooldown delay in ms
|
|
104
|
+
*/
|
|
105
|
+
getCooldownMs(): number;
|
|
106
|
+
/**
|
|
107
|
+
* Clear recorded failures (for testing or reset)
|
|
108
|
+
*/
|
|
109
|
+
clearFailures(): void;
|
|
110
|
+
/**
|
|
111
|
+
* Clear failures for a specific step ID
|
|
112
|
+
*/
|
|
113
|
+
clearFailuresForStep(stepId: string): void;
|
|
114
|
+
/**
|
|
115
|
+
* Get failures for a specific step ID
|
|
116
|
+
*/
|
|
117
|
+
getFailuresForStep(stepId: string): AuthFailure[];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Auth guard for proactive checks (OFF by default)
|
|
121
|
+
*/
|
|
122
|
+
export declare class AuthGuardChecker {
|
|
123
|
+
private guard;
|
|
124
|
+
constructor(guard: AuthGuard);
|
|
125
|
+
/**
|
|
126
|
+
* Check if guard is enabled
|
|
127
|
+
*/
|
|
128
|
+
isEnabled(): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Check auth status using configured strategy
|
|
131
|
+
*/
|
|
132
|
+
checkAuth(page: Page): Promise<boolean>;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Setup network response monitoring for browser sessions
|
|
136
|
+
*/
|
|
137
|
+
export declare function setupBrowserAuthMonitoring(page: Page, monitor: AuthFailureMonitor, logger: Logger, currentStepId?: string): void;
|
|
138
|
+
/**
|
|
139
|
+
* Check if a step should be skipped due to "once" cache
|
|
140
|
+
*/
|
|
141
|
+
export declare function shouldSkipStep(step: DslStep, onceCache: OnceCache, sessionId?: string, profileId?: string): boolean;
|
|
142
|
+
/**
|
|
143
|
+
* Get steps that should be rerun during recovery (all steps with once flag)
|
|
144
|
+
*/
|
|
145
|
+
export declare function getOnceSteps(steps: DslStep[]): DslStep[];
|
|
146
|
+
//# sourceMappingURL=authResilience.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authResilience.d.ts","sourceRoot":"","sources":["../src/authResilience.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth resilience module: handles "run once" steps, auth failure detection, and recovery
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
const ONCE_CACHE_DIR_ENV = 'SHOWRUN_ONCE_CACHE_DIR';
|
|
8
|
+
function getOnceCacheDir() {
|
|
9
|
+
const dir = process.env[ONCE_CACHE_DIR_ENV] ?? join(tmpdir(), 'showrun-once-cache');
|
|
10
|
+
return dir;
|
|
11
|
+
}
|
|
12
|
+
/** Sanitize id for use in cache filename (no path separators) */
|
|
13
|
+
function sanitizeCacheId(id) {
|
|
14
|
+
return id.replace(/[/\\]/g, '-');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Cache for tracking executed "once" steps per session/profile.
|
|
18
|
+
* Stores step outputs (vars and collectibles) to restore them when steps are skipped.
|
|
19
|
+
* Can be loaded from and persisted to disk when sessionId/profileId are provided.
|
|
20
|
+
*/
|
|
21
|
+
export class OnceCache {
|
|
22
|
+
sessionCache = new Map();
|
|
23
|
+
profileCache = new Map();
|
|
24
|
+
/**
|
|
25
|
+
* Create a cache, optionally loading from disk for the given sessionId and/or profileId.
|
|
26
|
+
*
|
|
27
|
+
* Storage locations:
|
|
28
|
+
* - Session cache: always in temp directory (ephemeral, cleared on server restart)
|
|
29
|
+
* - Profile cache: in profileCacheDir/.once-cache/profile.json if provided, else temp directory
|
|
30
|
+
*/
|
|
31
|
+
static fromDisk(sessionId, profileId, profileCacheDir) {
|
|
32
|
+
const defaultDir = getOnceCacheDir();
|
|
33
|
+
const cache = new OnceCache();
|
|
34
|
+
// Session cache always in temp directory (ephemeral)
|
|
35
|
+
if (sessionId) {
|
|
36
|
+
const file = join(defaultDir, `once-session-${sanitizeCacheId(sessionId)}.json`);
|
|
37
|
+
if (existsSync(file)) {
|
|
38
|
+
try {
|
|
39
|
+
const data = readFileSync(file, 'utf-8');
|
|
40
|
+
const parsed = JSON.parse(data);
|
|
41
|
+
// Backward compatibility: if data is an array (old format), convert to Map with empty outputs
|
|
42
|
+
if (Array.isArray(parsed)) {
|
|
43
|
+
parsed.forEach((id) => cache.sessionCache.set(id, { vars: {}, collectibles: {} }));
|
|
44
|
+
}
|
|
45
|
+
else if (typeof parsed === 'object' && parsed !== null) {
|
|
46
|
+
// New format: object with stepId -> StepOutput
|
|
47
|
+
for (const [stepId, outputs] of Object.entries(parsed)) {
|
|
48
|
+
const stepOutput = outputs;
|
|
49
|
+
cache.sessionCache.set(stepId, {
|
|
50
|
+
vars: stepOutput.vars ?? {},
|
|
51
|
+
collectibles: stepOutput.collectibles ?? {},
|
|
52
|
+
networkEntries: stepOutput.networkEntries,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Ignore corrupt or missing file
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Profile cache in pack directory if provided, else default temp dir
|
|
63
|
+
if (profileId) {
|
|
64
|
+
let profileFile;
|
|
65
|
+
if (profileCacheDir) {
|
|
66
|
+
const cacheSubdir = join(profileCacheDir, '.once-cache');
|
|
67
|
+
profileFile = join(cacheSubdir, 'profile.json');
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
profileFile = join(defaultDir, `once-profile-${sanitizeCacheId(profileId)}.json`);
|
|
71
|
+
}
|
|
72
|
+
if (existsSync(profileFile)) {
|
|
73
|
+
try {
|
|
74
|
+
const data = readFileSync(profileFile, 'utf-8');
|
|
75
|
+
const parsed = JSON.parse(data);
|
|
76
|
+
// Backward compatibility: if data is an array (old format), convert to Map with empty outputs
|
|
77
|
+
if (Array.isArray(parsed)) {
|
|
78
|
+
parsed.forEach((id) => cache.profileCache.set(id, { vars: {}, collectibles: {} }));
|
|
79
|
+
}
|
|
80
|
+
else if (typeof parsed === 'object' && parsed !== null) {
|
|
81
|
+
// New format: object with stepId -> StepOutput
|
|
82
|
+
for (const [stepId, outputs] of Object.entries(parsed)) {
|
|
83
|
+
const stepOutput = outputs;
|
|
84
|
+
cache.profileCache.set(stepId, {
|
|
85
|
+
vars: stepOutput.vars ?? {},
|
|
86
|
+
collectibles: stepOutput.collectibles ?? {},
|
|
87
|
+
networkEntries: stepOutput.networkEntries,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Ignore corrupt or missing file
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return cache;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Persist cache to disk for the given sessionId and/or profileId.
|
|
101
|
+
*
|
|
102
|
+
* Storage locations:
|
|
103
|
+
* - Session cache: always in temp directory (ephemeral)
|
|
104
|
+
* - Profile cache: in profileCacheDir/.once-cache/profile.json if provided, else temp directory
|
|
105
|
+
*/
|
|
106
|
+
persist(sessionId, profileId, profileCacheDir) {
|
|
107
|
+
const defaultDir = getOnceCacheDir();
|
|
108
|
+
// Session cache always in temp directory
|
|
109
|
+
if (sessionId) {
|
|
110
|
+
try {
|
|
111
|
+
mkdirSync(defaultDir, { recursive: true });
|
|
112
|
+
const file = join(defaultDir, `once-session-${sanitizeCacheId(sessionId)}.json`);
|
|
113
|
+
const cacheObj = {};
|
|
114
|
+
for (const [stepId, outputs] of this.sessionCache) {
|
|
115
|
+
cacheObj[stepId] = outputs;
|
|
116
|
+
}
|
|
117
|
+
writeFileSync(file, JSON.stringify(cacheObj, null, 2), 'utf-8');
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Ignore write errors
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Profile cache in pack directory if provided, else default temp dir
|
|
124
|
+
if (profileId) {
|
|
125
|
+
let profileFile;
|
|
126
|
+
let profileDir;
|
|
127
|
+
if (profileCacheDir) {
|
|
128
|
+
profileDir = join(profileCacheDir, '.once-cache');
|
|
129
|
+
profileFile = join(profileDir, 'profile.json');
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
profileDir = defaultDir;
|
|
133
|
+
profileFile = join(defaultDir, `once-profile-${sanitizeCacheId(profileId)}.json`);
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
mkdirSync(profileDir, { recursive: true });
|
|
137
|
+
const cacheObj = {};
|
|
138
|
+
for (const [stepId, outputs] of this.profileCache) {
|
|
139
|
+
cacheObj[stepId] = outputs;
|
|
140
|
+
}
|
|
141
|
+
writeFileSync(profileFile, JSON.stringify(cacheObj, null, 2), 'utf-8');
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Ignore write errors
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Check if a step should be skipped (already executed)
|
|
150
|
+
*/
|
|
151
|
+
isExecuted(stepId, scope) {
|
|
152
|
+
if (!scope)
|
|
153
|
+
return false;
|
|
154
|
+
if (scope === 'session') {
|
|
155
|
+
return this.sessionCache.has(stepId);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
return this.profileCache.has(stepId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Mark a step as executed with its outputs
|
|
163
|
+
*/
|
|
164
|
+
markExecuted(stepId, scope, outputs = { vars: {}, collectibles: {} }) {
|
|
165
|
+
if (scope === 'session') {
|
|
166
|
+
this.sessionCache.set(stepId, outputs);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.profileCache.set(stepId, outputs);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get cached outputs for a step
|
|
174
|
+
*/
|
|
175
|
+
getOutputs(stepId, scope) {
|
|
176
|
+
const cache = scope === 'session' ? this.sessionCache : this.profileCache;
|
|
177
|
+
return cache.get(stepId);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Clear cache for a specific scope
|
|
181
|
+
*/
|
|
182
|
+
clear(scope) {
|
|
183
|
+
if (scope === 'session') {
|
|
184
|
+
this.sessionCache.clear();
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
this.profileCache.clear();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Clear all caches
|
|
192
|
+
*/
|
|
193
|
+
clearAll() {
|
|
194
|
+
this.sessionCache.clear();
|
|
195
|
+
this.profileCache.clear();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Auth failure monitor that watches network responses
|
|
200
|
+
*/
|
|
201
|
+
export class AuthFailureMonitor {
|
|
202
|
+
authPolicy;
|
|
203
|
+
failures = [];
|
|
204
|
+
urlRegex;
|
|
205
|
+
constructor(authPolicy) {
|
|
206
|
+
this.authPolicy = {
|
|
207
|
+
enabled: authPolicy.enabled ?? true,
|
|
208
|
+
statusCodes: authPolicy.statusCodes ?? [401, 403],
|
|
209
|
+
urlIncludes: authPolicy.urlIncludes ?? [],
|
|
210
|
+
urlRegex: authPolicy.urlRegex,
|
|
211
|
+
loginUrlIncludes: authPolicy.loginUrlIncludes ?? [],
|
|
212
|
+
maxRecoveriesPerRun: authPolicy.maxRecoveriesPerRun ?? 1,
|
|
213
|
+
maxStepRetryAfterRecovery: authPolicy.maxStepRetryAfterRecovery ?? 1,
|
|
214
|
+
cooldownMs: authPolicy.cooldownMs ?? 0,
|
|
215
|
+
};
|
|
216
|
+
if (this.authPolicy.urlRegex) {
|
|
217
|
+
this.urlRegex = new RegExp(this.authPolicy.urlRegex);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Check if auth policy is enabled
|
|
222
|
+
*/
|
|
223
|
+
isEnabled() {
|
|
224
|
+
return this.authPolicy.enabled;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Check if a response indicates auth failure
|
|
228
|
+
*/
|
|
229
|
+
isAuthFailure(url, status) {
|
|
230
|
+
if (!this.authPolicy.enabled)
|
|
231
|
+
return false;
|
|
232
|
+
if (!this.authPolicy.statusCodes.includes(status))
|
|
233
|
+
return false;
|
|
234
|
+
// Check URL patterns if configured
|
|
235
|
+
if (this.authPolicy.urlIncludes.length > 0) {
|
|
236
|
+
const matches = this.authPolicy.urlIncludes.some((pattern) => url.includes(pattern));
|
|
237
|
+
if (!matches)
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
// Check URL regex if configured
|
|
241
|
+
if (this.urlRegex) {
|
|
242
|
+
if (!this.urlRegex.test(url))
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Record an auth failure
|
|
249
|
+
*/
|
|
250
|
+
recordFailure(failure) {
|
|
251
|
+
this.failures.push(failure);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get the latest auth failure
|
|
255
|
+
*/
|
|
256
|
+
getLatestFailure() {
|
|
257
|
+
return this.failures[this.failures.length - 1];
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get max recoveries allowed
|
|
261
|
+
*/
|
|
262
|
+
getMaxRecoveries() {
|
|
263
|
+
return this.authPolicy.maxRecoveriesPerRun;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Get max step retries after recovery
|
|
267
|
+
*/
|
|
268
|
+
getMaxStepRetries() {
|
|
269
|
+
return this.authPolicy.maxStepRetryAfterRecovery;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get cooldown delay in ms
|
|
273
|
+
*/
|
|
274
|
+
getCooldownMs() {
|
|
275
|
+
return this.authPolicy.cooldownMs;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Clear recorded failures (for testing or reset)
|
|
279
|
+
*/
|
|
280
|
+
clearFailures() {
|
|
281
|
+
this.failures = [];
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Clear failures for a specific step ID
|
|
285
|
+
*/
|
|
286
|
+
clearFailuresForStep(stepId) {
|
|
287
|
+
this.failures = this.failures.filter((f) => f.stepId !== stepId);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Get failures for a specific step ID
|
|
291
|
+
*/
|
|
292
|
+
getFailuresForStep(stepId) {
|
|
293
|
+
return this.failures.filter((f) => f.stepId === stepId);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Auth guard for proactive checks (OFF by default)
|
|
298
|
+
*/
|
|
299
|
+
export class AuthGuardChecker {
|
|
300
|
+
guard;
|
|
301
|
+
constructor(guard) {
|
|
302
|
+
this.guard = {
|
|
303
|
+
enabled: guard.enabled ?? false,
|
|
304
|
+
strategy: guard.strategy,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Check if guard is enabled
|
|
309
|
+
*/
|
|
310
|
+
isEnabled() {
|
|
311
|
+
return this.guard.enabled ?? false;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Check auth status using configured strategy
|
|
315
|
+
*/
|
|
316
|
+
async checkAuth(page) {
|
|
317
|
+
if (!this.isEnabled())
|
|
318
|
+
return true; // Guard disabled = assume auth is valid
|
|
319
|
+
const strategy = this.guard.strategy;
|
|
320
|
+
if (!strategy)
|
|
321
|
+
return true; // No strategy = assume auth is valid
|
|
322
|
+
// Check visible selector
|
|
323
|
+
if (strategy.visibleSelector) {
|
|
324
|
+
try {
|
|
325
|
+
const element = page.locator(strategy.visibleSelector);
|
|
326
|
+
const isVisible = await element.isVisible({ timeout: 5000 }).catch(() => false);
|
|
327
|
+
return isVisible;
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// Check URL pattern
|
|
334
|
+
if (strategy.urlIncludes) {
|
|
335
|
+
const currentUrl = page.url();
|
|
336
|
+
return currentUrl.includes(strategy.urlIncludes);
|
|
337
|
+
}
|
|
338
|
+
return true; // Default: assume valid
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Setup network response monitoring for browser sessions
|
|
343
|
+
*/
|
|
344
|
+
export function setupBrowserAuthMonitoring(page, monitor, logger, currentStepId) {
|
|
345
|
+
if (!monitor.isEnabled())
|
|
346
|
+
return;
|
|
347
|
+
page.on('response', (response) => {
|
|
348
|
+
const url = response.url();
|
|
349
|
+
const status = response.status();
|
|
350
|
+
if (monitor.isAuthFailure(url, status)) {
|
|
351
|
+
monitor.recordFailure({ url, status, stepId: currentStepId });
|
|
352
|
+
logger.log({
|
|
353
|
+
type: 'auth_failure_detected',
|
|
354
|
+
data: {
|
|
355
|
+
url,
|
|
356
|
+
status,
|
|
357
|
+
stepId: currentStepId,
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Check if a step should be skipped due to "once" cache
|
|
365
|
+
*/
|
|
366
|
+
export function shouldSkipStep(step, onceCache, sessionId, profileId) {
|
|
367
|
+
if (!step.once)
|
|
368
|
+
return false;
|
|
369
|
+
// Determine scope: if sessionId provided, use session; otherwise use profile
|
|
370
|
+
const scope = step.once === 'session' && sessionId ? 'session' : 'profile';
|
|
371
|
+
return onceCache.isExecuted(step.id, scope);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Get steps that should be rerun during recovery (all steps with once flag)
|
|
375
|
+
*/
|
|
376
|
+
export function getOnceSteps(steps) {
|
|
377
|
+
return steps.filter((step) => step.once !== undefined);
|
|
378
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified browser launcher
|
|
3
|
+
*
|
|
4
|
+
* Provides a unified interface for launching browsers with different engines
|
|
5
|
+
* (Chromium, Camoufox) and persistence modes.
|
|
6
|
+
*/
|
|
7
|
+
import { type Browser, type BrowserContext, type Page } from 'playwright';
|
|
8
|
+
import type { BrowserEngine, BrowserSettings, BrowserPersistence } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Browser session returned by launchBrowser
|
|
11
|
+
*/
|
|
12
|
+
export interface BrowserSession {
|
|
13
|
+
/**
|
|
14
|
+
* Browser context (for both Chromium and Camoufox)
|
|
15
|
+
*/
|
|
16
|
+
context: BrowserContext;
|
|
17
|
+
/**
|
|
18
|
+
* Main page in the context
|
|
19
|
+
*/
|
|
20
|
+
page: Page;
|
|
21
|
+
/**
|
|
22
|
+
* Browser instance (null for Camoufox which returns context directly)
|
|
23
|
+
*/
|
|
24
|
+
browser: Browser | null;
|
|
25
|
+
/**
|
|
26
|
+
* Engine used for this session
|
|
27
|
+
*/
|
|
28
|
+
engine: BrowserEngine;
|
|
29
|
+
/**
|
|
30
|
+
* Persistence mode
|
|
31
|
+
*/
|
|
32
|
+
persistence: BrowserPersistence;
|
|
33
|
+
/**
|
|
34
|
+
* User data directory path (if persistence is enabled)
|
|
35
|
+
*/
|
|
36
|
+
userDataDir?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Close the browser session
|
|
39
|
+
*/
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Configuration for launching a browser
|
|
44
|
+
*/
|
|
45
|
+
export interface LaunchBrowserConfig {
|
|
46
|
+
/**
|
|
47
|
+
* Browser settings from task pack
|
|
48
|
+
*/
|
|
49
|
+
browserSettings?: BrowserSettings;
|
|
50
|
+
/**
|
|
51
|
+
* Run in headless mode (default: true)
|
|
52
|
+
*/
|
|
53
|
+
headless?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Session ID for session persistence
|
|
56
|
+
*/
|
|
57
|
+
sessionId?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Pack directory path for profile persistence
|
|
60
|
+
*/
|
|
61
|
+
packPath?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Launches a browser with the specified configuration
|
|
65
|
+
*
|
|
66
|
+
* @param config - Browser launch configuration
|
|
67
|
+
* @returns Browser session with context, page, and close function
|
|
68
|
+
*/
|
|
69
|
+
export declare function launchBrowser(config: LaunchBrowserConfig): Promise<BrowserSession>;
|
|
70
|
+
/**
|
|
71
|
+
* Checks if a browser engine is available
|
|
72
|
+
*/
|
|
73
|
+
export declare function isBrowserEngineAvailable(engine: BrowserEngine): Promise<boolean>;
|
|
74
|
+
//# sourceMappingURL=browserLauncher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserLauncher.d.ts","sourceRoot":"","sources":["../src/browserLauncher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrF;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,OAAO,EAAE,cAAc,CAAC;IACxB;;OAEG;IACH,IAAI,EAAE,IAAI,CAAC;IACX;;OAEG;IACH,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC;IACtB;;OAEG;IACH,WAAW,EAAE,kBAAkB,CAAC;IAChC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,CAgCxF;AA4HD;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAgBtF"}
|