UNPKG

bbpro

Version:

BrowserBox - remote browser isolation tool

312 lines (292 loc) 9.98 kB
import {DEBUG, logit} from './common.js'; export const getKeyId = event => event.key && event.key.length > 1 ? event.key : event.code; export const controlChars = new Set([ "Enter", "Backspace", "Control", "Shift", "Alt", "Meta", "Space", "Delete", "ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Tab" ]); export default function transformEvent(e, {scale}, bounds) { const transformedEvent = { type: e.type, forceFrame: e.forceFrame }; const {source} = e; if ( !! source ) { transformedEvent.source = source; } let event, synthetic, originalEvent; if (e.synthetic) { synthetic = e; originalEvent = e.event; let form; if ( originalEvent ) { form = originalEvent.target && originalEvent.target.matches && originalEvent.target.matches('form') ? originalEvent.target : null; transformedEvent.originalType = originalEvent.type; transformedEvent.originalEvent = originalEvent; } switch(synthetic.type) { case "favicon": { throw new TypeError(`Client can not request favicons from back-end`); const {targetId} = synthetic; Object.assign(transformedEvent, {targetId}); break; } case "auth-response": { const {authResponse, requestId} = synthetic; Object.assign(transformedEvent, {authResponse, requestId}); break; } case "typing": { // get (composed) characters created let data = synthetic.data; Object.assign(transformedEvent, { characters: data }); break; } case "typing-syncValue": case "typing-clearAndInsertValue": { const {value,contextId} = synthetic; let encodedValue; if ( value != null && value != undefined ) { encodedValue = btoa(unescape(encodeURIComponent(value))); } Object.assign(transformedEvent, { encodedValue, value, contextId }); break; } case "typing-deleteContentBackward": { let encodedValueToDelete; if ( synthetic.valueToDelete ) { encodedValueToDelete = btoa(unescape(encodeURIComponent(synthetic.valueToDelete))); } Object.assign(transformedEvent, { encodedValueToDelete, contextId: synthetic.contextId }); break; } case "url-address": { // get URL address const address = synthetic.url; Object.assign(transformedEvent, {address}); break; } case "search-bar": { // get URL address const search = originalEvent.target.search.value; Object.assign(transformedEvent, {search}); break; } case "history": { // get button const action = form.clickedButton.value; Object.assign(transformedEvent, { action } ); break; } case "touchscroll": { const {deltaX, deltaY, bitmapX, bitmapY, contextId} = synthetic; Object.assign(transformedEvent, {deltaX, deltaY, bitmapX, bitmapY, contextId}); break; } case "zoom": { const {scale} = synthetic; const coords = getBitmapCoordinates(originalEvent, scale, bounds); Object.assign(transformedEvent, coords, {scale}); break; } case "select": { const value = originalEvent.target.value; const executionContext = synthetic.state.waitingExecutionContext; Object.assign(transformedEvent, {value, executionContext}); break; } case "activate-target": { const {targetId, source} = synthetic; DEBUG.val > DEBUG.med && console.log({activateTarget:{targetId}}); Object.assign(transformedEvent, {targetId, source}); break; } case "window-bounds": { const {forceFrame, resetRequested, width, height, targetId} = synthetic; DEBUG.val > DEBUG.med && console.log({windowBounds:{width,height,targetId}}); Object.assign(transformedEvent, {width, height,targetId, resetRequested, forceFrame}); break; } case "window-bounds-preImplementation": { // This is here until Browser.getWindowForTarget and Browser.setWindowBounds come online let width, height; if ( synthetic.width !== undefined && synthetic.height !== undefined ) { ({width,height} = synthetic); } else { ({width:{value:width}, height:{value:height}} = form); } const {targetId, forceFrame, resetRequested} = synthetic; Object.assign(transformedEvent, {width, height, targetId, resetRequested, forceFrame}); break; } case "user-agent": { const {userAgent, platform, acceptLanguage} = synthetic; Object.assign(transformedEvent, {userAgent, platform, acceptLanguage}); break; } case "hide-scrollbars": { break; } case "canKeysInput": { break; } case "getFavicon": { throw new TypeError(`Client can not request favicons from back-end`); const {targetId} = synthetic; Object.assign(transformedEvent, {targetId}); break; } case "getElementInfo": { transformedEvent.data = e.data; const {bitmapX:clientX, bitmapY:clientY} = getBitmapCoordinates(transformedEvent.data, scale, bounds); Object.assign(transformedEvent.data, {clientX,clientY}); DEBUG.debugCopyPaste && console.log({clientX,clientY}); break; } case "touchcancel": { break; } case "respond-to-modal" : { DEBUG.debugModal && console.log("Respond to modal", e); Object.assign(transformedEvent, e); break; } case "isSafari": { break; } case "isMobile": { break; } case "isFirefox": { break; } case "newIncognitoTab": { const address = synthetic.url || 'about:blank'; Object.assign(transformedEvent, {address}); break; } case "clearAllPageHistory": { break; } case "clearCache": { break; } case "clearCookies": { break; } default: { console.warn(`Unknown command ${JSON.stringify({synthetic})}`); break; } } } else if (e.raw || e.custom) { Object.assign(transformedEvent, e); } else { event = e; transformedEvent.originalEvent = e; switch(event.type) { case "keypress": case "keydown": case "keyup": { const id = getKeyId(event); if ( controlChars.has(id) ) { event.type == "keypress" && event.preventDefault && event.preventDefault(); transformedEvent.synthetic = true; transformedEvent.originalType = event.type; transformedEvent.type = "control-chars"; transformedEvent.key = event.key; transformedEvent.code = event.code; transformedEvent.keyCode = event.keyCode; DEBUG.val >= DEBUG.med && console.log(transformedEvent); } else if ( event.code == "Unidentified" || event.key == "Unidentified" ) { transformedEvent.key = event.key; transformedEvent.code = event.code; } else { transformedEvent.synthetic = true; transformedEvent.originalType = event.type; transformedEvent.type = event.type; transformedEvent.key = event.key; transformedEvent.code = event.code; transformedEvent.keyCode = event.keyCode; } break; } case "wheel": case "mousemove": case "mousedown": case "mouseup": case "pointermove": case "pointerdown": case "pointerup": { // get relevant X, Y coordinates and element under point // also get any relevant touch points and pressures and other associated // pointer or touch metadata or properties const {button} = event; const coords = getBitmapCoordinates(event, scale, bounds); Object.assign(transformedEvent, coords, {button}); break; } case "touchstart": case "touchmove": case "touchend": { const {touches} = event; const touchPoints = Array.from(touches).map(touch => { const {rotationAngle, radiusX, radiusY, force} = touch; const {clientX,clientY} = getBitmapCoordinates(touch, scale, bounds); return { id: touch.identifier, x: clientX, y: clientY, radiusX, radiusY, rotationAngle, force } }); Object.assign(transformedEvent, {touchPoints}); break; } } } DEBUG.val >= DEBUG.med && console.log(transformedEvent); return transformedEvent; } export function getBitmapCoordinates(event, scale = 1, bounds) { const {clientX,clientY} = event; const bitmap = event.target; let coordinates; DEBUG.debugBitmapCoordConversion && console.log({clientX, clientY, bitmap, event}); if ( bitmap ) { const { left:parentX, top:parentY, width:bitMapWidth, height: bitMapHeight, } = bitmap.getBoundingClientRect(); const localViewport = { x: (clientX - parentX)/scale, y: (clientY - parentY)/scale } const remoteViewport = { x: Math.max(0,Math.min(localViewport.x, bounds.x)), y: Math.max(0,Math.min(localViewport.y, bounds.y)) } DEBUG.debugBitmapCoordConversion && console.log({localViewport, remoteViewport, scale}); coordinates = { bitmapX: remoteViewport.x, bitmapY: remoteViewport.y }; if ( DEBUG.val > DEBUG.high ) { const info = {coordinates, parentX, parentY, clientX, clientY, scaleX, scaleY, dpi }; logit(info); } } else { coordinates = {bitmapX: clientX,bitmapY: clientY, clientX, clientY}; } return coordinates; }