├── images
├── logo128.png
├── logo16.png
├── logo32.png
└── logo48.png
├── .github
└── FUNDING.yml
├── js
├── load.js
├── main.js
├── settings.js
└── common.js
├── .gitignore
├── README.md
├── index.html
├── manifest.json
├── LICENSE
├── settings.html
└── css
└── style.css
/images/logo128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cjihrig/node-v8-inspector/HEAD/images/logo128.png
--------------------------------------------------------------------------------
/images/logo16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cjihrig/node-v8-inspector/HEAD/images/logo16.png
--------------------------------------------------------------------------------
/images/logo32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cjihrig/node-v8-inspector/HEAD/images/logo32.png
--------------------------------------------------------------------------------
/images/logo48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cjihrig/node-v8-inspector/HEAD/images/logo48.png
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [cjihrig]
4 | patreon: cjihrig
5 | custom: https://www.paypal.me/cjihrig/5
6 |
--------------------------------------------------------------------------------
/js/load.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | setBrowserClickAction();
4 |
5 |
6 | function launch (options) {
7 | launchDevTools(options)
8 | .catch(function errorHandler (err) {
9 | alert(`Could not launch debugger.\n${err.message}`);
10 | });
11 | }
12 |
13 |
14 | chrome.runtime.onMessage.addListener(launch);
15 |
16 |
17 | chrome.browserAction.onClicked.addListener(function onClick () {
18 | loadOptions(launch);
19 | });
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
35 | # OS X Garbage
36 | .DS_Store
37 | */.DS_Store
38 | */*/.DS_Store
39 |
40 | # Any zip files of releases
41 | *.zip
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # node-v8-inspector
4 |
5 | Chrome extension for launching V8 Inspector for Node.js debugging.
6 |
7 | ## Usage
8 |
9 | 1. Install the `node-v8-inspector` extension from the [Chrome Web Store](https://chrome.google.com/webstore/detail/nodejs-v8-inspector/lfnddfpljnhbneopljflpombpnkfhggl).
10 | 2. Run a Node.js application with the `--inspect` command line flag.
11 | 3. Open the `node-v8-inspector` extension in Chrome.
12 | 4. Verify that `host` and `port` match your application's host and debug port. The default value is 9229, the same default used by Node.js. The host defaults to `localhost`.
13 | 5. Press `Launch V8 Inspector` button.
14 | 6. Debug your application.
15 | 7. Profit (optional).
16 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Node.js V8 Inspector
5 |
6 |
7 |
8 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Node.js V8 Inspector",
3 | "version": "0.12.0",
4 | "manifest_version": 2,
5 | "description": "Extension for launching V8 Inspector for Node.js debugging",
6 | "author": "Continuation Labs (http://continuation.io/)",
7 | "background": {
8 | "scripts": ["js/common.js", "js/load.js"],
9 | "persistent": false
10 | },
11 | "browser_action": {
12 | "default_icon": "images/logo32.png"
13 | },
14 | "icons": {
15 | "16": "images/logo16.png",
16 | "32": "images/logo32.png",
17 | "48": "images/logo48.png",
18 | "128": "images/logo128.png"
19 | },
20 | "permissions": ["http://*/", "storage", "notifications"],
21 | "options_page": "settings.html",
22 | "commands": {
23 | "_execute_browser_action": {
24 | "suggested_key": {
25 | "windows": "Ctrl+Shift+8",
26 | "mac": "Command+Shift+8",
27 | "chromeos": "Ctrl+Shift+8",
28 | "linux": "Ctrl+Shift+8"
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/js/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | document.addEventListener('DOMContentLoaded', function onLoad() {
4 | loadOptions(function loadCb (options) {
5 | host.value = options.host;
6 | host.placeholder = options.host;
7 | port.value = options.port;
8 | port.placeholder = options.port;
9 |
10 | launch.addEventListener('click', function onClick() {
11 | const settings = {
12 | host: host.value,
13 | port: port.value,
14 | poll: options.poll
15 | };
16 |
17 | if (options.poll === true) {
18 | // If polling mode is enabled, send the work to the background page.
19 | window.close();
20 | chrome.runtime.sendMessage(settings);
21 | } else {
22 | error.style.display = 'none';
23 |
24 | launchDevTools(settings)
25 | .catch(function errorHandler (err) {
26 | error.innerHTML = `Could not launch debugger
${err.message}`;
27 | error.style.display = 'block';
28 | });
29 | }
30 | }, false);
31 | });
32 | }, false);
33 |
--------------------------------------------------------------------------------
/js/settings.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | document.addEventListener('DOMContentLoaded', function onLoad () {
4 | let saveStatus = document.getElementById('saveStatus');
5 |
6 | function clearSaveStatus () {
7 | saveStatus.textContent = '';
8 | }
9 |
10 | loadOptions(function loadCb (options) {
11 | host.value = options.host;
12 | host.placeholder = options.host;
13 | port.value = options.port;
14 | port.placeholder = options.port;
15 | poll.checked = options.poll;
16 | defaults.checked = options.defaults;
17 |
18 | save.addEventListener('click', function onClick () {
19 | storeOptions({
20 | host: host.value,
21 | port: port.value,
22 | poll: poll.checked,
23 | defaults: defaults.checked
24 | }, function storeCb () {
25 | setBrowserClickAction();
26 | // Update status to let user know options were saved.
27 | saveStatus.textContent = 'Options saved.';
28 | setTimeout(clearSaveStatus, 750);
29 | });
30 | }, false);
31 | });
32 | }, false);
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Continuation Labs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Node.js V8 Inspector Settings
5 |
6 |
7 |
8 |
9 |
Node.js V8 Inspector Settings
10 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | body {
6 | min-width: 300px;
7 | font-size: 18px;
8 | }
9 |
10 | form {
11 | display: flex;
12 | flex-direction: column;
13 | flex-wrap: nowrap;
14 | justify-content: flex-start;
15 | align-content: stretch;
16 | align-items: center;
17 | }
18 |
19 | label {
20 | font-size: 20px;
21 | }
22 |
23 | input[type=text],
24 | input[type=number] {
25 | display: block;
26 | margin: 0;
27 | font-family: sans-serif;
28 | font-size: 18px;
29 | appearance: none;
30 | box-shadow: none;
31 | border-radius: none;
32 | width: 75%;
33 | }
34 |
35 | input[type=text]:focus,
36 | input[type=number]:focus,
37 | input[type=checkbox]:focus {
38 | outline: none;
39 | }
40 |
41 | input[type=number]::-webkit-inner-spin-button,
42 | input[type=number]::-webkit-outer-spin-button {
43 | -webkit-appearance: none;
44 | margin: 0;
45 | }
46 |
47 | input[type=checkbox] {
48 | zoom: 2.5
49 | }
50 |
51 | label.form-label {
52 | margin-right: 15px;
53 | width: 25%;
54 | }
55 |
56 | div.button {
57 | background: #3498db;
58 | background-image: linear-gradient(to bottom, #3498db, #2980b9);
59 | box-shadow: 0px 1px 3px #666666;
60 | color: #ffffff;
61 | font-size: 20px;
62 | padding: 10px 20px 10px 20px;
63 | text-decoration: none;
64 | text-align: center;
65 | cursor: pointer;
66 | }
67 |
68 | div.button:hover {
69 | background: #3cb0fd;
70 | background-image: linear-gradient(to bottom, #3cb0fd, #3498db);
71 | text-decoration: none;
72 | }
73 |
74 | #error {
75 | color: #a94442;
76 | background-color: #f2dede;
77 | border-color: #ebcccc;
78 | padding: 15px;
79 | margin-bottom: 1rem;
80 | border: 1px solid transparent;
81 | border-radius: .25rem;
82 | display: none;
83 | flex: 1 1 auto;
84 | }
85 |
86 | .field {
87 | padding: 10px;
88 | border: solid 5px #c9c9c9;
89 | transition: border 0.3s;
90 | flex: 1 1 auto;
91 | }
92 | .field:focus,
93 | .field.focus {
94 | border: solid 5px #969696;
95 | }
96 |
97 | .flexRow {
98 | display: flex;
99 | flex-direction: row;
100 | flex-wrap: nowrap;
101 | justify-content: flex-start;
102 | align-content: stretch;
103 | align-items: center;
104 | order: 0;
105 | flex: 0 1 auto;
106 | align-self: auto;
107 | margin: 5px;
108 | }
109 |
110 | div.container {
111 | margin: 20px auto;
112 | border: 1px solid rgba(0, 0, 0, 0.15);
113 | padding: 10px;
114 | display: flex;
115 | flex-direction: column;
116 | flex-wrap: nowrap;
117 | justify-content: center;
118 | align-content: flex-start;
119 | align-items: stretch;
120 | max-width: 800px;
121 | }
122 |
--------------------------------------------------------------------------------
/js/common.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const defaultHost = 'localhost';
4 | const defaultPort = 9229;
5 | const defaultPoll = false;
6 | const launchDefaults = false;
7 | const notificationId = 'pollForDevTools';
8 | const interval = 500;
9 | let openedInspectorTab = null;
10 | let notificationActive = false;
11 | let timeoutId;
12 |
13 |
14 | function storeOptions (options, callback) {
15 | const settings = Object.assign({
16 | host: defaultHost,
17 | port: defaultPort,
18 | poll: defaultPoll,
19 | defaults: launchDefaults
20 | }, options);
21 |
22 | chrome.storage.sync.set(settings, callback);
23 | }
24 |
25 |
26 | function loadOptions (callback) {
27 | chrome.storage.sync.get({
28 | host: defaultHost,
29 | port: defaultPort,
30 | poll: defaultPoll,
31 | defaults: launchDefaults
32 | }, callback);
33 | }
34 |
35 |
36 | function setBrowserClickAction () {
37 | loadOptions(function loadCb (options) {
38 | if (options.defaults === true) {
39 | chrome.browserAction.setPopup({ popup: '' });
40 | } else {
41 | chrome.browserAction.setPopup({ popup: 'index.html' });
42 | }
43 | });
44 | }
45 |
46 |
47 | function parseJson (response) {
48 | if (response.status !== 200) {
49 | throw new Error(`Invalid configuration data at ${response.url}`);
50 | }
51 |
52 | return response.json();
53 | }
54 |
55 |
56 | function openInspectorTab (data, options) {
57 | return new Promise((resolve, reject) => {
58 | // The replace is for older versions. For newer versions, it is a no-op.
59 | const devtoolsFrontendUrl = data[0].devtoolsFrontendUrl.replace(
60 | /^https:\/\/chrome-devtools-frontend\.appspot\.com/i,
61 | 'chrome-devtools://devtools/remote'
62 | );
63 |
64 | const url = new URL(devtoolsFrontendUrl);
65 | const wsUrl = new URL(data[0].webSocketDebuggerUrl);
66 |
67 | // Update the WebSocket URL with the host and port options. Then, update
68 | // the DevTools URL with the new WebSocket URL. Also strip the protocol.
69 | wsUrl.hostname = options.host;
70 | wsUrl.port = options.port;
71 | url.searchParams.set('ws', wsUrl.toString().replace('ws://', ''));
72 |
73 | chrome.tabs.create({
74 | // Without decoding 'ws', DevTools won't load the source files properly.
75 | url: decodeURIComponent(url.toString())
76 | }, function onTabCreated(tab) {
77 | openedInspectorTab = tab;
78 | resolve(openedInspectorTab);
79 | });
80 | });
81 | }
82 |
83 |
84 | function closeInspectorTab () {
85 | if (openedInspectorTab) {
86 | // No callback, clear flag in chrome.tabs.onRemoved().
87 | chrome.tabs.remove(openedInspectorTab.id);
88 | }
89 | }
90 |
91 |
92 | function addNotification () {
93 | if (!notificationActive) {
94 | chrome.notifications.create(notificationId, {
95 | type: 'basic',
96 | title: 'Node.js V8 Inspector',
97 | message: 'Polling for debug server. Dismiss to stop polling.',
98 | isClickable: true,
99 | requireInteraction: true,
100 | iconUrl: 'images/logo32.png'
101 | }, function onNotificationCreated () {
102 | notificationActive = true;
103 | });
104 | }
105 | }
106 |
107 |
108 | function removeNotification () {
109 | if (notificationActive) {
110 | // No callback. Reset the flag in the onClosed listener.
111 | chrome.notifications.clear(notificationId);
112 | }
113 | }
114 |
115 |
116 | // Promise an openInspectorTab or reject if the devtools is not open.
117 | function openDevTools (options) {
118 | const jsonUrl = `http://${options.host}:${options.port}/json/list`;
119 |
120 | return fetch(jsonUrl)
121 | .then(function startedDebugging (response) {
122 | return parseJson(response)
123 | .then(function onJson (data) {
124 | // Server is active, do we need to openInspector?
125 | if (!openedInspectorTab) {
126 | return openInspectorTab(data, options);
127 | } else {
128 | return Promise.resolve(openInspectorTab);
129 | }
130 | });
131 | });
132 | }
133 |
134 |
135 | // Polling user experience loop: notification, poll, openInspector, repeat.
136 | function pollForDevTools (options) {
137 | // If already polling, do not start again.
138 | if (timeoutId) {
139 | return Promise.resolve('polling');
140 | }
141 |
142 | let tabIdOpenedByPolling = null;
143 |
144 | timeoutId = setInterval(() => {
145 | // Fetch every time and open inspector only if it is not open.
146 | openDevTools(options)
147 | .then(function readyForDebugging (openInspectorTab) {
148 | // If the inspector was opened, remove the notification.
149 | if (openInspectorTab) {
150 | removeNotification();
151 | // Remember which tab was opened.
152 | tabIdOpenedByPolling = openedInspectorTab.id
153 | }
154 | })
155 | .catch(function noAnswer () {
156 | // Server is not active. Assume the session is over.
157 | // Forget out tabId first, so we don't stop polling.
158 | tabIdOpenedByPolling = null;
159 | closeInspectorTab();
160 | // Tell the user we are ready for another session.
161 | addNotification();
162 | });
163 | }, interval);
164 |
165 | // If polling opened the inspector, stop polling on close.
166 | chrome.tabs.onRemoved.addListener(function onTabsRemoved (tabId) {
167 | if (tabIdOpenedByPolling && tabId === tabIdOpenedByPolling) {
168 | if (timeoutId) {
169 | clearInterval(timeoutId);
170 | timeoutId = null;
171 | }
172 |
173 | tabIdOpenedByPolling = null;
174 | }
175 | });
176 |
177 | return Promise.resolve('polling');
178 | }
179 |
180 |
181 | function launchDevTools (options) {
182 | if (options.poll) {
183 | return pollForDevTools(options);
184 | }
185 |
186 | return openDevTools(options);
187 | }
188 |
189 |
190 | chrome.tabs.onRemoved.addListener(function onTabsRemoved (tabId) {
191 | if (openedInspectorTab && tabId === openedInspectorTab.id) {
192 | openedInspectorTab = null;
193 | }
194 | });
195 |
196 |
197 | chrome.notifications.onClosed.addListener(function onNotificationCleared () {
198 | notificationActive = false;
199 | });
200 |
201 |
202 | // If the user is done debugging, stop polling.
203 | chrome.notifications.onClicked.addListener(function onNotificationClicked (id) {
204 | if (id === notificationId) {
205 | clearInterval(timeoutId);
206 | timeoutId = null;
207 | removeNotification();
208 | }
209 | });
210 |
--------------------------------------------------------------------------------