Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] main from ulixee:main #108

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
4cf7520
feat(core): start without session persistence
blakebyrnes Oct 25, 2024
f8f2c55
chore(profiles): automatic updates
blakebyrnes Oct 26, 2024
81d2c76
feat(net): simplify network stack
blakebyrnes Oct 26, 2024
fe8ac97
fix(client): before disconnect bug
blakebyrnes Oct 26, 2024
1605d79
chore(profiles): automatic updates
blakebyrnes Oct 27, 2024
db8c65f
chore: npm audit fixes
blakebyrnes Oct 27, 2024
b8aed1e
fix(commons): parse argv env files
blakebyrnes Oct 27, 2024
f8c09ac
chore: update lints
blakebyrnes Oct 28, 2024
d2d1e87
chore: fix dep using ssh
blakebyrnes Oct 28, 2024
cbb655f
refactor: merge callbacks with new page scripts
blakebyrnes Oct 25, 2024
ecaf56e
fix(agent): make ws callbacks no-cors
blakebyrnes Oct 25, 2024
1ad0c37
chore(agent): standarized new document callbacks
blakebyrnes Oct 28, 2024
20e2c41
fix(agent): single new doc callback per name
blakebyrnes Oct 28, 2024
26fe996
Merge pull request #326 from ulixee/page-scripts
soundofspace Oct 28, 2024
e21e077
chore(profiles): automatic updates
blakebyrnes Oct 29, 2024
3c18030
fix(core): dont log devtools internal ws messages
blakebyrnes Oct 29, 2024
2c61ca3
fix(net): set ws disconnecting earlier
blakebyrnes Oct 29, 2024
3861068
chore(profiles): automatic updates
blakebyrnes Oct 30, 2024
5942f27
fix(double-agent): kill trailing tls process
blakebyrnes Oct 30, 2024
e9671f8
chore(browser-emulator-builder): automatic update emulator data ref
blakebyrnes Oct 30, 2024
e0834be
chore(profiles): automatic updates
blakebyrnes Oct 31, 2024
3490565
chore(browser-emulator-builder): automatic update emulator data ref
blakebyrnes Oct 31, 2024
1988c85
chore(profiles): automatic updates
blakebyrnes Nov 1, 2024
1fa82d1
chore(profiles): automatic updates
blakebyrnes Nov 2, 2024
b5d073a
chore(profiles): automatic updates
blakebyrnes Nov 3, 2024
5678e06
chore(profiles): automatic updates
blakebyrnes Nov 4, 2024
e09385a
chore(profiles): automatic updates
blakebyrnes Nov 5, 2024
5a8a5fe
fix(net): clear connect when disconnected
blakebyrnes Nov 5, 2024
5830b3d
feat(agent): default to chrome 130
blakebyrnes Nov 5, 2024
94d31f4
chore(profiles): automatic updates
blakebyrnes Nov 6, 2024
62c359c
Merge pull request #328 from ulixee/chrome-130
blakebyrnes Nov 6, 2024
19c3ec7
chore(profiles): automatic updates
blakebyrnes Nov 7, 2024
ae29d7d
chore(browser-emulator-builder): automatic update emulator data ref
blakebyrnes Nov 7, 2024
3eabd96
fix(double-agent): probe data out of sync
blakebyrnes Nov 8, 2024
a5aebda
chore(profiles): automatic updates
blakebyrnes Nov 8, 2024
65fd65c
chore(profiles): automatic updates
blakebyrnes Nov 9, 2024
ffe14f5
chore(profiles): automatic updates
blakebyrnes Nov 10, 2024
ee6bd24
chore(profiles): automatic updates
blakebyrnes Nov 11, 2024
d8890fb
chore(profiles): automatic updates
blakebyrnes Nov 12, 2024
e9e7122
chore(profiles): automatic updates
blakebyrnes Nov 13, 2024
87d77aa
chore(profiles): automatic updates
blakebyrnes Nov 14, 2024
9ec71a9
chore(browser-emulator-builder): automatic update emulator data ref
blakebyrnes Nov 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 5 additions & 13 deletions agent/docs/Page.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,30 +89,22 @@ Shortcut to type a string of text.

- text `string`. Text characters to type.

### addNewDocumentScript(script, isolateFromWebpage): Promise<{ identifier:string }>
### addNewDocumentScript(script, isolateFromWebpage, callbacks): Promise<{ identifier:string }>

