Skip to content

Commit

Permalink
proxyless: client scripts (#2848)
Browse files Browse the repository at this point in the history
* proxyless: client scripts

* bump version

* update typing

---------

Co-authored-by: Popov Aleksey <[email protected]>
  • Loading branch information
miherlosev and Aleksey28 authored Feb 1, 2023
1 parent ad45f8a commit 5abd99e
Show file tree
Hide file tree
Showing 10 changed files with 46 additions and 27 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "testcafe-hammerhead",
"description": "A powerful web-proxy used as a core for the TestCafe testing framework (https://github.com/DevExpress/testcafe).",
"version": "28.3.1",
"version": "28.4.0",
"homepage": "https://github.com/DevExpress/testcafe-hammerhead",
"bugs": {
"url": "https://github.com/DevExpress/testcafe-hammerhead/issues"
Expand Down
1 change: 1 addition & 0 deletions src/processing/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface PageInjectableResources {
stylesheets: string[];
scripts: string[];
embeddedScripts: string[];
userScripts: string[];
}

export interface PageRestoreStoragesOptions {
Expand Down
2 changes: 1 addition & 1 deletion src/processing/resources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export async function process (ctx: RequestPipelineContext): Promise<Buffer> {
const urlReplacer = getResourceUrlReplacer(ctx);

if (pageProcessor === processor)
await ctx.prepareInjectableUserScripts();
await ctx.prepareInjectableUserScripts(ctx.eventFactory, ctx.session.injectable.userScripts);

// @ts-ignore: Cannot invoke an expression whose type lacks a call signature
const processed = processor.processResource(decoded, ctx, charset, urlReplacer);
Expand Down
6 changes: 6 additions & 0 deletions src/processing/resources/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ class PageProcessor extends ResourceProcessorBase {
});
}

if ((processingOptions as PageInjectableResources).userScripts) {
(processingOptions as PageInjectableResources).userScripts.forEach(script => {
injectedResources.push(PageProcessor._createShadowUIScriptWithUrlNode(script));
});
}

for (let i = injectedResources.length - 1; i > -1; i--)
parse5Utils.insertBeforeFirstScript(injectedResources[i], head);

Expand Down
28 changes: 26 additions & 2 deletions src/request-pipeline/context/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { PreparedResponseInfo } from '../request-hooks/events/info';
import ResponseEvent from '../request-hooks/events/response-event';
import { OnResponseEventData } from '../../typings/context';
import ConfigureResponseEventOptions from '../request-hooks/events/configure-response-event-options';
import requestIsMatchRule from '../request-hooks/request-is-match-rule';
import { UserScript } from '../../session';


export default abstract class BaseRequestPipelineContext {
Expand All @@ -19,10 +21,12 @@ export default abstract class BaseRequestPipelineContext {
public reqOpts: RequestOptions;
public mock: ResponseMock;
public onResponseEventData: OnResponseEventData[] = [];
protected injectableUserScripts: string[];

protected constructor (requestId: string) {
this.requestFilterRules = [];
this.requestId = requestId;
this.requestFilterRules = [];
this.requestId = requestId;
this.injectableUserScripts = [];
}

private async _forEachRequestFilterRule (fn: (rule: RequestFilterRule) => Promise<void>): Promise<void> {
Expand Down Expand Up @@ -98,4 +102,24 @@ export default abstract class BaseRequestPipelineContext {
await eventProvider.callRequestHookErrorHandler(targetRule, this.mock.error as Error);

}

public async prepareInjectableUserScripts (eventFactory: BaseRequestHookEventFactory, userScripts: UserScript[]): Promise<void> {
if (!userScripts.length)
return;

const requestInfo = eventFactory.createRequestInfo();
const matchedUserScripts = await Promise.all(userScripts.map(async userScript => {
if (await requestIsMatchRule(userScript.page, requestInfo))
return userScript;

return void 0;
} ));

const injectableUserScripts = matchedUserScripts
.filter(userScript => !!userScript)
.map(userScript => userScript?.url || '');

if (injectableUserScripts)
this.injectableUserScripts = injectableUserScripts;
}
}
22 changes: 1 addition & 21 deletions src/request-pipeline/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@ import * as contentTypeUtils from '../../utils/content-type';
import generateUniqueId from '../../utils/generate-unique-id';
import { check as checkSameOriginPolicy } from '../same-origin-policy';
import * as headerTransforms from '../header-transforms';
import { RequestInfo } from '../request-hooks/events/info';
import SERVICE_ROUTES from '../../proxy/service-routes';
import BUILTIN_HEADERS from '../builtin-header-names';
import logger from '../../utils/logger';
import createSpecialPageResponse from '../create-special-page-response';
import { fetchBody } from '../../utils/http';
import * as requestCache from '../cache';
import requestIsMatchRule from '../request-hooks/request-is-match-rule';
import { Http2Response } from '../destination-request/http2';
import BaseRequestPipelineContext from './base';
import RequestPipelineRequestHookEventFactory from '../request-hooks/events/factory';
Expand Down Expand Up @@ -120,7 +118,6 @@ export default class RequestPipelineContext extends BaseRequestPipelineContext {
isSameOriginPolicyFailed = false;
windowId?: string;
temporaryCacheEntry?: RequestCacheEntry;
_injectableUserScripts: string[] = [];
eventFactory: RequestPipelineRequestHookEventFactory;

constructor (readonly req: IncomingMessage,
Expand Down Expand Up @@ -355,23 +352,6 @@ export default class RequestPipelineContext extends BaseRequestPipelineContext {
logger.proxy.onContentInfoBuilt(this);
}

public async prepareInjectableUserScripts (): Promise<void> {
const requestInfo = RequestInfo.from(this);
const matchedUserScripts = await Promise.all(this.session.injectable.userScripts.map(async userScript => {
if (await requestIsMatchRule(userScript.page, requestInfo))
return userScript;

return void 0;
} ));

const injectableUserScripts = matchedUserScripts
.filter(userScript => !!userScript)
.map(userScript => userScript?.url || '');

if (injectableUserScripts)
this._injectableUserScripts = injectableUserScripts;
}

private _handleAttachment (): void {
let isOpenedInNewWindow = false;

Expand Down Expand Up @@ -410,7 +390,7 @@ export default class RequestPipelineContext extends BaseRequestPipelineContext {

getInjectableScripts (): string[] {
const taskScript = this.isIframe ? SERVICE_ROUTES.iframeTask : SERVICE_ROUTES.task;
const scripts = this.session.injectable.scripts.concat(taskScript, this._injectableUserScripts);
const scripts = this.session.injectable.scripts.concat(taskScript, this.injectableUserScripts);

return this._resolveInjectableUrls(scripts);
}
Expand Down
2 changes: 1 addition & 1 deletion src/session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { EventEmitter } from 'events';

const TASK_TEMPLATE = read('../client/task.js.mustache');

interface UserScript {
export interface UserScript {
url: string;
page: RequestFilterRule;
}
Expand Down
2 changes: 1 addition & 1 deletion test/server/data/without-request-pipeline/expected.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html><html lang="en"><head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" type="text/css" class="ui-stylesheet-hammerhead-shadow-ui" href="./styles.css"><script type="text/javascript" class="script-hammerhead-shadow-ui" charset="UTF-8" src="common/script1.js"></script><script type="text/javascript" class="script-hammerhead-shadow-ui" charset="UTF-8" src="./common/script2.js"></script><script type="text/javascript" class="script-hammerhead-shadow-ui" charset="UTF-8">var script1 = 1;</script><script type="text/javascript" class="script-hammerhead-shadow-ui" charset="UTF-8">var script2 = 2;</script></head>
<link rel="stylesheet" type="text/css" class="ui-stylesheet-hammerhead-shadow-ui" href="./styles.css"><script type="text/javascript" class="script-hammerhead-shadow-ui" charset="UTF-8" src="common/script1.js"></script><script type="text/javascript" class="script-hammerhead-shadow-ui" charset="UTF-8" src="./common/script2.js"></script><script type="text/javascript" class="script-hammerhead-shadow-ui" charset="UTF-8">var script1 = 1;</script><script type="text/javascript" class="script-hammerhead-shadow-ui" charset="UTF-8">var script2 = 2;</script><script type="text/javascript" class="script-hammerhead-shadow-ui" charset="UTF-8" src="/custom-script1.js"></script><script type="text/javascript" class="script-hammerhead-shadow-ui" charset="UTF-8" src="/custom-script2.js"></script></head>
<body><script class="self-removing-script-hammerhead-shadow-ui">(function () {var currentScript = document.currentScript;if (!currentScript) {var scripts = document.scripts;var scriptsLength = scripts.length;currentScript = scripts[scriptsLength - 1];}currentScript.parentNode.removeChild(currentScript);if (window["%hammerhead%"])window["%hammerhead%"].sandbox.node.raiseBodyCreatedEvent();})();</script>
<h1>Simple page</h1>

Expand Down
1 change: 1 addition & 0 deletions test/server/proxy/without-request-pipeline-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('New API', () => {
stylesheets: ['./styles.css'],
scripts: ['common/script1.js', './common/script2.js'],
embeddedScripts: ['var script1 = 1;', 'var script2 = 2;'],
userScripts: ['/custom-script1.js', '/custom-script2.js'],
};

const updatedPageContent = injectResources(pageContent, resources);
Expand Down
7 changes: 7 additions & 0 deletions ts-defs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ declare module 'testcafe-hammerhead' {
stylesheets: string[];
scripts: string[];
embeddedScripts: string[];
userScripts?: string[];
}

export interface PageRestoreStoragesOptions {
Expand Down Expand Up @@ -652,6 +653,9 @@ declare module 'testcafe-hammerhead' {
/** Information for generating the response events **/
onResponseEventData: OnResponseEventData[];

/** The target injectable user scripts **/
injectableUserScripts: string[];

/** Set request options for the current context **/
setRequestOptions (eventFactory: BaseRequestHookEventFactory): void;

Expand All @@ -672,5 +676,8 @@ declare module 'testcafe-hammerhead' {

/** Get OnResponseEventData depending on specified filter **/
getOnResponseEventData ({ includeBody }: { includeBody: boolean }): OnResponseEventData[];

/** Prepare the target injectable user scripts for the current route **/
prepareInjectableUserScripts (eventFactory: BaseRequestHookEventFactory, userScripts: UserScript[]): Promise<void>;
}
}

0 comments on commit 5abd99e

Please sign in to comment.