mirror of
https://github.com/meshtastic/web.git
synced 2026-08-04 08:53:03 -04:00
fix(sdk/nodes): seed self-node stub from MyNodeInfo
If a session's config bundle doesn't include a NodeInfo for the device
itself (fresh flash, cleared nodeDB, some firmware builds), the web
sidebar's `useMyNodeAsProto()` selector — which does
`nodes.list.find(n => n.num === myNodeNum)` — never resolves and the
"loading" spinner sticks indefinitely after the overlay closes.
NodesClient now subscribes to onMyNodeInfo. If no entry exists for the
incoming myNodeNum, it seeds a stub Node with just `num` set; subsequent
NodeInfo / UserPacket / PositionPacket events patch real data on top.
Guards against myNodeNum === 0 and against overwriting an existing entry.
DeviceInfoPanel already wraps user-dependent rendering in `{user && …}`,
so no app-layer change is needed — the panel quietly omits the avatar
block until a real UserPacket arrives.
Tests cover: stub seeded when only MyNodeInfo arrives, real NodeInfo wins
over a later MyNodeInfo, real NodeInfo patches over an existing stub, and
myNodeNum=0 is ignored.
This commit is contained in:
@@ -25,4 +25,57 @@ describe("NodesClient", () => {
|
||||
const client = new MeshClient({ transport });
|
||||
expect(client.nodes.byNum(999)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("seeds a self-node stub from onMyNodeInfo when no NodeInfo has arrived", () => {
|
||||
const { transport } = createFakeTransport();
|
||||
const client = new MeshClient({ transport });
|
||||
|
||||
client.events.onMyNodeInfo.dispatch(create(Protobuf.Mesh.MyNodeInfoSchema, { myNodeNum: 42 }));
|
||||
|
||||
const seeded = client.nodes.byNum(42);
|
||||
expect(seeded).toBeDefined();
|
||||
expect(seeded?.num).toBe(42);
|
||||
expect(seeded?.user).toBeUndefined();
|
||||
expect(seeded?.isFavorite).toBe(false);
|
||||
expect(seeded?.isIgnored).toBe(false);
|
||||
});
|
||||
|
||||
it("does not overwrite a richer NodeInfo entry with a later MyNodeInfo seed", () => {
|
||||
const { transport } = createFakeTransport();
|
||||
const client = new MeshClient({ transport });
|
||||
|
||||
client.events.onNodeInfoPacket.dispatch(
|
||||
create(Protobuf.Mesh.NodeInfoSchema, {
|
||||
num: 42,
|
||||
user: create(Protobuf.Mesh.UserSchema, { id: "!000a", longName: "Alice" }),
|
||||
}),
|
||||
);
|
||||
client.events.onMyNodeInfo.dispatch(create(Protobuf.Mesh.MyNodeInfoSchema, { myNodeNum: 42 }));
|
||||
|
||||
const node = client.nodes.byNum(42);
|
||||
expect(node?.user?.longName).toBe("Alice");
|
||||
});
|
||||
|
||||
it("lets a real NodeInfo patch over a seeded self-node stub", () => {
|
||||
const { transport } = createFakeTransport();
|
||||
const client = new MeshClient({ transport });
|
||||
|
||||
client.events.onMyNodeInfo.dispatch(create(Protobuf.Mesh.MyNodeInfoSchema, { myNodeNum: 42 }));
|
||||
client.events.onNodeInfoPacket.dispatch(
|
||||
create(Protobuf.Mesh.NodeInfoSchema, {
|
||||
num: 42,
|
||||
user: create(Protobuf.Mesh.UserSchema, { id: "!000a", longName: "Alice" }),
|
||||
}),
|
||||
);
|
||||
|
||||
const node = client.nodes.byNum(42);
|
||||
expect(node?.user?.longName).toBe("Alice");
|
||||
});
|
||||
|
||||
it("ignores onMyNodeInfo with myNodeNum=0", () => {
|
||||
const { transport } = createFakeTransport();
|
||||
const client = new MeshClient({ transport });
|
||||
client.events.onMyNodeInfo.dispatch(create(Protobuf.Mesh.MyNodeInfoSchema, { myNodeNum: 0 }));
|
||||
expect(client.nodes.list.value).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,6 +44,25 @@ export class NodesClient {
|
||||
|
||||
client.events.onNodeInfoPacket.subscribe((info) => this.handleIncoming(info));
|
||||
|
||||
// Seed a self-node stub from MyNodeInfo. Firmware always emits MyNodeInfo
|
||||
// during the wantConfigId handshake, but not every session includes a
|
||||
// NodeInfo for the device itself in the config bundle (fresh flash,
|
||||
// cleared nodeDB, etc.). Without this seed, consumers that look up
|
||||
// `nodes.list.find(n => n.num === myNodeNum)` — notably the web sidebar —
|
||||
// sit empty forever. The real NodeInfo / UserPacket / PositionPacket
|
||||
// handlers above patch the stub when they arrive.
|
||||
client.events.onMyNodeInfo.subscribe((info) => {
|
||||
if (info.myNodeNum === 0) return;
|
||||
if (this.store.has(info.myNodeNum)) return;
|
||||
const seeded: Node = {
|
||||
num: info.myNodeNum,
|
||||
isFavorite: false,
|
||||
isIgnored: false,
|
||||
};
|
||||
this.store.set(info.myNodeNum, seeded);
|
||||
void this.repository.upsert(seeded).catch(() => {});
|
||||
});
|
||||
|
||||
client.events.onUserPacket.subscribe((packet) => {
|
||||
this.patch(packet.from, { user: packet.data });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user