| // 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; | |
| } | |
| } |