diff --git a/API/DB.js b/API/DB.js deleted file mode 100644 index a07aa8d..0000000 --- a/API/DB.js +++ /dev/null @@ -1,133 +0,0 @@ -const { Pool, Client } = require('pg') -const pool = new Pool({ - user: 'postgres', - password: '8ed89611-dff5-444f-867c-c66098a349cd', - host: '172.17.0.1', - database: 'ws_trail', - port: 5432, - max: 4 -}) - -module.exports.SaveEvent = async function (data, sourceIP, country) { - - var userID = data.uid; - var context = data.ctx; - var eventType = data.type; - var eventTitle = data.title; - var eventValue = data.val; - var eventData = data.data; - var wlid = data.wlid; - - if ( country != null && country != ""){ - if ( eventData == null ){ - eventData = {}; - } - eventData.country = country; - } - - var SQL = `INSERT INTO Logs (time, userID, context, eventType, eventTitle, eventValue, eventData, sourceIP, wlid) - VALUES (NOW(), '${Clean(userID)}', '${Clean(context)}', '${Clean(eventType)}', '${Clean(eventTitle)}', - '${Clean(eventValue)}', '${Clean(JSON.stringify(eventData))}', '${sourceIP}', '${Clean(wlid)}' ) RETURNING time;` - - //console.log(SQL); - - var rez = null; - try{ - rez = await pool.query(SQL); - } - catch(ex){ - console.log("Crash in SQL Query for SaveEvent: " + SQL); - var obj = { count: 0, command: "INVALID_QUERY", time: 0 }; - return obj - } - //console.log(rez); - var obj = { count: rez.rowCount, command: rez.command, time: rez.rows[0].time }; - return obj -} - -module.exports.Query = async function (SQL, sourceIP) { - var rez = await pool.query(SQL); - //console.log(rez); - var obj = { count: rez.rowCount, rows: rez.rows }; - return obj -} - -module.exports.StartOrUpdateSession = async function (data, sourceIP, userAgent) { - - var userID = data.uid; - - var context = data.ctx; - if ( context == null ){ - context = ""; - } - - var wlid = data.wlid; - - if ( wlid == null || wlid == "" ){ - wlid = -1; - } - else{ - try{ - wlid = parseInt(wlid); - }catch(ex){} - } - - - var SQLSearch = `SELECT * FROM sessions WHERE lastupdate > ( now()::timestamp - INTERVAL '1 min' )::timestamp ORDER BY id desc LIMIT 1`; - var rezSearch = await pool.query(SQLSearch); - - if ( rezSearch.rowCount == 0 ){ - var SQL = `INSERT INTO sessions (start_time, is_active, wlid, userid, context, total_time, active_time, idle_time, user_agent, sourceip, lastupdate) - VALUES (NOW(), '1', '${Clean(wlid)}', '${Clean(userID)}', '${Clean(context)}', 0, 0, 0, '${Clean(userAgent)}', '${sourceIP}', NOW() ) RETURNING id;` - var rez = await pool.query(SQL); - //console.log(rez); - var obj = { count: rez.rowCount, command: rez.command, sessionID: rez.rows[0].id }; - return obj - } - else{ - var timeDiffInMinutes = `extract(epoch from (NOW()::timestamp - start_time::timestamp))::integer`; - var SQL = `UPDATE sessions SET is_active = '1', total_time = ${timeDiffInMinutes}, lastupdate = NOW() WHERE id = '${rezSearch.rows[0].id}';` - var rez = await pool.query(SQL); - //console.log(rez); - var obj = { count: rez.rowCount, command: rez.command, sessionID: rezSearch.rows[0].id }; - return obj - } - -} - -module.exports.UpdateSession = async function (sessionID, active_time, is_active) { - - var timeDiffInMinutes = `extract(epoch from (NOW() - start_time::timestamp))::integer`; - - var SQL = `UPDATE sessions SET is_active = '${Clean(is_active)}', total_time = ${timeDiffInMinutes}, active_time = active_time + ${active_time}, idle_time = (${timeDiffInMinutes}) - (active_time + ${active_time}), lastupdate = NOW() WHERE id = '${sessionID}';` - //console.log(SQL); - - var rez = await pool.query(SQL); - - var obj = { count: rez.rowCount, command: rez.command, sessionID: sessionID }; - return obj - -} - -module.exports.CloseSession = async function (sessionID) { - - /* - var timeDiffInMinutes = `(DATE_PART('day', end_time::timestamp - start_time::timestamp) * 24 + - DATE_PART('hour', end_time::timestamp - start_time::timestamp)) * 60 + - DATE_PART('minute', end_time::timestamp - start_time::timestamp)`; - */ - - var timeDiffInMinutes = `extract(epoch from (NOW()::timestamp - start_time::timestamp))::integer`; - - var SQL = `UPDATE sessions SET end_time = NOW(), is_active = '0', total_time = ${timeDiffInMinutes}, idle_time = (${timeDiffInMinutes}) - (active_time), lastupdate = NOW() WHERE id = '${sessionID}';` - var rez = await pool.query(SQL); - //console.log(rez); - var obj = { count: rez.rowCount, command: rez.command, sessionID: sessionID }; - return obj - -} - -function Clean(txt){ - if ( txt == null ){ return ""; } - else{ return (txt + "").replace(/\'/g, "''"); } -} \ No newline at end of file diff --git a/API/REST/GenerateJWT.js b/API/REST/GenerateJWT.js deleted file mode 100644 index 18a6104..0000000 --- a/API/REST/GenerateJWT.js +++ /dev/null @@ -1,56 +0,0 @@ -var jwt = require('jsonwebtoken'); -var JWTKey = require("../../appconfig.json").JWTKey; -var hardcodedAPIKey = require("../../appconfig.json").ApiKey; - -exports.handler = async (event, context, callback) => { - - var beginPipeline = process.hrtime(); - - //debug - //var body = {"uid":"120", "token": "123615z1ef88136zg5e1"}; - - var ApiKey = event.headers["x-api-key"]; - - //PROD - var body = null; - try{ - body = JSON.parse(event.body); - } - catch(ex){ - callback(null, { - status: 400, - content: "Invalid content provided, posted body must be in JSON" - }); - return; - } - - - var uid = body.uid; - //if ( ApiKey != hardcodedAPIKey ){ - if ( ApiKey != hardcodedAPIKey ){ - //console.log(event); - callback(null, { - status: 400, - content: "Invalid token provided" - }); - return; - } - - var token = jwt.sign(body, JWTKey); - - - const nanoSeconds = process.hrtime(beginPipeline).reduce((sec, nano) => sec * 1e9 + nano); - var durationMS = (nanoSeconds/1000000); - - var processTime = durationMS.toFixed(2) + "ms"; - - callback(null, { - status: 200, - content: {"jwt": token}, - headers:{ - "processTime": processTime, - "Content-Type": "application/json" - } - }); - -}; \ No newline at end of file diff --git a/API/REST/Query.js b/API/REST/Query.js deleted file mode 100644 index 3f19774..0000000 --- a/API/REST/Query.js +++ /dev/null @@ -1,127 +0,0 @@ -var DB = require("../DB.js"); -var hardcodedAPIKey = require("../../appconfig.json").ApiKey; - -exports.handler = async (event, context, callback) => { - - var beginPipeline = process.hrtime(); - - //debug - //var body = {"uid":"120", "ctx":"", "start":"", "end":"", "offset": 0, "token": "123615z1ef88136zg5e1"}; - var ApiKey = event.headers["x-api-key"]; - - //PROD - var body = null; - try{ - body = JSON.parse(event.body); - } - catch(ex){ - callback(null, { - status: 400, - content: "Invalid content provided, posted body must be in JSON" - }); - return; - } - - var uid = body.uid; - var ctx = body.ctx; - var start = body.start; - var end = body.end; - - var offset = body.offset; - var pageSize = 200; - if ( offset == null || offset == ""){ - offset = 0; - } - - var pageSize = body.pageSize; - if ( pageSize == null || pageSize == ""){ - pageSize = 25; - } - - var eventType = body.eventType; - if ( eventType == null || eventType == ""){ - eventType = ""; - } - - var showSession = body.session; - if ( showSession == null || showSession == ""){ - showSession = ""; - } - - var wlid = body.wlid; - if ( wlid == null || wlid == ""){ - wlid = ""; - } - - if ( ApiKey != hardcodedAPIKey ){ - callback(null, { - status: 400, - content: "Invalid token provided" - }); - return; - } - - //TODO: ensure user is allowed to query this uid & ctx! - - var SQL = "SELECT * FROM logs WHERE 1=1 "; - if ( uid != null && uid != "" ){ - SQL += " AND userID = '" + Clean(uid) + "' "; - } - if ( ctx != null && ctx != "" ){ - SQL += " AND context = '" + Clean(ctx) + "' "; - } - if ( start != null && start != "" ){ - SQL += " AND time >= '" + Clean(start) + "' "; - } - if ( end != null && end != "" ){ - SQL += " AND time <= '" + Clean(end) + "' "; - } - - if ( eventType != "") { - SQL += " AND eventtype = '" + Clean(eventType) + "' "; - } - - if ( eventType != "Session" && showSession == false ) { - SQL += " AND eventtype != 'Session' "; - } - - if ( wlid != "") { - SQL += " AND wlid = '" + Clean(wlid) + "' "; - } - - //ORDER - SQL += " ORDER BY time desc " - - //add a LIMIT & OFFSET - SQL += " LIMIT " + pageSize + " OFFSET " + Clean(offset); - - - - var obj = await DB.Query(SQL, event.ip); - - const nanoSeconds = process.hrtime(beginPipeline).reduce((sec, nano) => sec * 1e9 + nano); - var durationMS = (nanoSeconds/1000000); - - obj.processTime = durationMS.toFixed(2) + "ms"; - - callback(null, { - status: 200, - content: obj, - headers:{ - "processTime": obj.processTime, - "Content-Type": "application/json; charset=utf-8" - } - }); - -}; - - - -function Clean(txt){ - if ( txt == null ){ - return ""; - } - else{ - return (txt + "").replace(/\'/g, "''"); - } -} \ No newline at end of file diff --git a/API/REST/Save.js b/API/REST/Save.js deleted file mode 100644 index 9de000b..0000000 --- a/API/REST/Save.js +++ /dev/null @@ -1,84 +0,0 @@ -var DB = require("../DB.js"); - -var jwt = require('jsonwebtoken'); -var JWTKey = require("../../appconfig.json").JWTKey; -var hardcodedAPIKey = require("../../appconfig.json").ApiKey; - -exports.handler = async (event, context, callback) => { - - var beginPipeline = process.hrtime(); - - - var Authorization = event.headers["authorization"]; - var jwtSTR = Authorization; - if ( jwtSTR != null ){ - jwtSTR = Authorization.replace("Bearer ", ""); - } - - var ApiKey = event.headers["x-api-key"]; - var apiAuth = false; - if ( ApiKey == hardcodedAPIKey ){ - apiAuth = true; - } - - var decodedJWT = null; - if (jwtSTR != null && jwtSTR != ""){ - decodedJWT = jwt.verify(jwtSTR, JWTKey); - } - - var jwtAuth = false; - if ( decodedJWT == null || decodedJWT.uid == null ){ - // - } - else{ - jwtAuth = true; - } - - - if ( !apiAuth && !jwtAuth ){ - callback(null, { - status: 400, - content: "Invalid token / jwt provided" - }); - return; - } - - - //debug - //var body = {"uid":"120", "ctx":"", "type":"Start", "title":"", "val": 1, "data": {}}; - - //PROD - - var body = null; - try{ - body = JSON.parse(event.body); - } - catch(ex){ - callback(null, { - status: 400, - content: '{ "error": "Invalid content provided, posted body must be in JSON" }' - }); - return; - } - - if ( event.headers["cf-ipcountry"] != null && event.headers["cf-ipcountry"] != ""){ - if ( event.ws == null ){ - event.ws = {}; - } - event.ws.country = event.headers["cf-ipcountry"]; - } - - var obj = await DB.SaveEvent(body, event.ip, event.ws.country); - const nanoSeconds = process.hrtime(beginPipeline).reduce((sec, nano) => sec * 1e9 + nano); - var durationMS = (nanoSeconds/1000000); - - callback(null, { - status: 200, - content: obj, - headers:{ - "execTime": durationMS.toFixed(2) + "ms", - "Content-Type": "application/json" - } - }); - -}; \ No newline at end of file diff --git a/API/REST/Screenshot.js b/API/REST/Screenshot.js new file mode 100644 index 0000000..f64a9fa --- /dev/null +++ b/API/REST/Screenshot.js @@ -0,0 +1,58 @@ +const puppeteer = require('puppeteer'); +var hardcodedAPIKey = require("../../appconfig.json").ApiKey; +const tools = require('../shared.js'); + +const os = require('os') +const cpuCount = os.cpus().length +var maxConcurrency = (cpuCount)/2; +if (maxConcurrency < 1){ + maxConcurrency = 1; +} + +exports.handler = async (event, context, callback) => { + + var sharedmem = context.sharedmem; + var beginPipeline = process.hrtime(); + + /* + if ( !apiAuth && !jwtAuth ){ + callback(null, { + status: 400, + content: "Invalid token / jwt provided" + }); + return; + } + */ + while ( sharedmem.getInteger("nbPuppeteerProcess") >= maxConcurrency ){ + await sleep(20); + } + + sharedmem.incInteger("nbPuppeteerProcess", 1); + + var url = event.queryStringParameters.url; + if ( url == null || url == "" ){ + url = "https://www.google.com"; + } + else{ + url = decodeURIComponent(url); + } + + var screenshotResult = await tools.screnshotForUrl(url, true); + //var screenshotResult = await screnshotForUrlTab(url); + + sharedmem.incInteger("nbPuppeteerProcess", -1); + + const nanoSeconds = process.hrtime(beginPipeline).reduce((sec, nano) => sec * 1e9 + nano); + var durationMS = (nanoSeconds/1000000); + + callback(null, { + status: 200, + content: screenshotResult.data, + headers:{ + "execTime": durationMS.toFixed(2) + "ms", + "nbPuppeteerProcess": sharedmem.getInteger("nbPuppeteerProcess"), + "Content-Type": screenshotResult.mimeType + } + }); + +}; \ No newline at end of file diff --git a/API/REST/Sessions.js b/API/REST/Sessions.js deleted file mode 100644 index ed3bbc4..0000000 --- a/API/REST/Sessions.js +++ /dev/null @@ -1,104 +0,0 @@ -var DB = require("../DB.js"); -var hardcodedAPIKey = require("../../appconfig.json").ApiKey; - -exports.handler = async (event, context, callback) => { - - var beginPipeline = process.hrtime(); - - //debug - //var body = {"uid":"120", "ctx":"", "start":"", "end":"", "offset": 0, "token": "123615z1ef88136zg5e1"}; - var ApiKey = event.headers["x-api-key"]; - - //PROD - var body = null; - try{ - body = JSON.parse(event.body); - } - catch(ex){ - callback(null, { - status: 400, - content: "Invalid content provided, posted body must be in JSON" - }); - return; - } - - var uid = body.uid; - var ctx = body.ctx; - - var wlid = ""; - if ( wlid != null && wlid != "" ){ - wlid = body.wlid; - } - - var start = body.start; - var end = body.end; - - var offset = body.offset; - if ( offset == null || offset == ""){ - offset = 0; - } - - - if ( ApiKey != hardcodedAPIKey ){ - callback(null, { - status: 400, - content: "Invalid token provided" - }); - return; - } - - //TODO: ensure user is allowed to query this uid & ctx! - - var SQL = "SELECT * FROM sessions WHERE 1=1 "; - if ( uid != null && uid != "" ){ - SQL += " AND userid = '" + Clean(uid) + "' "; - } - if ( ctx != null && ctx != "" ){ - SQL += " AND context = '" + Clean(ctx) + "' "; - } - if ( wlid != null && wlid != "" ){ - SQL += " AND wlid = '" + Clean(ctx) + "' "; - } - if ( start != null && start != "" ){ - SQL += " AND lastupdate >= '" + Clean(start) + "' "; - } - if ( end != null && end != "" ){ - SQL += " AND lastupdate <= '" + Clean(end) + "' "; - } - - //ORDER - SQL += " ORDER BY lastupdate desc " - - //add a LIMIT & OFFSET - SQL += " LIMIT 200 OFFSET " + Clean(offset); - - - - var obj = await DB.Query(SQL, event.ip); - - const nanoSeconds = process.hrtime(beginPipeline).reduce((sec, nano) => sec * 1e9 + nano); - var durationMS = (nanoSeconds/1000000); - - obj.processTime = durationMS.toFixed(2) + "ms"; - - callback(null, { - status: 200, - content: obj, - headers:{ - "processTime": obj.processTime, - "Content-Type": "application/json" - } - }); - -}; - - - -function Clean(txt){ - if ( txt == null ){ - return ""; - } - else{ - return (txt + "").replace(/\'/g, "''"); - } -} \ No newline at end of file diff --git a/API/WS/Chat.js b/API/WS/Chat.js deleted file mode 100644 index a1205bf..0000000 --- a/API/WS/Chat.js +++ /dev/null @@ -1,25 +0,0 @@ - -exports.open = (event, context, callback) => { - //Say hello or send a message to the client, increment number of connected users, ... - event.ws.subscribe('mainChannel'); - callback(null, "Open from CloudGate! Thread: " + require('worker_threads').threadId); -}; - -exports.message = (event, context, callback) => { - //Do something with the message received from the client (echo, broadcast it, subscribe to a channel, execute some code ...) - - if ( event.body != null && event.body != ""){ - event.app.publish('mainChannel', event.body); - } - - //return the body received (echo) - callback(null, null); -}; - -exports.close = (event, context, callback) => { - // Do something like decrement number of users, close session, ... - - //here your response will be discarded because the websocket - //is already closed at clientside when we receive this event - callback(null, null); -}; \ No newline at end of file diff --git a/API/WS/Monitor.js b/API/WS/Monitor.js deleted file mode 100644 index a6d9266..0000000 --- a/API/WS/Monitor.js +++ /dev/null @@ -1,299 +0,0 @@ -const qs = require('querystring'); -var DB = require("../DB.js"); - -var jwt = require('jsonwebtoken'); -var JWTKey = require("../../appconfig.json").JWTKey; - -var sessionsList = {}; -var maxDurationBeforeQuitSession = 30000; - -exports.upgrade = async (event, context, callback) => { - //console.log("Upgraded!"); -}; - -exports.open = async (event, context, callback) => { - - try{ - - if ( event.headers["cf-connecting-ip"] != null && event.headers["cf-connecting-ip"] != ""){ - event.ip = event.headers["cf-connecting-ip"]; - } - - if ( event.headers["cf-ipcountry"] != null && event.headers["cf-ipcountry"] != ""){ - event.ws.country = event.headers["cf-ipcountry"]; - } - - event.ws.startTime = (+new Date()); - event.ws.startActiveTime = (+new Date()); - event.ws.isActive = true; - - var params = {}; - try{ - params = qs.parse(event.query); - } - catch(exParams){ - - } - - var jwtSTR = params.jwt; - var wlid = "-1"; - if ( params.wlid != null && params.wlid != ""){ - wlid = params.wlid; - } - event.ws.wlid = wlid; - - //console.log(jwtSTR); - event.ws.jwt = jwtSTR; - - if ( event.ws.jwt != null && event.ws.jwt != "" ){ - try{ - - var decodedJWT = jwt.verify(jwtSTR, JWTKey); - if ( decodedJWT == null || decodedJWT.uid == null ){ - callback(null, "Invalid JWT provided"); - return; - } - - event.ws.decodedJWT = decodedJWT; - event.ws.uid = decodedJWT.uid; - - //Check if we have an existing active session for this user - //IN-MEMORY -> should be moved to shared memory in C++ - sharedmem = context.sharedmem; - var cachedSession = sharedmem.getString(decodedJWT.uid + "", "SessionsList"); - - //keep count of number of websockets opened for this user - sharedmem.incInteger(decodedJWT.uid + "", 1, "SessionsCount"); - sharedmem.setString(event.ws.uid + "", "0", "SessionsFinished"); - - //if ( sessionsList[decodedJWT.uid] == null ){ - if ( cachedSession == null || cachedSession == "" ){ - //console.log(cachedSession); - //console.log(event); - - //sessionsList[decodedJWT.uid] = (+new Date()); - var newSessionID = (+new Date()) + ""; - sharedmem.setString(decodedJWT.uid + "", newSessionID + "", "SessionsList"); - sharedmem.setString(decodedJWT.uid + "_lastBeat", (+new Date()), "Heartbeats"); - event.ws.sessionID = newSessionID; - - //INSERT Start of session in logs table - var newSessionEvent = {"uid": decodedJWT.uid, "ctx": "", "wlid": wlid, "type": "Session", "title": "Start", "val": "", "data": {"sessionID": newSessionID, "userAgent": event.headers["user-agent"]}}; - - if ( decodedJWT.firstname != null){ - newSessionEvent.data.firstname = decodedJWT.firstname; - } - if ( decodedJWT.lastname != null){ - newSessionEvent.data.lastname = decodedJWT.lastname; - } - if ( decodedJWT.email != null){ - newSessionEvent.data.email = decodedJWT.email; - } - - //console.log("newSessionEvent"); - //console.log(newSessionEvent); - - await DB.SaveEvent(newSessionEvent, event.ip, event.ws.country); - - console.log("start of sessionID: " + newSessionID) - } - else{ - event.ws.sessionID = cachedSession; - console.log("resuming of sessionID: " + cachedSession) - } - - callback(null, "Monitoring started"); - } - catch(ex){ - console.log("crash in monitor: "); - console.log(ex); - callback(null, ex.message); - return; - } - } - - } - catch(ex){ - console.log(ex); - console.log(ex.stack); - console.log("Crash in OPEN"); - } - - - callback(null, JSON.stringify({"status": "OK", "msg": "Monitoring Started"})); -}; - - -var timeoutsList = {}; - -exports.message = async (event, context, callback) => { - - //console.log("MSG 0"); - try{ - - //Do something with the message received from the client (echo, broadcast it, subscribe to a channel, execute some code ...) - var msg = event.body; - var obj = JSON.parse(msg); - var decodedJWT = event.ws.decodedJWT; - - if ( (obj.ctx || obj.channel) != "public"){ - if ( event.ws.jwt != null && event.ws.jwt != "" ){ - try{ - decodedJWT = jwt.verify(event.ws.jwt, JWTKey); - if ( decodedJWT == null || decodedJWT.uid == null ){ - callback(null, JSON.stringify({"error": "Invalid JWT provided"})); - return; - } - } - catch(ex){ - callback(null, JSON.stringify({"error": "Invalid JWT provided"})); - return; - } - } - else{ - callback(null, JSON.stringify({"error": "You must provide a valid JWT to publish or subscribe to CTX: " + (obj.ctx || obj.channel) })); - return; - } - } - - //add firstname, lastname, email to data object if available in jwt - if ( obj.data == null ){ - obj.data = {}; - } - if ( decodedJWT.firstname != null){ - obj.data.firstname = decodedJWT.firstname; - } - if ( decodedJWT.lastname != null){ - obj.data.lastname = decodedJWT.lastname; - } - if ( decodedJWT.email != null){ - obj.data.email = decodedJWT.email; - } - msg = JSON.stringify(obj); - - var resp = null; - if ( obj.cmd == "event" ){ - - //publish on the context (appid) channel - //TODO: ensure the user have permission to publish to that ctx (appid) - if ( obj.ctx != null && obj.ctx != ""){ - event.app.publish(obj.ctx, msg); - } - - //Store in DB - var result = await DB.SaveEvent(obj, event.ip, event.ws.country); - - //resp = result; //disabled because we don't need to confirm to the client - } - else if ( obj.cmd == "heartbeat" ){ - - event.ws.isActive = true; - sharedmem.setString(event.ws.uid + "_lastBeat", (+new Date()), "Heartbeats"); - sharedmem.setString(event.ws.uid + "_isActive", "1", "Heartbeats"); - - //cancel previous timeout if exist - if ( timeoutsList[event.ws.sessionID] != null ){ - clearTimeout(timeoutsList[event.ws.sessionID]); - //console.log("timeout cleared for session: " + event.ws.sessionID) - delete timeoutsList[event.ws.sessionID]; - } - - timeoutsList[event.ws.sessionID] = setTimeout( async function(){ - event.ws.isActive = false; - var lastHeartbeat = -1; - if ( event != null && event.ws != null ){ - lastHeartbeat = sharedmem.getString(event.ws.uid + "_lastBeat", "Heartbeats"); - if ( lastHeartbeat != "") { - lastHeartbeat = parseInt(lastHeartbeat); - } - } - }, maxDurationBeforeQuitSession); - - } - else if ( obj.cmd == "subscribe" ){ - //subscribe the caller to his ctx channel - //TODO: ensure the user is allowed to register this context (have permission for that appID) - if ( obj.channel != null && obj.channel != ""){ - event.ws.subscribe(obj.channel); - resp = {cmd: "subscriptionStatus", subscribed: true, channel: obj.channel}; - } - else{ - resp = {cmd: "subscriptionStatus", subscribed: false, msg: "Channel not found or you don't have permission..."}; - } - } - - } - catch(ex){ - console.log(ex); - console.log("Crash in Message"); - } - - - - //return the response to the websocket client (caller) - if ( resp != null ){ - callback(null, JSON.stringify(resp)); - } - -}; - -exports.close = async (event, context, callback) => { - - //check in 15 sec if the session was not reopened by another tab - console.log("Closing websocket for sessionID: " + event.ws.sessionID); - sharedmem.incInteger(event.ws.uid + "", -1, "SessionsCount"); - console.log("Remaining websockets for that uid: " + sharedmem.getInteger(event.ws.uid + "", "SessionsCount") ); - - var remainingSockets = parseInt(sharedmem.getInteger(event.ws.uid + "", "SessionsCount")); - - //No more sockets open for that uid, let's wait 15 seconds to check if the user is going to reconnect - //if not we will end the session - if ( remainingSockets == 0 ){ - setTimeout( async function(){ - var lastHeartbeat = parseInt(sharedmem.getString(event.ws.uid + "_lastBeat", "Heartbeats")); - var remainingSockets = parseInt(sharedmem.getInteger(event.ws.uid + "", "SessionsCount")); - - //check if session have already been ended elsewhere - if ( sharedmem.getString(event.ws.uid + "", "SessionsFinished") == "1" ) { - return; //session already ended by another thread - } - - if ( lastHeartbeat + maxDurationBeforeQuitSession < (+new Date()) && remainingSockets == 0 ) { - sharedmem.setString(event.ws.uid + "_isActive", "0", "Heartbeats"); - - //lets close the session, no other thread have resumed that session in the last 15 seconds - var activeTimeDuration = (+new Date()) - event.ws.startActiveTime; - activeTimeDuration = activeTimeDuration / 1000; - - //INSERT end of session log - var endSessionEvent = {"uid": event.ws.decodedJWT.uid, "ctx": "", "wlid": event.ws.wlid, "type": "Session", "title": "Stop", "val": "", "data": {"sessionID": event.ws.sessionID, "durationInSeconds": activeTimeDuration}}; - - //add firstname, lastname, email to data object if available in jwt - if ( event.ws.decodedJWT.firstname != null){ - endSessionEvent.data.firstname = event.ws.decodedJWT.firstname; - } - if ( event.ws.decodedJWT.lastname != null){ - endSessionEvent.data.lastname = event.ws.decodedJWT.lastname; - } - if ( event.ws.decodedJWT.email != null){ - endSessionEvent.data.email = event.ws.decodedJWT.email; - } - await DB.SaveEvent(endSessionEvent, event.ip, event.ws.country); - - console.log("end of sessionID: " + event.ws.sessionID + ", Duration: " + activeTimeDuration); - - delete sessionsList[event.ws.decodedJWT.uid]; - event.ws.startActiveTime = null; - sharedmem.setString(event.ws.uid + "", "", "SessionsList"); - sharedmem.setString(event.ws.uid + "", "1", "SessionsFinished"); - } - else{ - console.log("sessionID: " + event.ws.sessionID + " is continued in another thread ... so we are not closing the session"); - } - }, maxDurationBeforeQuitSession) - } - - //here your response will be discarded because the websocket - //is already closed at clientside when we receive this event - callback(null, null); -}; diff --git a/API/WS/Screenshot.js b/API/WS/Screenshot.js new file mode 100644 index 0000000..0d63553 --- /dev/null +++ b/API/WS/Screenshot.js @@ -0,0 +1,103 @@ +const qs = require('querystring'); +var jwt = require('jsonwebtoken'); +var JWTKey = require("../../appconfig.json").JWTKey; +const tools = require('../shared.js'); + +const os = require('os') +const cpuCount = os.cpus().length +var maxConcurrency = (cpuCount)/2; +if (maxConcurrency < 1){ + maxConcurrency = 1; +} + +exports.upgrade = async (event, context, callback) => { + //console.log("Upgraded!"); +}; + +exports.open = async (event, context, callback) => { + callback(null, JSON.stringify({"status": "OK", "msg": "Connected to websocket screenshot service"})); +}; + + +var timeoutsList = {}; + +exports.message = async (event, context, callback) => { + + try{ + + //Do something with the message received from the client (echo, broadcast it, subscribe to a channel, execute some code ...) + var msg = event.body; + var obj = JSON.parse(msg); + msg = JSON.stringify(obj); + + var resp = null; + + if ( obj.cmd == "screenshot" ){ + + var sharedmem = context.sharedmem; + var beginPipeline = process.hrtime(); + + while ( sharedmem.getInteger("nbPuppeteerProcess") >= maxConcurrency ){ + await sleep(20); + } + + sharedmem.incInteger("nbPuppeteerProcess", 1); + + var url = obj.url; + if ( url == null || url == "" ){ + url = "https://www.google.com"; + } + else{ + url = decodeURIComponent(url); + } + + var screenshotResult = await tools.screnshotForUrl(url, true); + //var screenshotResult = await screnshotForUrlTab(url); + + sharedmem.incInteger("nbPuppeteerProcess", -1); + + const nanoSeconds = process.hrtime(beginPipeline).reduce((sec, nano) => sec * 1e9 + nano); + var durationMS = (nanoSeconds/1000000); + + /* + callback(null, { + status: 200, + content: screenshotResult.data, + headers:{ + "execTime": durationMS.toFixed(2) + "ms", + "nbPuppeteerProcess": sharedmem.getInteger("nbPuppeteerProcess"), + "Content-Type": screenshotResult.mimeType + } + }); + */ + + resp = { + "cmd": "responseScreenshot", + "data": screenshotResult.data.toString("base64"), + "execTime": durationMS.toFixed(2) + "ms", + "originalTS": obj.originalTS, + "Content-Type": screenshotResult.mimeType + }; + } + + } + catch(ex){ + console.log(ex); + console.log("Crash in Message handling"); + } + + + + //return the response to the websocket client (caller) + if ( resp != null ){ + callback(null, JSON.stringify(resp)); + } + +}; + +exports.close = async (event, context, callback) => { + + //here your response will be discarded because the websocket + //is already closed at clientside when we receive this event + callback(null, null); +}; diff --git a/API/shared.js b/API/shared.js new file mode 100644 index 0000000..b0f61e1 --- /dev/null +++ b/API/shared.js @@ -0,0 +1,167 @@ +const puppeteer = require('puppeteer'); + +var browser = null; +module.exports.screnshotForUrlTab = async function (url, isfullPage) { + return new Promise(async function (resolve, reject) { + + var timestamp = (+new Date()); + var buffer = null; + try{ + + if ( browser == null){ + browser = await puppeteer.launch({args: ['--no-sandbox']}); + } + + //const browser = await puppeteer.launch({args: ['--no-sandbox']}); + const page = await browser.newPage(); + await page.goto(url); + + await page.setViewport({ + width: 1280, + height: 900, + isMobile: false, + deviceScaleFactor: 1, + }); + + //scroll the whole page for lazy loading + //await autoScroll(page); + + //wait for the page to be fully loaded - max 1000ms wait + /* + try{ + await page.waitForNavigation({waitUntil: 'networkidle2', timeout: 1000}); + } catch(ex){ + + } + */ + + buffer = await page.screenshot({ + //type:'png', + type:'jpeg', + quality: 88, + encoding: 'binary', + fullPage: isfullPage + }); + //await browser.close(); + await page.close(); + + let mimeType = "image/jpeg"; + var resp = { + mimeType: mimeType, + data: buffer + }; + + resolve(resp); + + } + catch(ex){ + console.log("Error while taking screenshot: " + ex.message); + console.log(ex); + + try{ + //await browser.close(); + }catch(ex){ + + } + + var resp = { + status: "error", + details: ex, + mimeType: "application/json" + }; + reject(resp); + } + }); +} + +module.exports.screnshotForUrl = async function (url, isfullPage) { + return new Promise(async function (resolve, reject) { + + var timestamp = (+new Date()); + var buffer = null; + var browser = null; + try { + + browser = await puppeteer.launch({args: ['--no-sandbox']}); + const page = await browser.newPage(); + await page.goto(url); + + await page.setViewport({ + width: 1280, + height: 900, + isMobile: false, + deviceScaleFactor: 1, + }); + + //scroll the whole page for lazy loading + await autoScroll(page); + + //wait for the page to be fully loaded - max 1000ms wait + try{ + //await page.waitForNavigation({waitUntil: 'networkidle2', timeout: 1000}); + } catch(ex){ + + } + + + buffer = await page.screenshot({ + type:'jpeg', + quality: 84, + encoding: 'binary', + fullPage: isfullPage + }); + await browser.close(); + + let mimeType = "image/jpeg"; + var resp = { + mimeType: mimeType, + data: buffer + }; + + resolve(resp); + + } + catch(ex){ + console.log("Error while taking screenshot: " + ex.message); + console.log(ex); + + try{ + await browser.close(); + }catch(ex){ + + } + + var resp = { + status: "error", + details: ex, + mimeType: "application/json" + }; + reject(resp); + } + }); +} + + +async function autoScroll (page) { + await page.evaluate(async () => { + await new Promise((resolve, reject) => { + let totalHeight = 0 + let distance = 500 + let timer = setInterval(() => { + let scrollHeight = document.body.scrollHeight + window.scrollBy(0, distance) + totalHeight += distance + if(totalHeight >= scrollHeight){ + clearInterval(timer) + resolve() + } + }, 100) + }) + }) +} + +module.exports.sleep = function (ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} \ No newline at end of file diff --git a/DB/createDB.sh b/DB/createDB.sh deleted file mode 100755 index 8894154..0000000 --- a/DB/createDB.sh +++ /dev/null @@ -1,11 +0,0 @@ -# CREATE THE DB -docker exec timescaledb psql -U postgres -c "CREATE DATABASE ws_trail;"; - -# COPY SQL script to the container -docker cp ./createTables.sql timescaledb:/tmp/createTables.sql; - -# CREATE Tables with the SQL script -docker exec timescaledb psql -U postgres -d ws_trail -a -f /tmp/createTables.sql; - -# CREATE THE HYPERTABLE (TimescaleDB) ON THE TIME COLUMN -docker exec timescaledb psql -U postgres -d ws_trail -c "SELECT create_hypertable('logs', 'time');"; diff --git a/DB/createTables.sql b/DB/createTables.sql deleted file mode 100644 index 3da88d4..0000000 --- a/DB/createTables.sql +++ /dev/null @@ -1,64 +0,0 @@ --- --- PostgreSQL database dump --- - --- Dumped from database version 12.3 --- Dumped by pg_dump version 12.3 - -SET statement_timeout = 0; -SET lock_timeout = 0; -SET idle_in_transaction_session_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SELECT pg_catalog.set_config('search_path', '', false); -SET check_function_bodies = false; -SET xmloption = content; -SET client_min_messages = warning; -SET row_security = off; - -SET default_tablespace = ''; - -SET default_table_access_method = heap; - --- --- Name: logs; Type: TABLE; Schema: public; Owner: postgres --- - -CREATE TABLE public.logs ( - "time" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, - userid character varying(255) NOT NULL, - context character varying(255), - eventtype character varying(255), - eventtitle character varying(255), - eventvalue character varying(255), - eventdata character varying(500), - sourceip character varying(255), - wlid integer -); -ALTER TABLE public.logs OWNER TO postgres; -CREATE INDEX logs_time_idx ON public.logs USING btree ("time" DESC); - - -CREATE TABLE public.sessions ( - id SERIAL PRIMARY KEY, - start_time timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, - end_time timestamp, - is_active boolean, - wlid integer, - userid character varying(255) NOT NULL, - context character varying(255), - total_time integer, - active_time integer, - idle_time integer, - user_agent character varying(500), - sourceip character varying(255), - lastupdate timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL - -); -ALTER TABLE public.sessions OWNER TO postgres; -CREATE INDEX sessions_time_idx ON public.sessions USING btree (start_time DESC); - - --- --- PostgreSQL database dump complete --- \ No newline at end of file diff --git a/DB/startTimescaleDB.sh b/DB/startTimescaleDB.sh deleted file mode 100755 index f000ab2..0000000 --- a/DB/startTimescaleDB.sh +++ /dev/null @@ -1,2 +0,0 @@ -docker run -d --name timescaledb -p 172.17.0.1:5432:5432 -e POSTGRES_PASSWORD=8ed89611-dff5-444f-867c-c66098a349cd timescale/timescaledb:latest-pg12 - diff --git a/Dockerfile b/Dockerfile index d1a1c44..f34a2f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,8 +7,13 @@ WORKDIR /usr/src/app # Install app dependencies COPY * ./ + +RUN apt-get update && apt-get install -y git + RUN npm install +RUN apt-get update && apt-get install -y gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget libgbm-dev libxcb-dri3-0 + # Bundle app source COPY . . diff --git a/appconfig.json b/appconfig.json index 0b3af7a..0bed268 100644 --- a/appconfig.json +++ b/appconfig.json @@ -9,38 +9,19 @@ "TypeAPI": "LOCAL", "TypeFS": "LOCAL", "apiEndpoints": { - "/api/save" : { - "src": "./API/REST/", - "handler": "Save.handler" - }, - "/api/query" : { + "/api/screenshot" : { "src" : "./API/REST/", - "handler": "Query.handler" - }, - "/api/sessions" : { - "src" : "./API/REST/", - "handler": "Sessions.handler" - }, - "/api/GenerateJWT" : { - "src" : "./API/REST/", - "handler": "GenerateJWT.handler" + "handler": "Screenshot.handler" } }, "websocketEndpoints": { - "/ws/monitor" : { - "src" : "./API/WS/", - "upgrade": "Monitor.upgrade", - "open": "Monitor.open", - "message": "Monitor.message", - "close": "Monitor.close" - }, - "/ws/chat" : { + "/ws/screenshot" : { "src" : "./API/WS/", - "open": "Chat.open", - "message": "Chat.message", - "close": "Chat.close" + "upgrade": "Screenshot.upgrade", + "open": "Screenshot.open", + "message": "Screenshot.message", + "close": "Screenshot.close" } }, - "ApiKey": "1d0ea49c-e026-43f5-93ce-6428bfa86e0d", - "JWTKey": "2142979b-d237-4792-b197-d2720c973e2d" + "ApiKey": "1d0ea49c-e026-43f5-93ce-6428bfa86e0d" } \ No newline at end of file diff --git a/deploy.sh b/deploy.sh index 2186fad..f9b1e7e 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1 +1 @@ -ssh root@116.203.203.32 "cd ws-trail && git pull && pm2 reload ws-trail"; \ No newline at end of file +ssh root@xxx.xxx.xxx.xxx "cd ws-screenshot && git pull && pm2 reload ws-screenshot"; \ No newline at end of file diff --git a/docker-build.sh b/docker-build.sh index a7382c8..a80920f 100755 --- a/docker-build.sh +++ b/docker-build.sh @@ -1 +1 @@ -docker build -t ws-trail . +docker build -t ws-screenshot . diff --git a/docker-run.sh b/docker-run.sh index 96cf77e..85ca9be 100755 --- a/docker-run.sh +++ b/docker-run.sh @@ -1 +1 @@ -docker run -p 3000:3000 -it ws-trail +docker run -p 3000:3000 -it ws-screenshot diff --git a/docker-service.sh b/docker-service.sh index db33138..72310dd 100755 --- a/docker-service.sh +++ b/docker-service.sh @@ -1 +1 @@ -docker run -p 3000:3000 -it ws-trail -d --restart always \ No newline at end of file +docker run -p 3000:3000 -it ws-screenshot -d --restart always \ No newline at end of file diff --git a/installPuppeteerNativeDeps.sh b/installPuppeteerNativeDeps.sh new file mode 100755 index 0000000..e964601 --- /dev/null +++ b/installPuppeteerNativeDeps.sh @@ -0,0 +1 @@ +sudo apt-get install gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget libgbm-dev libxcb-dri3-0 \ No newline at end of file diff --git a/omnidb/run.sh b/omnidb/run.sh deleted file mode 100755 index ba724dc..0000000 --- a/omnidb/run.sh +++ /dev/null @@ -1 +0,0 @@ -docker run -it --rm -v conf:/etc/omnidb -p 0.0.0.0:9000:8080 -p 0.0.0.0:25482:25482 taivokasper/omnidb diff --git a/package-lock.json b/package-lock.json index 658b6f8..294ea28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@elestio/cloudgate": { - "version": "1.0.52", - "resolved": "https://registry.npmjs.org/@elestio/cloudgate/-/cloudgate-1.0.52.tgz", - "integrity": "sha512-BEfGoEn/w3A7PKiQ7ISY7XP63wGKUnXsn95UDRTH3GOmGh06882pbi3tDcyiR2RRfYAaXAAxpHcUHVz/k9bR5A==", + "version": "1.0.60", + "resolved": "https://registry.npmjs.org/@elestio/cloudgate/-/cloudgate-1.0.60.tgz", + "integrity": "sha512-mte1f4hm4SPqRXIGaQR2dbK10ZTPhQQ3wQgCLhmJtudsuC5eoL740t33kHStDeB1UoW5SWf88y3sbNnsYdighA==", "requires": { "@greenlock/manager": "^3.1.0", "@root/csr": "^0.8.1", @@ -25,7 +25,9 @@ "hashlru": "^2.3.0", "ioredis": "^4.16.0", "mime": "^2.4.4", + "multiparty": "^4.2.2", "mysql": "^2.18.1", + "node-forge": "^0.9.1", "optimist": "^0.6.1", "request": "^2.88.2", "serverless-http": "^2.3.2", @@ -58,9 +60,9 @@ }, "dependencies": { "@root/keypairs": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.10.0.tgz", - "integrity": "sha512-t8VocY46Mtb0NTsxzyLLf5tsgfw0BXLYVADAyiRdEdqHcvPFGJdjkXNtHVQuSV/FMaC65iTOHVP4E6X8iT3Ikg==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.10.1.tgz", + "integrity": "sha512-LuBheiQwMLCN5iWmhjyW9Mgc7uX16+gABSDfFafaBQnSPVylnOksHa22bqZmDkMCsinOOjUecgy2UgREOT3cOg==", "requires": { "@root/encoding": "^1.0.1", "@root/pem": "^1.0.4", @@ -127,11 +129,26 @@ } }, "@types/aws-lambda": { - "version": "8.10.60", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.60.tgz", - "integrity": "sha512-zce8DXLRcBJ3ZAJBsNGkobIdXb34whTtZaGCaauRaDI9ZfleZk/x9wNeIcyvDy4m5w5SJSCZ3yFo3xreOgWowA==", + "version": "8.10.62", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.62.tgz", + "integrity": "sha512-43XhJY5jvyH03U0cRsSqeHWhvmGZNoLzTPSAZ3JAqgG9p20uw+Sx9Auk5OAefJajd9ZcfjDJ/9lJ2BiBxfKJRw==", "optional": true }, + "@types/node": { + "version": "14.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.1.tgz", + "integrity": "sha512-aYNbO+FZ/3KGeQCEkNhHFRIzBOUgc7QvcVNKXbfnhDkSfwUv91JsQQa10rDgKSTSLkXZ1UIyPe4FJJNVgw1xWQ==", + "optional": true + }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "optional": true, + "requires": { + "@types/node": "*" + } + }, "acme": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/acme/-/acme-3.0.3.tgz", @@ -148,10 +165,15 @@ "@root/mkdirp": "^1.0.0" } }, + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" + }, "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -187,9 +209,9 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "aws-sdk": { - "version": "2.730.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.730.0.tgz", - "integrity": "sha512-hYHplsbgUDP0XuaVJaHx2L/nC0E1i+4dGlkAQpkJz3qQ4NDfLnrbUFsA2g3AyWAsrnjrBNCkexPvwnV82Qng1g==", + "version": "2.751.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.751.0.tgz", + "integrity": "sha512-0lo7YKTfjEwoP+2vK7F7WGNigvwFxqiM96PzBaseOpOelfhFHPKEJpk2Poa12JI89c8dGoc1PhTQ1TSTJK3ZqQ==", "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -215,9 +237,9 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" }, "axios": { "version": "0.19.2", @@ -255,6 +277,37 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==" }, + "bl": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -282,6 +335,11 @@ "isarray": "^1.0.0" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -317,6 +375,11 @@ "readdirp": "~3.4.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "clear-module": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.1.tgz", @@ -380,6 +443,16 @@ "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "devtools-protocol": { + "version": "0.0.799653", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.799653.tgz", + "integrity": "sha512-t1CcaZbvm8pOlikqrsIM9GOa7Ipp07+4h/q9u0JXBWjPCjHdBl9KkddX87Vv9vBHoBGtwV79sYQNGnQM6iS5gg==" + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -415,6 +488,32 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.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" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -442,6 +541,14 @@ "undici": "^1.2.2" } }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "requires": { + "pend": "~1.2.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -450,6 +557,15 @@ "to-regex-range": "^5.0.1" } }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, "follow-redirects": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", @@ -473,6 +589,11 @@ "mime-types": "^2.1.12" } }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -484,6 +605,14 @@ "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", "optional": true }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -528,11 +657,11 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "requires": { - "ajv": "^6.5.5", + "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, @@ -541,6 +670,18 @@ "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==" }, + "http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -551,6 +692,30 @@ "sshpk": "^1.7.0" } }, + "https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "requires": { + "agent-base": "5", + "debug": "4" + }, + "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" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", @@ -731,6 +896,14 @@ "safe-buffer": "^5.0.1" } }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -807,11 +980,33 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "multiparty": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.2.tgz", + "integrity": "sha512-NtZLjlvsjcoGrzojtwQwn/Tm90aWJ6XXtPppYF4WmOk/6ncdwMMKggFY2NlRRN9yiCEIVxpOfPWahVEG2HAG8Q==", + "requires": { + "http-errors": "~1.8.0", + "safe-buffer": "5.2.1", + "uid-safe": "2.1.5" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, "mysql": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", @@ -823,6 +1018,11 @@ "sqlstring": "2.3.1" } }, + "node-forge": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.2.tgz", + "integrity": "sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw==" + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -850,6 +1050,27 @@ "wordwrap": "~0.0.2" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, "packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", @@ -863,6 +1084,11 @@ "callsites": "^3.1.0" } }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -873,6 +1099,11 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -945,6 +1176,14 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "^4.0.0" + } + }, "postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -973,6 +1212,16 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -992,6 +1241,40 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" }, + "puppeteer": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-5.3.0.tgz", + "integrity": "sha512-GjqMk5GRro3TO0sw3QMsF1H7n+/jaK2OW45qMvqjYUyJ7y4oA//9auy969HHhTG3HZXaMxY/NWXF/NXlAFIvtw==", + "requires": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.799653", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "mime": "^2.0.3", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "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" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -1002,6 +1285,11 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -1097,6 +1385,14 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -1130,6 +1426,11 @@ "@types/aws-lambda": "^8.10.19" } }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "shelljs": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", @@ -1174,6 +1475,11 @@ "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.0.1.tgz", "integrity": "sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg==" }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -1183,9 +1489,44 @@ } }, "systeminformation": { - "version": "4.26.10", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-4.26.10.tgz", - "integrity": "sha512-bO4FIzrjESAfh4KHwkUJym3jvKtJ4oJ2PG0BBQGBmKa0pF2oanpkB7CF4ZsSX7vfp3+GKaLzioVwpV/3Tyk+lQ==" + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-4.27.3.tgz", + "integrity": "sha512-0Nc8AYEK818h7FI+bbe/kj7xXsMD5zOHvO9alUqQH/G4MHXu5tHQfWqC/bzWOk4JtoQPhnyLgxMYncDA2eeSBw==" + }, + "tar-fs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", + "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "tar-stream": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", + "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } }, "through": { "version": "2.3.8", @@ -1205,6 +1546,11 @@ "is-number": "^7.0.0" } }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -1238,15 +1584,43 @@ "version": "github:uNetworking/uWebSockets.js#bec5db11465aa6ddd3f5d2b098fb3180a7639ccb", "from": "github:uNetworking/uWebSockets.js#binaries" }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + }, + "dependencies": { + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + } + } + }, "undici": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-1.3.0.tgz", - "integrity": "sha512-JE5HzCCsZQzfTtgAVY9LUipmRv3/L+yzcTN6S3DSJWxk1E3RIgyVCIkYv9qV7B3OWu515wci37hhOLllVvGCyA==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-1.3.1.tgz", + "integrity": "sha512-NX7FzPHOrsIEHv3PDlVX4wzN1gB7WlCUzkcL2Y3rrEE4vKrlJ2Oyvn+QdACR5qpeCI7j3XtfDirGuZlkL2JDDw==" }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", "requires": { "punycode": "^2.1.0" }, @@ -1320,6 +1694,15 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } } } } diff --git a/package.json b/package.json index e813bf7..e6d6eb0 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,10 @@ }, "homepage": "https://github.com/elestio/ws-trail#readme", "dependencies": { - "@elestio/cloudgate": "^1.0.52", + "@elestio/cloudgate": "^1.0.60", "jsonwebtoken": "^8.5.1", "pg": "^8.3.0", + "puppeteer": "^5.3.0", "uWebSockets.js": "github:uNetworking/uWebSockets.js#binaries" } } diff --git a/public/client.js b/public/client.js index fb755f2..a6fca71 100644 --- a/public/client.js +++ b/public/client.js @@ -12,14 +12,10 @@ function handler() { } function connect() { - var jwt = ""; - if ( _wsTrail_jwt != "" ){ - jwt = _wsTrail_jwt; - } - + var protocolPrefix = (window.location.protocol === 'https:') ? 'wss:' : 'ws:'; var rootURL = protocolPrefix + '//' + location.host; - var ws = new WebSocket(rootURL + '/ws/monitor?jwt=' + jwt); + var ws = new WebSocket(rootURL + '/ws/screenshot'); var nbConnectRetry = 5; globalWS = ws; ws.onopen = function() { @@ -35,8 +31,17 @@ function connect() { var msg = e.data; //console.log('Message:', msg); - $(_wsTrail_logSelector).append( sanitizeHTML(msg) + "
"); - $('body').scrollTop($('body').height()); + var obj = JSON.parse(msg); + if ( obj.cmd == "responseScreenshot" ){ + $("#resultImg").attr("src", "data:image/jpeg;base64, " + obj.data); + $("#stats").html("Processing time: " + obj.execTime + " - Roundtrip: " + ( (+new Date()) - obj.originalTS) + "ms" ); + $("#resultImg").show(); + } + else{ + $(_wsTrail_logSelector).append( sanitizeHTML(msg) + "
"); + $('body').scrollTop($('body').height()); + } + }; ws.onclose = function(e) { @@ -54,11 +59,11 @@ function connect() { } var sendQueue = []; -function SendEvent(eventType, eventTitle, eventValue, eventData){ - - var event = { cmd: "event", uid: _wsTrail_uid, ctx: _wsTrail_ctx, type: eventType, - title: eventTitle, val: eventValue, data: eventData, wlid: _wsTrail_wlid }; - Send(event); +function AskForScreenshot(url){ + $("#resultImg").hide(); + $("#stats").html("Please wait ..."); + var event = { cmd: "screenshot", url: url, originalTS: (+new Date()) }; + Send(event); } diff --git a/public/index.html b/public/index.html index 691fde5..7e19339 100644 --- a/public/index.html +++ b/public/index.html @@ -15,10 +15,15 @@

WS-Screenshot test page

- -
+ + + + +

- +
+ +