Fix/add npm jsr building (#722)
* fixed github workflows to improve handling of mutl runtimes * updating readme * Update packages/core/src/meshDevice.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/core/package.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/transport-http/package.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -30,7 +30,7 @@ jobs:
|
|||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/deno
|
path: ~/.cache/deno
|
||||||
key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock', '**/deno.json', '**/deno.jsonc') }}
|
key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock', '**/package.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-deno-
|
${{ runner.os }}-deno-
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
~/.bun/install/cache
|
~/.bun/install/cache
|
||||||
packages/web/node_modules
|
packages/web/node_modules
|
||||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-bun-
|
${{ runner.os }}-bun-
|
||||||
|
|
||||||
@@ -60,10 +60,7 @@ jobs:
|
|||||||
for pkg_dir in ${{ steps.changed_packages.outputs.all_changed_and_modified_files }}; do
|
for pkg_dir in ${{ steps.changed_packages.outputs.all_changed_and_modified_files }}; do
|
||||||
echo "🔍 Inspecting $pkg_dir..."
|
echo "🔍 Inspecting $pkg_dir..."
|
||||||
|
|
||||||
if [[ -f "$pkg_dir/deno.json" ]]; then
|
if [[ -f "$pkg_dir/deno.lock" ]]; then
|
||||||
echo "🔧 Building with Deno: $pkg_dir"
|
|
||||||
deno task build "$pkg_dir"
|
|
||||||
elif [[ -f "$pkg_dir/bun.lockb" ]]; then
|
|
||||||
echo "🔧 Building with Bun: $pkg_dir"
|
echo "🔧 Building with Bun: $pkg_dir"
|
||||||
(cd "$pkg_dir" && bun install && bun run build)
|
(cd "$pkg_dir" && bun install && bun run build)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/deno
|
path: ~/.cache/deno
|
||||||
key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock', '**/deno.json', '**/deno.jsonc') }}
|
key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-deno-
|
${{ runner.os }}-deno-
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
excluded=("packages/web" "packages/transport-deno")
|
excluded=("packages/web" "packages/transport-deno" "packages/transport-node")
|
||||||
|
|
||||||
for pkg_dir in ${{ steps.changed_packages.outputs.all_changed_and_modified_files }}; do
|
for pkg_dir in ${{ steps.changed_packages.outputs.all_changed_and_modified_files }}; do
|
||||||
echo "🔍 Inspecting $pkg_dir"
|
echo "🔍 Inspecting $pkg_dir"
|
||||||
@@ -90,12 +90,12 @@ jobs:
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "$pkg_dir/deno.json" ]]; then
|
if [[ -f "$pkg_dir/jsr.json" ]]; then
|
||||||
echo "🦕 Building with Deno: $pkg_dir"
|
echo "🦕 Publishing to NPM: $pkg_dir"
|
||||||
deno task build "$pkg_dir"
|
bun run build:npm $pkg_dir
|
||||||
echo "📦 Publishing to JSR"
|
echo "📦 Publishing to JSR"
|
||||||
(cd "$pkg_dir" && deno publish --allow-dirty)
|
(cd "$pkg_dir" && deno publish --allow-dirty)
|
||||||
elif [[ -f "$pkg_dir/bun.lockb" ]]; then
|
elif [[ -f "$pkg_dir/bun.lock" ]]; then
|
||||||
echo "🥖 Building with Bun: $pkg_dir"
|
echo "🥖 Building with Bun: $pkg_dir"
|
||||||
(cd "$pkg_dir" && bun install && bun run build)
|
(cd "$pkg_dir" && bun install && bun run build)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ This monorepo leverages the following technologies:
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
You'll need to have [Bun](https://bun.sh/) installed to work with this
|
You'll need to have [Bun](https://bun.sh/) installed to work with this monorepo.
|
||||||
monorepo. Follow the installation instructions on their home page.
|
Follow the installation instructions on their home page.
|
||||||
|
|
||||||
### Development Setup
|
### Development Setup
|
||||||
|
|
||||||
@@ -80,22 +80,12 @@ monorepo. Follow the installation instructions on their home page.
|
|||||||
To start the development server for the web client:
|
To start the development server for the web client:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run --filter web dev
|
cd ./packages/web && bun run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
This will typically run the web client on http://localhost:3000 and requires a
|
This will typically run the web client on http://localhost:3000 and requires a
|
||||||
Chromium browser
|
Chromium browser
|
||||||
|
|
||||||
## Meshtastic JS Packages
|
|
||||||
|
|
||||||
While the js packages are primarily libraries, you can run their tests or
|
|
||||||
specific development scripts if defined within their package.json files. For
|
|
||||||
example, to run tests for a specific package:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bun run --filter core test
|
|
||||||
```
|
|
||||||
|
|
||||||
### Feedback
|
### Feedback
|
||||||
|
|
||||||
If you encounter any issues with nightly builds, please report them in our
|
If you encounter any issues with nightly builds, please report them in our
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/2.0.5/schema.json",
|
|
||||||
"files": {
|
"files": {
|
||||||
"includes": ["**", "!node_modules/**", "!**/*.css", "!dist/**", "!build/**", "!coverage/**", "!**/*.d.ts"],
|
"includes": ["**/*.ts", "**/*.tsx", "!**/*.test.ts", "!**/*.test.tsx", "!npm_modules/**", "!dist/**", "!npm/**"],
|
||||||
"ignoreUnknown": false
|
"ignoreUnknown": false
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
@@ -14,6 +13,7 @@
|
|||||||
},
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"includes": ["**", "!test/**"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"recommended": true,
|
"recommended": true,
|
||||||
"suspicious": {
|
"suspicious": {
|
||||||
@@ -21,13 +21,9 @@
|
|||||||
"noDebugger": "error"
|
"noDebugger": "error"
|
||||||
},
|
},
|
||||||
"style": {
|
"style": {
|
||||||
"useConst": "error",
|
|
||||||
"useBlockStatements": "error",
|
"useBlockStatements": "error",
|
||||||
"useSingleVarDeclarator": "off"
|
"useSingleVarDeclarator": "off"
|
||||||
},
|
},
|
||||||
"complexity": {
|
|
||||||
"noForEach": "off"
|
|
||||||
},
|
|
||||||
"correctness": {
|
"correctness": {
|
||||||
"noUnusedVariables": "error",
|
"noUnusedVariables": "error",
|
||||||
"noUnusedImports": "error"
|
"noUnusedImports": "error"
|
||||||
@@ -40,10 +36,9 @@
|
|||||||
"semicolons": "always"
|
"semicolons": "always"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"json": {
|
"json": {
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"indentStyle": "space",
|
"enabled": false
|
||||||
"indentWidth": 2
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -3,12 +3,51 @@
|
|||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "meshtastic-web",
|
"name": "meshtastic-web",
|
||||||
|
"dependencies": {
|
||||||
|
"@bufbuild/protobuf": "^2.6.1",
|
||||||
|
"ste-simple-events": "^3.0.11",
|
||||||
|
"tslog": "^4.9.3",
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.8.3",
|
"@types/node": "^22.16.4",
|
||||||
"bun": "^1.1.18",
|
"bun": "^1.2.18",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"packages/core": {
|
||||||
|
"name": "@meshtastic/core",
|
||||||
|
"version": "2.6.5",
|
||||||
|
"dependencies": {
|
||||||
|
"@meshtastic/protobufs": "npm:@jsr/meshtastic__protobufs",
|
||||||
|
"crc": "npm:crc@^4.3.2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages/transport-deno": {
|
||||||
|
"name": "@meshtastic/transport-deno",
|
||||||
|
"version": "0.1.1",
|
||||||
|
},
|
||||||
|
"packages/transport-http": {
|
||||||
|
"name": "@meshtastic/transport-http",
|
||||||
|
"version": "0.2.1",
|
||||||
|
},
|
||||||
|
"packages/transport-node": {
|
||||||
|
"name": "@meshtastic/transport-node",
|
||||||
|
"version": "0.0.1",
|
||||||
|
},
|
||||||
|
"packages/transport-web-bluetooth": {
|
||||||
|
"name": "@meshtastic/transport-web-bluetooth",
|
||||||
|
"version": "0.1.2",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/web-bluetooth": "npm:@types/web-bluetooth@^0.0.20",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages/transport-web-serial": {
|
||||||
|
"name": "@meshtastic/transport-web-serial",
|
||||||
|
"version": "0.2.1",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/w3c-web-serial": "npm:@types/w3c-web-serial@^1.0.7",
|
||||||
|
},
|
||||||
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "meshtastic-web",
|
"name": "meshtastic-web",
|
||||||
"version": "2.7.0-0",
|
"version": "2.7.0-0",
|
||||||
@@ -167,25 +206,25 @@
|
|||||||
|
|
||||||
"@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="],
|
"@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="],
|
||||||
|
|
||||||
"@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="],
|
"@biomejs/biome": ["@biomejs/biome@2.0.6", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.0.6", "@biomejs/cli-darwin-x64": "2.0.6", "@biomejs/cli-linux-arm64": "2.0.6", "@biomejs/cli-linux-arm64-musl": "2.0.6", "@biomejs/cli-linux-x64": "2.0.6", "@biomejs/cli-linux-x64-musl": "2.0.6", "@biomejs/cli-win32-arm64": "2.0.6", "@biomejs/cli-win32-x64": "2.0.6" }, "bin": { "biome": "bin/biome" } }, "sha512-RRP+9cdh5qwe2t0gORwXaa27oTOiQRQvrFf49x2PA1tnpsyU7FIHX4ZOFMtBC4QNtyWsN7Dqkf5EDbg4X+9iqA=="],
|
||||||
|
|
||||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="],
|
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.0.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-AzdiNNjNzsE6LfqWyBvcL29uWoIuZUkndu+wwlXW13EKcBHbbKjNQEZIJKYDc6IL+p7bmWGx3v9ZtcRyIoIz5A=="],
|
||||||
|
|
||||||
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="],
|
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.0.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-wJjjP4E7bO4WJmiQaLnsdXMa516dbtC6542qeRkyJg0MqMXP0fvs4gdsHhZ7p9XWTAmGIjZHFKXdsjBvKGIJJQ=="],
|
||||||
|
|
||||||
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="],
|
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.0.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZSVf6TYo5rNMUHIW1tww+rs/krol7U5A1Is/yzWyHVZguuB0lBnIodqyFuwCNqG9aJGyk7xIMS8HG0qGUPz0SA=="],
|
||||||
|
|
||||||
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="],
|
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.0.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-CVPEMlin3bW49sBqLBg2x016Pws7eUXA27XYDFlEtponD0luYjg2zQaMJ2nOqlkKG9fqzzkamdYxHdMDc2gZFw=="],
|
||||||
|
|
||||||
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="],
|
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.0.6", "", { "os": "linux", "cpu": "x64" }, "sha512-geM1MkHTV1Kh2Cs/Xzot9BOF3WBacihw6bkEmxkz4nSga8B9/hWy5BDiOG3gHDGIBa8WxT0nzsJs2f/hPqQIQw=="],
|
||||||
|
|
||||||
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="],
|
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.0.6", "", { "os": "linux", "cpu": "x64" }, "sha512-mKHE/e954hR/hSnAcJSjkf4xGqZc/53Kh39HVW1EgO5iFi0JutTN07TSjEMg616julRtfSNJi0KNyxvc30Y4rQ=="],
|
||||||
|
|
||||||
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="],
|
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.0.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-290V4oSFoKaprKE1zkYVsDfAdn0An5DowZ+GIABgjoq1ndhvNxkJcpxPsiYtT7slbVe3xmlT0ncdfOsN7KruzA=="],
|
||||||
|
|
||||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="],
|
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.0.6", "", { "os": "win32", "cpu": "x64" }, "sha512-bfM1Bce0d69Ao7pjTjUS+AWSZ02+5UHdiAP85Th8e9yV5xzw6JrHXbL5YWlcEKQ84FIZMdDc7ncuti1wd2sdbw=="],
|
||||||
|
|
||||||
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.6.0", "", {}, "sha512-6cuonJVNOIL7lTj5zgo/Rc2bKAo4/GvN+rKCrUj7GdEHRzCk8zKOfFwUsL9nAVk5rSIsRmlgcpLzTRysopEeeg=="],
|
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.6.1", "", {}, "sha512-DaG6XlyKpz08bmHY5SGX2gfIllaqtDJ/KwVoxsmP22COOLYwDBe7yD3DZGwXem/Xq7QOc9cuR7R3MpAv5CFfDw=="],
|
||||||
|
|
||||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.6", "", { "os": "aix", "cpu": "ppc64" }, "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw=="],
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.6", "", { "os": "aix", "cpu": "ppc64" }, "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw=="],
|
||||||
|
|
||||||
@@ -281,13 +320,19 @@
|
|||||||
|
|
||||||
"@maplibre/maplibre-gl-style-spec": ["@maplibre/maplibre-gl-style-spec@23.3.0", "", { "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", "@mapbox/unitbezier": "^0.0.1", "json-stringify-pretty-compact": "^4.0.0", "minimist": "^1.2.8", "quickselect": "^3.0.0", "rw": "^1.3.3", "tinyqueue": "^3.0.0" }, "bin": { "gl-style-format": "dist/gl-style-format.mjs", "gl-style-migrate": "dist/gl-style-migrate.mjs", "gl-style-validate": "dist/gl-style-validate.mjs" } }, "sha512-IGJtuBbaGzOUgODdBRg66p8stnwj9iDXkgbYKoYcNiiQmaez5WVRfXm4b03MCDwmZyX93csbfHFWEJJYHnn5oA=="],
|
"@maplibre/maplibre-gl-style-spec": ["@maplibre/maplibre-gl-style-spec@23.3.0", "", { "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", "@mapbox/unitbezier": "^0.0.1", "json-stringify-pretty-compact": "^4.0.0", "minimist": "^1.2.8", "quickselect": "^3.0.0", "rw": "^1.3.3", "tinyqueue": "^3.0.0" }, "bin": { "gl-style-format": "dist/gl-style-format.mjs", "gl-style-migrate": "dist/gl-style-migrate.mjs", "gl-style-validate": "dist/gl-style-validate.mjs" } }, "sha512-IGJtuBbaGzOUgODdBRg66p8stnwj9iDXkgbYKoYcNiiQmaez5WVRfXm4b03MCDwmZyX93csbfHFWEJJYHnn5oA=="],
|
||||||
|
|
||||||
"@meshtastic/core": ["@jsr/meshtastic__core@2.6.4", "https://npm.jsr.io/~/11/@jsr/meshtastic__core/2.6.4.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3", "@jsr/meshtastic__protobufs": "^2.6.2", "crc": "^4.3.2", "ste-simple-events": "^3.0.11", "tslog": "^4.9.3" } }, "sha512-1Kz5DK6peFxluHOJR38vFwfgeJzMXTz+3p6TvibjILVhSQC2U1nu8aJbn6w5zhRqS+j79OmtrRvdzL6VNsTkkQ=="],
|
"@meshtastic/core": ["@meshtastic/core@workspace:packages/core"],
|
||||||
|
|
||||||
"@meshtastic/transport-http": ["@jsr/meshtastic__transport-http@0.2.1", "https://npm.jsr.io/~/11/@jsr/meshtastic__transport-http/0.2.1.tgz", { "dependencies": { "@jsr/meshtastic__core": "^2.6.0" } }, "sha512-lmQKr3aIINKvtGROU4HchmSVqbZSbkIHqajowRRC8IAjsnR0zNTyxz210QyY4pFUF9hpcW3GRjwq5h/VO2JuGg=="],
|
"@meshtastic/protobufs": ["@jsr/meshtastic__protobufs@2.7.0", "https://npm.jsr.io/~/11/@jsr/meshtastic__protobufs/2.7.0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3" } }, "sha512-ndZhUyB/ADSyjJI+iSeSOoIKqNGZ2+ERVjfY0qnh4jgF740tFTwefC5mzZhOqDLbreGFYS79+429NtH5Ujdzdg=="],
|
||||||
|
|
||||||
"@meshtastic/transport-web-bluetooth": ["@jsr/meshtastic__transport-web-bluetooth@0.1.2", "https://npm.jsr.io/~/11/@jsr/meshtastic__transport-web-bluetooth/0.1.2.tgz", { "dependencies": { "@jsr/meshtastic__core": "^2.6.4" } }, "sha512-Z+5pv9RXNgY0/crKExOH3pZ6LT0HIXFmnBL7NX5AO2knOFRn+4lmxQEhhmiTTlkUfqyEfAvbjuY5u4mq9TPTdQ=="],
|
"@meshtastic/transport-deno": ["@meshtastic/transport-deno@workspace:packages/transport-deno"],
|
||||||
|
|
||||||
"@meshtastic/transport-web-serial": ["@jsr/meshtastic__transport-web-serial@0.2.1", "https://npm.jsr.io/~/11/@jsr/meshtastic__transport-web-serial/0.2.1.tgz", { "dependencies": { "@jsr/meshtastic__core": "^2.6.0" } }, "sha512-yumjEGLkAuJYOC3aWKvZzbQqi/LnqaKfNpVCY7Ki7oLtAshNiZrBLiwiFhN7+ZR9FfMdJThyBMqREBDRRWTO1Q=="],
|
"@meshtastic/transport-http": ["@meshtastic/transport-http@workspace:packages/transport-http"],
|
||||||
|
|
||||||
|
"@meshtastic/transport-node": ["@meshtastic/transport-node@workspace:packages/transport-node"],
|
||||||
|
|
||||||
|
"@meshtastic/transport-web-bluetooth": ["@meshtastic/transport-web-bluetooth@workspace:packages/transport-web-bluetooth"],
|
||||||
|
|
||||||
|
"@meshtastic/transport-web-serial": ["@meshtastic/transport-web-serial@workspace:packages/transport-web-serial"],
|
||||||
|
|
||||||
"@noble/curves": ["@noble/curves@1.9.2", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g=="],
|
"@noble/curves": ["@noble/curves@1.9.2", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g=="],
|
||||||
|
|
||||||
@@ -781,7 +826,7 @@
|
|||||||
|
|
||||||
"@types/mapbox__vector-tile": ["@types/mapbox__vector-tile@1.3.4", "", { "dependencies": { "@types/geojson": "*", "@types/mapbox__point-geometry": "*", "@types/pbf": "*" } }, "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg=="],
|
"@types/mapbox__vector-tile": ["@types/mapbox__vector-tile@1.3.4", "", { "dependencies": { "@types/geojson": "*", "@types/mapbox__point-geometry": "*", "@types/pbf": "*" } }, "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.0.14", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw=="],
|
"@types/node": ["@types/node@22.16.4", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-PYRhNtZdm2wH/NT2k/oAJ6/f2VD2N2Dag0lGlx2vWgMSJXGNmlce5MiTQzoWAiIJtso30mjnfQCOKVH+kAQC/g=="],
|
||||||
|
|
||||||
"@types/pbf": ["@types/pbf@3.0.5", "", {}, "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA=="],
|
"@types/pbf": ["@types/pbf@3.0.5", "", {}, "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA=="],
|
||||||
|
|
||||||
@@ -795,7 +840,7 @@
|
|||||||
|
|
||||||
"@types/w3c-web-serial": ["@types/w3c-web-serial@1.0.8", "", {}, "sha512-QQOT+bxQJhRGXoZDZGLs3ksLud1dMNnMiSQtBA0w8KXvLpXX4oM4TZb6J0GgJ8UbCaHo5s9/4VQT8uXy9JER2A=="],
|
"@types/w3c-web-serial": ["@types/w3c-web-serial@1.0.8", "", {}, "sha512-QQOT+bxQJhRGXoZDZGLs3ksLud1dMNnMiSQtBA0w8KXvLpXX4oM4TZb6J0GgJ8UbCaHo5s9/4VQT8uXy9JER2A=="],
|
||||||
|
|
||||||
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.20", "", {}, "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="],
|
||||||
|
|
||||||
"@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="],
|
"@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="],
|
||||||
|
|
||||||
@@ -1335,7 +1380,7 @@
|
|||||||
|
|
||||||
"typewise-core": ["typewise-core@1.2.0", "", {}, "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg=="],
|
"typewise-core": ["typewise-core@1.2.0", "", {}, "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||||
|
|
||||||
"union-value": ["union-value@1.0.1", "", { "dependencies": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", "set-value": "^2.0.1" } }, "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg=="],
|
"union-value": ["union-value@1.0.1", "", { "dependencies": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", "set-value": "^2.0.1" } }, "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg=="],
|
||||||
|
|
||||||
@@ -1393,6 +1438,12 @@
|
|||||||
|
|
||||||
"zustand": ["zustand@5.0.6", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A=="],
|
"zustand": ["zustand@5.0.6", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A=="],
|
||||||
|
|
||||||
|
"@jsr/meshtastic__core/@bufbuild/protobuf": ["@bufbuild/protobuf@2.6.0", "", {}, "sha512-6cuonJVNOIL7lTj5zgo/Rc2bKAo4/GvN+rKCrUj7GdEHRzCk8zKOfFwUsL9nAVk5rSIsRmlgcpLzTRysopEeeg=="],
|
||||||
|
|
||||||
|
"@jsr/meshtastic__protobufs/@bufbuild/protobuf": ["@bufbuild/protobuf@2.6.0", "", {}, "sha512-6cuonJVNOIL7lTj5zgo/Rc2bKAo4/GvN+rKCrUj7GdEHRzCk8zKOfFwUsL9nAVk5rSIsRmlgcpLzTRysopEeeg=="],
|
||||||
|
|
||||||
|
"@meshtastic/protobufs/@bufbuild/protobuf": ["@bufbuild/protobuf@2.6.0", "", {}, "sha512-6cuonJVNOIL7lTj5zgo/Rc2bKAo4/GvN+rKCrUj7GdEHRzCk8zKOfFwUsL9nAVk5rSIsRmlgcpLzTRysopEeeg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.4", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.4", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg=="],
|
||||||
@@ -1645,7 +1696,15 @@
|
|||||||
|
|
||||||
"lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
"lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
"meshtastic-web/@biomejs/biome": ["@biomejs/biome@2.0.6", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.0.6", "@biomejs/cli-darwin-x64": "2.0.6", "@biomejs/cli-linux-arm64": "2.0.6", "@biomejs/cli-linux-arm64-musl": "2.0.6", "@biomejs/cli-linux-x64": "2.0.6", "@biomejs/cli-linux-x64-musl": "2.0.6", "@biomejs/cli-win32-arm64": "2.0.6", "@biomejs/cli-win32-x64": "2.0.6" }, "bin": { "biome": "bin/biome" } }, "sha512-RRP+9cdh5qwe2t0gORwXaa27oTOiQRQvrFf49x2PA1tnpsyU7FIHX4ZOFMtBC4QNtyWsN7Dqkf5EDbg4X+9iqA=="],
|
"meshtastic-web/@meshtastic/core": ["@jsr/meshtastic__core@2.6.4", "https://npm.jsr.io/~/11/@jsr/meshtastic__core/2.6.4.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3", "@jsr/meshtastic__protobufs": "^2.6.2", "crc": "^4.3.2", "ste-simple-events": "^3.0.11", "tslog": "^4.9.3" } }, "sha512-1Kz5DK6peFxluHOJR38vFwfgeJzMXTz+3p6TvibjILVhSQC2U1nu8aJbn6w5zhRqS+j79OmtrRvdzL6VNsTkkQ=="],
|
||||||
|
|
||||||
|
"meshtastic-web/@meshtastic/transport-http": ["@jsr/meshtastic__transport-http@0.2.1", "https://npm.jsr.io/~/11/@jsr/meshtastic__transport-http/0.2.1.tgz", { "dependencies": { "@jsr/meshtastic__core": "^2.6.0" } }, "sha512-lmQKr3aIINKvtGROU4HchmSVqbZSbkIHqajowRRC8IAjsnR0zNTyxz210QyY4pFUF9hpcW3GRjwq5h/VO2JuGg=="],
|
||||||
|
|
||||||
|
"meshtastic-web/@meshtastic/transport-web-bluetooth": ["@jsr/meshtastic__transport-web-bluetooth@0.1.2", "https://npm.jsr.io/~/11/@jsr/meshtastic__transport-web-bluetooth/0.1.2.tgz", { "dependencies": { "@jsr/meshtastic__core": "^2.6.4" } }, "sha512-Z+5pv9RXNgY0/crKExOH3pZ6LT0HIXFmnBL7NX5AO2knOFRn+4lmxQEhhmiTTlkUfqyEfAvbjuY5u4mq9TPTdQ=="],
|
||||||
|
|
||||||
|
"meshtastic-web/@meshtastic/transport-web-serial": ["@jsr/meshtastic__transport-web-serial@0.2.1", "https://npm.jsr.io/~/11/@jsr/meshtastic__transport-web-serial/0.2.1.tgz", { "dependencies": { "@jsr/meshtastic__core": "^2.6.0" } }, "sha512-yumjEGLkAuJYOC3aWKvZzbQqi/LnqaKfNpVCY7Ki7oLtAshNiZrBLiwiFhN7+ZR9FfMdJThyBMqREBDRRWTO1Q=="],
|
||||||
|
|
||||||
|
"meshtastic-web/@types/node": ["@types/node@24.0.14", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw=="],
|
||||||
|
|
||||||
"peek-stream/through2": ["through2@2.0.5", "", { "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ=="],
|
"peek-stream/through2": ["through2@2.0.5", "", { "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ=="],
|
||||||
|
|
||||||
@@ -1689,23 +1748,9 @@
|
|||||||
|
|
||||||
"geojson-polygon-self-intersections/rbush/quickselect": ["quickselect@1.1.1", "", {}, "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ=="],
|
"geojson-polygon-self-intersections/rbush/quickselect": ["quickselect@1.1.1", "", {}, "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ=="],
|
||||||
|
|
||||||
"happy-dom/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
"meshtastic-web/@meshtastic/core/@bufbuild/protobuf": ["@bufbuild/protobuf@2.6.0", "", {}, "sha512-6cuonJVNOIL7lTj5zgo/Rc2bKAo4/GvN+rKCrUj7GdEHRzCk8zKOfFwUsL9nAVk5rSIsRmlgcpLzTRysopEeeg=="],
|
||||||
|
|
||||||
"meshtastic-web/@biomejs/biome/@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.0.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-AzdiNNjNzsE6LfqWyBvcL29uWoIuZUkndu+wwlXW13EKcBHbbKjNQEZIJKYDc6IL+p7bmWGx3v9ZtcRyIoIz5A=="],
|
"meshtastic-web/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||||
|
|
||||||
"meshtastic-web/@biomejs/biome/@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.0.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-wJjjP4E7bO4WJmiQaLnsdXMa516dbtC6542qeRkyJg0MqMXP0fvs4gdsHhZ7p9XWTAmGIjZHFKXdsjBvKGIJJQ=="],
|
|
||||||
|
|
||||||
"meshtastic-web/@biomejs/biome/@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.0.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZSVf6TYo5rNMUHIW1tww+rs/krol7U5A1Is/yzWyHVZguuB0lBnIodqyFuwCNqG9aJGyk7xIMS8HG0qGUPz0SA=="],
|
|
||||||
|
|
||||||
"meshtastic-web/@biomejs/biome/@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.0.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-CVPEMlin3bW49sBqLBg2x016Pws7eUXA27XYDFlEtponD0luYjg2zQaMJ2nOqlkKG9fqzzkamdYxHdMDc2gZFw=="],
|
|
||||||
|
|
||||||
"meshtastic-web/@biomejs/biome/@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.0.6", "", { "os": "linux", "cpu": "x64" }, "sha512-geM1MkHTV1Kh2Cs/Xzot9BOF3WBacihw6bkEmxkz4nSga8B9/hWy5BDiOG3gHDGIBa8WxT0nzsJs2f/hPqQIQw=="],
|
|
||||||
|
|
||||||
"meshtastic-web/@biomejs/biome/@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.0.6", "", { "os": "linux", "cpu": "x64" }, "sha512-mKHE/e954hR/hSnAcJSjkf4xGqZc/53Kh39HVW1EgO5iFi0JutTN07TSjEMg616julRtfSNJi0KNyxvc30Y4rQ=="],
|
|
||||||
|
|
||||||
"meshtastic-web/@biomejs/biome/@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.0.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-290V4oSFoKaprKE1zkYVsDfAdn0An5DowZ+GIABgjoq1ndhvNxkJcpxPsiYtT7slbVe3xmlT0ncdfOsN7KruzA=="],
|
|
||||||
|
|
||||||
"meshtastic-web/@biomejs/biome/@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.0.6", "", { "os": "win32", "cpu": "x64" }, "sha512-bfM1Bce0d69Ao7pjTjUS+AWSZ02+5UHdiAP85Th8e9yV5xzw6JrHXbL5YWlcEKQ84FIZMdDc7ncuti1wd2sdbw=="],
|
|
||||||
|
|
||||||
"peek-stream/through2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
"peek-stream/through2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||||
|
|
||||||
|
|||||||
+19
-4
@@ -12,13 +12,28 @@
|
|||||||
"url": "https://github.com/meshtastic/web/issues"
|
"url": "https://github.com/meshtastic/web/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://meshtastic.org",
|
"homepage": "https://meshtastic.org",
|
||||||
"workspaces": ["packages/web"],
|
"workspaces": ["packages/*"],
|
||||||
|
"simple-git-hooks": {
|
||||||
|
"pre-commit": "bun run check:fix"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"lint": "biome lint",
|
||||||
|
"lint:fix": "biome lint --write",
|
||||||
|
"format": "biome format",
|
||||||
|
"format:fix": "biome format . --write",
|
||||||
|
"check": "biome check",
|
||||||
|
"check:fix": "biome check --write",
|
||||||
"build:npm": "deno run -A scripts/build_npm_package.ts"
|
"build:npm": "deno run -A scripts/build_npm_package.ts"
|
||||||
},
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@bufbuild/protobuf": "^2.6.1",
|
||||||
|
"@meshtastic/protobufs": "npm:@jsr/meshtastic__protobufs",
|
||||||
|
"ste-simple-events": "^3.0.11",
|
||||||
|
"tslog": "^4.9.3"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.8.3",
|
"bun": "^1.2.18",
|
||||||
"bun": "^1.1.18",
|
"typescript": "^5.8.3",
|
||||||
"typescript": "^5.8.3"
|
"@types/node": "^22.16.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@meshtastic/core",
|
|
||||||
"version": "2.6.4",
|
|
||||||
"description": "Core functionalities for Meshtastic web applications.",
|
|
||||||
"exports": {
|
|
||||||
".": "./mod.ts"
|
|
||||||
},
|
|
||||||
"imports": {
|
|
||||||
"crc": "npm:crc@^4.3.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "@meshtastic/core",
|
||||||
|
"version": "2.6.5",
|
||||||
|
"description": "Core functionalities for Meshtastic web applications.",
|
||||||
|
"exports": {
|
||||||
|
".": "./mod.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"crc": "npm:crc@^4.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,6 @@ const broadcastNum = 0xffffffff;
|
|||||||
const minFwVer = 2.2;
|
const minFwVer = 2.2;
|
||||||
|
|
||||||
export const Constants = {
|
export const Constants = {
|
||||||
broadcastNum,
|
broadcastNum,
|
||||||
minFwVer,
|
minFwVer,
|
||||||
};
|
};
|
||||||
|
|||||||
+1148
-1148
File diff suppressed because it is too large
Load Diff
+84
-84
@@ -1,45 +1,45 @@
|
|||||||
import type * as Protobuf from "@meshtastic/protobufs";
|
import type * as Protobuf from "@meshtastic/protobufs";
|
||||||
|
|
||||||
interface Packet {
|
interface Packet {
|
||||||
type: "packet";
|
type: "packet";
|
||||||
data: Uint8Array;
|
data: Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DebugLog {
|
interface DebugLog {
|
||||||
type: "debug";
|
type: "debug";
|
||||||
data: string;
|
data: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeviceOutput = Packet | DebugLog;
|
export type DeviceOutput = Packet | DebugLog;
|
||||||
|
|
||||||
export interface Transport {
|
export interface Transport {
|
||||||
toDevice: WritableStream<Uint8Array>;
|
toDevice: WritableStream<Uint8Array>;
|
||||||
fromDevice: ReadableStream<DeviceOutput>;
|
fromDevice: ReadableStream<DeviceOutput>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueueItem {
|
export interface QueueItem {
|
||||||
id: number;
|
id: number;
|
||||||
data: Uint8Array;
|
data: Uint8Array;
|
||||||
sent: boolean;
|
sent: boolean;
|
||||||
added: Date;
|
added: Date;
|
||||||
promise: Promise<number>;
|
promise: Promise<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HttpRetryConfig {
|
export interface HttpRetryConfig {
|
||||||
maxRetries: number;
|
maxRetries: number;
|
||||||
initialDelayMs: number;
|
initialDelayMs: number;
|
||||||
maxDelayMs: number;
|
maxDelayMs: number;
|
||||||
backoffFactor: number;
|
backoffFactor: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DeviceStatusEnum {
|
export enum DeviceStatusEnum {
|
||||||
DeviceRestarting = 1,
|
DeviceRestarting = 1,
|
||||||
DeviceDisconnected = 2,
|
DeviceDisconnected = 2,
|
||||||
DeviceConnecting = 3,
|
DeviceConnecting = 3,
|
||||||
DeviceReconnecting = 4,
|
DeviceReconnecting = 4,
|
||||||
DeviceConnected = 5,
|
DeviceConnected = 5,
|
||||||
DeviceConfiguring = 6,
|
DeviceConfiguring = 6,
|
||||||
DeviceConfigured = 7,
|
DeviceConfigured = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LogEventPacket = LogEvent & { date: Date };
|
export type LogEventPacket = LogEvent & { date: Date };
|
||||||
@@ -47,83 +47,83 @@ export type LogEventPacket = LogEvent & { date: Date };
|
|||||||
export type PacketDestination = "broadcast" | "direct";
|
export type PacketDestination = "broadcast" | "direct";
|
||||||
|
|
||||||
export interface PacketMetadata<T> {
|
export interface PacketMetadata<T> {
|
||||||
id: number;
|
id: number;
|
||||||
rxTime: Date;
|
rxTime: Date;
|
||||||
type: PacketDestination;
|
type: PacketDestination;
|
||||||
from: number;
|
from: number;
|
||||||
to: number;
|
to: number;
|
||||||
channel: ChannelNumber;
|
channel: ChannelNumber;
|
||||||
data: T;
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EmitterScope {
|
export enum EmitterScope {
|
||||||
MeshDevice = 1,
|
MeshDevice = 1,
|
||||||
SerialConnection = 2,
|
SerialConnection = 2,
|
||||||
NodeSerialConnection = 3,
|
NodeSerialConnection = 3,
|
||||||
BleConnection = 4,
|
BleConnection = 4,
|
||||||
HttpConnection = 5,
|
HttpConnection = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Emitter {
|
export enum Emitter {
|
||||||
Constructor = 0,
|
Constructor = 0,
|
||||||
SendText = 1,
|
SendText = 1,
|
||||||
SendWaypoint = 2,
|
SendWaypoint = 2,
|
||||||
SendPacket = 3,
|
SendPacket = 3,
|
||||||
SendRaw = 4,
|
SendRaw = 4,
|
||||||
SetConfig = 5,
|
SetConfig = 5,
|
||||||
SetModuleConfig = 6,
|
SetModuleConfig = 6,
|
||||||
ConfirmSetConfig = 7,
|
ConfirmSetConfig = 7,
|
||||||
SetOwner = 8,
|
SetOwner = 8,
|
||||||
SetChannel = 9,
|
SetChannel = 9,
|
||||||
ConfirmSetChannel = 10,
|
ConfirmSetChannel = 10,
|
||||||
ClearChannel = 11,
|
ClearChannel = 11,
|
||||||
GetChannel = 12,
|
GetChannel = 12,
|
||||||
GetAllChannels = 13,
|
GetAllChannels = 13,
|
||||||
GetConfig = 14,
|
GetConfig = 14,
|
||||||
GetModuleConfig = 15,
|
GetModuleConfig = 15,
|
||||||
GetOwner = 16,
|
GetOwner = 16,
|
||||||
Configure = 17,
|
Configure = 17,
|
||||||
HandleFromRadio = 18,
|
HandleFromRadio = 18,
|
||||||
HandleMeshPacket = 19,
|
HandleMeshPacket = 19,
|
||||||
Connect = 20,
|
Connect = 20,
|
||||||
Ping = 21,
|
Ping = 21,
|
||||||
ReadFromRadio = 22,
|
ReadFromRadio = 22,
|
||||||
WriteToRadio = 23,
|
WriteToRadio = 23,
|
||||||
SetDebugMode = 24,
|
SetDebugMode = 24,
|
||||||
GetMetadata = 25,
|
GetMetadata = 25,
|
||||||
ResetNodes = 26,
|
ResetNodes = 26,
|
||||||
Shutdown = 27,
|
Shutdown = 27,
|
||||||
Reboot = 28,
|
Reboot = 28,
|
||||||
RebootOta = 29,
|
RebootOta = 29,
|
||||||
FactoryReset = 30,
|
FactoryReset = 30,
|
||||||
EnterDfuMode = 31,
|
EnterDfuMode = 31,
|
||||||
RemoveNodeByNum = 32,
|
RemoveNodeByNum = 32,
|
||||||
SetCannedMessages = 33,
|
SetCannedMessages = 33,
|
||||||
Disconnect = 34,
|
Disconnect = 34,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LogEvent {
|
export interface LogEvent {
|
||||||
scope: EmitterScope;
|
scope: EmitterScope;
|
||||||
emitter: Emitter;
|
emitter: Emitter;
|
||||||
message: string;
|
message: string;
|
||||||
level: Protobuf.Mesh.LogRecord_Level;
|
level: Protobuf.Mesh.LogRecord_Level;
|
||||||
packet?: Uint8Array;
|
packet?: Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ChannelNumber {
|
export enum ChannelNumber {
|
||||||
Primary = 0,
|
Primary = 0,
|
||||||
Channel1 = 1,
|
Channel1 = 1,
|
||||||
Channel2 = 2,
|
Channel2 = 2,
|
||||||
Channel3 = 3,
|
Channel3 = 3,
|
||||||
Channel4 = 4,
|
Channel4 = 4,
|
||||||
Channel5 = 5,
|
Channel5 = 5,
|
||||||
Channel6 = 6,
|
Channel6 = 6,
|
||||||
Admin = 7,
|
Admin = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Destination = number | "self" | "broadcast";
|
export type Destination = number | "self" | "broadcast";
|
||||||
|
|
||||||
export interface PacketError {
|
export interface PacketError {
|
||||||
id: number;
|
id: number;
|
||||||
error: Protobuf.Mesh.Routing_Error;
|
error: Protobuf.Mesh.Routing_Error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,378 +4,378 @@ import type { PacketMetadata } from "../types.ts";
|
|||||||
import type * as Types from "../types.ts";
|
import type * as Types from "../types.ts";
|
||||||
|
|
||||||
export class EventSystem {
|
export class EventSystem {
|
||||||
/**
|
/**
|
||||||
* Fires when a new FromRadio message has been received from the device
|
* Fires when a new FromRadio message has been received from the device
|
||||||
*
|
*
|
||||||
* @event onLogEvent
|
* @event onLogEvent
|
||||||
*/
|
*/
|
||||||
public readonly onLogEvent: SimpleEventDispatcher<Types.LogEventPacket> =
|
public readonly onLogEvent: SimpleEventDispatcher<Types.LogEventPacket> =
|
||||||
new SimpleEventDispatcher<Types.LogEventPacket>();
|
new SimpleEventDispatcher<Types.LogEventPacket>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new FromRadio message has been received from the device
|
* Fires when a new FromRadio message has been received from the device
|
||||||
*
|
*
|
||||||
* @event onFromRadio
|
* @event onFromRadio
|
||||||
*/
|
*/
|
||||||
public readonly onFromRadio: SimpleEventDispatcher<Protobuf.Mesh.FromRadio> =
|
public readonly onFromRadio: SimpleEventDispatcher<Protobuf.Mesh.FromRadio> =
|
||||||
new SimpleEventDispatcher<Protobuf.Mesh.FromRadio>();
|
new SimpleEventDispatcher<Protobuf.Mesh.FromRadio>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new FromRadio message containing a Data packet has been
|
* Fires when a new FromRadio message containing a Data packet has been
|
||||||
* received from the device
|
* received from the device
|
||||||
*
|
*
|
||||||
* @event onMeshPacket
|
* @event onMeshPacket
|
||||||
*/
|
*/
|
||||||
public readonly onMeshPacket: SimpleEventDispatcher<Protobuf.Mesh.MeshPacket> =
|
public readonly onMeshPacket: SimpleEventDispatcher<Protobuf.Mesh.MeshPacket> =
|
||||||
new SimpleEventDispatcher<Protobuf.Mesh.MeshPacket>();
|
new SimpleEventDispatcher<Protobuf.Mesh.MeshPacket>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MyNodeInfo message has been received from the device
|
* Fires when a new MyNodeInfo message has been received from the device
|
||||||
*
|
*
|
||||||
* @event onMyNodeInfo
|
* @event onMyNodeInfo
|
||||||
*/
|
*/
|
||||||
public readonly onMyNodeInfo: SimpleEventDispatcher<Protobuf.Mesh.MyNodeInfo> =
|
public readonly onMyNodeInfo: SimpleEventDispatcher<Protobuf.Mesh.MyNodeInfo> =
|
||||||
new SimpleEventDispatcher<Protobuf.Mesh.MyNodeInfo>();
|
new SimpleEventDispatcher<Protobuf.Mesh.MyNodeInfo>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a NodeInfo packet has been
|
* Fires when a new MeshPacket message containing a NodeInfo packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onNodeInfoPacket
|
* @event onNodeInfoPacket
|
||||||
*/
|
*/
|
||||||
public readonly onNodeInfoPacket: SimpleEventDispatcher<Protobuf.Mesh.NodeInfo> =
|
public readonly onNodeInfoPacket: SimpleEventDispatcher<Protobuf.Mesh.NodeInfo> =
|
||||||
new SimpleEventDispatcher<Protobuf.Mesh.NodeInfo>();
|
new SimpleEventDispatcher<Protobuf.Mesh.NodeInfo>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new Channel message is received
|
* Fires when a new Channel message is received
|
||||||
*
|
*
|
||||||
* @event onChannelPacket
|
* @event onChannelPacket
|
||||||
*/
|
*/
|
||||||
public readonly onChannelPacket: SimpleEventDispatcher<Protobuf.Channel.Channel> =
|
public readonly onChannelPacket: SimpleEventDispatcher<Protobuf.Channel.Channel> =
|
||||||
new SimpleEventDispatcher<Protobuf.Channel.Channel>();
|
new SimpleEventDispatcher<Protobuf.Channel.Channel>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new Config message is received
|
* Fires when a new Config message is received
|
||||||
*
|
*
|
||||||
* @event onConfigPacket
|
* @event onConfigPacket
|
||||||
*/
|
*/
|
||||||
public readonly onConfigPacket: SimpleEventDispatcher<Protobuf.Config.Config> =
|
public readonly onConfigPacket: SimpleEventDispatcher<Protobuf.Config.Config> =
|
||||||
new SimpleEventDispatcher<Protobuf.Config.Config>();
|
new SimpleEventDispatcher<Protobuf.Config.Config>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new ModuleConfig message is received
|
* Fires when a new ModuleConfig message is received
|
||||||
*
|
*
|
||||||
* @event onModuleConfigPacket
|
* @event onModuleConfigPacket
|
||||||
*/
|
*/
|
||||||
public readonly onModuleConfigPacket: SimpleEventDispatcher<Protobuf.ModuleConfig.ModuleConfig> =
|
public readonly onModuleConfigPacket: SimpleEventDispatcher<Protobuf.ModuleConfig.ModuleConfig> =
|
||||||
new SimpleEventDispatcher<Protobuf.ModuleConfig.ModuleConfig>();
|
new SimpleEventDispatcher<Protobuf.ModuleConfig.ModuleConfig>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a ATAK packet has been
|
* Fires when a new MeshPacket message containing a ATAK packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onAtakPacket
|
* @event onAtakPacket
|
||||||
*/
|
*/
|
||||||
public readonly onAtakPacket: SimpleEventDispatcher<
|
public readonly onAtakPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Text packet has been
|
* Fires when a new MeshPacket message containing a Text packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onMessagePacket
|
* @event onMessagePacket
|
||||||
*/
|
*/
|
||||||
public readonly onMessagePacket: SimpleEventDispatcher<
|
public readonly onMessagePacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<string>
|
PacketMetadata<string>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<string>>();
|
> = new SimpleEventDispatcher<PacketMetadata<string>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Remote Hardware packet has
|
* Fires when a new MeshPacket message containing a Remote Hardware packet has
|
||||||
* been received from device
|
* been received from device
|
||||||
*
|
*
|
||||||
* @event onRemoteHardwarePacket
|
* @event onRemoteHardwarePacket
|
||||||
*/
|
*/
|
||||||
public readonly onRemoteHardwarePacket: SimpleEventDispatcher<
|
public readonly onRemoteHardwarePacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Protobuf.RemoteHardware.HardwareMessage>
|
PacketMetadata<Protobuf.RemoteHardware.HardwareMessage>
|
||||||
> = new SimpleEventDispatcher<
|
> = new SimpleEventDispatcher<
|
||||||
PacketMetadata<Protobuf.RemoteHardware.HardwareMessage>
|
PacketMetadata<Protobuf.RemoteHardware.HardwareMessage>
|
||||||
>();
|
>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Position packet has been
|
* Fires when a new MeshPacket message containing a Position packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onPositionPacket
|
* @event onPositionPacket
|
||||||
*/
|
*/
|
||||||
public readonly onPositionPacket: SimpleEventDispatcher<
|
public readonly onPositionPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Protobuf.Mesh.Position>
|
PacketMetadata<Protobuf.Mesh.Position>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.Position>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.Position>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a User packet has been
|
* Fires when a new MeshPacket message containing a User packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onUserPacket
|
* @event onUserPacket
|
||||||
*/
|
*/
|
||||||
public readonly onUserPacket: SimpleEventDispatcher<
|
public readonly onUserPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Protobuf.Mesh.User>
|
PacketMetadata<Protobuf.Mesh.User>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.User>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.User>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Routing packet has been
|
* Fires when a new MeshPacket message containing a Routing packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onRoutingPacket
|
* @event onRoutingPacket
|
||||||
*/
|
*/
|
||||||
public readonly onRoutingPacket: SimpleEventDispatcher<
|
public readonly onRoutingPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Protobuf.Mesh.Routing>
|
PacketMetadata<Protobuf.Mesh.Routing>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.Routing>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.Routing>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when the device receives a Metadata packet
|
* Fires when the device receives a Metadata packet
|
||||||
*
|
*
|
||||||
* @event onDeviceMetadataPacket
|
* @event onDeviceMetadataPacket
|
||||||
*/
|
*/
|
||||||
public readonly onDeviceMetadataPacket: SimpleEventDispatcher<
|
public readonly onDeviceMetadataPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Protobuf.Mesh.DeviceMetadata>
|
PacketMetadata<Protobuf.Mesh.DeviceMetadata>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.DeviceMetadata>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.DeviceMetadata>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when the device receives a Canned Message Module message packet
|
* Fires when the device receives a Canned Message Module message packet
|
||||||
*
|
*
|
||||||
* @event onCannedMessageModulePacket
|
* @event onCannedMessageModulePacket
|
||||||
*/
|
*/
|
||||||
public readonly onCannedMessageModulePacket: SimpleEventDispatcher<
|
public readonly onCannedMessageModulePacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<string>
|
PacketMetadata<string>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<string>>();
|
> = new SimpleEventDispatcher<PacketMetadata<string>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Waypoint packet has been
|
* Fires when a new MeshPacket message containing a Waypoint packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onWaypointPacket
|
* @event onWaypointPacket
|
||||||
*/
|
*/
|
||||||
public readonly onWaypointPacket: SimpleEventDispatcher<
|
public readonly onWaypointPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Protobuf.Mesh.Waypoint>
|
PacketMetadata<Protobuf.Mesh.Waypoint>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.Waypoint>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.Waypoint>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing an Audio packet has been
|
* Fires when a new MeshPacket message containing an Audio packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onAudioPacket
|
* @event onAudioPacket
|
||||||
*/
|
*/
|
||||||
public readonly onAudioPacket: SimpleEventDispatcher<
|
public readonly onAudioPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Detection Sensor packet has been
|
* Fires when a new MeshPacket message containing a Detection Sensor packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onDetectionSensorPacket
|
* @event onDetectionSensorPacket
|
||||||
*/
|
*/
|
||||||
public readonly onDetectionSensorPacket: SimpleEventDispatcher<
|
public readonly onDetectionSensorPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Ping packet has been
|
* Fires when a new MeshPacket message containing a Ping packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onPingPacket
|
* @event onPingPacket
|
||||||
*/
|
*/
|
||||||
public readonly onPingPacket: SimpleEventDispatcher<
|
public readonly onPingPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a IP Tunnel packet has been
|
* Fires when a new MeshPacket message containing a IP Tunnel packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onIpTunnelPacket
|
* @event onIpTunnelPacket
|
||||||
*/
|
*/
|
||||||
public readonly onIpTunnelPacket: SimpleEventDispatcher<
|
public readonly onIpTunnelPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Paxcounter packet has been
|
* Fires when a new MeshPacket message containing a Paxcounter packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onPaxcounterPacket
|
* @event onPaxcounterPacket
|
||||||
*/
|
*/
|
||||||
public readonly onPaxcounterPacket: SimpleEventDispatcher<
|
public readonly onPaxcounterPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Protobuf.PaxCount.Paxcount>
|
PacketMetadata<Protobuf.PaxCount.Paxcount>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.PaxCount.Paxcount>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.PaxCount.Paxcount>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Serial packet has been
|
* Fires when a new MeshPacket message containing a Serial packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onSerialPacket
|
* @event onSerialPacket
|
||||||
*/
|
*/
|
||||||
public readonly onSerialPacket: SimpleEventDispatcher<
|
public readonly onSerialPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Store and Forward packet
|
* Fires when a new MeshPacket message containing a Store and Forward packet
|
||||||
* has been received from device
|
* has been received from device
|
||||||
*
|
*
|
||||||
* @event onStoreForwardPacket
|
* @event onStoreForwardPacket
|
||||||
*/
|
*/
|
||||||
public readonly onStoreForwardPacket: SimpleEventDispatcher<
|
public readonly onStoreForwardPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Store and Forward packet
|
* Fires when a new MeshPacket message containing a Store and Forward packet
|
||||||
* has been received from device
|
* has been received from device
|
||||||
*
|
*
|
||||||
* @event onRangeTestPacket
|
* @event onRangeTestPacket
|
||||||
*/
|
*/
|
||||||
public readonly onRangeTestPacket: SimpleEventDispatcher<
|
public readonly onRangeTestPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Telemetry packet has been
|
* Fires when a new MeshPacket message containing a Telemetry packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onTelemetryPacket
|
* @event onTelemetryPacket
|
||||||
*/
|
*/
|
||||||
public readonly onTelemetryPacket: SimpleEventDispatcher<
|
public readonly onTelemetryPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Protobuf.Telemetry.Telemetry>
|
PacketMetadata<Protobuf.Telemetry.Telemetry>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Telemetry.Telemetry>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Telemetry.Telemetry>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a ZPS packet has been
|
* Fires when a new MeshPacket message containing a ZPS packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onZPSPacket
|
* @event onZPSPacket
|
||||||
*/
|
*/
|
||||||
public readonly onZpsPacket: SimpleEventDispatcher<
|
public readonly onZpsPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Simulator packet has been
|
* Fires when a new MeshPacket message containing a Simulator packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onSimulatorPacket
|
* @event onSimulatorPacket
|
||||||
*/
|
*/
|
||||||
public readonly onSimulatorPacket: SimpleEventDispatcher<
|
public readonly onSimulatorPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Trace Route packet has been
|
* Fires when a new MeshPacket message containing a Trace Route packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onTraceRoutePacket
|
* @event onTraceRoutePacket
|
||||||
*/
|
*/
|
||||||
public readonly onTraceRoutePacket: SimpleEventDispatcher<
|
public readonly onTraceRoutePacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Protobuf.Mesh.RouteDiscovery>
|
PacketMetadata<Protobuf.Mesh.RouteDiscovery>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.RouteDiscovery>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.RouteDiscovery>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Neighbor Info packet has been
|
* Fires when a new MeshPacket message containing a Neighbor Info packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onNeighborInfoPacket
|
* @event onNeighborInfoPacket
|
||||||
*/
|
*/
|
||||||
public readonly onNeighborInfoPacket: SimpleEventDispatcher<
|
public readonly onNeighborInfoPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Protobuf.Mesh.NeighborInfo>
|
PacketMetadata<Protobuf.Mesh.NeighborInfo>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.NeighborInfo>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Protobuf.Mesh.NeighborInfo>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing an ATAK packet has been
|
* Fires when a new MeshPacket message containing an ATAK packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onAtakPluginPacket
|
* @event onAtakPluginPacket
|
||||||
*/
|
*/
|
||||||
public readonly onAtakPluginPacket: SimpleEventDispatcher<
|
public readonly onAtakPluginPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Map Report packet has been
|
* Fires when a new MeshPacket message containing a Map Report packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onMapReportPacket
|
* @event onMapReportPacket
|
||||||
*/
|
*/
|
||||||
public readonly onMapReportPacket: SimpleEventDispatcher<
|
public readonly onMapReportPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing a Private packet has been
|
* Fires when a new MeshPacket message containing a Private packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onPrivatePacket
|
* @event onPrivatePacket
|
||||||
*/
|
*/
|
||||||
public readonly onPrivatePacket: SimpleEventDispatcher<
|
public readonly onPrivatePacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new MeshPacket message containing an ATAK Forwarder packet has been
|
* Fires when a new MeshPacket message containing an ATAK Forwarder packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onAtakForwarderPacket
|
* @event onAtakForwarderPacket
|
||||||
*/
|
*/
|
||||||
public readonly onAtakForwarderPacket: SimpleEventDispatcher<
|
public readonly onAtakForwarderPacket: SimpleEventDispatcher<
|
||||||
PacketMetadata<Uint8Array>
|
PacketMetadata<Uint8Array>
|
||||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when the devices connection or configuration status changes
|
* Fires when the devices connection or configuration status changes
|
||||||
*
|
*
|
||||||
* @event onDeviceStatus
|
* @event onDeviceStatus
|
||||||
*/
|
*/
|
||||||
public readonly onDeviceStatus: SimpleEventDispatcher<Types.DeviceStatusEnum> =
|
public readonly onDeviceStatus: SimpleEventDispatcher<Types.DeviceStatusEnum> =
|
||||||
new SimpleEventDispatcher<Types.DeviceStatusEnum>();
|
new SimpleEventDispatcher<Types.DeviceStatusEnum>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a new FromRadio message containing a LogRecord packet has been
|
* Fires when a new FromRadio message containing a LogRecord packet has been
|
||||||
* received from device
|
* received from device
|
||||||
*
|
*
|
||||||
* @event onLogRecord
|
* @event onLogRecord
|
||||||
*/
|
*/
|
||||||
public readonly onLogRecord: SimpleEventDispatcher<Protobuf.Mesh.LogRecord> =
|
public readonly onLogRecord: SimpleEventDispatcher<Protobuf.Mesh.LogRecord> =
|
||||||
new SimpleEventDispatcher<Protobuf.Mesh.LogRecord>();
|
new SimpleEventDispatcher<Protobuf.Mesh.LogRecord>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when the device receives a meshPacket, returns a timestamp
|
* Fires when the device receives a meshPacket, returns a timestamp
|
||||||
*
|
*
|
||||||
* @event onMeshHeartbeat
|
* @event onMeshHeartbeat
|
||||||
*/
|
*/
|
||||||
public readonly onMeshHeartbeat: SimpleEventDispatcher<Date> =
|
public readonly onMeshHeartbeat: SimpleEventDispatcher<Date> =
|
||||||
new SimpleEventDispatcher<Date>();
|
new SimpleEventDispatcher<Date>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outputs any debug log data (currently serial connections only)
|
* Outputs any debug log data (currently serial connections only)
|
||||||
*
|
*
|
||||||
* @event onDeviceDebugLog
|
* @event onDeviceDebugLog
|
||||||
*/
|
*/
|
||||||
public readonly onDeviceDebugLog: SimpleEventDispatcher<Uint8Array> =
|
public readonly onDeviceDebugLog: SimpleEventDispatcher<Uint8Array> =
|
||||||
new SimpleEventDispatcher<Uint8Array>();
|
new SimpleEventDispatcher<Uint8Array>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outputs status of pending settings changes
|
* Outputs status of pending settings changes
|
||||||
*
|
*
|
||||||
* @event onpendingSettingsChange
|
* @event onpendingSettingsChange
|
||||||
*/
|
*/
|
||||||
public readonly onPendingSettingsChange: SimpleEventDispatcher<boolean> =
|
public readonly onPendingSettingsChange: SimpleEventDispatcher<boolean> =
|
||||||
new SimpleEventDispatcher<boolean>();
|
new SimpleEventDispatcher<boolean>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when a QueueStatus message is generated
|
* Fires when a QueueStatus message is generated
|
||||||
*
|
*
|
||||||
* @event onQueueStatus
|
* @event onQueueStatus
|
||||||
*/
|
*/
|
||||||
public readonly onQueueStatus: SimpleEventDispatcher<Protobuf.Mesh.QueueStatus> =
|
public readonly onQueueStatus: SimpleEventDispatcher<Protobuf.Mesh.QueueStatus> =
|
||||||
new SimpleEventDispatcher<Protobuf.Mesh.QueueStatus>();
|
new SimpleEventDispatcher<Protobuf.Mesh.QueueStatus>();
|
||||||
}
|
}
|
||||||
|
|||||||
+100
-100
@@ -4,116 +4,116 @@ import { SimpleEventDispatcher } from "ste-simple-events";
|
|||||||
import type { PacketError, QueueItem } from "../types.ts";
|
import type { PacketError, QueueItem } from "../types.ts";
|
||||||
|
|
||||||
export class Queue {
|
export class Queue {
|
||||||
private queue: QueueItem[] = [];
|
private queue: QueueItem[] = [];
|
||||||
private lock = false;
|
private lock = false;
|
||||||
private ackNotifier = new SimpleEventDispatcher<number>();
|
private ackNotifier = new SimpleEventDispatcher<number>();
|
||||||
private errorNotifier = new SimpleEventDispatcher<PacketError>();
|
private errorNotifier = new SimpleEventDispatcher<PacketError>();
|
||||||
private timeout: number;
|
private timeout: number;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.timeout = 60000;
|
this.timeout = 60000;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getState(): QueueItem[] {
|
public getState(): QueueItem[] {
|
||||||
return this.queue;
|
return this.queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public clear(): void {
|
public clear(): void {
|
||||||
this.queue = [];
|
this.queue = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public push(item: Omit<QueueItem, "promise" | "sent" | "added">): void {
|
public push(item: Omit<QueueItem, "promise" | "sent" | "added">): void {
|
||||||
const queueItem: QueueItem = {
|
const queueItem: QueueItem = {
|
||||||
...item,
|
...item,
|
||||||
sent: false,
|
sent: false,
|
||||||
added: new Date(),
|
added: new Date(),
|
||||||
promise: new Promise<number>((resolve, reject) => {
|
promise: new Promise<number>((resolve, reject) => {
|
||||||
this.ackNotifier.subscribe((id) => {
|
this.ackNotifier.subscribe((id) => {
|
||||||
if (item.id === id) {
|
if (item.id === id) {
|
||||||
this.remove(item.id);
|
this.remove(item.id);
|
||||||
resolve(id);
|
resolve(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.errorNotifier.subscribe((e) => {
|
this.errorNotifier.subscribe((e) => {
|
||||||
if (item.id === e.id) {
|
if (item.id === e.id) {
|
||||||
this.remove(item.id);
|
this.remove(item.id);
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.queue.findIndex((qi) => qi.id === item.id) !== -1) {
|
if (this.queue.findIndex((qi) => qi.id === item.id) !== -1) {
|
||||||
this.remove(item.id);
|
this.remove(item.id);
|
||||||
const decoded = fromBinary(Protobuf.Mesh.ToRadioSchema, item.data);
|
const decoded = fromBinary(Protobuf.Mesh.ToRadioSchema, item.data);
|
||||||
console.warn(
|
console.warn(
|
||||||
`Packet ${item.id} of type ${decoded.payloadVariant.case} timed out`,
|
`Packet ${item.id} of type ${decoded.payloadVariant.case} timed out`,
|
||||||
);
|
);
|
||||||
|
|
||||||
reject({
|
reject({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
error: Protobuf.Mesh.Routing_Error.TIMEOUT,
|
error: Protobuf.Mesh.Routing_Error.TIMEOUT,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, this.timeout);
|
}, this.timeout);
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
this.queue.push(queueItem);
|
this.queue.push(queueItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public remove(id: number): void {
|
public remove(id: number): void {
|
||||||
if (this.lock) {
|
if (this.lock) {
|
||||||
setTimeout(() => this.remove(id), 100);
|
setTimeout(() => this.remove(id), 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.queue = this.queue.filter((item) => item.id !== id);
|
this.queue = this.queue.filter((item) => item.id !== id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public processAck(id: number): void {
|
public processAck(id: number): void {
|
||||||
this.ackNotifier.dispatch(id);
|
this.ackNotifier.dispatch(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public processError(e: PacketError): void {
|
public processError(e: PacketError): void {
|
||||||
console.error(
|
console.error(
|
||||||
`Error received for packet ${e.id}: ${
|
`Error received for packet ${e.id}: ${
|
||||||
Protobuf.Mesh.Routing_Error[e.error]
|
Protobuf.Mesh.Routing_Error[e.error]
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
this.errorNotifier.dispatch(e);
|
this.errorNotifier.dispatch(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public wait(id: number): Promise<number> {
|
public wait(id: number): Promise<number> {
|
||||||
const queueItem = this.queue.find((qi) => qi.id === id);
|
const queueItem = this.queue.find((qi) => qi.id === id);
|
||||||
if (!queueItem) {
|
if (!queueItem) {
|
||||||
throw new Error("Packet does not exist");
|
throw new Error("Packet does not exist");
|
||||||
}
|
}
|
||||||
return queueItem.promise;
|
return queueItem.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async processQueue(
|
public async processQueue(
|
||||||
outputStream: WritableStream<Uint8Array>,
|
outputStream: WritableStream<Uint8Array>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (this.lock) {
|
if (this.lock) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lock = true;
|
this.lock = true;
|
||||||
const writer = outputStream.getWriter();
|
const writer = outputStream.getWriter();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (this.queue.filter((p) => !p.sent).length > 0) {
|
while (this.queue.filter((p) => !p.sent).length > 0) {
|
||||||
const item = this.queue.filter((p) => !p.sent)[0];
|
const item = this.queue.filter((p) => !p.sent)[0];
|
||||||
if (item) {
|
if (item) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||||
try {
|
try {
|
||||||
await writer.write(item.data);
|
await writer.write(item.data);
|
||||||
item.sent = true;
|
item.sent = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error sending packet ${item.id}`, error);
|
console.error(`Error sending packet ${item.id}`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
writer.releaseLock();
|
writer.releaseLock();
|
||||||
this.lock = false;
|
this.lock = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,219 +4,219 @@ import type { MeshDevice } from "../../../mod.ts";
|
|||||||
import type { DeviceOutput } from "../../types.ts";
|
import type { DeviceOutput } from "../../types.ts";
|
||||||
|
|
||||||
export const decodePacket = (device: MeshDevice) =>
|
export const decodePacket = (device: MeshDevice) =>
|
||||||
new WritableStream<DeviceOutput>({
|
new WritableStream<DeviceOutput>({
|
||||||
write(chunk) {
|
write(chunk) {
|
||||||
switch (chunk.type) {
|
switch (chunk.type) {
|
||||||
case "debug": {
|
case "debug": {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "packet": {
|
case "packet": {
|
||||||
const decodedMessage = fromBinary(
|
const decodedMessage = fromBinary(
|
||||||
Protobuf.Mesh.FromRadioSchema,
|
Protobuf.Mesh.FromRadioSchema,
|
||||||
chunk.data,
|
chunk.data,
|
||||||
);
|
);
|
||||||
device.events.onFromRadio.dispatch(decodedMessage);
|
device.events.onFromRadio.dispatch(decodedMessage);
|
||||||
|
|
||||||
/** @todo Add map here when `all=true` gets fixed. */
|
/** @todo Add map here when `all=true` gets fixed. */
|
||||||
switch (decodedMessage.payloadVariant.case) {
|
switch (decodedMessage.payloadVariant.case) {
|
||||||
case "packet": {
|
case "packet": {
|
||||||
device.handleMeshPacket(decodedMessage.payloadVariant.value);
|
device.handleMeshPacket(decodedMessage.payloadVariant.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "myInfo": {
|
case "myInfo": {
|
||||||
device.events.onMyNodeInfo.dispatch(
|
device.events.onMyNodeInfo.dispatch(
|
||||||
decodedMessage.payloadVariant.value,
|
decodedMessage.payloadVariant.value,
|
||||||
);
|
);
|
||||||
device.log.info(
|
device.log.info(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
"📱 Received Node info for this device",
|
"📱 Received Node info for this device",
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "nodeInfo": {
|
case "nodeInfo": {
|
||||||
device.log.info(
|
device.log.info(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
`📱 Received Node Info packet for node: ${decodedMessage.payloadVariant.value.num}`,
|
`📱 Received Node Info packet for node: ${decodedMessage.payloadVariant.value.num}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
device.events.onNodeInfoPacket.dispatch(
|
device.events.onNodeInfoPacket.dispatch(
|
||||||
decodedMessage.payloadVariant.value,
|
decodedMessage.payloadVariant.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
//TODO: HERE
|
//TODO: HERE
|
||||||
if (decodedMessage.payloadVariant.value.position) {
|
if (decodedMessage.payloadVariant.value.position) {
|
||||||
device.events.onPositionPacket.dispatch({
|
device.events.onPositionPacket.dispatch({
|
||||||
id: decodedMessage.id,
|
id: decodedMessage.id,
|
||||||
rxTime: new Date(),
|
rxTime: new Date(),
|
||||||
from: decodedMessage.payloadVariant.value.num,
|
from: decodedMessage.payloadVariant.value.num,
|
||||||
to: decodedMessage.payloadVariant.value.num,
|
to: decodedMessage.payloadVariant.value.num,
|
||||||
type: "direct",
|
type: "direct",
|
||||||
channel: Types.ChannelNumber.Primary,
|
channel: Types.ChannelNumber.Primary,
|
||||||
data: decodedMessage.payloadVariant.value.position,
|
data: decodedMessage.payloadVariant.value.position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: HERE
|
//TODO: HERE
|
||||||
if (decodedMessage.payloadVariant.value.user) {
|
if (decodedMessage.payloadVariant.value.user) {
|
||||||
device.events.onUserPacket.dispatch({
|
device.events.onUserPacket.dispatch({
|
||||||
id: decodedMessage.id,
|
id: decodedMessage.id,
|
||||||
rxTime: new Date(),
|
rxTime: new Date(),
|
||||||
from: decodedMessage.payloadVariant.value.num,
|
from: decodedMessage.payloadVariant.value.num,
|
||||||
to: decodedMessage.payloadVariant.value.num,
|
to: decodedMessage.payloadVariant.value.num,
|
||||||
type: "direct",
|
type: "direct",
|
||||||
channel: Types.ChannelNumber.Primary,
|
channel: Types.ChannelNumber.Primary,
|
||||||
data: decodedMessage.payloadVariant.value.user,
|
data: decodedMessage.payloadVariant.value.user,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "config": {
|
case "config": {
|
||||||
if (decodedMessage.payloadVariant.value.payloadVariant.case) {
|
if (decodedMessage.payloadVariant.value.payloadVariant.case) {
|
||||||
device.log.trace(
|
device.log.trace(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
`💾 Received Config packet of variant: ${decodedMessage.payloadVariant.value.payloadVariant.case}`,
|
`💾 Received Config packet of variant: ${decodedMessage.payloadVariant.value.payloadVariant.case}`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
device.log.warn(
|
device.log.warn(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
`⚠️ Received Config packet of variant: ${"UNK"}`,
|
`⚠️ Received Config packet of variant: ${"UNK"}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
device.events.onConfigPacket.dispatch(
|
device.events.onConfigPacket.dispatch(
|
||||||
decodedMessage.payloadVariant.value,
|
decodedMessage.payloadVariant.value,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "logRecord": {
|
case "logRecord": {
|
||||||
device.log.trace(
|
device.log.trace(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
"Received onLogRecord",
|
"Received onLogRecord",
|
||||||
);
|
);
|
||||||
device.events.onLogRecord.dispatch(
|
device.events.onLogRecord.dispatch(
|
||||||
decodedMessage.payloadVariant.value,
|
decodedMessage.payloadVariant.value,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "configCompleteId": {
|
case "configCompleteId": {
|
||||||
if (decodedMessage.payloadVariant.value !== device.configId) {
|
if (decodedMessage.payloadVariant.value !== device.configId) {
|
||||||
device.log.error(
|
device.log.error(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
`❌ Invalid config id received from device, expected ${device.configId} but received ${decodedMessage.payloadVariant.value}`,
|
`❌ Invalid config id received from device, expected ${device.configId} but received ${decodedMessage.payloadVariant.value}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
device.log.info(
|
device.log.info(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
`⚙️ Valid config id received from device: ${device.configId}`,
|
`⚙️ Valid config id received from device: ${device.configId}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
device.updateDeviceStatus(
|
device.updateDeviceStatus(
|
||||||
Types.DeviceStatusEnum.DeviceConfigured,
|
Types.DeviceStatusEnum.DeviceConfigured,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "rebooted": {
|
case "rebooted": {
|
||||||
device.configure().catch(() => {
|
device.configure().catch(() => {
|
||||||
// TODO: FIX, workaround for `wantConfigId` not getting acks.
|
// TODO: FIX, workaround for `wantConfigId` not getting acks.
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "moduleConfig": {
|
case "moduleConfig": {
|
||||||
if (decodedMessage.payloadVariant.value.payloadVariant.case) {
|
if (decodedMessage.payloadVariant.value.payloadVariant.case) {
|
||||||
device.log.trace(
|
device.log.trace(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
`💾 Received Module Config packet of variant: ${decodedMessage.payloadVariant.value.payloadVariant.case}`,
|
`💾 Received Module Config packet of variant: ${decodedMessage.payloadVariant.value.payloadVariant.case}`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
device.log.warn(
|
device.log.warn(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
"⚠️ Received Module Config packet of variant: UNK",
|
"⚠️ Received Module Config packet of variant: UNK",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
device.events.onModuleConfigPacket.dispatch(
|
device.events.onModuleConfigPacket.dispatch(
|
||||||
decodedMessage.payloadVariant.value,
|
decodedMessage.payloadVariant.value,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "channel": {
|
case "channel": {
|
||||||
device.log.trace(
|
device.log.trace(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
`🔐 Received Channel: ${decodedMessage.payloadVariant.value.index}`,
|
`🔐 Received Channel: ${decodedMessage.payloadVariant.value.index}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
device.events.onChannelPacket.dispatch(
|
device.events.onChannelPacket.dispatch(
|
||||||
decodedMessage.payloadVariant.value,
|
decodedMessage.payloadVariant.value,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "queueStatus": {
|
case "queueStatus": {
|
||||||
device.log.trace(
|
device.log.trace(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
`🚧 Received Queue Status: ${decodedMessage.payloadVariant.value}`,
|
`🚧 Received Queue Status: ${decodedMessage.payloadVariant.value}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
device.events.onQueueStatus.dispatch(
|
device.events.onQueueStatus.dispatch(
|
||||||
decodedMessage.payloadVariant.value,
|
decodedMessage.payloadVariant.value,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "xmodemPacket": {
|
case "xmodemPacket": {
|
||||||
device.xModem.handlePacket(decodedMessage.payloadVariant.value);
|
device.xModem.handlePacket(decodedMessage.payloadVariant.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "metadata": {
|
case "metadata": {
|
||||||
if (
|
if (
|
||||||
Number.parseFloat(
|
Number.parseFloat(
|
||||||
decodedMessage.payloadVariant.value.firmwareVersion,
|
decodedMessage.payloadVariant.value.firmwareVersion,
|
||||||
) < Constants.minFwVer
|
) < Constants.minFwVer
|
||||||
) {
|
) {
|
||||||
device.log.fatal(
|
device.log.fatal(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
`Device firmware outdated. Min supported: ${Constants.minFwVer} got : ${decodedMessage.payloadVariant.value.firmwareVersion}`,
|
`Device firmware outdated. Min supported: ${Constants.minFwVer} got : ${decodedMessage.payloadVariant.value.firmwareVersion}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
device.log.debug(
|
device.log.debug(
|
||||||
Types.Emitter[Types.Emitter.GetMetadata],
|
Types.Emitter[Types.Emitter.GetMetadata],
|
||||||
"🏷️ Received metadata packet",
|
"🏷️ Received metadata packet",
|
||||||
);
|
);
|
||||||
|
|
||||||
device.events.onDeviceMetadataPacket.dispatch({
|
device.events.onDeviceMetadataPacket.dispatch({
|
||||||
id: decodedMessage.id,
|
id: decodedMessage.id,
|
||||||
rxTime: new Date(),
|
rxTime: new Date(),
|
||||||
from: 0,
|
from: 0,
|
||||||
to: 0,
|
to: 0,
|
||||||
type: "direct",
|
type: "direct",
|
||||||
channel: Types.ChannelNumber.Primary,
|
channel: Types.ChannelNumber.Primary,
|
||||||
data: decodedMessage.payloadVariant.value,
|
data: decodedMessage.payloadVariant.value,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "mqttClientProxyMessage": {
|
case "mqttClientProxyMessage": {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
device.log.warn(
|
device.log.warn(
|
||||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||||
`⚠️ Unhandled payload variant: ${decodedMessage.payloadVariant.case}`,
|
`⚠️ Unhandled payload variant: ${decodedMessage.payloadVariant.case}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,71 +1,71 @@
|
|||||||
import type { DeviceOutput } from "../../types.ts";
|
import type { DeviceOutput } from "../../types.ts";
|
||||||
|
|
||||||
export const fromDeviceStream: () => TransformStream<Uint8Array, DeviceOutput> =
|
export const fromDeviceStream: () => TransformStream<Uint8Array, DeviceOutput> =
|
||||||
(
|
(
|
||||||
// onReleaseEvent: SimpleEventDispatcher<boolean>,
|
// onReleaseEvent: SimpleEventDispatcher<boolean>,
|
||||||
) => {
|
) => {
|
||||||
let byteBuffer = new Uint8Array([]);
|
let byteBuffer = new Uint8Array([]);
|
||||||
const textDecoder = new TextDecoder();
|
const textDecoder = new TextDecoder();
|
||||||
return new TransformStream<Uint8Array, DeviceOutput>({
|
return new TransformStream<Uint8Array, DeviceOutput>({
|
||||||
transform(chunk: Uint8Array, controller): void {
|
transform(chunk: Uint8Array, controller): void {
|
||||||
// onReleaseEvent.subscribe(() => {
|
// onReleaseEvent.subscribe(() => {
|
||||||
// controller.terminate();
|
// controller.terminate();
|
||||||
// });
|
// });
|
||||||
byteBuffer = new Uint8Array([...byteBuffer, ...chunk]);
|
byteBuffer = new Uint8Array([...byteBuffer, ...chunk]);
|
||||||
let processingExhausted = false;
|
let processingExhausted = false;
|
||||||
while (byteBuffer.length !== 0 && !processingExhausted) {
|
while (byteBuffer.length !== 0 && !processingExhausted) {
|
||||||
const framingIndex = byteBuffer.findIndex((byte) => byte === 0x94);
|
const framingIndex = byteBuffer.findIndex((byte) => byte === 0x94);
|
||||||
const framingByte2 = byteBuffer[framingIndex + 1];
|
const framingByte2 = byteBuffer[framingIndex + 1];
|
||||||
if (framingByte2 === 0xc3) {
|
if (framingByte2 === 0xc3) {
|
||||||
if (byteBuffer.subarray(0, framingIndex).length) {
|
if (byteBuffer.subarray(0, framingIndex).length) {
|
||||||
controller.enqueue({
|
controller.enqueue({
|
||||||
type: "debug",
|
type: "debug",
|
||||||
data: textDecoder.decode(byteBuffer.subarray(0, framingIndex)),
|
data: textDecoder.decode(byteBuffer.subarray(0, framingIndex)),
|
||||||
});
|
});
|
||||||
byteBuffer = byteBuffer.subarray(framingIndex);
|
byteBuffer = byteBuffer.subarray(framingIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
const msb = byteBuffer[2];
|
const msb = byteBuffer[2];
|
||||||
const lsb = byteBuffer[3];
|
const lsb = byteBuffer[3];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
msb !== undefined &&
|
msb !== undefined &&
|
||||||
lsb !== undefined &&
|
lsb !== undefined &&
|
||||||
byteBuffer.length >= 4 + (msb << 8) + lsb
|
byteBuffer.length >= 4 + (msb << 8) + lsb
|
||||||
) {
|
) {
|
||||||
const packet = byteBuffer.subarray(4, 4 + (msb << 8) + lsb);
|
const packet = byteBuffer.subarray(4, 4 + (msb << 8) + lsb);
|
||||||
|
|
||||||
const malformedDetectorIndex = packet.findIndex(
|
const malformedDetectorIndex = packet.findIndex(
|
||||||
(byte) => byte === 0x94,
|
(byte) => byte === 0x94,
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
malformedDetectorIndex !== -1 &&
|
malformedDetectorIndex !== -1 &&
|
||||||
packet[malformedDetectorIndex + 1] === 0xc3
|
packet[malformedDetectorIndex + 1] === 0xc3
|
||||||
) {
|
) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`⚠️ Malformed packet found, discarding: ${byteBuffer
|
`⚠️ Malformed packet found, discarding: ${byteBuffer
|
||||||
.subarray(0, malformedDetectorIndex - 1)
|
.subarray(0, malformedDetectorIndex - 1)
|
||||||
.toString()}`,
|
.toString()}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
byteBuffer = byteBuffer.subarray(malformedDetectorIndex);
|
byteBuffer = byteBuffer.subarray(malformedDetectorIndex);
|
||||||
} else {
|
} else {
|
||||||
byteBuffer = byteBuffer.subarray(3 + (msb << 8) + lsb + 1);
|
byteBuffer = byteBuffer.subarray(3 + (msb << 8) + lsb + 1);
|
||||||
|
|
||||||
controller.enqueue({
|
controller.enqueue({
|
||||||
type: "packet",
|
type: "packet",
|
||||||
data: packet,
|
data: packet,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/** Only partioal message in buffer, wait for the rest */
|
/** Only partioal message in buffer, wait for the rest */
|
||||||
processingExhausted = true;
|
processingExhausted = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/** Message not complete, only 1 byte in buffer */
|
/** Message not complete, only 1 byte in buffer */
|
||||||
processingExhausted = true;
|
processingExhausted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
* Pads packets with appropriate framing information before writing to the output stream.
|
* Pads packets with appropriate framing information before writing to the output stream.
|
||||||
*/
|
*/
|
||||||
export const toDeviceStream: TransformStream<Uint8Array, Uint8Array> =
|
export const toDeviceStream: TransformStream<Uint8Array, Uint8Array> =
|
||||||
new TransformStream<Uint8Array, Uint8Array>({
|
new TransformStream<Uint8Array, Uint8Array>({
|
||||||
transform(chunk: Uint8Array, controller): void {
|
transform(chunk: Uint8Array, controller): void {
|
||||||
const bufLen = chunk.length;
|
const bufLen = chunk.length;
|
||||||
const header = new Uint8Array([
|
const header = new Uint8Array([
|
||||||
0x94,
|
0x94,
|
||||||
0xc3,
|
0xc3,
|
||||||
(bufLen >> 8) & 0xff,
|
(bufLen >> 8) & 0xff,
|
||||||
bufLen & 0xff,
|
bufLen & 0xff,
|
||||||
]);
|
]);
|
||||||
controller.enqueue(new Uint8Array([...header, ...chunk]));
|
controller.enqueue(new Uint8Array([...header, ...chunk]));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+116
-116
@@ -6,130 +6,130 @@ import crc16ccitt from "crc/calculators/crc16ccitt";
|
|||||||
type XmodemProps = (toRadio: Uint8Array, id?: number) => Promise<number>;
|
type XmodemProps = (toRadio: Uint8Array, id?: number) => Promise<number>;
|
||||||
|
|
||||||
export class Xmodem {
|
export class Xmodem {
|
||||||
private sendRaw: XmodemProps;
|
private sendRaw: XmodemProps;
|
||||||
private rxBuffer: Uint8Array[];
|
private rxBuffer: Uint8Array[];
|
||||||
private txBuffer: Uint8Array[];
|
private txBuffer: Uint8Array[];
|
||||||
private textEncoder: TextEncoder;
|
private textEncoder: TextEncoder;
|
||||||
private counter: number;
|
private counter: number;
|
||||||
|
|
||||||
constructor(sendRaw: XmodemProps) {
|
constructor(sendRaw: XmodemProps) {
|
||||||
this.sendRaw = sendRaw;
|
this.sendRaw = sendRaw;
|
||||||
this.rxBuffer = [];
|
this.rxBuffer = [];
|
||||||
this.txBuffer = [];
|
this.txBuffer = [];
|
||||||
this.textEncoder = new TextEncoder();
|
this.textEncoder = new TextEncoder();
|
||||||
this.counter = 0;
|
this.counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadFile(filename: string): Promise<number> {
|
async downloadFile(filename: string): Promise<number> {
|
||||||
return await this.sendCommand(
|
return await this.sendCommand(
|
||||||
Protobuf.Xmodem.XModem_Control.STX,
|
Protobuf.Xmodem.XModem_Control.STX,
|
||||||
this.textEncoder.encode(filename),
|
this.textEncoder.encode(filename),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadFile(filename: string, data: Uint8Array): Promise<number> {
|
async uploadFile(filename: string, data: Uint8Array): Promise<number> {
|
||||||
for (let i = 0; i < data.length; i += 128) {
|
for (let i = 0; i < data.length; i += 128) {
|
||||||
this.txBuffer.push(data.slice(i, i + 128));
|
this.txBuffer.push(data.slice(i, i + 128));
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.sendCommand(
|
return await this.sendCommand(
|
||||||
Protobuf.Xmodem.XModem_Control.SOH,
|
Protobuf.Xmodem.XModem_Control.SOH,
|
||||||
this.textEncoder.encode(filename),
|
this.textEncoder.encode(filename),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendCommand(
|
async sendCommand(
|
||||||
command: Protobuf.Xmodem.XModem_Control,
|
command: Protobuf.Xmodem.XModem_Control,
|
||||||
buffer?: Uint8Array,
|
buffer?: Uint8Array,
|
||||||
sequence?: number,
|
sequence?: number,
|
||||||
crc16?: number,
|
crc16?: number,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const toRadio = create(Protobuf.Mesh.ToRadioSchema, {
|
const toRadio = create(Protobuf.Mesh.ToRadioSchema, {
|
||||||
payloadVariant: {
|
payloadVariant: {
|
||||||
case: "xmodemPacket",
|
case: "xmodemPacket",
|
||||||
value: {
|
value: {
|
||||||
buffer,
|
buffer,
|
||||||
control: command,
|
control: command,
|
||||||
seq: sequence,
|
seq: sequence,
|
||||||
crc16: crc16,
|
crc16: crc16,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return await this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadio));
|
return await this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadio));
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePacket(packet: Protobuf.Xmodem.XModem): Promise<number> {
|
async handlePacket(packet: Protobuf.Xmodem.XModem): Promise<number> {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
switch (packet.control) {
|
switch (packet.control) {
|
||||||
case Protobuf.Xmodem.XModem_Control.NUL: {
|
case Protobuf.Xmodem.XModem_Control.NUL: {
|
||||||
// nothing
|
// nothing
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Protobuf.Xmodem.XModem_Control.SOH: {
|
case Protobuf.Xmodem.XModem_Control.SOH: {
|
||||||
this.counter = packet.seq;
|
this.counter = packet.seq;
|
||||||
if (this.validateCrc16(packet)) {
|
if (this.validateCrc16(packet)) {
|
||||||
this.rxBuffer[this.counter] = packet.buffer;
|
this.rxBuffer[this.counter] = packet.buffer;
|
||||||
return this.sendCommand(Protobuf.Xmodem.XModem_Control.ACK);
|
return this.sendCommand(Protobuf.Xmodem.XModem_Control.ACK);
|
||||||
}
|
}
|
||||||
return await this.sendCommand(
|
return await this.sendCommand(
|
||||||
Protobuf.Xmodem.XModem_Control.NAK,
|
Protobuf.Xmodem.XModem_Control.NAK,
|
||||||
undefined,
|
undefined,
|
||||||
packet.seq,
|
packet.seq,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case Protobuf.Xmodem.XModem_Control.STX: {
|
case Protobuf.Xmodem.XModem_Control.STX: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Protobuf.Xmodem.XModem_Control.EOT: {
|
case Protobuf.Xmodem.XModem_Control.EOT: {
|
||||||
// end of transmission
|
// end of transmission
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Protobuf.Xmodem.XModem_Control.ACK: {
|
case Protobuf.Xmodem.XModem_Control.ACK: {
|
||||||
this.counter++;
|
this.counter++;
|
||||||
if (this.txBuffer[this.counter - 1]) {
|
if (this.txBuffer[this.counter - 1]) {
|
||||||
return this.sendCommand(
|
return this.sendCommand(
|
||||||
Protobuf.Xmodem.XModem_Control.SOH,
|
Protobuf.Xmodem.XModem_Control.SOH,
|
||||||
this.txBuffer[this.counter - 1],
|
this.txBuffer[this.counter - 1],
|
||||||
this.counter,
|
this.counter,
|
||||||
crc16ccitt(this.txBuffer[this.counter - 1] ?? new Uint8Array()),
|
crc16ccitt(this.txBuffer[this.counter - 1] ?? new Uint8Array()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.counter === this.txBuffer.length + 1) {
|
if (this.counter === this.txBuffer.length + 1) {
|
||||||
return this.sendCommand(Protobuf.Xmodem.XModem_Control.EOT);
|
return this.sendCommand(Protobuf.Xmodem.XModem_Control.EOT);
|
||||||
}
|
}
|
||||||
this.clear();
|
this.clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Protobuf.Xmodem.XModem_Control.NAK: {
|
case Protobuf.Xmodem.XModem_Control.NAK: {
|
||||||
return this.sendCommand(
|
return this.sendCommand(
|
||||||
Protobuf.Xmodem.XModem_Control.SOH,
|
Protobuf.Xmodem.XModem_Control.SOH,
|
||||||
this.txBuffer[this.counter],
|
this.txBuffer[this.counter],
|
||||||
this.counter,
|
this.counter,
|
||||||
crc16ccitt(this.txBuffer[this.counter - 1] ?? new Uint8Array()),
|
crc16ccitt(this.txBuffer[this.counter - 1] ?? new Uint8Array()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case Protobuf.Xmodem.XModem_Control.CAN: {
|
case Protobuf.Xmodem.XModem_Control.CAN: {
|
||||||
this.clear();
|
this.clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Protobuf.Xmodem.XModem_Control.CTRLZ: {
|
case Protobuf.Xmodem.XModem_Control.CTRLZ: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(0);
|
return Promise.resolve(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
validateCrc16(packet: Protobuf.Xmodem.XModem): boolean {
|
validateCrc16(packet: Protobuf.Xmodem.XModem): boolean {
|
||||||
return crc16ccitt(packet.buffer) === packet.crc16;
|
return crc16ccitt(packet.buffer) === packet.crc16;
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.counter = 0;
|
this.counter = 0;
|
||||||
this.rxBuffer = [];
|
this.rxBuffer = [];
|
||||||
this.txBuffer = [];
|
this.txBuffer = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,31 +2,31 @@ import { Utils } from "@meshtastic/core";
|
|||||||
import type { Types } from "@meshtastic/core";
|
import type { Types } from "@meshtastic/core";
|
||||||
|
|
||||||
export class TransportDeno implements Types.Transport {
|
export class TransportDeno implements Types.Transport {
|
||||||
private _toDevice: WritableStream<Uint8Array>;
|
private _toDevice: WritableStream<Uint8Array>;
|
||||||
private _fromDevice: ReadableStream<Types.DeviceOutput>;
|
private _fromDevice: ReadableStream<Types.DeviceOutput>;
|
||||||
|
|
||||||
public static async create(hostname: string): Promise<TransportDeno> {
|
public static async create(hostname: string): Promise<TransportDeno> {
|
||||||
const connection = await Deno.connect({
|
const connection = await Deno.connect({
|
||||||
hostname,
|
hostname,
|
||||||
port: 4403,
|
port: 4403,
|
||||||
});
|
});
|
||||||
return new TransportDeno(connection);
|
return new TransportDeno(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(connection: Deno.Conn) {
|
constructor(connection: Deno.Conn) {
|
||||||
Utils.toDeviceStream.readable.pipeTo(connection.writable);
|
Utils.toDeviceStream.readable.pipeTo(connection.writable);
|
||||||
|
|
||||||
this._toDevice = Utils.toDeviceStream.writable;
|
this._toDevice = Utils.toDeviceStream.writable;
|
||||||
this._fromDevice = connection.readable.pipeThrough(
|
this._fromDevice = connection.readable.pipeThrough(
|
||||||
Utils.fromDeviceStream(),
|
Utils.fromDeviceStream(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get toDevice(): WritableStream<Uint8Array> {
|
get toDevice(): WritableStream<Uint8Array> {
|
||||||
return this._toDevice;
|
return this._toDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
||||||
return this._fromDevice;
|
return this._fromDevice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "@meshtastic/transport-http",
|
||||||
|
"version": "0.2.2",
|
||||||
|
"exports": "./mod.ts"
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@meshtastic/transport-http",
|
"name": "@meshtastic/transport-http",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"description": "A transport layer for Meshtastic applications using HTTP.",
|
"description": "A transport layer for Meshtastic applications using HTTP.",
|
||||||
"exports": {
|
"exports": {".": "./mod.ts"},
|
||||||
".": "./mod.ts"
|
"tasks": {
|
||||||
|
"build": "deno build"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,89 +1,89 @@
|
|||||||
import type { Types } from "@meshtastic/core";
|
import type { Types } from "@meshtastic/core";
|
||||||
|
|
||||||
export class TransportHTTP implements Types.Transport {
|
export class TransportHTTP implements Types.Transport {
|
||||||
private _toDevice: WritableStream<Uint8Array>;
|
private _toDevice: WritableStream<Uint8Array>;
|
||||||
private _fromDevice: ReadableStream<Types.DeviceOutput>;
|
private _fromDevice: ReadableStream<Types.DeviceOutput>;
|
||||||
private url: string;
|
private url: string;
|
||||||
private receiveBatchRequests: boolean;
|
private receiveBatchRequests: boolean;
|
||||||
private fetchInterval: number;
|
private fetchInterval: number;
|
||||||
|
|
||||||
public static async create(
|
public static async create(
|
||||||
address: string,
|
address: string,
|
||||||
tls?: boolean,
|
tls?: boolean,
|
||||||
): Promise<TransportHTTP> {
|
): Promise<TransportHTTP> {
|
||||||
const connectionUrl = `${tls ? "https" : "http"}://${address}`;
|
const connectionUrl = `${tls ? "https" : "http"}://${address}`;
|
||||||
await fetch(`${connectionUrl}/json/report`);
|
await fetch(`${connectionUrl}/json/report`);
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
return new TransportHTTP(connectionUrl);
|
return new TransportHTTP(connectionUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(url: string) {
|
constructor(url: string) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.receiveBatchRequests = false;
|
this.receiveBatchRequests = false;
|
||||||
this.fetchInterval = 3000;
|
this.fetchInterval = 3000;
|
||||||
|
|
||||||
this._toDevice = new WritableStream<Uint8Array>({
|
this._toDevice = new WritableStream<Uint8Array>({
|
||||||
write: async (chunk) => {
|
write: async (chunk) => {
|
||||||
await this.writeToRadio(chunk);
|
await this.writeToRadio(chunk);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let controller: ReadableStreamDefaultController<Types.DeviceOutput>;
|
let controller: ReadableStreamDefaultController<Types.DeviceOutput>;
|
||||||
|
|
||||||
this._fromDevice = new ReadableStream<Types.DeviceOutput>({
|
this._fromDevice = new ReadableStream<Types.DeviceOutput>({
|
||||||
start: (ctrl) => {
|
start: (ctrl) => {
|
||||||
controller = ctrl;
|
controller = ctrl;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await this.readFromRadio(controller);
|
await this.readFromRadio(controller);
|
||||||
}, this.fetchInterval);
|
}, this.fetchInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async readFromRadio(
|
private async readFromRadio(
|
||||||
controller: ReadableStreamDefaultController<Types.DeviceOutput>,
|
controller: ReadableStreamDefaultController<Types.DeviceOutput>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let readBuffer = new ArrayBuffer(1);
|
let readBuffer = new ArrayBuffer(1);
|
||||||
while (readBuffer.byteLength > 0) {
|
while (readBuffer.byteLength > 0) {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${this.url}/api/v1/fromradio?all=${
|
`${this.url}/api/v1/fromradio?all=${
|
||||||
this.receiveBatchRequests ? "true" : "false"
|
this.receiveBatchRequests ? "true" : "false"
|
||||||
}`,
|
}`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/x-protobuf",
|
Accept: "application/x-protobuf",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
readBuffer = await response.arrayBuffer();
|
readBuffer = await response.arrayBuffer();
|
||||||
|
|
||||||
if (readBuffer.byteLength > 0) {
|
if (readBuffer.byteLength > 0) {
|
||||||
controller.enqueue({
|
controller.enqueue({
|
||||||
type: "packet",
|
type: "packet",
|
||||||
data: new Uint8Array(readBuffer),
|
data: new Uint8Array(readBuffer),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async writeToRadio(data: Uint8Array): Promise<void> {
|
private async writeToRadio(data: Uint8Array): Promise<void> {
|
||||||
await fetch(`${this.url}/api/v1/toradio`, {
|
await fetch(`${this.url}/api/v1/toradio`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-protobuf",
|
"Content-Type": "application/x-protobuf",
|
||||||
},
|
},
|
||||||
body: data,
|
body: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get toDevice(): WritableStream<Uint8Array> {
|
get toDevice(): WritableStream<Uint8Array> {
|
||||||
return this._toDevice;
|
return this._toDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
||||||
return this._fromDevice;
|
return this._fromDevice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,73 +4,73 @@ import { Utils } from "@meshtastic/core";
|
|||||||
import type { Types } from "@meshtastic/core";
|
import type { Types } from "@meshtastic/core";
|
||||||
|
|
||||||
export class TransportNode implements Types.Transport {
|
export class TransportNode implements Types.Transport {
|
||||||
private readonly _toDevice: WritableStream<Uint8Array>;
|
private readonly _toDevice: WritableStream<Uint8Array>;
|
||||||
private readonly _fromDevice: ReadableStream<Types.DeviceOutput>;
|
private readonly _fromDevice: ReadableStream<Types.DeviceOutput>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and connects a new TransportNode instance.
|
* Creates and connects a new TransportNode instance.
|
||||||
* @param hostname - The IP address or hostname of the Meshtastic device.
|
* @param hostname - The IP address or hostname of the Meshtastic device.
|
||||||
* @param port - The port number for the TCP connection (defaults to 4403).
|
* @param port - The port number for the TCP connection (defaults to 4403).
|
||||||
* @returns A promise that resolves with a connected TransportNode instance.
|
* @returns A promise that resolves with a connected TransportNode instance.
|
||||||
*/
|
*/
|
||||||
public static create(hostname: string, port = 4403): Promise<TransportNode> {
|
public static create(hostname: string, port = 4403): Promise<TransportNode> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const socket = new Socket();
|
const socket = new Socket();
|
||||||
|
|
||||||
const onError = (err: Error) => {
|
const onError = (err: Error) => {
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
reject(err);
|
reject(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.once("error", onError);
|
socket.once("error", onError);
|
||||||
|
|
||||||
socket.connect(port, hostname, () => {
|
socket.connect(port, hostname, () => {
|
||||||
socket.removeListener("error", onError);
|
socket.removeListener("error", onError);
|
||||||
resolve(new TransportNode(socket));
|
resolve(new TransportNode(socket));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new TransportNode.
|
* Constructs a new TransportNode.
|
||||||
* @param connection - An active Node.js net.Socket connection.
|
* @param connection - An active Node.js net.Socket connection.
|
||||||
*/
|
*/
|
||||||
constructor(connection: Socket) {
|
constructor(connection: Socket) {
|
||||||
connection.on("error", (err) => {
|
connection.on("error", (err) => {
|
||||||
console.error("Socket connection error:", err);
|
console.error("Socket connection error:", err);
|
||||||
});
|
});
|
||||||
|
|
||||||
const fromDeviceSource = Readable.toWeb(
|
const fromDeviceSource = Readable.toWeb(
|
||||||
connection,
|
connection,
|
||||||
) as ReadableStream<Uint8Array>;
|
) as ReadableStream<Uint8Array>;
|
||||||
this._fromDevice = fromDeviceSource.pipeThrough(Utils.fromDeviceStream());
|
this._fromDevice = fromDeviceSource.pipeThrough(Utils.fromDeviceStream());
|
||||||
|
|
||||||
// Stream for data going FROM the application TO the Meshtastic device.
|
// Stream for data going FROM the application TO the Meshtastic device.
|
||||||
const toDeviceTransform = Utils.toDeviceStream;
|
const toDeviceTransform = Utils.toDeviceStream;
|
||||||
this._toDevice = toDeviceTransform.writable;
|
this._toDevice = toDeviceTransform.writable;
|
||||||
|
|
||||||
// The readable end of the transform is then piped to the Node.js socket.
|
// The readable end of the transform is then piped to the Node.js socket.
|
||||||
// A similar assertion is needed here because `Writable.toWeb` also returns
|
// A similar assertion is needed here because `Writable.toWeb` also returns
|
||||||
// a generically typed stream (`WritableStream<any>`).
|
// a generically typed stream (`WritableStream<any>`).
|
||||||
toDeviceTransform.readable
|
toDeviceTransform.readable
|
||||||
.pipeTo(Writable.toWeb(connection) as WritableStream<Uint8Array>)
|
.pipeTo(Writable.toWeb(connection) as WritableStream<Uint8Array>)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("Error piping data to socket:", err);
|
console.error("Error piping data to socket:", err);
|
||||||
connection.destroy(err as Error);
|
connection.destroy(err as Error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The WritableStream to send data to the Meshtastic device.
|
* The WritableStream to send data to the Meshtastic device.
|
||||||
*/
|
*/
|
||||||
public get toDevice(): WritableStream<Uint8Array> {
|
public get toDevice(): WritableStream<Uint8Array> {
|
||||||
return this._toDevice;
|
return this._toDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ReadableStream to receive data from the Meshtastic device.
|
* The ReadableStream to receive data from the Meshtastic device.
|
||||||
*/
|
*/
|
||||||
public get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
public get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
||||||
return this._fromDevice;
|
return this._fromDevice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "@meshtastic/transport-web-bluetooth",
|
||||||
|
"version": "0.1.3",
|
||||||
|
"exports": "./mod.ts"
|
||||||
|
}
|
||||||
+5
-5
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@meshtastic/transport-web-bluetooth",
|
"name": "@meshtastic/transport-web-bluetooth",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"description": "A transport layer for Meshtastic applications using Web Bluetooth.",
|
"description": "A transport layer for Meshtastic applications using Web Bluetooth.",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./mod.ts"
|
".": "./mod.ts"
|
||||||
},
|
},
|
||||||
"imports": {
|
"devDependencies": {
|
||||||
"@types/web-bluetooth": "npm:@types/web-bluetooth@^0.0.20"
|
"@types/web-bluetooth": "npm:@types/web-bluetooth@^0.0.20"
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"types": ["@types/web-bluetooth"]
|
"types": ["@types/web-bluetooth"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,133 +1,135 @@
|
|||||||
import type { Types } from "@meshtastic/core";
|
import type { Types } from "@meshtastic/core";
|
||||||
|
|
||||||
export class TransportWebBluetooth implements Types.Transport {
|
export class TransportWebBluetooth implements Types.Transport {
|
||||||
private _toDevice: WritableStream<Uint8Array>;
|
private _toDevice: WritableStream<Uint8Array>;
|
||||||
private _fromDevice: ReadableStream<Types.DeviceOutput>;
|
private _fromDevice: ReadableStream<Types.DeviceOutput>;
|
||||||
private _fromDeviceController?: ReadableStreamDefaultController<Types.DeviceOutput>;
|
private _fromDeviceController?: ReadableStreamDefaultController<Types.DeviceOutput>;
|
||||||
private _isFirstWrite = true;
|
private _isFirstWrite = true;
|
||||||
|
|
||||||
private toRadioCharacteristic: BluetoothRemoteGATTCharacteristic;
|
private toRadioCharacteristic: BluetoothRemoteGATTCharacteristic;
|
||||||
private fromRadioCharacteristic: BluetoothRemoteGATTCharacteristic;
|
private fromRadioCharacteristic: BluetoothRemoteGATTCharacteristic;
|
||||||
private fromNumCharacteristic: BluetoothRemoteGATTCharacteristic;
|
private fromNumCharacteristic: BluetoothRemoteGATTCharacteristic;
|
||||||
|
|
||||||
static ToRadioUuid = "f75c76d2-129e-4dad-a1dd-7866124401e7";
|
static ToRadioUuid = "f75c76d2-129e-4dad-a1dd-7866124401e7";
|
||||||
static FromRadioUuid = "2c55e69e-4993-11ed-b878-0242ac120002";
|
static FromRadioUuid = "2c55e69e-4993-11ed-b878-0242ac120002";
|
||||||
static FromNumUuid = "ed9da18c-a800-4f66-a670-aa7547e34453";
|
static FromNumUuid = "ed9da18c-a800-4f66-a670-aa7547e34453";
|
||||||
static ServiceUuid = "6ba1b218-15a8-461f-9fa8-5dcae273eafd";
|
static ServiceUuid = "6ba1b218-15a8-461f-9fa8-5dcae273eafd";
|
||||||
|
|
||||||
public static async create(): Promise<TransportWebBluetooth> {
|
public static async create(): Promise<TransportWebBluetooth> {
|
||||||
const device = await navigator.bluetooth.requestDevice({
|
const device = await navigator.bluetooth.requestDevice({
|
||||||
filters: [{ services: [this.ServiceUuid] }],
|
filters: [{ services: [TransportWebBluetooth.ServiceUuid] }],
|
||||||
});
|
});
|
||||||
return await this.prepareConnection(device);
|
return await TransportWebBluetooth.prepareConnection(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async createFromDevice(
|
public static async createFromDevice(
|
||||||
device: BluetoothDevice,
|
device: BluetoothDevice,
|
||||||
): Promise<TransportWebBluetooth> {
|
): Promise<TransportWebBluetooth> {
|
||||||
return await this.prepareConnection(device);
|
return await TransportWebBluetooth.prepareConnection(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async prepareConnection(
|
public static async prepareConnection(
|
||||||
device: BluetoothDevice,
|
device: BluetoothDevice,
|
||||||
): Promise<TransportWebBluetooth> {
|
): Promise<TransportWebBluetooth> {
|
||||||
const gattServer = await device.gatt?.connect();
|
const gattServer = await device.gatt?.connect();
|
||||||
|
|
||||||
if (!gattServer) {
|
if (!gattServer) {
|
||||||
throw new Error("Failed to connect to GATT server");
|
throw new Error("Failed to connect to GATT server");
|
||||||
}
|
}
|
||||||
|
|
||||||
const service = await gattServer.getPrimaryService(this.ServiceUuid);
|
const service = await gattServer.getPrimaryService(
|
||||||
|
TransportWebBluetooth.ServiceUuid,
|
||||||
|
);
|
||||||
|
|
||||||
const toRadioCharacteristic = await service.getCharacteristic(
|
const toRadioCharacteristic = await service.getCharacteristic(
|
||||||
this.ToRadioUuid,
|
TransportWebBluetooth.ToRadioUuid,
|
||||||
);
|
);
|
||||||
const fromRadioCharacteristic = await service.getCharacteristic(
|
const fromRadioCharacteristic = await service.getCharacteristic(
|
||||||
this.FromRadioUuid,
|
TransportWebBluetooth.FromRadioUuid,
|
||||||
);
|
);
|
||||||
const fromNumCharacteristic = await service.getCharacteristic(
|
const fromNumCharacteristic = await service.getCharacteristic(
|
||||||
this.FromNumUuid,
|
TransportWebBluetooth.FromNumUuid,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!toRadioCharacteristic ||
|
!toRadioCharacteristic ||
|
||||||
!fromRadioCharacteristic ||
|
!fromRadioCharacteristic ||
|
||||||
!fromNumCharacteristic
|
!fromNumCharacteristic
|
||||||
) {
|
) {
|
||||||
throw new Error("Failed to find required characteristics");
|
throw new Error("Failed to find required characteristics");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Connected to device", device.name);
|
console.log("Connected to device", device.name);
|
||||||
|
|
||||||
return new TransportWebBluetooth(
|
return new TransportWebBluetooth(
|
||||||
toRadioCharacteristic,
|
toRadioCharacteristic,
|
||||||
fromRadioCharacteristic,
|
fromRadioCharacteristic,
|
||||||
fromNumCharacteristic,
|
fromNumCharacteristic,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
toRadioCharacteristic: BluetoothRemoteGATTCharacteristic,
|
toRadioCharacteristic: BluetoothRemoteGATTCharacteristic,
|
||||||
fromRadioCharacteristic: BluetoothRemoteGATTCharacteristic,
|
fromRadioCharacteristic: BluetoothRemoteGATTCharacteristic,
|
||||||
fromNumCharacteristic: BluetoothRemoteGATTCharacteristic,
|
fromNumCharacteristic: BluetoothRemoteGATTCharacteristic,
|
||||||
) {
|
) {
|
||||||
this.toRadioCharacteristic = toRadioCharacteristic;
|
this.toRadioCharacteristic = toRadioCharacteristic;
|
||||||
this.fromRadioCharacteristic = fromRadioCharacteristic;
|
this.fromRadioCharacteristic = fromRadioCharacteristic;
|
||||||
this.fromNumCharacteristic = fromNumCharacteristic;
|
this.fromNumCharacteristic = fromNumCharacteristic;
|
||||||
|
|
||||||
this._fromDevice = new ReadableStream({
|
this._fromDevice = new ReadableStream({
|
||||||
start: (ctrl) => {
|
start: (ctrl) => {
|
||||||
this._fromDeviceController = ctrl;
|
this._fromDeviceController = ctrl;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this._toDevice = new WritableStream({
|
this._toDevice = new WritableStream({
|
||||||
write: async (chunk) => {
|
write: async (chunk) => {
|
||||||
await this.toRadioCharacteristic.writeValue(chunk);
|
await this.toRadioCharacteristic.writeValue(chunk);
|
||||||
|
|
||||||
if (this._isFirstWrite && this._fromDeviceController) {
|
if (this._isFirstWrite && this._fromDeviceController) {
|
||||||
this._isFirstWrite = false;
|
this._isFirstWrite = false;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.readFromRadio(this._fromDeviceController!);
|
this.readFromRadio(this._fromDeviceController!);
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.fromNumCharacteristic.addEventListener(
|
this.fromNumCharacteristic.addEventListener(
|
||||||
"characteristicvaluechanged",
|
"characteristicvaluechanged",
|
||||||
() => {
|
() => {
|
||||||
if (this._fromDeviceController) {
|
if (this._fromDeviceController) {
|
||||||
this.readFromRadio(this._fromDeviceController);
|
this.readFromRadio(this._fromDeviceController);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
this.fromNumCharacteristic.startNotifications();
|
this.fromNumCharacteristic.startNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
get toDevice(): WritableStream<Uint8Array> {
|
get toDevice(): WritableStream<Uint8Array> {
|
||||||
return this._toDevice;
|
return this._toDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
||||||
return this._fromDevice;
|
return this._fromDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async readFromRadio(
|
protected async readFromRadio(
|
||||||
controller: ReadableStreamDefaultController<Types.DeviceOutput>,
|
controller: ReadableStreamDefaultController<Types.DeviceOutput>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let hasMoreData = true;
|
let hasMoreData = true;
|
||||||
while (hasMoreData && this.fromRadioCharacteristic) {
|
while (hasMoreData && this.fromRadioCharacteristic) {
|
||||||
const value = await this.fromRadioCharacteristic.readValue();
|
const value = await this.fromRadioCharacteristic.readValue();
|
||||||
if (value.byteLength === 0) {
|
if (value.byteLength === 0) {
|
||||||
hasMoreData = false;
|
hasMoreData = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
controller.enqueue({
|
controller.enqueue({
|
||||||
type: "packet",
|
type: "packet",
|
||||||
data: new Uint8Array(value.buffer),
|
data: new Uint8Array(value.buffer),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@meshtastic/transport-web-serial",
|
|
||||||
"version": "0.2.1",
|
|
||||||
"description": "A transport layer for Meshtastic applications using Web Serial API.",
|
|
||||||
"exports": {
|
|
||||||
".": "./mod.ts"
|
|
||||||
},
|
|
||||||
"imports": {
|
|
||||||
"@types/w3c-web-serial": "npm:@types/w3c-web-serial@^1.0.7"
|
|
||||||
},
|
|
||||||
"compilerOptions": {
|
|
||||||
"types": ["@types/w3c-web-serial"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "@meshtastic/transport-web-serial",
|
||||||
|
"version": "0.2.2",
|
||||||
|
"exports": "./mod.ts"
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "@meshtastic/transport-web-serial",
|
||||||
|
"version": "0.2.2",
|
||||||
|
"description": "A transport layer for Meshtastic applications using Web Serial API.",
|
||||||
|
"exports": {
|
||||||
|
".": "./mod.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/w3c-web-serial": "npm:@types/w3c-web-serial@^1.0.7"
|
||||||
|
},
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["@types/w3c-web-serial"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,41 +2,41 @@ import { Utils } from "@meshtastic/core";
|
|||||||
import type { Types } from "@meshtastic/core";
|
import type { Types } from "@meshtastic/core";
|
||||||
|
|
||||||
export class TransportWebSerial implements Types.Transport {
|
export class TransportWebSerial implements Types.Transport {
|
||||||
private _toDevice: WritableStream<Uint8Array>;
|
private _toDevice: WritableStream<Uint8Array>;
|
||||||
private _fromDevice: ReadableStream<Types.DeviceOutput>;
|
private _fromDevice: ReadableStream<Types.DeviceOutput>;
|
||||||
|
|
||||||
public static async create(baudRate?: number): Promise<TransportWebSerial> {
|
public static async create(baudRate?: number): Promise<TransportWebSerial> {
|
||||||
const port = await navigator.serial.requestPort();
|
const port = await navigator.serial.requestPort();
|
||||||
await port.open({ baudRate: baudRate || 115200 });
|
await port.open({ baudRate: baudRate || 115200 });
|
||||||
return new TransportWebSerial(port);
|
return new TransportWebSerial(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async createFromPort(
|
public static async createFromPort(
|
||||||
port: SerialPort,
|
port: SerialPort,
|
||||||
baudRate?: number,
|
baudRate?: number,
|
||||||
): Promise<TransportWebSerial> {
|
): Promise<TransportWebSerial> {
|
||||||
await port.open({ baudRate: baudRate || 115200 });
|
await port.open({ baudRate: baudRate || 115200 });
|
||||||
return new TransportWebSerial(port);
|
return new TransportWebSerial(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(connection: SerialPort) {
|
constructor(connection: SerialPort) {
|
||||||
if (!connection.readable || !connection.writable) {
|
if (!connection.readable || !connection.writable) {
|
||||||
throw new Error("Stream not accessible");
|
throw new Error("Stream not accessible");
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils.toDeviceStream.readable.pipeTo(connection.writable);
|
Utils.toDeviceStream.readable.pipeTo(connection.writable);
|
||||||
|
|
||||||
this._toDevice = Utils.toDeviceStream.writable;
|
this._toDevice = Utils.toDeviceStream.writable;
|
||||||
this._fromDevice = connection.readable.pipeThrough(
|
this._fromDevice = connection.readable.pipeThrough(
|
||||||
Utils.fromDeviceStream(),
|
Utils.fromDeviceStream(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get toDevice(): WritableStream<Uint8Array> {
|
get toDevice(): WritableStream<Uint8Array> {
|
||||||
return this._toDevice;
|
return this._toDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
||||||
return this._fromDevice;
|
return this._fromDevice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-20
@@ -18,23 +18,6 @@ or served from a node
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Progress Web App Support (PWA)
|
|
||||||
|
|
||||||
Meshtastic Web Client now includes Progressive Web App (PWA) functionality,
|
|
||||||
allowing users to:
|
|
||||||
|
|
||||||
- Install the app on desktop and mobile devices
|
|
||||||
- Access the interface offline
|
|
||||||
- Receive updates automatically
|
|
||||||
- Experience faster load times with caching
|
|
||||||
|
|
||||||
To install as a PWA:
|
|
||||||
|
|
||||||
- On desktop: Look for the install icon in your browser's address bar
|
|
||||||
- On mobile: Use "Add to Home Screen" option in your browser menu
|
|
||||||
|
|
||||||
PWA functionality works with both the hosted version and self-hosted instances.
|
|
||||||
|
|
||||||
## Self-host
|
## Self-host
|
||||||
|
|
||||||
The client can be self hosted using the precompiled container images with an OCI
|
The client can be self hosted using the precompiled container images with an OCI
|
||||||
@@ -56,7 +39,7 @@ Our release process follows these guidelines:
|
|||||||
|
|
||||||
- **Versioning:** We use Semantic Versioning (`Major.Minor.Patch`).
|
- **Versioning:** We use Semantic Versioning (`Major.Minor.Patch`).
|
||||||
- **Stable Releases:** Published around the beginning of each month (e.g.,
|
- **Stable Releases:** Published around the beginning of each month (e.g.,
|
||||||
`v2.3.4`).
|
`v2.6.1`).
|
||||||
- **Pre-releases:** A pre-release is typically issued mid-month for testing and
|
- **Pre-releases:** A pre-release is typically issued mid-month for testing and
|
||||||
early adoption.
|
early adoption.
|
||||||
- **Nightly Builds:** An experimental Docker image containing the latest
|
- **Nightly Builds:** An experimental Docker image containing the latest
|
||||||
@@ -106,6 +89,7 @@ instructions listed on the home page.
|
|||||||
Install the dependencies.
|
Install the dependencies.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
cd packages/web &&
|
||||||
bun install
|
bun install
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -141,8 +125,6 @@ reasons:
|
|||||||
configuration, enhancing code quality and developer experience.
|
configuration, enhancing code quality and developer experience.
|
||||||
- **Modern JavaScript**: First-class support for ESM imports, top-level await,
|
- **Modern JavaScript**: First-class support for ESM imports, top-level await,
|
||||||
and other modern JavaScript features.
|
and other modern JavaScript features.
|
||||||
- **All-in-One Tooling**: Built-in package manager, bundler, test runner, and
|
|
||||||
transpiler eliminate the need for multiple third-party tools.
|
|
||||||
- **Node.js Compatibility**: Drop-in replacement for Node.js with better
|
- **Node.js Compatibility**: Drop-in replacement for Node.js with better
|
||||||
performance and built-in tooling.
|
performance and built-in tooling.
|
||||||
- **Reproducible Builds**: Lockfile ensures consistent builds across all
|
- **Reproducible Builds**: Lockfile ensures consistent builds across all
|
||||||
|
|||||||
@@ -11,9 +11,7 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/meshtastic/web/issues"
|
"url": "https://github.com/meshtastic/web/issues"
|
||||||
},
|
},
|
||||||
"simple-git-hooks": {
|
|
||||||
"pre-commit": "bun run check:fix"
|
|
||||||
},
|
|
||||||
"homepage": "https://meshtastic.org",
|
"homepage": "https://meshtastic.org",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "bunx --bun vite build",
|
"build": "bunx --bun vite build",
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Avatar } from "@components/UI/Avatar.tsx";
|
import { Avatar } from "@components/UI/Avatar.tsx";
|
||||||
import {
|
import {
|
||||||
CommandDialog,
|
CommandDialog,
|
||||||
CommandEmpty,
|
CommandEmpty,
|
||||||
CommandGroup,
|
CommandGroup,
|
||||||
CommandInput,
|
CommandInput,
|
||||||
CommandItem,
|
CommandItem,
|
||||||
CommandList,
|
CommandList,
|
||||||
} from "@components/UI/Command.tsx";
|
} from "@components/UI/Command.tsx";
|
||||||
import { usePinnedItems } from "@core/hooks/usePinnedItems.ts";
|
import { usePinnedItems } from "@core/hooks/usePinnedItems.ts";
|
||||||
import { useAppStore } from "@core/stores/appStore.ts";
|
import { useAppStore } from "@core/stores/appStore.ts";
|
||||||
@@ -14,345 +14,345 @@ import { cn } from "@core/utils/cn.ts";
|
|||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import { useCommandState } from "cmdk";
|
import { useCommandState } from "cmdk";
|
||||||
import {
|
import {
|
||||||
ArrowLeftRightIcon,
|
ArrowLeftRightIcon,
|
||||||
BoxSelectIcon,
|
BoxSelectIcon,
|
||||||
BugIcon,
|
BugIcon,
|
||||||
CloudOff,
|
CloudOff,
|
||||||
EraserIcon,
|
EraserIcon,
|
||||||
FactoryIcon,
|
FactoryIcon,
|
||||||
LayersIcon,
|
LayersIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
MapIcon,
|
MapIcon,
|
||||||
MessageSquareIcon,
|
MessageSquareIcon,
|
||||||
Pin,
|
Pin,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
PowerIcon,
|
PowerIcon,
|
||||||
QrCodeIcon,
|
QrCodeIcon,
|
||||||
RefreshCwIcon,
|
RefreshCwIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
SmartphoneIcon,
|
SmartphoneIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
UsersIcon,
|
UsersIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export interface Group {
|
export interface Group {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
commands: Command[];
|
commands: Command[];
|
||||||
}
|
}
|
||||||
export interface Command {
|
export interface Command {
|
||||||
label: string;
|
label: string;
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
subItems?: SubItem[];
|
subItems?: SubItem[];
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
}
|
}
|
||||||
export interface SubItem {
|
export interface SubItem {
|
||||||
label: string;
|
label: string;
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
action: () => void;
|
action: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CommandPalette = () => {
|
export const CommandPalette = () => {
|
||||||
const {
|
const {
|
||||||
commandPaletteOpen,
|
commandPaletteOpen,
|
||||||
setCommandPaletteOpen,
|
setCommandPaletteOpen,
|
||||||
setConnectDialogOpen,
|
setConnectDialogOpen,
|
||||||
setSelectedDevice,
|
setSelectedDevice,
|
||||||
} = useAppStore();
|
} = useAppStore();
|
||||||
const { getDevices } = useDeviceStore();
|
const { getDevices } = useDeviceStore();
|
||||||
const { setDialogOpen, getNode, connection } = useDevice();
|
const { setDialogOpen, getNode, connection } = useDevice();
|
||||||
const { pinnedItems, togglePinnedItem } = usePinnedItems({
|
const { pinnedItems, togglePinnedItem } = usePinnedItems({
|
||||||
storageName: "pinnedCommandMenuGroups",
|
storageName: "pinnedCommandMenuGroups",
|
||||||
});
|
});
|
||||||
const { t } = useTranslation("commandPalette");
|
const { t } = useTranslation("commandPalette");
|
||||||
const navigate = useNavigate({ from: "/" });
|
const navigate = useNavigate({ from: "/" });
|
||||||
|
|
||||||
const groups: Group[] = [
|
const groups: Group[] = [
|
||||||
{
|
{
|
||||||
id: "gotoGroup",
|
id: "gotoGroup",
|
||||||
label: t("goto.label"),
|
label: t("goto.label"),
|
||||||
icon: LinkIcon,
|
icon: LinkIcon,
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
label: t("goto.command.messages"),
|
label: t("goto.command.messages"),
|
||||||
icon: MessageSquareIcon,
|
icon: MessageSquareIcon,
|
||||||
action() {
|
action() {
|
||||||
navigate({ to: "/messages" });
|
navigate({ to: "/messages" });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("goto.command.map"),
|
label: t("goto.command.map"),
|
||||||
icon: MapIcon,
|
icon: MapIcon,
|
||||||
action() {
|
action() {
|
||||||
navigate({ to: "/map" });
|
navigate({ to: "/map" });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("goto.command.config"),
|
label: t("goto.command.config"),
|
||||||
icon: SettingsIcon,
|
icon: SettingsIcon,
|
||||||
action() {
|
action() {
|
||||||
navigate({ to: "/config" });
|
navigate({ to: "/config" });
|
||||||
},
|
},
|
||||||
tags: ["settings"],
|
tags: ["settings"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("goto.command.channels"),
|
label: t("goto.command.channels"),
|
||||||
icon: LayersIcon,
|
icon: LayersIcon,
|
||||||
action() {
|
action() {
|
||||||
navigate({ to: "/channels" });
|
navigate({ to: "/channels" });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("goto.command.nodes"),
|
label: t("goto.command.nodes"),
|
||||||
icon: UsersIcon,
|
icon: UsersIcon,
|
||||||
action() {
|
action() {
|
||||||
navigate({ to: "/nodes" });
|
navigate({ to: "/nodes" });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "manageGroup",
|
id: "manageGroup",
|
||||||
label: t("manage.label"),
|
label: t("manage.label"),
|
||||||
icon: SmartphoneIcon,
|
icon: SmartphoneIcon,
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
label: t("manage.command.switchNode"),
|
label: t("manage.command.switchNode"),
|
||||||
icon: ArrowLeftRightIcon,
|
icon: ArrowLeftRightIcon,
|
||||||
subItems: getDevices().map((device) => ({
|
subItems: getDevices().map((device) => ({
|
||||||
label:
|
label:
|
||||||
getNode(device.hardware.myNodeNum)?.user?.longName ??
|
getNode(device.hardware.myNodeNum)?.user?.longName ??
|
||||||
t("unknown.shortName"),
|
t("unknown.shortName"),
|
||||||
icon: (
|
icon: (
|
||||||
<Avatar
|
<Avatar
|
||||||
text={
|
text={
|
||||||
getNode(device.hardware.myNodeNum)?.user?.shortName ??
|
getNode(device.hardware.myNodeNum)?.user?.shortName ??
|
||||||
t("unknown.shortName")
|
t("unknown.shortName")
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
action() {
|
action() {
|
||||||
setSelectedDevice(device.id);
|
setSelectedDevice(device.id);
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("manage.command.connectNewNode"),
|
label: t("manage.command.connectNewNode"),
|
||||||
icon: PlusIcon,
|
icon: PlusIcon,
|
||||||
action() {
|
action() {
|
||||||
setConnectDialogOpen(true);
|
setConnectDialogOpen(true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "contextualGroup",
|
id: "contextualGroup",
|
||||||
label: t("contextual.label"),
|
label: t("contextual.label"),
|
||||||
icon: BoxSelectIcon,
|
icon: BoxSelectIcon,
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
label: t("contextual.command.qrCode"),
|
label: t("contextual.command.qrCode"),
|
||||||
icon: QrCodeIcon,
|
icon: QrCodeIcon,
|
||||||
subItems: [
|
subItems: [
|
||||||
{
|
{
|
||||||
label: t("contextual.command.qrGenerator"),
|
label: t("contextual.command.qrGenerator"),
|
||||||
icon: <QrCodeIcon size={16} />,
|
icon: <QrCodeIcon size={16} />,
|
||||||
action() {
|
action() {
|
||||||
setDialogOpen("QR", true);
|
setDialogOpen("QR", true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("contextual.command.qrImport"),
|
label: t("contextual.command.qrImport"),
|
||||||
icon: <QrCodeIcon size={16} />,
|
icon: <QrCodeIcon size={16} />,
|
||||||
action() {
|
action() {
|
||||||
setDialogOpen("import", true);
|
setDialogOpen("import", true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("contextual.command.scheduleShutdown"),
|
label: t("contextual.command.scheduleShutdown"),
|
||||||
icon: PowerIcon,
|
icon: PowerIcon,
|
||||||
action() {
|
action() {
|
||||||
setDialogOpen("shutdown", true);
|
setDialogOpen("shutdown", true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("contextual.command.scheduleReboot"),
|
label: t("contextual.command.scheduleReboot"),
|
||||||
icon: RefreshCwIcon,
|
icon: RefreshCwIcon,
|
||||||
action() {
|
action() {
|
||||||
setDialogOpen("reboot", true);
|
setDialogOpen("reboot", true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("contextual.command.rebootToOtaMode"),
|
label: t("contextual.command.rebootToOtaMode"),
|
||||||
icon: RefreshCwIcon,
|
icon: RefreshCwIcon,
|
||||||
action() {
|
action() {
|
||||||
setDialogOpen("rebootOTA", true);
|
setDialogOpen("rebootOTA", true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("contextual.command.resetNodeDb"),
|
label: t("contextual.command.resetNodeDb"),
|
||||||
icon: TrashIcon,
|
icon: TrashIcon,
|
||||||
action() {
|
action() {
|
||||||
connection?.resetNodes();
|
connection?.resetNodes();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("contextual.command.disconnect"),
|
label: t("contextual.command.disconnect"),
|
||||||
icon: CloudOff,
|
icon: CloudOff,
|
||||||
action() {
|
action() {
|
||||||
connection?.disconnect().catch((error) => {
|
connection?.disconnect().catch((error) => {
|
||||||
console.error("Failed to disconnect:", error);
|
console.error("Failed to disconnect:", error);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("contextual.command.factoryResetDevice"),
|
label: t("contextual.command.factoryResetDevice"),
|
||||||
icon: FactoryIcon,
|
icon: FactoryIcon,
|
||||||
action() {
|
action() {
|
||||||
connection?.factoryResetDevice();
|
connection?.factoryResetDevice();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("contextual.command.factoryResetConfig"),
|
label: t("contextual.command.factoryResetConfig"),
|
||||||
icon: FactoryIcon,
|
icon: FactoryIcon,
|
||||||
action() {
|
action() {
|
||||||
connection?.factoryResetConfig();
|
connection?.factoryResetConfig();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "debugGroup",
|
id: "debugGroup",
|
||||||
label: t("debug.label"),
|
label: t("debug.label"),
|
||||||
icon: BugIcon,
|
icon: BugIcon,
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
label: t("debug.command.reconfigure"),
|
label: t("debug.command.reconfigure"),
|
||||||
icon: RefreshCwIcon,
|
icon: RefreshCwIcon,
|
||||||
action() {
|
action() {
|
||||||
void connection?.configure();
|
void connection?.configure();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("debug.command.clearAllStoredMessages"),
|
label: t("debug.command.clearAllStoredMessages"),
|
||||||
icon: EraserIcon,
|
icon: EraserIcon,
|
||||||
action() {
|
action() {
|
||||||
setDialogOpen("deleteMessages", true);
|
setDialogOpen("deleteMessages", true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const sortedGroups = [...groups].sort((a, b) => {
|
const sortedGroups = [...groups].sort((a, b) => {
|
||||||
const aPinned = pinnedItems.includes(a.id) ? 1 : 0;
|
const aPinned = pinnedItems.includes(a.id) ? 1 : 0;
|
||||||
const bPinned = pinnedItems.includes(b.id) ? 1 : 0;
|
const bPinned = pinnedItems.includes(b.id) ? 1 : 0;
|
||||||
return bPinned - aPinned;
|
return bPinned - aPinned;
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeydown = (e: KeyboardEvent) => {
|
const handleKeydown = (e: KeyboardEvent) => {
|
||||||
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setCommandPaletteOpen(true);
|
setCommandPaletteOpen(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
globalThis.addEventListener("keydown", handleKeydown);
|
globalThis.addEventListener("keydown", handleKeydown);
|
||||||
return () => globalThis.removeEventListener("keydown", handleKeydown);
|
return () => globalThis.removeEventListener("keydown", handleKeydown);
|
||||||
}, [setCommandPaletteOpen]);
|
}, [setCommandPaletteOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommandDialog
|
<CommandDialog
|
||||||
open={commandPaletteOpen}
|
open={commandPaletteOpen}
|
||||||
onOpenChange={setCommandPaletteOpen}
|
onOpenChange={setCommandPaletteOpen}
|
||||||
>
|
>
|
||||||
<CommandInput placeholder={t("search.commandPalette")} />
|
<CommandInput placeholder={t("search.commandPalette")} />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>{t("emptyState")}</CommandEmpty>
|
<CommandEmpty>{t("emptyState")}</CommandEmpty>
|
||||||
{sortedGroups.map((group) => (
|
{sortedGroups.map((group) => (
|
||||||
<CommandGroup
|
<CommandGroup
|
||||||
key={group.label}
|
key={group.label}
|
||||||
heading={
|
heading={
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span>{group.label}</span>
|
<span>{group.label}</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => togglePinnedItem(group.id)}
|
onClick={() => togglePinnedItem(group.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"transition-all duration-300 scale-100 cursor-pointer p-2 focus:*:data-label:opacity-100",
|
"transition-all duration-300 scale-100 cursor-pointer p-2 focus:*:data-label:opacity-100",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-label
|
data-label
|
||||||
className="transition-all block absolute w-full mb-auto mt-auto ml-0 mr-0 text-xs left-0 -top-5 opacity-0 rounded-lg"
|
className="transition-all block absolute w-full mb-auto mt-auto ml-0 mr-0 text-xs left-0 -top-5 opacity-0 rounded-lg"
|
||||||
/>
|
/>
|
||||||
<Pin
|
<Pin
|
||||||
size={16}
|
size={16}
|
||||||
className={cn(
|
className={cn(
|
||||||
"transition-opacity",
|
"transition-opacity",
|
||||||
pinnedItems.includes(group.id)
|
pinnedItems.includes(group.id)
|
||||||
? "opacity-100 text-red-500"
|
? "opacity-100 text-red-500"
|
||||||
: "opacity-40 hover:opacity-70",
|
: "opacity-40 hover:opacity-70",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{group.commands.map((command) => (
|
{group.commands.map((command) => (
|
||||||
<div key={command.label}>
|
<div key={command.label}>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
command.action?.();
|
command.action?.();
|
||||||
setCommandPaletteOpen(false);
|
setCommandPaletteOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<command.icon size={16} className="mr-2" />
|
<command.icon size={16} className="mr-2" />
|
||||||
{command.label}
|
{command.label}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
{command.subItems?.map((subItem) => (
|
{command.subItems?.map((subItem) => (
|
||||||
<SubItem
|
<SubItem
|
||||||
key={subItem.label}
|
key={subItem.label}
|
||||||
label={subItem.label}
|
label={subItem.label}
|
||||||
icon={subItem.icon}
|
icon={subItem.icon}
|
||||||
action={subItem.action}
|
action={subItem.action}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
))}
|
))}
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</CommandDialog>
|
</CommandDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SubItem = ({
|
const SubItem = ({
|
||||||
label,
|
label,
|
||||||
icon,
|
icon,
|
||||||
action,
|
action,
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string;
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
action: () => void;
|
action: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const search = useCommandState((state) => state.search);
|
const search = useCommandState((state) => state.search);
|
||||||
if (!search) {
|
if (!search) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommandItem onSelect={action}>
|
<CommandItem onSelect={action}>
|
||||||
{icon}
|
{icon}
|
||||||
{label}
|
{label}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+4
-4
@@ -1,11 +1,11 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly env: {
|
readonly env: {
|
||||||
readonly VITE_COMMIT_HASH: string;
|
readonly VITE_COMMIT_HASH: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv;
|
readonly env: ImportMetaEnv;
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-46
@@ -7,56 +7,56 @@ import { defineConfig } from "vite";
|
|||||||
|
|
||||||
let hash = "";
|
let hash = "";
|
||||||
try {
|
try {
|
||||||
hash = execSync("git rev-parse --short HEAD", { encoding: "utf8" }).trim();
|
hash = execSync("git rev-parse --short HEAD", { encoding: "utf8" }).trim();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error getting git hash:", error);
|
console.error("Error getting git hash:", error);
|
||||||
hash = "DEV";
|
hash = "DEV";
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONTENT_SECURITY_POLICY =
|
const CONTENT_SECURITY_POLICY =
|
||||||
"script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' data: https://rsms.me https://cdn.jsdelivr.net; img-src 'self' data:; font-src 'self' data: https://rsms.me https://cdn.jsdelivr.net; worker-src 'self' blob:; object-src 'none'; base-uri 'self';";
|
"script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' data: https://rsms.me https://cdn.jsdelivr.net; img-src 'self' data:; font-src 'self' data: https://rsms.me https://cdn.jsdelivr.net; worker-src 'self' blob:; object-src 'none'; base-uri 'self';";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
// VitePWA({
|
// VitePWA({
|
||||||
// registerType: "autoUpdate",
|
// registerType: "autoUpdate",
|
||||||
// strategies: "generateSW",
|
// strategies: "generateSW",
|
||||||
// devOptions: {
|
// devOptions: {
|
||||||
// enabled: true,
|
// enabled: true,
|
||||||
// },
|
// },
|
||||||
// workbox: {
|
// workbox: {
|
||||||
// cleanupOutdatedCaches: true,
|
// cleanupOutdatedCaches: true,
|
||||||
// sourcemap: true,
|
// sourcemap: true,
|
||||||
// },
|
// },
|
||||||
// }),
|
// }),
|
||||||
],
|
],
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ["react/jsx-runtime"],
|
include: ["react/jsx-runtime"],
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
"import.meta.env.VITE_COMMIT_HASH": JSON.stringify(hash),
|
"import.meta.env.VITE_COMMIT_HASH": JSON.stringify(hash),
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
assetsDir: "./",
|
assetsDir: "./",
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@app": path.resolve(process.cwd(), "./src"),
|
"@app": path.resolve(process.cwd(), "./src"),
|
||||||
"@pages": path.resolve(process.cwd(), "./src/pages"),
|
"@pages": path.resolve(process.cwd(), "./src/pages"),
|
||||||
"@components": path.resolve(process.cwd(), "./src/components"),
|
"@components": path.resolve(process.cwd(), "./src/components"),
|
||||||
"@core": path.resolve(process.cwd(), "./src/core"),
|
"@core": path.resolve(process.cwd(), "./src/core"),
|
||||||
"@layouts": path.resolve(process.cwd(), "./src/layouts"),
|
"@layouts": path.resolve(process.cwd(), "./src/layouts"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
headers: {
|
headers: {
|
||||||
"content-security-policy": CONTENT_SECURITY_POLICY,
|
"content-security-policy": CONTENT_SECURITY_POLICY,
|
||||||
"Cross-Origin-Opener-Policy": "same-origin",
|
"Cross-Origin-Opener-Policy": "same-origin",
|
||||||
"Cross-Origin-Embedder-Policy": "require-corp",
|
"Cross-Origin-Embedder-Policy": "require-corp",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,25 +7,25 @@ import { enableMapSet } from "immer";
|
|||||||
|
|
||||||
enableMapSet();
|
enableMapSet();
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@app": path.resolve(process.cwd(), "./src"),
|
"@app": path.resolve(process.cwd(), "./src"),
|
||||||
"@public": path.resolve(process.cwd(), "./public"),
|
"@public": path.resolve(process.cwd(), "./public"),
|
||||||
"@core": path.resolve(process.cwd(), "./src/core"),
|
"@core": path.resolve(process.cwd(), "./src/core"),
|
||||||
"@pages": path.resolve(process.cwd(), "./src/pages"),
|
"@pages": path.resolve(process.cwd(), "./src/pages"),
|
||||||
"@components": path.resolve(process.cwd(), "./src/components"),
|
"@components": path.resolve(process.cwd(), "./src/components"),
|
||||||
"@layouts": path.resolve(process.cwd(), "./src/layouts"),
|
"@layouts": path.resolve(process.cwd(), "./src/layouts"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
environment: "happy-dom",
|
environment: "happy-dom",
|
||||||
globals: true,
|
globals: true,
|
||||||
mockReset: true,
|
mockReset: true,
|
||||||
clearMocks: true,
|
clearMocks: true,
|
||||||
restoreMocks: true,
|
restoreMocks: true,
|
||||||
root: path.resolve(process.cwd(), "./src"),
|
root: path.resolve(process.cwd(), "./src"),
|
||||||
include: ["**/*.{test,spec}.{ts,tsx}"],
|
include: ["**/*.{test,spec}.{ts,tsx}"],
|
||||||
setupFiles: ["./src/tests/setup.ts"],
|
setupFiles: ["./src/tests/setup.ts"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,57 +1,57 @@
|
|||||||
import { join } from "jsr:@std/path@1/join";
|
import { build, emptyDir } from "https://jsr.io/@deno/dnt/0.42.3/mod.ts";
|
||||||
import { build, emptyDir } from "@deno/dnt";
|
import { join } from "https://jsr.io/@std/path/1.1.1/mod.ts";
|
||||||
|
|
||||||
interface DenoJsonConfig {
|
interface DenoJsonConfig {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
description: string;
|
description: string;
|
||||||
imports?: Record<string, string>;
|
imports?: Record<string, string>;
|
||||||
exports?: Record<string, string>;
|
exports?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getJson(filePath: string) {
|
async function getJson(filePath: string) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(await Deno.readTextFile(filePath));
|
return JSON.parse(await Deno.readTextFile(filePath));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
throw new Error(`Error reading or parsing ${filePath}: ${e.message}`);
|
throw new Error(`Error reading or parsing ${filePath}: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Deno.args.length !== 1) {
|
if (Deno.args.length !== 1) {
|
||||||
console.error("Usage: deno task build:npm <path-to-package>");
|
console.error("Usage: deno task build:npm <path-to-package>");
|
||||||
console.error("Example: deno task build:npm packages/core");
|
console.error("Example: deno task build:npm packages/core");
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const packagePath = Deno.args[0];
|
const packagePath = Deno.args[0];
|
||||||
const denoJsonPath = join(packagePath, "deno.json");
|
const denoJsonPath = join(packagePath, "package.json");
|
||||||
const outDir = join(packagePath, "npm");
|
const outDir = join(packagePath, "npm");
|
||||||
|
|
||||||
// Read the deno.json file to get the package metadata.
|
// Read the deno.json file to get the package metadata.
|
||||||
let jsonContent: DenoJsonConfig;
|
let jsonContent: DenoJsonConfig;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
jsonContent = await getJson(denoJsonPath);
|
jsonContent = await getJson(denoJsonPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Error reading or parsing ${denoJsonPath}:`, error);
|
console.log(`Error reading or parsing ${denoJsonPath}:`, error);
|
||||||
|
|
||||||
if (error instanceof Deno.errors.NotFound) {
|
if (error instanceof Deno.errors.NotFound) {
|
||||||
console.error(`Error: Config file not found at ${denoJsonPath}`);
|
console.error(`Error: Config file not found at ${denoJsonPath}`);
|
||||||
} else {
|
} else {
|
||||||
console.error(`Error reading or parsing ${denoJsonPath}:`, error);
|
console.error(`Error reading or parsing ${denoJsonPath}:`, error);
|
||||||
}
|
}
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, version, description } = jsonContent;
|
const { name, version, description } = jsonContent;
|
||||||
|
|
||||||
if (!name || !version || !description) {
|
if (!name || !version || !description) {
|
||||||
console.error(
|
console.error(
|
||||||
`Error: 'name', 'version', and 'description' must be defined in ${denoJsonPath}`,
|
`Error: 'name', 'version', and 'description' must be defined in ${denoJsonPath}`,
|
||||||
);
|
);
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Building ${name}@${version} from ${packagePath}...`);
|
console.log(`Building ${name}@${version} from ${packagePath}...`);
|
||||||
@@ -60,40 +60,42 @@ console.log(`Building ${name}@${version} from ${packagePath}...`);
|
|||||||
await emptyDir(outDir);
|
await emptyDir(outDir);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await build({
|
await build({
|
||||||
entryPoints: [join(packagePath, "mod.ts")],
|
entryPoints: [join(packagePath, "mod.ts")],
|
||||||
outDir,
|
outDir,
|
||||||
test: false,
|
test: false,
|
||||||
shims: {
|
esModule: true,
|
||||||
deno: true,
|
declaration: false,
|
||||||
},
|
shims: {
|
||||||
package: {
|
deno: true,
|
||||||
name,
|
},
|
||||||
version,
|
package: {
|
||||||
description,
|
name,
|
||||||
license: "GPL-3.0-only",
|
version,
|
||||||
repository: {
|
description,
|
||||||
type: "git",
|
license: "GPL-3.0-only",
|
||||||
url: "git+https://github.com/meshtastic/web.git",
|
repository: {
|
||||||
},
|
type: "git",
|
||||||
bugs: {
|
url: "git+https://github.com/meshtastic/web.git",
|
||||||
url: "https://github.com/meshtastic/web/issues",
|
},
|
||||||
},
|
bugs: {
|
||||||
},
|
url: "https://github.com/meshtastic/web/issues",
|
||||||
compilerOptions: {
|
},
|
||||||
lib: ["DOM", "ESNext"],
|
},
|
||||||
},
|
compilerOptions: {
|
||||||
postBuild() {
|
lib: ["DOM", "ESNext"],
|
||||||
Deno.copyFileSync("LICENSE", join(outDir, "LICENSE"));
|
},
|
||||||
Deno.copyFileSync(
|
postBuild() {
|
||||||
join(packagePath, "README.md"),
|
Deno.copyFileSync("LICENSE", join(outDir, "LICENSE"));
|
||||||
join(outDir, "README.md"),
|
Deno.copyFileSync(
|
||||||
);
|
join(packagePath, "README.md"),
|
||||||
},
|
join(outDir, "README.md"),
|
||||||
});
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error building ${name}@${version}:`, error);
|
console.error(`Error building ${name}@${version}:`, error);
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`✅ Successfully built ${name}@${version} to ${outDir}`);
|
console.log(`✅ Successfully built ${name}@${version} to ${outDir}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user