From fe8a1b0ade26a00f0d55ef9acb937dc1ea25ffe1 Mon Sep 17 00:00:00 2001 From: Anton Kolomiiets Date: Thu, 2 Mar 2023 09:32:04 +0200 Subject: [PATCH] Video recorder (#13) * update to stage * merge stage master * initial update add support to video recorder and save to aws S3 bucket * clean code * update test --- .gitignore | 3 +- Dockerfile | 3 +- client/src/components/Checkbox/index.tsx | 80 +++ .../containers/EditCodeContainer/index.tsx | 16 +- client/src/page/Home/index.tsx | 9 +- client/src/utils/api.ts | 10 +- controllers/creator.js | 6 + helper/index-config.js | 43 +- helper/roles/putRulePolicy.json | 6 + service/cloudFormation/sam-template-empty.yml | 5 + service/lambdaFunction/.npmrc | 1 + service/lambdaFunction/getHarFile.js | 69 +-- service/lambdaFunction/handlerHar.js | 29 +- service/lambdaFunction/index.js | 22 +- service/lambdaFunction/package-lock.json | 547 +++++++++++++++--- service/lambdaFunction/package.json | 8 +- service/lambdaFunction/rsData.js | 8 + service/lambdaFunction/uploadVideoToBucket.js | 58 ++ utils/AIM-role.js | 55 ++ utils/lambda-creator.js | 9 +- utils/lambdaFunctionLocal/index.js | 1 + utils/setup-cf-template.js | 22 + 22 files changed, 869 insertions(+), 141 deletions(-) create mode 100644 client/src/components/Checkbox/index.tsx create mode 100644 service/lambdaFunction/.npmrc create mode 100644 service/lambdaFunction/uploadVideoToBucket.js diff --git a/.gitignore b/.gitignore index 30602f5..5f2b73c 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ cloudFormation.zip example-test-2.js example-test.js client/build -index-config-bkp.js \ No newline at end of file +index-config-bkp.js +upload-test.js \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 9b6d9a7..459e5ac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,8 @@ COPY ./ ./ WORKDIR /usr/app/service/lambdaFunction -RUN npm install +RUN npm install --force +RUN npm install @ffmpeg-installer/linux-x64 --force WORKDIR /usr/app/client diff --git a/client/src/components/Checkbox/index.tsx b/client/src/components/Checkbox/index.tsx new file mode 100644 index 0000000..441402f --- /dev/null +++ b/client/src/components/Checkbox/index.tsx @@ -0,0 +1,80 @@ +import React, { FunctionComponent } from 'react'; +import Text from '../Text'; + +import styled from 'styled-components'; + +interface IProps { + title: string; + description: string; + onClick: () => void; + statusCheckbox: boolean; +} +const IconCheck = styled.svg` + display: none; +`; +const CheckboxContainer = styled.div` + display: flex; + margin-bottom: 15px; +`; +const CheckboxTextWrapper = styled.div` + margin-left: 10px; + h2 { + margin-bottom: 5px; + } +`; +const CheckboxWrapper = styled.div` + margin-top: 10px; + cursor: pointer; + display: flex; + -webkit-box-align: center; + align-items: center; + -webkit-box-pack: center; + justify-content: center; + width: 16px; + height: 16px; + border: 1px solid rgb(231, 231, 231); + border-radius: 2px; + transition: all 150ms ease 0s; + background-color: white; + outline: none; + align-self: flex-start; + &.active { + display: flex; + background: #5b78a4; + } + &.active svg { + display: flex; + } +`; + +const Checkbox: FunctionComponent = ({ + title, + description, + statusCheckbox, + onClick, +}) => { + return ( + + + + + + + + {title} + {description} + + + ); +}; + +export default Checkbox; diff --git a/client/src/containers/EditCodeContainer/index.tsx b/client/src/containers/EditCodeContainer/index.tsx index d877acf..5110fef 100644 --- a/client/src/containers/EditCodeContainer/index.tsx +++ b/client/src/containers/EditCodeContainer/index.tsx @@ -5,7 +5,7 @@ import CodeEditor from '../../components/CodeEditor'; import EnvVariableContainer from '../EnvVariableContainer'; import Text from '../../components/Text'; import Select from '../../components/Select'; - +import Checkbox from '../../components/Checkbox'; import { availableCodeLanguages, platformDevice, @@ -63,6 +63,7 @@ const MainWrapper = styled.div` justify-content: space-between; height: 75%; `; +const RecordVideoWrapper = styled.div``; const BottomWrapper = styled.div` display: flex; @@ -97,18 +98,22 @@ interface IProps { uniqueKey: (isUnique: boolean) => void; codeSnippet: string; testDevice: string; + onChangeRecordStatus: boolean; setCodeSnippet: (val: string) => void; setEnvVariable: (envVariable: EnvVariable[]) => void; setTestDevice: (val: string) => void; + onChangeRecord: () => void; } const EditCodeContainer: FunctionComponent = ({ uniqueKey, codeSnippet, testDevice, + onChangeRecordStatus, setCodeSnippet, setEnvVariable, setTestDevice, + onChangeRecord, }) => { const [codeLanguage, setCodeLanguage] = useState('Playwright'); const [loading, setLoading] = useState(false); @@ -175,6 +180,14 @@ const EditCodeContainer: FunctionComponent = ({ codeSnippet={codeSnippet} /> + + + Select Device Select the device on which you would like to execute @@ -187,6 +200,7 @@ const EditCodeContainer: FunctionComponent = ({ currentValue={testDevice} /> + { }); const [codeSnippet, setCodeSnippet] = useState(DEFAULT_CODE); const [testDevice, setTestDevice] = useState('Desktop Chrome'); - + const [onChangeRecordStatus, onChangeRecord] = useState(false); const [activeStep, setActiveStep] = useState('edit_code'); const [stageDeploy, setStageDeploy] = useState({ message: 'Function creating...', @@ -132,6 +132,9 @@ const Home: FunctionComponent = () => { const onDownload = (step: boolean) => { setIsDownload(step); }; + const onChangeRecordHandler = () => { + onChangeRecord(!onChangeRecordStatus); + }; const [activeCloudProvider, setActiveCloudProvider] = useState('AWS'); @@ -158,6 +161,7 @@ const Home: FunctionComponent = () => { response = await api.downloadCFTemplate( codeSnippet, + onChangeRecordStatus, testDevice, envList, configs.name.value, @@ -198,6 +202,7 @@ const Home: FunctionComponent = () => { rangeTimeVariable[ activeRangeTime.split(' ').reverse().join('_') ], + onChangeRecordStatus, testDevice, codeSnippet, configs.name.value, @@ -291,6 +296,8 @@ const Home: FunctionComponent = () => { uniqueKey={uniqueEnvVariableHandler} testDevice={testDevice} codeSnippet={codeSnippet} + onChangeRecordStatus={onChangeRecordStatus} + onChangeRecord={onChangeRecordHandler} setCodeSnippet={onSetCodeSnippetHandler} setEnvVariable={onSetEnvVariableHandler} setTestDevice={setTestDevice} diff --git a/client/src/utils/api.ts b/client/src/utils/api.ts index bd7e111..719fd64 100644 --- a/client/src/utils/api.ts +++ b/client/src/utils/api.ts @@ -84,7 +84,7 @@ class Api { } }; - initPage = async (rangeTime: number, testDevice: string, codeSnippet: string, name: string, accessKey: string, secretKey: string, bucketName: string, token: string, listenerUrl: string, region: string, listEnvVariables: object, onStage: (stage: StatusProps) => void, description?: string) => { + initPage = async (rangeTime: number, onChangeRecordStatus: boolean, testDevice: string, codeSnippet: string, name: string, accessKey: string, secretKey: string, bucketName: string, token: string, listenerUrl: string, region: string, listEnvVariables: object, onStage: (stage: StatusProps) => void, description?: string) => { const responseModify = await this.customFetch( { code: codeSnippet, testDevice }, @@ -174,7 +174,7 @@ class Api { region, listEnvVariables, listenerUrl, - + onChangeRecordStatus }, settings.endPointUrls.createLambdaUrl, ); @@ -226,7 +226,7 @@ class Api { return cloudBridgeEventResp; } }; - downloadCFTemplate = async (codeSnippet: string, testDevice: string, envList: object, name: string, rangeTime: number, bucket: string, token: string, region: string, listener: string, onDownload: (step: boolean) => void, description?: string) => { + downloadCFTemplate = async (codeSnippet: string, onChangeRecordStatus: boolean, testDevice: string, envList: object, name: string, rangeTime: number, bucket: string, token: string, region: string, listener: string, onDownload: (step: boolean) => void, description?: string) => { onDownload(true) const responseModify = await this.customFetch( @@ -248,7 +248,9 @@ class Api { bucket, listener, region, - rangeTime + rangeTime, + onChangeRecordStatus, + testDevice }, settings.endPointUrls.createCfZip, ); diff --git a/controllers/creator.js b/controllers/creator.js index 9bfcf7f..9595727 100644 --- a/controllers/creator.js +++ b/controllers/creator.js @@ -90,6 +90,7 @@ exports.createLambda = async (req, res) => { region, listEnvVariables, listenerUrl, + onChangeRecordStatus, } = req.body; try { const lambdaResp = await createLambda( @@ -103,6 +104,7 @@ exports.createLambda = async (req, res) => { region, listEnvVariables, listenerUrl, + onChangeRecordStatus, ); if (lambdaResp.error) { throw Error(lambdaResp.err); @@ -180,6 +182,8 @@ exports.createZipCF = async (req, res) => { listener, region, rangeTime, + onChangeRecordStatus, + testDevice, } = req.body; try { @@ -192,6 +196,8 @@ exports.createZipCF = async (req, res) => { listener, region, rangeTime, + onChangeRecordStatus, + testDevice, ); await fileToZipCF(name); diff --git a/helper/index-config.js b/helper/index-config.js index 25bfc8b..53f6242 100644 --- a/helper/index-config.js +++ b/helper/index-config.js @@ -4,6 +4,7 @@ module.exports = { const path = require('path'); const readSendData = require('./rsData'); const cfnResponse = require('cfn-response-async'); + const { saveVideo } = require('playwright-video'); const pageHandler = require('./handlerHar'); const firstRun = async (event, context) => { @@ -24,10 +25,12 @@ module.exports = { let context = null; let err = null; let page = null; - let browser = null; + let capture; + let browser; + const visitedUrls= []; try { - browser = await playwright.launchChromium(false); + browser = await playwright.launchChromium(); context = await browser.newContext({ ...mobileDevice @@ -35,13 +38,18 @@ module.exports = { await context.tracing.start({ screenshots: false, snapshots: false }); page = await context.newPage(); - let count = 0; page.on('load', async (data) => { - count++; - await pageHandler(data, count); + visitedUrls.push(data.url()); + }); + if(process.env.IS_RECORD === 'to_record'){ + capture = await saveVideo(page, path.join(__dirname,'..', '..', 'tmp', 'video.mp4')); + } `, - startFile: `const playwright = require('playwright-aws-lambda'); + startFile: ` + const { saveVideo } = require('playwright-video'); + + const playwright = require('playwright-aws-lambda'); const path = require('path'); const readSendData = require('./rsData'); const cfnResponse = require('cfn-response-async'); @@ -63,32 +71,43 @@ module.exports = { let context = null; let err = null; let page = null; + let capture; let browser; + const visitedUrls= []; + try { - browser = await playwright.launchChromium(false); + browser = await playwright.launchChromium(); + context = await browser.newContext({ }); await context.tracing.start({ screenshots: false, snapshots: false }); page = await context.newPage(); - let count = 0; page.on('load', async (data) => { - count++; - await pageHandler(data, count); + visitedUrls.push(data.url()); }); + if(process.env.IS_RECORD === 'to_record'){ + capture = await saveVideo(page, path.join(__dirname,'..', '..', 'tmp', 'video.mp4')); + } `, endFile: ` } catch (error) { + console.log(error); err = error.message; } finally { if (browser) { await context.tracing.stop({ path: path.join(__dirname, '..', '..', 'tmp', 'trace.zip'), }); + if(process.env.IS_RECORD === 'to_record'){ + await capture.stop(); + } await context.close(); await browser.close(); + await pageHandler(visitedUrls); + } } await readSendData(err); @@ -123,6 +142,7 @@ const handlerLocally = async () => { `, startFileLocally: `const playwright = require('playwright-aws-lambda'); const { chromium } = require('playwright-core'); + const path = require('path'); const errorStatusHandler = require('./statusError'); @@ -131,7 +151,8 @@ const handlerLocally = async () => { let err = null; let page = null; let browser = null; - try { + try { + browser = await chromium.launch({}); context = await browser.newContext(); page = await context.newPage(); diff --git a/helper/roles/putRulePolicy.json b/helper/roles/putRulePolicy.json index 41b04a2..fe1fb64 100644 --- a/helper/roles/putRulePolicy.json +++ b/helper/roles/putRulePolicy.json @@ -12,6 +12,12 @@ "ec2:CreateSnapshot" ], "Resource": "*" + }, + { + "Sid": "S3BucketGetPut", + "Effect": "Allow", + "Action": ["s3:GetObject", "s3:PutObject"], + "Resource": "*" } ] } diff --git a/service/cloudFormation/sam-template-empty.yml b/service/cloudFormation/sam-template-empty.yml index ccc422d..b9fdb12 100644 --- a/service/cloudFormation/sam-template-empty.yml +++ b/service/cloudFormation/sam-template-empty.yml @@ -33,6 +33,10 @@ Resources: LISTENER_URL: string TOKEN: string NAME_FUNCTION: string + REGION: string + BUCKET_NAME: string + TEST_DEVICE: string + IS_RECORD: string syntheticQueryS3Bucket: Type: 'AWS::IAM::Role' Properties: @@ -55,6 +59,7 @@ Resources: - Effect: Allow Action: - 's3:GetObject' + - 's3:PutObject' - 'lambda:GetFunction' Resource: '*' logzioSyntheticMonitorPrimerInvoke: diff --git a/service/lambdaFunction/.npmrc b/service/lambdaFunction/.npmrc new file mode 100644 index 0000000..4d936e8 --- /dev/null +++ b/service/lambdaFunction/.npmrc @@ -0,0 +1 @@ +unsafe-perm=true diff --git a/service/lambdaFunction/getHarFile.js b/service/lambdaFunction/getHarFile.js index 8b70ce2..cac92ad 100644 --- a/service/lambdaFunction/getHarFile.js +++ b/service/lambdaFunction/getHarFile.js @@ -3,43 +3,46 @@ const { harFromMessages } = require('chrome-har'); module.exports = { getHarFile: async function (pageUrl) { - // list of events for converting to HAR + let browser; const events = []; - // event types to observe - const observe = [ - 'Page.loadEventFired', - 'Page.domContentEventFired', - 'Page.frameStartedLoading', - 'Page.frameAttached', - 'Network.requestWillBeSent', - 'Network.requestServedFromCache', - 'Network.dataReceived', - 'Network.responseReceived', - 'Network.resourceChangedPriority', - 'Network.loadingFinished', - 'Network.loadingFailed', - ]; - // Create a URL object for use later - const url = new URL(pageUrl); + try { + // list of events for converting to HAR - const browser = await playwright.launchChromium(false); - const page = await browser.newPage(); + // event types to observe + const observe = [ + 'Page.loadEventFired', + 'Page.domContentEventFired', + 'Page.frameStartedLoading', + 'Page.frameAttached', + 'Network.requestWillBeSent', + 'Network.requestServedFromCache', + 'Network.dataReceived', + 'Network.responseReceived', + 'Network.resourceChangedPriority', + 'Network.loadingFinished', + 'Network.loadingFailed', + ]; + // Create a URL object for use later + // const url = new URL(pageUrl); + browser = await playwright.launchChromium({ headless: true }); + const pageHar = await browser.newPage(); - // register events listeners - const client = await page.context().newCDPSession(page); - await client.send('Page.enable'); - await client.send('Network.enable'); - observe.forEach((method) => { - client.on(method, (params) => { - events.push({ method, params }); + // register events listeners + const client = await pageHar.context().newCDPSession(pageHar); + await client.send('Page.enable'); + await client.send('Network.enable'); + observe.forEach((method) => { + client.on(method, (params) => { + events.push({ method, params }); + }); }); - }); - - await page.goto(url.href); - await browser.close(); - - // convert events to HAR file - return harFromMessages(events); + await pageHar.goto(pageUrl); + } catch (err) { + console.log(err); + } finally { + await browser.close(); + return harFromMessages(events); + } }, }; diff --git a/service/lambdaFunction/handlerHar.js b/service/lambdaFunction/handlerHar.js index cfee765..3b08309 100644 --- a/service/lambdaFunction/handlerHar.js +++ b/service/lambdaFunction/handlerHar.js @@ -2,14 +2,23 @@ const { getHarFile } = require('./getHarFile'); const { writeFileSync } = require('fs'); const path = require('path'); -module.exports = pageHandler = async (page, count) => { - const result = await getHarFile(page.url()); - if (count) { - writeFileSync( - path.join(__dirname, '..', '..', 'tmp', `${count}.har`), - JSON.stringify(result), - ); - } else { - return harObject; - } +const pageHandler = async (visitedUrls, count) => { + return new Promise(async (resolve, reject) => { + try { + let count = 0; + for (let i = 0; i < visitedUrls.length; i++) { + count++; + const result = await getHarFile(visitedUrls[i]); + + writeFileSync( + path.join(__dirname, '..', '..', 'tmp', `${count}.har`), + JSON.stringify(result), + ); + } + resolve(true); + } catch (err) { + reject(err); + } + }); }; +module.exports = pageHandler; diff --git a/service/lambdaFunction/index.js b/service/lambdaFunction/index.js index 339b33f..b50435a 100644 --- a/service/lambdaFunction/index.js +++ b/service/lambdaFunction/index.js @@ -1,9 +1,10 @@ +const { saveVideo } = require('playwright-video'); + const playwright = require('playwright-aws-lambda'); const path = require('path'); const readSendData = require('./rsData'); const cfnResponse = require('cfn-response-async'); const pageHandler = require('./handlerHar'); - const firstRun = async (event, context) => { await regularRun(); @@ -20,27 +21,38 @@ const regularRun = async () => { let context = null; let err = null; let page = null; + let capture; let browser; + const visitedUrls = []; + try { - browser = await playwright.launchChromium(false); + browser = await playwright.launchChromium(); + const size = { width: 1280, height: 600 }; + context = await browser.newContext({}); await context.tracing.start({ screenshots: false, snapshots: false }); page = await context.newPage(); - let count = 0; page.on('load', async (data) => { - count++; - await pageHandler(data, count); + visitedUrls.push(data.url()); + // await pageHandler(data, count); }); + capture = await saveVideo( + page, + path.join(__dirname, '..', '..', 'tmp', 'video.mp4'), + ); } catch (error) { + console.log(error); err = error.message; } finally { if (browser) { await context.tracing.stop({ path: path.join(__dirname, '..', '..', 'tmp', 'trace.zip'), }); + await capture.stop(); await context.close(); await browser.close(); + await pageHandler(visitedUrls); } } await readSendData(err); diff --git a/service/lambdaFunction/package-lock.json b/service/lambdaFunction/package-lock.json index 4c9a887..b997ec6 100644 --- a/service/lambdaFunction/package-lock.json +++ b/service/lambdaFunction/package-lock.json @@ -9,32 +9,165 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@ffmpeg-installer/ffmpeg": "^1.1.0", + "@ffmpeg-installer/linux-x64": "^4.1.0", "@menadevs/objectron": "^0.1.14", "cfn-response-async": "^1.0.0", "crypto": "^1.0.1", "dotenv": "^16.0.1", + "fluent-ffmpeg": "^2.1.2", "jszip": "^3.10.1", "logzio-nodejs": "^2.1.4", "moment": "^2.29.4", "node-uuid": "^1.4.8", "playwright-aws-lambda": "^0.9.0", "playwright-core": "^1.30.0", - "playwright-har": "^0.1.1" + "playwright-har": "^0.1.1", + "playwright-video": "^2.4.0" }, "devDependencies": { - "playwright": "^1.30.0" + "playwright": "^1.31.1" } }, + "node_modules/@ffmpeg-installer/darwin-arm64": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-arm64/-/darwin-arm64-4.1.5.tgz", + "integrity": "sha512-hYqTiP63mXz7wSQfuqfFwfLOfwwFChUedeCVKkBtl/cliaTM7/ePI9bVzfZ2c+dWu3TqCwLDRWNSJ5pqZl8otA==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@ffmpeg-installer/darwin-x64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-x64/-/darwin-x64-4.1.0.tgz", + "integrity": "sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw==", + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@ffmpeg-installer/ffmpeg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/ffmpeg/-/ffmpeg-1.1.0.tgz", + "integrity": "sha512-Uq4rmwkdGxIa9A6Bd/VqqYbT7zqh1GrT5/rFwCwKM70b42W5gIjWeVETq6SdcL0zXqDtY081Ws/iJWhr1+xvQg==", + "optionalDependencies": { + "@ffmpeg-installer/darwin-arm64": "4.1.5", + "@ffmpeg-installer/darwin-x64": "4.1.0", + "@ffmpeg-installer/linux-arm": "4.1.3", + "@ffmpeg-installer/linux-arm64": "4.1.4", + "@ffmpeg-installer/linux-ia32": "4.1.0", + "@ffmpeg-installer/linux-x64": "4.1.0", + "@ffmpeg-installer/win32-ia32": "4.1.0", + "@ffmpeg-installer/win32-x64": "4.1.0" + } + }, + "node_modules/@ffmpeg-installer/linux-arm": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm/-/linux-arm-4.1.3.tgz", + "integrity": "sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg==", + "cpu": [ + "arm" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/linux-arm64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm64/-/linux-arm64-4.1.4.tgz", + "integrity": "sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/linux-ia32": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-ia32/-/linux-ia32-4.1.0.tgz", + "integrity": "sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ==", + "cpu": [ + "ia32" + ], + "hasInstallScript": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/linux-x64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-x64/-/linux-x64-4.1.0.tgz", + "integrity": "sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A==", + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/win32-ia32": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-ia32/-/win32-ia32-4.1.0.tgz", + "integrity": "sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@ffmpeg-installer/win32-x64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-x64/-/win32-x64-4.1.0.tgz", + "integrity": "sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@menadevs/objectron": { "version": "0.1.14", "resolved": "https://registry.npmjs.org/@menadevs/objectron/-/objectron-0.1.14.tgz", "integrity": "sha512-zgi0DJtaxNjiWZjWxOTidwiRrOL0ZTniNekmllPzfR62WaIj446yBdheUnYMs3cH4Ow0om+cOY5cxXb7WbZe7Q==" }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/axios": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", @@ -60,6 +193,15 @@ "uuid": "8.0.0" } }, + "node_modules/chrome-har/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -88,12 +230,19 @@ "integrity": "sha512-mPh1mslned+5PuIuiUfbw4CikHk6AEAf2Baxih+wP5fssv+wmlVhvgZ7mq+BhLt7Sr/Hc8leWDiwe6YnrpNt3g==" }, "node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "ms": "^2.1.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/delayed-stream": { @@ -112,6 +261,18 @@ "node": ">=12" } }, + "node_modules/fluent-ffmpeg": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz", + "integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==", + "dependencies": { + "async": ">=0.2.9", + "which": "^1.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -144,6 +305,33 @@ "node": ">= 6" } }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -159,11 +347,35 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -175,6 +387,33 @@ "setimmediate": "^1.0.5" } }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/lambdafs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/lambdafs/-/lambdafs-2.1.1.tgz", @@ -438,9 +677,9 @@ } }, "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/node-uuid": { "version": "1.4.8", @@ -521,6 +760,21 @@ "playwright-chromium": "^1.8.0" } }, + "node_modules/playwright-video": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/playwright-video/-/playwright-video-2.4.0.tgz", + "integrity": "sha512-NA4ND29uaMEy78wXepH0dS2UnFd/GxSoScyoViql8mv5jpkEPHJPqxBKTO8l3rYbbDfFqrP/vQUPmsCB1UNXng==", + "dependencies": { + "debug": "*", + "fluent-ffmpeg": "^2.1.2", + "fs-extra": "^9.0.1", + "playwright-core": "^1.4.1", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=10.15.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -539,38 +793,11 @@ "node": ">=6" } }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/tough-cookie": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", @@ -584,6 +811,11 @@ "node": ">=6" } }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -604,19 +836,102 @@ "bin": { "uuid": "dist/bin/uuid" } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } } }, "dependencies": { + "@ffmpeg-installer/darwin-arm64": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-arm64/-/darwin-arm64-4.1.5.tgz", + "integrity": "sha512-hYqTiP63mXz7wSQfuqfFwfLOfwwFChUedeCVKkBtl/cliaTM7/ePI9bVzfZ2c+dWu3TqCwLDRWNSJ5pqZl8otA==", + "optional": true + }, + "@ffmpeg-installer/darwin-x64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-x64/-/darwin-x64-4.1.0.tgz", + "integrity": "sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw==", + "optional": true + }, + "@ffmpeg-installer/ffmpeg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/ffmpeg/-/ffmpeg-1.1.0.tgz", + "integrity": "sha512-Uq4rmwkdGxIa9A6Bd/VqqYbT7zqh1GrT5/rFwCwKM70b42W5gIjWeVETq6SdcL0zXqDtY081Ws/iJWhr1+xvQg==", + "requires": { + "@ffmpeg-installer/darwin-arm64": "4.1.5", + "@ffmpeg-installer/darwin-x64": "4.1.0", + "@ffmpeg-installer/linux-arm": "4.1.3", + "@ffmpeg-installer/linux-arm64": "4.1.4", + "@ffmpeg-installer/linux-ia32": "4.1.0", + "@ffmpeg-installer/linux-x64": "4.1.0", + "@ffmpeg-installer/win32-ia32": "4.1.0", + "@ffmpeg-installer/win32-x64": "4.1.0" + } + }, + "@ffmpeg-installer/linux-arm": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm/-/linux-arm-4.1.3.tgz", + "integrity": "sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg==", + "optional": true + }, + "@ffmpeg-installer/linux-arm64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm64/-/linux-arm64-4.1.4.tgz", + "integrity": "sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg==", + "optional": true + }, + "@ffmpeg-installer/linux-ia32": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-ia32/-/linux-ia32-4.1.0.tgz", + "integrity": "sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ==", + "optional": true + }, + "@ffmpeg-installer/linux-x64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-x64/-/linux-x64-4.1.0.tgz", + "integrity": "sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A==" + }, + "@ffmpeg-installer/win32-ia32": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-ia32/-/win32-ia32-4.1.0.tgz", + "integrity": "sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw==", + "optional": true + }, + "@ffmpeg-installer/win32-x64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-x64/-/win32-x64-4.1.0.tgz", + "integrity": "sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg==", + "optional": true + }, "@menadevs/objectron": { "version": "0.1.14", "resolved": "https://registry.npmjs.org/@menadevs/objectron/-/objectron-0.1.14.tgz", "integrity": "sha512-zgi0DJtaxNjiWZjWxOTidwiRrOL0ZTniNekmllPzfR62WaIj446yBdheUnYMs3cH4Ow0om+cOY5cxXb7WbZe7Q==" }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + }, "axios": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", @@ -640,6 +955,16 @@ "debug": "4.1.1", "tough-cookie": "4.0.0", "uuid": "8.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } } }, "combined-stream": { @@ -666,11 +991,11 @@ "integrity": "sha512-mPh1mslned+5PuIuiUfbw4CikHk6AEAf2Baxih+wP5fssv+wmlVhvgZ7mq+BhLt7Sr/Hc8leWDiwe6YnrpNt3g==" }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "delayed-stream": { @@ -683,6 +1008,15 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" }, + "fluent-ffmpeg": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz", + "integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==", + "requires": { + "async": ">=0.2.9", + "which": "^1.1.1" + } + }, "follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -698,6 +1032,29 @@ "mime-types": "^2.1.12" } }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + } + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -713,11 +1070,32 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + } + } + }, "jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -727,6 +1105,35 @@ "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "lambdafs": { @@ -909,9 +1316,9 @@ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node-uuid": { "version": "1.4.8", @@ -962,6 +1369,18 @@ "playwright-chromium": "^1.8.0" } }, + "playwright-video": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/playwright-video/-/playwright-video-2.4.0.tgz", + "integrity": "sha512-NA4ND29uaMEy78wXepH0dS2UnFd/GxSoScyoViql8mv5jpkEPHJPqxBKTO8l3rYbbDfFqrP/vQUPmsCB1UNXng==", + "requires": { + "debug": "*", + "fluent-ffmpeg": "^2.1.2", + "fs-extra": "^9.0.1", + "playwright-core": "^1.4.1", + "tslib": "^2.0.1" + } + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -977,38 +1396,11 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, "tough-cookie": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", @@ -1019,6 +1411,11 @@ "universalify": "^0.1.2" } }, + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -1033,6 +1430,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } } } } diff --git a/service/lambdaFunction/package.json b/service/lambdaFunction/package.json index 5d34ae6..43b21c5 100644 --- a/service/lambdaFunction/package.json +++ b/service/lambdaFunction/package.json @@ -17,19 +17,23 @@ }, "homepage": "https://github.com/resdenia/syntetic-scripting#readme", "dependencies": { + "@ffmpeg-installer/ffmpeg": "^1.1.0", + "@ffmpeg-installer/linux-x64": "^4.1.0", "@menadevs/objectron": "^0.1.14", "cfn-response-async": "^1.0.0", "crypto": "^1.0.1", "dotenv": "^16.0.1", + "fluent-ffmpeg": "^2.1.2", "jszip": "^3.10.1", "logzio-nodejs": "^2.1.4", "moment": "^2.29.4", "node-uuid": "^1.4.8", "playwright-aws-lambda": "^0.9.0", "playwright-core": "^1.30.0", - "playwright-har": "^0.1.1" + "playwright-har": "^0.1.1", + "playwright-video": "^2.4.0" }, "devDependencies": { - "playwright": "^1.30.0" + "playwright": "^1.31.1" } } diff --git a/service/lambdaFunction/rsData.js b/service/lambdaFunction/rsData.js index dacefde..4dba8c9 100644 --- a/service/lambdaFunction/rsData.js +++ b/service/lambdaFunction/rsData.js @@ -5,7 +5,9 @@ const readSendTraceData = require('./rsTraceData'); const loggerGenerator = require('./logger'); const { regionData } = require('./geolocation'); const errorStatusHandler = require('./statusError'); +const uploadVideoToBucket = require('./uploadVideoToBucket'); const createSessionId = require('./sessionId'); + const createResponseStatusClass = require('./responseStatusClass'); function sleep(ms) { @@ -19,6 +21,11 @@ const readSendData = async (error = '') => { const harsInDir = fs.readdirSync('/tmp'); try { + // Upload video to the bucket + if (process.env.IS_RECORD === 'to_record') { + await uploadVideoToBucket(sessionId); + } + harsInDir.forEach((file) => { if (file.split('.').length > 1 && file.split('.')[1] === 'har') { const fileData = fs.readFileSync(`/tmp/${file}`); @@ -67,6 +74,7 @@ const readSendData = async (error = '') => { statusResult: error ? 0 : 1, sessionId, nameTest: process.env.NAME_FUNCTION, + videoUrl: `https://${process.env.BUCKET_NAME}.s3.amazonaws/${process.env.NAME_FUNCTION}/${sessionId}.mp4`, }); await sleep(4000); logger.sendAndClose(); diff --git a/service/lambdaFunction/uploadVideoToBucket.js b/service/lambdaFunction/uploadVideoToBucket.js new file mode 100644 index 0000000..2bd0a7e --- /dev/null +++ b/service/lambdaFunction/uploadVideoToBucket.js @@ -0,0 +1,58 @@ +const AWS = require('aws-sdk'); +const fs = require('fs'); +const path = require('path'); + +const renameVideoFile = async (sessionId) => { + try { + let files = fs.readdirSync(path.join(__dirname, '..', '..', 'tmp')); + const videoExtenstion = '.mp4'; + + files.forEach((file) => { + const extensionOfFile = file.substr(file.length - 4); + if (videoExtenstion === extensionOfFile) { + fs.renameSync( + path.join(__dirname, '..', '..', 'tmp', file), + path.join(__dirname, '..', '..', 'tmp', `${sessionId}.mp4`), + ); + } + }); + } catch (err) { + console.log(err); + } +}; + +const uploadVideoToBucket = async (sessionId) => { + try { + await renameVideoFile(sessionId); + + const fileName = `${sessionId}.mp4`; + const fileRoute = path.join(__dirname, '..', '..', 'tmp', fileName); + const file = fs.createReadStream(fileRoute); + + if (file) { + const s3bucket = new AWS.S3(); + const params = { + Bucket: process.env.BUCKET_NAME, + Key: `${process.env.NAME_FUNCTION}/${fileName}`, + Body: file, + ContentType: 'video/mp4', + }; + + return new Promise((resolve, reject) => { + s3bucket.upload(params, function (err, res) { + if (err) { + reject({ error: true, err }); + } else { + resolve({ error: false, message: 'done uploaded' }); + } + }); + }); + } else { + throw Error('Failed to upload data'); + } + } catch (err) { + console.log(err); + } +}; + +module.exports = uploadVideoToBucket; diff --git a/utils/AIM-role.js b/utils/AIM-role.js index 5eb3cb6..5485eb2 100644 --- a/utils/AIM-role.js +++ b/utils/AIM-role.js @@ -17,6 +17,20 @@ const isRoleExists = async (iam, roleName) => { return result; }; +const isPolicyExists = async (iam, policyName) => { + const paramsPolicy = { + PolicyArn: policyName, + }; + const result = new Promise((resolve, reject) => { + iam.getPolicy(paramsPolicy, function (err, data) { + if (err) reject({ error: true, err }); // an error occurred + else resolve(data); // successful response + }); + }); + + return result; +}; + const createRole = async (iam, roleName, pathToRole) => { const policy = JSON.parse( fs.readFileSync( @@ -65,12 +79,14 @@ exports.AIMRole = async ( roleName, pathToRole, policyArn, + accountId, ) => { AWS.config.update({ accessKeyId: accessKey, secretAccessKey: secretKey, }); const iam = new AWS.IAM(); + try { const getIfRoleExists = await isRoleExists(iam, roleName); if (getIfRoleExists.Role) { @@ -90,7 +106,22 @@ exports.AIMRole = async ( } const setPolicyStatus = await setPolicy(iam, roleName, policyArn); + const ifGetPutPolicyExists = await isPolicyExists( + iam, + `arn:aws:iam::${accountId}:policy/S3BucketGetPut`, + ); + if (!ifGetPutPolicyExists.Policy) { + const lambdaPolicyGetPut = await createCustomPolicy(iam); + const setPolicyLambdaStatus = await setPolicy( + iam, + roleName, + lambdaPolicyGetPut.Policy.Arn, + ); + if (setPolicyLambdaStatus.err) { + throw setPolicyLambdaStatus.err; + } + } if (setPolicyStatus.err) { throw setPolicyStatus.err; } @@ -101,3 +132,27 @@ exports.AIMRole = async ( return err; } }; + +const createCustomPolicy = async (iam) => { + const customPolicy = { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: ['s3:GetObject', 's3:PutObject'], + Resource: '*', + }, + ], + }; + const params = { + PolicyDocument: JSON.stringify(customPolicy), + PolicyName: 'S3BucketGetPut', + }; + // Create an IAM service client object. + return new Promise((resolve, reject) => { + iam.createPolicy(params, function (err, data) { + if (err) reject(err); // an error occurred + else resolve(data); // successful response + }); + }); +}; diff --git a/utils/lambda-creator.js b/utils/lambda-creator.js index af1ae47..192016a 100644 --- a/utils/lambda-creator.js +++ b/utils/lambda-creator.js @@ -20,6 +20,7 @@ const { logger } = require('./logger'); * @param {string} region - AWS region where need to create synthetic logic * @param {object} listEnvVariables - list of Environment variable for Lambda Function * @param {string} listenerUrl - Logzio listener url to send metrics/logs + * @param {boolean} onChangeRecordStatus - Flag for record a browser based video of test */ exports.createLambda = async ( @@ -33,6 +34,7 @@ exports.createLambda = async ( region, listEnvVariables, listenerUrl, + onChangeRecordStatus, ) => { const accountId = await getAccountId(accessKey, secretKey); @@ -56,6 +58,7 @@ exports.createLambda = async ( BASIC_EXECUTION_ROLE_NAME, pathToRole, policyArn, + accountId, ); if (iamRole.err) { @@ -72,7 +75,7 @@ exports.createLambda = async ( Runtime: 'nodejs16.x', Description: description, MemorySize: 512, - Timeout: 80, + Timeout: 300, Environment: { Variables: { ...variables, @@ -81,6 +84,10 @@ exports.createLambda = async ( REGION: region, TEST_DEVICE: testDevice, NAME_FUNCTION: functionName, + BUCKET_NAME: bucketName, + IS_RECORD: onChangeRecordStatus + ? 'to_record' + : 'not_to_record', }, }, }; diff --git a/utils/lambdaFunctionLocal/index.js b/utils/lambdaFunctionLocal/index.js index c9c36d6..290b984 100644 --- a/utils/lambdaFunctionLocal/index.js +++ b/utils/lambdaFunctionLocal/index.js @@ -1,5 +1,6 @@ const playwright = require('playwright-aws-lambda'); const { chromium } = require('playwright-core'); +const path = require('path'); const errorStatusHandler = require('./statusError'); diff --git a/utils/setup-cf-template.js b/utils/setup-cf-template.js index 349b97a..810ab10 100644 --- a/utils/setup-cf-template.js +++ b/utils/setup-cf-template.js @@ -15,6 +15,9 @@ const dirOutput = path.join(__dirname, '..', 'output'); * @param {string} listener - Logzio listener url to send metrics/logs * @param {string} region - AWS Region name * @param {string} rangeTime - set interval for run Lambda function( in minutes) + * @param {boolean} onChangeRecordStatus - Flag for record a browser based video of test + * @param {string} testDevice - Device where need to run a test + */ exports.setupCFTemplate = async ( listEnvVariables, @@ -25,6 +28,8 @@ exports.setupCFTemplate = async ( listener, region, rangeTime, + onChangeRecordStatus, + testDevice, ) => { try { const doc = yaml.load( @@ -52,6 +57,8 @@ exports.setupCFTemplate = async ( listener, region, rangeTime, + onChangeRecordStatus, + testDevice, ); if (listEnvVariables.length > 0) { @@ -135,6 +142,8 @@ exports.setupCFTemplate = async ( * @param {string} listener - Logzio listener url to send metrics/logs * @param {string} region - AWS Region name * @param {string} rangeTime - set interval for run Lambda function( in minutes) + * @param {boolean} onChangeRecordStatus - Flag for record a browser based video of test + * @param {string} testDevice - Device where need to run a test */ const updateTemplate = ( template, @@ -145,6 +154,8 @@ const updateTemplate = ( listener, region, rangeTime, + onChangeRecordStatus, + testDevice, ) => { const newYaml = { ...template }; @@ -183,6 +194,17 @@ const updateTemplate = ( newYaml.Resources.ScheduledLambda.Properties.Environment.Variables.NAME_FUNCTION = name; + // is record + newYaml.Resources.ScheduledLambda.Properties.Environment.Variables.IS_RECORD = + onChangeRecordStatus ? 'to_record' : 'not_to_record'; + //bucket name + newYaml.Resources.ScheduledLambda.Properties.Environment.Variables.BUCKET_NAME = + bucket; + + // test device + newYaml.Resources.ScheduledLambda.Properties.Environment.Variables.TEST_DEVICE = + testDevice; + return newYaml; };