@magicred-1/react-native-lxmf 0.2.22 → 0.2.24

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.
@@ -34,7 +34,7 @@ export interface LxmfMessageEvent {
34
34
  }[];
35
35
  }
36
36
  export interface LxmfEvent {
37
- type: 'statusChanged' | 'packetReceived' | 'txReceived' | 'beaconDiscovered' | 'messageReceived' | 'announceReceived' | 'log' | 'error';
37
+ type: 'statusChanged' | 'packetReceived' | 'txReceived' | 'beaconDiscovered' | 'messageReceived' | 'announceReceived' | 'messageQueued' | 'messageDelivered' | 'messageFailed' | 'log' | 'error';
38
38
  [key: string]: any;
39
39
  }
40
40
  /** Node transport mode */
@@ -6,12 +6,13 @@ import {
6
6
  ScrollView,
7
7
  Share,
8
8
  StyleSheet,
9
+ Switch,
9
10
  Text,
10
11
  TextInput,
11
12
  View,
12
13
  } from 'react-native';
13
14
  import * as SecureStore from 'expo-secure-store';
14
- import { LxmfModule, LxmfNodeMode, type LxmfEvent, useLxmf } from '@magicred-1/react-native-lxmf';
15
+ import { LxmfModule, LxmfNodeMode, type LxmfEvent, type LxmfMessageEvent, useLxmf } from '@magicred-1/react-native-lxmf';
15
16
 
16
17
  // Persisted identity blob schema (versioned). Stored in expo-secure-store under
17
18
  // IDENTITY_KEY — encrypted at rest on iOS (Keychain) and Android (Keystore-backed).
@@ -78,25 +79,14 @@ function fmtTime(e: LxmfEvent): string {
78
79
  .toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
79
80
  }
80
81
 
81
- /** Extract message text from raw LXMF hex content.
82
- * Skips 96B header (dest+src+sig), scans for longest printable run. */
83
- function decodeLxmfContent(hexOrStr: string | undefined): string {
84
- if (!hexOrStr) return '';
85
- const hex = String(hexOrStr);
86
- if (!/^[0-9a-fA-F]+$/.test(hex)) return hex;
87
- const len = hex.length / 2;
88
- if (len <= 96) return `[${len}B raw]`;
82
+ function base64ToUtf8(b64: string): string {
83
+ if (!b64) return '';
89
84
  try {
90
- const payload = new Uint8Array(len - 96);
91
- for (let i = 0; i < payload.length; i++) {
92
- payload[i] = Number.parseInt(hex.slice((i + 96) * 2, (i + 96) * 2 + 2), 16);
93
- }
94
- const decoded = new TextDecoder('utf-8', { fatal: false }).decode(payload);
95
- const runs = decoded.match(/[\x20-\x7E\n\r\t]{4,}/g) ?? [];
96
- if (runs.length === 0) return `[${len}B binary]`;
97
- return runs.reduce((a, b) => a.length >= b.length ? a : b).trim();
85
+ const binary = globalThis.atob(b64);
86
+ const bytes = Uint8Array.from(binary, c => c.codePointAt(0) ?? 0);
87
+ return new TextDecoder('utf-8', { fatal: false }).decode(bytes);
98
88
  } catch {
99
- return `[${len}B]`;
89
+ return '';
100
90
  }
101
91
  }
102
92
 
@@ -205,6 +195,7 @@ export default function HomeScreen() {
205
195
  const [tcpHost, setTcpHost] = useState('192.168.1.135');
206
196
  const [tcpPort, setTcpPort] = useState('4243');
207
197
  const [displayName, setDisplayName] = useState('lxmf-mobile');
198
+ const [isBeacon, setIsBeacon] = useState(false);
208
199
  const [bleActive, setBleActive] = useState(false);
209
200
  const [tcpActive, setTcpActive] = useState(false);
210
201
  const [transportMsg, setTransportMsg] = useState('');
@@ -335,13 +326,14 @@ export default function HomeScreen() {
335
326
  mode: LxmfNodeMode.ReticulumAndBle,
336
327
  tcpInterfaces: [{ host, port }],
337
328
  displayName: displayName.trim() || 'lxmf-mobile',
329
+ isBeacon,
338
330
  });
339
331
  if (ok) {
340
332
  setTcpActive(true);
341
333
  startBLE();
342
334
  setBleActive(true);
343
335
  }
344
- }, [tcpHost, tcpPort, displayName, start, startBLE]);
336
+ }, [tcpHost, tcpPort, displayName, isBeacon, start, startBLE]);
345
337
 
346
338
  const onStopTcp = useCallback(async () => {
347
339
  stopBLE();
@@ -378,6 +370,7 @@ export default function HomeScreen() {
378
370
  mode: LxmfNodeMode.ReticulumAndBle,
379
371
  tcpInterfaces: [{ host, port }],
380
372
  displayName: displayName.trim() || 'lxmf-mobile',
373
+ isBeacon,
381
374
  });
382
375
  if (!ok) {
383
376
  setTransportMsg('Failed to start node.');
@@ -387,7 +380,7 @@ export default function HomeScreen() {
387
380
  }
388
381
  startBLE();
389
382
  setBleActive(true);
390
- }, [isRunning, tcpHost, tcpPort, start, startBLE, displayName]);
383
+ }, [isRunning, tcpHost, tcpPort, isBeacon, start, startBLE, displayName]);
391
384
 
