import * as url from 'url' ; import * as path from 'path' ; import { Protocol as Crdp } from 'devtools-protocol' ; import { logger } from 'vscode-debugadapter' ; import * as utils from '../utils' ; import { ITarget } from './chromeConnection' ; import { IPathMapping } from '../debugAdapterInterfaces' ; import { pathToRegex } from '../utils' ; import { isInternalRemotePath } from '../remoteMapper' ; import { Socket } from 'net' ; import * as errors from '../errors' ; /**
 * Takes the path component of a target url (starting with '/') and applies pathMapping
 */ export function applyPathMappingsToTargetUrlPath ( scriptUrlPath , pathMapping ) { if ( ! pathMapping ) { return '' ; } if ( ! scriptUrlPath || ! scriptUrlPath . startsWith ( '/' ) ) { return '' ; } const mappingKeys = Object . keys ( pathMapping ) . sort ( ( a , b ) => b . length - a . length ) ; for ( let pattern of mappingKeys ) { if ( ! pattern ) { continue ; } const mappingRHS = pathMapping [ pattern ] ; if ( pattern [ 0 ] !== '/' ) { logger . log ( `PathMapping keys should be absolute: ${ pattern }` ) ; pattern = '/' + pattern ; } if ( pathMappingPatternMatchesPath ( pattern , scriptUrlPath ) ) { return toClientPath ( pattern , mappingRHS , scriptUrlPath ) ; } } return '' ; } function pathMappingPatternMatchesPath ( pattern , scriptPath ) { if ( pattern === scriptPath ) { return true ; } if ( ! pattern . endsWith ( '/' ) ) { pattern += '/' ; } return scriptPath . startsWith ( pattern ) ; } export function applyPathMappingsToTargetUrl ( scriptUrl , pathMapping ) { const parsedUrl = url . parse ( scriptUrl ) ; if ( ! parsedUrl . protocol || parsedUrl . protocol . startsWith ( 'file' ) || ! parsedUrl . pathname ) { return '' ; } return applyPathMappingsToTargetUrlPath ( parsedUrl . pathname , pathMapping ) ; } function toClientPath ( pattern , mappingRHS , scriptPath ) { const rest = decodeURIComponent ( scriptPath . substring ( pattern . length ) ) ; const mappedResult = rest ? utils . properJoin ( mappingRHS , rest ) : mappingRHS ; return mappedResult ; } /**
 * Maps a url from target to an absolute local path, if it exists.
 * If not given an absolute path (with file: prefix), searches the current working directory for a matching file.
 * http://localhost/scripts/code.js => d:/app/scripts/code.js
 * file:///d:/scripts/code.js => d:/scripts/code.js
 */ export async function targetUrlToClientPath ( aUrl , pathMapping ) { if ( ! aUrl ) { return '' ; } const canonicalUrl = utils . canonicalizeUrl ( aUrl ) ; if ( utils . isFileUrl ( aUrl ) ) { if ( await utils . exists ( canonicalUrl ) ) { return canonicalUrl ; } const networkPath = utils . fileUrlToNetworkPath ( aUrl ) ; if ( networkPath !== aUrl && await utils . exists ( networkPath ) ) { return networkPath ; } } let pathName = url . parse ( canonicalUrl ) . pathname ; if ( ! pathName || pathName === '/' ) { return '' ; } const pathParts = pathName . replace ( /^\// , '' ) . split ( /[\/\\]/ ) ; while ( pathParts . length > 0 ) { const joinedPath = '/' + pathParts . join ( '/' ) ; const clientPath = applyPathMappingsToTargetUrlPath ( joinedPath , pathMapping ) ; if ( isInternalRemotePath ( clientPath ) ) { return clientPath ; } else if ( clientPath && await utils . exists ( clientPath ) ) { return utils . canonicalizeUrl ( clientPath ) ; } pathParts . shift ( ) ; } return '' ; } /**
 * Convert a RemoteObject to a value+variableHandleRef for the client.
 * TODO - Delete after Microsoft/vscode#12019!!
 */ export function remoteObjectToValue ( object , stringify = true ) { let value = '' ; let variableHandleRef ; if ( object ) { if ( object . type === 'object' ) { if ( object . subtype === 'null' ) { value = 'null' ; } else { variableHandleRef = object . objectId ; value = object . description ; } } else if ( object . type === 'undefined' ) { value = 'undefined' ; } else if ( object . type === 'function' ) { const firstBraceIdx = object . description . indexOf ( '{' ) ; if ( firstBraceIdx >= 0 ) { value = object . description . substring ( 0 , firstBraceIdx ) + '{ … }' ; } else { const firstArrowIdx = object . description . indexOf ( '=>' ) ; value = firstArrowIdx >= 0 ? object . description . substring ( 0 , firstArrowIdx + 2 ) + ' …' : object . description ; } } else { if ( typeof object . value === 'undefined' ) { value = object . description ; } else if ( object . type === 'number' ) { value = object . description ; } else { value = stringify ? JSON . stringify ( object . value ) : object . value ; } } } return { value , variableHandleRef } ; } /**
 * Returns the targets from the given list that match the targetUrl, which may have * wildcards.
 * Ignores the protocol and is case-insensitive.
 */ export function getMatchingTargets ( targets , targetUrlPattern ) { const standardizeMatch = ( aUrl ) => { aUrl = aUrl . toLowerCase ( ) ; if ( utils . isFileUrl ( aUrl ) ) { aUrl = utils . fileUrlToPath ( aUrl ) ; } else if ( utils . isURL ( aUrl ) && aUrl . indexOf ( '://' ) >= 0 ) { aUrl = aUrl . substr ( aUrl . indexOf ( '://' ) + 3 ) ; } if ( aUrl . endsWith ( '/' ) ) aUrl = aUrl . substr ( 0 , aUrl . length - 1 ) ; return aUrl ; } ; targetUrlPattern = standardizeMatch ( targetUrlPattern ) ; targetUrlPattern = utils . escapeRegexSpecialChars ( targetUrlPattern , '/*' ) . replace ( /\*/g , '.*' ) ; const targetUrlRegex = new RegExp ( '^' + targetUrlPattern + '$' , 'g' ) ; return targets . filter ( target => ! ! standardizeMatch ( target . url ) . match ( targetUrlRegex ) ) ; } const PROTO_NAME = '__proto__' ; const NUM_REGEX = /^[0-9]+$/ ; export function compareVariableNames ( var1 , var2 ) { if ( var1 === PROTO_NAME ) { return 1 ; } else if ( var2 === PROTO_NAME ) { return - 1 ; } const isNum1 = ! ! var1 . match ( NUM_REGEX ) ; const isNum2 = ! ! var2 . match ( NUM_REGEX ) ; if ( isNum1 && ! isNum2 ) { return 1 ; } else if ( ! isNum1 && isNum2 ) { return - 1 ; } else if ( isNum1 && isNum2 ) { const int1 = parseInt ( var1 , 10 ) ; const int2 = parseInt ( var2 , 10 ) ; return int1 - int2 ; } return var1 . localeCompare ( var2 ) ; } export function remoteObjectToCallArgument ( object ) { return { objectId : object . objectId , unserializableValue : object . unserializableValue , value : object . value } ; } /**
 * .exception is not present in Node < 6.6 - TODO this would be part of a generic solution for handling
 * protocol differences in the future.
 * This includes the error message and full stack
 */ export function descriptionFromExceptionDetails ( exceptionDetails ) { let description ; if ( exceptionDetails . exception ) { description = exceptionDetails . exception . description || 'Error: ' + exceptionDetails . exception . value ; } else { description = exceptionDetails . text ; } return description || '' ; } /**
 * Get just the error message from the exception details - the first line without the full stack
 */ export function errorMessageFromExceptionDetails ( exceptionDetails ) { let description = descriptionFromExceptionDetails ( exceptionDetails ) ; const newlineIdx = description . indexOf ( '\n' ) ; if ( newlineIdx >= 0 ) { description = description . substr ( 0 , newlineIdx ) ; } return description ; } export function getEvaluateName ( parentEvaluateName , name ) { if ( ! parentEvaluateName ) return name ; let nameAccessor ; if ( /^[a-zA-Z_$][a-zA-Z_$0-9]*$/ . test ( name ) ) { nameAccessor = '.' + name ; } else if ( /^\d+$/ . test ( name ) ) { nameAccessor = `[${ name }]` ; } else { nameAccessor = `[${ JSON . stringify ( name ) }]` ; } return parentEvaluateName + nameAccessor ; } export function selectBreakpointLocation ( lineNumber , columnNumber , locations ) { for ( let i = locations . length - 1 ; i >= 0 ; i -- ) { if ( locations [ i ] . columnNumber <= columnNumber ) { return locations [ i ] ; } } return locations [ 0 ] ; } export const EVAL_NAME_PREFIX = 'VM' ; export function isEvalScript ( scriptPath ) { return scriptPath . startsWith ( EVAL_NAME_PREFIX ) ; } export function getUrlRegexForBreakOnLoad ( url ) { const fileNameWithoutFullPath = path . parse ( url ) . base ; const fileNameWithoutExtension = path . parse ( fileNameWithoutFullPath ) . name ; const escapedFileName = pathToRegex ( fileNameWithoutExtension ) ; return '.*[\\\\\\/]' + escapedFileName + '([^A-z^0-9].*)?$' ; } param port param host param timeout returns export async function isPortInUse ( port , host = '127.0.0.1' , timeout = 400 ) { return new Promise < boolean > ( ( resolve , _reject ) => { const socket = new Socket ( ) ; function createCallback ( inUse ) { return ( ) => { resolve ( inUse ) ; socket . removeAllListeners ( ) ; socket . destroy ( ) ; } ; } socket . setTimeout ( timeout ) ; socket . on ( 'connect' , createCallback ( true ) ) ; socket . on ( 'timeout' , createCallback ( false ) ) ; socket . on ( 'error' , createCallback ( false ) ) ; socket . connect ( port , host ) ; } ) ; } param userDataDir throws export async function getLaunchedPort ( userDataDir ) { const activePortFilePath = path . join ( userDataDir , 'DevToolsActivePort' ) ; try { const activePortArgs = await utils . readFileP ( activePortFilePath , 'utf-8' ) ; const [ portStr ] = activePortArgs . split ( '\n' ) ; const port = parseInt ( portStr , 10 ) ; if ( isNaN ( port ) ) return Promise . reject ( errors . activePortFileContentsInvalid ( activePortFilePath , activePortArgs ) ) ; return port ; } catch ( err ) { return Promise . reject ( errors . failedToReadPortFromUserDataDir ( userDataDir , err ) ) ; } }