@matter/nodejs 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.
Files changed (197) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1 -0
  3. package/dist/cjs/behavior/index.d.ts +7 -0
  4. package/dist/cjs/behavior/index.d.ts.map +1 -0
  5. package/dist/cjs/behavior/index.js +8 -0
  6. package/dist/cjs/behavior/index.js.map +6 -0
  7. package/dist/cjs/behavior/instrumentation.d.ts +11 -0
  8. package/dist/cjs/behavior/instrumentation.d.ts.map +1 -0
  9. package/dist/cjs/behavior/instrumentation.js +47 -0
  10. package/dist/cjs/behavior/instrumentation.js.map +6 -0
  11. package/dist/cjs/behavior/register.d.ts +7 -0
  12. package/dist/cjs/behavior/register.d.ts.map +1 -0
  13. package/dist/cjs/behavior/register.js +9 -0
  14. package/dist/cjs/behavior/register.js.map +6 -0
  15. package/dist/cjs/crypto/NodeJsCrypto.d.ts +29 -0
  16. package/dist/cjs/crypto/NodeJsCrypto.d.ts.map +1 -0
  17. package/dist/cjs/crypto/NodeJsCrypto.js +154 -0
  18. package/dist/cjs/crypto/NodeJsCrypto.js.map +6 -0
  19. package/dist/cjs/crypto/index.d.ts +8 -0
  20. package/dist/cjs/crypto/index.d.ts.map +1 -0
  21. package/dist/cjs/crypto/index.js +25 -0
  22. package/dist/cjs/crypto/index.js.map +6 -0
  23. package/dist/cjs/crypto/register.d.ts +7 -0
  24. package/dist/cjs/crypto/register.d.ts.map +1 -0
  25. package/dist/cjs/crypto/register.js +21 -0
  26. package/dist/cjs/crypto/register.js.map +6 -0
  27. package/dist/cjs/environment/NodeJsActionTracer.d.ts +15 -0
  28. package/dist/cjs/environment/NodeJsActionTracer.d.ts.map +1 -0
  29. package/dist/cjs/environment/NodeJsActionTracer.js +80 -0
  30. package/dist/cjs/environment/NodeJsActionTracer.js.map +6 -0
  31. package/dist/cjs/environment/NodeJsEnvironment.d.ts +55 -0
  32. package/dist/cjs/environment/NodeJsEnvironment.d.ts.map +1 -0
  33. package/dist/cjs/environment/NodeJsEnvironment.js +121 -0
  34. package/dist/cjs/environment/NodeJsEnvironment.js.map +6 -0
  35. package/dist/cjs/environment/ProcessManager.d.ts +45 -0
  36. package/dist/cjs/environment/ProcessManager.d.ts.map +1 -0
  37. package/dist/cjs/environment/ProcessManager.js +112 -0
  38. package/dist/cjs/environment/ProcessManager.js.map +6 -0
  39. package/dist/cjs/environment/index.d.ts +10 -0
  40. package/dist/cjs/environment/index.d.ts.map +1 -0
  41. package/dist/cjs/environment/index.js +27 -0
  42. package/dist/cjs/environment/index.js.map +6 -0
  43. package/dist/cjs/environment/register.d.ts +7 -0
  44. package/dist/cjs/environment/register.d.ts.map +1 -0
  45. package/dist/cjs/environment/register.js +10 -0
  46. package/dist/cjs/environment/register.js.map +6 -0
  47. package/dist/cjs/index.d.ts +12 -0
  48. package/dist/cjs/index.d.ts.map +1 -0
  49. package/dist/cjs/index.js +29 -0
  50. package/dist/cjs/index.js.map +6 -0
  51. package/dist/cjs/log/FileLogger.d.ts +13 -0
  52. package/dist/cjs/log/FileLogger.d.ts.map +1 -0
  53. package/dist/cjs/log/FileLogger.js +46 -0
  54. package/dist/cjs/log/FileLogger.js.map +6 -0
  55. package/dist/cjs/log/index.d.ts +7 -0
  56. package/dist/cjs/log/index.d.ts.map +1 -0
  57. package/dist/cjs/log/index.js +24 -0
  58. package/dist/cjs/log/index.js.map +6 -0
  59. package/dist/cjs/net/NodeJsNetwork.d.ts +27 -0
  60. package/dist/cjs/net/NodeJsNetwork.d.ts.map +1 -0
  61. package/dist/cjs/net/NodeJsNetwork.js +145 -0
  62. package/dist/cjs/net/NodeJsNetwork.js.map +6 -0
  63. package/dist/cjs/net/NodeJsUdpChannel.d.ts +23 -0
  64. package/dist/cjs/net/NodeJsUdpChannel.d.ts.map +1 -0
  65. package/dist/cjs/net/NodeJsUdpChannel.js +181 -0
  66. package/dist/cjs/net/NodeJsUdpChannel.js.map +6 -0
  67. package/dist/cjs/net/index.d.ts +9 -0
  68. package/dist/cjs/net/index.d.ts.map +1 -0
  69. package/dist/cjs/net/index.js +26 -0
  70. package/dist/cjs/net/index.js.map +6 -0
  71. package/dist/cjs/net/register.d.ts +7 -0
  72. package/dist/cjs/net/register.d.ts.map +1 -0
  73. package/dist/cjs/net/register.js +16 -0
  74. package/dist/cjs/net/register.js.map +6 -0
  75. package/dist/cjs/package.json +11 -0
  76. package/dist/cjs/storage/StorageBackendDisk.d.ts +27 -0
  77. package/dist/cjs/storage/StorageBackendDisk.d.ts.map +1 -0
  78. package/dist/cjs/storage/StorageBackendDisk.js +128 -0
  79. package/dist/cjs/storage/StorageBackendDisk.js.map +6 -0
  80. package/dist/cjs/storage/StorageBackendJsonFile.d.ts +27 -0
  81. package/dist/cjs/storage/StorageBackendJsonFile.d.ts.map +1 -0
  82. package/dist/cjs/storage/StorageBackendJsonFile.js +110 -0
  83. package/dist/cjs/storage/StorageBackendJsonFile.js.map +6 -0
  84. package/dist/cjs/storage/index.d.ts +8 -0
  85. package/dist/cjs/storage/index.d.ts.map +1 -0
  86. package/dist/cjs/storage/index.js +25 -0
  87. package/dist/cjs/storage/index.js.map +6 -0
  88. package/dist/cjs/tsconfig.tsbuildinfo +1 -0
  89. package/dist/esm/behavior/index.d.ts +7 -0
  90. package/dist/esm/behavior/index.d.ts.map +1 -0
  91. package/dist/esm/behavior/index.js +7 -0
  92. package/dist/esm/behavior/index.js.map +6 -0
  93. package/dist/esm/behavior/instrumentation.d.ts +11 -0
  94. package/dist/esm/behavior/instrumentation.d.ts.map +1 -0
  95. package/dist/esm/behavior/instrumentation.js +27 -0
  96. package/dist/esm/behavior/instrumentation.js.map +6 -0
  97. package/dist/esm/behavior/register.d.ts +7 -0
  98. package/dist/esm/behavior/register.d.ts.map +1 -0
  99. package/dist/esm/behavior/register.js +8 -0
  100. package/dist/esm/behavior/register.js.map +6 -0
  101. package/dist/esm/crypto/NodeJsCrypto.d.ts +29 -0
  102. package/dist/esm/crypto/NodeJsCrypto.d.ts.map +1 -0
  103. package/dist/esm/crypto/NodeJsCrypto.js +135 -0
  104. package/dist/esm/crypto/NodeJsCrypto.js.map +6 -0
  105. package/dist/esm/crypto/index.d.ts +8 -0
  106. package/dist/esm/crypto/index.d.ts.map +1 -0
  107. package/dist/esm/crypto/index.js +8 -0
  108. package/dist/esm/crypto/index.js.map +6 -0
  109. package/dist/esm/crypto/register.d.ts +7 -0
  110. package/dist/esm/crypto/register.d.ts.map +1 -0
  111. package/dist/esm/crypto/register.js +20 -0
  112. package/dist/esm/crypto/register.js.map +6 -0
  113. package/dist/esm/environment/NodeJsActionTracer.d.ts +15 -0
  114. package/dist/esm/environment/NodeJsActionTracer.d.ts.map +1 -0
  115. package/dist/esm/environment/NodeJsActionTracer.js +60 -0
  116. package/dist/esm/environment/NodeJsActionTracer.js.map +6 -0
  117. package/dist/esm/environment/NodeJsEnvironment.d.ts +55 -0
  118. package/dist/esm/environment/NodeJsEnvironment.d.ts.map +1 -0
  119. package/dist/esm/environment/NodeJsEnvironment.js +108 -0
  120. package/dist/esm/environment/NodeJsEnvironment.js.map +6 -0
  121. package/dist/esm/environment/ProcessManager.d.ts +45 -0
  122. package/dist/esm/environment/ProcessManager.d.ts.map +1 -0
  123. package/dist/esm/environment/ProcessManager.js +92 -0
  124. package/dist/esm/environment/ProcessManager.js.map +6 -0
  125. package/dist/esm/environment/index.d.ts +10 -0
  126. package/dist/esm/environment/index.d.ts.map +1 -0
  127. package/dist/esm/environment/index.js +10 -0
  128. package/dist/esm/environment/index.js.map +6 -0
  129. package/dist/esm/environment/register.d.ts +7 -0
  130. package/dist/esm/environment/register.d.ts.map +1 -0
  131. package/dist/esm/environment/register.js +9 -0
  132. package/dist/esm/environment/register.js.map +6 -0
  133. package/dist/esm/index.d.ts +12 -0
  134. package/dist/esm/index.d.ts.map +1 -0
  135. package/dist/esm/index.js +12 -0
  136. package/dist/esm/index.js.map +6 -0
  137. package/dist/esm/log/FileLogger.d.ts +13 -0
  138. package/dist/esm/log/FileLogger.d.ts.map +1 -0
  139. package/dist/esm/log/FileLogger.js +26 -0
  140. package/dist/esm/log/FileLogger.js.map +6 -0
  141. package/dist/esm/log/index.d.ts +7 -0
  142. package/dist/esm/log/index.d.ts.map +1 -0
  143. package/dist/esm/log/index.js +7 -0
  144. package/dist/esm/log/index.js.map +6 -0
  145. package/dist/esm/net/NodeJsNetwork.d.ts +27 -0
  146. package/dist/esm/net/NodeJsNetwork.d.ts.map +1 -0
  147. package/dist/esm/net/NodeJsNetwork.js +133 -0
  148. package/dist/esm/net/NodeJsNetwork.js.map +6 -0
  149. package/dist/esm/net/NodeJsUdpChannel.d.ts +23 -0
  150. package/dist/esm/net/NodeJsUdpChannel.d.ts.map +1 -0
  151. package/dist/esm/net/NodeJsUdpChannel.js +159 -0
  152. package/dist/esm/net/NodeJsUdpChannel.js.map +6 -0
  153. package/dist/esm/net/index.d.ts +9 -0
  154. package/dist/esm/net/index.d.ts.map +1 -0
  155. package/dist/esm/net/index.js +9 -0
  156. package/dist/esm/net/index.js.map +6 -0
  157. package/dist/esm/net/register.d.ts +7 -0
  158. package/dist/esm/net/register.d.ts.map +1 -0
  159. package/dist/esm/net/register.js +15 -0
  160. package/dist/esm/net/register.js.map +6 -0
  161. package/dist/esm/package.json +11 -0
  162. package/dist/esm/storage/StorageBackendDisk.d.ts +27 -0
  163. package/dist/esm/storage/StorageBackendDisk.d.ts.map +1 -0
  164. package/dist/esm/storage/StorageBackendDisk.js +108 -0
  165. package/dist/esm/storage/StorageBackendDisk.js.map +6 -0
  166. package/dist/esm/storage/StorageBackendJsonFile.d.ts +27 -0
  167. package/dist/esm/storage/StorageBackendJsonFile.d.ts.map +1 -0
  168. package/dist/esm/storage/StorageBackendJsonFile.js +90 -0
  169. package/dist/esm/storage/StorageBackendJsonFile.js.map +6 -0
  170. package/dist/esm/storage/index.d.ts +8 -0
  171. package/dist/esm/storage/index.d.ts.map +1 -0
  172. package/dist/esm/storage/index.js +8 -0
  173. package/dist/esm/storage/index.js.map +6 -0
  174. package/dist/esm/tsconfig.tsbuildinfo +1 -0
  175. package/package.json +114 -0
  176. package/src/behavior/index.ts +7 -0
  177. package/src/behavior/instrumentation.ts +32 -0
  178. package/src/behavior/register.ts +9 -0
  179. package/src/crypto/NodeJsCrypto.ts +164 -0
  180. package/src/crypto/index.ts +8 -0
  181. package/src/crypto/register.ts +25 -0
  182. package/src/environment/NodeJsActionTracer.ts +70 -0
  183. package/src/environment/NodeJsEnvironment.ts +172 -0
  184. package/src/environment/ProcessManager.ts +128 -0
  185. package/src/environment/index.ts +10 -0
  186. package/src/environment/register.ts +10 -0
  187. package/src/index.ts +12 -0
  188. package/src/log/FileLogger.ts +30 -0
  189. package/src/log/index.ts +7 -0
  190. package/src/net/NodeJsNetwork.ts +157 -0
  191. package/src/net/NodeJsUdpChannel.ts +179 -0
  192. package/src/net/index.ts +9 -0
  193. package/src/net/register.ts +20 -0
  194. package/src/storage/StorageBackendDisk.ts +132 -0
  195. package/src/storage/StorageBackendJsonFile.ts +102 -0
  196. package/src/storage/index.ts +8 -0
  197. package/src/tsconfig.json +28 -0
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Destructable, Environment, Logger, RuntimeService, VariableService } from "#general";
8
+ import type { NodeJsEnvironment } from "./NodeJsEnvironment.js";
9
+
10
+ const logger = Logger.get("ProcessManager");
11
+
12
+ /**
13
+ * ProcessManager watches Node.js signals SIGINT and SIGUSR2 to terminate the Matter.js runtime and trigger Matter.js
14
+ * diagnostics respectively. It sets the process exit code to 0 if the runtime completes without error and to 1 if the
15
+ * runtime crashes.
16
+ *
17
+ * If enabled, SIGINT will perform a soft interrupt of the runtime once. ProcessManager will not process subsequent
18
+ * interrupts so they will result in forced exit if no other handler exists.
19
+ *
20
+ * {@link NodeJsEnvironment} installs a ProcessManager into the default Matter.js {@link Environment}.
21
+ *
22
+ * You can modify behavior by:
23
+ *
24
+ * - Passing an {@link Environment} other than {@link Environment.default} to your components
25
+ *
26
+ * - Disabling signal and exit code support with {@link VariableService} variables "runtime.signals" and
27
+ * "runtime.exitcode" respectively
28
+ *
29
+ * - Destroying ProcessManager in the default environment using {@link ProcessManager.close}
30
+ *
31
+ * - Subclassing ProcessManager, overriding specific functionality, and installing with {@link Environment.set}
32
+ */
33
+ export class ProcessManager implements Destructable {
34
+ protected runtime: RuntimeService;
35
+ #signalHandlersInstalled = false;
36
+
37
+ constructor(protected env: Environment) {
38
+ this.runtime = env.get(RuntimeService);
39
+
40
+ this.runtime.started.on(this.startListener);
41
+ this.runtime.stopped.on(this.stopListener);
42
+ this.runtime.crashed.on(this.crashListener);
43
+
44
+ if (this.hasUnhandledErrorSupport) {
45
+ process.addListener("uncaughtExceptionMonitor", event => {
46
+ Logger.reportUnhandledError(event);
47
+ });
48
+ }
49
+ }
50
+
51
+ close() {
52
+ this.runtime.started.off(this.startListener);
53
+ this.runtime.stopped.off(this.stopListener);
54
+ this.runtime.crashed.off(this.crashListener);
55
+ this.#ignoreSignals();
56
+ }
57
+
58
+ [Symbol.dispose]() {
59
+ this.close();
60
+ }
61
+
62
+ protected get hasSignalSupport() {
63
+ return this.env.vars.get("runtime.signals", true);
64
+ }
65
+
66
+ protected get hasExitCodeSupport() {
67
+ return this.env.vars.get("runtime.exitcode", true);
68
+ }
69
+
70
+ protected get hasUnhandledErrorSupport() {
71
+ return this.env.vars.get("runtime.unhandlederrors", true);
72
+ }
73
+
74
+ protected startListener = () => {
75
+ this.env.vars.use(() => {
76
+ if (this.hasSignalSupport) {
77
+ if (this.#signalHandlersInstalled) {
78
+ return;
79
+ }
80
+ process.on("SIGINT", this.interruptHandler);
81
+ process.on("SIGTERM", this.interruptHandler);
82
+ process.on("SIGUSR2", this.diagnosticHandler);
83
+ process.on("exit", this.exitHandler);
84
+ } else {
85
+ this.#ignoreSignals();
86
+ }
87
+ });
88
+ };
89
+
90
+ protected stopListener = () => {
91
+ this.#ignoreSignals();
92
+
93
+ if (this.hasExitCodeSupport && process.exitCode === undefined) {
94
+ process.exitCode = 0;
95
+ }
96
+ };
97
+
98
+ protected crashListener = () => {
99
+ if (this.hasExitCodeSupport) {
100
+ process.exitCode = 1;
101
+ }
102
+ };
103
+
104
+ protected interruptHandler = (signal: string) => {
105
+ process.off(signal, this.interruptHandler);
106
+ this.runtime.cancel();
107
+ };
108
+
109
+ protected exitHandler = () => {
110
+ if (process.exitCode === 13) {
111
+ logger.error("Internal error: Premature process exit because ongoing work has stalled");
112
+ }
113
+ };
114
+
115
+ protected diagnosticHandler = () => {
116
+ this.env.diagnose();
117
+ };
118
+
119
+ #ignoreSignals() {
120
+ if (this.#signalHandlersInstalled) {
121
+ process.off("SIGINT", this.interruptHandler);
122
+ process.off("SIGTERM", this.interruptHandler);
123
+ process.off("SIGUSR2", this.diagnosticHandler);
124
+ process.off("exit", this.exitHandler);
125
+ this.#signalHandlersInstalled = false;
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import "./register.js";
8
+ export * from "./NodeJsActionTracer.js";
9
+ export * from "./NodeJsEnvironment.js";
10
+ export * from "./ProcessManager.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Environment } from "#general";
8
+ import { NodeJsEnvironment } from "./NodeJsEnvironment.js";
9
+
10
+ Environment.default = NodeJsEnvironment();
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ export * from "./behavior/index.js";
8
+ export * from "./crypto/index.js";
9
+ export * from "./environment/index.js";
10
+ export * from "./log/index.js";
11
+ export * from "./net/index.js";
12
+ export * from "./storage/index.js";
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { LogLevel } from "#general";
8
+ import { open } from "fs/promises";
9
+
10
+ /**
11
+ * Creates a file based logger to append to the given path.
12
+ * The file is opened on start and closed when the process shuts down.
13
+ * Errors are logged to the console.
14
+ */
15
+ export async function createFileLogger(path: string) {
16
+ const fileHandle = await open(path, "a");
17
+ const writer = fileHandle.createWriteStream();
18
+ process.on(
19
+ "beforeExit",
20
+ () => void fileHandle.close().catch(err => err && console.error(`Failed to close log file: ${err}`)),
21
+ );
22
+
23
+ return (_level: LogLevel, formattedLog: string) => {
24
+ try {
25
+ writer.write(`${formattedLog}\n`);
26
+ } catch (error) {
27
+ console.error(`Failed to write to log file: ${error}`);
28
+ }
29
+ };
30
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ export * from "./FileLogger.js";
@@ -0,0 +1,157 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import {
8
+ Cache,
9
+ InterfaceType,
10
+ isIPv6,
11
+ Logger,
12
+ Network,
13
+ NetworkError,
14
+ NetworkInterface,
15
+ NetworkInterfaceDetails,
16
+ onSameNetwork,
17
+ UdpChannel,
18
+ UdpChannelOptions,
19
+ } from "#general";
20
+ import { NetworkInterfaceInfo, networkInterfaces } from "os";
21
+ import { NodeJsUdpChannel } from "./NodeJsUdpChannel.js";
22
+
23
+ const logger = Logger.get("NetworkNode");
24
+
25
+ export class NodeJsNetwork extends Network {
26
+ static getMulticastInterfaceIpv4(netInterface: string): string | undefined {
27
+ const netInterfaceInfo = networkInterfaces()[netInterface];
28
+ if (netInterfaceInfo === undefined) throw new NetworkError(`Unknown interface: ${netInterface}`);
29
+ for (const { address, family } of netInterfaceInfo) {
30
+ if (family === "IPv4") {
31
+ return address;
32
+ }
33
+ }
34
+ return undefined;
35
+ }
36
+
37
+ static getMembershipMulticastInterfaces(netInterface: string | undefined, ipv4: boolean): (string | undefined)[] {
38
+ if (ipv4) {
39
+ return [undefined];
40
+ } else {
41
+ let networkInterfaceEntries = Object.entries(networkInterfaces());
42
+ if (netInterface !== undefined) {
43
+ networkInterfaceEntries = networkInterfaceEntries.filter(([name]) => name === netInterface);
44
+ }
45
+ const multicastInterfaces = networkInterfaceEntries.flatMap(([netInterface, netInterfaceInfo]) => {
46
+ if (netInterfaceInfo === undefined) return [];
47
+ const zone = this.getNetInterfaceZoneIpv6Internal(netInterface, netInterfaceInfo);
48
+ return zone === undefined ? [] : [`::%${zone}`];
49
+ });
50
+ if (multicastInterfaces.length === 0) {
51
+ logger.warn(
52
+ `No IPv6 multicast interface found${
53
+ netInterface !== undefined ? ` for interface ${netInterface}` : ""
54
+ }.`,
55
+ );
56
+ }
57
+ return multicastInterfaces;
58
+ }
59
+ }
60
+
61
+ static getNetInterfaceZoneIpv6(netInterface: string): string | undefined {
62
+ const netInterfaceInfo = networkInterfaces()[netInterface];
63
+ if (netInterfaceInfo === undefined) throw new NetworkError(`Unknown interface: ${netInterface}`);
64
+ return this.getNetInterfaceZoneIpv6Internal(netInterface, netInterfaceInfo);
65
+ }
66
+
67
+ static getNetInterfaceForIp(ip: string) {
68
+ // Finding the local interface on the same interface is complex and won't change
69
+ // So let's cache the results for 5mn
70
+ return this.netInterfaces.get(ip);
71
+ }
72
+
73
+ private static readonly netInterfaces = new Cache<string | undefined>(
74
+ "Network interface",
75
+ (ip: string) => this.getNetInterfaceForRemoteAddress(ip),
76
+ 5 * 60 * 1000 /* 5mn */,
77
+ );
78
+
79
+ override async close() {
80
+ await NodeJsNetwork.netInterfaces.close();
81
+ }
82
+
83
+ private static getNetInterfaceForRemoteAddress(ip: string) {
84
+ if (ip.includes("%")) {
85
+ // IPv6 address with scope
86
+ return ip.split("%")[1];
87
+ } else {
88
+ const interfaces = networkInterfaces();
89
+ for (const name in interfaces) {
90
+ const netInterfaces = interfaces[name] as NetworkInterfaceInfo[];
91
+ for (const { address, netmask } of netInterfaces) {
92
+ if (onSameNetwork(ip, address, netmask)) {
93
+ return this.getNetInterfaceZoneIpv6Internal(name, netInterfaces);
94
+ }
95
+ }
96
+ }
97
+ if (isIPv6(ip)) {
98
+ if (ip.startsWith("fd")) {
99
+ // IPv6 address is an ULA
100
+ return ""; // consider it as being ok and using the "Default interface"
101
+ }
102
+ }
103
+ return undefined;
104
+ }
105
+ }
106
+
107
+ private static getNetInterfaceZoneIpv6Internal(
108
+ netInterface: string,
109
+ netInterfaceInfos: NetworkInterfaceInfo[] | undefined,
110
+ ): string | undefined {
111
+ if (process.platform !== "win32") {
112
+ return netInterface;
113
+ }
114
+ if (netInterfaceInfos === undefined) return undefined;
115
+ return netInterfaceInfos
116
+ .find(({ address, family }) => family === "IPv6" && address.startsWith("fe80::"))
117
+ ?.scopeid?.toString();
118
+ }
119
+
120
+ /**
121
+ * Get all network interfaces.
122
+ * The optional configuration parameter allows to map interface names to types if this mapping is known.
123
+ * Each network interface which has no mapped type is returned as Ethernet for now.
124
+ *
125
+ * @param configuration - An array of objects with the name and type properties.
126
+ */
127
+ getNetInterfaces(configuration: NetworkInterface[] = []): NetworkInterface[] {
128
+ const result = new Array<NetworkInterface>();
129
+ const interfaces = networkInterfaces();
130
+ for (const name in interfaces) {
131
+ const netInterfaces = interfaces[name] as NetworkInterfaceInfo[];
132
+ if (netInterfaces.length === 0) continue;
133
+ if (netInterfaces[0].internal) continue;
134
+ let type = InterfaceType.Ethernet;
135
+ if (configuration.length > 0) {
136
+ const nameType = configuration.find(({ name: mapName }) => name === mapName);
137
+ if (nameType !== undefined && nameType.type !== undefined) {
138
+ type = nameType.type;
139
+ }
140
+ }
141
+ result.push({ name, type });
142
+ }
143
+ return result;
144
+ }
145
+
146
+ getIpMac(netInterface: string): NetworkInterfaceDetails | undefined {
147
+ const netInterfaceInfo = networkInterfaces()[netInterface];
148
+ if (netInterfaceInfo === undefined) return undefined;
149
+ const ipV4 = netInterfaceInfo.filter(({ family }) => family === "IPv4").map(({ address }) => address);
150
+ const ipV6 = netInterfaceInfo.filter(({ family }) => family === "IPv6").map(({ address }) => address);
151
+ return { mac: netInterfaceInfo[0].mac, ipV4, ipV6 };
152
+ }
153
+
154
+ override createUdpChannel(options: UdpChannelOptions): Promise<UdpChannel> {
155
+ return NodeJsUdpChannel.create(options);
156
+ }
157
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import {
8
+ ChannelType,
9
+ Diagnostic,
10
+ isIPv4,
11
+ isIPv6,
12
+ Logger,
13
+ MAX_UDP_MESSAGE_SIZE,
14
+ NetworkError,
15
+ UdpChannel,
16
+ UdpChannelOptions,
17
+ } from "#general";
18
+ import { RetransmissionLimitReachedError } from "#protocol";
19
+ import * as dgram from "dgram";
20
+ import { NodeJsNetwork } from "./NodeJsNetwork.js";
21
+
22
+ const logger = Logger.get("UdpChannelNode");
23
+
24
+ function createDgramSocket(host: string | undefined, port: number | undefined, options: dgram.SocketOptions) {
25
+ const socket = dgram.createSocket(options);
26
+ return new Promise<dgram.Socket>((resolve, reject) => {
27
+ const handleBindError = (error: Error) => {
28
+ try {
29
+ socket.close();
30
+ } catch (error) {
31
+ logger.debug("Error on closing socket", error);
32
+ }
33
+ reject(error);
34
+ };
35
+ socket.on("error", handleBindError);
36
+ socket.bind(port, host, () => {
37
+ const { address: localHost, port: localPort } = socket.address();
38
+ logger.debug(
39
+ "Socket created and bound ",
40
+ Diagnostic.dict({
41
+ remoteAddress: `${host}:${port}`,
42
+ localAddress: `${localHost}:${localPort}`,
43
+ }),
44
+ );
45
+ socket.removeListener("error", handleBindError);
46
+ socket.on("error", error => logger.error(error));
47
+ resolve(socket);
48
+ });
49
+ });
50
+ }
51
+
52
+ export class NodeJsUdpChannel implements UdpChannel {
53
+ static async create({
54
+ listeningPort,
55
+ type,
56
+ listeningAddress,
57
+ netInterface,
58
+ membershipAddresses,
59
+ }: UdpChannelOptions) {
60
+ const socketOptions: dgram.SocketOptions = { type, reuseAddr: true };
61
+ if (type === "udp6") {
62
+ socketOptions.ipv6Only = true;
63
+ }
64
+ const socket = await createDgramSocket(listeningAddress, listeningPort, socketOptions);
65
+ socket.setBroadcast(true);
66
+ let netInterfaceZone: string | undefined;
67
+ if (netInterface !== undefined) {
68
+ netInterfaceZone = NodeJsNetwork.getNetInterfaceZoneIpv6(netInterface);
69
+ let multicastInterface: string | undefined;
70
+ if (type === "udp4") {
71
+ multicastInterface = NodeJsNetwork.getMulticastInterfaceIpv4(netInterface);
72
+ if (multicastInterface === undefined) {
73
+ throw new NetworkError(`No IPv4 addresses on interface: ${netInterface}`);
74
+ }
75
+ } else {
76
+ if (netInterfaceZone === undefined) {
77
+ throw new NetworkError(`No IPv6 addresses on interface: ${netInterface}`);
78
+ }
79
+ multicastInterface = `::%${netInterfaceZone}`;
80
+ }
81
+ logger.debug(
82
+ "Initialize multicast",
83
+ Diagnostic.dict({
84
+ address: `${multicastInterface}:${listeningPort}`,
85
+ interface: netInterface,
86
+ type: type,
87
+ }),
88
+ );
89
+ socket.setMulticastInterface(multicastInterface);
90
+ }
91
+ if (membershipAddresses !== undefined) {
92
+ const multicastInterfaces = NodeJsNetwork.getMembershipMulticastInterfaces(netInterface, type === "udp4");
93
+ for (const address of membershipAddresses) {
94
+ for (const multicastInterface of multicastInterfaces) {
95
+ try {
96
+ socket.addMembership(address, multicastInterface);
97
+ } catch (error) {
98
+ logger.warn(
99
+ `Error adding membership for address ${address}${
100
+ multicastInterface ? ` with interface ${multicastInterface}` : ""
101
+ }: ${error}`,
102
+ );
103
+ }
104
+ }
105
+ }
106
+ }
107
+ return new NodeJsUdpChannel(type, socket, netInterfaceZone);
108
+ }
109
+
110
+ readonly maxPayloadSize = MAX_UDP_MESSAGE_SIZE;
111
+
112
+ constructor(
113
+ private readonly type: "udp4" | "udp6",
114
+ private readonly socket: dgram.Socket,
115
+ private readonly netInterface?: string,
116
+ ) {}
117
+
118
+ onData(listener: (netInterface: string, peerAddress: string, peerPort: number, data: Uint8Array) => void) {
119
+ const messageListener = (data: Uint8Array, { address, port }: dgram.RemoteInfo) => {
120
+ const netInterface = this.netInterface ?? NodeJsNetwork.getNetInterfaceForIp(address);
121
+ if (netInterface === undefined) return;
122
+ listener(netInterface, address, port, data);
123
+ };
124
+
125
+ this.socket.on("message", messageListener);
126
+ return {
127
+ close: async () => {
128
+ this.socket.removeListener("message", messageListener);
129
+ },
130
+ };
131
+ }
132
+
133
+ async send(host: string, port: number, data: Uint8Array) {
134
+ return new Promise<void>((resolve, reject) => {
135
+ this.socket.send(data, port, host, error => {
136
+ if (error !== null) {
137
+ const netError =
138
+ error instanceof Error && "code" in error && error.code === "EHOSTUNREACH"
139
+ ? new RetransmissionLimitReachedError(error.message)
140
+ : new NetworkError(error.message);
141
+ netError.stack = error.stack;
142
+ reject(netError);
143
+ return;
144
+ }
145
+ resolve();
146
+ });
147
+ });
148
+ }
149
+
150
+ async close() {
151
+ try {
152
+ this.socket.close();
153
+ } catch (error) {
154
+ logger.debug("Error on closing socket", error);
155
+ }
156
+ }
157
+
158
+ get port() {
159
+ return this.socket.address().port;
160
+ }
161
+
162
+ supports(type: ChannelType, address?: string) {
163
+ if (type !== ChannelType.UDP) {
164
+ return false;
165
+ }
166
+
167
+ if (address === undefined) {
168
+ return true;
169
+ }
170
+
171
+ // TODO - we currently only discriminate based on protocol type. We should also determine whether the address subnet is correct
172
+
173
+ if (this.type === "udp4") {
174
+ return isIPv4(address);
175
+ }
176
+
177
+ return isIPv6(address);
178
+ }
179
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import "./register.js";
8
+ export * from "./NodeJsNetwork.js";
9
+ export * from "./NodeJsUdpChannel.js";
@@ -0,0 +1,20 @@
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 "#general";
8
+ import { NodeJsNetwork } from "./NodeJsNetwork.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
+
16
+ Network.get = singleton(() => new NodeJsNetwork());
17
+
18
+ // Ensure network gets cleaned up on exit
19
+ process.on("beforeExit", () => void Network.get().close());
20
+ }