@matter/react-native 0.11.0-alpha.0-20241005-e3e4e4a7a
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/LICENSE +201 -0
- package/README.md +56 -0
- package/dist/cjs/ble/BleReactNative.d.ts +17 -0
- package/dist/cjs/ble/BleReactNative.d.ts.map +1 -0
- package/dist/cjs/ble/BleReactNative.js +61 -0
- package/dist/cjs/ble/BleReactNative.js.map +6 -0
- package/dist/cjs/ble/BleScanner.d.ts +52 -0
- package/dist/cjs/ble/BleScanner.d.ts.map +1 -0
- package/dist/cjs/ble/BleScanner.js +239 -0
- package/dist/cjs/ble/BleScanner.js.map +6 -0
- package/dist/cjs/ble/ReactNativeBleChannel.d.ts +33 -0
- package/dist/cjs/ble/ReactNativeBleChannel.d.ts.map +1 -0
- package/dist/cjs/ble/ReactNativeBleChannel.js +252 -0
- package/dist/cjs/ble/ReactNativeBleChannel.js.map +6 -0
- package/dist/cjs/ble/ReactNativeBleClient.d.ts +25 -0
- package/dist/cjs/ble/ReactNativeBleClient.d.ts.map +1 -0
- package/dist/cjs/ble/ReactNativeBleClient.js +143 -0
- package/dist/cjs/ble/ReactNativeBleClient.js.map +6 -0
- package/dist/cjs/ble/export.d.ts +8 -0
- package/dist/cjs/ble/export.d.ts.map +1 -0
- package/dist/cjs/ble/export.js +25 -0
- package/dist/cjs/ble/export.js.map +6 -0
- package/dist/cjs/crypto/ReactNativeCrypto.d.ts +12 -0
- package/dist/cjs/crypto/ReactNativeCrypto.d.ts.map +1 -0
- package/dist/cjs/crypto/ReactNativeCrypto.js +95 -0
- package/dist/cjs/crypto/ReactNativeCrypto.js.map +6 -0
- package/dist/cjs/crypto/export.d.ts +7 -0
- package/dist/cjs/crypto/export.d.ts.map +1 -0
- package/dist/cjs/crypto/export.js +24 -0
- package/dist/cjs/crypto/export.js.map +6 -0
- package/dist/cjs/crypto/register.d.ts +7 -0
- package/dist/cjs/crypto/register.d.ts.map +1 -0
- package/dist/cjs/crypto/register.js +15 -0
- package/dist/cjs/crypto/register.js.map +6 -0
- package/dist/cjs/environment/ReactNativeEnvironment.d.ts +16 -0
- package/dist/cjs/environment/ReactNativeEnvironment.d.ts.map +1 -0
- package/dist/cjs/environment/ReactNativeEnvironment.js +60 -0
- package/dist/cjs/environment/ReactNativeEnvironment.js.map +6 -0
- package/dist/cjs/environment/export.d.ts +7 -0
- package/dist/cjs/environment/export.d.ts.map +1 -0
- package/dist/cjs/environment/export.js +24 -0
- package/dist/cjs/environment/export.js.map +6 -0
- package/dist/cjs/environment/register.d.ts +7 -0
- package/dist/cjs/environment/register.d.ts.map +1 -0
- package/dist/cjs/environment/register.js +12 -0
- package/dist/cjs/environment/register.js.map +6 -0
- package/dist/cjs/export.d.ts +9 -0
- package/dist/cjs/export.d.ts.map +1 -0
- package/dist/cjs/export.js +10 -0
- package/dist/cjs/export.js.map +6 -0
- package/dist/cjs/net/NetworkReactNative.d.ts +20 -0
- package/dist/cjs/net/NetworkReactNative.d.ts.map +1 -0
- package/dist/cjs/net/NetworkReactNative.js +173 -0
- package/dist/cjs/net/NetworkReactNative.js.map +6 -0
- package/dist/cjs/net/UdpChannelReactNative.d.ts +39 -0
- package/dist/cjs/net/UdpChannelReactNative.d.ts.map +1 -0
- package/dist/cjs/net/UdpChannelReactNative.js +184 -0
- package/dist/cjs/net/UdpChannelReactNative.js.map +6 -0
- package/dist/cjs/net/export.d.ts +8 -0
- package/dist/cjs/net/export.d.ts.map +1 -0
- package/dist/cjs/net/export.js +35 -0
- package/dist/cjs/net/export.js.map +6 -0
- package/dist/cjs/net/register.d.ts +7 -0
- package/dist/cjs/net/register.d.ts.map +1 -0
- package/dist/cjs/net/register.js +15 -0
- package/dist/cjs/net/register.js.map +6 -0
- package/dist/cjs/package.json +10 -0
- package/dist/cjs/storage/StorageBackendAsyncStorage.d.ts +31 -0
- package/dist/cjs/storage/StorageBackendAsyncStorage.d.ts.map +1 -0
- package/dist/cjs/storage/StorageBackendAsyncStorage.js +142 -0
- package/dist/cjs/storage/StorageBackendAsyncStorage.js.map +6 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -0
- package/dist/esm/ble/BleReactNative.d.ts +17 -0
- package/dist/esm/ble/BleReactNative.d.ts.map +1 -0
- package/dist/esm/ble/BleReactNative.js +41 -0
- package/dist/esm/ble/BleReactNative.js.map +6 -0
- package/dist/esm/ble/BleScanner.d.ts +52 -0
- package/dist/esm/ble/BleScanner.d.ts.map +1 -0
- package/dist/esm/ble/BleScanner.js +219 -0
- package/dist/esm/ble/BleScanner.js.map +6 -0
- package/dist/esm/ble/ReactNativeBleChannel.d.ts +33 -0
- package/dist/esm/ble/ReactNativeBleChannel.d.ts.map +1 -0
- package/dist/esm/ble/ReactNativeBleChannel.js +257 -0
- package/dist/esm/ble/ReactNativeBleChannel.js.map +6 -0
- package/dist/esm/ble/ReactNativeBleClient.d.ts +25 -0
- package/dist/esm/ble/ReactNativeBleClient.d.ts.map +1 -0
- package/dist/esm/ble/ReactNativeBleClient.js +123 -0
- package/dist/esm/ble/ReactNativeBleClient.js.map +6 -0
- package/dist/esm/ble/export.d.ts +8 -0
- package/dist/esm/ble/export.d.ts.map +1 -0
- package/dist/esm/ble/export.js +8 -0
- package/dist/esm/ble/export.js.map +6 -0
- package/dist/esm/crypto/ReactNativeCrypto.d.ts +12 -0
- package/dist/esm/crypto/ReactNativeCrypto.d.ts.map +1 -0
- package/dist/esm/crypto/ReactNativeCrypto.js +65 -0
- package/dist/esm/crypto/ReactNativeCrypto.js.map +6 -0
- package/dist/esm/crypto/export.d.ts +7 -0
- package/dist/esm/crypto/export.d.ts.map +1 -0
- package/dist/esm/crypto/export.js +7 -0
- package/dist/esm/crypto/export.js.map +6 -0
- package/dist/esm/crypto/register.d.ts +7 -0
- package/dist/esm/crypto/register.d.ts.map +1 -0
- package/dist/esm/crypto/register.js +14 -0
- package/dist/esm/crypto/register.js.map +6 -0
- package/dist/esm/environment/ReactNativeEnvironment.d.ts +16 -0
- package/dist/esm/environment/ReactNativeEnvironment.d.ts.map +1 -0
- package/dist/esm/environment/ReactNativeEnvironment.js +40 -0
- package/dist/esm/environment/ReactNativeEnvironment.js.map +6 -0
- package/dist/esm/environment/export.d.ts +7 -0
- package/dist/esm/environment/export.d.ts.map +1 -0
- package/dist/esm/environment/export.js +7 -0
- package/dist/esm/environment/export.js.map +6 -0
- package/dist/esm/environment/register.d.ts +7 -0
- package/dist/esm/environment/register.d.ts.map +1 -0
- package/dist/esm/environment/register.js +11 -0
- package/dist/esm/environment/register.js.map +6 -0
- package/dist/esm/export.d.ts +9 -0
- package/dist/esm/export.d.ts.map +1 -0
- package/dist/esm/export.js +9 -0
- package/dist/esm/export.js.map +6 -0
- package/dist/esm/net/NetworkReactNative.d.ts +20 -0
- package/dist/esm/net/NetworkReactNative.d.ts.map +1 -0
- package/dist/esm/net/NetworkReactNative.js +151 -0
- package/dist/esm/net/NetworkReactNative.js.map +6 -0
- package/dist/esm/net/UdpChannelReactNative.d.ts +39 -0
- package/dist/esm/net/UdpChannelReactNative.d.ts.map +1 -0
- package/dist/esm/net/UdpChannelReactNative.js +162 -0
- package/dist/esm/net/UdpChannelReactNative.js.map +6 -0
- package/dist/esm/net/export.d.ts +8 -0
- package/dist/esm/net/export.d.ts.map +1 -0
- package/dist/esm/net/export.js +14 -0
- package/dist/esm/net/export.js.map +6 -0
- package/dist/esm/net/register.d.ts +7 -0
- package/dist/esm/net/register.d.ts.map +1 -0
- package/dist/esm/net/register.js +14 -0
- package/dist/esm/net/register.js.map +6 -0
- package/dist/esm/package.json +10 -0
- package/dist/esm/storage/StorageBackendAsyncStorage.d.ts +31 -0
- package/dist/esm/storage/StorageBackendAsyncStorage.d.ts.map +1 -0
- package/dist/esm/storage/StorageBackendAsyncStorage.js +112 -0
- package/dist/esm/storage/StorageBackendAsyncStorage.js.map +6 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -0
- package/package.json +105 -0
- package/src/ble/BleReactNative.ts +45 -0
- package/src/ble/BleScanner.ts +277 -0
- package/src/ble/ReactNativeBleChannel.ts +313 -0
- package/src/ble/ReactNativeBleClient.ts +132 -0
- package/src/ble/export.ts +8 -0
- package/src/crypto/ReactNativeCrypto.ts +108 -0
- package/src/crypto/export.ts +7 -0
- package/src/crypto/register.ts +16 -0
- package/src/environment/ReactNativeEnvironment.ts +53 -0
- package/src/environment/export.ts +7 -0
- package/src/environment/register.ts +16 -0
- package/src/export.ts +11 -0
- package/src/net/NetworkReactNative.ts +190 -0
- package/src/net/UdpChannelReactNative.ts +219 -0
- package/src/net/export.ts +13 -0
- package/src/net/register.ts +17 -0
- package/src/storage/StorageBackendAsyncStorage.ts +145 -0
- package/src/tsconfig.json +17 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import dgram from "react-native-udp";
|
|
7
|
+
|
|
8
|
+
// @ts-expect-error globalThis is no index structure
|
|
9
|
+
global.dgram = dgram;
|
|
10
|
+
|
|
11
|
+
// @ts-expect-error globalThis is no index structure
|
|
12
|
+
const rnDGramCreateSocket = dgram.createSocket;
|
|
13
|
+
// @ts-expect-error globalThis is no index structure
|
|
14
|
+
// Work around because React-Native UDP lib is not providing the setMulticastInterface method
|
|
15
|
+
dgram.createSocket = (...args: any[]) => {
|
|
16
|
+
const socket = rnDGramCreateSocket(...args);
|
|
17
|
+
socket.setMulticastInterface = () => {}; // Stub for now
|
|
18
|
+
|
|
19
|
+
const originalSend = socket.send;
|
|
20
|
+
socket.send = (
|
|
21
|
+
buffer: Uint8Array,
|
|
22
|
+
port: number,
|
|
23
|
+
address: string,
|
|
24
|
+
callback: (error: Error | null, bytes: number) => void,
|
|
25
|
+
) => originalSend(buffer, 0, buffer.length, port, address, callback);
|
|
26
|
+
return socket;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
AsyncCache,
|
|
31
|
+
InterfaceType,
|
|
32
|
+
isIPv6,
|
|
33
|
+
Logger,
|
|
34
|
+
Network,
|
|
35
|
+
NetworkError,
|
|
36
|
+
NetworkInterface,
|
|
37
|
+
NetworkInterfaceDetails,
|
|
38
|
+
onSameNetwork,
|
|
39
|
+
UdpChannel,
|
|
40
|
+
UdpChannelOptions,
|
|
41
|
+
} from "#general";
|
|
42
|
+
import { fetch as fetchNetworkInfo } from "@react-native-community/netinfo";
|
|
43
|
+
import { UdpChannelReactNative } from "./UdpChannelReactNative.js";
|
|
44
|
+
|
|
45
|
+
const logger = Logger.get("NetworkNode");
|
|
46
|
+
|
|
47
|
+
type NetworkInterfaceInfo = {
|
|
48
|
+
address: string;
|
|
49
|
+
family: string;
|
|
50
|
+
netmask: string;
|
|
51
|
+
internal: boolean;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
async function networkInterfaces() {
|
|
55
|
+
const netInfo = [await fetchNetworkInfo("wifi"), await fetchNetworkInfo("ethernet")];
|
|
56
|
+
const networkInterfaces: Record<string, NetworkInterfaceInfo[]> = {};
|
|
57
|
+
netInfo.forEach(({ type, isConnected, details }) => {
|
|
58
|
+
if (!details || !isConnected) return;
|
|
59
|
+
const ipAddress = "ipAddress" in details ? (details.ipAddress as string | null) : undefined;
|
|
60
|
+
const subnet = "subnet" in details ? (details.subnet as string | null) : undefined;
|
|
61
|
+
if (!ipAddress || !subnet) return;
|
|
62
|
+
networkInterfaces[type] = [
|
|
63
|
+
{
|
|
64
|
+
address: ipAddress,
|
|
65
|
+
family: isIPv6(ipAddress) ? "IPv6" : "IPv4",
|
|
66
|
+
netmask: subnet,
|
|
67
|
+
internal: false,
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
});
|
|
71
|
+
return networkInterfaces;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class NetworkReactNative extends Network {
|
|
75
|
+
static async getMulticastInterfaceIpv4(netInterface: string): Promise<string | undefined> {
|
|
76
|
+
const netInterfaceInfo = (await networkInterfaces())[netInterface];
|
|
77
|
+
if (netInterfaceInfo === undefined) throw new NetworkError(`Unknown interface: ${netInterface}`);
|
|
78
|
+
for (const { address, family } of netInterfaceInfo) {
|
|
79
|
+
if (family === "IPv4") {
|
|
80
|
+
return address;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static async getMembershipMulticastInterfaces(
|
|
87
|
+
netInterface: string | undefined,
|
|
88
|
+
ipv4: boolean,
|
|
89
|
+
): Promise<(string | undefined)[]> {
|
|
90
|
+
if (ipv4) {
|
|
91
|
+
return [undefined];
|
|
92
|
+
} else {
|
|
93
|
+
let networkInterfaceEntries = Object.entries(await networkInterfaces());
|
|
94
|
+
if (netInterface !== undefined) {
|
|
95
|
+
networkInterfaceEntries = networkInterfaceEntries.filter(([name]) => name === netInterface);
|
|
96
|
+
}
|
|
97
|
+
const multicastInterfaces = networkInterfaceEntries.flatMap(([netInterface, netInterfaceInfo]) => {
|
|
98
|
+
if (netInterfaceInfo === undefined) return [];
|
|
99
|
+
const zone = netInterface;
|
|
100
|
+
return zone === undefined ? [] : [`::%${zone}`];
|
|
101
|
+
});
|
|
102
|
+
if (multicastInterfaces.length === 0) {
|
|
103
|
+
logger.warn(
|
|
104
|
+
`No IPv6 multicast interface found${
|
|
105
|
+
netInterface !== undefined ? ` for interface ${netInterface}` : ""
|
|
106
|
+
}.`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return multicastInterfaces;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static getNetInterfaceForIp(ip: string) {
|
|
114
|
+
// Finding the local interface on the same interface is complex and won't change
|
|
115
|
+
// So let's cache the results for 5mn
|
|
116
|
+
return this.netInterfaces.get(ip);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private static readonly netInterfaces = new AsyncCache<string | undefined>(
|
|
120
|
+
"Network interface",
|
|
121
|
+
(ip: string) => this.getNetInterfaceForRemoveAddress(ip),
|
|
122
|
+
5 * 60 * 1000 /* 5mn */,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
override async close() {
|
|
126
|
+
await NetworkReactNative.netInterfaces.close();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private static async getNetInterfaceForRemoveAddress(ip: string) {
|
|
130
|
+
if (ip.includes("%")) {
|
|
131
|
+
// IPv6 address with scope
|
|
132
|
+
return ip.split("%")[1];
|
|
133
|
+
} else {
|
|
134
|
+
const interfaces = await networkInterfaces();
|
|
135
|
+
for (const name in interfaces) {
|
|
136
|
+
const netInterfaces = interfaces[name];
|
|
137
|
+
for (const { address, netmask } of netInterfaces) {
|
|
138
|
+
if (onSameNetwork(ip, address, netmask)) {
|
|
139
|
+
return name;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (isIPv6(ip)) {
|
|
144
|
+
if (ip.startsWith("fd")) {
|
|
145
|
+
// IPv6 address is an ULA
|
|
146
|
+
return ""; // consider it as being ok and using the "Default interface"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get all network interfaces.
|
|
155
|
+
* The optional configuration parameter allows to map interface names to types if this mapping is known.
|
|
156
|
+
* Each network interface which has no mapped type is returned as Ethernet for now.
|
|
157
|
+
*
|
|
158
|
+
* @param configuration - An array of objects with the name and type properties.
|
|
159
|
+
*/
|
|
160
|
+
async getNetInterfaces(configuration: NetworkInterface[] = []): Promise<NetworkInterface[]> {
|
|
161
|
+
const result = new Array<NetworkInterface>();
|
|
162
|
+
const interfaces = await networkInterfaces();
|
|
163
|
+
for (const name in interfaces) {
|
|
164
|
+
const netInterfaces = interfaces[name];
|
|
165
|
+
if (netInterfaces.length === 0) continue;
|
|
166
|
+
if (netInterfaces[0].internal) continue;
|
|
167
|
+
let type = InterfaceType.Ethernet;
|
|
168
|
+
if (configuration.length > 0) {
|
|
169
|
+
const nameType = configuration.find(({ name: mapName }) => name === mapName);
|
|
170
|
+
if (nameType !== undefined && nameType.type !== undefined) {
|
|
171
|
+
type = nameType.type;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
result.push({ name, type });
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async getIpMac(netInterface: string): Promise<NetworkInterfaceDetails | undefined> {
|
|
180
|
+
const netInterfaceInfo = (await networkInterfaces())[netInterface];
|
|
181
|
+
if (netInterfaceInfo === undefined) return undefined;
|
|
182
|
+
const ipV4 = netInterfaceInfo.filter(({ family }) => family === "IPv4").map(({ address }) => address);
|
|
183
|
+
const ipV6 = netInterfaceInfo.filter(({ family }) => family === "IPv6").map(({ address }) => address);
|
|
184
|
+
return { mac: "00:00:00:00:00:00", ipV4, ipV6 };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
override createUdpChannel(options: UdpChannelOptions): Promise<UdpChannel> {
|
|
188
|
+
return UdpChannelReactNative.create(options);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import dgram from "react-native-udp";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
ChannelType,
|
|
10
|
+
Diagnostic,
|
|
11
|
+
isIPv4,
|
|
12
|
+
isIPv6,
|
|
13
|
+
Logger,
|
|
14
|
+
MAX_UDP_MESSAGE_SIZE,
|
|
15
|
+
NetworkError,
|
|
16
|
+
UdpChannel,
|
|
17
|
+
UdpChannelOptions,
|
|
18
|
+
} from "#general";
|
|
19
|
+
import { NetworkReactNative } from "./NetworkReactNative.js";
|
|
20
|
+
|
|
21
|
+
const logger = Logger.get("UdpChannelNode");
|
|
22
|
+
|
|
23
|
+
// Move out some dram types not available in react-native-udp
|
|
24
|
+
// TODO find a way to clean that up once anything is working
|
|
25
|
+
interface RemoteInfo {
|
|
26
|
+
address: string;
|
|
27
|
+
family: "IPv4" | "IPv6";
|
|
28
|
+
port: number;
|
|
29
|
+
size: number;
|
|
30
|
+
}
|
|
31
|
+
type SocketType = "udp4" | "udp6";
|
|
32
|
+
interface SocketOptions {
|
|
33
|
+
type: SocketType;
|
|
34
|
+
reuseAddr?: boolean | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
ipv6Only?: boolean | undefined;
|
|
39
|
+
recvBufferSize?: number | undefined;
|
|
40
|
+
sendBufferSize?: number | undefined;
|
|
41
|
+
lookup?:
|
|
42
|
+
| ((
|
|
43
|
+
hostname: string,
|
|
44
|
+
options: any,
|
|
45
|
+
callback: (err: Error | null, address: string, family: number) => void,
|
|
46
|
+
) => void)
|
|
47
|
+
| undefined;
|
|
48
|
+
}
|
|
49
|
+
interface Socket {
|
|
50
|
+
setBroadcast(flag: boolean): void;
|
|
51
|
+
setMulticastInterface(interfaceAddress: string): void;
|
|
52
|
+
addMembership(multicastAddress: string, multicastInterface?: string): void;
|
|
53
|
+
on(event: "message", listener: (msg: Uint8Array, rinfo: RemoteInfo) => void): void;
|
|
54
|
+
on(event: "error", listener: (error: Error) => void): void;
|
|
55
|
+
removeListener(event: "message", listener: (msg: Uint8Array, rinfo: RemoteInfo) => void): void;
|
|
56
|
+
removeListener(event: "error", listener: (error: Error) => void): void;
|
|
57
|
+
send(msg: Uint8Array, port: number, address: string, callback: (error: Error | null) => void): void;
|
|
58
|
+
close(): void;
|
|
59
|
+
address(): { address: string; port: number };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createDgramSocket(host: string | undefined, port: number | undefined, options: SocketOptions) {
|
|
63
|
+
// @ts-expect-error default types are strange
|
|
64
|
+
const socket = dgram.createSocket({
|
|
65
|
+
...options,
|
|
66
|
+
reusePort: options.reuseAddr,
|
|
67
|
+
});
|
|
68
|
+
return new Promise<Socket>((resolve, reject) => {
|
|
69
|
+
const handleBindError = (error: Error) => {
|
|
70
|
+
try {
|
|
71
|
+
socket.close();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
logger.debug("Error on closing socket", error);
|
|
74
|
+
}
|
|
75
|
+
reject(error);
|
|
76
|
+
};
|
|
77
|
+
socket.on("error", handleBindError);
|
|
78
|
+
socket.bind(port, host, (error: any) => {
|
|
79
|
+
if (error) return;
|
|
80
|
+
const { address: localHost, port: localPort } = socket.address();
|
|
81
|
+
logger.debug(
|
|
82
|
+
"Socket created and bound ",
|
|
83
|
+
Diagnostic.dict({
|
|
84
|
+
remoteAddress: `${host}:${port}`,
|
|
85
|
+
localAddress: `${localHost}:${localPort}`,
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
socket.removeListener("error", handleBindError);
|
|
89
|
+
socket.on("error", (error: Error) => logger.error(error));
|
|
90
|
+
resolve(socket);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export class UdpChannelReactNative implements UdpChannel {
|
|
96
|
+
static async create({
|
|
97
|
+
listeningPort,
|
|
98
|
+
type,
|
|
99
|
+
listeningAddress,
|
|
100
|
+
netInterface,
|
|
101
|
+
membershipAddresses,
|
|
102
|
+
}: UdpChannelOptions) {
|
|
103
|
+
const socketOptions: SocketOptions = { type, reuseAddr: true };
|
|
104
|
+
if (type === "udp6") {
|
|
105
|
+
socketOptions.ipv6Only = true;
|
|
106
|
+
}
|
|
107
|
+
const socket = await createDgramSocket(listeningAddress, listeningPort, socketOptions);
|
|
108
|
+
socket.setBroadcast(true);
|
|
109
|
+
let netInterfaceZone: string | undefined;
|
|
110
|
+
if (netInterface !== undefined) {
|
|
111
|
+
netInterfaceZone = netInterface;
|
|
112
|
+
let multicastInterface: string | undefined;
|
|
113
|
+
if (type === "udp4") {
|
|
114
|
+
multicastInterface = await NetworkReactNative.getMulticastInterfaceIpv4(netInterface);
|
|
115
|
+
if (multicastInterface === undefined) {
|
|
116
|
+
throw new NetworkError(`No IPv4 addresses on interface: ${netInterface}`);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
multicastInterface = `::%${netInterfaceZone}`;
|
|
120
|
+
}
|
|
121
|
+
logger.debug(
|
|
122
|
+
"Initialize multicast",
|
|
123
|
+
Diagnostic.dict({
|
|
124
|
+
address: `${multicastInterface}:${listeningPort}`,
|
|
125
|
+
interface: netInterface,
|
|
126
|
+
type: type,
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
socket.setMulticastInterface(multicastInterface);
|
|
130
|
+
}
|
|
131
|
+
if (membershipAddresses !== undefined) {
|
|
132
|
+
const multicastInterfaces = await NetworkReactNative.getMembershipMulticastInterfaces(
|
|
133
|
+
netInterface,
|
|
134
|
+
type === "udp4",
|
|
135
|
+
);
|
|
136
|
+
for (const address of membershipAddresses) {
|
|
137
|
+
for (const multicastInterface of multicastInterfaces) {
|
|
138
|
+
try {
|
|
139
|
+
socket.addMembership(address, multicastInterface);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.warn(
|
|
142
|
+
`Error adding membership for address ${address}${
|
|
143
|
+
multicastInterface ? ` with interface ${multicastInterface}` : ""
|
|
144
|
+
}: ${error}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return new UdpChannelReactNative(type, socket, netInterfaceZone);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
readonly maxPayloadSize = MAX_UDP_MESSAGE_SIZE;
|
|
154
|
+
|
|
155
|
+
constructor(
|
|
156
|
+
private readonly type: "udp4" | "udp6",
|
|
157
|
+
private readonly socket: Socket,
|
|
158
|
+
private readonly netInterface?: string,
|
|
159
|
+
) {}
|
|
160
|
+
|
|
161
|
+
onData(listener: (netInterface: string, peerAddress: string, peerPort: number, data: Uint8Array) => void) {
|
|
162
|
+
const messageListener = async (data: Uint8Array, { address, port }: RemoteInfo) => {
|
|
163
|
+
const netInterface = this.netInterface ?? (await NetworkReactNative.getNetInterfaceForIp(address));
|
|
164
|
+
if (netInterface === undefined) return;
|
|
165
|
+
listener(netInterface, address, port, data);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
169
|
+
this.socket.on("message", messageListener);
|
|
170
|
+
return {
|
|
171
|
+
close: async () => {
|
|
172
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
173
|
+
this.socket.removeListener("message", messageListener);
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async send(host: string, port: number, data: Uint8Array) {
|
|
179
|
+
return new Promise<void>((resolve, reject) => {
|
|
180
|
+
this.socket.send(data, port, host, error => {
|
|
181
|
+
if (error !== null) {
|
|
182
|
+
const netError = new NetworkError(error.message);
|
|
183
|
+
netError.stack = error.stack;
|
|
184
|
+
reject(netError);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
resolve();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async close() {
|
|
193
|
+
try {
|
|
194
|
+
this.socket.close();
|
|
195
|
+
} catch (error) {
|
|
196
|
+
logger.debug("Error on closing socket", error);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
get port() {
|
|
201
|
+
return this.socket.address().port;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
supports(type: ChannelType, address?: string) {
|
|
205
|
+
if (type !== ChannelType.UDP) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (address === undefined) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (this.type === "udp4") {
|
|
214
|
+
return isIPv4(address);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return isIPv6(address);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Network } from "@matter/general";
|
|
8
|
+
|
|
9
|
+
export * from "./NetworkReactNative.js";
|
|
10
|
+
|
|
11
|
+
export async function closeNetwork() {
|
|
12
|
+
return Network.get().close();
|
|
13
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Network, NoProviderError, singleton } from "@matter/general";
|
|
8
|
+
import { NetworkReactNative } from "./NetworkReactNative.js";
|
|
9
|
+
|
|
10
|
+
// Check if Network singleton is already registered and auto register if not
|
|
11
|
+
try {
|
|
12
|
+
Network.get();
|
|
13
|
+
} catch (error) {
|
|
14
|
+
NoProviderError.accept(error);
|
|
15
|
+
Network.get = singleton(() => new NetworkReactNative());
|
|
16
|
+
// use net export closeNetwork() to close the network
|
|
17
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { MaybeAsyncStorage, StorageError, SupportedStorageTypes, fromJson, toJson } from "#general";
|
|
8
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
9
|
+
|
|
10
|
+
export class StorageBackendAsyncStorage extends MaybeAsyncStorage {
|
|
11
|
+
#namespace: string;
|
|
12
|
+
protected isInitialized = false;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new instance of the AsyncStorage storage backend. In a "namespace" is provided then the keys will be
|
|
16
|
+
* prefixed with the namespace (separated with a # which is normally not used in matter.js keys).
|
|
17
|
+
*/
|
|
18
|
+
constructor(namespace?: string) {
|
|
19
|
+
super();
|
|
20
|
+
this.#namespace = namespace ?? "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get initialized() {
|
|
24
|
+
return this.isInitialized;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
initialize() {
|
|
28
|
+
this.isInitialized = true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
close() {
|
|
32
|
+
this.isInitialized = false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
clear() {
|
|
36
|
+
// @ts-expect-error AsyncStorage types are not correct
|
|
37
|
+
return AsyncStorage.clear();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getContextBaseKey(contexts: string[], allowEmptyContext = false) {
|
|
41
|
+
const contextKey = contexts.join(".");
|
|
42
|
+
if (
|
|
43
|
+
(!contextKey.length && !allowEmptyContext) ||
|
|
44
|
+
contextKey.includes("..") ||
|
|
45
|
+
contextKey.startsWith(".") ||
|
|
46
|
+
contextKey.endsWith(".")
|
|
47
|
+
)
|
|
48
|
+
throw new StorageError("Context must not be an empty and not contain dots.");
|
|
49
|
+
return `${this.#namespace.length ? `${this.#namespace}#` : ""}${contextKey}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
buildStorageKey(contexts: string[], key: string) {
|
|
53
|
+
if (!key.length) {
|
|
54
|
+
throw new StorageError("Key must not be an empty string.");
|
|
55
|
+
}
|
|
56
|
+
const contextKey = this.getContextBaseKey(contexts);
|
|
57
|
+
return `${contextKey}.${key}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async get<T extends SupportedStorageTypes>(contexts: string[], key: string): Promise<T | undefined> {
|
|
61
|
+
// @ts-expect-error AsyncStorage types are not correct
|
|
62
|
+
const value = await AsyncStorage.getItem(this.buildStorageKey(contexts, key));
|
|
63
|
+
if (value === null) return undefined;
|
|
64
|
+
return fromJson(value) as T;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
set(contexts: string[], key: string, value: SupportedStorageTypes): Promise<void>;
|
|
68
|
+
set(contexts: string[], values: Record<string, SupportedStorageTypes>): Promise<void>;
|
|
69
|
+
async set(
|
|
70
|
+
contexts: string[],
|
|
71
|
+
keyOrValues: string | Record<string, SupportedStorageTypes>,
|
|
72
|
+
value?: SupportedStorageTypes,
|
|
73
|
+
) {
|
|
74
|
+
if (typeof keyOrValues === "string") {
|
|
75
|
+
// @ts-expect-error AsyncStorage types are not correct
|
|
76
|
+
await AsyncStorage.setItem(this.buildStorageKey(contexts, keyOrValues), toJson(value));
|
|
77
|
+
} else {
|
|
78
|
+
for (const [key, value] of Object.entries(keyOrValues)) {
|
|
79
|
+
// @ts-expect-error AsyncStorage types are not correct
|
|
80
|
+
await AsyncStorage.setItem(this.buildStorageKey(contexts, key), toJson(value));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
delete(contexts: string[], key: string) {
|
|
86
|
+
// @ts-expect-error AsyncStorage types are not correct
|
|
87
|
+
return AsyncStorage.removeItem(this.buildStorageKey(contexts, key));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Returns all keys of a storage context without keys of sub-contexts */
|
|
91
|
+
async keys(contexts: string[]) {
|
|
92
|
+
const contextKey = this.getContextBaseKey(contexts);
|
|
93
|
+
const keys = [];
|
|
94
|
+
const contextKeyStart = `${contextKey}.`;
|
|
95
|
+
// @ts-expect-error AsyncStorage types are not correct
|
|
96
|
+
const allKeys = await AsyncStorage.getAllKeys();
|
|
97
|
+
for (const key of allKeys) {
|
|
98
|
+
if (key.startsWith(contextKeyStart) && !key.includes(".", contextKeyStart.length)) {
|
|
99
|
+
keys.push(key.substring(contextKeyStart.length));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return keys;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async values(contexts: string[]) {
|
|
106
|
+
// Initialize and context checks are done by keys method
|
|
107
|
+
const values = {} as Record<string, SupportedStorageTypes>;
|
|
108
|
+
for (const key of await this.keys(contexts)) {
|
|
109
|
+
values[key] = await this.get(contexts, key);
|
|
110
|
+
}
|
|
111
|
+
return values;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async contexts(contexts: string[]) {
|
|
115
|
+
const contextKey = this.getContextBaseKey(contexts, true);
|
|
116
|
+
const startContextKey = contextKey.length ? `${contextKey}.` : "";
|
|
117
|
+
const foundContexts = new Array<string>();
|
|
118
|
+
// @ts-expect-error AsyncStorage types are not correct
|
|
119
|
+
const allKeys = await AsyncStorage.getAllKeys();
|
|
120
|
+
for (const key of allKeys) {
|
|
121
|
+
if (key.startsWith(startContextKey)) {
|
|
122
|
+
const subKeys = key.substring(startContextKey.length).split(".");
|
|
123
|
+
if (subKeys.length === 1) continue; // found leaf key
|
|
124
|
+
const context = subKeys[0];
|
|
125
|
+
if (!foundContexts.includes(context)) {
|
|
126
|
+
foundContexts.push(context);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return foundContexts;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async clearAll(contexts: string[]) {
|
|
134
|
+
const contextKey = this.getContextBaseKey(contexts, true);
|
|
135
|
+
const startContextKey = contextKey.length ? `${contextKey}.` : "";
|
|
136
|
+
// @ts-expect-error AsyncStorage types are not correct
|
|
137
|
+
const allKeys = await AsyncStorage.getAllKeys();
|
|
138
|
+
for (const key of allKeys) {
|
|
139
|
+
if (key.startsWith(startContextKey)) {
|
|
140
|
+
// @ts-expect-error AsyncStorage types are not correct
|
|
141
|
+
await AsyncStorage.removeItem(key);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|