392
385
  const onStopBle = useCallback(async () => {
393
386
  stopBLE();
@@ -505,6 +498,16 @@ export default function HomeScreen() {
505
498
  autoCapitalize="none"
506
499
  autoCorrect={false}
507
500
  />
501
+ <View style={S.switchRow}>
502
+ <Text style={S.switchLabel}>Beacon mode</Text>
503
+ <Switch
504
+ value={isBeacon}
505
+ onValueChange={setIsBeacon}
506
+ disabled={isRunning}
507
+ trackColor={{ false: C.border, true: C.accent }}
508
+ thumbColor={isBeacon ? C.accentBright : C.textDim}
509
+ />
510
+ </View>
508
511
  {transportMsg ? <Text style={S.warn}>{transportMsg}</Text> : null}
509
512
  <View style={S.btnRow}>
510
513
  <Btn label="Start TCP" onPress={onStartTcp} disabled={!isNativeAvailable || isRunning || !identityHydrated} />
@@ -624,28 +627,31 @@ export default function HomeScreen() {
624
627
  <Text style={S.muted}>No messages yet.</Text>
625
628
  ) : (
626
629
  msgEvts.map((e, i) => {
627
- const body = decodeLxmfContent(e.content);
628
- const sender = String(e.source ?? '');
630
+ const msg = e as unknown as LxmfMessageEvent;
631
+ const bodyText = base64ToUtf8(msg.body ?? '');
632
+ const titleText = msg.title ? base64ToUtf8(msg.title) : '';
633
+ const sender = msg.source ?? '';
629
634
  return (
630
635
  <View key={`${evtKey(e, 'msg-')}-${i}`} style={S.itemCard}>
631
636
  <View style={S.announceHeader}>
632
637
  <View style={S.announceInfo}>
633
- <Text selectable style={S.itemTitle}>
634
- From: {shortHex(sender)}
635
- </Text>
636
- {body ? <Text selectable style={S.itemBody}>{body}</Text> : null}
638
+ <Text selectable style={S.itemTitle}>From: {shortHex(sender)}</Text>
639
+ {titleText ? <Text selectable style={S.msgTitle}>{titleText}</Text> : null}
640
+ {bodyText ? <Text selectable style={S.itemBody}>{bodyText}</Text> : null}
641
+ {msg.image ? (
642
+ <Text style={S.mediaBadge}>[img: {msg.image.mimeType}]</Text>
643
+ ) : null}
644
+ {msg.files?.length ? (
645
+ <Text style={S.mediaBadge}>[{msg.files.length} file{msg.files.length > 1 ? 's' : ''}]</Text>
646
+ ) : null}
637
647
  <Text style={S.itemMeta}>{fmtTime(e)}</Text>
638
648
  </View>
639
649
  {sender ? (
640
650
  <View style={S.announceActions}>
641
- <Pressable
642
- style={S.copyBtn}
643
- onPress={() => copyToClipboard(sender)}>
651
+ <Pressable style={S.copyBtn} onPress={() => copyToClipboard(sender)}>
644
652
  <Text style={S.copyBtnText}>⎘</Text>
645
653
  </Pressable>
646
- <Pressable
647
- style={S.sendToBtn}
648
- onPress={() => { setDest(sender); setSendResult(''); }}>
654
+ <Pressable style={S.sendToBtn} onPress={() => { setDest(sender); setSendResult(''); }}>
649
655
  <Text style={S.sendToBtnText}>↩ Reply</Text>
650
656
  </Pressable>
651
657
  </View>
@@ -871,4 +877,12 @@ const S = StyleSheet.create({
871
877
 
872
878
  // Destination pre-filled indicator
873
879
  destFilled: { color: C.accentBright, fontSize: 12, fontFamily: 'monospace' },
880
+
881
+ // Beacon mode toggle row
882
+ switchRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 4 },
883
+ switchLabel: { color: C.textDim, fontSize: 13 },
884
+
885
+ // Message card extras
886
+ msgTitle: { color: C.text, fontSize: 13, fontWeight: '600', fontStyle: 'italic' },
887
+ mediaBadge: { color: C.accentBright, fontSize: 11, fontFamily: 'monospace', marginTop: 2 },
874
888
  });
@@ -8,32 +8,32 @@
8
8
  <key>BinaryPath</key>
9
9
  <string>liblxmf_rn.a</string>
10
10
  <key>LibraryIdentifier</key>
11
- <string>ios-arm64</string>
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-arm64_x86_64-simulator</string>
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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magicred-1/react-native-lxmf",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
4
4
  "description": "LXMF Reticulum mesh networking for React Native + Expo",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",