patch: devtools command
This commit is contained in:
Родитель
33e059a2d5
Коммит
3e1e5c7779
|
@ -28,6 +28,7 @@ jobs:
|
|||
- run: yarn prettier
|
||||
- run: yarn lint
|
||||
- run: yarn build
|
||||
- run: yarn build:web
|
||||
- run: npx semantic-release
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
env:
|
||||
|
|
14
package.json
14
package.json
|
@ -7,7 +7,9 @@
|
|||
"scripts": {
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"prettier": "prettier --write src/**/*.ts",
|
||||
"build": "microbundle",
|
||||
"build:cli": "microbundle",
|
||||
"build:web": "tsc public/index.ts",
|
||||
"devtools": "yarn build:web && node ./dist/cli.js devtools --trace",
|
||||
"watch": "microbundle watch"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -41,14 +43,20 @@
|
|||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.1",
|
||||
"faye-websocket": "^0.11.4",
|
||||
"microbundle": "^0.14.1",
|
||||
"prettier": "^2.4.1",
|
||||
"semantic-release": "^18.0.0",
|
||||
"tslint-microsoft-contrib": "^6.2.0"
|
||||
"tslint-microsoft-contrib": "^6.2.0",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"serialport": "^9.2.5",
|
||||
"webusb": "^2.2.0",
|
||||
"ws": "^8.2.3"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"public"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" xmlns="http://www.w3.org/2000/svg"><path fill="gold" d="M22.369 14.045H4.037a2.618 2.618 0 0 1-2.656.173A2.493 2.493 0 0 1 0 12c0-.933.533-1.788 1.381-2.218a2.618 2.618 0 0 1 2.656.173h18.332c.9 0 1.631.712 1.631 1.59v.91c0 .878-.73 1.59-1.631 1.59zM4.124 12c0-.404-.164-.791-.457-1.077a1.581 1.581 0 0 0-1.104-.446c-.414 0-.811.16-1.104.446A1.504 1.504 0 0 0 1.002 12c0 .404.164.791.457 1.077.293.285.69.446 1.104.446.414 0 .811-.16 1.104-.446.293-.286.457-.673.457-1.077zM4.187 18c0-.404-.167-.791-.464-1.077a1.618 1.618 0 0 0-1.121-.446c-.42 0-.824.16-1.12.446A1.493 1.493 0 0 0 1.016 18c0 .404.167.791.464 1.077.297.285.7.446 1.121.446.42 0 .823-.16 1.12-.446.298-.286.465-.673.465-1.077zm16.157 2.045H4.098a2.692 2.692 0 0 1-2.695.173C.54 19.788 0 18.933 0 18s.54-1.788 1.403-2.218a2.692 2.692 0 0 1 2.695.173h16.246c.915 0 1.656.712 1.656 1.59v.91c0 .878-.741 1.59-1.656 1.59zM24 6.455c0 .878-.73 1.59-1.631 1.59H4.037a2.618 2.618 0 0 1-2.656.173A2.493 2.493 0 0 1 0 6c0-.933.533-1.788 1.381-2.218a2.618 2.618 0 0 1 2.65.168l.006.005h18.332c.9 0 1.631.712 1.631 1.59ZM4.124 6c0-.404-.164-.791-.457-1.077a1.581 1.581 0 0 0-1.104-.446c-.414 0-.811.16-1.104.446A1.504 1.504 0 0 0 1.002 6c0 .404.164.791.457 1.077.293.285.69.446 1.104.446.414 0 .811-.16 1.104-.446.293-.286.457-.673.457-1.077z"/></svg>
|
После Ширина: | Высота: | Размер: 1.4 KiB |
|
@ -0,0 +1,19 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<link rel="icon" href="/favicon.svg" type="image/x-icon">
|
||||
</head>
|
||||
<style>
|
||||
iframe {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<iframe id="frame" alt="Jacdac Dashboard" allow="usb;serial;bluetooth;vr" allowfullscreen sandbox="allow-scripts allow-downloads allow-same-origin"></iframe>
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,32 @@
|
|||
(function () {
|
||||
var frame = document.getElementById("frame");
|
||||
var sender = Math.random() + "";
|
||||
frame.src = "https://microsoft.github.io/jacdac-docs/dashboard/#" + sender;
|
||||
// node.js -> iframe dashboard
|
||||
var ws = new WebSocket("ws://localhost:8081/");
|
||||
console.debug("devtools: connecting to local server...");
|
||||
ws.addEventListener("open", function () {
|
||||
console.debug("devtools: connected to local server");
|
||||
});
|
||||
ws.addEventListener("message", function (msg) {
|
||||
var data = msg.data;
|
||||
frame.contentWindow.postMessage({
|
||||
type: "messagepacket",
|
||||
channel: "jacdac",
|
||||
data: data,
|
||||
sender: sender,
|
||||
broadcast: true
|
||||
});
|
||||
});
|
||||
ws.addEventListener("close", function () {
|
||||
console.debug("devtools: connection closed");
|
||||
});
|
||||
// iframe dashboard -> node.js
|
||||
window.addEventListener("message", function (msg) {
|
||||
var data = msg.data;
|
||||
if (data && data.type === "messagepacket" && data.channel === "jacdac") {
|
||||
if ((ws === null || ws === void 0 ? void 0 : ws.readyState) === WebSocket.OPEN)
|
||||
ws.send(data.data);
|
||||
}
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,33 @@
|
|||
(function () {
|
||||
const frame = document.getElementById("frame") as HTMLIFrameElement
|
||||
const sender = Math.random() + ""
|
||||
frame.src = "https://microsoft.github.io/jacdac-docs/dashboard/#" + sender
|
||||
|
||||
// node.js -> iframe dashboard
|
||||
const ws = new WebSocket("ws://localhost:8081/")
|
||||
console.debug(`devtools: connecting to local server...`)
|
||||
ws.addEventListener("open", () => {
|
||||
console.debug(`devtools: connected to local server`)
|
||||
})
|
||||
ws.addEventListener("message", (msg) => {
|
||||
const data = msg.data
|
||||
frame.contentWindow.postMessage({
|
||||
type: "messagepacket",
|
||||
channel: "jacdac",
|
||||
data: data,
|
||||
sender,
|
||||
broadcast: true,
|
||||
})
|
||||
})
|
||||
ws.addEventListener("close", () => {
|
||||
console.debug(`devtools: connection closed`)
|
||||
})
|
||||
// iframe dashboard -> node.js
|
||||
window.addEventListener("message", msg => {
|
||||
const data = msg.data
|
||||
if (data && data.type ==="messagepacket" && data.channel === "jacdac") {
|
||||
if (ws?.readyState === WebSocket.OPEN)
|
||||
ws.send(data.data)
|
||||
}
|
||||
})
|
||||
})()
|
|
@ -0,0 +1,117 @@
|
|||
import {
|
||||
JDBus,
|
||||
Packet,
|
||||
PACKET_PROCESS,
|
||||
printPacket,
|
||||
serializeToTrace,
|
||||
} from "jacdac-ts"
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const WebSocket = require("faye-websocket")
|
||||
const http = require("http")
|
||||
const fs = require("fs")
|
||||
const url = require("url")
|
||||
const path = require("path")
|
||||
|
||||
const log = console.log
|
||||
const debug = console.debug
|
||||
const error = console.error
|
||||
|
||||
export async function devToolsCommand(options?: { packets?: boolean }) {
|
||||
const { packets } = options || {}
|
||||
const port = 8081
|
||||
|
||||
debug(`starting dev tools...`)
|
||||
log(` dashboard: http://localhost:${port}`)
|
||||
log(` websocket: ws://localhost:${port}`)
|
||||
|
||||
// start http server
|
||||
const clients: WebSocket[] = []
|
||||
const server = http.createServer(function (req, res) {
|
||||
//debug(`${req.method} ${req.url}`)
|
||||
|
||||
// parse URL
|
||||
const parsedUrl = url.parse(req.url)
|
||||
// extract URL path
|
||||
let pathname = `.${parsedUrl.pathname}`
|
||||
if (pathname === "./") pathname = "./index.html"
|
||||
// based on the URL path, extract the file extension. e.g. .js, .doc, ...
|
||||
const ext = path.parse(pathname).ext
|
||||
// maps file extension to MIME typere
|
||||
const map = {
|
||||
".ico": "image/x-icon",
|
||||
".html": "text/html",
|
||||
".js": "text/javascript",
|
||||
}
|
||||
|
||||
const fname = path.join(__dirname, "../public", pathname)
|
||||
fs.exists(fname, exist => {
|
||||
if (!exist) {
|
||||
// if the file is not found, return 404
|
||||
res.statusCode = 404
|
||||
debug(`not found`)
|
||||
return
|
||||
}
|
||||
|
||||
// if is a directory search for index file matching the extension
|
||||
//if (fs.statSync(fname).isDirectory()) fname += "index" + ext
|
||||
|
||||
// read file from file system
|
||||
fs.readFile(fname, (err, data) => {
|
||||
if (err) {
|
||||
res.statusCode = 500
|
||||
debug(`error reading file`)
|
||||
} else {
|
||||
// if the file is found, set Content-type and send data
|
||||
res.setHeader("Content-type", map[ext] || "text/plain")
|
||||
res.end(data)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// passive bus to sniff packets
|
||||
const bus = new JDBus([], { client: false, disableRoleManager: true })
|
||||
bus.passive = true
|
||||
|
||||
const processPacket = (message: ArrayBuffer, sender: string) => {
|
||||
const data = new Uint8Array(message)
|
||||
const pkt = Packet.fromBinary(data, bus.timestamp)
|
||||
pkt.sender = sender
|
||||
bus.processPacket(pkt)
|
||||
}
|
||||
|
||||
server.on("upgrade", (request, socket, body) => {
|
||||
// is this a socket?
|
||||
if (WebSocket.isWebSocket(request)) {
|
||||
let client = new WebSocket(request, socket, body)
|
||||
const sender = Math.random() + ""
|
||||
clients.push(client)
|
||||
log(`client: connected (${clients.length} clients)`)
|
||||
client.on("message", event => {
|
||||
const { data } = event
|
||||
clients.filter(c => c !== client).forEach(c => c.send(data))
|
||||
processPacket(data, sender)
|
||||
})
|
||||
client.on("close", () => {
|
||||
const i = clients.indexOf(client)
|
||||
clients.splice(i, 1)
|
||||
client = undefined
|
||||
log(`client: disconnected (${clients.length} clients)`)
|
||||
})
|
||||
client.on("error", ev => error(ev))
|
||||
}
|
||||
})
|
||||
|
||||
if (packets)
|
||||
bus.on(PACKET_PROCESS, (pkt: Packet) => {
|
||||
const str = printPacket(pkt, {
|
||||
showTime: true,
|
||||
skipRepeatedAnnounce: true,
|
||||
skipResetIn: true,
|
||||
})
|
||||
if (str) debug(serializeToTrace(pkt, 0))
|
||||
})
|
||||
bus.start()
|
||||
server.listen(port)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { program } = require("commander")
|
||||
import type { CommandOptions } from "commander"
|
||||
import { devToolsCommand } from "./devtools"
|
||||
import { parseCommand } from "./parse"
|
||||
import { streamCommand } from "./stream"
|
||||
|
||||
|
@ -33,13 +34,16 @@ async function mainCli() {
|
|||
.option("--catalog", "generate .json files for device catalog")
|
||||
.action(streamCommand)
|
||||
|
||||
createCommand("devtools")
|
||||
.option("-p, --packets", "show all packets")
|
||||
.action(devToolsCommand)
|
||||
|
||||
await program.parseAsync(process.argv)
|
||||
}
|
||||
|
||||
async function mainWrapper() {
|
||||
try {
|
||||
await mainCli()
|
||||
process.exit(0)
|
||||
} catch (e) {
|
||||
error("Exception: " + e.stack)
|
||||
error("Build failed")
|
||||
|
|
33
yarn.lock
33
yarn.lock
|
@ -3218,6 +3218,13 @@ fastq@^1.6.0:
|
|||
dependencies:
|
||||
reusify "^1.0.4"
|
||||
|
||||
faye-websocket@^0.11.4:
|
||||
version "0.11.4"
|
||||
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
|
||||
integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==
|
||||
dependencies:
|
||||
websocket-driver ">=0.5.1"
|
||||
|
||||
figures@^1.0.1:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz"
|
||||
|
@ -3666,6 +3673,11 @@ http-cache-semantics@^4.1.0:
|
|||
resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz"
|
||||
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
|
||||
|
||||
http-parser-js@>=0.5.1:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9"
|
||||
integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==
|
||||
|
||||
http-proxy-agent@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz"
|
||||
|
@ -6166,7 +6178,7 @@ sade@^1.7.4:
|
|||
dependencies:
|
||||
mri "^1.1.0"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
|
||||
safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
@ -6913,6 +6925,11 @@ typescript@^4.1.3:
|
|||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz"
|
||||
integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==
|
||||
|
||||
typescript@^4.4.4:
|
||||
version "4.4.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"
|
||||
integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.14.3"
|
||||
resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.3.tgz"
|
||||
|
@ -7073,6 +7090,20 @@ webidl-conversions@^3.0.0:
|
|||
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
|
||||
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
|
||||
|
||||
websocket-driver@>=0.5.1:
|
||||
version "0.7.4"
|
||||
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
|
||||
integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
|
||||
dependencies:
|
||||
http-parser-js ">=0.5.1"
|
||||
safe-buffer ">=5.1.0"
|
||||
websocket-extensions ">=0.1.1"
|
||||
|
||||
websocket-extensions@>=0.1.1:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
|
||||
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
|
||||
|
||||
webusb@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/webusb/-/webusb-2.2.0.tgz"
|
||||
|
|
Загрузка…
Ссылка в новой задаче