Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/client/lib/Resource.ts
1028 views
1
import inspectInstanceProperties from 'awaited-dom/base/inspectInstanceProperties';
2
import StateMachine from 'awaited-dom/base/StateMachine';
3
import ResourceType from '@secret-agent/interfaces/ResourceType';
4
import IResourceMeta from '@secret-agent/interfaces/IResourceMeta';
5
import Timer from '@secret-agent/commons/Timer';
6
import IWaitForResourceOptions from '@secret-agent/interfaces/IWaitForResourceOptions';
7
import TimeoutError from '@secret-agent/commons/interfaces/TimeoutError';
8
import * as Util from 'util';
9
import CoreTab from './CoreTab';
10
import ResourceRequest, { createResourceRequest } from './ResourceRequest';
11
import ResourceResponse, { createResourceResponse } from './ResourceResponse';
12
import { createWebsocketResource } from './WebsocketResource';
13
import IWaitForResourceFilter from '../interfaces/IWaitForResourceFilter';
14
import Tab, { getCoreTab } from './Tab';
15
16
const { getState, setState } = StateMachine<Resource, IState>();
17
18
interface IState {
19
resource: IResourceMeta;
20
request: ResourceRequest;
21
response: ResourceResponse;
22
coreTab: Promise<CoreTab>;
23
}
24
25
const propertyKeys: (keyof Resource)[] = [
26
'url',
27
'isRedirect',
28
'type',
29
'request',
30
'response',
31
'documentUrl',
32
'data',
33
'json',
34
'text',
35
];
36
37
export default class Resource {
38
public get request(): ResourceRequest {
39
return getState(this).request;
40
}
41
42
public get response(): ResourceResponse {
43
return getState(this).response;
44
}
45
46
public get url(): string {
47
return getState(this).resource.url;
48
}
49
50
public get documentUrl(): string {
51
return getState(this).resource.documentUrl;
52
}
53
54
public get type(): ResourceType {
55
return getState(this).resource.type;
56
}
57
58
public get isRedirect(): boolean {
59
return getState(this).resource.isRedirect ?? false;
60
}
61
62
public get data(): Promise<Buffer> {
63
const id = getState(this).resource.id;
64
const coreTab = getState(this).coreTab;
65
return coreTab.then(x => x.getResourceProperty<Buffer>(id, 'data'));
66
}
67
68
public text(): Promise<string> {
69
return this.data.then(x => x.toString());
70
}
71
72
public json(): Promise<any> {
73
return this.text().then(JSON.parse);
74
}
75
76
public [Util.inspect.custom](): any {
77
return inspectInstanceProperties(this, propertyKeys as any);
78
}
79
80
public static async waitFor(
81
tab: Tab,
82
filter: IWaitForResourceFilter,
83
options: IWaitForResourceOptions,
84
): Promise<Resource[]> {
85
const coreTab = await getCoreTab(tab);
86
const resources: Resource[] = [];
87
88
const idsSeen = new Set<number>();
89
90
const timer = new Timer(options?.timeoutMs ?? 30e3);
91
92
const resourceFilter = { url: filter.url, type: filter.type };
93
const resourceOptions: IWaitForResourceOptions = {
94
sinceCommandId: options?.sinceCommandId,
95
timeoutMs: 2e3,
96
throwIfTimeout: false,
97
};
98
99
let isComplete = false;
100
const done = (): boolean => (isComplete = true);
101
102
do {
103
try {
104
const waitForResourcePromise = coreTab.waitForResource(resourceFilter, resourceOptions);
105
const foundResources = await timer.waitForPromise(
106
waitForResourcePromise,
107
'Timeout waiting for Resource(s)',
108
);
109
resourceOptions.sinceCommandId = coreTab.commandQueue.lastCommandId;
110
111
for (const resourceMeta of foundResources) {
112
if (idsSeen.has(resourceMeta.id)) continue;
113
idsSeen.add(resourceMeta.id);
114
115
const resource = createResource(Promise.resolve(coreTab), resourceMeta);
116
117
let shouldInclude = true;
118
119
if (filter.filterFn) {
120
// resources can trigger commandQueue functions, so time them out
121
shouldInclude = await timer.waitForPromise(
122
Promise.resolve(filter.filterFn(resource, done)),
123
'Timeout waiting for waitResource.filterFn',
124
);
125
}
126
127
if (shouldInclude) resources.push(resource);
128
129
if (isComplete) break;
130
}
131
} catch (err) {
132
if (err instanceof TimeoutError) {
133
if (options?.throwIfTimeout === false) {
134
return resources;
135
}
136
}
137
throw err;
138
}
139
140
// if no filter callback provided, break after 1 found
141
if (!filter.filterFn && resources.length) {
142
done();
143
}
144
} while (!isComplete);
145
146
return resources;
147
}
148
}
149
150
export function createResource(coreTab: Promise<CoreTab>, resourceMeta: IResourceMeta): Resource {
151
if (resourceMeta.type === 'Websocket') {
152
return createWebsocketResource(resourceMeta, coreTab);
153
}
154
const resource = new Resource();
155
const request = createResourceRequest(coreTab, resourceMeta.id);
156
const response = createResourceResponse(coreTab, resourceMeta.id);
157
setState(resource, { coreTab, resource: resourceMeta, request, response });
158
return resource;
159
}
160
161