@soapbox.pub/nostr-lora 0.1.0
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/LICENSE +21 -0
- package/README.md +173 -0
- package/dist/assembler.d.ts +74 -0
- package/dist/connection-manager.d.ts +30 -0
- package/dist/constants.d.ts +31 -0
- package/dist/event-codec.d.ts +15 -0
- package/dist/gm-protocol.d.ts +30 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/lora-transport.d.ts +109 -0
- package/dist/protocol.d.ts +78 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +57 -0
- package/dist/react.js.map +1 -0
- package/dist/send-queue.d.ts +40 -0
- package/dist/serial-connection.d.ts +41 -0
- package/dist/serial-connection.js +955 -0
- package/dist/serial-connection.js.map +1 -0
- package/dist/use-lora-transport.d.ts +32 -0
- package/dist/utils.d.ts +24 -0
- package/package.json +48 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","sources":["../lib/use-lora-transport.ts"],"sourcesContent":["import { useState, useCallback, useRef, useEffect } from 'react';\nimport { SerialConnectionManager } from './serial-connection.js';\nimport { LoRaTransport } from './lora-transport.js';\nimport type { TransportOptions, PacketReceiveInfo } from './lora-transport.js';\nimport type { NostrEvent } from './event-codec.js';\nimport type { QueueSnapshot } from './send-queue.js';\n\nexport interface UseNostrLoraOptions extends TransportOptions {\n /** Called when a Nostr event is received over LoRa. */\n onEvent?: (event: NostrEvent) => void;\n}\n\nexport interface UseNostrLoraReturn {\n // ── State ─────────────────────────────────────────────────────────────────\n isConnected: boolean;\n connecting: boolean;\n error: Error | null;\n portLabel: string | null;\n deviceName: string | null;\n sendQueue: QueueSnapshot[];\n lastPacket: PacketReceiveInfo | null;\n\n // ── Actions ───────────────────────────────────────────────────────────────\n /** Open the serial port (triggers browser port picker) and begin transport. */\n connect(): Promise<void>;\n /** Close the connection. */\n disconnect(): Promise<void>;\n /** Send a fully-signed Nostr event over LoRa. */\n sendEvent(event: NostrEvent): Promise<void>;\n /** Manually broadcast a GM announcement (advanced). */\n sendGm(nodeId: Uint8Array, eventCount?: number, recentSince?: number): Promise<void>;\n /** Live-update the inter-packet timing without reconnecting. */\n setTimingOptions(opts: { delay?: number; jitter?: number }): void;\n\n // ── Refs ──────────────────────────────────────────────────────────────────\n /** Direct access to the underlying `LoRaTransport` for attaching custom listeners. */\n transport: LoRaTransport | null;\n}\n\nexport function useNostrLora(options: UseNostrLoraOptions = {}): UseNostrLoraReturn {\n const [isConnected, setIsConnected] = useState(false);\n const [connecting, setConnecting] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [portLabel, setPortLabel] = useState<string | null>(null);\n const [deviceName, setDeviceName] = useState<string | null>(null);\n const [sendQueue, setSendQueue] = useState<QueueSnapshot[]>([]);\n const [lastPacket, setLastPacket] = useState<PacketReceiveInfo | null>(null);\n\n const connectionRef = useRef<SerialConnectionManager | null>(null);\n const transportRef = useRef<LoRaTransport | null>(null);\n\n // Stable ref to options so callbacks don't go stale\n const optionsRef = useRef(options);\n useEffect(() => { optionsRef.current = options; }, [options]);\n\n const connect = useCallback(async () => {\n setConnecting(true);\n setError(null);\n try {\n const cm = new SerialConnectionManager(optionsRef.current.logger);\n connectionRef.current = cm;\n\n cm.on('deviceInfo', (info) => setDeviceName(info.name));\n\n const { onEvent, ...transportOptions } = optionsRef.current;\n const transport = new LoRaTransport(cm, transportOptions);\n transportRef.current = transport;\n\n transport.on('connect', (label) => { setIsConnected(true); setPortLabel(label); });\n transport.on('disconnect', () => { setIsConnected(false); setPortLabel(null); });\n transport.on('error', (err) => setError(err));\n transport.on('queue:update', (q) => setSendQueue(q));\n transport.on('packet:receive',(info) => setLastPacket(info));\n if (onEvent) transport.on('event:receive', onEvent);\n\n await transport.begin();\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n setConnecting(false);\n } finally {\n setConnecting(false);\n }\n }, []);\n\n const disconnect = useCallback(async () => {\n await transportRef.current?.end();\n transportRef.current = null;\n connectionRef.current = null;\n }, []);\n\n const sendEvent = useCallback(async (event: NostrEvent) => {\n if (!transportRef.current) throw new Error('Not connected');\n await transportRef.current.sendEvent(event);\n }, []);\n\n const sendGm = useCallback(async (nodeId: Uint8Array, eventCount?: number, recentSince?: number) => {\n if (!transportRef.current) throw new Error('Not connected');\n await transportRef.current.sendGm(nodeId, eventCount, recentSince);\n }, []);\n\n const setTimingOptions = useCallback((opts: { delay?: number; jitter?: number }) => {\n transportRef.current?.setTimingOptions(opts);\n }, []);\n\n return {\n isConnected,\n connecting,\n error,\n portLabel,\n deviceName,\n sendQueue,\n lastPacket,\n connect,\n disconnect,\n sendEvent,\n sendGm,\n setTimingOptions,\n transport: transportRef.current,\n };\n}"],"names":["useNostrLora","options","isConnected","setIsConnected","useState","connecting","setConnecting","error","setError","portLabel","setPortLabel","deviceName","setDeviceName","sendQueue","setSendQueue","lastPacket","setLastPacket","connectionRef","useRef","transportRef","optionsRef","useEffect","connect","useCallback","cm","SerialConnectionManager","info","onEvent","transportOptions","transport","LoRaTransport","label","err","q","e","disconnect","_a","sendEvent","event","sendGm","nodeId","eventCount","recentSince","setTimingOptions","opts"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { EventEmitter, Logger } from './utils.js';
|
|
2
|
+
import { DefaultOptions } from './constants.js';
|
|
3
|
+
export interface QueueItemMeta {
|
|
4
|
+
_onSent?: () => Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export interface QueueItem {
|
|
7
|
+
id: number;
|
|
8
|
+
label: string;
|
|
9
|
+
type: string;
|
|
10
|
+
packets: Uint8Array[];
|
|
11
|
+
meta: QueueItemMeta;
|
|
12
|
+
status: "pending" | "sending";
|
|
13
|
+
}
|
|
14
|
+
export interface QueueSnapshot {
|
|
15
|
+
id: number;
|
|
16
|
+
label: string;
|
|
17
|
+
type: string;
|
|
18
|
+
packetCount: number;
|
|
19
|
+
status: "pending" | "sending";
|
|
20
|
+
}
|
|
21
|
+
export type SendQueueEvents = {
|
|
22
|
+
"packet:send": [packet: Uint8Array, item: QueueItem];
|
|
23
|
+
"queue:update": [snapshot: QueueSnapshot[]];
|
|
24
|
+
"error": [err: Error];
|
|
25
|
+
};
|
|
26
|
+
export declare class SendQueue extends EventEmitter<SendQueueEvents> {
|
|
27
|
+
private sendFn;
|
|
28
|
+
private items;
|
|
29
|
+
private running;
|
|
30
|
+
private seq;
|
|
31
|
+
private log;
|
|
32
|
+
INTER_PACKET_DELAY: number;
|
|
33
|
+
INTER_PACKET_JITTER: number;
|
|
34
|
+
constructor(sendFn: (packet: Uint8Array) => Promise<void>, options?: Partial<Pick<DefaultOptions, "INTER_PACKET_DELAY" | "INTER_PACKET_JITTER">>, logger?: Logger | null);
|
|
35
|
+
enqueue(packets: Uint8Array[], label: string, type: string, meta?: QueueItemMeta): number;
|
|
36
|
+
get snapshot(): QueueSnapshot[];
|
|
37
|
+
private notifyUpdate;
|
|
38
|
+
private drain;
|
|
39
|
+
private run;
|
|
40
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { EventEmitter, Logger } from './utils.js';
|
|
2
|
+
import { ConnectionManager, ConnectionManagerEvents } from './connection-manager.js';
|
|
3
|
+
export interface DeviceInfo {
|
|
4
|
+
name: string;
|
|
5
|
+
firmwareVersion: string;
|
|
6
|
+
}
|
|
7
|
+
export interface RadioConfig {
|
|
8
|
+
frequency: number;
|
|
9
|
+
bandwidth: number;
|
|
10
|
+
spreadingFactor: number;
|
|
11
|
+
codingRate: number;
|
|
12
|
+
power?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Web Serial connection to a MeshCore LoRa device.
|
|
16
|
+
* Implements `ConnectionManager` so it can be passed directly to `LoRaTransport`.
|
|
17
|
+
*
|
|
18
|
+
* Key constraint: WebSerialConnection.write() has no internal queue — it calls
|
|
19
|
+
* getWriter()/releaseLock() per write. Two overlapping writes crash with
|
|
20
|
+
* "WritableStream is already locked". We must never issue a write while another
|
|
21
|
+
* is in flight. `LoRaTransport`'s `SendQueue` ensures this.
|
|
22
|
+
*/
|
|
23
|
+
export declare class SerialConnectionManager extends EventEmitter<ConnectionManagerEvents & {
|
|
24
|
+
"deviceInfo": [info: DeviceInfo];
|
|
25
|
+
}> implements ConnectionManager {
|
|
26
|
+
private connection;
|
|
27
|
+
private label;
|
|
28
|
+
private log;
|
|
29
|
+
constructor(logger?: Logger | null);
|
|
30
|
+
open(): Promise<void>;
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
sendRawData(data: Uint8Array): Promise<void>;
|
|
33
|
+
/** Configure the LoRa radio parameters. Must be called after `open()`. */
|
|
34
|
+
setRadioConfig(config: RadioConfig): Promise<void>;
|
|
35
|
+
isConnected(): boolean;
|
|
36
|
+
isWebSerialSupported(): boolean;
|
|
37
|
+
/** The USB VID:PID or "Serial device" label for the connected port. */
|
|
38
|
+
get portLabel(): string | null;
|
|
39
|
+
private buildPortLabel;
|
|
40
|
+
private setupHandlers;
|
|
41
|
+
}
|