Browse Source

Merge branch 'main' into persistent-http-nodes

pull/708/head
Dan Ditomaso 1 year ago
committed by GitHub
parent
commit
95c446b86d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/ci.yml
  2. 37
      .github/workflows/js-ci.yml
  3. 120
      .github/workflows/release.yml
  4. 0
      LICENSE
  5. 8
      README.md
  6. 27
      deno.json
  7. 76
      deno.lock
  8. 1
      packages/core/README.md
  9. 1
      packages/core/deno.json
  10. 1
      packages/transport-deno/README.md
  11. 1
      packages/transport-deno/deno.json
  12. 1
      packages/transport-http/README.md
  13. 1
      packages/transport-http/deno.json
  14. 28
      packages/transport-node/README.md
  15. 8
      packages/transport-node/deno.json
  16. 1
      packages/transport-node/mod.ts
  17. 77
      packages/transport-node/src/transport.ts
  18. 1
      packages/transport-web-bluetooth/README.md
  19. 1
      packages/transport-web-bluetooth/deno.json
  20. 1
      packages/transport-web-serial/README.md
  21. 1
      packages/transport-web-serial/deno.json
  22. 1
      packages/web/deno.json
  23. 5
      packages/web/index.html
  24. 2
      packages/web/public/i18n/locales/en/dialog.json
  25. 1
      packages/web/src/components/Dialog/LocationResponseDialog.tsx
  26. 1
      packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx
  27. 3
      packages/web/src/components/Dialog/PKIBackupDialog.tsx
  28. 3
      packages/web/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx
  29. 7
      packages/web/src/core/dto/NodeNumToNodeInfoDTO.ts
  30. 1
      packages/web/src/pages/Messages.tsx
  31. 4
      packages/web/vite.config.ts
  32. 101
      scripts/build_npm_package.ts

2
.github/workflows/ci.yml

@ -41,4 +41,4 @@ jobs:
run: deno fmt --check run: deno fmt --check
- name: Build Package - name: Build Package
run: deno task build run: deno task --filter web build

37
.github/workflows/js-ci.yml

@ -1,37 +0,0 @@
name: Pull Request
on:
push:
paths:
- "packages/core"
- "packages/transport-deno"
- "packages/transport-http"
- "packages/transport-web-bluetooth"
- "packages/transport-web-serial"
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Check formatting
run: deno fmt --check
- name: Check types
run: deno lint
- name: Publish to JSR
run: npx jsr publish

120
.github/workflows/release.yml

