@meshwhisper/sdk 0.1.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 +138 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +19 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/chaff/index.d.ts +91 -0
- package/dist/chaff/index.d.ts.map +1 -0
- package/dist/chaff/index.js +268 -0
- package/dist/chaff/index.js.map +1 -0
- package/dist/cluster/index.d.ts +159 -0
- package/dist/cluster/index.d.ts.map +1 -0
- package/dist/cluster/index.js +393 -0
- package/dist/cluster/index.js.map +1 -0
- package/dist/compliance/index.d.ts +129 -0
- package/dist/compliance/index.d.ts.map +1 -0
- package/dist/compliance/index.js +315 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/crypto/index.d.ts +65 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +146 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/group/index.d.ts +155 -0
- package/dist/group/index.d.ts.map +1 -0
- package/dist/group/index.js +560 -0
- package/dist/group/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/namespace/index.d.ts +155 -0
- package/dist/namespace/index.d.ts.map +1 -0
- package/dist/namespace/index.js +278 -0
- package/dist/namespace/index.js.map +1 -0
- package/dist/node/index.d.ts +4 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +19 -0
- package/dist/node/index.js.map +1 -0
- package/dist/packet/index.d.ts +63 -0
- package/dist/packet/index.d.ts.map +1 -0
- package/dist/packet/index.js +244 -0
- package/dist/packet/index.js.map +1 -0
- package/dist/permissions/index.d.ts +107 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +282 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/persistence/idb-storage.d.ts +27 -0
- package/dist/persistence/idb-storage.d.ts.map +1 -0
- package/dist/persistence/idb-storage.js +75 -0
- package/dist/persistence/idb-storage.js.map +1 -0
- package/dist/persistence/index.d.ts +4 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +3 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/node-storage.d.ts +33 -0
- package/dist/persistence/node-storage.d.ts.map +1 -0
- package/dist/persistence/node-storage.js +90 -0
- package/dist/persistence/node-storage.js.map +1 -0
- package/dist/persistence/serialization.d.ts +4 -0
- package/dist/persistence/serialization.d.ts.map +1 -0
- package/dist/persistence/serialization.js +49 -0
- package/dist/persistence/serialization.js.map +1 -0
- package/dist/persistence/types.d.ts +29 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +5 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/ratchet/index.d.ts +80 -0
- package/dist/ratchet/index.d.ts.map +1 -0
- package/dist/ratchet/index.js +259 -0
- package/dist/ratchet/index.js.map +1 -0
- package/dist/reciprocity/index.d.ts +109 -0
- package/dist/reciprocity/index.d.ts.map +1 -0
- package/dist/reciprocity/index.js +311 -0
- package/dist/reciprocity/index.js.map +1 -0
- package/dist/relay/index.d.ts +87 -0
- package/dist/relay/index.d.ts.map +1 -0
- package/dist/relay/index.js +286 -0
- package/dist/relay/index.js.map +1 -0
- package/dist/routing/index.d.ts +136 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +478 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/sdk/index.d.ts +322 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +1530 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sybil/index.d.ts +123 -0
- package/dist/sybil/index.d.ts.map +1 -0
- package/dist/sybil/index.js +491 -0
- package/dist/sybil/index.js.map +1 -0
- package/dist/transport/browser/index.d.ts +34 -0
- package/dist/transport/browser/index.d.ts.map +1 -0
- package/dist/transport/browser/index.js +176 -0
- package/dist/transport/browser/index.js.map +1 -0
- package/dist/transport/local/index.d.ts +57 -0
- package/dist/transport/local/index.d.ts.map +1 -0
- package/dist/transport/local/index.js +442 -0
- package/dist/transport/local/index.js.map +1 -0
- package/dist/transport/negotiator/index.d.ts +79 -0
- package/dist/transport/negotiator/index.d.ts.map +1 -0
- package/dist/transport/negotiator/index.js +289 -0
- package/dist/transport/negotiator/index.js.map +1 -0
- package/dist/transport/node/index.d.ts +56 -0
- package/dist/transport/node/index.d.ts.map +1 -0
- package/dist/transport/node/index.js +209 -0
- package/dist/transport/node/index.js.map +1 -0
- package/dist/transport/noop/index.d.ts +11 -0
- package/dist/transport/noop/index.d.ts.map +1 -0
- package/dist/transport/noop/index.js +20 -0
- package/dist/transport/noop/index.js.map +1 -0
- package/dist/transport/p2p/index.d.ts +109 -0
- package/dist/transport/p2p/index.d.ts.map +1 -0
- package/dist/transport/p2p/index.js +237 -0
- package/dist/transport/p2p/index.js.map +1 -0
- package/dist/transport/websocket/index.d.ts +89 -0
- package/dist/transport/websocket/index.d.ts.map +1 -0
- package/dist/transport/websocket/index.js +498 -0
- package/dist/transport/websocket/index.js.map +1 -0
- package/dist/transport/websocket/serialize.d.ts +5 -0
- package/dist/transport/websocket/serialize.d.ts.map +1 -0
- package/dist/transport/websocket/serialize.js +55 -0
- package/dist/transport/websocket/serialize.js.map +1 -0
- package/dist/types.d.ts +215 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/x3dh/index.d.ts +120 -0
- package/dist/x3dh/index.d.ts.map +1 -0
- package/dist/x3dh/index.js +290 -0
- package/dist/x3dh/index.js.map +1 -0
- package/package.json +59 -0
- package/src/browser/index.ts +19 -0
- package/src/chaff/index.ts +340 -0
- package/src/cluster/index.ts +482 -0
- package/src/compliance/index.ts +407 -0
- package/src/crypto/index.ts +193 -0
- package/src/group/index.ts +719 -0
- package/src/index.ts +87 -0
- package/src/lz4js.d.ts +58 -0
- package/src/namespace/index.ts +336 -0
- package/src/node/index.ts +19 -0
- package/src/packet/index.ts +326 -0
- package/src/permissions/index.ts +405 -0
- package/src/persistence/idb-storage.ts +83 -0
- package/src/persistence/index.ts +3 -0
- package/src/persistence/node-storage.ts +96 -0
- package/src/persistence/serialization.ts +75 -0
- package/src/persistence/types.ts +33 -0
- package/src/ratchet/index.ts +363 -0
- package/src/reciprocity/index.ts +371 -0
- package/src/relay/index.ts +382 -0
- package/src/routing/index.ts +577 -0
- package/src/sdk/index.ts +1994 -0
- package/src/sybil/index.ts +661 -0
- package/src/transport/browser/index.ts +201 -0
- package/src/transport/local/index.ts +540 -0
- package/src/transport/negotiator/index.ts +397 -0
- package/src/transport/node/index.ts +234 -0
- package/src/transport/noop/index.ts +22 -0
- package/src/transport/p2p/index.ts +345 -0
- package/src/transport/websocket/index.ts +660 -0
- package/src/transport/websocket/serialize.ts +68 -0
- package/src/types.ts +275 -0
- package/src/x3dh/index.ts +388 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// MeshWhisper SDK — Platform P2P Transport
|
|
3
|
+
// Abstract bridge for native P2P (Apple Multipeer Connectivity,
|
|
4
|
+
// Google Nearby Connections) with TypeScript-side Transport
|
|
5
|
+
// wrapper. Native SDKs implement PlatformP2PBridge; this module
|
|
6
|
+
// provides the Transport adapter and a no-op fallback for
|
|
7
|
+
// environments where native P2P is unavailable.
|
|
8
|
+
// ============================================================
|
|
9
|
+
|
|
10
|
+
import type { Packet, Transport, PacketFlags } from '../../types.js';
|
|
11
|
+
|
|
12
|
+
// ---- Bridge Interfaces ----
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Metadata about a discovered P2P peer.
|
|
16
|
+
* Provided by the native layer during discovery.
|
|
17
|
+
*/
|
|
18
|
+
export interface PeerInfo {
|
|
19
|
+
/** Human-readable name for the peer device. */
|
|
20
|
+
displayName: string;
|
|
21
|
+
/** The service identifier under which this peer was discovered. */
|
|
22
|
+
serviceId: string;
|
|
23
|
+
/** Advertised capabilities (e.g. "relay", "store-forward"). */
|
|
24
|
+
capabilities: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Abstract interface that native platform code (Swift / Kotlin)
|
|
29
|
+
* must implement to provide P2P connectivity to the TypeScript SDK.
|
|
30
|
+
*
|
|
31
|
+
* - iOS: backed by Apple Multipeer Connectivity Framework
|
|
32
|
+
* (BLE discovery + Wi-Fi Direct data transfer).
|
|
33
|
+
* - Android: backed by Google Nearby Connections API
|
|
34
|
+
* (Bluetooth + Wi-Fi).
|
|
35
|
+
*
|
|
36
|
+
* All methods are designed to be thin wrappers around the native
|
|
37
|
+
* APIs so the bridge implementation stays minimal.
|
|
38
|
+
*/
|
|
39
|
+
export interface PlatformP2PBridge {
|
|
40
|
+
/** Begin advertising this device for the given service. */
|
|
41
|
+
startAdvertising(serviceId: string): Promise<void>;
|
|
42
|
+
|
|
43
|
+
/** Begin scanning for peers advertising the given service. */
|
|
44
|
+
startDiscovery(serviceId: string): Promise<void>;
|
|
45
|
+
|
|
46
|
+
/** Stop advertising. */
|
|
47
|
+
stopAdvertising(): Promise<void>;
|
|
48
|
+
|
|
49
|
+
/** Stop discovery scanning. */
|
|
50
|
+
stopDiscovery(): Promise<void>;
|
|
51
|
+
|
|
52
|
+
/** Send raw bytes to a connected peer. */
|
|
53
|
+
sendData(peerId: string, data: Uint8Array): Promise<void>;
|
|
54
|
+
|
|
55
|
+
/** Register a callback invoked when a new peer is discovered. */
|
|
56
|
+
onPeerDiscovered(callback: (peerId: string, info: PeerInfo) => void): void;
|
|
57
|
+
|
|
58
|
+
/** Register a callback invoked when a previously discovered peer is lost. */
|
|
59
|
+
onPeerLost(callback: (peerId: string) => void): void;
|
|
60
|
+
|
|
61
|
+
/** Register a callback invoked when data is received from a peer. */
|
|
62
|
+
onDataReceived(callback: (peerId: string, data: Uint8Array) => void): void;
|
|
63
|
+
|
|
64
|
+
/** Returns the IDs of all currently connected peers. */
|
|
65
|
+
getConnectedPeers(): string[];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns true if the underlying native P2P framework is
|
|
69
|
+
* available on this device / OS version.
|
|
70
|
+
*/
|
|
71
|
+
isSupported(): boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---- Packet Serialization ----
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Minimum header size in bytes:
|
|
78
|
+
* version (1) + flags (1) + destHash (8) + senderEphemeralId (16)
|
|
79
|
+
* + ttl (1) + payloadLength (4) = 31
|
|
80
|
+
*/
|
|
81
|
+
const HEADER_SIZE = 31;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Serializes a Packet into a compact binary format for transmission
|
|
85
|
+
* over the native P2P bridge.
|
|
86
|
+
*
|
|
87
|
+
* Wire format (big-endian):
|
|
88
|
+
* [0] u8 version
|
|
89
|
+
* [1] u8 flags
|
|
90
|
+
* [2..9] 8B destHash
|
|
91
|
+
* [10..25] 16B senderEphemeralId
|
|
92
|
+
* [26] u8 ttl
|
|
93
|
+
* [27..30] u32 payloadLength
|
|
94
|
+
* [31..] var encryptedPayload
|
|
95
|
+
*/
|
|
96
|
+
function serializePacket(packet: Packet): Uint8Array {
|
|
97
|
+
const totalLength = HEADER_SIZE + packet.encryptedPayload.length;
|
|
98
|
+
const buffer = new Uint8Array(totalLength);
|
|
99
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
100
|
+
|
|
101
|
+
let offset = 0;
|
|
102
|
+
|
|
103
|
+
// version (u8)
|
|
104
|
+
view.setUint8(offset, packet.version);
|
|
105
|
+
offset += 1;
|
|
106
|
+
|
|
107
|
+
// flags (u8)
|
|
108
|
+
view.setUint8(offset, packet.flags as number);
|
|
109
|
+
offset += 1;
|
|
110
|
+
|
|
111
|
+
// destHash (8 bytes)
|
|
112
|
+
buffer.set(packet.destHash, offset);
|
|
113
|
+
offset += 8;
|
|
114
|
+
|
|
115
|
+
// senderEphemeralId (16 bytes)
|
|
116
|
+
buffer.set(packet.senderEphemeralId, offset);
|
|
117
|
+
offset += 16;
|
|
118
|
+
|
|
119
|
+
// ttl (u8)
|
|
120
|
+
view.setUint8(offset, packet.ttl);
|
|
121
|
+
offset += 1;
|
|
122
|
+
|
|
123
|
+
// payloadLength (u32 big-endian)
|
|
124
|
+
view.setUint32(offset, packet.payloadLength, false);
|
|
125
|
+
offset += 4;
|
|
126
|
+
|
|
127
|
+
// encryptedPayload
|
|
128
|
+
buffer.set(packet.encryptedPayload, offset);
|
|
129
|
+
|
|
130
|
+
return buffer;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Deserializes a Uint8Array back into a Packet.
|
|
135
|
+
* Throws if the data is too short or the payload length mismatches.
|
|
136
|
+
*/
|
|
137
|
+
function deserializePacket(data: Uint8Array): Packet {
|
|
138
|
+
if (data.length < HEADER_SIZE) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`P2P: packet too short (${data.length} bytes, need at least ${HEADER_SIZE})`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
145
|
+
let offset = 0;
|
|
146
|
+
|
|
147
|
+
const version = view.getUint8(offset);
|
|
148
|
+
offset += 1;
|
|
149
|
+
|
|
150
|
+
const flags = view.getUint8(offset) as PacketFlags;
|
|
151
|
+
offset += 1;
|
|
152
|
+
|
|
153
|
+
const destHash = data.slice(offset, offset + 8);
|
|
154
|
+
offset += 8;
|
|
155
|
+
|
|
156
|
+
const senderEphemeralId = data.slice(offset, offset + 16);
|
|
157
|
+
offset += 16;
|
|
158
|
+
|
|
159
|
+
const ttl = view.getUint8(offset);
|
|
160
|
+
offset += 1;
|
|
161
|
+
|
|
162
|
+
const payloadLength = view.getUint32(offset, false);
|
|
163
|
+
offset += 4;
|
|
164
|
+
|
|
165
|
+
const encryptedPayload = data.slice(offset, offset + payloadLength);
|
|
166
|
+
if (encryptedPayload.length !== payloadLength) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`P2P: payload length mismatch (header says ${payloadLength}, got ${encryptedPayload.length})`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
version,
|
|
174
|
+
flags,
|
|
175
|
+
destHash,
|
|
176
|
+
senderEphemeralId,
|
|
177
|
+
ttl,
|
|
178
|
+
payloadLength,
|
|
179
|
+
encryptedPayload,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---- No-Op Bridge ----
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Default bridge used when no native implementation is registered.
|
|
187
|
+
* All methods are safe no-ops. isSupported() returns false so the
|
|
188
|
+
* transport layer knows to skip this bearer.
|
|
189
|
+
*/
|
|
190
|
+
class NoOpBridge implements PlatformP2PBridge {
|
|
191
|
+
async startAdvertising(_serviceId: string): Promise<void> {}
|
|
192
|
+
async startDiscovery(_serviceId: string): Promise<void> {}
|
|
193
|
+
async stopAdvertising(): Promise<void> {}
|
|
194
|
+
async stopDiscovery(): Promise<void> {}
|
|
195
|
+
async sendData(_peerId: string, _data: Uint8Array): Promise<void> {}
|
|
196
|
+
onPeerDiscovered(_callback: (peerId: string, info: PeerInfo) => void): void {}
|
|
197
|
+
onPeerLost(_callback: (peerId: string) => void): void {}
|
|
198
|
+
onDataReceived(_callback: (peerId: string, data: Uint8Array) => void): void {}
|
|
199
|
+
getConnectedPeers(): string[] {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
isSupported(): boolean {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ---- Bridge Registry ----
|
|
208
|
+
|
|
209
|
+
/** The currently registered bridge (defaults to NoOpBridge). */
|
|
210
|
+
let activeBridge: PlatformP2PBridge = new NoOpBridge();
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Registers a platform-specific P2P bridge implementation.
|
|
214
|
+
*
|
|
215
|
+
* Called by native code (via JSI on React Native, or similar
|
|
216
|
+
* interop layers) to inject the real Multipeer / Nearby
|
|
217
|
+
* Connections wrapper at runtime.
|
|
218
|
+
*
|
|
219
|
+
* @param bridge - The native bridge implementation.
|
|
220
|
+
*/
|
|
221
|
+
export function registerPlatformBridge(bridge: PlatformP2PBridge): void {
|
|
222
|
+
activeBridge = bridge;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ---- Service ID Generation ----
|
|
226
|
+
|
|
227
|
+
/** Max length for Bonjour / NSD service types. */
|
|
228
|
+
const MAX_SERVICE_ID_LENGTH = 15;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generates a platform-appropriate service identifier from an
|
|
232
|
+
* application namespace string.
|
|
233
|
+
*
|
|
234
|
+
* Both Apple Multipeer Connectivity and Android NSD impose
|
|
235
|
+
* restrictions on service type identifiers (length, allowed
|
|
236
|
+
* characters). This function produces a short, deterministic,
|
|
237
|
+
* lowercase-alphanumeric ID derived from the namespace.
|
|
238
|
+
*
|
|
239
|
+
* @param namespace - The application namespace (e.g. "com.example.chat").
|
|
240
|
+
* @returns A service ID string safe for both iOS and Android.
|
|
241
|
+
*/
|
|
242
|
+
export function generateServiceId(namespace: string): string {
|
|
243
|
+
// Simple FNV-1a 32-bit hash for determinism without heavy deps.
|
|
244
|
+
let h = 0x811c9dc5;
|
|
245
|
+
for (let i = 0; i < namespace.length; i++) {
|
|
246
|
+
h ^= namespace.charCodeAt(i);
|
|
247
|
+
h = Math.imul(h, 0x01000193);
|
|
248
|
+
}
|
|
249
|
+
// Unsigned conversion then base-36 for compact alphanumeric output.
|
|
250
|
+
const hashStr = (h >>> 0).toString(36);
|
|
251
|
+
// Prefix with "mw-" (MeshWhisper) for readability, truncate to limit.
|
|
252
|
+
const serviceId = `mw-${hashStr}`;
|
|
253
|
+
return serviceId.slice(0, MAX_SERVICE_ID_LENGTH);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ---- Transport Implementation ----
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* PlatformP2PTransport adapts a PlatformP2PBridge into the
|
|
260
|
+
* SDK's Transport interface.
|
|
261
|
+
*
|
|
262
|
+
* When a native bridge has been registered via
|
|
263
|
+
* `registerPlatformBridge()`, this transport serializes Packets
|
|
264
|
+
* to wire bytes, sends them through the bridge, and
|
|
265
|
+
* deserializes incoming bytes back into Packets for the mesh
|
|
266
|
+
* router.
|
|
267
|
+
*
|
|
268
|
+
* When no bridge is registered, the transport reports itself
|
|
269
|
+
* as unavailable and all operations are safe no-ops.
|
|
270
|
+
*/
|
|
271
|
+
export class PlatformP2PTransport implements Transport {
|
|
272
|
+
readonly type = 'platform_p2p' as const;
|
|
273
|
+
|
|
274
|
+
private readonly serviceId: string;
|
|
275
|
+
private receiveCallback: ((packet: Packet, source: string) => void) | null = null;
|
|
276
|
+
private running = false;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @param namespace - Application namespace used to derive
|
|
280
|
+
* the P2P service identifier.
|
|
281
|
+
*/
|
|
282
|
+
constructor(namespace: string) {
|
|
283
|
+
this.serviceId = generateServiceId(namespace);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Returns the bridge currently in use. Reads from the
|
|
288
|
+
* module-level registry so that bridges registered after
|
|
289
|
+
* construction are picked up automatically.
|
|
290
|
+
*/
|
|
291
|
+
private get bridge(): PlatformP2PBridge {
|
|
292
|
+
return activeBridge;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ---- Transport interface ----
|
|
296
|
+
|
|
297
|
+
async isAvailable(): Promise<boolean> {
|
|
298
|
+
return this.bridge.isSupported();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async start(): Promise<void> {
|
|
302
|
+
if (this.running) return;
|
|
303
|
+
|
|
304
|
+
if (!this.bridge.isSupported()) {
|
|
305
|
+
// Silently skip — the transport negotiator will use
|
|
306
|
+
// the next bearer in priority order.
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Wire up data reception.
|
|
311
|
+
this.bridge.onDataReceived((peerId: string, data: Uint8Array) => {
|
|
312
|
+
if (!this.receiveCallback) return;
|
|
313
|
+
try {
|
|
314
|
+
const packet = deserializePacket(data);
|
|
315
|
+
this.receiveCallback(packet, peerId);
|
|
316
|
+
} catch {
|
|
317
|
+
// Malformed packet — drop silently. A production build
|
|
318
|
+
// may want to emit a diagnostic event here.
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
await this.bridge.startAdvertising(this.serviceId);
|
|
323
|
+
await this.bridge.startDiscovery(this.serviceId);
|
|
324
|
+
this.running = true;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async stop(): Promise<void> {
|
|
328
|
+
if (!this.running) return;
|
|
329
|
+
|
|
330
|
+
await this.bridge.stopAdvertising();
|
|
331
|
+
await this.bridge.stopDiscovery();
|
|
332
|
+
this.running = false;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async send(packet: Packet, destination: string): Promise<void> {
|
|
336
|
+
if (!this.running || !this.bridge.isSupported()) return;
|
|
337
|
+
|
|
338
|
+
const data = serializePacket(packet);
|
|
339
|
+
await this.bridge.sendData(destination, data);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
onReceive(callback: (packet: Packet, source: string) => void): void {
|
|
343
|
+
this.receiveCallback = callback;
|
|
344
|
+
}
|
|
345
|
+
}
|