kard-network-ble-mesh 1.0.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/README.md +216 -0
- package/android/build.gradle +94 -0
- package/android/gradle.properties +4 -0
- package/android/src/main/AndroidManifest.xml +18 -0
- package/android/src/main/AndroidManifestNew.xml +17 -0
- package/android/src/main/java/com/blemesh/BleMeshModule.kt +1143 -0
- package/android/src/main/java/com/blemesh/BleMeshPackage.kt +16 -0
- package/ios/BleMesh.m +45 -0
- package/ios/BleMesh.swift +1075 -0
- package/kard-network-ble-mesh.podspec +27 -0
- package/lib/commonjs/index.js +241 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/index.js +236 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/index.d.ts +103 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/package.json +90 -0
- package/src/index.ts +334 -0
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kard-network-ble-mesh",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Kard Network BLE Mesh networking library for React Native with seamless permissions",
|
|
5
|
+
"main": "lib/commonjs/index",
|
|
6
|
+
"module": "lib/module/index",
|
|
7
|
+
"types": "lib/typescript/index.d.ts",
|
|
8
|
+
"react-native": "src/index",
|
|
9
|
+
"source": "src/index",
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"lib",
|
|
13
|
+
"android",
|
|
14
|
+
"ios",
|
|
15
|
+
"cpp",
|
|
16
|
+
"*.podspec",
|
|
17
|
+
"!ios/build",
|
|
18
|
+
"!android/build",
|
|
19
|
+
"!android/gradle",
|
|
20
|
+
"!android/gradlew",
|
|
21
|
+
"!android/gradlew.bat",
|
|
22
|
+
"!android/local.properties",
|
|
23
|
+
"!**/__tests__",
|
|
24
|
+
"!**/__fixtures__",
|
|
25
|
+
"!**/__mocks__",
|
|
26
|
+
"!**/.*"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"typescript": "tsc --noEmit",
|
|
30
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
31
|
+
"prepare": "bob build",
|
|
32
|
+
"clean": "del-cli android/build lib",
|
|
33
|
+
"build": "npm run clean && bob build",
|
|
34
|
+
"publish:npm": "bash scripts/build-and-publish.sh",
|
|
35
|
+
"version:patch": "npm version patch",
|
|
36
|
+
"version:minor": "npm version minor",
|
|
37
|
+
"version:major": "npm version major",
|
|
38
|
+
"release": "npm run build && npm run publish:npm"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"react-native",
|
|
42
|
+
"ios",
|
|
43
|
+
"android",
|
|
44
|
+
"bluetooth",
|
|
45
|
+
"ble",
|
|
46
|
+
"mesh",
|
|
47
|
+
"p2p",
|
|
48
|
+
"kard"
|
|
49
|
+
],
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/anthropics/kard-network-ble-mesh.git"
|
|
53
|
+
},
|
|
54
|
+
"author": "Kard Network",
|
|
55
|
+
"license": "MIT",
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/anthropics/kard-network-ble-mesh/issues"
|
|
58
|
+
},
|
|
59
|
+
"homepage": "https://github.com/anthropics/kard-network-ble-mesh#readme",
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"registry": "https://registry.npmjs.org/"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/react": "^18.2.0",
|
|
65
|
+
"@types/react-native": "^0.72.0",
|
|
66
|
+
"del-cli": "^5.0.0",
|
|
67
|
+
"react": "18.2.0",
|
|
68
|
+
"react-native": "0.73.0",
|
|
69
|
+
"react-native-builder-bob": "^0.23.0",
|
|
70
|
+
"typescript": "^5.0.0"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"react": "*",
|
|
74
|
+
"react-native": "*"
|
|
75
|
+
},
|
|
76
|
+
"react-native-builder-bob": {
|
|
77
|
+
"source": "src",
|
|
78
|
+
"output": "lib",
|
|
79
|
+
"targets": [
|
|
80
|
+
"commonjs",
|
|
81
|
+
"module",
|
|
82
|
+
[
|
|
83
|
+
"typescript",
|
|
84
|
+
{
|
|
85
|
+
"project": "tsconfig.build.json"
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { NativeModules, NativeEventEmitter, Platform, PermissionsAndroid, Permission } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const LINKING_ERROR =
|
|
4
|
+
`The package 'kard-network-ble-mesh' doesn't seem to be linked. Make sure: \n\n` +
|
|
5
|
+
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
|
|
6
|
+
'- You rebuilt the app after installing the package\n' +
|
|
7
|
+
'- You are not using Expo Go (this package requires a development build)\n';
|
|
8
|
+
|
|
9
|
+
const BleMeshModule = NativeModules.BleMesh
|
|
10
|
+
? NativeModules.BleMesh
|
|
11
|
+
: new Proxy(
|
|
12
|
+
{},
|
|
13
|
+
{
|
|
14
|
+
get() {
|
|
15
|
+
throw new Error(LINKING_ERROR);
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const eventEmitter = new NativeEventEmitter(BleMeshModule);
|
|
21
|
+
|
|
22
|
+
// Types
|
|
23
|
+
export interface Peer {
|
|
24
|
+
peerId: string;
|
|
25
|
+
nickname: string;
|
|
26
|
+
isConnected: boolean;
|
|
27
|
+
rssi?: number;
|
|
28
|
+
lastSeen: number;
|
|
29
|
+
isVerified: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Message {
|
|
33
|
+
id: string;
|
|
34
|
+
content: string;
|
|
35
|
+
senderPeerId: string;
|
|
36
|
+
senderNickname: string;
|
|
37
|
+
timestamp: number;
|
|
38
|
+
isPrivate: boolean;
|
|
39
|
+
channel?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface FileTransfer {
|
|
43
|
+
id: string;
|
|
44
|
+
fileName: string;
|
|
45
|
+
fileSize: number;
|
|
46
|
+
mimeType: string;
|
|
47
|
+
data: string; // base64 encoded
|
|
48
|
+
senderPeerId: string;
|
|
49
|
+
timestamp: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface PermissionStatus {
|
|
53
|
+
bluetooth: boolean;
|
|
54
|
+
bluetoothAdvertise?: boolean; // Android 12+
|
|
55
|
+
bluetoothConnect?: boolean; // Android 12+
|
|
56
|
+
bluetoothScan?: boolean; // Android 12+
|
|
57
|
+
location: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'scanning';
|
|
61
|
+
|
|
62
|
+
export interface MeshServiceConfig {
|
|
63
|
+
nickname?: string;
|
|
64
|
+
autoRequestPermissions?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Event types
|
|
68
|
+
export type PeerListUpdatedEvent = { peers: Peer[] };
|
|
69
|
+
export type MessageReceivedEvent = { message: Message };
|
|
70
|
+
export type FileReceivedEvent = { file: FileTransfer };
|
|
71
|
+
export type ConnectionStateChangedEvent = { state: ConnectionState; peerCount: number };
|
|
72
|
+
export type PermissionsChangedEvent = { permissions: PermissionStatus };
|
|
73
|
+
export type ErrorEvent = { code: string; message: string };
|
|
74
|
+
|
|
75
|
+
// Event listener types
|
|
76
|
+
type EventCallback<T> = (data: T) => void;
|
|
77
|
+
|
|
78
|
+
class BleMeshService {
|
|
79
|
+
private isInitialized = false;
|
|
80
|
+
|
|
81
|
+
// Request all required permissions for BLE mesh networking
|
|
82
|
+
async requestPermissions(): Promise<PermissionStatus> {
|
|
83
|
+
if (Platform.OS === 'android') {
|
|
84
|
+
return this.requestAndroidPermissions();
|
|
85
|
+
} else if (Platform.OS === 'ios') {
|
|
86
|
+
return this.requestIOSPermissions();
|
|
87
|
+
}
|
|
88
|
+
throw new Error('Unsupported platform');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private async requestAndroidPermissions(): Promise<PermissionStatus> {
|
|
92
|
+
const apiLevel = Platform.Version as number;
|
|
93
|
+
|
|
94
|
+
let permissions: Permission[] = [];
|
|
95
|
+
|
|
96
|
+
if (apiLevel >= 31) {
|
|
97
|
+
// Android 12+ (API 31+)
|
|
98
|
+
permissions = [
|
|
99
|
+
PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADVERTISE as Permission,
|
|
100
|
+
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT as Permission,
|
|
101
|
+
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN as Permission,
|
|
102
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION as Permission,
|
|
103
|
+
];
|
|
104
|
+
} else {
|
|
105
|
+
// Android 11 and below
|
|
106
|
+
permissions = [
|
|
107
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION as Permission,
|
|
108
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION as Permission,
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const results = await PermissionsAndroid.requestMultiple(permissions);
|
|
113
|
+
|
|
114
|
+
const status: PermissionStatus = {
|
|
115
|
+
bluetooth: true,
|
|
116
|
+
location: results[PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION] === 'granted',
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
if (apiLevel >= 31) {
|
|
120
|
+
status.bluetoothAdvertise = results[PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADVERTISE] === 'granted';
|
|
121
|
+
status.bluetoothConnect = results[PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] === 'granted';
|
|
122
|
+
status.bluetoothScan = results[PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] === 'granted';
|
|
123
|
+
status.bluetooth = status.bluetoothAdvertise && status.bluetoothConnect && status.bluetoothScan;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return status;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async requestIOSPermissions(): Promise<PermissionStatus> {
|
|
130
|
+
// On iOS, Bluetooth permissions are requested automatically when starting services
|
|
131
|
+
// The native module handles the permission prompts
|
|
132
|
+
const result = await BleMeshModule.requestPermissions();
|
|
133
|
+
return {
|
|
134
|
+
bluetooth: result.bluetooth,
|
|
135
|
+
location: result.location,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check current permission status without requesting
|
|
140
|
+
async checkPermissions(): Promise<PermissionStatus> {
|
|
141
|
+
if (Platform.OS === 'android') {
|
|
142
|
+
return this.checkAndroidPermissions();
|
|
143
|
+
}
|
|
144
|
+
return BleMeshModule.checkPermissions();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private async checkAndroidPermissions(): Promise<PermissionStatus> {
|
|
148
|
+
const apiLevel = Platform.Version as number;
|
|
149
|
+
|
|
150
|
+
const fineLocation = await PermissionsAndroid.check(
|
|
151
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const status: PermissionStatus = {
|
|
155
|
+
bluetooth: true,
|
|
156
|
+
location: fineLocation,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (apiLevel >= 31) {
|
|
160
|
+
const advertise = await PermissionsAndroid.check(
|
|
161
|
+
PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADVERTISE
|
|
162
|
+
);
|
|
163
|
+
const connect = await PermissionsAndroid.check(
|
|
164
|
+
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT
|
|
165
|
+
);
|
|
166
|
+
const scan = await PermissionsAndroid.check(
|
|
167
|
+
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
status.bluetoothAdvertise = advertise;
|
|
171
|
+
status.bluetoothConnect = connect;
|
|
172
|
+
status.bluetoothScan = scan;
|
|
173
|
+
status.bluetooth = advertise && connect && scan;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return status;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Initialize and start the mesh service
|
|
180
|
+
async start(config: MeshServiceConfig = {}): Promise<void> {
|
|
181
|
+
const { nickname = 'anon', autoRequestPermissions = true } = config;
|
|
182
|
+
|
|
183
|
+
if (autoRequestPermissions) {
|
|
184
|
+
const permissions = await this.requestPermissions();
|
|
185
|
+
const hasAllPermissions = permissions.bluetooth && permissions.location;
|
|
186
|
+
|
|
187
|
+
if (!hasAllPermissions) {
|
|
188
|
+
throw new Error('Required permissions not granted. Bluetooth and Location permissions are required.');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await BleMeshModule.start(nickname);
|
|
193
|
+
this.isInitialized = true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Stop the mesh service
|
|
197
|
+
async stop(): Promise<void> {
|
|
198
|
+
if (!this.isInitialized) return;
|
|
199
|
+
await BleMeshModule.stop();
|
|
200
|
+
this.isInitialized = false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Set the user's nickname
|
|
204
|
+
async setNickname(nickname: string): Promise<void> {
|
|
205
|
+
this.ensureInitialized();
|
|
206
|
+
await BleMeshModule.setNickname(nickname);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Get the current peer ID
|
|
210
|
+
async getMyPeerId(): Promise<string> {
|
|
211
|
+
this.ensureInitialized();
|
|
212
|
+
return BleMeshModule.getMyPeerId();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Get the current nickname
|
|
216
|
+
async getMyNickname(): Promise<string> {
|
|
217
|
+
this.ensureInitialized();
|
|
218
|
+
return BleMeshModule.getMyNickname();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Get list of currently connected peers
|
|
222
|
+
async getPeers(): Promise<Peer[]> {
|
|
223
|
+
this.ensureInitialized();
|
|
224
|
+
return BleMeshModule.getPeers();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Send a public broadcast message to all peers
|
|
228
|
+
async sendMessage(content: string, channel?: string): Promise<string> {
|
|
229
|
+
this.ensureInitialized();
|
|
230
|
+
return BleMeshModule.sendMessage(content, channel || null);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Send a private encrypted message to a specific peer
|
|
234
|
+
async sendPrivateMessage(content: string, recipientPeerId: string): Promise<string> {
|
|
235
|
+
this.ensureInitialized();
|
|
236
|
+
return BleMeshModule.sendPrivateMessage(content, recipientPeerId);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Send a file (broadcast or private)
|
|
240
|
+
async sendFile(
|
|
241
|
+
filePath: string,
|
|
242
|
+
options?: { recipientPeerId?: string; channel?: string }
|
|
243
|
+
): Promise<string> {
|
|
244
|
+
this.ensureInitialized();
|
|
245
|
+
return BleMeshModule.sendFile(filePath, options?.recipientPeerId || null, options?.channel || null);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Send a read receipt for a message
|
|
249
|
+
async sendReadReceipt(messageId: string, recipientPeerId: string): Promise<void> {
|
|
250
|
+
this.ensureInitialized();
|
|
251
|
+
await BleMeshModule.sendReadReceipt(messageId, recipientPeerId);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Check if we have an encrypted session with a peer
|
|
255
|
+
async hasEncryptedSession(peerId: string): Promise<boolean> {
|
|
256
|
+
this.ensureInitialized();
|
|
257
|
+
return BleMeshModule.hasEncryptedSession(peerId);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Initiate a Noise handshake with a peer
|
|
261
|
+
async initiateHandshake(peerId: string): Promise<void> {
|
|
262
|
+
this.ensureInitialized();
|
|
263
|
+
await BleMeshModule.initiateHandshake(peerId);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Get the identity fingerprint for verification
|
|
267
|
+
async getIdentityFingerprint(): Promise<string> {
|
|
268
|
+
this.ensureInitialized();
|
|
269
|
+
return BleMeshModule.getIdentityFingerprint();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Get peer's fingerprint for verification
|
|
273
|
+
async getPeerFingerprint(peerId: string): Promise<string | null> {
|
|
274
|
+
this.ensureInitialized();
|
|
275
|
+
return BleMeshModule.getPeerFingerprint(peerId);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Force a broadcast announce to refresh presence
|
|
279
|
+
async broadcastAnnounce(): Promise<void> {
|
|
280
|
+
this.ensureInitialized();
|
|
281
|
+
await BleMeshModule.broadcastAnnounce();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Event listeners
|
|
285
|
+
onPeerListUpdated(callback: EventCallback<PeerListUpdatedEvent>): () => void {
|
|
286
|
+
const subscription = eventEmitter.addListener('onPeerListUpdated', callback);
|
|
287
|
+
return () => subscription.remove();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
onMessageReceived(callback: EventCallback<MessageReceivedEvent>): () => void {
|
|
291
|
+
const subscription = eventEmitter.addListener('onMessageReceived', callback);
|
|
292
|
+
return () => subscription.remove();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
onFileReceived(callback: EventCallback<FileReceivedEvent>): () => void {
|
|
296
|
+
const subscription = eventEmitter.addListener('onFileReceived', callback);
|
|
297
|
+
return () => subscription.remove();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
onConnectionStateChanged(callback: EventCallback<ConnectionStateChangedEvent>): () => void {
|
|
301
|
+
const subscription = eventEmitter.addListener('onConnectionStateChanged', callback);
|
|
302
|
+
return () => subscription.remove();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
onReadReceipt(callback: EventCallback<{ messageId: string; fromPeerId: string }>): () => void {
|
|
306
|
+
const subscription = eventEmitter.addListener('onReadReceipt', callback);
|
|
307
|
+
return () => subscription.remove();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
onDeliveryAck(callback: EventCallback<{ messageId: string; fromPeerId: string }>): () => void {
|
|
311
|
+
const subscription = eventEmitter.addListener('onDeliveryAck', callback);
|
|
312
|
+
return () => subscription.remove();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
onError(callback: EventCallback<ErrorEvent>): () => void {
|
|
316
|
+
const subscription = eventEmitter.addListener('onError', callback);
|
|
317
|
+
return () => subscription.remove();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private ensureInitialized(): void {
|
|
321
|
+
if (!this.isInitialized) {
|
|
322
|
+
throw new Error('BleMesh service not initialized. Call start() first.');
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Export singleton instance
|
|
328
|
+
export const BleMesh = new BleMeshService();
|
|
329
|
+
|
|
330
|
+
// Also export the class for those who want multiple instances
|
|
331
|
+
export { BleMeshService };
|
|
332
|
+
|
|
333
|
+
// Default export
|
|
334
|
+
export default BleMesh;
|