Add a new script to run on all new Frames created from this Page, as well as before any new Navigations are completed on the main [Frame](./Frame.md). Returns an identifier that can be uninstalled by passing to `page.removeDocumentScript`. These scripts also have a `callback(name: string, payload: string)` injected, which they can use to send data to nodejs, see `addPageCallback` for how to listen to this callback.
Add a new script to run on all new Frames created from this Page, as well as before any new Navigations are completed on the main [Frame](./Frame.md). Returns an identifier that can be uninstalled by passing to `page.removeDocumentScript`. These scripts optionally can have a `callbackFn` that can be provided. The first parameter will be the returned identifier.

#### **Arguments**:

- script `string`. The script to run
- isolateFromWebpage `boolean`. Should this script run in the same Javascript memory as the main webpage, or should it be isolated. NOTE: to manipulate the default DOM objects and methods, you must set this flag to `false`. Defaults to `true`.
- callbacks `object`. Optional callback function details
- \<key\> `string`. The name of the callback function
- \<value\> `(identifier: string, frameId: string) => void` Optional callback to trigger when this Page Binding is triggered. Will also emit the event `page-callback-triggered`.

### removeDocumentScript(scriptId): Promise<void>

Uninstall a newDocumentScript. NOTE: this will not un-do anything ran on the current Page + Frames.

#### **Arguments**:

### addPageCallback(name, onCallbackFn): Promise<RegisteredEventListener>

All scripts added by addNewDocumentScript have a callback function injected in them. This "function" can be called from in-Page javascript back to Node.js. By calling addPageCallback with the same name as used in the page script you can subscribe to these callbacks and run onCallbackFn.

#### **Arguments**:

- name `string`. The name used when running `callback(name, payload)` from within a page script.
- onCallbackFn `(payload: string, frameId: string) => void` A callback to trigger when this Page Binding is triggered.

### setJavaScriptEnabled(enabled): Promise<void>

Disable (or re-enable) Page scripts/javascript. This still allows the Isolated environment to operate.
Expand Down
1 change: 1 addition & 0 deletions agent/main/lib/Browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ export default class Browser extends TypedEventEmitter<IBrowserEvents> implement
...this.engine.launchArguments,
'--no-startup-window',
'--use-mock-keychain', // Use mock keychain on Mac to prevent blocking permissions dialogs
'--disable-features=MediaRouter', // no prompt for network connections
];

if (!options.disableMitm) {
Expand Down
2 changes: 1 addition & 1 deletion agent/main/lib/BrowserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ export default class BrowserContext
try {
const logId = this.logger.info('BrowserContext.Closing');
for (const waitingPage of this.waitForPageAttachedById.values()) {
await waitingPage.reject(new CanceledPromiseError('BrowserContext shutting down'), true);
waitingPage.reject(new CanceledPromiseError('BrowserContext shutting down'), true);
}
if (this.browser.devtoolsSession.isConnected()) {
await Promise.all([...this.pagesById.values()].map(x => x.close()));
Expand Down
17 changes: 16 additions & 1 deletion agent/main/lib/DevtoolsSessionLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default class DevtoolsSessionLogger extends TypedEventEmitter<IDevtoolsLo
{ maxLength: 0, path: ['request', 'postDataEntries'] },
],
],
['Network.webSocketFrameSent', [{ maxLength: 50, path: 'payloadData' }]],
]);

public readonly truncateMessageResponses: Set<string>;
Expand All @@ -40,6 +41,7 @@ export default class DevtoolsSessionLogger extends TypedEventEmitter<IDevtoolsLo
private events = new EventSubscriber();
private fetchRequestIdToNetworkId = new Map<string, string>();
private devtoolsSessions = new WeakSet<DevtoolsSession>();
private requestsToSkip = new Set<string>();
private browserContextInitiatedMessageIds = new Set<number>();
private sentMessagesById: {
[id: number]: {
Expand Down Expand Up @@ -78,7 +80,12 @@ export default class DevtoolsSessionLogger extends TypedEventEmitter<IDevtoolsLo
this.onEventReceive.bind(this, details),
true,
);
this.events.on(devtoolsSession.messageEvents, 'send', this.onEventSend.bind(this, details), true);
this.events.on(
devtoolsSession.messageEvents,
'send',
this.onEventSend.bind(this, details),
true,
);
}

