@intuned/runtime-dev 0.0.1-split.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +21 -0
- package/.eslintignore +10 -0
- package/.eslintrc.js +39 -0
- package/.vite/deps_temp_01af7156/package.json +3 -0
- package/.vscode/extensions.json +3 -0
- package/.vscode/launch.json +102 -0
- package/.vscode/settings.json +12 -0
- package/WebTemplate/accessKeyHelpers.ts +28 -0
- package/WebTemplate/api.ts +139 -0
- package/WebTemplate/app.ts +18 -0
- package/WebTemplate/controllers/async.ts +138 -0
- package/WebTemplate/controllers/authSessions/check.ts +68 -0
- package/WebTemplate/controllers/authSessions/create.ts +128 -0
- package/WebTemplate/controllers/authSessions/index.ts +41 -0
- package/WebTemplate/controllers/authSessions/killOperation.ts +35 -0
- package/WebTemplate/controllers/authSessions/resumeOperation.ts +80 -0
- package/WebTemplate/controllers/authSessions/store.ts +14 -0
- package/WebTemplate/controllers/controllers.ts +73 -0
- package/WebTemplate/controllers/runApi/helpers.ts +220 -0
- package/WebTemplate/controllers/runApi/index.ts +68 -0
- package/WebTemplate/controllers/runApi/types.ts +13 -0
- package/WebTemplate/controllers/traces.ts +151 -0
- package/WebTemplate/features.ts +8 -0
- package/WebTemplate/headers.ts +6 -0
- package/WebTemplate/index.playwright.ts +47 -0
- package/WebTemplate/index.vanilla.ts +44 -0
- package/WebTemplate/jobs.ts +356 -0
- package/WebTemplate/shutdown.ts +64 -0
- package/WebTemplate/utils.ts +294 -0
- package/bin/intuned-api-run +2 -0
- package/bin/intuned-auth-session-check +2 -0
- package/bin/intuned-auth-session-create +2 -0
- package/bin/intuned-auth-session-load +2 -0
- package/bin/intuned-auth-session-refresh +2 -0
- package/bin/intuned-browser-save-state +2 -0
- package/bin/intuned-browser-start +2 -0
- package/bin/intuned-build +2 -0
- package/bin/intuned-ts-check +2 -0
- package/package.json +133 -0
- package/playwright.config.ts +48 -0
- package/src/commands/api/run.ts +225 -0
- package/src/commands/auth-sessions/load.ts +42 -0
- package/src/commands/auth-sessions/run-check.ts +70 -0
- package/src/commands/auth-sessions/run-create.ts +124 -0
- package/src/commands/browser/save-state.ts +22 -0
- package/src/commands/browser/start-browser.ts +17 -0
- package/src/commands/build.ts +125 -0
- package/src/commands/common/browserUtils.ts +80 -0
- package/src/commands/common/getDefaultExportFromFile.ts +13 -0
- package/src/commands/common/getFirstLineNumber.test.ts +274 -0
- package/src/commands/common/getFirstLineNumber.ts +146 -0
- package/src/commands/common/sendMessageToClient.ts +8 -0
- package/src/commands/common/utils/fileUtils.ts +25 -0
- package/src/commands/common/utils/settings.ts +23 -0
- package/src/commands/common/utils/webTemplate.ts +46 -0
- package/src/commands/testing/saveVisibleHtml.ts +29 -0
- package/src/commands/ts-check.ts +88 -0
- package/src/common/Logger/Logger/index.ts +64 -0
- package/src/common/Logger/Logger/types.ts +9 -0
- package/src/common/Logger/index.ts +64 -0
- package/src/common/Logger/types.ts +9 -0
- package/src/common/assets/browser_scripts.js +2214 -0
- package/src/common/asyncLocalStorage/index.ts +29 -0
- package/src/common/cleanEnvironmentVariables.ts +13 -0
- package/src/common/constants.ts +1 -0
- package/src/common/contextStorageStateHelpers.ts +71 -0
- package/src/common/getPlaywrightConstructs.ts +283 -0
- package/src/common/jwtTokenManager.ts +111 -0
- package/src/common/settingsSchema.ts +16 -0
- package/src/common/telemetry.ts +49 -0
- package/src/index.ts +14 -0
- package/src/runtime/RunError.ts +16 -0
- package/src/runtime/downloadDirectory.ts +14 -0
- package/src/runtime/enums.d.ts +11 -0
- package/src/runtime/enums.ts +11 -0
- package/src/runtime/executionHelpers.test.ts +70 -0
- package/src/runtime/export.d.ts +202 -0
- package/src/runtime/extendPayload.ts +22 -0
- package/src/runtime/extendTimeout.ts +32 -0
- package/src/runtime/index.ts +8 -0
- package/src/runtime/requestMoreInfo.ts +40 -0
- package/src/runtime/runInfo.ts +19 -0
- package/template.tsconfig.json +14 -0
- package/tsconfig.eslint.json +5 -0
- package/tsconfig.json +24 -0
- package/typedoc.json +49 -0
- package/vite.config.ts +17 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { authSessionsContextsStore } from "./store";
|
|
2
|
+
import { AUTH_SESSIONS_FOLDER_NAME } from "@intuned/runtime/dist/common/constants";
|
|
3
|
+
import { getDownloadDirectoryPath } from "@intuned/runtime";
|
|
4
|
+
import { getProductionPlaywrightConstructs } from "../../../src/common/getPlaywrightConstructs";
|
|
5
|
+
import { callFunction, getTraceFilePath, isHeadless } from "../../utils";
|
|
6
|
+
import * as fs from "fs-extra";
|
|
7
|
+
import { getContextStorageState } from "@intuned/runtime/dist/common/contextStorageStateHelpers";
|
|
8
|
+
|
|
9
|
+
export async function createAuthSession({
|
|
10
|
+
parameters,
|
|
11
|
+
operationId,
|
|
12
|
+
mode,
|
|
13
|
+
proxy,
|
|
14
|
+
saveTrace,
|
|
15
|
+
}: {
|
|
16
|
+
operationId: string;
|
|
17
|
+
mode: "async" | "sync";
|
|
18
|
+
parameters: object | null | undefined;
|
|
19
|
+
proxy?: {
|
|
20
|
+
server: string;
|
|
21
|
+
username: string;
|
|
22
|
+
password: string;
|
|
23
|
+
};
|
|
24
|
+
saveTrace: boolean;
|
|
25
|
+
}) {
|
|
26
|
+
const isAuthSessionEnabled = (await fs.readJSON("./Intuned.json"))
|
|
27
|
+
.authSessions?.enabled;
|
|
28
|
+
|
|
29
|
+
if (!isAuthSessionEnabled) {
|
|
30
|
+
return {
|
|
31
|
+
status: 400,
|
|
32
|
+
body: {
|
|
33
|
+
error: "Invalid Request",
|
|
34
|
+
message: "auth sessions are not enabled",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (mode !== "async") {
|
|
40
|
+
return {
|
|
41
|
+
status: 400,
|
|
42
|
+
body: {
|
|
43
|
+
error: "Invalid Request",
|
|
44
|
+
message: "only async mode is supported",
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (authSessionsContextsStore.has(operationId)) {
|
|
50
|
+
return {
|
|
51
|
+
status: 400,
|
|
52
|
+
body: {
|
|
53
|
+
error: "Invalid Request",
|
|
54
|
+
message:
|
|
55
|
+
"operation id already exists, please use a different one, or kill the existing operation, and try again",
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const downloadsPath = getDownloadDirectoryPath();
|
|
60
|
+
const headless = isHeadless();
|
|
61
|
+
const { page, context } = await getProductionPlaywrightConstructs({
|
|
62
|
+
headless,
|
|
63
|
+
proxy,
|
|
64
|
+
downloadsPath,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
if (saveTrace) {
|
|
69
|
+
try {
|
|
70
|
+
await context.tracing.start({ screenshots: true, snapshots: true });
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error("Error starting trace", error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const operationGenerator = await callFunction(
|
|
77
|
+
AUTH_SESSIONS_FOLDER_NAME,
|
|
78
|
+
"create",
|
|
79
|
+
[parameters ?? {}, page, context]
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
await fs.remove(downloadsPath);
|
|
83
|
+
|
|
84
|
+
const result = await operationGenerator.next();
|
|
85
|
+
|
|
86
|
+
if (result.done) {
|
|
87
|
+
if (saveTrace) {
|
|
88
|
+
try {
|
|
89
|
+
const traceFilePath = getTraceFilePath(operationId);
|
|
90
|
+
await context.tracing.stop({ path: traceFilePath });
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error("Error stopping trace", error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const fullSessionState = await getContextStorageState(context);
|
|
96
|
+
await context.close();
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
status: 200,
|
|
100
|
+
body: { ...fullSessionState, status: 200 },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
authSessionsContextsStore.set(operationId, {
|
|
105
|
+
done: false,
|
|
106
|
+
generator: operationGenerator,
|
|
107
|
+
context,
|
|
108
|
+
requestInfo: result.value,
|
|
109
|
+
saveTrace,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
status: 200,
|
|
114
|
+
body: result.value,
|
|
115
|
+
};
|
|
116
|
+
} catch (e) {
|
|
117
|
+
if (saveTrace) {
|
|
118
|
+
try {
|
|
119
|
+
const traceFilePath = getTraceFilePath(operationId);
|
|
120
|
+
await context.tracing.stop({ path: traceFilePath });
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error("Error stopping trace", error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
throw e;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createAuthSession } from "./create";
|
|
2
|
+
import { makeEndpointControllers } from "../controllers";
|
|
3
|
+
import { checkAuthSession } from "./check";
|
|
4
|
+
import { killAuthSessionOperation } from "./killOperation";
|
|
5
|
+
import { resumeAuthSessionOperation } from "./resumeOperation";
|
|
6
|
+
|
|
7
|
+
export const {
|
|
8
|
+
sync: createAuthSessionController,
|
|
9
|
+
async: createAuthSessionAsyncController,
|
|
10
|
+
} = makeEndpointControllers({
|
|
11
|
+
requestId: (req) => `createAuthSession ${req.body.operationId}`,
|
|
12
|
+
handler: createAuthSession,
|
|
13
|
+
parameters: (req) => [req.body],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const {
|
|
17
|
+
sync: checkAuthSessionController,
|
|
18
|
+
async: checkAuthSessionAsyncController,
|
|
19
|
+
} = makeEndpointControllers({
|
|
20
|
+
requestId: "checkAuthSession",
|
|
21
|
+
handler: checkAuthSession,
|
|
22
|
+
parameters: (req) => [req.body],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const {
|
|
26
|
+
sync: killAuthSessionOperationController,
|
|
27
|
+
async: killAuthSessionOperationAsyncController,
|
|
28
|
+
} = makeEndpointControllers({
|
|
29
|
+
requestId: (req) => `killAuthSessionOperation ${req.body.operationId}`,
|
|
30
|
+
handler: killAuthSessionOperation,
|
|
31
|
+
parameters: (req) => [req.body],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const {
|
|
35
|
+
sync: resumeAuthSessionOperationController,
|
|
36
|
+
async: resumeAuthSessionOperationAsyncController,
|
|
37
|
+
} = makeEndpointControllers({
|
|
38
|
+
requestId: (req) => `resumeAuthSessionOperation ${req.body.operationId}`,
|
|
39
|
+
handler: resumeAuthSessionOperation,
|
|
40
|
+
parameters: (req) => [req.body],
|
|
41
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { authSessionsContextsStore } from "./store";
|
|
2
|
+
|
|
3
|
+
export async function killAuthSessionOperation({
|
|
4
|
+
operationId,
|
|
5
|
+
}: {
|
|
6
|
+
operationId: string;
|
|
7
|
+
}): Promise<{
|
|
8
|
+
status: number;
|
|
9
|
+
body: any;
|
|
10
|
+
}> {
|
|
11
|
+
const operation = authSessionsContextsStore.get(operationId);
|
|
12
|
+
if (!operation) {
|
|
13
|
+
return {
|
|
14
|
+
status: 200,
|
|
15
|
+
body: {
|
|
16
|
+
done: true,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await operation.context.browser()?.close();
|
|
23
|
+
authSessionsContextsStore.delete(operationId);
|
|
24
|
+
|
|
25
|
+
return { status: 200, body: { done: true } };
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return {
|
|
28
|
+
status: 500,
|
|
29
|
+
body: {
|
|
30
|
+
error,
|
|
31
|
+
message: "the server failed to kill the operation",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { authSessionsContextsStore } from "./store";
|
|
2
|
+
import { getContextStorageState } from "@intuned/runtime/dist/common/contextStorageStateHelpers";
|
|
3
|
+
import { getTraceFilePath } from "../../utils";
|
|
4
|
+
|
|
5
|
+
export async function resumeAuthSessionOperation({
|
|
6
|
+
operationId,
|
|
7
|
+
input,
|
|
8
|
+
}: {
|
|
9
|
+
operationId: string;
|
|
10
|
+
input: string;
|
|
11
|
+
}) {
|
|
12
|
+
const operation = authSessionsContextsStore.get(operationId);
|
|
13
|
+
if (!operation) {
|
|
14
|
+
return {
|
|
15
|
+
status: 404,
|
|
16
|
+
body: {
|
|
17
|
+
error: "Invalid Request",
|
|
18
|
+
message: "operation not found",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
if (
|
|
25
|
+
operation.requestInfo.requestType === "multiple_choice" &&
|
|
26
|
+
!operation.requestInfo.choices.includes(input)
|
|
27
|
+
) {
|
|
28
|
+
return {
|
|
29
|
+
status: 400,
|
|
30
|
+
body: {
|
|
31
|
+
error: "Invalid Request",
|
|
32
|
+
message: "input does not match any of the choices",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const result = await operation.generator.next(input);
|
|
38
|
+
|
|
39
|
+
authSessionsContextsStore.set(operationId, {
|
|
40
|
+
done: result.done,
|
|
41
|
+
generator: operation.generator,
|
|
42
|
+
context: operation.context,
|
|
43
|
+
requestInfo: result.value,
|
|
44
|
+
saveTrace: operation.saveTrace,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (result.done) {
|
|
48
|
+
if (operation.saveTrace) {
|
|
49
|
+
try {
|
|
50
|
+
const traceFilePath = getTraceFilePath(operationId);
|
|
51
|
+
await operation.context.tracing.stop({ path: traceFilePath });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error("Error stopping trace", error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const storageState = await getContextStorageState(operation.context);
|
|
57
|
+
await operation.context.browser()?.close();
|
|
58
|
+
authSessionsContextsStore.delete(operationId);
|
|
59
|
+
return {
|
|
60
|
+
status: 200,
|
|
61
|
+
body: storageState,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
status: 200,
|
|
67
|
+
body: result.value,
|
|
68
|
+
};
|
|
69
|
+
} catch (e) {
|
|
70
|
+
if (operation.saveTrace) {
|
|
71
|
+
try {
|
|
72
|
+
const traceFilePath = getTraceFilePath(operationId);
|
|
73
|
+
await operation.context.tracing.stop({ path: traceFilePath });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error("Error stopping trace", error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//@ts-ignore
|
|
2
|
+
import { RequestMoreInfoDetails } from "@intuned/sdk/dist/common/requestMoreInfo";
|
|
3
|
+
import { BrowserContext } from "@intuned/playwright-core";
|
|
4
|
+
|
|
5
|
+
export const authSessionsContextsStore = new Map<
|
|
6
|
+
string,
|
|
7
|
+
{
|
|
8
|
+
done: boolean | undefined;
|
|
9
|
+
generator: AsyncGenerator<any, void, unknown>;
|
|
10
|
+
context: BrowserContext;
|
|
11
|
+
requestInfo: RequestMoreInfoDetails;
|
|
12
|
+
saveTrace: boolean;
|
|
13
|
+
}
|
|
14
|
+
>();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { AsyncRunEndpointController } from "../utils";
|
|
2
|
+
import { Handler } from "@tinyhttp/app";
|
|
3
|
+
import { AsyncEndpointBody, runEndpointAsync } from "./async";
|
|
4
|
+
|
|
5
|
+
export type RequestHandler<P extends any[], R> = (...parameters: P) => Promise<{
|
|
6
|
+
status: number;
|
|
7
|
+
body: R;
|
|
8
|
+
}>;
|
|
9
|
+
|
|
10
|
+
export function makeSyncEndpointController<_Parameters extends any[], _Result>({
|
|
11
|
+
handler,
|
|
12
|
+
parameters,
|
|
13
|
+
}: {
|
|
14
|
+
handler: RequestHandler<_Parameters, any>;
|
|
15
|
+
parameters: _Parameters | ((req: any) => _Parameters);
|
|
16
|
+
}): Handler {
|
|
17
|
+
return async (req, res) => {
|
|
18
|
+
const { status, body } = await handler(
|
|
19
|
+
...(typeof parameters === "function" ? parameters(req) : parameters)
|
|
20
|
+
);
|
|
21
|
+
res.status(status).json(body);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function makeAsyncEndpointController<_Parameters extends any[]>({
|
|
26
|
+
requestId,
|
|
27
|
+
handler,
|
|
28
|
+
parameters,
|
|
29
|
+
}: {
|
|
30
|
+
requestId: string | ((req: any) => string);
|
|
31
|
+
handler: RequestHandler<_Parameters, any>;
|
|
32
|
+
parameters: _Parameters | ((req: any) => _Parameters);
|
|
33
|
+
}): Handler {
|
|
34
|
+
return async (req, res) => {
|
|
35
|
+
const { taskToken, startTime, functionsToken } =
|
|
36
|
+
req.body as AsyncEndpointBody<_Parameters>;
|
|
37
|
+
if (AsyncRunEndpointController.isRunning(taskToken)) {
|
|
38
|
+
res.status(409).json({ error: "Already running" });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
void runEndpointAsync(
|
|
43
|
+
handler,
|
|
44
|
+
typeof parameters === "function" ? parameters(req) : parameters,
|
|
45
|
+
{
|
|
46
|
+
taskToken,
|
|
47
|
+
startTime,
|
|
48
|
+
functionsToken,
|
|
49
|
+
requestId: typeof requestId === "function" ? requestId(req) : requestId,
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
res.status(202).json({});
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function makeEndpointControllers<_Parameters extends any[]>({
|
|
58
|
+
requestId,
|
|
59
|
+
handler,
|
|
60
|
+
parameters,
|
|
61
|
+
}: {
|
|
62
|
+
requestId: string | ((req: any) => string);
|
|
63
|
+
handler: RequestHandler<_Parameters, any>;
|
|
64
|
+
parameters: _Parameters | ((req: any) => _Parameters);
|
|
65
|
+
}): {
|
|
66
|
+
sync: Handler;
|
|
67
|
+
async: Handler;
|
|
68
|
+
} {
|
|
69
|
+
return {
|
|
70
|
+
sync: makeSyncEndpointController({ handler, parameters }),
|
|
71
|
+
async: makeAsyncEndpointController({ requestId, handler, parameters }),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import {
|
|
2
|
+
callFunction,
|
|
3
|
+
getTraceFilePath,
|
|
4
|
+
checkAuthSessionWithRetries,
|
|
5
|
+
waitWithExtendableTimeout,
|
|
6
|
+
} from "../../utils";
|
|
7
|
+
import { IntunedSettings } from "@intuned/runtime/dist/common/settingsSchema";
|
|
8
|
+
import { getDownloadDirectoryPath } from "@intuned/runtime";
|
|
9
|
+
import { getProductionPlaywrightConstructs } from "@intuned/runtime";
|
|
10
|
+
import * as fs from "fs-extra";
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
import { PayloadItem } from "../../../src/common/types";
|
|
13
|
+
import { getTelemetryClient } from "@intuned/runtime/dist/common/telemetry";
|
|
14
|
+
import { getExecutionContext } from "@intuned/runtime";
|
|
15
|
+
import { backendFunctionsTokenManager } from "@intuned/runtime/dist/common/jwtTokenManager";
|
|
16
|
+
import { setContextStorageState } from "@intuned/runtime/dist/common/contextStorageStateHelpers";
|
|
17
|
+
import { remove } from "fs-extra";
|
|
18
|
+
import { RunBody } from "./types";
|
|
19
|
+
|
|
20
|
+
export async function runApi({
|
|
21
|
+
session,
|
|
22
|
+
params,
|
|
23
|
+
proxy,
|
|
24
|
+
executionContext,
|
|
25
|
+
functionsToken,
|
|
26
|
+
runId,
|
|
27
|
+
attemptNumber,
|
|
28
|
+
functionName,
|
|
29
|
+
shouldSaveTrace,
|
|
30
|
+
headless = true,
|
|
31
|
+
}: RunBody & {
|
|
32
|
+
runId: string;
|
|
33
|
+
attemptNumber: string | undefined;
|
|
34
|
+
functionName: string;
|
|
35
|
+
shouldSaveTrace: boolean;
|
|
36
|
+
headless?: boolean;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
status: number;
|
|
39
|
+
body: any;
|
|
40
|
+
}> {
|
|
41
|
+
const telemetryClient = getTelemetryClient();
|
|
42
|
+
const integrationId = process.env.INTUNED_INTEGRATION_ID as string;
|
|
43
|
+
const traceFilePath = getTraceFilePath(runId, attemptNumber);
|
|
44
|
+
|
|
45
|
+
if (!runId) {
|
|
46
|
+
return {
|
|
47
|
+
status: 400,
|
|
48
|
+
body: {
|
|
49
|
+
error: "runId header not provided",
|
|
50
|
+
message: "Please add provide run id to header",
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let traceStarted = false;
|
|
56
|
+
|
|
57
|
+
backendFunctionsTokenManager.token = functionsToken;
|
|
58
|
+
const downloadsPath = getDownloadDirectoryPath();
|
|
59
|
+
const { page, context } = await getProductionPlaywrightConstructs({
|
|
60
|
+
headless,
|
|
61
|
+
proxy,
|
|
62
|
+
downloadsPath,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// handle timeout
|
|
66
|
+
const requestTimeout = +(process.env.REQUEST_TIMEOUT ?? 600) * 1000;
|
|
67
|
+
getExecutionContext()!.timeoutInfo = {
|
|
68
|
+
...(getExecutionContext()?.timeoutInfo ?? {}),
|
|
69
|
+
timeoutDuration: requestTimeout,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
async function saveTraceIfNeeded(errorMessage: string) {
|
|
73
|
+
if (!shouldSaveTrace || !traceStarted) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
await context.tracing.stop({ path: traceFilePath });
|
|
78
|
+
} catch (error: any) {
|
|
79
|
+
console.log(errorMessage, error?.message);
|
|
80
|
+
await remove(traceFilePath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function handleTimeout() {
|
|
85
|
+
console.log("timeout triggered");
|
|
86
|
+
await saveTraceIfNeeded("failed to save script after timeout");
|
|
87
|
+
|
|
88
|
+
await context.close();
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
status: 508,
|
|
92
|
+
body: {
|
|
93
|
+
error: "TimeoutError",
|
|
94
|
+
message: "The request timed out",
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (shouldSaveTrace) {
|
|
100
|
+
await context.tracing.start({
|
|
101
|
+
screenshots: true,
|
|
102
|
+
snapshots: true,
|
|
103
|
+
sources: true,
|
|
104
|
+
});
|
|
105
|
+
traceStarted = true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const isAuthSessionsEnabled = (
|
|
109
|
+
(await fs.readJSON("./Intuned.json", "utf-8")) as IntunedSettings
|
|
110
|
+
).authSessions?.enabled;
|
|
111
|
+
|
|
112
|
+
const abortController = new AbortController();
|
|
113
|
+
async function runFunction() {
|
|
114
|
+
try {
|
|
115
|
+
if (isAuthSessionsEnabled) {
|
|
116
|
+
if (!session) {
|
|
117
|
+
await context.close();
|
|
118
|
+
return {
|
|
119
|
+
status: 401,
|
|
120
|
+
message: "auth sessions is required",
|
|
121
|
+
error: "auth sessions is required",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await setContextStorageState(context, session);
|
|
126
|
+
const authSessionCheckResult = await checkAuthSessionWithRetries(
|
|
127
|
+
page,
|
|
128
|
+
context,
|
|
129
|
+
2
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (abortController.signal.aborted) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!authSessionCheckResult) {
|
|
137
|
+
try {
|
|
138
|
+
telemetryClient?.trackEvent({
|
|
139
|
+
name: "AUTH_SESSION_RECOVER_FAILED",
|
|
140
|
+
properties: {
|
|
141
|
+
runId,
|
|
142
|
+
functionName,
|
|
143
|
+
integrationId,
|
|
144
|
+
message: "auth session check failed",
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
await saveTraceIfNeeded("failed to save trace after check failed");
|
|
148
|
+
} catch (error: any) {
|
|
149
|
+
console.log(
|
|
150
|
+
"failed to save script after check failed",
|
|
151
|
+
error?.message
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
await context.close();
|
|
156
|
+
return {
|
|
157
|
+
status: 401,
|
|
158
|
+
message: "auth session check failed",
|
|
159
|
+
error: "AUTH_SESSION_CHECK_FAILED",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log("Running function", functionName);
|
|
165
|
+
const result = await callFunction("api", functionName as string, [
|
|
166
|
+
params ?? {},
|
|
167
|
+
page,
|
|
168
|
+
context,
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
await saveTraceIfNeeded("failed to save trace after run is done");
|
|
172
|
+
await context.close();
|
|
173
|
+
|
|
174
|
+
const response: {
|
|
175
|
+
result: any;
|
|
176
|
+
updatedSession?: object;
|
|
177
|
+
payloadToAppend: PayloadItem[] | null;
|
|
178
|
+
// TODO: remove
|
|
179
|
+
metadata?: {
|
|
180
|
+
startTime: number;
|
|
181
|
+
endTime: number;
|
|
182
|
+
};
|
|
183
|
+
status?: number;
|
|
184
|
+
} = {
|
|
185
|
+
result,
|
|
186
|
+
updatedSession: undefined,
|
|
187
|
+
payloadToAppend: getExecutionContext()!.extendedPayloads?.map((i) => ({
|
|
188
|
+
apiName: i.api,
|
|
189
|
+
parameters: i.parameters,
|
|
190
|
+
})),
|
|
191
|
+
status: 200,
|
|
192
|
+
};
|
|
193
|
+
return response;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.log("run errored", error);
|
|
196
|
+
await saveTraceIfNeeded("failed to save trace after run errored");
|
|
197
|
+
await context.close();
|
|
198
|
+
throw error;
|
|
199
|
+
} finally {
|
|
200
|
+
await fs.remove(downloadsPath);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const resultWithTimeout = await waitWithExtendableTimeout({
|
|
205
|
+
promise: runFunction(),
|
|
206
|
+
initialTimeout: requestTimeout,
|
|
207
|
+
abortController,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (resultWithTimeout.timedOut === true) {
|
|
211
|
+
return await handleTimeout();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const { result } = resultWithTimeout;
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
status: 200,
|
|
218
|
+
body: result,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Handler } from "@tinyhttp/app";
|
|
2
|
+
import { RUN_ID_HEADER, ATTEMPT_NUMBER_HEADER } from "../../headers";
|
|
3
|
+
import { runApi } from "./helpers";
|
|
4
|
+
import { AsyncRunEndpointController, isHeadless } from "../../utils";
|
|
5
|
+
import { makeEndpointControllers } from "../controllers";
|
|
6
|
+
export { runApi } from "./helpers";
|
|
7
|
+
|
|
8
|
+
export interface Proxy {
|
|
9
|
+
server: string;
|
|
10
|
+
username: string;
|
|
11
|
+
password: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RunBody {
|
|
15
|
+
session: any;
|
|
16
|
+
params?: object;
|
|
17
|
+
proxy?: Proxy;
|
|
18
|
+
executionContext: any;
|
|
19
|
+
functionsToken: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const { sync: runApiController, async: runApiAsyncController } =
|
|
23
|
+
makeEndpointControllers({
|
|
24
|
+
requestId: (req) =>
|
|
25
|
+
`runApi ${req.headers[RUN_ID_HEADER]} (${req.headers[ATTEMPT_NUMBER_HEADER]})`,
|
|
26
|
+
parameters: (req) => {
|
|
27
|
+
// e.g. /api/run/folder1/api should be folder1/api
|
|
28
|
+
const functionName = req.params.wild
|
|
29
|
+
.split("/")
|
|
30
|
+
// filter(Boolean) should remove empty strings in case of a trailing slash
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.join("/");
|
|
33
|
+
|
|
34
|
+
const { session, params, proxy, executionContext, functionsToken } =
|
|
35
|
+
req.body as RunBody;
|
|
36
|
+
const runId = req.headers[RUN_ID_HEADER] as string;
|
|
37
|
+
const attemptNumber = req.headers[ATTEMPT_NUMBER_HEADER] as
|
|
38
|
+
| string
|
|
39
|
+
| undefined;
|
|
40
|
+
const shouldSaveTrace = req.query.saveTrace === "true";
|
|
41
|
+
const headless = isHeadless();
|
|
42
|
+
const result: Parameters<typeof runApi> = [
|
|
43
|
+
{
|
|
44
|
+
session,
|
|
45
|
+
params,
|
|
46
|
+
proxy,
|
|
47
|
+
executionContext,
|
|
48
|
+
functionsToken,
|
|
49
|
+
runId,
|
|
50
|
+
attemptNumber,
|
|
51
|
+
functionName,
|
|
52
|
+
shouldSaveTrace,
|
|
53
|
+
headless,
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
return result;
|
|
57
|
+
},
|
|
58
|
+
handler: async (parameters: Parameters<typeof runApi>[0]) => {
|
|
59
|
+
const { status, body } = await runApi(parameters);
|
|
60
|
+
return { status, body };
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const getActiveAsyncEndpointController: Handler = async (req, res) => {
|
|
65
|
+
res
|
|
66
|
+
.status(200)
|
|
67
|
+
.json({ count: AsyncRunEndpointController.activeRequestsCount });
|
|
68
|
+
};
|