@ -5,65 +5,140 @@ on:
types: [released, prereleased] types: [released, prereleased]
permissions: permissions:
id-token: write # This is required for requesting the JWT
contents: write contents: write
packages: write packages: write
jobs: jobs:
build-and-package: build-and-publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout # --- Checkout code ---
- name: Checkout Code
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
fetch-depth: 0
# --- Setup Deno ---
- name: Setup Deno - name: Setup Deno
uses: denoland/setup-deno@v2 uses: denoland/setup-deno@v2
with: with:
deno-version: v2.x deno-version: v2.x
- name: Install Dependencies - name: Cache Deno Dependencies
working-directory: packages/web uses: actions/cache@v4
run: deno install with:
path: ~/.cache/deno
key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock') }}
restore-keys: |
${{ runner.os }}-deno-
- name: Run tests # --- Determine Changed Packages ---
working-directory: packages/web - name: Get Changed Package Directories
run: deno task test id: changed_packages
uses: tj-actions/changed-files@v46
with:
dir_names: true
files: packages/**
files_ignore: "packages/web/**,packages/transport-deno/npm/**"
# --- Setup Node for NPM Publishing ---
- name: Setup Node.js
if: steps.changed_packages.outputs.all_changed_and_modified_files != ''
uses: actions/setup-node@v4
with:
node-version: 22
registry-url: "https://registry.npmjs.org"
- name: Verify NPM Authentication
if: steps.changed_packages.outputs.all_changed_and_modified_files != ''
run: npm whoami
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# --- Build and Publish Packages to NPM & JSR ---
- name: Build and Publish Changed Packages
if: steps.changed_packages.outputs.all_changed_and_modified_files != ''
run: |
set -euo pipefail
- name: Build Package excluded=("packages/web packages/transport-deno")
for pkg_dir in ${{ steps.changed_packages.outputs.all_changed_and_modified_files }}; do
echo "Building for NPM..."
deno task build:npm "$pkg_dir"
echo "Publishing to NPM..."
npm publish "$pkg_dir/npm" --access public
echo "Publishing to JSR..."
# We run this in a subshell to change directory just for this command.
# --allow-dirty is necessary because the 'npm' build directory is untracked.
(cd "$pkg_dir" && deno publish --allow-dirty)
done
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: No Packages Changed
if: steps.changed_packages.outputs.all_changed_and_modified_files == ''
run: echo "No changed packages detected. Skipping publish."
# --- Web Package Specific Tasks ---
- name: Check for Web Package Changes
id: web_changes
run: |
if [[ "${{ steps.changed_packages.outputs.all_changed_and_modified_files }}" == *"packages/web"* ]]; then
echo "web_changed=true" >> $GITHUB_OUTPUT
else
echo "web_changed=false" >> $GITHUB_OUTPUT
fi
- name: Run Web App Tests
if: steps.web_changes.outputs.web_changed == 'true'
working-directory: packages/web working-directory: packages/web
run: deno task build run: deno task test
- name: Package Output - name: Create Web App Release Archive
if: steps.web_changes.outputs.web_changed == 'true'
working-directory: packages/web working-directory: packages/web
run: deno task package run: deno task package # Generates dist/build.tar
- name: Archive compressed build - name: Upload Web App Archive
if: steps.web_changes.outputs.web_changed == 'true'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: build name: web-build
if-no-files-found: error
path: packages/web/dist/build.tar path: packages/web/dist/build.tar
- name: Attach build.tar to release - name: Attach Web Archive to GitHub Release
run: | if: steps.web_changes.outputs.web_changed == 'true'
gh release upload ${{ github.event.release.tag_name }} packages/web/dist/build.tar run: gh release upload ${{ github.event.release.tag_name }} packages/web/dist/build.tar
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# --- Build & Push Container Image ---
- name: Set up QEMU - name: Set up QEMU
if: steps.web_changes.outputs.web_changed == 'true'
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
- name: Buildah Build - name: Build Container Image
if: steps.web_changes.outputs.web_changed == 'true'
id: build-container id: build-container
uses: redhat-actions/buildah-build@v2 uses: redhat-actions/buildah-build@v2
with: with:
containerfiles: | containerfiles: |
./infra/Containerfile ./infra/Containerfile
image: ${{github.event.repository.full_name}} image: ghcr.io/${{ github.repository }}
tags: latest ${{ github.sha }} tags: latest, ${{ github.event.release.tag_name }}
oci: true oci: true
platforms: linux/amd64, linux/arm64 platforms: linux/amd64, linux/arm64
- name: Push To Registry - name: Push Container to GHCR
id: push-to-registry id: push-to-registry
if: steps.web_changes.outputs.web_changed == 'true'
uses: redhat-actions/push-to-registry@v2 uses: redhat-actions/push-to-registry@v2
with: with:
image: ${{ steps.build-container.outputs.image }} image: ${{ steps.build-container.outputs.image }}
@ -72,5 +147,6 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Print image url - name: Output Image URL
if: steps.web_changes.outputs.web_changed == 'true'
run: echo "Image pushed to ${{ steps.push-to-registry.outputs.registry-paths }}" run: echo "Image pushed to ${{ steps.push-to-registry.outputs.registry-paths }}"

0
packages/web/LICENSE → LICENSE

8
README.md

@ -20,13 +20,14 @@ All projects are located within the `packages/` directory:
designed to be hosted or served directly from a Meshtastic node. designed to be hosted or served directly from a Meshtastic node.
- **[Hosted version](https://client.meshtastic.org)** - **[Hosted version](https://client.meshtastic.org)**
- **`packages/core`:** Core functionality for Meshtastic JS. - **`packages/core`:** Core functionality for Meshtastic JS.
- **`packages/transport-node`:** TCP Transport for the NodeJS runtime.
- **`packages/transport-deno`:** TCP Transport for the Deno runtime. - **`packages/transport-deno`:** TCP Transport for the Deno runtime.
- **`packages/transport-http`:** HTTP Transport. - **`packages/transport-http`:** HTTP Transport.
- **`packages/transport-web-bluetooth`:** Web Bluetooth Transport. - **`packages/transport-web-bluetooth`:** Web Bluetooth Transport.
- **`packages/transport-web-serial`:** Web Serial Transport. - **`packages/transport-web-serial`:** Web Serial Transport.
All `Meshtastic JS` packages (core and transports) are published to All `Meshtastic JS` packages (core and transports) are published both to
[JSR](https://jsr.io/@meshtastic). [JSR](https://jsr.io/@meshtastic). [NPM](https://www.npmjs.com/org/meshtastic)
--- ---
@ -34,8 +35,7 @@ All `Meshtastic JS` packages (core and transports) are published to
| Project | Repobeats | | Project | Repobeats |
| :-------------------- | :-------------------------------------------------------------------------------------------------------------------- | | :-------------------- | :-------------------------------------------------------------------------------------------------------------------- |
| Meshtastic Web Client | ![Alt](https://repobeats.axiom.co/api/embed/e5b062db986cb005d83e81724c00cb2b9cce8e4c.svg "Repobeats analytics image") | | Meshtastic Web | ![Alt](https://repobeats.axiom.co/api/embed/e5b062db986cb005d83e81724c00cb2b9cce8e4c.svg "Repobeats analytics image") |
| Meshtastic JS | ![Alt](https://repobeats.axiom.co/api/embed/5330641586e92a2ec84676fedb98f6d4a7b25d69.svg "Repobeats analytics image") |
--- ---

27
deno.json

@ -3,12 +3,17 @@
"./packages/web", "./packages/web",
"./packages/core", "./packages/core",
"./packages/transport-deno", "./packages/transport-deno",
"./packages/transport-node",
"./packages/transport-http", "./packages/transport-http",
"./packages/transport-web-bluetooth", "./packages/transport-web-bluetooth",
"./packages/transport-web-serial" "./packages/transport-web-serial"
], ],
"tasks": {
"build:npm": "deno run -A scripts/build_npm_package.ts"
},
"imports": { "imports": {
"@bufbuild/protobuf": "npm:@bufbuild/protobuf@^2.2.3", "@bufbuild/protobuf": "npm:@bufbuild/protobuf@^2.2.3",
"@deno/dnt": "jsr:@deno/dnt@^0.42.1",
"@meshtastic/protobufs": "jsr:@meshtastic/protobufs@^2.7.0", "@meshtastic/protobufs": "jsr:@meshtastic/protobufs@^2.7.0",
"@types/node": "npm:@types/node@^22.13.10", "@types/node": "npm:@types/node@^22.13.10",
"ste-simple-events": "npm:ste-simple-events@^3.0.11", "ste-simple-events": "npm:ste-simple-events@^3.0.11",
@ -33,16 +38,14 @@
"unstable": [ "unstable": [
"sloppy-imports" "sloppy-imports"
], ],
"tasks": { "exclude": [
"dev": "deno task dev:ui", "node_modules",
"dev:ui": "VITE_APP_VERSION=development deno run -A npm:vite dev", "dist",
"build": "deno run -A npm:vite build", "npm",
"build:analyze": "BUNDLE_ANALYZE=true deno task build", "build",
"lint": "deno lint src/", "coverage",
"lint:fix": "deno lint --fix src/", "out",
"format": "deno fmt src/", ".vscode-test"
"test": "deno run -A npm:vitest", ]
"preview": "deno run -A npm:vite preview",
"package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ ."
}
} }

76
deno.lock

@ -1,9 +1,25 @@
{ {
"version": "5", "version": "5",
"specifiers": { "specifiers": {
"jsr:@david/code-block-writer@^13.0.2": "13.0.3",
"jsr:@deno/[email protected]": "0.20.1",
"jsr:@deno/dnt@~0.42.1": "0.42.1",
"jsr:@deno/[email protected]": "0.86.9",
"jsr:@meshtastic/protobufs@^2.7.0": "2.7.0", "jsr:@meshtastic/protobufs@^2.7.0": "2.7.0",
"jsr:@std/bytes@^1.0.5": "1.0.6",
"jsr:@std/fmt@1": "1.0.8",
"jsr:@std/fmt@^1.0.3": "1.0.8",
"jsr:@std/fs@1": "1.0.19",
"jsr:@std/fs@^1.0.6": "1.0.19",
"jsr:@std/internal@^1.0.9": "1.0.9", "jsr:@std/internal@^1.0.9": "1.0.9",
"jsr:@std/[email protected]": "0.225.2",
"jsr:@std/path@*": "1.1.1",
"jsr:@std/path@1": "1.1.1",
"jsr:@std/path@^1.0.8": "1.1.1",
"jsr:@std/path@^1.1.0": "1.1.1", "jsr:@std/path@^1.1.0": "1.1.1",
"jsr:@std/path@^1.1.1": "1.1.1",
"jsr:@ts-morph/[email protected]": "0.25.0",
"jsr:@ts-morph/[email protected]": "0.25.0",
"npm:@bufbuild/protobuf@^2.2.3": "2.6.0", "npm:@bufbuild/protobuf@^2.2.3": "2.6.0",
"npm:@bufbuild/protobuf@^2.6.0": "2.6.0", "npm:@bufbuild/protobuf@^2.6.0": "2.6.0",
"npm:@hookform/resolvers@^5.1.1": "[email protected][email protected][email protected]", "npm:@hookform/resolvers@^5.1.1": "[email protected][email protected][email protected]",
@ -95,20 +111,79 @@
"npm:[email protected]": "5.0.6_@[email protected][email protected][email protected]" "npm:[email protected]": "5.0.6_@[email protected][email protected][email protected]"
}, },
"jsr": { "jsr": {
"@david/[email protected]": {
"integrity": "f98c77d320f5957899a61bfb7a9bead7c6d83ad1515daee92dbacc861e13bb7f"
},
"@deno/[email protected]": {
"integrity": "dc4f3add14307f3ff3b712441ea4acabcbfc9a13f67c5adc78c3aac16ac5e2a0",
"dependencies": [
"jsr:@deno/graph",
"jsr:@std/fmt@^1.0.3",
"jsr:@std/fs@^1.0.6",
"jsr:@std/io",
"jsr:@std/path@^1.0.8"
]
},
"@deno/[email protected]": {
"integrity": "85322b38eb40d4e8c5216d62536152c35b1bda9dc47c8c60860610397b960223",
"dependencies": [
"jsr:@david/code-block-writer",
"jsr:@deno/cache-dir",
"jsr:@std/fmt@1",
"jsr:@std/fs@1",
"jsr:@std/path@1",
"jsr:@ts-morph/bootstrap"
]
},
"@deno/[email protected]": {
"integrity": "c4f353a695bcc5246c099602977dabc6534eacea9999a35a8cb24e807192e6a1"
},
"@meshtastic/[email protected]": { "@meshtastic/[email protected]": {
"integrity": "38357241bd8a7431c87366dbe12ce9e69f204ebb6ec23da12f7682765b6c8376", "integrity": "38357241bd8a7431c87366dbe12ce9e69f204ebb6ec23da12f7682765b6c8376",
"dependencies": [ "dependencies": [
"npm:@bufbuild/protobuf@^2.2.3" "npm:@bufbuild/protobuf@^2.2.3"
] ]
}, },
"@std/[email protected]": {
"integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a"
},
"@std/[email protected]": {
"integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7"
},
"@std/[email protected]": {
"integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06",
"dependencies": [
"jsr:@std/internal",
"jsr:@std/path@^1.1.1"
]
},
"@std/[email protected]": { "@std/[email protected]": {
"integrity": "bdfb97f83e4db7a13e8faab26fb1958d1b80cc64366501af78a0aee151696eb8" "integrity": "bdfb97f83e4db7a13e8faab26fb1958d1b80cc64366501af78a0aee151696eb8"
}, },
"@std/[email protected]": {
"integrity": "3c740cd4ee4c082e6cfc86458f47e2ab7cb353dc6234d5e9b1f91a2de5f4d6c7",
"dependencies": [
"jsr:@std/bytes"
]
},
"@std/[email protected]": { "@std/[email protected]": {
"integrity": "fe00026bd3a7e6a27f73709b83c607798be40e20c81dde655ce34052fd82ec76", "integrity": "fe00026bd3a7e6a27f73709b83c607798be40e20c81dde655ce34052fd82ec76",
"dependencies": [ "dependencies": [
"jsr:@std/internal" "jsr:@std/internal"
] ]
},
"@ts-morph/[email protected]": {
"integrity": "3cd33ee80ac0aab8e5d2660c639a02187f0c8abfe454636ce86c00eb7e8407db",
"dependencies": [
"jsr:@ts-morph/common"
]
},
"@ts-morph/[email protected]": {
"integrity": "e3ed1771e2fb61fbc3d2cb39ebbc4f89cd686d6d9bc6d91a71372be055ac1967",
"dependencies": [
"jsr:@std/fs@1",
"jsr:@std/path@1"
]
} }
}, },
"npm": { "npm": {
@ -5106,6 +5181,7 @@
}, },
"workspace": { "workspace": {
"dependencies": [ "dependencies": [
"jsr:@deno/dnt@~0.42.1",
"jsr:@meshtastic/protobufs@^2.7.0", "jsr:@meshtastic/protobufs@^2.7.0",
"npm:@bufbuild/protobuf@^2.2.3", "npm:@bufbuild/protobuf@^2.2.3",
"npm:@types/node@^22.13.10", "npm:@types/node@^22.13.10",

1
packages/core/README.md

@ -11,6 +11,7 @@
`@meshtastic/core` Provides core functionality for interfacing with Meshtastic `@meshtastic/core` Provides core functionality for interfacing with Meshtastic
devices. Installation instructions are available at devices. Installation instructions are available at
[JSR](https://jsr.io/@meshtastic/core) [JSR](https://jsr.io/@meshtastic/core)
[NPM](https://www.npmjs.com/org/meshtastic)
## Usage ## Usage

1
packages/core/deno.json

@ -1,6 +1,7 @@
{ {
"name": "@meshtastic/core", "name": "@meshtastic/core",
"version": "2.6.4", "version": "2.6.4",
"description": "Core functionalities for Meshtastic web applications.",
"exports": { "exports": {
".": "./mod.ts" ".": "./mod.ts"
}, },

1
packages/transport-deno/README.md

@ -11,6 +11,7 @@
`@meshtastic/transport-deno` Provides TCP transport (Deno) for Meshtastic `@meshtastic/transport-deno` Provides TCP transport (Deno) for Meshtastic
devices. Installation instructions are avaliable at devices. Installation instructions are avaliable at
[JSR](https://jsr.io/@meshtastic/transport-deno) [JSR](https://jsr.io/@meshtastic/transport-deno)
[NPM](https://www.npmjs.com/org/meshtastic/transport-deno)
## Usage ## Usage

1
packages/transport-deno/deno.json

@ -1,6 +1,7 @@
{ {
"name": "@meshtastic/transport-deno", "name": "@meshtastic/transport-deno",
"version": "0.1.1", "version": "0.1.1",
"description": "Deno-specific transport layer for Meshtastic web applications.",
"exports": { "exports": {
".": "./mod.ts" ".": "./mod.ts"
} }

1
packages/transport-http/README.md

@ -11,6 +11,7 @@
`@meshtastic/transport-http` Provides HTTP(S) transport for Meshtastic devices. `@meshtastic/transport-http` Provides HTTP(S) transport for Meshtastic devices.
Installation instructions are available at Installation instructions are available at
[JSR](https://jsr.io/@meshtastic/transport-http) [JSR](https://jsr.io/@meshtastic/transport-http)
[NPM](https://www.npmjs.com/package/@meshtastic/transport-http)
## Usage ## Usage

1
packages/transport-http/deno.json

@ -1,6 +1,7 @@
{ {
"name": "@meshtastic/transport-http", "name": "@meshtastic/transport-http",
"version": "0.2.1", "version": "0.2.1",
"description": "A transport layer for Meshtastic applications using HTTP.",
"exports": { "exports": {
".": "./mod.ts" ".": "./mod.ts"
} }

28
packages/transport-node/README.md

@ -0,0 +1,28 @@
# @meshtastic/transport-node
[![JSR](https://jsr.io/badges/@meshtastic/transport-node)](https://jsr.io/@meshtastic/transport-node)
[![CI](https://img.shields.io/github/actions/workflow/status/meshtastic/js/ci.yml?branch=master&label=actions&logo=github&color=yellow)](https://github.com/meshtastic/js/actions/workflows/ci.yml)
[![CLA assistant](https://cla-assistant.io/readme/badge/meshtastic/meshtastic.js)](https://cla-assistant.io/meshtastic/meshtastic.js)
[![Fiscal Contributors](https://opencollective.com/meshtastic/tiers/badge.svg?label=Fiscal%20Contributors&color=deeppink)](https://opencollective.com/meshtastic/)
[![Vercel](https://img.shields.io/static/v1?label=Powered%20by&message=Vercel&style=flat&logo=vercel&color=000000)](https://vercel.com?utm_source=meshtastic&utm_campaign=oss)
## Overview
`@meshtastic/transport-node` Provides TCP transport (Node) for Meshtastic
devices. Installation instructions are available at
[JSR](https://jsr.io/@meshtastic/transport-node)
[NPM](https://www.npmjs.com/package/@meshtastic/transport-node)
## Usage
```ts
import { MeshDevice } from "@meshtastic/core";
import { TransportNode } from "@meshtastic/transport-node";
const transport = await TransportNode.create("10.10.0.57");
const device = new MeshDevice(transport);
```
## Stats
![Alt](https://repobeats.axiom.co/api/embed/5330641586e92a2ec84676fedb98f6d4a7b25d69.svg "Repobeats analytics image")

8
packages/transport-node/deno.json

@ -0,0 +1,8 @@
{
"name": "@meshtastic/transport-node",
"version": "0.0.1",
"description": "NodeJS-specific transport layer for Meshtastic web applications.",
"exports": {
".": "./mod.ts"
}
}

1
packages/transport-node/mod.ts

@ -0,0 +1 @@
export { TransportNode } from "./src/transport.ts";

77
packages/transport-node/src/transport.ts

@ -0,0 +1,77 @@
import { Utils } from "@meshtastic/core";
import type { Types } from "@meshtastic/core";
import { Socket } from "node:net";
import { Readable, Writable } from "node:stream";
export class TransportNode implements Types.Transport {
private readonly _toDevice: WritableStream<Uint8Array>;
private readonly _fromDevice: ReadableStream<Types.DeviceOutput>;
/**
* Creates and connects a new TransportNode instance.
* @param hostname - The IP address or hostname of the Meshtastic device.
* @param port - The port number for the TCP connection (defaults to 4403).
* @returns A promise that resolves with a connected TransportNode instance.
*/
public static create(hostname: string, port = 4403): Promise<TransportNode> {
return new Promise((resolve, reject) => {
const socket = new Socket();
const onError = (err: Error) => {
socket.destroy();
reject(err);
};
socket.once("error", onError);
socket.connect(port, hostname, () => {
socket.removeListener("error", onError);
resolve(new TransportNode(socket));
});
});
}
/**
* Constructs a new TransportNode.
* @param connection - An active Node.js net.Socket connection.
*/
constructor(connection: Socket) {
connection.on("error", (err) => {
console.error("Socket connection error:", err);
});
const fromDeviceSource = Readable.toWeb(connection) as ReadableStream<
Uint8Array
>;
this._fromDevice = fromDeviceSource.pipeThrough(Utils.fromDeviceStream());
// Stream for data going FROM the application TO the Meshtastic device.
const toDeviceTransform = new TransformStream<Uint8Array, Uint8Array>();
this._toDevice = toDeviceTransform.writable;
// 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 generically typed stream (`WritableStream<any>`).
toDeviceTransform.readable.pipeTo(
Writable.toWeb(connection) as WritableStream<Uint8Array>,
)
.catch((err) => {
console.error("Error piping data to socket:", err);
connection.destroy(err as Error);
});
}
/**
* The WritableStream to send data to the Meshtastic device.
*/
public get toDevice(): WritableStream<Uint8Array> {
return this._toDevice;
}
/**
* The ReadableStream to receive data from the Meshtastic device.
*/
public get fromDevice(): ReadableStream<Types.DeviceOutput> {
return this._fromDevice;
}
}