private onEventSend(
Expand Down Expand Up @@ -143,6 +150,12 @@ export default class DevtoolsSessionLogger extends TypedEventEmitter<IDevtoolsLo
params.networkId ??
params.requestId;
if (params.networkId) this.fetchRequestIdToNetworkId.set(params.requestId, params.networkId);
if (
event.method === 'Network.webSocketCreated' &&
this.browserContext.websocketSession?.isWebsocketUrl(params.url)
) {
this.requestsToSkip.add(requestId);
}

if (!pageId && params.targetInfo && params.targetInfo?.type === 'page') {
pageId = params.targetInfo.targetId;
Expand Down Expand Up @@ -173,6 +186,8 @@ export default class DevtoolsSessionLogger extends TypedEventEmitter<IDevtoolsLo
event.frameId = frameId;
event.requestId = requestId;

if (requestId && this.requestsToSkip.has(requestId)) return;

const method = event.method;
const result = event.result;
if (result) {
Expand Down
66 changes: 37 additions & 29 deletions agent/main/lib/FramesManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { IBoundLog } from '@ulixee/commons/interfaces/ILog';
import { CanceledPromiseError } from '@ulixee/commons/interfaces/IPendingWaitEvent';
import { IWebsocketEvents } from '@ulixee/unblocked-specification/agent/browser/IWebsocketSession';
import IResourceMeta from '@ulixee/unblocked-specification/agent/net/IResourceMeta';
import { IPageEvents } from '@ulixee/unblocked-specification/agent/browser/IPage';
import { IDomPaintEvent } from '@ulixee/unblocked-specification/agent/browser/Location';
import {
IPageEvents,
TNewDocumentCallbackFn,
} from '@ulixee/unblocked-specification/agent/browser/IPage';
import Resolvable from '@ulixee/commons/lib/Resolvable';
import DevtoolsSession from './DevtoolsSession';
import Frame from './Frame';
Expand Down Expand Up @@ -48,8 +50,6 @@ export default class FramesManager extends TypedEventEmitter<IFrameManagerEvents
}

public devtoolsSession: DevtoolsSession;
public websocketIdToFrameId = new Map<string, string>();

protected readonly logger: IBoundLog;

private onFrameCreatedResourceEventsByFrameId: {
Expand All @@ -71,7 +71,7 @@ export default class FramesManager extends TypedEventEmitter<IFrameManagerEvents
private readonly events = new EventSubscriber();
private readonly networkManager: NetworkManager;
private readonly domStorageTracker: DomStorageTracker;
private pageCallbacks = new Map<string, Array<(payload: string, frame: IFrame) => any>>();
private pageCallbacks = new Map<string, TNewDocumentCallbackFn>();

private isReady: Promise<void>;

Expand All @@ -93,7 +93,7 @@ export default class FramesManager extends TypedEventEmitter<IFrameManagerEvents
this.events.on(
this.websocketSession,
'message-received',
this.onWebsocketSessionMessageRecieved,
this.onWebsocketSessionMessageReceived,
);
}

Expand Down Expand Up @@ -201,22 +201,23 @@ export default class FramesManager extends TypedEventEmitter<IFrameManagerEvents
this.framesByFrameId.clear();
}

public async addPageCallback(
name: string,
onCallback: (payload: string, frame: IFrame) => any,
): Promise<any> {
const callbacks = this.pageCallbacks.get(name) ?? [];
if (callbacks.length === 0) this.pageCallbacks.set(name, callbacks);
callbacks.push(onCallback);
}

public async addNewDocumentScript(
script: string,
installInIsolatedScope = true,
callbacks?: { [name: string]: TNewDocumentCallbackFn | null },
devtoolsSession?: DevtoolsSession,
): Promise<{ identifier: string }> {
devtoolsSession ??= this.devtoolsSession;
script = this.websocketSession.injectWebsocketCallbackIntoScript(script);
if (callbacks) {
script = this.websocketSession.injectWebsocketCallbackIntoScript(script);
for (const [name, onCallbackFn] of Object.entries(callbacks)) {
if (onCallbackFn) {
if (this.pageCallbacks.has(name) && this.pageCallbacks.get(name) !== onCallbackFn)
throw new Error(`Duplicate page callback registered ${name}`);
this.pageCallbacks.set(name, onCallbackFn);
}
}
}
const installedScript = await devtoolsSession.send('Page.addScriptToEvaluateOnNewDocument', {
source: script,
worldName: installInIsolatedScope ? ISOLATED_WORLD : undefined,
Expand Down Expand Up @@ -466,14 +467,12 @@ export default class FramesManager extends TypedEventEmitter<IFrameManagerEvents
this.domStorageTracker.track(frame.securityOrigin);
}

private onDomPaintEvent(
frameId: number,
paintEvent: { event: IDomPaintEvent; timestamp: number; url: string },
): void {
const { event, timestamp, url } = paintEvent;
private onDomPaintEvent(payload: string, frame: IFrame): void {
const { event, timestamp, url } = JSON.parse(payload);
const frameId = frame.frameId;
void this.isReady.then(() => {
const frame = this.framesByFrameId.get(frameId);
frame.navigations.onDomPaintEvent(event, url, timestamp);
const coreFrame = this.framesByFrameId.get(frameId);
coreFrame.navigations.onDomPaintEvent(event, url, timestamp);
return null;
});
}
Expand Down Expand Up @@ -692,14 +691,23 @@ export default class FramesManager extends TypedEventEmitter<IFrameManagerEvents
);
}

private async onWebsocketSessionMessageRecieved(
private async onWebsocketSessionMessageReceived(
event: IWebsocketEvents['message-received'],
): Promise<void> {
const callbacks = this.pageCallbacks.get(event.name);
const frame = this.framesById.get(event.id);
if (!callbacks || !frame) return;
const callback = this.pageCallbacks.get(event.name);
let frame = this.framesById.get(event.id);
if (!frame) {
// try again after ready
await this.isReady;
frame = this.framesById.get(event.id);
if (!frame) return;
}

await this.isReady;
callbacks.forEach(callback => callback(event.payload, frame));
if (callback) await callback(event.payload, frame);
this.page.emit('page-callback-triggered', {
name: event.name,
frameId: frame.frameId,
payload: event.payload,
});
}
}
16 changes: 6 additions & 10 deletions agent/main/lib/InjectedScripts.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as fs from 'fs';
import { stringifiedTypeSerializerClass } from '@ulixee/commons/lib/TypeSerializer';
import { IDomPaintEvent } from '@ulixee/unblocked-specification/agent/browser/Location';
import FramesManager from './FramesManager';
import DevtoolsSession from './DevtoolsSession';
import { TNewDocumentCallbackFn } from '@ulixee/unblocked-specification/agent/browser/IPage';

const pageScripts = {
NodeTracker: fs.readFileSync(`${__dirname}/../injected-scripts/NodeTracker.js`, 'utf8'),
Expand All @@ -11,7 +11,7 @@ const pageScripts = {
PaintEvents: fs.readFileSync(`${__dirname}/../injected-scripts/PaintEvents.js`, 'utf8'),
};

const pageEventsCallbackName = '__ulxPagePaintEventListenerCallback';
const pageEventsCallbackName = 'onPaintEvent';
export const injectedScript = `(function ulxInjectedScripts(callbackName) {
const exports = {}; // workaround for ts adding an exports variable
${stringifiedTypeSerializerClass};
Expand All @@ -35,19 +35,15 @@ export default class InjectedScripts {
public static install(
framesManager: FramesManager,
devtoolsSession: DevtoolsSession,
onPaintEvent: (
frameId: number,
event: { url: string; event: IDomPaintEvent; timestamp: number },
) => void,
onPaintEvent: TNewDocumentCallbackFn,
): Promise<any> {
return Promise.all([
framesManager.addPageCallback(
pageEventsCallbackName,
(payload, frame) => onPaintEvent(frame.frameId, JSON.parse(payload)),
),
framesManager.addNewDocumentScript(
injectedScript,
framesManager.page.installJsPathIntoIsolatedContext,
{
[pageEventsCallbackName]: onPaintEvent,
},
devtoolsSession,
),
]);
Expand Down
33 changes: 12 additions & 21 deletions agent/main/lib/Page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
import { IBoundLog } from '@ulixee/commons/interfaces/ILog';
import { CanceledPromiseError } from '@ulixee/commons/interfaces/IPendingWaitEvent';
import IRegisteredEventListener from '@ulixee/commons/interfaces/IRegisteredEventListener';
import EventSubscriber from '@ulixee/commons/lib/EventSubscriber';
import { TypedEventEmitter } from '@ulixee/commons/lib/eventUtils';
import Timer from '@ulixee/commons/lib/Timer';
Expand All @@ -26,7 +25,11 @@ import IDialog from '@ulixee/unblocked-specification/agent/browser/IDialog';
import IExecJsPathResult from '@ulixee/unblocked-specification/agent/browser/IExecJsPathResult';
import { IFrame } from '@ulixee/unblocked-specification/agent/browser/IFrame';
import INavigation from '@ulixee/unblocked-specification/agent/browser/INavigation';
import { IPage, IPageEvents } from '@ulixee/unblocked-specification/agent/browser/IPage';
import {
IPage,
IPageEvents,
TNewDocumentCallbackFn,
} from '@ulixee/unblocked-specification/agent/browser/IPage';
import IScreenshotOptions from '@ulixee/unblocked-specification/agent/browser/IScreenshotOptions';
import { ILoadStatus, LoadStatus } from '@ulixee/unblocked-specification/agent/browser/Location';
import {
Expand Down Expand Up @@ -235,9 +238,15 @@ export default class Page extends TypedEventEmitter<IPageLevelEvents> implements
addNewDocumentScript(
script: string,
isolatedEnvironment: boolean,
callbacks?: { [name: string]: TNewDocumentCallbackFn | null },
devtoolsSession?: DevtoolsSession,
): Promise<{ identifier: string }> {
return this.framesManager.addNewDocumentScript(script, isolatedEnvironment, devtoolsSession);
return this.framesManager.addNewDocumentScript(
script,
isolatedEnvironment,
callbacks,
devtoolsSession,
);
}

removeDocumentScript(identifier: string, devtoolsSession?: DevtoolsSession): Promise<void> {
Expand All @@ -247,24 +256,6 @@ export default class Page extends TypedEventEmitter<IPageLevelEvents> implements
);
}

addPageCallback(
name: string,
onCallback?: (payload: string, frame: IFrame) => any,
): Promise<IRegisteredEventListener> {
return this.framesManager.addPageCallback(
name,
(payload, frame) => {
if (onCallback) onCallback(payload, frame);

this.emit('page-callback-triggered', {
name,
payload,
frameId: frame.frameId,
});
},
);
}

async setJavaScriptEnabled(enabled: boolean): Promise<void> {
await this.devtoolsSession.send('Emulation.setScriptExecutionDisabled', {
value: !enabled,
Expand Down
3 changes: 0 additions & 3 deletions agent/main/lib/WebsocketMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,6 @@ export default class WebsocketMessages extends TypedEventEmitter<{
isMitmEnabled: boolean,
): IWebsocketMessage | undefined {
if (!event.resourceId && isMitmEnabled) {
this.logger.error(`CaptureWebsocketMessageError.UnregisteredResource`, {
event,
});
return;
}

Expand Down
17 changes: 9 additions & 8 deletions agent/main/lib/WebsocketSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const SCRIPT_PLACEHOLDER = '';
const { log } = Log(module);

export class WebsocketSession extends TypedEventEmitter<IWebsocketEvents> {
readonly isReady: Promise<void>;
isReady: Resolvable<void>;

private readonly host = 'websocket.localhost';
private port: number;
Expand All @@ -37,27 +37,28 @@ export class WebsocketSession extends TypedEventEmitter<IWebsocketEvents> {
}

async initialize(): Promise<void> {
const resolver = new Resolvable<void>(10e3);
if (this.isReady) return this.isReady.promise;
this.isReady = new Resolvable();

this.server.on('error', resolver.reject);
this.server.on('error', this.isReady.reject);
this.server.listen(0, () => {
const address = this.server.address();
if (typeof address === 'string') {
throw new Error('Unexpected server address format (string)');
}
this.port = address.port;
resolver.resolve();
this.isReady.resolve();
});

this.server.on('upgrade', this.handleUpgrade.bind(this));
this.wss.on('connection', this.handleConnection.bind(this));

return resolver.promise;
return this.isReady.promise;
}

close(): void {
this.wss.close();
this.server.close();
this.server.unref().close();
this.intervals.forEach(interval => clearInterval(interval));
}

Expand Down Expand Up @@ -166,8 +167,8 @@ function injectedScript(): void {
const url = `${this.host}:${this.port}?secret=${this.secret}&clientId=${clientId}`;
// This will signal to network manager we are trying to make websocket connection
// This is needed later to map clientId to frameId
// eslint-disable-next-line no-console
fetch(`http://${url}`).catch(error => console.log(error));
fetch(`http://${url}`).catch(() => {});

let callback: WebsocketCallback;
try {
const socket = new WebSocket(`ws://${url}`);
Expand Down
Loading