@meshcore-cz/meshpkt 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,25 +1,202 @@
1
1
  # @meshcore-cz/meshpkt
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@meshcore-cz/meshpkt)](https://www.npmjs.com/package/@meshcore-cz/meshpkt)
4
+ [![Go Reference](https://pkg.go.dev/badge/github.com/meshcore-cz/meshpkt.svg)](https://pkg.go.dev/github.com/meshcore-cz/meshpkt)
5
+
3
6
  MeshCore radio packet codec for JavaScript and TypeScript, powered by WebAssembly.
4
7
 
8
+ > **Early development.** This package is a work in progress. APIs may change before v1.0 — pin to a specific version in production.
9
+
10
+ - **npm:** [npmjs.com/package/@meshcore-cz/meshpkt](https://www.npmjs.com/package/@meshcore-cz/meshpkt)
11
+ - **Go source & docs:** [pkg.go.dev/github.com/meshcore-cz/meshpkt](https://pkg.go.dev/github.com/meshcore-cz/meshpkt)
12
+
5
13
  ## Install
6
14
 
7
15
  ```sh
8
16
  npm install @meshcore-cz/meshpkt
9
- ````
17
+ ```
10
18
 
11
- ## Usage
19
+ ## Quick start
12
20
 
13
21
  ```ts
14
22
  import { load } from "@meshcore-cz/meshpkt";
15
23
 
16
24
  const meshpkt = await load();
25
+ ```
26
+
27
+ `load()` fetches and initialises the bundled TinyGo WASM module. Call it once; subsequent calls return the same promise. All methods are synchronous after that.
28
+
29
+ ## Error handling
30
+
31
+ Every method returns either the typed result or `{ error: string }`. The `"error"` key is the reliable way to distinguish them:
17
32
 
18
- const envelope = meshpkt.decodeEnvelope(
19
- "your-hex-encoded-meshcore-packet"
33
+ ```ts
34
+ const result = meshpkt.decodeEnvelope(hex);
35
+ if ("error" in result) {
36
+ console.error("decode failed:", result.error);
37
+ } else {
38
+ console.log(result.type, result.hopCount);
39
+ }
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Examples
45
+
46
+ ### Decode a packet from the air
47
+
48
+ ```ts
49
+ const env = meshpkt.decodeEnvelope(
50
+ "15453287568f06bf2d5ad94765d9aaa4aef45a465a5a84142b5abb55eafe11980bc7b891"
20
51
  );
21
52
 
22
- console.log(envelope);
53
+ if ("error" in env) throw new Error(env.error);
54
+
55
+ console.log(env.route); // "FLOOD"
56
+ console.log(env.type); // "ADVERT"
57
+ console.log(env.hopCount); // 5
58
+ console.log(env.hops); // ["3287", "568f", "06bf", "2d5a", "d947"]
59
+ console.log(env.payloadHex); // raw payload, pass to a decode* call below
60
+ ```
61
+
62
+ ---
63
+
64
+ ### Channel (GRP_TXT) messages
65
+
66
+ ```ts
67
+ // Encode — by channel name
68
+ const pkt = meshpkt.encodeGroupText("#general", "Alice", "Hello mesh!");
69
+ if ("error" in pkt) throw new Error(pkt.error);
70
+ console.log(pkt.hex); // ready to send over the radio link
71
+
72
+ // Encode — by pre-shared secret (16 bytes hex)
73
+ const secret = meshpkt.deriveChannelSecret("#general");
74
+ if ("error" in secret) throw new Error(secret.error);
75
+ const pkt2 = meshpkt.encodeGroupTextSecret(secret.hex, "Alice", "Hello mesh!");
76
+
77
+ // Decode — extract payload first, then decrypt
78
+ const env = meshpkt.decodeEnvelope(rawHex);
79
+ if ("error" in env || env.type !== "GRP_TXT") return;
80
+
81
+ const msg = meshpkt.decodeGroupText(env.payloadHex, "#general");
82
+ if ("error" in msg) throw new Error(msg.error);
83
+
84
+ console.log(msg.sender); // "Alice"
85
+ console.log(msg.text); // "Hello mesh!"
86
+ console.log(new Date(msg.timestamp * 1000));
87
+ ```
88
+
89
+ ---
90
+
91
+ ### Direct (TXT_MSG) messages
92
+
93
+ ```ts
94
+ // Generate a keypair (or load an existing one)
95
+ const kp = meshpkt.generateKeypair();
96
+ if ("error" in kp) throw new Error(kp.error);
97
+ // kp.privateKey / kp.publicKey — 64-char hex strings
98
+
99
+ // Encode
100
+ const pkt = meshpkt.encodeDirectText(myPrivKey, peerPubKey, "Hey, direct!");
101
+ if ("error" in pkt) throw new Error(pkt.error);
102
+
103
+ // Decode
104
+ const env = meshpkt.decodeEnvelope(rawHex);
105
+ if ("error" in env || env.type !== "TXT_MSG") return;
106
+
107
+ const msg = meshpkt.decodeDirectText(env.payloadHex, myPrivKey, peerPubKey);
108
+ if ("error" in msg) throw new Error(msg.error);
109
+
110
+ console.log(msg.text); // "Hey, direct!"
111
+ console.log(msg.destHash); // first-byte hash of recipient's public key
112
+ ```
113
+
114
+ ---
115
+
116
+ ### Node advertisements (ADVERT)
117
+
118
+ The signature is verified automatically. `decodeAdvert` returns `{ error }` if
119
+ the packet carries a non-zero signature that does not verify — tampered data is
120
+ rejected before you ever see the fields.
121
+
122
+ ```ts
123
+ const env = meshpkt.decodeEnvelope(rawHex);
124
+ if ("error" in env || env.type !== "ADVERT") return;
125
+
126
+ const adv = meshpkt.decodeAdvert(env.payloadHex);
127
+ if ("error" in adv) throw new Error(adv.error); // also catches bad signatures
128
+
129
+ console.log(adv.name); // "CZ.NIC Repeater"
130
+ console.log(adv.publicKey); // 64-char hex — treat this as the stable identity
131
+ console.log(adv.nodeType); // 2 = repeater
132
+ console.log(adv.sigVerified); // true = Ed25519 signature verified
133
+ // false = all-zero signature (unsigned packet)
134
+ if (adv.hasGPS) {
135
+ console.log(adv.lat, adv.lon);
136
+ }
137
+ ```
138
+
139
+ > **Note:** `sigVerified: true` proves the holder of `publicKey` signed exactly
140
+ > this name and these coordinates. It does not prove the name is unique — use
141
+ > `publicKey` as the stable identity, and treat the name as a signed label.
142
+
143
+ ---
144
+
145
+ ### Key utilities
146
+
147
+ ```ts
148
+ // Generate a fresh X25519 keypair
149
+ const kp = meshpkt.generateKeypair();
150
+ if ("error" in kp) throw new Error(kp.error);
151
+ console.log(kp.publicKey); // 64-char hex
152
+ console.log(kp.privateKey); // 64-char hex
153
+
154
+ // Derive a channel PSK from its name
155
+ const secret = meshpkt.deriveChannelSecret("#ops");
156
+ if ("error" in secret) throw new Error(secret.error);
157
+ console.log(secret.hex); // 32-char hex (16 bytes)
158
+
159
+ // Compute X25519 shared secret for direct-message crypto
160
+ const shared = meshpkt.sharedSecret(myPrivKey, peerPubKey);
161
+ if ("error" in shared) throw new Error(shared.error);
162
+ console.log(shared.hex); // 64-char hex (32 bytes; use first 16 as AES key)
163
+ ```
164
+
165
+ ---
166
+
167
+ ### Loader options
168
+
169
+ By default `load()` resolves the `.wasm` and `wasm_exec.js` files relative to the package using `import.meta.url`. Override either URL when your bundler places assets elsewhere:
170
+
171
+ ```ts
172
+ const meshpkt = await load({
173
+ wasmURL: new URL("/assets/meshpkt.wasm", import.meta.url),
174
+ wasmExecURL: new URL("/assets/wasm_exec.js", import.meta.url),
175
+ });
176
+ ```
177
+
178
+ ---
179
+
180
+ ## TypeScript types
181
+
182
+ All result interfaces are re-exported from the package root:
183
+
184
+ ```ts
185
+ import type {
186
+ Envelope,
187
+ GroupTextPayload,
188
+ DirectTextPayload,
189
+ AdvertPayload,
190
+ GrpDataPayload,
191
+ AckPayload,
192
+ ReqPayload,
193
+ ResponsePayload,
194
+ PathPayload,
195
+ AnonReqPayload,
196
+ ControlPayload,
197
+ KeypairResult,
198
+ ErrResult,
199
+ } from "@meshcore-cz/meshpkt";
23
200
  ```
24
201
 
25
- The package includes the TinyGo WebAssembly module and generated TypeScript types.
202
+ The `RouteTypes` and `PayloadTypes` constant arrays (code + label) are also exported for building UI dropdowns or switch statements.
package/dist/meshpkt.wasm CHANGED
Binary file
@@ -41,6 +41,7 @@ export interface AdvertPayload {
41
41
  hasGPS: boolean;
42
42
  lat?: number;
43
43
  lon?: number;
44
+ sigVerified: boolean;
44
45
  }
45
46
  export interface AckPayload {
46
47
  crc: number;
@@ -87,6 +88,23 @@ export interface ControlPayload {
87
88
  discoverSNR?: number;
88
89
  discoverPubKey?: string;
89
90
  }
91
+ export interface TracePayload {
92
+ tag: number;
93
+ authCode: number;
94
+ flags: number;
95
+ hashWidth: number;
96
+ routeHashes: string[];
97
+ snrs: string[];
98
+ hopCount: number;
99
+ }
100
+ export interface MultipartPayload {
101
+ remaining: number;
102
+ innerType: string;
103
+ innerTypeCode: number;
104
+ innerPayloadHex: string;
105
+ ackCrc?: number;
106
+ ackCrcHex?: string;
107
+ }
90
108
  export interface KeypairResult {
91
109
  publicKey: string;
92
110
  privateKey: string;
@@ -97,11 +115,13 @@ export interface MeshcoreWasm {
97
115
  encodeGrpData(channelName: string, dataType: number, data: string): HexResult | ErrResult;
98
116
  encodeGrpDataSecret(secret: string, dataType: number, data: string): HexResult | ErrResult;
99
117
  encodeDirectText(privKey: string, peerPubKey: string, text: string): HexResult | ErrResult;
100
- encodeAdvert(pubKey: string, signature: string, name: string, hasGPS: number, lat: unknown, lon: unknown): HexResult | ErrResult;
118
+ encodeAdvert(pubKey: string, signature: string, name: string, hasGPS: number, lat: number, lon: number): HexResult | ErrResult;
101
119
  encodeAck(crc: number): HexResult | ErrResult;
102
120
  encodeReq(privKey: string, peerPubKey: string, reqType: number, data: string): HexResult | ErrResult;
103
121
  encodeAnonReq(destPubKey: string, myPrivKey: string, data: string): HexResult | ErrResult;
104
122
  encodeDiscoverReq(typeFilter: number, tag: number, since: number, prefixOnly: number): HexResult | ErrResult;
123
+ encodeTrace(tag: number, authCode: number, flags: number, routeHashes: string): HexResult | ErrResult;
124
+ encodeMultipartAck(remaining: number, crc: number): HexResult | ErrResult;
105
125
  encodeRaw(route: number, payloadType: number, version: number, pathHashSize: number, payload: string): HexResult | ErrResult;
106
126
  decodeEnvelope(packet: string): Envelope | ErrResult;
107
127
  decodeGroupText(payload: string, channelName: string): GroupTextPayload | ErrResult;
@@ -116,11 +136,13 @@ export interface MeshcoreWasm {
116
136
  decodePath(payload: string, privKey: string, peerPubKey: string): PathPayload | ErrResult;
117
137
  decodeAnonReq(payload: string, myPrivKey: string): AnonReqPayload | ErrResult;
118
138
  decodeControl(payload: string): ControlPayload | ErrResult;
139
+ decodeTrace(packet: string): TracePayload | ErrResult;
140
+ decodeMultipart(payload: string): MultipartPayload | ErrResult;
119
141
  generateKeypair(): KeypairResult | ErrResult;
120
142
  deriveChannelSecret(channelName: string): HexResult | ErrResult;
121
143
  sharedSecret(privKey: string, peerPubKey: string): HexResult | ErrResult;
122
144
  }
123
- export declare const meshcoreOpNames: readonly ["encodeGroupText", "encodeGroupTextSecret", "encodeGrpData", "encodeGrpDataSecret", "encodeDirectText", "encodeAdvert", "encodeAck", "encodeReq", "encodeAnonReq", "encodeDiscoverReq", "encodeRaw", "decodeEnvelope", "decodeGroupText", "decodeGroupTextSecret", "decodeDirectText", "decodeAdvert", "decodeAck", "decodeGrpData", "decodeGrpDataSecret", "decodeReq", "decodeResponse", "decodePath", "decodeAnonReq", "decodeControl", "generateKeypair", "deriveChannelSecret", "sharedSecret"];
145
+ export declare const meshcoreOpNames: readonly ["encodeGroupText", "encodeGroupTextSecret", "encodeGrpData", "encodeGrpDataSecret", "encodeDirectText", "encodeAdvert", "encodeAck", "encodeReq", "encodeAnonReq", "encodeDiscoverReq", "encodeTrace", "encodeMultipartAck", "encodeRaw", "decodeEnvelope", "decodeGroupText", "decodeGroupTextSecret", "decodeDirectText", "decodeAdvert", "decodeAck", "decodeGrpData", "decodeGrpDataSecret", "decodeReq", "decodeResponse", "decodePath", "decodeAnonReq", "decodeControl", "decodeTrace", "decodeMultipart", "generateKeypair", "deriveChannelSecret", "sharedSecret"];
124
146
  export declare const RouteTypes: readonly [{
125
147
  readonly code: 0;
126
148
  readonly label: "TRANSPORT_FLOOD";
@@ -174,3 +196,42 @@ export declare const PayloadTypes: readonly [{
174
196
  readonly code: 15;
175
197
  readonly label: "RAW_CUSTOM";
176
198
  }];
199
+ export interface ParamMeta {
200
+ name: string;
201
+ kind: string;
202
+ label: string;
203
+ placeholder: string;
204
+ optional: boolean;
205
+ choices: {
206
+ value: number;
207
+ label: string;
208
+ }[];
209
+ showWhen: string;
210
+ showValue: number;
211
+ group: string;
212
+ action: string;
213
+ widget: string;
214
+ autoFill: string;
215
+ secret: boolean;
216
+ }
217
+ export interface ResultMeta {
218
+ name: string;
219
+ kind: string;
220
+ optional: boolean;
221
+ label: string;
222
+ }
223
+ export interface OpMeta {
224
+ name: string;
225
+ category: string;
226
+ label: string;
227
+ tabGroup: string;
228
+ tabGroupLabel: string;
229
+ tabGroupSub: string;
230
+ tabGroupDoc: string;
231
+ tabLabel: string;
232
+ packetType: string;
233
+ params: ParamMeta[];
234
+ result: ResultMeta[];
235
+ resultTypeName: string;
236
+ }
237
+ export declare const OpMetas: OpMeta[];