1
packages/transport-web-bluetooth/README.md

@ -11,6 +11,7 @@
`@meshtastic/transport-web-bluetooth` Provides Web Bluetooth transport for `@meshtastic/transport-web-bluetooth` Provides Web Bluetooth transport for
Meshtastic devices. Installation instructions are available at Meshtastic devices. Installation instructions are available at
[JSR](https://jsr.io/@meshtastic/transport-web-bluetooth) [JSR](https://jsr.io/@meshtastic/transport-web-bluetooth)
[NPM](https://www.npmjs.com/org/meshtastic/transport-web-bluetooth)
## Usage ## Usage

1
packages/transport-web-bluetooth/deno.json

@ -1,6 +1,7 @@
{ {
"name": "@meshtastic/transport-web-bluetooth", "name": "@meshtastic/transport-web-bluetooth",
"version": "0.1.2", "version": "0.1.2",
"description": "A transport layer for Meshtastic applications using Web Bluetooth.",
"exports": { "exports": {
".": "./mod.ts" ".": "./mod.ts"
}, },

1
packages/transport-web-serial/README.md

@ -11,6 +11,7 @@
`@meshtastic/transport-web-serial` Provides Web Serial transport for Meshtastic `@meshtastic/transport-web-serial` Provides Web Serial transport for Meshtastic
devices. Installation instructions are avaliable at devices. Installation instructions are avaliable at
[JSR](https://jsr.io/@meshtastic/transport-web-serial) [JSR](https://jsr.io/@meshtastic/transport-web-serial)
[NPM](https://www.npmjs.com/package/@meshtastic/transport-web-serial)
## Usage ## Usage

1
packages/transport-web-serial/deno.json

@ -1,6 +1,7 @@
{ {
"name": "@meshtastic/transport-web-serial", "name": "@meshtastic/transport-web-serial",
"version": "0.2.1", "version": "0.2.1",
"description": "A transport layer for Meshtastic applications using Web Serial API.",
"exports": { "exports": {
".": "./mod.ts" ".": "./mod.ts"
}, },

1
packages/web/deno.json

@ -45,7 +45,6 @@
], ],
"strictPropertyInitialization": false "strictPropertyInitialization": false
}, },
"exclude": [ "exclude": [
"routeTree.gen.ts", "routeTree.gen.ts",
"node_modules/", "node_modules/",

5
packages/web/index.html

@ -19,10 +19,7 @@
crossorigin="anonymous" crossorigin="anonymous"
/> />
<meta name="theme-color" content="#67ea94" /> <meta name="theme-color" content="#67ea94" />
<meta <meta name="viewport" content="width=device-width, initial-scale=1" />
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
/>
<meta name="description" content="Meshtastic Web Client" /> <meta name="description" content="Meshtastic Web Client" />
<title>Meshtastic Web</title> <title>Meshtastic Web</title>
</head> </head>

2
packages/web/public/i18n/locales/en/dialog.json

@ -218,6 +218,6 @@
"managedMode": { "managedMode": {
"confirmUnderstanding": "Yes, I know what I'm doing", "confirmUnderstanding": "Yes, I know what I'm doing",
"title": "Are you sure?", "title": "Are you sure?",
"description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can <bold>only</bold> be changed through Remote Admin messages. This setting is not required for remote node administration." "description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration."
} }
} }

1
packages/web/src/components/Dialog/LocationResponseDialog.tsx

@ -47,6 +47,7 @@ export const LocationResponseDialog = ({
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{t("locationResponse.title", { {t("locationResponse.title", {
interpolation: { escapeValue: false },
identifier: `${longName} (${shortName})`, identifier: `${longName} (${shortName})`,
})} })}
</DialogTitle> </DialogTitle>

1
packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx

@ -169,6 +169,7 @@ export const NodeDetailsDialog = ({
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{t("nodeDetails.title", { {t("nodeDetails.title", {
interpolation: { escapeValue: false },
identifier: `${node.user?.longName ?? t("unknown.shortName")} (${ identifier: `${node.user?.longName ?? t("unknown.shortName")} (${
node.user?.shortName ?? t("unknown.shortName") node.user?.shortName ?? t("unknown.shortName")
})`, })`,

3
packages/web/src/components/Dialog/PKIBackupDialog.tsx

@ -50,6 +50,7 @@ export const PkiBackupDialog = ({
<head> <head>
<title>${ <title>${
t("pkiBackup.header", { t("pkiBackup.header", {
interpolation: { escapeValue: false },
shortName: getMyNode()?.user?.shortName ?? t("unknown.shortName"), shortName: getMyNode()?.user?.shortName ?? t("unknown.shortName"),
longName: getMyNode()?.user?.longName ?? t("unknown.longName"), longName: getMyNode()?.user?.longName ?? t("unknown.longName"),
}) })
@ -63,6 +64,7 @@ export const PkiBackupDialog = ({
<body> <body>
<h1>${ <h1>${
t("pkiBackup.header", { t("pkiBackup.header", {
interpolation: { escapeValue: false },
shortName: getMyNode()?.user?.shortName ?? t("unknown.shortName"), shortName: getMyNode()?.user?.shortName ?? t("unknown.shortName"),
longName: getMyNode()?.user?.longName ?? t("unknown.longName"), longName: getMyNode()?.user?.longName ?? t("unknown.longName"),
}) })
@ -103,6 +105,7 @@ export const PkiBackupDialog = ({
const link = document.createElement("a"); const link = document.createElement("a");
link.href = url; link.href = url;
link.download = t("pkiBackup.fileName", { link.download = t("pkiBackup.fileName", {
interpolation: { escapeValue: false },
shortName: getMyNode()?.user?.shortName ?? t("unknown.shortName"), shortName: getMyNode()?.user?.shortName ?? t("unknown.shortName"),
longName: getMyNode()?.user?.longName ?? t("unknown.longName"), longName: getMyNode()?.user?.longName ?? t("unknown.longName"),
}); });

3
packages/web/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx

@ -10,7 +10,7 @@ import { LockKeyholeOpenIcon } from "lucide-react";
import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts"; import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useMessageStore } from "../../../core/stores/messageStore/index.ts"; import { useMessageStore } from "@core/stores/messageStore/index.ts";
export interface RefreshKeysDialogProps { export interface RefreshKeysDialogProps {
open: boolean; open: boolean;
@ -35,6 +35,7 @@ export const RefreshKeysDialog = (
const text = { const text = {
title: t("refreshKeys.title", { title: t("refreshKeys.title", {
interpolation: { escapeValue: false },
identifier: nodeWithError?.user?.longName ?? "", identifier: nodeWithError?.user?.longName ?? "",
}), }),
description: `${t("refreshKeys.description.unableToSendDmPrefix")}${ description: `${t("refreshKeys.description.unableToSendDmPrefix")}${

7
packages/web/src/core/dto/NodeNumToNodeInfoDTO.ts

@ -8,14 +8,11 @@ class NodeInfoFactory {
const last4 = userIdHex.slice(-4); const last4 = userIdHex.slice(-4);
const longName = `Meshtastic ${last4}`; const longName = `Meshtastic ${last4}`;
const shortName = last4; const shortName = last4;
const hwModel = Protobuf.Mesh.HardwareModel.UNSET;
return create(Protobuf.Mesh.UserSchema, { return create(Protobuf.Mesh.UserSchema, {
id: userId, id: userId,
longName: longName, longName: longName.toString(),
shortName: shortName, shortName: shortName.toString(),
hwModel: hwModel,
isLicensed: false,
}); });
} }

1
packages/web/src/pages/Messages.tsx

@ -308,6 +308,7 @@ export const MessagesPage = () => {
<PageLayout <PageLayout
label={`${ label={`${
t("page.title", { t("page.title", {
interpolation: { escapeValue: false },
chatName: isBroadcast && currentChannel chatName: isBroadcast && currentChannel
? getChannelName(currentChannel) ? getChannelName(currentChannel)
: isDirect && otherNode : isDirect && otherNode

4
packages/web/vite.config.ts

@ -12,6 +12,9 @@ try {
hash = "DEV"; hash = "DEV";
} }
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';";
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
react(), react(),
@ -49,6 +52,7 @@ export default defineConfig({
server: { server: {
port: 3000, port: 3000,
headers: { headers: {
"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",
}, },

101
scripts/build_npm_package.ts

@ -0,0 +1,101 @@
import { build, emptyDir } from "@deno/dnt";
import { join } from "jsr:@std/path@1/join";
interface DenoJsonConfig {
name: string;
version: string;
description: string;
imports?: Record<string, string>;
exports?: Record<string, string>;
}
async function getJson(filePath: string) {
try {
return JSON.parse(await Deno.readTextFile(filePath));
} catch (e) {
if (e instanceof Error) {
throw new Error(`Error reading or parsing ${filePath}: ${e.message}`);
}
}
}
if (Deno.args.length !== 1) {
console.error("Usage: deno task build:npm <path-to-package>");
console.error(
"Example: deno task build:npm packages/core",
);
Deno.exit(1);
}
const packagePath = Deno.args[0];
const denoJsonPath = join(packagePath, "deno.json");
const outDir = join(packagePath, "npm");
// Read the deno.json file to get the package metadata.
let jsonContent: DenoJsonConfig;
try {
jsonContent = await getJson(denoJsonPath);
} catch (error) {
console.log(`Error reading or parsing ${denoJsonPath}:`, error);
if (error instanceof Deno.errors.NotFound) {
console.error(`Error: Config file not found at ${denoJsonPath}`);
} else {
console.error(`Error reading or parsing ${denoJsonPath}:`, error);
}
Deno.exit(1);
}
const { name, version, description } = jsonContent;
if (!name || !version || !description) {
console.error(
`Error: 'name', 'version', and 'description' must be defined in ${denoJsonPath}`,
);
Deno.exit(1);
}
console.log(`Building ${name}@${version} from ${packagePath}...`);
// Clean the output directory before building.
await emptyDir(outDir);
try {
await build({
entryPoints: [join(packagePath, "mod.ts")],
outDir,
test: false,
shims: {
deno: true,
},
package: {
name,
version,
description,
license: "GPL-3.0-only",
repository: {
type: "git",
url: "git+https://github.com/meshtastic/web.git",
},
bugs: {
url: "https://github.com/meshtastic/web/issues",
},
},
compilerOptions: {
lib: ["DOM", "ESNext"],
},
postBuild() {
Deno.copyFileSync("LICENSE", join(outDir, "LICENSE"));
Deno.copyFileSync(
join(packagePath, "README.md"),
join(outDir, "README.md"),
);
},
});
} catch (error) {
console.error(`Error building ${name}@${version}:`, error);
Deno.exit(1);
}
console.log(`✅ Successfully built ${name}@${version} to ${outDir}`);
Loading…
Cancel
Save