@cryptiklemur/lattice 1.28.2 → 1.28.3
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback, useRef } from "react";
|
|
1
|
+
import { useState, useEffect, useCallback, useRef, memo } from "react";
|
|
2
2
|
import { X, Copy, Check, Loader2 } from "lucide-react";
|
|
3
3
|
import { useWebSocket } from "../../hooks/useWebSocket";
|
|
4
4
|
import { useMesh } from "../../hooks/useMesh";
|
|
@@ -13,7 +13,7 @@ interface PairingDialogProps {
|
|
|
13
13
|
onClose: () => void;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function PairingDialog(props: PairingDialogProps) {
|
|
16
|
+
export var PairingDialog = memo(function PairingDialog(props: PairingDialogProps) {
|
|
17
17
|
var ws = useWebSocket();
|
|
18
18
|
var mesh = useMesh();
|
|
19
19
|
var [tab, setTab] = useState<Tab>("generate");
|
|
@@ -198,17 +198,6 @@ export function PairingDialog(props: PairingDialogProps) {
|
|
|
198
198
|
</button>
|
|
199
199
|
</div>
|
|
200
200
|
|
|
201
|
-
{mesh.inviteQr && (
|
|
202
|
-
<div className="flex justify-center mb-4">
|
|
203
|
-
<img
|
|
204
|
-
src={mesh.inviteQr}
|
|
205
|
-
alt="QR code for invite"
|
|
206
|
-
className="w-40 h-40 rounded border border-base-300"
|
|
207
|
-
style={{ imageRendering: "pixelated" }}
|
|
208
|
-
/>
|
|
209
|
-
</div>
|
|
210
|
-
)}
|
|
211
|
-
|
|
212
201
|
<button
|
|
213
202
|
onClick={handleGenerateInvite}
|
|
214
203
|
className="text-[12px] text-base-content/40 underline cursor-pointer"
|
|
@@ -287,4 +276,4 @@ export function PairingDialog(props: PairingDialogProps) {
|
|
|
287
276
|
</div>
|
|
288
277
|
</div>
|
|
289
278
|
);
|
|
290
|
-
}
|
|
279
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useState, useCallback, memo } from "react";
|
|
2
2
|
import { Plus, CircleDot, Circle } from "lucide-react";
|
|
3
3
|
import { useWebSocket } from "../../hooks/useWebSocket";
|
|
4
4
|
import { useMesh } from "../../hooks/useMesh";
|
|
@@ -83,6 +83,8 @@ export function MeshStatus() {
|
|
|
83
83
|
var { nodes } = useMesh();
|
|
84
84
|
var [pairingOpen, setPairingOpen] = useState(false);
|
|
85
85
|
|
|
86
|
+
var handleClosePairing = useCallback(function () { setPairingOpen(false); }, []);
|
|
87
|
+
|
|
86
88
|
function handleUnpair(nodeId: string) {
|
|
87
89
|
ws.send({ type: "mesh:unpair", nodeId });
|
|
88
90
|
}
|
|
@@ -138,7 +140,7 @@ export function MeshStatus() {
|
|
|
138
140
|
|
|
139
141
|
<PairingDialog
|
|
140
142
|
isOpen={pairingOpen}
|
|
141
|
-
onClose={
|
|
143
|
+
onClose={handleClosePairing}
|
|
142
144
|
/>
|
|
143
145
|
</div>
|
|
144
146
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cryptiklemur/lattice",
|
|
3
|
-
"version": "1.28.
|
|
3
|
+
"version": "1.28.3",
|
|
4
4
|
"description": "Multi-machine agentic dashboard for Claude Code. Monitor sessions, manage MCP servers and skills, orchestrate across mesh-networked nodes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Aaron Scherer <me@aaronscherer.me>",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import QRCode from "qrcode";
|
|
3
2
|
|
|
4
3
|
var BASE62_CHARS = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz";
|
|
4
|
+
var CODE_LENGTH = 16;
|
|
5
5
|
|
|
6
6
|
var PAIRING_TOKEN_TTL = 300000;
|
|
7
7
|
var CLEANUP_INTERVAL = 60000;
|
|
@@ -16,7 +16,10 @@ function base62Encode(buf: Buffer): string {
|
|
|
16
16
|
result = BASE62_CHARS[Number(n % base)] + result;
|
|
17
17
|
n = n / base;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
while (result.length < CODE_LENGTH) {
|
|
20
|
+
result = BASE62_CHARS[0] + result;
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
function base62Decode(s: string): Buffer {
|
|
@@ -36,11 +39,29 @@ function base62Decode(s: string): Buffer {
|
|
|
36
39
|
return Buffer.from(hex, "hex");
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
function packPayload(address: string, port: number, token: Buffer): Buffer {
|
|
43
|
+
var parts = address.split(".");
|
|
44
|
+
var buf = Buffer.alloc(4 + 2 + token.length);
|
|
45
|
+
for (var i = 0; i < 4; i++) {
|
|
46
|
+
buf[i] = parseInt(parts[i] || "0", 10);
|
|
47
|
+
}
|
|
48
|
+
buf.writeUInt16BE(port, 4);
|
|
49
|
+
token.copy(buf, 6);
|
|
50
|
+
return buf;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function unpackPayload(buf: Buffer): { address: string; port: number; token: string } | null {
|
|
54
|
+
if (buf.length < 8) return null;
|
|
55
|
+
var address = buf[0] + "." + buf[1] + "." + buf[2] + "." + buf[3];
|
|
56
|
+
var port = buf.readUInt16BE(4);
|
|
57
|
+
var token = buf.subarray(6).toString("hex");
|
|
58
|
+
return { address, port, token };
|
|
59
|
+
}
|
|
60
|
+
|
|
39
61
|
function formatCode(raw: string): string {
|
|
40
|
-
var upper = raw.toUpperCase();
|
|
41
62
|
var chunks: string[] = [];
|
|
42
|
-
for (var i = 0; i <
|
|
43
|
-
chunks.push(
|
|
63
|
+
for (var i = 0; i < raw.length; i += 4) {
|
|
64
|
+
chunks.push(raw.slice(i, i + 4));
|
|
44
65
|
}
|
|
45
66
|
return "LTCE-" + chunks.join("-");
|
|
46
67
|
}
|
|
@@ -53,17 +74,15 @@ export async function generateInviteCode(
|
|
|
53
74
|
address: string,
|
|
54
75
|
port: number
|
|
55
76
|
): Promise<{ code: string; token: string; qrDataUrl: string }> {
|
|
56
|
-
var
|
|
57
|
-
var
|
|
77
|
+
var tokenBuf = randomBytes(4);
|
|
78
|
+
var token = tokenBuf.toString("hex");
|
|
79
|
+
var payload = packPayload(address, port, tokenBuf);
|
|
58
80
|
var encoded = base62Encode(payload);
|
|
59
81
|
var code = formatCode(encoded);
|
|
60
82
|
|
|
61
83
|
pendingTokens.set(token, Date.now());
|
|
62
84
|
|
|
63
|
-
|
|
64
|
-
var qrDataUrl = "data:image/svg+xml;base64," + Buffer.from(qrSvg).toString("base64");
|
|
65
|
-
|
|
66
|
-
return { code, token, qrDataUrl };
|
|
85
|
+
return { code, token, qrDataUrl: "" };
|
|
67
86
|
}
|
|
68
87
|
|
|
69
88
|
export function parseInviteCode(
|
|
@@ -71,19 +90,8 @@ export function parseInviteCode(
|
|
|
71
90
|
): { address: string; port: number; token: string } | null {
|
|
72
91
|
try {
|
|
73
92
|
var stripped = stripCode(code);
|
|
74
|
-
var decoded = base62Decode(stripped)
|
|
75
|
-
|
|
76
|
-
if (parts.length < 3) {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
var token = parts[parts.length - 1];
|
|
80
|
-
var portStr = parts[parts.length - 2];
|
|
81
|
-
var address = parts.slice(0, parts.length - 2).join(":");
|
|
82
|
-
var port = parseInt(portStr, 10);
|
|
83
|
-
if (isNaN(port)) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
return { address, port, token };
|
|
93
|
+
var decoded = base62Decode(stripped);
|
|
94
|
+
return unpackPayload(decoded);
|
|
87
95
|
} catch {
|
|
88
96
|
return null;
|
|
89
97
|
}
|