@magicred-1/react-native-lxmf 0.2.31 → 0.2.32
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.
|
@@ -207,6 +207,7 @@ export default function HomeScreen() {
|
|
|
207
207
|
|
|
208
208
|
const [unpairedRNodes, setUnpairedRNodes] = useState(0);
|
|
209
209
|
const [liveBleCount, setLiveBleCount] = useState(0);
|
|
210
|
+
const [storedMsgs, setStoredMsgs] = useState<any[]>([]);
|
|
210
211
|
|
|
211
212
|
// Identity hydration: read once from secure store on mount. Until hydrated,
|
|
212
213
|
// we pass 'new' so Rust generates a fresh identity (which we'll then persist
|
|
@@ -218,20 +219,13 @@ export default function HomeScreen() {
|
|
|
218
219
|
(async () => {
|
|
219
220
|
try {
|
|
220
221
|
const raw = await SecureStore.getItemAsync(IDENTITY_KEY);
|
|
221
|
-
// TODO(sentinel): remove debug logs before merge — lengths/booleans only, no key material
|
|
222
|
-
console.log('[persist] hydrate', { hasRaw: !!raw, rawLen: raw?.length ?? 0 });
|
|
223
222
|
if (cancelled) return;
|
|
224
223
|
if (raw) {
|
|
225
224
|
const parsed = JSON.parse(raw);
|
|
226
|
-
|
|
227
|
-
console.log('[persist] parsed', { valid, hasIdHex: typeof parsed?.identity_hex === 'string', hasAddrHex: typeof parsed?.address_hex === 'string' });
|
|
228
|
-
if (valid) {
|
|
229
|
-
setStoredIdentity(parsed);
|
|
230
|
-
}
|
|
225
|
+
if (isValidIdentity(parsed)) setStoredIdentity(parsed);
|
|
231
226
|
}
|
|
232
|
-
} catch
|
|
233
|
-
|
|
234
|
-
// Corrupt blob or storage error — fall through; we'll generate fresh.
|
|
227
|
+
} catch {
|
|
228
|
+
// Corrupt blob or storage error — fall through; generate fresh identity.
|
|
235
229
|
} finally {
|
|
236
230
|
if (!cancelled) setIdentityHydrated(true);
|
|
237
231
|
}
|
|
@@ -240,31 +234,23 @@ export default function HomeScreen() {
|
|
|
240
234
|
}, []);
|
|
241
235
|
|
|
242
236
|
const {
|
|
243
|
-
isNativeAvailable, isRunning, status, error, events,
|
|
244
|
-
start, stop, send, getStatus, getIdentityHex,
|
|
245
|
-
|
|
237
|
+
isNativeAvailable, isRunning, status, error, events,
|
|
238
|
+
start, stop, send, broadcast, getStatus, getIdentityHex, fetchMessages,
|
|
239
|
+
bleUnpairedRNodeCount,
|
|
246
240
|
} = useLxmf({
|
|
247
241
|
identityHex: storedIdentity?.identity_hex ?? 'new',
|
|
248
242
|
lxmfAddressHex: storedIdentity?.address_hex ?? 'new',
|
|
249
243
|
logLevel: 3,
|
|
250
244
|
});
|
|
251
245
|
|
|
252
|
-
// Persist identity after node starts
|
|
253
|
-
// or if the running identity differs — defensive against schema migrations).
|
|
246
|
+
// Persist identity after node starts (only when identity changes from stored copy).
|
|
254
247
|
useEffect(() => {
|
|
255
248
|
if (!isRunning) return;
|
|
256
249
|
const idHex = getIdentityHex();
|
|
257
250
|
const addrHex = status?.addressHex;
|
|
258
|
-
// TODO(sentinel): remove debug logs before merge — lengths/booleans only, no key material
|
|
259
|
-
console.log('[persist] save check', {
|
|
260
|
-
idHexLen: idHex?.length ?? 0,
|
|
261
|
-
addrHexLen: addrHex?.length ?? 0,
|
|
262
|
-
alreadyStoredSame: storedIdentity?.identity_hex === idHex && storedIdentity?.address_hex === addrHex,
|
|
263
|
-
});
|
|
264
251
|
if (!idHex || idHex.length !== 128) return;
|
|
265
252
|
if (!addrHex || !/^[0-9a-fA-F]{32}$/.test(addrHex)) return;
|
|
266
253
|
if (storedIdentity?.identity_hex === idHex && storedIdentity?.address_hex === addrHex) return;
|
|
267
|
-
|
|
268
254
|
const blob: StoredIdentity = {
|
|
269
255
|
version: IDENTITY_SCHEMA_VERSION,
|
|
270
256
|
identity_hex: idHex,
|
|
@@ -272,16 +258,15 @@ export default function HomeScreen() {
|
|
|
272
258
|
created_at: new Date().toISOString(),
|
|
273
259
|
};
|
|
274
260
|
SecureStore.setItemAsync(IDENTITY_KEY, JSON.stringify(blob))
|
|
275
|
-
.then(() =>
|
|
276
|
-
|
|
277
|
-
setStoredIdentity(blob);
|
|
278
|
-
})
|
|
279
|
-
.catch((e: any) => {
|
|
280
|
-
console.log('[persist] save FAIL', e?.message ?? 'unknown');
|
|
281
|
-
/* persistence failure is non-fatal for the running session */
|
|
282
|
-
});
|
|
261
|
+
.then(() => setStoredIdentity(blob))
|
|
262
|
+
.catch(() => { /* non-fatal */ });
|
|
283
263
|
}, [isRunning, status?.addressHex, storedIdentity, getIdentityHex]);
|
|
284
264
|
|
|
265
|
+
// Load persisted messages from SQLite whenever node starts.
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
if (isRunning) setStoredMsgs(fetchMessages(50));
|
|
268
|
+
}, [isRunning, fetchMessages]);
|
|
269
|
+
|
|
285
270
|
// ── Derived ───────────────────────────────────────────────────────────────
|
|
286
271
|
|
|
287
272
|
const counts = useMemo(() => {
|
|
@@ -326,21 +311,18 @@ export default function HomeScreen() {
|
|
|
326
311
|
mode: LxmfNodeMode.ReticulumAndBle,
|
|
327
312
|
tcpInterfaces: [{ host, port }],
|
|
328
313
|
displayName: displayName.trim() || 'lxmf-mobile',
|
|
329
|
-
isBeacon,
|
|
330
314
|
});
|
|
331
315
|
if (ok) {
|
|
332
316
|
setTcpActive(true);
|
|
333
|
-
startBLE();
|
|
334
317
|
setBleActive(true);
|
|
335
318
|
}
|
|
336
|
-
}, [tcpHost, tcpPort, displayName,
|
|
319
|
+
}, [tcpHost, tcpPort, displayName, start]);
|
|
337
320
|
|
|
338
321
|
const onStopTcp = useCallback(async () => {
|
|
339
|
-
stopBLE();
|
|
340
322
|
await stop();
|
|
341
323
|
setTcpActive(false);
|
|
342
324
|
setBleActive(false);
|
|
343
|
-
}, [stop
|
|
325
|
+
}, [stop]);
|
|
344
326
|
|
|
345
327
|
const onStartBle = useCallback(async () => {
|
|
346
328
|
setTransportMsg('');
|
|
@@ -353,43 +335,31 @@ export default function HomeScreen() {
|
|
|
353
335
|
]
|
|
354
336
|
: [PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION];
|
|
355
337
|
const results = await PermissionsAndroid.requestMultiple(perms);
|
|
356
|
-
|
|
357
|
-
if (denied) {
|
|
338
|
+
if (Object.values(results).some(r => r !== PermissionsAndroid.RESULTS.GRANTED)) {
|
|
358
339
|
setTransportMsg('BLE permissions denied.');
|
|
359
340
|
return;
|
|
360
341
|
}
|
|
361
342
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (!host) { setTransportMsg('Host required for TCP+BLE mode.'); return; }
|
|
368
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) { setTransportMsg('Port 1–65535.'); return; }
|
|
369
|
-
const ok = await start({
|
|
370
|
-
mode: LxmfNodeMode.ReticulumAndBle,
|
|
371
|
-
tcpInterfaces: [{ host, port }],
|
|
372
|
-
displayName: displayName.trim() || 'lxmf-mobile',
|
|
373
|
-
isBeacon,
|
|
374
|
-
});
|
|
375
|
-
if (!ok) {
|
|
376
|
-
setTransportMsg('Failed to start node.');
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
setTcpActive(true);
|
|
380
|
-
}
|
|
381
|
-
startBLE();
|
|
343
|
+
const ok = await start({
|
|
344
|
+
mode: LxmfNodeMode.BleOnly,
|
|
345
|
+
displayName: displayName.trim() || 'lxmf-mobile',
|
|
346
|
+
});
|
|
347
|
+
if (!ok) { setTransportMsg('Failed to start BLE node.'); return; }
|
|
382
348
|
setBleActive(true);
|
|
383
|
-
}, [
|
|
349
|
+
}, [start, displayName]);
|
|
384
350
|
|
|
385
351
|
const onStopBle = useCallback(async () => {
|
|
386
|
-
|
|
352
|
+
await stop();
|
|
387
353
|
setBleActive(false);
|
|
388
354
|
setUnpairedRNodes(0);
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
355
|
+
}, [stop]);
|
|
356
|
+
|
|
357
|
+
const onBroadcast = useCallback(async () => {
|
|
358
|
+
if (!knownPeerHashes.length) { setSendResult('No known peers.'); return; }
|
|
359
|
+
const dests = knownPeerHashes.map(p => p.hash);
|
|
360
|
+
const r = await broadcast(dests, utf8ToBase64(msgText));
|
|
361
|
+
setSendResult(r >= 0 ? `Broadcast #${r} → ${dests.length} peers` : 'Broadcast failed.');
|
|
362
|
+
}, [knownPeerHashes, msgText, broadcast]);
|
|
393
363
|
|
|
394
364
|
// Poll for unpaired RNodes while BLE is active
|
|
395
365
|
useEffect(() => {
|
|
@@ -617,13 +587,51 @@ export default function HomeScreen() {
|
|
|
617
587
|
/>
|
|
618
588
|
<View style={S.btnRow}>
|
|
619
589
|
<Btn label="Send" onPress={onSend} disabled={!isRunning} />
|
|
590
|
+
<Btn label="Broadcast" onPress={onBroadcast} disabled={!isRunning || !knownPeerHashes.length} />
|
|
620
591
|
</View>
|
|
621
592
|
{sendResult ? <Text style={S.feedback}>{sendResult}</Text> : null}
|
|
622
593
|
</Accordion>
|
|
623
594
|
|
|
624
595
|
{/* ── Messages ─────────────────────────────────────────────────────── */}
|
|
625
|
-
<Accordion title="Messages" badge={counts.messages} defaultOpen>
|
|
626
|
-
{
|
|
596
|
+
<Accordion title="Messages" badge={counts.messages + storedMsgs.length} defaultOpen>
|
|
597
|
+
{/* Persisted (SQLite) */}
|
|
598
|
+
{storedMsgs.length > 0 && (
|
|
599
|
+
<>
|
|
600
|
+
<Text style={S.sectionLabel}>Persisted ({storedMsgs.length})</Text>
|
|
601
|
+
{storedMsgs.map((m: any, i: number) => {
|
|
602
|
+
const bodyText = base64ToUtf8(m.body ?? '');
|
|
603
|
+
const titleText = m.title ? base64ToUtf8(m.title) : '';
|
|
604
|
+
const sender = m.source ?? m.source_hash ?? '';
|
|
605
|
+
const t = m.timestamp ? new Date(m.timestamp > 10_000_000_000 ? m.timestamp : m.timestamp * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '';
|
|
606
|
+
return (
|
|
607
|
+
<View key={`stored-${i}-${sender}`} style={[S.itemCard, S.storedCard]}>
|
|
608
|
+
<View style={S.announceHeader}>
|
|
609
|
+
<View style={S.announceInfo}>
|
|
610
|
+
<Text selectable style={S.itemTitle}>From: {shortHex(sender)}</Text>
|
|
611
|
+
{titleText ? <Text selectable style={S.msgTitle}>{titleText}</Text> : null}
|
|
612
|
+
{bodyText ? <Text selectable style={S.itemBody}>{bodyText}</Text> : null}
|
|
613
|
+
{t ? <Text style={S.itemMeta}>{t}</Text> : null}
|
|
614
|
+
</View>
|
|
615
|
+
{sender ? (
|
|
616
|
+
<View style={S.announceActions}>
|
|
617
|
+
<Pressable style={S.copyBtn} onPress={() => copyToClipboard(sender)}>
|
|
618
|
+
<Text style={S.copyBtnText}>⎘</Text>
|
|
619
|
+
</Pressable>
|
|
620
|
+
<Pressable style={S.sendToBtn} onPress={() => { setDest(sender); setSendResult(''); }}>
|
|
621
|
+
<Text style={S.sendToBtnText}>↩ Reply</Text>
|
|
622
|
+
</Pressable>
|
|
623
|
+
</View>
|
|
624
|
+
) : null}
|
|
625
|
+
</View>
|
|
626
|
+
</View>
|
|
627
|
+
);
|
|
628
|
+
})}
|
|
629
|
+
</>
|
|
630
|
+
)}
|
|
631
|
+
|
|
632
|
+
{/* Live (in-session) */}
|
|
633
|
+
{msgEvts.length > 0 && <Text style={S.sectionLabel}>Live session</Text>}
|
|
634
|
+
{msgEvts.length === 0 && storedMsgs.length === 0 ? (
|
|
627
635
|
<Text style={S.muted}>No messages yet.</Text>
|
|
628
636
|
) : (
|
|
629
637
|
msgEvts.map((e, i) => {
|
|
@@ -885,4 +893,6 @@ const S = StyleSheet.create({
|
|
|
885
893
|
// Message card extras
|
|
886
894
|
msgTitle: { color: C.text, fontSize: 13, fontWeight: '600', fontStyle: 'italic' },
|
|
887
895
|
mediaBadge: { color: C.accentBright, fontSize: 11, fontFamily: 'monospace', marginTop: 2 },
|
|
896
|
+
sectionLabel: { color: C.textDim, fontSize: 11, fontWeight: '600', textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 4 },
|
|
897
|
+
storedCard: { borderColor: '#253d50', backgroundColor: '#0b1a25' },
|
|
888
898
|
});
|
|
@@ -8,32 +8,32 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>liblxmf_rn.a</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-
|
|
11
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
12
12
|
<key>LibraryPath</key>
|
|
13
13
|
<string>liblxmf_rn.a</string>
|
|
14
14
|
<key>SupportedArchitectures</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>arm64</string>
|
|
17
|
+
<string>x86_64</string>
|
|
17
18
|
</array>
|
|
18
19
|
<key>SupportedPlatform</key>
|
|
19
20
|
<string>ios</string>
|
|
21
|
+
<key>SupportedPlatformVariant</key>
|
|
22
|
+
<string>simulator</string>
|
|
20
23
|
</dict>
|
|
21
24
|
<dict>
|
|
22
25
|
<key>BinaryPath</key>
|
|
23
26
|
<string>liblxmf_rn.a</string>
|
|
24
27
|
<key>LibraryIdentifier</key>
|
|
25
|
-
<string>ios-
|
|
28
|
+
<string>ios-arm64</string>
|
|
26
29
|
<key>LibraryPath</key>
|
|
27
30
|
<string>liblxmf_rn.a</string>
|
|
28
31
|
<key>SupportedArchitectures</key>
|
|
29
32
|
<array>
|
|
30
33
|
<string>arm64</string>
|
|
31
|
-
<string>x86_64</string>
|
|
32
34
|
</array>
|
|
33
35
|
<key>SupportedPlatform</key>
|
|
34
36
|
<string>ios</string>
|
|
35
|
-
<key>SupportedPlatformVariant</key>
|
|
36
|
-
<string>simulator</string>
|
|
37
37
|
</dict>
|
|
38
38
|
</array>
|
|
39
39
|
<key>CFBundlePackageType</key>
|