Andrew Top | 286dd78 | 2018-10-02 16:52:45 -0700 | [diff] [blame] | 1 | // Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | var fs = require('fs'); |
| 5 | var http = require('http'); |
| 6 | var path = require('path'); |
| 7 | var parseURL = require('url').parse; |
| 8 | |
| 9 | var utils = require('../utils'); |
| 10 | |
| 11 | const remoteDebuggingPort = parseInt(process.env.REMOTE_DEBUGGING_PORT, 10) || 9222; |
| 12 | const serverPort = parseInt(process.env.PORT, 10) || 8090; |
| 13 | const localProtocolPath = process.env.LOCAL_PROTOCOL_PATH; |
| 14 | const devtoolsFolder = path.resolve(path.join(__dirname, '../..')); |
| 15 | |
| 16 | http.createServer(requestHandler).listen(serverPort); |
| 17 | console.log(`Started hosted mode server at http://localhost:${serverPort}\n`); |
| 18 | console.log('For info on using the hosted mode server, see our contributing docs:'); |
| 19 | console.log('https://bit.ly/devtools-contribution-guide'); |
| 20 | console.log('Tip: Look for the \'Development server options\' section\n'); |
| 21 | |
| 22 | function requestHandler(request, response) { |
| 23 | var filePath = parseURL(request.url).pathname; |
| 24 | if (filePath === '/') { |
| 25 | var landingURL = `http://localhost:${remoteDebuggingPort}#custom=true&experiments=true`; |
| 26 | sendResponse(200, `<html>Please go to <a href="${landingURL}">${landingURL}</a></html>`); |
| 27 | return; |
| 28 | } |
| 29 | |
| 30 | var proxiedFile = proxy(filePath, sendResponse); |
| 31 | if (proxiedFile) { |
| 32 | proxiedFile.then(data => sendResponse(200, data)).catch(handleProxyError); |
| 33 | return; |
| 34 | } |
| 35 | |
| 36 | function handleProxyError(err) { |
| 37 | console.log(`Error serving the file ${filePath}:`, err); |
| 38 | console.log(`Make sure you opened Chrome with the flag "--remote-debugging-port=${remoteDebuggingPort}"`); |
| 39 | sendResponse(500, '500 - Internal Server Error'); |
| 40 | } |
| 41 | |
| 42 | var absoluteFilePath = path.join(process.cwd(), filePath); |
| 43 | if (!path.resolve(absoluteFilePath).startsWith(devtoolsFolder)) { |
| 44 | console.log(`File requested is outside of devtools folder: ${devtoolsFolder}`); |
| 45 | sendResponse(403, `403 - Access denied. File requested is outside of devtools folder: ${devtoolsFolder}`); |
| 46 | return; |
| 47 | } |
| 48 | |
| 49 | fs.exists(absoluteFilePath, fsExistsCallback); |
| 50 | |
| 51 | function fsExistsCallback(fileExists) { |
| 52 | if (!fileExists) { |
| 53 | console.log(`Cannot find file ${absoluteFilePath}`); |
| 54 | sendResponse(404, '404 - File not found'); |
| 55 | return; |
| 56 | } |
| 57 | fs.readFile(absoluteFilePath, 'binary', readFileCallback); |
| 58 | } |
| 59 | |
| 60 | function readFileCallback(err, file) { |
| 61 | if (err) { |
| 62 | console.log(`Unable to read local file ${absoluteFilePath}:`, err); |
| 63 | sendResponse(500, '500 - Internal Server Error'); |
| 64 | return; |
| 65 | } |
| 66 | sendResponse(200, file); |
| 67 | } |
| 68 | |
| 69 | function sendResponse(statusCode, data) { |
Kaido Kert | f585e26 | 2020-06-08 11:42:28 -0700 | [diff] [blame] | 70 | if (request.url.endsWith('.js')) { |
| 71 | response.setHeader('Content-Type', 'text/javascript'); |
| 72 | } |
| 73 | |
Andrew Top | 286dd78 | 2018-10-02 16:52:45 -0700 | [diff] [blame] | 74 | response.writeHead(statusCode); |
| 75 | response.write(data, 'binary'); |
| 76 | response.end(); |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | var proxyFilePathToURL = { |
| 81 | '/front_end/SupportedCSSProperties.js': cloudURL.bind(null, 'SupportedCSSProperties.js'), |
| 82 | '/front_end/InspectorBackendCommands.js': cloudURL.bind(null, 'InspectorBackendCommands.js'), |
| 83 | '/favicon.ico': () => 'https://chrome-devtools-frontend.appspot.com/favicon.ico', |
| 84 | '/front_end/accessibility/ARIAProperties.js': cloudURL.bind(null, 'accessibility/ARIAProperties.js'), |
| 85 | }; |
| 86 | |
| 87 | function cloudURL(path, commitHash) { |
| 88 | return `https://chrome-devtools-frontend.appspot.com/serve_file/@${commitHash}/${path}`; |
| 89 | } |
| 90 | |
| 91 | var proxyFileCache = new Map(); |
| 92 | |
| 93 | function proxy(filePath) { |
| 94 | if (!(filePath in proxyFilePathToURL)) |
| 95 | return null; |
| 96 | if (localProtocolPath && filePath === '/front_end/InspectorBackendCommands.js') |
| 97 | return serveLocalProtocolFile(); |
| 98 | if (process.env.CHROMIUM_COMMIT) |
| 99 | return onProxyFileURL(proxyFilePathToURL[filePath](process.env.CHROMIUM_COMMIT)); |
| 100 | return utils.fetch(`http://localhost:${remoteDebuggingPort}/json/version`) |
| 101 | .then(onBrowserMetadata) |
| 102 | .then(onProxyFileURL); |
| 103 | |
| 104 | function serveLocalProtocolFile() { |
| 105 | return new Promise((resolve, reject) => { |
| 106 | fs.exists(localProtocolPath, fsExistsCallback); |
| 107 | function fsExistsCallback(fileExists) { |
| 108 | if (!fileExists) { |
| 109 | reject(new Error(`Cannot find local protocol file ${localProtocolPath}`)); |
| 110 | return; |
| 111 | } |
| 112 | fs.readFile(localProtocolPath, 'binary', readFileCallback); |
| 113 | } |
| 114 | function readFileCallback(err, file) { |
| 115 | if (err) { |
| 116 | reject(new Error(`Unable to read local protocol file ${localProtocolPath}`)); |
| 117 | return; |
| 118 | } |
| 119 | return resolve(file); |
| 120 | } |
| 121 | }); |
| 122 | } |
| 123 | |
| 124 | function onBrowserMetadata(metadata) { |
| 125 | var metadataObject = JSON.parse(metadata); |
| 126 | var match = metadataObject['WebKit-Version'].match(/\s\(@(\b[0-9a-f]{5,40}\b)/); |
| 127 | var commitHash = match[1]; |
| 128 | var proxyFileURL = proxyFilePathToURL[filePath](commitHash); |
| 129 | return proxyFileURL; |
| 130 | } |
| 131 | |
| 132 | function onProxyFileURL(proxyFileURL) { |
| 133 | if (proxyFileCache.has(proxyFileURL)) |
| 134 | return Promise.resolve(proxyFileCache.get(proxyFileURL)); |
| 135 | return utils.fetch(proxyFileURL).then(cacheProxyFile.bind(null, proxyFileURL)).catch(onMissingFile); |
| 136 | } |
| 137 | |
| 138 | function onMissingFile() { |
| 139 | var isFullCheckout = utils.shellOutput('git config --get remote.origin.url') === |
| 140 | 'https://chromium.googlesource.com/chromium/src.git'; |
| 141 | var earlierCommitHash; |
| 142 | var gitLogCommand = `git log --max-count=1 --grep="Commit-Position" --before="12 hours ago"`; |
| 143 | if (isFullCheckout) { |
| 144 | earlierCommitHash = utils.shellOutput(`${gitLogCommand} --pretty=format:"%H"`); |
| 145 | } else { |
| 146 | var commitMessage = utils.shellOutput(`${gitLogCommand}`); |
| 147 | earlierCommitHash = commitMessage.match(/Cr-Mirrored-Commit: (.*)/)[1]; |
| 148 | } |
| 149 | var fallbackURL = proxyFilePathToURL[filePath](earlierCommitHash); |
| 150 | console.log('WARNING: Unable to fetch generated file based on browser\'s revision'); |
| 151 | console.log('Fetching earlier revision of same file as fallback'); |
| 152 | console.log('There may be a mismatch between the front-end and back-end'); |
| 153 | console.log(fallbackURL, '\n'); |
| 154 | return utils.fetch(fallbackURL).then(cacheProxyFile.bind(null, fallbackURL)); |
| 155 | } |
| 156 | |
| 157 | function cacheProxyFile(proxyFileURL, data) { |
| 158 | proxyFileCache.set(proxyFileURL, data); |
| 159 | return data; |
| 160 | } |
| 161 | } |