Skip to content

Commit 5955544

Browse files
authored
chore(server): share common routes (#18055)
1 parent 8b5cb13 commit 5955544

File tree

15 files changed

+446
-402
lines changed

15 files changed

+446
-402
lines changed

cli/types/cypress.d.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ declare namespace Cypress {
2222
password: string
2323
}
2424

25+
interface RemoteState {
26+
auth?: {
27+
username: string
28+
password: string
29+
}
30+
domainName: string
31+
strategy: 'file' | 'http'
32+
origin: string
33+
fileServer: string
34+
props: Record<string, any>
35+
visiting: string
36+
}
37+
2538
interface Backend {
2639
/**
2740
* Firefox only: Force Cypress to run garbage collection routines.
@@ -2856,19 +2869,15 @@ declare namespace Cypress {
28562869
projectName: string
28572870
projectRoot: string
28582871
proxyUrl: string
2872+
remote: RemoteState
28592873
report: boolean
28602874
reporterRoute: string
28612875
reporterUrl: string
28622876
socketId: null | string
28632877
socketIoCookie: string
28642878
socketIoRoute: string
2865-
spec: {
2866-
absolute: string
2867-
name: string
2868-
relative: string
2869-
specFilter: null | string
2870-
specType: 'integration' | 'component'
2871-
}
2879+
spec: Cypress['spec']
2880+
specs: Array<Cypress['spec']>
28722881
xhrRoute: string
28732882
xhrUrl: string
28742883
}

packages/proxy/lib/http/request-middleware.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import _ from 'lodash'
2-
import type CyServer from '@packages/server'
32
import { blocked, cors } from '@packages/network'
43
import { InterceptRequest } from '@packages/net-stubbing'
54
import debugModule from 'debug'
@@ -126,7 +125,7 @@ const StripUnsupportedAcceptEncoding: RequestMiddleware = function () {
126125
this.next()
127126
}
128127

129-
function reqNeedsBasicAuthHeaders (req, { auth, origin }: CyServer.RemoteState) {
128+
function reqNeedsBasicAuthHeaders (req, { auth, origin }: Cypress.RemoteState) {
130129
//if we have auth headers, this request matches our origin, protection space, and the user has not supplied auth headers
131130
return auth && !req.headers['authorization'] && cors.urlMatchesOriginProtectionSpace(req.proxiedUrl, origin)
132131
}

packages/resolve-dist/lib/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import path from 'path'
22

33
let fs: typeof import('fs-extra')
44

5-
type FoldersWithDist = 'static' | 'runner' | 'runner-ct' | 'driver'
5+
export type RunnerPkg = 'runner' | 'runner-ct'
6+
7+
type FoldersWithDist = 'static' | 'driver' | RunnerPkg
68

79
export const getPathToDist = (folder: FoldersWithDist, ...args: string[]) => {
810
return path.join(...[__dirname, '..', '..', folder, 'dist', ...args])
@@ -14,7 +16,7 @@ export const getRunnerInjectionContents = () => {
1416
return fs.readFile(getPathToDist('runner', 'injection.js'))
1517
}
1618

17-
export const getPathToIndex = (pkg: 'runner' | 'runner-ct') => {
19+
export const getPathToIndex = (pkg: RunnerPkg) => {
1820
return getPathToDist(pkg, 'index.html')
1921
}
2022

packages/server/index.d.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
// types for the `server` package
1616
export namespace CyServer {
17-
export type getRemoteState = () => RemoteState
17+
export type getRemoteState = () => Cypress.RemoteState
1818

1919
// TODO: pull this from main types
2020
export interface Config {
@@ -28,18 +28,6 @@ export namespace CyServer {
2828
responseTimeout: number
2929
}
3030

31-
export interface RemoteState {
32-
auth?: {
33-
username: string
34-
password: string
35-
}
36-
domainName: string
37-
strategy: 'file' | 'http'
38-
origin: string
39-
fileServer: string
40-
visiting: string
41-
}
42-
4331
export interface Socket {
4432
toDriver: (eventName: string, ...args: any) => void
4533
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { Request, Response } from 'express'
2+
import type httpProxy from 'http-proxy'
3+
import Debug from 'debug'
4+
import files from './files'
5+
import type { Cfg } from '../project-base'
6+
7+
const debug = Debug('cypress:server:iframes')
8+
9+
interface IFramesController {
10+
config: Cfg
11+
}
12+
13+
interface E2E extends IFramesController {
14+
getRemoteState: () => any
15+
getSpec: () => Cypress.Cypress['spec'] | null
16+
}
17+
18+
interface CT extends IFramesController {
19+
nodeProxy: httpProxy
20+
}
21+
22+
export const iframesController = {
23+
e2e: ({ getSpec, getRemoteState, config }: E2E, req: Request, res: Response) => {
24+
const extraOptions = {
25+
specFilter: getSpec()?.specFilter,
26+
specType: 'integration',
27+
}
28+
29+
debug('handling iframe for project spec %o', {
30+
spec: getSpec(),
31+
extraOptions,
32+
})
33+
34+
files.handleIframe(req, res, config, getRemoteState, extraOptions)
35+
},
36+
37+
component: ({ config, nodeProxy }: CT, req: Request, res: Response) => {
38+
// always proxy to the index.html file
39+
// attach header data for webservers
40+
// to properly intercept and serve assets from the correct src root
41+
// TODO: define a contract for dev-server plugins to configure this behavior
42+
req.headers.__cypress_spec_path = req.params[0]
43+
req.url = `${config.devServerPublicPathRoute}/index.html`
44+
45+
// user the node proxy here instead of the network proxy
46+
// to avoid the user accidentally intercepting and modifying
47+
// our internal index.html handler
48+
49+
nodeProxy.web(req, res, {}, (e) => {
50+
if (e) {
51+
// eslint-disable-next-line
52+
debug('Proxy request error. This is likely the socket hangup issue, we can basically ignore this because the stream will automatically continue once the asset will be available', e)
53+
}
54+
})
55+
},
56+
}

packages/server/lib/controllers/runner.js

Lines changed: 0 additions & 65 deletions
This file was deleted.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import _ from 'lodash'
2+
import type { Response } from 'express'
3+
import send from 'send'
4+
import os from 'os'
5+
import { fs } from '../util/fs'
6+
import path from 'path'
7+
import Debug from 'debug'
8+
import pkg from '@packages/root'
9+
import { getPathToDist, getPathToIndex, RunnerPkg } from '@packages/resolve-dist'
10+
import type { InitializeRoutes } from '../routes'
11+
import type { PlatformName } from '@packages/launcher'
12+
import type { Cfg } from '../project-base'
13+
14+
const debug = Debug('cypress:server:runner')
15+
16+
const PATH_TO_NON_PROXIED_ERROR = path.join(__dirname, '..', 'html', 'non_proxied_error.html')
17+
18+
const _serveNonProxiedError = (res: Response) => {
19+
return fs.readFile(PATH_TO_NON_PROXIED_ERROR)
20+
.then((html) => {
21+
return res.type('html').end(html)
22+
})
23+
}
24+
25+
export interface ServeOptions extends Pick<InitializeRoutes, 'getSpec' | 'config' | 'getCurrentBrowser' | 'getRemoteState' | 'specsStore'> {
26+
testingType: Cypress.TestingType
27+
}
28+
29+
export const serveRunner = (runnerPkg: RunnerPkg, config: Cfg, res: Response) => {
30+
// base64 before embedding so user-supplied contents can't break out of <script>
31+
// https://github.com/cypress-io/cypress/issues/4952
32+
const base64Config = Buffer.from(JSON.stringify(config)).toString('base64')
33+
34+
const runnerPath = process.env.CYPRESS_INTERNAL_RUNNER_PATH || getPathToIndex(runnerPkg)
35+
36+
return res.render(runnerPath, {
37+
base64Config,
38+
projectName: config.projectName,
39+
})
40+
}
41+
42+
export const runner = {
43+
serve (req, res, runnerPkg: RunnerPkg, options: ServeOptions) {
44+
if (req.proxiedUrl.startsWith('/')) {
45+
debug('request was not proxied via Cypress, erroring %o', _.pick(req, 'proxiedUrl'))
46+
47+
return _serveNonProxiedError(res)
48+
}
49+
50+
let { config, getRemoteState, getCurrentBrowser, getSpec, specsStore } = options
51+
52+
config = _.clone(config)
53+
// at any given point, rather than just arbitrarily modifying it.
54+
// @ts-ignore
55+
config.testingType = options.testingType
56+
57+
// TODO #1: bug. Passing `remote.domainName` breaks CT for unknown reasons.
58+
// If you pass a remote object with a domainName key, we get cross-origin
59+
// iframe access errors.
60+
// repro:
61+
// {
62+
// "domainName": "localhost"
63+
// }
64+
// TODO: Find out what the problem.
65+
if (options.testingType === 'e2e') {
66+
config.remote = getRemoteState()
67+
}
68+
69+
config.version = pkg.version
70+
config.platform = os.platform() as PlatformName
71+
config.arch = os.arch()
72+
config.spec = getSpec() ?? undefined
73+
config.specs = specsStore.specFiles
74+
config.browser = getCurrentBrowser()
75+
76+
debug('serving runner index.html with config %o',
77+
_.pick(config, 'version', 'platform', 'arch', 'projectName'))
78+
79+
// log the env object's keys without values to avoid leaking sensitive info
80+
debug('env object has the following keys: %s', _.keys(config.env).join(', '))
81+
82+
return serveRunner(runnerPkg, config, res)
83+
},
84+
85+
handle (testingType, req, res) {
86+
const pathToFile = getPathToDist(testingType === 'e2e' ? 'runner' : 'runner-ct', req.params[0])
87+
88+
return send(req, pathToFile)
89+
.pipe(res)
90+
},
91+
}

packages/server/lib/controllers/static.js

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import send from 'send'
2+
import type { Request, Response } from 'express'
3+
import { getPathToDist } from '@packages/resolve-dist'
4+
5+
export const staticCtrl = {
6+
handle (req: Request, res: Response) {
7+
const pathToFile = getPathToDist('static', req.params[0])
8+
9+
return send(req, pathToFile)
10+
.pipe(res)
11+
},
12+
}

0 commit comments

Comments
 (0)