Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/client/lib/SetupAwaitedHandler.ts
1028 views
1
import AwaitedHandler, { NotImplementedError } from 'awaited-dom/base/AwaitedHandler';
2
import AwaitedPath, { IJsPath } from 'awaited-dom/base/AwaitedPath';
3
import Constructable from 'awaited-dom/base/Constructable';
4
import INodePointer from 'awaited-dom/base/INodePointer';
5
import IExecJsPathResult from '@secret-agent/interfaces/IExecJsPathResult';
6
import { getNodePointerFnName } from '@secret-agent/interfaces/jsPathFnNames';
7
import StateMachine from 'awaited-dom/base/StateMachine';
8
import IJsPathResult from '@secret-agent/interfaces/IJsPathResult';
9
import IAwaitedOptions from '../interfaces/IAwaitedOptions';
10
import CoreFrameEnvironment from './CoreFrameEnvironment';
11
12
export const delegate = {
13
getProperty,
14
setProperty,
15
construct,
16
runMethod,
17
runStatic,
18
createNodePointer,
19
};
20
21
interface IStateHandler<TClass> {
22
getState(instance: TClass): any;
23
setState(instance: TClass, properties: any): void;
24
className?: string;
25
}
26
27
// Sets up AwaitedHandler initializer hooks. See Noderdom/AwaitedDOM
28
AwaitedHandler.delegate = delegate;
29
AwaitedHandler.setStorageSymbol(Symbol.for('@secret-agent/InternalAwaitedState'));
30
31
async function getProperty<T, TClass>(
32
stateHandler: IStateHandler<TClass>,
33
instance: TClass,
34
name: string,
35
): Promise<T> {
36
const { awaitedPath, coreFrame, awaitedOptions } = await getAwaitedState(stateHandler, instance);
37
const finalPath = awaitedPath.addProperty(instance as any, name);
38
39
const result = await execJsPath<T>(coreFrame, awaitedOptions, finalPath.toJSON());
40
return cleanResult(stateHandler, instance, result, new Error().stack);
41
}
42
43
async function setProperty<T, TClass>(
44
stateHandler: IStateHandler<TClass>,
45
instance: TClass,
46
name: string,
47
value: T,
48
): Promise<void> {
49
await awaitRemoteInitializer(stateHandler.getState(instance));
50
stateHandler.setState(instance, { [name]: value });
51
}
52
53
async function runMethod<T, TClass>(
54
stateHandler: IStateHandler<TClass>,
55
instance: TClass,
56
name: string,
57
args: any[],
58
): Promise<T> {
59
const { awaitedPath, coreFrame, awaitedOptions } = await getAwaitedState(stateHandler, instance);
60
const finalPath = awaitedPath.addMethod(instance as any, name, ...args);
61
62
const result = await execJsPath<T>(coreFrame, awaitedOptions, finalPath.toJSON());
63
return cleanResult(stateHandler, instance, result, new Error().stack);
64
}
65
66
async function createNodePointer<TClass>(
67
stateHandler: IStateHandler<TClass>,
68
instance: TClass,
69
): Promise<INodePointer> {
70
const { awaitedPath, coreFrame, awaitedOptions } = await getAwaitedState(stateHandler, instance);
71
const finalPath = awaitedPath.addMethod(instance as any, getNodePointerFnName);
72
73
const result = await execJsPath<null>(coreFrame, awaitedOptions, finalPath.toJSON());
74
return result?.nodePointer;
75
}
76
77
function runStatic<T, TClass>(
78
stateHandler: IStateHandler<TClass>,
79
_klass: Constructable<TClass>,
80
name: string,
81
): T {
82
throw new NotImplementedError(`${stateHandler.className}.${name} static method not implemented`);
83
}
84
85
function construct<TClass>(self: AwaitedHandler<TClass>): TClass {
86
throw new NotImplementedError(`${self.className} constructor not implemented`);
87
}
88
89
export async function getAwaitedState<TClass>(
90
stateHandler: IStateHandler<TClass>,
91
instance: TClass,
92
): Promise<{
93
awaitedPath: AwaitedPath;
94
coreFrame: CoreFrameEnvironment;
95
awaitedOptions: IAwaitedOptions;
96
}> {
97
await awaitRemoteInitializer(instance);
98
const state = stateHandler.getState(instance);
99
const awaitedPath = state.awaitedPath as AwaitedPath;
100
const awaitedOptions = state.awaitedOptions as IAwaitedOptions;
101
const awaitedCoreFrame = await awaitedOptions.coreFrame;
102
return { awaitedPath, coreFrame: awaitedCoreFrame, awaitedOptions };
103
}
104
105
export function getAwaitedPathAsMethodArg(awaitedPath: AwaitedPath): string {
106
return `$$jsPath=${JSON.stringify(awaitedPath.toJSON())}`;
107
}
108
109
const { getState: getAwaitedPathState } = StateMachine<any, { awaitedPath?: AwaitedPath }>();
110
111
export function convertJsPathArgs(path: IJsPath): void {
112
for (const part of path) {
113
// if part is method call, see if any params need to be remotely initialized first
114
if (!Array.isArray(part)) continue;
115
116
for (let i = 0; i < part.length; i += 1) {
117
const param = part[i];
118
if (typeof param === 'object') {
119
if (Array.isArray(param)) {
120
convertJsPathArgs(param);
121
} else {
122
const awaitedPath = getAwaitedPathState(param)?.awaitedPath;
123
if (awaitedPath) {
124
part[i] = getAwaitedPathAsMethodArg(awaitedPath);
125
}
126
}
127
}
128
}
129
}
130
}
131
132
export async function execJsPath<T>(
133
coreFrame: CoreFrameEnvironment,
134
awaitedOptions: IAwaitedOptions & { prefetchedJsPaths?: Promise<Map<string, IJsPathResult>> },
135
path: IJsPath,
136
): Promise<IExecJsPathResult<T>> {
137
convertJsPathArgs(path);
138
if (awaitedOptions.prefetchedJsPaths) {
139
const prefetchedJsPaths = await awaitedOptions.prefetchedJsPaths;
140
const prefetched = prefetchedJsPaths.get(JSON.stringify(path));
141
if (prefetched) {
142
const result = prefetched.result;
143
coreFrame.recordDetachedJsPath(prefetched.index, new Date(), new Date());
144
return result;
145
}
146
}
147
return coreFrame.execJsPath<T>(path);
148
}
149
150
export function cleanResult<T, TClass>(
151
stateHandler: IStateHandler<TClass>,
152
instance: TClass,
153
result: IExecJsPathResult<T>,
154
startStack: string,
155
): T {
156
if (!result) return null;
157
158
if (result.nodePointer) {
159
stateHandler.setState(instance, {
160
nodePointer: result.nodePointer,
161
});
162
}
163
if (result?.pathError) {
164
const error = new Error(result.pathError.error);
165
error.name = 'InjectedScriptError';
166
(error as any).pathState = result.pathError.pathState;
167
error.stack = startStack.replace('Error:', '').trim();
168
throw error;
169
}
170
return result.value;
171
}
172
173
async function awaitRemoteInitializer(state: any): Promise<void> {
174
if (state?.remoteInitializerPromise) {
175
await state.remoteInitializerPromise;
176
}
177
}
178
179