mirror of
https://github.com/aljazceru/pear-docs.git
synced 2025-12-17 22:44:21 +01:00
Merge pull request #29 from holepunchto/inspector-changes
Inspector changes
This commit is contained in:
@@ -272,6 +272,9 @@
|
|||||||
<section>
|
<section>
|
||||||
<div class="col"><developer-tooling></devtools></div>
|
<div class="col"><developer-tooling></devtools></div>
|
||||||
</section>
|
</section>
|
||||||
|
<script>
|
||||||
|
process.env.WS_NO_BUFFER_UTIL = false // Squashing an error from the 'ws' module
|
||||||
|
</script>
|
||||||
<script type='module' src='./lib/devtools.js'></script>
|
<script type='module' src='./lib/devtools.js'></script>
|
||||||
</app-router>
|
</app-router>
|
||||||
<script type='module' src='./lib/app-router.js'></script>
|
<script type='module' src='./lib/app-router.js'></script>
|
||||||
|
|||||||
272
lib/devtools.js
272
lib/devtools.js
@@ -1,11 +1,13 @@
|
|||||||
/* eslint-env browser */
|
/* eslint-env browser */
|
||||||
import http from 'http'
|
import http from 'http'
|
||||||
import { WebSocketServer } from 'ws'
|
import { WebSocketServer } from 'ws'
|
||||||
import { Session } from '@holepunchto/pear-inspect'
|
import { Session } from 'pear-inspect'
|
||||||
import b4a from 'b4a'
|
import b4a from 'b4a'
|
||||||
|
import { spawn } from 'child_process'
|
||||||
|
|
||||||
customElements.define('developer-tooling', class extends HTMLElement {
|
customElements.define('developer-tooling', class extends HTMLElement {
|
||||||
router = null
|
router = null
|
||||||
|
port = 9342
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
super()
|
super()
|
||||||
@@ -17,33 +19,57 @@ customElements.define('developer-tooling', class extends HTMLElement {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#add-key-error {
|
#add-key-error,
|
||||||
|
#change-port-error {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
#no-apps.hidden {
|
#server-message:hover #change-port-show {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#change-port-show {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app {
|
.app {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding: 0.25rem;
|
||||||
|
padding-top: 0.1rem;
|
||||||
|
padding-bottom: 0.15rem;
|
||||||
}
|
}
|
||||||
.app .title {
|
.app .title {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
.app:hover .copy,
|
||||||
|
.app:hover .open-in-chrome,
|
||||||
.app:hover .remove {
|
.app:hover .remove {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
.app .copy,
|
||||||
|
.app .open-in-chrome {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.app .copy,
|
||||||
|
.app .open-in-chrome,
|
||||||
.app .remove {
|
.app .remove {
|
||||||
cursor: pointer;
|
|
||||||
padding-right: 10px;
|
|
||||||
margin-left: calc(-1rem - 10px);
|
|
||||||
width: 1rem;
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.button {
|
||||||
|
cursor: pointer;
|
||||||
|
background: #3a4816;
|
||||||
|
color: #efeaea;
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
font-family: 'overpass-mono';
|
||||||
|
border-radius: 1px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
#remote-inspect-explain {
|
#remote-inspect-explain {
|
||||||
float:left;
|
float:left;
|
||||||
max-width:55%;
|
max-width:55%;
|
||||||
@@ -52,6 +78,10 @@ customElements.define('developer-tooling', class extends HTMLElement {
|
|||||||
float:right;
|
float:right;
|
||||||
max-width:40%;
|
max-width:40%;
|
||||||
}
|
}
|
||||||
|
#server-message {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
h2 { margin: 0 }
|
h2 { margin: 0 }
|
||||||
p {
|
p {
|
||||||
@@ -93,25 +123,25 @@ customElements.define('developer-tooling', class extends HTMLElement {
|
|||||||
<div>
|
<div>
|
||||||
<h1>Developer Tooling</h1>
|
<h1>Developer Tooling</h1>
|
||||||
<div>
|
<div>
|
||||||
<div id=remote-inspect-explain>
|
<div id="remote-inspect-explain">
|
||||||
<h2>Remotely inspect Pear applications.</h2>
|
<h2>Remotely inspect Pear applications.</h2>
|
||||||
|
|
||||||
<p> Some application setup is required to enable remote debugging </p>
|
<p> Some application setup is required to enable remote debugging </p>
|
||||||
<p> Install the <code>pear-inspect</code> module into the application </p>
|
<p> Install the <code>pear-inspect</code> module into the application </p>
|
||||||
<pre><code>npm install pear-inspect</code></pre>
|
<pre><code>npm install pear-inspect</code></pre>
|
||||||
<p> Add the following code to the application, before any other code: </p>
|
<p> Add the following code to the application, before any other code: </p>
|
||||||
|
|
||||||
<pre><code>if (Pear.config.dev) {
|
<pre><code>if (Pear.config.dev) {
|
||||||
const { Inspector } = await import('pear-inspect')
|
const { Inspector } = await import('pear-inspect')
|
||||||
const inpector = await new Inspector()
|
const inpector = await new Inspector()
|
||||||
const key = await inpector.enable()
|
const key = await inpector.enable()
|
||||||
console.log('Pear Inspector key:', key.toString('hex'))
|
console.log('Debug with pear://runtime/devtools/'
|
||||||
|
+ key.toString('hex'))
|
||||||
}</code></pre>
|
}</code></pre>
|
||||||
|
|
||||||
<p>When the application is opened in development mode the inspection key will be logged.</p>
|
<p>When the application is opened in development mode the inspection key will be logged.</p>
|
||||||
<p>Paste the logged key into the input and use a compatible inspect protocol tool, such as chrome://inspect to view the remote target</p>
|
<p>Paste the logged key into the input to add it. Then open in Chrome or copy the URL to a compatible inspect tool to view the remote target.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id=remote-inspect>
|
<div id="remote-inspect">
|
||||||
<div>
|
<div>
|
||||||
<form id="add-key-form">
|
<form id="add-key-form">
|
||||||
<input id="add-key-input" type="text" placeholder="Paste Pear Inspector Key Here"/>
|
<input id="add-key-input" type="text" placeholder="Paste Pear Inspector Key Here"/>
|
||||||
@@ -121,8 +151,24 @@ customElements.define('developer-tooling', class extends HTMLElement {
|
|||||||
<h2>Apps</h2>
|
<h2>Apps</h2>
|
||||||
<h3 id="no-apps">No apps added. Add an inspect key to start debugging.</h3>
|
<h3 id="no-apps">No apps added. Add an inspect key to start debugging.</h3>
|
||||||
<div id="apps"></div>
|
<div id="apps"></div>
|
||||||
|
<div id="server-message" class="hidden">
|
||||||
|
Pear DevTools connection running on
|
||||||
|
<div>
|
||||||
|
<span id="server-location">localhost</span>
|
||||||
|
<span id="change-port-show" class="button">
|
||||||
|
Change port
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<form id="change-port-form" class="hidden">
|
||||||
|
<input id="change-port-input" type="number" placeholder="New port" />
|
||||||
|
</form>
|
||||||
|
<p id="change-port-error"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
this.root = this.attachShadow({ mode: 'open' })
|
this.root = this.attachShadow({ mode: 'open' })
|
||||||
@@ -131,52 +177,134 @@ customElements.define('developer-tooling', class extends HTMLElement {
|
|||||||
this.addKeyFormElem = this.root.querySelector('#add-key-form')
|
this.addKeyFormElem = this.root.querySelector('#add-key-form')
|
||||||
this.addKeyInputElem = this.root.querySelector('#add-key-input')
|
this.addKeyInputElem = this.root.querySelector('#add-key-input')
|
||||||
this.addKeyErrorElem = this.root.querySelector('#add-key-error')
|
this.addKeyErrorElem = this.root.querySelector('#add-key-error')
|
||||||
|
this.changePortFormElem = this.root.querySelector('#change-port-form')
|
||||||
|
this.changePortInputElem = this.root.querySelector('#change-port-input')
|
||||||
|
this.changePortErrorElem = this.root.querySelector('#change-port-error')
|
||||||
|
this.changePortShowElem = this.root.querySelector('#change-port-show')
|
||||||
this.appsElem = this.root.querySelector('#apps')
|
this.appsElem = this.root.querySelector('#apps')
|
||||||
this.noAppsElem = this.root.querySelector('#no-apps')
|
this.noAppsElem = this.root.querySelector('#no-apps')
|
||||||
|
this.changePortElem = this.root.querySelector('#change-port')
|
||||||
this.apps = new Map()
|
this.apps = new Map()
|
||||||
|
|
||||||
this.addKeyInputElem.addEventListener('keypress', e => {
|
this.addKeyInputElem.addEventListener('keydown', e => {
|
||||||
this.addKeyErrorElem.textContent = ''
|
this.addKeyErrorElem.textContent = ''
|
||||||
})
|
})
|
||||||
|
|
||||||
this.addKeyFormElem.addEventListener('submit', e => {
|
this.addKeyFormElem.addEventListener('submit', e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const inspectorKey = this.addKeyInputElem.value
|
const inspectorKey = this.addKeyInputElem.value
|
||||||
if (inspectorKey.length !== 64) {
|
this.addApp(inspectorKey)
|
||||||
this.addKeyErrorElem.textContent = 'Key needs to be 64 characters long'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessionId = generateUuid()
|
|
||||||
const inspectorSession = new Session({ inspectorKey: b4a.from(inspectorKey, 'hex') })
|
|
||||||
const app = {
|
|
||||||
inspectorKey,
|
|
||||||
title: '',
|
|
||||||
url: '',
|
|
||||||
inspectorSession
|
|
||||||
}
|
|
||||||
|
|
||||||
inspectorSession.on('close', () => {
|
|
||||||
this.apps.delete(sessionId)
|
|
||||||
this.renderApps()
|
|
||||||
})
|
|
||||||
inspectorSession.on('info', ({ filename }) => {
|
|
||||||
app.url = filename
|
|
||||||
app.title = filename.split('/').pop()
|
|
||||||
this.apps.set(sessionId, app)
|
|
||||||
this.renderApps()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.addKeyInputElem.value = ''
|
|
||||||
this.addKeyErrorElem.textContent = ''
|
|
||||||
this.renderApps()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const devtoolsHttpServer = http.createServer()
|
this.changePortInputElem.addEventListener('keydown', e => {
|
||||||
|
const isEscape = e.key === 'Escape'
|
||||||
|
this.changePortErrorElem.textContent = ''
|
||||||
|
if (isEscape) {
|
||||||
|
this.changePortFormElem.classList.add('hidden')
|
||||||
|
this.changePortInputElem.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.changePortShowElem.addEventListener('click', () => {
|
||||||
|
this.changePortFormElem.classList.remove('hidden')
|
||||||
|
})
|
||||||
|
this.changePortFormElem.addEventListener('submit', e => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.port = Number(this.changePortInputElem.value)
|
||||||
|
this.initServer()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const shouldLoadApp = Pear.config.linkData?.startsWith('devtools/')
|
||||||
|
if (shouldLoadApp) {
|
||||||
|
this.addApp(Pear.config.linkData)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
this.appsElem.replaceChildren(...[...this.apps].map(([sessionId, app]) => {
|
||||||
|
const div = document.createElement('div')
|
||||||
|
div.innerHTML = `
|
||||||
|
<div class="app">
|
||||||
|
<div class="title">${app.title} (${app.url})</div>
|
||||||
|
<div class="button copy">Copy URL</div>
|
||||||
|
<div class="button open-in-chrome">Open in Chrome</div>
|
||||||
|
<div class="button remove">✕</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
div.querySelector('.copy').addEventListener('click', () => {
|
||||||
|
navigator.clipboard.writeText(`devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:${this.port}/${sessionId}`)
|
||||||
|
})
|
||||||
|
div.querySelector('.open-in-chrome').addEventListener('click', () => {
|
||||||
|
openChrome(`devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:${this.port}/${sessionId}`)
|
||||||
|
})
|
||||||
|
div.querySelector('.remove').addEventListener('click', () => {
|
||||||
|
this.apps.delete(sessionId)
|
||||||
|
this.render()
|
||||||
|
})
|
||||||
|
|
||||||
|
return div
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (this.apps.size > 0) {
|
||||||
|
this.noAppsElem.classList.add('hidden')
|
||||||
|
} else {
|
||||||
|
this.noAppsElem.classList.remove('hidden')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.port) {
|
||||||
|
this.root.querySelector('#server-location').textContent = `http://localhost:${this.port}`
|
||||||
|
this.root.querySelector('#server-message').classList.remove('hidden')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addApp (inspectorKey) {
|
||||||
|
const isUrl = inspectorKey.includes('devtools/')
|
||||||
|
if (isUrl) inspectorKey = inspectorKey.split('devtools/').pop()
|
||||||
|
const isIncorrectLength = inspectorKey.length !== 64
|
||||||
|
if (isIncorrectLength) {
|
||||||
|
this.addKeyErrorElem.textContent = 'Key needs to be 64 characters long'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionId = generateUuid()
|
||||||
|
const inspectorSession = new Session({ inspectorKey: b4a.from(inspectorKey, 'hex') })
|
||||||
|
const app = {
|
||||||
|
inspectorKey,
|
||||||
|
title: '',
|
||||||
|
url: '',
|
||||||
|
inspectorSession
|
||||||
|
}
|
||||||
|
|
||||||
|
inspectorSession.on('close', () => {
|
||||||
|
this.apps.delete(sessionId)
|
||||||
|
this.render()
|
||||||
|
})
|
||||||
|
inspectorSession.on('info', ({ filename }) => {
|
||||||
|
app.url = filename
|
||||||
|
app.title = filename.split('/').pop()
|
||||||
|
this.apps.set(sessionId, app)
|
||||||
|
this.render()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.addKeyInputElem.value = ''
|
||||||
|
this.addKeyErrorElem.textContent = ''
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
initServer () {
|
||||||
|
this.devtoolsHttpServer?.close()
|
||||||
|
|
||||||
|
this.devtoolsHttpServer = http.createServer()
|
||||||
const devToolsWsServer = new WebSocketServer({ noServer: true })
|
const devToolsWsServer = new WebSocketServer({ noServer: true })
|
||||||
|
|
||||||
devtoolsHttpServer.listen(9229, () => console.log('[devtoolsHttpServer] running on port 9229'))
|
this.devtoolsHttpServer.listen(this.port, () => {
|
||||||
devtoolsHttpServer.on('request', (req, res) => {
|
console.log(`[devtoolsHttpServer] running on port ${this.port}`)
|
||||||
|
this.render()
|
||||||
|
this.changePortFormElem.classList.add('hidden')
|
||||||
|
})
|
||||||
|
this.devtoolsHttpServer.on('error', err => this.changePortErrorElem.textContent = err?.message)
|
||||||
|
this.devtoolsHttpServer.on('request', (req, res) => {
|
||||||
if (req.url !== '/json/list') {
|
if (req.url !== '/json/list') {
|
||||||
res.writeHead(404)
|
res.writeHead(404)
|
||||||
res.end()
|
res.end()
|
||||||
@@ -185,14 +313,14 @@ customElements.define('developer-tooling', class extends HTMLElement {
|
|||||||
|
|
||||||
const targets = [...this.apps].map(([sessionId, app]) => ({
|
const targets = [...this.apps].map(([sessionId, app]) => ({
|
||||||
description: 'node.js instance', // `Pear app: ${app.name}`,
|
description: 'node.js instance', // `Pear app: ${app.name}`,
|
||||||
devtoolsFrontendUrl: `devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:9229/${sessionId}`,
|
devtoolsFrontendUrl: `devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:${this.port}/${sessionId}`,
|
||||||
devtoolsFrontendUrlCompat: `devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229/${sessionId}`,
|
devtoolsFrontendUrlCompat: `devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:${this.port}/${sessionId}`,
|
||||||
faviconUrl: 'https://nodejs.org/static/images/favicons/favicon.ico',
|
faviconUrl: 'https://nodejs.org/static/images/favicons/favicon.ico',
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
title: app.title,
|
title: app.title,
|
||||||
type: 'node',
|
type: 'node',
|
||||||
url: `file://${app.url}`,
|
url: `file://${app.url}`,
|
||||||
webSocketDebuggerUrl: `ws://127.0.0.1:9229/${sessionId}`
|
webSocketDebuggerUrl: `ws://127.0.0.1:${this.port}/${sessionId}`
|
||||||
}))
|
}))
|
||||||
|
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
@@ -202,7 +330,7 @@ customElements.define('developer-tooling', class extends HTMLElement {
|
|||||||
})
|
})
|
||||||
res.end(JSON.stringify(targets))
|
res.end(JSON.stringify(targets))
|
||||||
})
|
})
|
||||||
devtoolsHttpServer.on('upgrade', (request, socket, head) => {
|
this.devtoolsHttpServer.on('upgrade', (request, socket, head) => {
|
||||||
console.log(`[devtoolsHttpServer] UPGRADE. url=${request.url}`)
|
console.log(`[devtoolsHttpServer] UPGRADE. url=${request.url}`)
|
||||||
const sessionId = request.url.substr(1)
|
const sessionId = request.url.substr(1)
|
||||||
const sessionIdExists = this.apps.has(sessionId)
|
const sessionIdExists = this.apps.has(sessionId)
|
||||||
@@ -231,39 +359,14 @@ customElements.define('developer-tooling', class extends HTMLElement {
|
|||||||
inspectorSession.disconnect()
|
inspectorSession.disconnect()
|
||||||
inspectorSession.off('message', onMessage)
|
inspectorSession.off('message', onMessage)
|
||||||
app.connected = false
|
app.connected = false
|
||||||
this.renderApps()
|
this.render()
|
||||||
})
|
})
|
||||||
|
|
||||||
app.connected = true
|
app.connected = true
|
||||||
this.renderApps()
|
this.render()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
renderApps () {
|
|
||||||
this.appsElem.replaceChildren(...[...this.apps].map(([sessionId, app]) => {
|
|
||||||
const div = document.createElement('div')
|
|
||||||
div.innerHTML = `
|
|
||||||
<div class="app">
|
|
||||||
<div class="remove" data-session-id="${sessionId}">✕</div>
|
|
||||||
<div class="title">${app.title} (${app.url})</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
div.querySelector('.remove').addEventListener('click', () => {
|
|
||||||
this.apps.delete(sessionId)
|
|
||||||
this.renderApps()
|
|
||||||
})
|
|
||||||
|
|
||||||
return div
|
|
||||||
}))
|
|
||||||
|
|
||||||
if (this.apps.size > 0) {
|
|
||||||
this.noAppsElem.classList.add('hidden')
|
|
||||||
} else {
|
|
||||||
this.noAppsElem.classList.remove('hidden')
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
load () {
|
load () {
|
||||||
this.style.display = ''
|
this.style.display = ''
|
||||||
}
|
}
|
||||||
@@ -286,3 +389,16 @@ function generateUuid () {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openChrome (url) {
|
||||||
|
const params = {
|
||||||
|
darwin: ['open', '-a', 'Google Chrome', url],
|
||||||
|
linux: ['google-chrome', url],
|
||||||
|
win32: ['start', 'chrome', url]
|
||||||
|
}[process.platform]
|
||||||
|
|
||||||
|
if (!params) throw new Error('Cannot open Chrome')
|
||||||
|
|
||||||
|
const [command, ...args] = params
|
||||||
|
spawn(command, args)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@holepunchto/pear-inspect": "^3.0.1",
|
|
||||||
"b4a": "^1.6.4",
|
"b4a": "^1.6.4",
|
||||||
"bare-path": "^2.1.0",
|
"bare-path": "^2.1.0",
|
||||||
|
"pear-inspect": "^1.0.3",
|
||||||
"redhat-overpass-font": "^1.0.0",
|
"redhat-overpass-font": "^1.0.0",
|
||||||
"ws": "^8.16.0"
|
"ws": "^8.16.0"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user