| // Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| var fs = require('fs'); |
| var http = require('http'); |
| var path = require('path'); |
| var parseURL = require('url').parse; |
| |
| var utils = require('../utils'); |
| |
| const remoteDebuggingPort = parseInt(process.env.REMOTE_DEBUGGING_PORT, 10) || 9222; |
| const serverPort = parseInt(process.env.PORT, 10) || 8090; |
| const localProtocolPath = process.env.LOCAL_PROTOCOL_PATH; |
| const devtoolsFolder = path.resolve(path.join(__dirname, '../..')); |
| |
| http.createServer(requestHandler).listen(serverPort); |
| console.log(`Started hosted mode server at http://localhost:${serverPort}\n`); |
| console.log('For info on using the hosted mode server, see our contributing docs:'); |
| console.log('https://bit.ly/devtools-contribution-guide'); |
| console.log('Tip: Look for the \'Development server options\' section\n'); |
| |
| function requestHandler(request, response) { |
| var filePath = parseURL(request.url).pathname; |
| if (filePath === '/') { |
| var landingURL = `http://localhost:${remoteDebuggingPort}#custom=true&experiments=true`; |
| sendResponse(200, `<html>Please go to <a href="${landingURL}">${landingURL}</a></html>`); |
| return; |
| } |
| |
| var proxiedFile = proxy(filePath, sendResponse); |
| if (proxiedFile) { |
| proxiedFile.then(data => sendResponse(200, data)).catch(handleProxyError); |
| return; |
| } |
| |
| function handleProxyError(err) { |
| console.log(`Error serving the file ${filePath}:`, err); |
| console.log(`Make sure you opened Chrome with the flag "--remote-debugging-port=${remoteDebuggingPort}"`); |
| sendResponse(500, '500 - Internal Server Error'); |
| } |
| |
| var absoluteFilePath = path.join(process.cwd(), filePath); |
| if (!path.resolve(absoluteFilePath).startsWith(devtoolsFolder)) { |
| console.log(`File requested is outside of devtools folder: ${devtoolsFolder}`); |
| sendResponse(403, `403 - Access denied. File requested is outside of devtools folder: ${devtoolsFolder}`); |
| return; |
| } |
| |
| fs.exists(absoluteFilePath, fsExistsCallback); |
| |
| function fsExistsCallback(fileExists) { |
| if (!fileExists) { |
| console.log(`Cannot find file ${absoluteFilePath}`); |
| sendResponse(404, '404 - File not found'); |
| return; |
| } |
| fs.readFile(absoluteFilePath, 'binary', readFileCallback); |
| } |
| |
| function readFileCallback(err, file) { |
| if (err) { |
| console.log(`Unable to read local file ${absoluteFilePath}:`, err); |
| sendResponse(500, '500 - Internal Server Error'); |
| return; |
| } |
| sendResponse(200, file); |
| } |
| |
| function sendResponse(statusCode, data) { |
| if (request.url.endsWith('.js')) { |
| response.setHeader('Content-Type', 'text/javascript'); |
| } |
| |
| response.writeHead(statusCode); |
| response.write(data, 'binary'); |
| response.end(); |
| } |
| } |
| |
| var proxyFilePathToURL = { |
| '/front_end/SupportedCSSProperties.js': cloudURL.bind(null, 'SupportedCSSProperties.js'), |
| '/front_end/InspectorBackendCommands.js': cloudURL.bind(null, 'InspectorBackendCommands.js'), |
| '/favicon.ico': () => 'https://chrome-devtools-frontend.appspot.com/favicon.ico', |
| '/front_end/accessibility/ARIAProperties.js': cloudURL.bind(null, 'accessibility/ARIAProperties.js'), |
| }; |
| |
| function cloudURL(path, commitHash) { |
| return `https://chrome-devtools-frontend.appspot.com/serve_file/@${commitHash}/${path}`; |
| } |
| |
| var proxyFileCache = new Map(); |
| |
| function proxy(filePath) { |
| if (!(filePath in proxyFilePathToURL)) |
| return null; |
| if (localProtocolPath && filePath === '/front_end/InspectorBackendCommands.js') |
| return serveLocalProtocolFile(); |
| if (process.env.CHROMIUM_COMMIT) |
| return onProxyFileURL(proxyFilePathToURL[filePath](process.env.CHROMIUM_COMMIT)); |
| return utils.fetch(`http://localhost:${remoteDebuggingPort}/json/version`) |
| .then(onBrowserMetadata) |
| .then(onProxyFileURL); |
| |
| function serveLocalProtocolFile() { |
| return new Promise((resolve, reject) => { |
| fs.exists(localProtocolPath, fsExistsCallback); |
| function fsExistsCallback(fileExists) { |
| if (!fileExists) { |
| reject(new Error(`Cannot find local protocol file ${localProtocolPath}`)); |
| return; |
| } |
| fs.readFile(localProtocolPath, 'binary', readFileCallback); |
| } |
| function readFileCallback(err, file) { |
| if (err) { |
| reject(new Error(`Unable to read local protocol file ${localProtocolPath}`)); |
| return; |
| } |
| return resolve(file); |
| } |
| }); |
| } |
| |
| function onBrowserMetadata(metadata) { |
| var metadataObject = JSON.parse(metadata); |
| var match = metadataObject['WebKit-Version'].match(/\s\(@(\b[0-9a-f]{5,40}\b)/); |
| var commitHash = match[1]; |
| var proxyFileURL = proxyFilePathToURL[filePath](commitHash); |
| return proxyFileURL; |
| } |
| |
| function onProxyFileURL(proxyFileURL) { |
| if (proxyFileCache.has(proxyFileURL)) |
| return Promise.resolve(proxyFileCache.get(proxyFileURL)); |
| return utils.fetch(proxyFileURL).then(cacheProxyFile.bind(null, proxyFileURL)).catch(onMissingFile); |
| } |
| |
| function onMissingFile() { |
| var isFullCheckout = utils.shellOutput('git config --get remote.origin.url') === |
| 'https://chromium.googlesource.com/chromium/src.git'; |
| var earlierCommitHash; |
| var gitLogCommand = `git log --max-count=1 --grep="Commit-Position" --before="12 hours ago"`; |
| if (isFullCheckout) { |
| earlierCommitHash = utils.shellOutput(`${gitLogCommand} --pretty=format:"%H"`); |
| } else { |
| var commitMessage = utils.shellOutput(`${gitLogCommand}`); |
| earlierCommitHash = commitMessage.match(/Cr-Mirrored-Commit: (.*)/)[1]; |
| } |
| var fallbackURL = proxyFilePathToURL[filePath](earlierCommitHash); |
| console.log('WARNING: Unable to fetch generated file based on browser\'s revision'); |
| console.log('Fetching earlier revision of same file as fallback'); |
| console.log('There may be a mismatch between the front-end and back-end'); |
| console.log(fallbackURL, '\n'); |
| return utils.fetch(fallbackURL).then(cacheProxyFile.bind(null, fallbackURL)); |
| } |
| |
| function cacheProxyFile(proxyFileURL, data) { |
| proxyFileCache.set(proxyFileURL, data); |
| return data; |
| } |
| } |