matterbridge 1.0.6
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/.eslintrc.json +45 -0
- package/.gitattributes +2 -0
- package/.prettierignore +2 -0
- package/.prettierrc.json +12 -0
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/dist/AirQualityCluster.d.ts +147 -0
- package/dist/AirQualityCluster.d.ts.map +1 -0
- package/dist/AirQualityCluster.js +73 -0
- package/dist/AirQualityCluster.js.map +1 -0
- package/dist/ColorControlServer.d.ts +69 -0
- package/dist/ColorControlServer.d.ts.map +1 -0
- package/dist/ColorControlServer.js +85 -0
- package/dist/ColorControlServer.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/matterbridge.d.ts +70 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +719 -0
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +20 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +53 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeDevice.d.ts +57 -0
- package/dist/matterbridgeDevice.d.ts.map +1 -0
- package/dist/matterbridgeDevice.js +289 -0
- package/dist/matterbridgeDevice.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +20 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +53 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/frontend/README.md +70 -0
- package/frontend/build/asset-manifest.json +15 -0
- package/frontend/build/favicon.ico +0 -0
- package/frontend/build/index.html +1 -0
- package/frontend/build/manifest.json +15 -0
- package/frontend/build/matter.png +0 -0
- package/frontend/build/robots.txt +3 -0
- package/frontend/build/static/css/main.8b969fd5.css +2 -0
- package/frontend/build/static/css/main.8b969fd5.css.map +1 -0
- package/frontend/build/static/js/453.8ab44547.chunk.js +2 -0
- package/frontend/build/static/js/453.8ab44547.chunk.js.map +1 -0
- package/frontend/build/static/js/main.a000062f.js +3 -0
- package/frontend/build/static/js/main.a000062f.js.LICENSE.txt +78 -0
- package/frontend/build/static/js/main.a000062f.js.map +1 -0
- package/frontend/package-lock.json +18351 -0
- package/frontend/package.json +40 -0
- package/frontend/public/favicon.ico +0 -0
- package/frontend/public/index.html +15 -0
- package/frontend/public/manifest.json +15 -0
- package/frontend/public/matter.png +0 -0
- package/frontend/public/robots.txt +3 -0
- package/package.json +65 -0
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
import { NodeStorageManager } from 'node-persist-manager';
|
|
2
|
+
import { AnsiLogger, BLUE, BRIGHT, GREEN, RESET, REVERSE, REVERSEOFF, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, nf, rs, } from 'node-ansi-logger';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
4
|
+
import { promises as fs } from 'fs';
|
|
5
|
+
import EventEmitter from 'events';
|
|
6
|
+
import express from 'express';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { CommissioningServer, MatterServer } from '@project-chip/matter-node.js';
|
|
10
|
+
import { VendorId } from '@project-chip/matter-node.js/datatype';
|
|
11
|
+
import { Aggregator, DeviceTypes } from '@project-chip/matter-node.js/device';
|
|
12
|
+
import { Format, Level, Logger } from '@project-chip/matter-node.js/log';
|
|
13
|
+
import { QrCodeSchema } from '@project-chip/matter-node.js/schema';
|
|
14
|
+
import { StorageBackendDisk, StorageBackendJsonFile, StorageManager } from '@project-chip/matter-node.js/storage';
|
|
15
|
+
import { requireMinNodeVersion, getParameter, hasParameter } from '@project-chip/matter-node.js/util';
|
|
16
|
+
import { CryptoNode } from '@project-chip/matter-node.js/crypto';
|
|
17
|
+
import { BasicInformationCluster, BridgedDeviceBasicInformationCluster } from '@project-chip/matter-node.js/cluster';
|
|
18
|
+
export class Matterbridge extends EventEmitter {
|
|
19
|
+
systemInformation = {
|
|
20
|
+
ipv4Address: '',
|
|
21
|
+
ipv6Address: '',
|
|
22
|
+
nodeVersion: '',
|
|
23
|
+
hostname: '',
|
|
24
|
+
osType: '',
|
|
25
|
+
osRelease: '',
|
|
26
|
+
osPlatform: '',
|
|
27
|
+
osArch: '',
|
|
28
|
+
totalMemory: '',
|
|
29
|
+
freeMemory: '',
|
|
30
|
+
systemUptime: '',
|
|
31
|
+
};
|
|
32
|
+
rootDirectory;
|
|
33
|
+
bridgeMode = '';
|
|
34
|
+
log;
|
|
35
|
+
hasCleanupStarted = false;
|
|
36
|
+
registeredPlugins = [];
|
|
37
|
+
registeredDevices = [];
|
|
38
|
+
storage = undefined;
|
|
39
|
+
context = undefined;
|
|
40
|
+
app;
|
|
41
|
+
storageManager;
|
|
42
|
+
matterbridgeContext;
|
|
43
|
+
mattercontrollerContext;
|
|
44
|
+
matterServer;
|
|
45
|
+
matterAggregator;
|
|
46
|
+
commissioningServer;
|
|
47
|
+
commissioningController;
|
|
48
|
+
constructor() {
|
|
49
|
+
super();
|
|
50
|
+
// set Matterbridge logger
|
|
51
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */ });
|
|
52
|
+
this.log.info('Matterbridge is running...');
|
|
53
|
+
// log system info
|
|
54
|
+
this.logNodeAndSystemInfo();
|
|
55
|
+
// check node version and throw error
|
|
56
|
+
requireMinNodeVersion(18);
|
|
57
|
+
// register SIGINT SIGTERM signal handlers
|
|
58
|
+
this.registerSignalHandlers();
|
|
59
|
+
// set matter.js logger level and format
|
|
60
|
+
Logger.defaultLogLevel = Level.DEBUG;
|
|
61
|
+
Logger.format = Format.ANSI;
|
|
62
|
+
this.initialize();
|
|
63
|
+
}
|
|
64
|
+
async initialize() {
|
|
65
|
+
// Initialize NodeStorage
|
|
66
|
+
this.storage = new NodeStorageManager();
|
|
67
|
+
this.context = await this.storage.createStorage('matterbridge');
|
|
68
|
+
this.registeredPlugins = await this.context?.get('plugins', []);
|
|
69
|
+
// Initialize frontend
|
|
70
|
+
this.initializeFrontend();
|
|
71
|
+
// Parse command line
|
|
72
|
+
await this.parseCommandLine();
|
|
73
|
+
}
|
|
74
|
+
async parseCommandLine() {
|
|
75
|
+
if (hasParameter('help')) {
|
|
76
|
+
this.log.info(`\nmatterbridge -help -bridge -add <plugin path> -remove <plugin path>
|
|
77
|
+
- help: show the help
|
|
78
|
+
- bridge: start the bridge
|
|
79
|
+
- list: list the registered plugin
|
|
80
|
+
- add <plugin path>: register the plugin
|
|
81
|
+
- remove <plugin path>: remove the plugin\n`);
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
if (hasParameter('list')) {
|
|
85
|
+
this.log.info('Registered plugins:');
|
|
86
|
+
this.registeredPlugins.forEach((plugin) => {
|
|
87
|
+
this.log.info(`- ${BLUE}${plugin.name}${nf} '${BLUE}${BRIGHT}${plugin.description}${RESET}${nf}' type: ${GREEN}${plugin.type}${nf}`);
|
|
88
|
+
});
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
if (getParameter('add')) {
|
|
92
|
+
this.log.debug(`Registering plugin ${getParameter('add')}`);
|
|
93
|
+
await this.loadPlugin(getParameter('add'), 'add');
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
if (getParameter('remove')) {
|
|
97
|
+
this.log.debug(`Unregistering plugin ${getParameter('remove')}`);
|
|
98
|
+
await this.loadPlugin(getParameter('remove'), 'remove');
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
await this.startStorage('json', '.matterbridge.json');
|
|
102
|
+
if (hasParameter('childbridge')) {
|
|
103
|
+
this.bridgeMode = 'childbridge';
|
|
104
|
+
this.registeredPlugins.forEach(async (plugin) => {
|
|
105
|
+
this.log.info(`Loading plugin ${BLUE}${plugin.name}${nf} type ${GREEN}${plugin.type}${nf}`);
|
|
106
|
+
await this.loadPlugin(plugin.path, 'load');
|
|
107
|
+
});
|
|
108
|
+
await this.startMatterBridge();
|
|
109
|
+
}
|
|
110
|
+
if (hasParameter('bridge')) {
|
|
111
|
+
this.bridgeMode = 'bridge';
|
|
112
|
+
this.registeredPlugins.forEach(async (plugin) => {
|
|
113
|
+
this.log.info(`Loading plugin ${BLUE}${plugin.name}${nf} type ${GREEN}${plugin.type}${nf}`);
|
|
114
|
+
await this.loadPlugin(plugin.path, 'load');
|
|
115
|
+
});
|
|
116
|
+
await this.startMatterBridge();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Typed method for emitting events
|
|
120
|
+
emit(event, ...args) {
|
|
121
|
+
return super.emit(event, ...args);
|
|
122
|
+
}
|
|
123
|
+
// Typed method for listening to events
|
|
124
|
+
on(event, listener) {
|
|
125
|
+
super.on(event, listener);
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
async loadPlugin(packageJsonPath, mode = 'load') {
|
|
129
|
+
if (!packageJsonPath.endsWith('package.json'))
|
|
130
|
+
packageJsonPath = path.join(packageJsonPath, 'package.json');
|
|
131
|
+
this.log.debug(`Loading plugin ${BLUE}${packageJsonPath}${RESET}`);
|
|
132
|
+
try {
|
|
133
|
+
// Load the package.json of the plugin
|
|
134
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
135
|
+
// Resolve the main module path relative to package.json
|
|
136
|
+
const pluginPath = path.resolve(path.dirname(packageJsonPath), packageJson.main);
|
|
137
|
+
// Convert the file path to a URL
|
|
138
|
+
const pluginUrl = pathToFileURL(pluginPath);
|
|
139
|
+
// Dynamically import the plugin
|
|
140
|
+
this.log.debug(`Importing plugin ${BLUE}${pluginUrl.href}${RESET}`);
|
|
141
|
+
const plugin = await import(pluginUrl.href);
|
|
142
|
+
// Call the default export function of the plugin, passing this MatterBridge instance
|
|
143
|
+
if (plugin.default) {
|
|
144
|
+
const platform = plugin.default(this, new AnsiLogger({ logName: packageJson.description, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */ }));
|
|
145
|
+
platform.name = packageJson.name;
|
|
146
|
+
if (mode === 'load') {
|
|
147
|
+
this.log.info(`Plugin ${BLUE}${packageJsonPath}${RESET} type ${GREEN}${platform.type}${RESET} loaded (entrypoint ${UNDERLINE}${pluginPath}${UNDERLINEOFF})`);
|
|
148
|
+
// Update plugin info
|
|
149
|
+
const plugin = this.registeredPlugins.find((plugin) => plugin.name === packageJson.name);
|
|
150
|
+
if (plugin) {
|
|
151
|
+
plugin.name = packageJson.name;
|
|
152
|
+
plugin.description = packageJson.description;
|
|
153
|
+
plugin.version = packageJson.version;
|
|
154
|
+
plugin.author = packageJson.author;
|
|
155
|
+
plugin.type = platform.type;
|
|
156
|
+
plugin.loaded = true;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
this.log.error(`Plugin ${packageJson.name} not found`);
|
|
160
|
+
}
|
|
161
|
+
// Register handlers
|
|
162
|
+
if (platform.type === 'AccessoryPlatform') {
|
|
163
|
+
platform.on('registerDeviceAccessoryPlatform', (device) => {
|
|
164
|
+
this.log.debug(`Received ${REVERSE}registerDeviceAccessoryPlatform${REVERSEOFF} for device ${device.name}`);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
else if (platform.type === 'DynamicPlatform') {
|
|
168
|
+
platform.on('registerDeviceDynamicPlatform', (device) => {
|
|
169
|
+
this.log.debug(`Received ${REVERSE}registerDeviceDynamicPlatform${REVERSEOFF} for device ${device.name}`);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
this.log.error(`loadPlugin error platform.type ${REVERSE}${platform.type}${REVERSEOFF} for plugin ${packageJson.name}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else if (mode === 'add') {
|
|
177
|
+
if (!this.registeredPlugins.find((plugin) => plugin.name === packageJson.name)) {
|
|
178
|
+
this.registeredPlugins.push({
|
|
179
|
+
path: packageJsonPath,
|
|
180
|
+
type: platform.type,
|
|
181
|
+
name: packageJson.name,
|
|
182
|
+
version: packageJson.version,
|
|
183
|
+
description: packageJson.description,
|
|
184
|
+
author: packageJson.author,
|
|
185
|
+
});
|
|
186
|
+
await this.context?.set('plugins', this.registeredPlugins);
|
|
187
|
+
this.log.info(`Plugin ${packageJsonPath} type ${platform.type} added to matterbridge`);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
this.log.warn(`Plugin ${packageJsonPath} already added to matterbridge`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else if (mode === 'remove') {
|
|
194
|
+
if (this.registeredPlugins.find((plugin) => plugin.name === packageJson.name)) {
|
|
195
|
+
this.registeredPlugins.splice(this.registeredPlugins.findIndex((plugin) => plugin.name === packageJson.name));
|
|
196
|
+
await this.context?.set('plugins', this.registeredPlugins);
|
|
197
|
+
this.log.info(`Plugin ${packageJsonPath} removed from matterbridge`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
this.log.warn(`Plugin ${packageJsonPath} not registerd in matterbridge`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
this.log.error(`Plugin at ${pluginPath} does not provide a default export`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
this.log.error(`Failed to load plugin from ${packageJsonPath}: ${err}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
registerSignalHandlers() {
|
|
213
|
+
process.on('SIGINT', async () => {
|
|
214
|
+
await this.cleanup('SIGINT received, cleaning up...');
|
|
215
|
+
});
|
|
216
|
+
process.on('SIGTERM', async () => {
|
|
217
|
+
await this.cleanup('SIGTERM received, cleaning up...');
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async cleanup(message) {
|
|
221
|
+
if (!this.hasCleanupStarted) {
|
|
222
|
+
this.hasCleanupStarted = true;
|
|
223
|
+
this.log.debug(message);
|
|
224
|
+
// Emitting the shutdown event with a reason
|
|
225
|
+
this.emit('shutdown', 'Matterbridge is closing: ' + message);
|
|
226
|
+
// Set reachability to false
|
|
227
|
+
this.log.debug(`*Changing reachability for ${this.registeredDevices.length} devices:`);
|
|
228
|
+
this.registeredDevices.forEach((device) => {
|
|
229
|
+
this.log.debug(`*--child device: ${device.device.name}`);
|
|
230
|
+
if (this.bridgeMode === 'bridge')
|
|
231
|
+
device.device.setBridgedDeviceReachability(false);
|
|
232
|
+
});
|
|
233
|
+
setTimeout(async () => {
|
|
234
|
+
// Closing matter
|
|
235
|
+
await this.stopMatter();
|
|
236
|
+
// Closing storage
|
|
237
|
+
await this.stopStorage();
|
|
238
|
+
this.log.debug('Cleanup completed.');
|
|
239
|
+
process.exit(0);
|
|
240
|
+
}, this.bridgeMode === 'bridge' ? 2000 : 0);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async addDevice(pluginName, device) {
|
|
244
|
+
if (this.bridgeMode === 'bridge') {
|
|
245
|
+
const basic = device.getClusterServerById(BasicInformationCluster.id);
|
|
246
|
+
if (!basic) {
|
|
247
|
+
this.log.error('addDevice error: cannot find the BasicInformationCluster');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
device.createDefaultBridgedDeviceBasicInformationClusterServer(basic.getNodeLabelAttribute(), basic.getUniqueIdAttribute(), basic.getVendorIdAttribute(), basic.getVendorNameAttribute(), basic.getProductNameAttribute());
|
|
251
|
+
this.matterAggregator.addBridgedDevice(device);
|
|
252
|
+
this.registeredDevices.push({ plugin: pluginName, device });
|
|
253
|
+
this.log.debug(`addDevice called from plugin ${pluginName}`);
|
|
254
|
+
}
|
|
255
|
+
if (this.bridgeMode === 'childbridge') {
|
|
256
|
+
const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
|
|
257
|
+
if (plugin) {
|
|
258
|
+
plugin.started = true;
|
|
259
|
+
this.registeredDevices.push({ plugin: pluginName, device });
|
|
260
|
+
this.log.debug(`addDevice called from plugin ${pluginName}`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
this.log.error(`addDevice error: plugin ${pluginName} not found`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async addBridgedDevice(pluginName, device) {
|
|
268
|
+
if (this.bridgeMode === 'bridge') {
|
|
269
|
+
this.matterAggregator.addBridgedDevice(device);
|
|
270
|
+
this.registeredDevices.push({ plugin: pluginName, device });
|
|
271
|
+
this.log.debug(`addBridgedDevice called from plugin ${pluginName}`);
|
|
272
|
+
}
|
|
273
|
+
if (this.bridgeMode === 'childbridge') {
|
|
274
|
+
const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
|
|
275
|
+
if (plugin) {
|
|
276
|
+
plugin.started = true;
|
|
277
|
+
this.registeredDevices.push({ plugin: pluginName, device });
|
|
278
|
+
this.log.debug(`addBridgedDevice called from plugin ${pluginName}`);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
this.log.error(`addBridgedDevice error: plugin ${pluginName} not found`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
async startStorage(storageType, storageName) {
|
|
286
|
+
if (!storageName.endsWith('.json')) {
|
|
287
|
+
storageName += '.json';
|
|
288
|
+
}
|
|
289
|
+
this.log.debug(`Starting storage ${storageType} ${storageName}`);
|
|
290
|
+
if (storageType === 'disk') {
|
|
291
|
+
const storageDisk = new StorageBackendDisk(storageName);
|
|
292
|
+
this.storageManager = new StorageManager(storageDisk);
|
|
293
|
+
}
|
|
294
|
+
if (storageType === 'json') {
|
|
295
|
+
const storageJson = new StorageBackendJsonFile(storageName);
|
|
296
|
+
this.storageManager = new StorageManager(storageJson);
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
await this.storageManager.initialize();
|
|
300
|
+
this.log.debug('Storage initialized');
|
|
301
|
+
if (storageType === 'json') {
|
|
302
|
+
this.backupJsonStorage(storageName, storageName.replace('.json', '') + '.backup.json');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
this.log.error('Storage initialize() error!');
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async backupJsonStorage(storageName, backupName) {
|
|
311
|
+
try {
|
|
312
|
+
this.log.debug(`Making backup copy of ${storageName}`);
|
|
313
|
+
await fs.copyFile(storageName, backupName);
|
|
314
|
+
this.log.debug(`Successfully backed up ${storageName} to ${backupName}`);
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
if (err instanceof Error && 'code' in err) {
|
|
318
|
+
if (err.code === 'ENOENT') {
|
|
319
|
+
this.log.info(`No existing file to back up for ${storageName}. This is expected on the first run.`);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
this.log.error(`Error making backup copy of ${storageName}: ${err.message}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
this.log.error(`An unexpected error occurred during the backup of ${storageName}: ${String(err)}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async stopStorage() {
|
|
331
|
+
this.log.debug('Stopping storage');
|
|
332
|
+
await this.storageManager?.close();
|
|
333
|
+
this.log.debug('Storage closed');
|
|
334
|
+
}
|
|
335
|
+
async startMatterBridge() {
|
|
336
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
337
|
+
await this.createMatterServer(this.storageManager);
|
|
338
|
+
this.log.debug('Creating matterbridge context: matterbridge');
|
|
339
|
+
this.matterbridgeContext = this.storageManager.createContext('matterbridge');
|
|
340
|
+
this.matterbridgeContext.set('port', 5500);
|
|
341
|
+
this.matterbridgeContext.set('passcode', 20232024);
|
|
342
|
+
this.matterbridgeContext.set('discriminator', 3940);
|
|
343
|
+
this.matterbridgeContext.set('deviceName', 'matterbridge aggregator');
|
|
344
|
+
this.matterbridgeContext.set('deviceType', DeviceTypes.AGGREGATOR.code);
|
|
345
|
+
this.matterbridgeContext.set('vendorId', 0xfff1);
|
|
346
|
+
this.matterbridgeContext.set('vendorName', 'matterbridge');
|
|
347
|
+
this.matterbridgeContext.set('productId', 0x8000);
|
|
348
|
+
this.matterbridgeContext.set('productName', 'node-matterbridge');
|
|
349
|
+
this.matterbridgeContext.set('uniqueId', this.matterbridgeContext.get('uniqueId', CryptoNode.getRandomData(8).toHex()));
|
|
350
|
+
this.log.debug('Creating matterbridge commissioning server');
|
|
351
|
+
this.commissioningServer = await this.createMatterCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
352
|
+
if (this.bridgeMode === 'bridge') {
|
|
353
|
+
this.log.debug('Creating matter aggregator for matterbridge');
|
|
354
|
+
this.matterAggregator = await this.createMatterAggregator();
|
|
355
|
+
this.log.debug('Adding matter aggregator to commissioning server');
|
|
356
|
+
this.commissioningServer.addDevice(this.matterAggregator);
|
|
357
|
+
this.matterServer.addCommissioningServer(this.commissioningServer);
|
|
358
|
+
this.log.debug('Starting matter server');
|
|
359
|
+
await this.matterServer.start();
|
|
360
|
+
this.log.debug('Started matter server');
|
|
361
|
+
this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, 'Matterbridge');
|
|
362
|
+
}
|
|
363
|
+
if (this.bridgeMode === 'childbridge') {
|
|
364
|
+
this.log.debug('Creating matter aggregator for matterbridge');
|
|
365
|
+
this.matterAggregator = await this.createMatterAggregator();
|
|
366
|
+
this.log.debug('Adding matter aggregator to commissioning server');
|
|
367
|
+
this.commissioningServer.addDevice(this.matterAggregator);
|
|
368
|
+
this.matterServer.addCommissioningServer(this.commissioningServer);
|
|
369
|
+
this.registeredPlugins.forEach(async (plugin) => {
|
|
370
|
+
// Start the interval to check if all plugins are loaded
|
|
371
|
+
const loadedInterval = setInterval(async () => {
|
|
372
|
+
this.log.debug(`Waiting for plugin ${BLUE}${plugin.name}${db} to load (${plugin.loaded}) and send device (${plugin.started})...`);
|
|
373
|
+
if (!plugin.loaded)
|
|
374
|
+
return;
|
|
375
|
+
this.log.debug(`Sending start platform for plugin ${BLUE}${plugin.name}${db}`);
|
|
376
|
+
this.emit('startAccessoryPlatform', 'Matterbridge is commissioned and controllers are connected');
|
|
377
|
+
this.emit('startDynamicPlatform', 'Matterbridge is commissioned and controllers are connected');
|
|
378
|
+
clearInterval(loadedInterval);
|
|
379
|
+
}, 1000);
|
|
380
|
+
// Start the interval to check if all plugins are started
|
|
381
|
+
const startedInterval = setInterval(async () => {
|
|
382
|
+
this.log.debug(`**Waiting for plugin ${BLUE}${plugin.name}${db} to load (${plugin.loaded}) and send device (${plugin.started})...`);
|
|
383
|
+
if (!plugin.started)
|
|
384
|
+
return;
|
|
385
|
+
this.log.debug(`**Creating storage context for plugin ${BLUE}${plugin.name}${db}`);
|
|
386
|
+
plugin.storageContext = this.storageManager.createContext(plugin.name);
|
|
387
|
+
//plugin.storageContext.set('port', undefined);
|
|
388
|
+
//plugin.storageContext.set('passcode', undefined);
|
|
389
|
+
//plugin.storageContext.set('discriminator', undefined);
|
|
390
|
+
plugin.storageContext.set('deviceName', 'matterbridge aggregator');
|
|
391
|
+
plugin.storageContext.set('deviceType', DeviceTypes.AGGREGATOR.code);
|
|
392
|
+
plugin.storageContext.set('vendorId', 0xfff1);
|
|
393
|
+
plugin.storageContext.set('vendorName', 'matterbridge');
|
|
394
|
+
plugin.storageContext.set('productId', 0x8000);
|
|
395
|
+
plugin.storageContext.set('productName', plugin.name.slice(0, 32));
|
|
396
|
+
plugin.storageContext.set('uniqueId', plugin.storageContext.get('uniqueId', CryptoNode.getRandomData(8).toHex()));
|
|
397
|
+
this.log.debug(`**Creating commissioning server for plugin ${BLUE}${plugin.name}${db}`);
|
|
398
|
+
plugin.commissioningServer = await this.createMatterCommisioningServer(plugin.storageContext, plugin.name);
|
|
399
|
+
if (plugin.type === 'AccessoryPlatform') {
|
|
400
|
+
this.registeredDevices.forEach((registeredDevice) => {
|
|
401
|
+
if (registeredDevice.plugin === plugin.name) {
|
|
402
|
+
plugin.commissioningServer?.addDevice(registeredDevice.device);
|
|
403
|
+
this.matterServer.addCommissioningServer(plugin.commissioningServer);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
if (plugin.type === 'DynamicPlatform') {
|
|
409
|
+
this.log.debug(`**Creating aggregator for plugin ${BLUE}${plugin.name}${db}`);
|
|
410
|
+
plugin.aggregator = await this.createMatterAggregator();
|
|
411
|
+
this.log.debug(`**Adding matter aggregator to commissioning server for plugin ${BLUE}${plugin.name}${db}`);
|
|
412
|
+
plugin.commissioningServer?.addDevice(plugin.aggregator);
|
|
413
|
+
this.registeredDevices.forEach((registeredDevice) => {
|
|
414
|
+
if (registeredDevice.plugin === plugin.name)
|
|
415
|
+
plugin.aggregator?.addBridgedDevice(registeredDevice.device);
|
|
416
|
+
});
|
|
417
|
+
this.matterServer.addCommissioningServer(plugin.commissioningServer);
|
|
418
|
+
}
|
|
419
|
+
clearInterval(startedInterval);
|
|
420
|
+
}, 1000);
|
|
421
|
+
});
|
|
422
|
+
// Start the interval to check if all plugins are loaded and started and so start the matter server
|
|
423
|
+
const startMatterInterval = setInterval(async () => {
|
|
424
|
+
let allStarted = true;
|
|
425
|
+
this.registeredPlugins.forEach(async (plugin) => {
|
|
426
|
+
this.log.debug(`***Waiting in startMatter interval for plugin ${BLUE}${plugin.name}${db} to load (${plugin.loaded}) and send device (${plugin.started})...`);
|
|
427
|
+
if (!plugin.loaded || !plugin.started)
|
|
428
|
+
allStarted = false;
|
|
429
|
+
});
|
|
430
|
+
if (!allStarted)
|
|
431
|
+
return;
|
|
432
|
+
this.log.debug('***Starting matter server from startMatter interval');
|
|
433
|
+
await this.matterServer.start();
|
|
434
|
+
this.log.debug('***Started matter server from startMatter interval');
|
|
435
|
+
this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, 'Matterbridge');
|
|
436
|
+
this.registeredPlugins.forEach(async (plugin) => {
|
|
437
|
+
this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.name);
|
|
438
|
+
});
|
|
439
|
+
clearInterval(startMatterInterval);
|
|
440
|
+
}, 1000);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
showCommissioningQRCode(commissioningServer, storageContext, name) {
|
|
445
|
+
if (!commissioningServer || !storageContext || !name)
|
|
446
|
+
return;
|
|
447
|
+
if (!commissioningServer.isCommissioned()) {
|
|
448
|
+
this.log.info(`***The commissioning server for ${BLUE}${name}${nf} is not commissioned. Pair it and restart the process to run matterbridge.`);
|
|
449
|
+
const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
|
|
450
|
+
storageContext.set('qrPairingCode', qrPairingCode);
|
|
451
|
+
storageContext.set('manualPairingCode', manualPairingCode);
|
|
452
|
+
const QrCode = new QrCodeSchema();
|
|
453
|
+
this.log.debug(`Pairing code\n\n${QrCode.encode(qrPairingCode)}\nManual pairing code: ${manualPairingCode}\n`);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
this.log.info(`***The commissioning server for ${BLUE}${name}${nf} is already commissioned. Waiting for controllers to connect ...`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async createMatterCommisioningServer(context, name) {
|
|
460
|
+
//const port = context.get('port') as number;
|
|
461
|
+
//const passcode = context.get('passcode') as number;
|
|
462
|
+
//const discriminator = context.get('discriminator') as number;
|
|
463
|
+
const deviceName = context.get('deviceName');
|
|
464
|
+
const deviceType = context.get('deviceType');
|
|
465
|
+
const vendorId = context.get('vendorId');
|
|
466
|
+
const vendorName = context.get('vendorName'); // Home app = Manufacturer
|
|
467
|
+
const productId = context.get('productId');
|
|
468
|
+
const productName = context.get('productName'); // Home app = Model
|
|
469
|
+
const uniqueId = context.get('uniqueId');
|
|
470
|
+
// eslint-disable-next-line max-len
|
|
471
|
+
//this.log.debug(`Creating matter commissioning server with port ${port} passcode ${passcode} discriminator ${discriminator} deviceName ${deviceName} deviceType ${deviceType}`);
|
|
472
|
+
this.log.debug(`Creating matter commissioning server with deviceName ${deviceName} deviceType ${deviceType}`);
|
|
473
|
+
const commissioningServer = new CommissioningServer({
|
|
474
|
+
port: undefined,
|
|
475
|
+
passcode: undefined,
|
|
476
|
+
discriminator: undefined,
|
|
477
|
+
deviceName,
|
|
478
|
+
deviceType,
|
|
479
|
+
basicInformation: {
|
|
480
|
+
vendorId: VendorId(vendorId),
|
|
481
|
+
vendorName,
|
|
482
|
+
productId,
|
|
483
|
+
productName,
|
|
484
|
+
nodeLabel: productName,
|
|
485
|
+
productLabel: productName,
|
|
486
|
+
softwareVersion: 1,
|
|
487
|
+
softwareVersionString: '1.0.0', // Home app = Firmware Revision
|
|
488
|
+
hardwareVersion: 1,
|
|
489
|
+
hardwareVersionString: '1.0.0',
|
|
490
|
+
uniqueId,
|
|
491
|
+
serialNumber: `matterbridge-${uniqueId}`,
|
|
492
|
+
reachable: true,
|
|
493
|
+
},
|
|
494
|
+
activeSessionsChangedCallback: (fabricIndex) => {
|
|
495
|
+
const info = commissioningServer.getActiveSessionInformation(fabricIndex);
|
|
496
|
+
this.log.debug(`Active sessions changed on fabric ${fabricIndex}`, debugStringify(info));
|
|
497
|
+
if (info && info[0]?.isPeerActive === true && info[0]?.secure === true && info[0]?.numberOfActiveSubscriptions >= 1) {
|
|
498
|
+
this.log.info(`Controller connected to ${BLUE}${name}${nf} ready to start...`);
|
|
499
|
+
Logger.defaultLogLevel = Level.INFO;
|
|
500
|
+
setTimeout(() => {
|
|
501
|
+
if (this.bridgeMode === 'bridge') {
|
|
502
|
+
this.emit('startAccessoryPlatform', 'Matterbridge is commissioned and controllers are connected');
|
|
503
|
+
this.emit('startDynamicPlatform', 'Matterbridge is commissioned and controllers are connected');
|
|
504
|
+
}
|
|
505
|
+
}, 2000);
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
commissioningChangedCallback: (fabricIndex) => {
|
|
509
|
+
const info = commissioningServer.getCommissionedFabricInformation(fabricIndex);
|
|
510
|
+
this.log.debug(`Commissioning changed on fabric ${fabricIndex}`, debugStringify(info));
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
|
|
514
|
+
return commissioningServer;
|
|
515
|
+
}
|
|
516
|
+
async createMatterServer(storageManager) {
|
|
517
|
+
this.log.debug('Creating matter server');
|
|
518
|
+
this.matterServer = new MatterServer(storageManager, { mdnsAnnounceInterface: undefined });
|
|
519
|
+
}
|
|
520
|
+
async createMatterAggregator() {
|
|
521
|
+
const matterAggregator = new Aggregator();
|
|
522
|
+
return matterAggregator;
|
|
523
|
+
}
|
|
524
|
+
async stopMatter() {
|
|
525
|
+
this.log.debug('Stopping matter commissioningServer');
|
|
526
|
+
await this.commissioningServer?.close();
|
|
527
|
+
this.log.debug('Stopping matter commissioningController');
|
|
528
|
+
await this.commissioningController?.close();
|
|
529
|
+
this.log.debug('Stopping matter server');
|
|
530
|
+
await this.matterServer?.close();
|
|
531
|
+
this.log.debug('Matter server closed');
|
|
532
|
+
}
|
|
533
|
+
logNodeAndSystemInfo() {
|
|
534
|
+
// IP address information
|
|
535
|
+
const networkInterfaces = os.networkInterfaces();
|
|
536
|
+
this.systemInformation.ipv4Address = 'Not found';
|
|
537
|
+
this.systemInformation.ipv6Address = 'Not found';
|
|
538
|
+
for (const interfaceDetails of Object.values(networkInterfaces)) {
|
|
539
|
+
if (!interfaceDetails) {
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
for (const detail of interfaceDetails) {
|
|
543
|
+
if (detail.family === 'IPv4' && !detail.internal && this.systemInformation.ipv4Address === 'Not found') {
|
|
544
|
+
this.systemInformation.ipv4Address = detail.address;
|
|
545
|
+
}
|
|
546
|
+
else if (detail.family === 'IPv6' && !detail.internal && this.systemInformation.ipv6Address === 'Not found') {
|
|
547
|
+
this.systemInformation.ipv6Address = detail.address;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Break if both addresses are found to improve efficiency
|
|
551
|
+
if (this.systemInformation.ipv4Address !== 'Not found' && this.systemInformation.ipv6Address !== 'Not found') {
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// Node information
|
|
556
|
+
this.systemInformation.nodeVersion = process.versions.node;
|
|
557
|
+
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
558
|
+
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
559
|
+
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
560
|
+
// Host system information
|
|
561
|
+
this.systemInformation.hostname = os.hostname();
|
|
562
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
563
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
564
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
565
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
566
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
567
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
568
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
569
|
+
// Log the system information
|
|
570
|
+
this.log.debug('Host System Information:');
|
|
571
|
+
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
572
|
+
this.log.debug(`- IPv4 Address: ${this.systemInformation.ipv4Address}`);
|
|
573
|
+
this.log.debug(`- IPv6 Address: ${this.systemInformation.ipv6Address}`);
|
|
574
|
+
this.log.debug(`- Node.js: ${versionMajor}.${versionMinor}.${versionPatch}`);
|
|
575
|
+
this.log.debug(`- OS Type: ${this.systemInformation.osType}`);
|
|
576
|
+
this.log.debug(`- OS Release: ${this.systemInformation.osRelease}`);
|
|
577
|
+
this.log.debug(`- Platform: ${this.systemInformation.osPlatform}`);
|
|
578
|
+
this.log.debug(`- Architecture: ${this.systemInformation.osArch}`);
|
|
579
|
+
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
580
|
+
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
581
|
+
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
582
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
583
|
+
const cmdArgs = process.argv.slice(2).join(' ');
|
|
584
|
+
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
585
|
+
// Current working directory
|
|
586
|
+
const currentDir = process.cwd();
|
|
587
|
+
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
588
|
+
// Package root directory
|
|
589
|
+
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
590
|
+
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
591
|
+
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Initializes the frontend of Matterbridge.
|
|
595
|
+
*
|
|
596
|
+
* @param port The port number to run the frontend server on. Default is 3000.
|
|
597
|
+
*/
|
|
598
|
+
async initializeFrontend(port = 3000) {
|
|
599
|
+
//const __filename = fileURLToPath(import.meta.url);
|
|
600
|
+
//const __dirname = path.dirname(__filename);
|
|
601
|
+
this.log.debug(`Initializing the frontend on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${rs}`);
|
|
602
|
+
this.app = express();
|
|
603
|
+
// Serve React build directory
|
|
604
|
+
this.app.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
605
|
+
// Endpoint to provide QR pairing code
|
|
606
|
+
this.app.get('/api/qr-code', (req, res) => {
|
|
607
|
+
this.log.debug('The frontend sent /api/qr-code');
|
|
608
|
+
const qrData = { qrPairingCode: this.matterbridgeContext.get('qrPairingCode') };
|
|
609
|
+
res.json(qrData);
|
|
610
|
+
});
|
|
611
|
+
// Endpoint to provide system information
|
|
612
|
+
this.app.get('/api/system-info', (req, res) => {
|
|
613
|
+
this.log.debug('The frontend sent /api/system-info');
|
|
614
|
+
res.json(this.systemInformation);
|
|
615
|
+
});
|
|
616
|
+
// Endpoint to provide plugins
|
|
617
|
+
this.app.get('/api/plugins', (req, res) => {
|
|
618
|
+
this.log.debug('The frontend sent /api/plugins');
|
|
619
|
+
const data = [];
|
|
620
|
+
this.registeredPlugins.forEach((plugin) => {
|
|
621
|
+
data.push({ name: plugin.name, description: plugin.description, version: plugin.version, author: plugin.author, type: plugin.type });
|
|
622
|
+
});
|
|
623
|
+
res.json(data);
|
|
624
|
+
});
|
|
625
|
+
// Endpoint to provide devices
|
|
626
|
+
this.app.get('/api/devices', (req, res) => {
|
|
627
|
+
this.log.debug('The frontend sent /api/devices');
|
|
628
|
+
const data = [];
|
|
629
|
+
this.registeredDevices.forEach((device) => {
|
|
630
|
+
let name = device.device.getClusterServer(BasicInformationCluster)?.getNodeLabelAttribute();
|
|
631
|
+
if (!name && device.device.getClusterServer(BridgedDeviceBasicInformationCluster)) {
|
|
632
|
+
const bridgeInfo = device.device.getClusterServer(BridgedDeviceBasicInformationCluster);
|
|
633
|
+
if (bridgeInfo && bridgeInfo.getNodeLabelAttribute)
|
|
634
|
+
name = bridgeInfo.getNodeLabelAttribute();
|
|
635
|
+
}
|
|
636
|
+
data.push({ pluginName: device.plugin, type: device.device.name, endpoint: device.device.id, name: name ?? 'Unknown', cluster: 'Unknown' });
|
|
637
|
+
});
|
|
638
|
+
res.json(data);
|
|
639
|
+
});
|
|
640
|
+
// Fallback for routing
|
|
641
|
+
this.app.get('*', (req, res) => {
|
|
642
|
+
this.log.warn('The frontend sent *');
|
|
643
|
+
res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
644
|
+
});
|
|
645
|
+
this.app.listen(port, () => {
|
|
646
|
+
this.log.debug(`The frontend is running on ${UNDERLINE}http://localhost:${port}${rs}`);
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
/*
|
|
651
|
+
npx create-react-app matterbridge-frontend
|
|
652
|
+
cd matterbridge-frontend
|
|
653
|
+
npm install react-router-dom
|
|
654
|
+
|
|
655
|
+
Success! Created frontend at C:\Users\lligu\OneDrive\GitHub\matterbridge\frontend
|
|
656
|
+
Inside that directory, you can run several commands:
|
|
657
|
+
|
|
658
|
+
npm start
|
|
659
|
+
Starts the development server.
|
|
660
|
+
|
|
661
|
+
npm run build
|
|
662
|
+
Bundles the app into static files for production.
|
|
663
|
+
|
|
664
|
+
npm test
|
|
665
|
+
Starts the test runner.
|
|
666
|
+
|
|
667
|
+
npm run eject
|
|
668
|
+
Removes this tool and copies build dependencies, configuration files
|
|
669
|
+
and scripts into the app directory. If you do this, you can’t go back!
|
|
670
|
+
|
|
671
|
+
We suggest that you begin by typing:
|
|
672
|
+
|
|
673
|
+
cd frontend
|
|
674
|
+
npm start
|
|
675
|
+
|
|
676
|
+
Happy hacking!
|
|
677
|
+
PS C:\Users\lligu\OneDrive\GitHub\matterbridge> cd frontend
|
|
678
|
+
PS C:\Users\lligu\OneDrive\GitHub\matterbridge\frontend> npm run build
|
|
679
|
+
|
|
680
|
+
> frontend@0.1.0 build
|
|
681
|
+
> react-scripts build
|
|
682
|
+
|
|
683
|
+
Creating an optimized production build...
|
|
684
|
+
One of your dependencies, babel-preset-react-app, is importing the
|
|
685
|
+
"@babel/plugin-proposal-private-property-in-object" package without
|
|
686
|
+
declaring it in its dependencies. This is currently working because
|
|
687
|
+
"@babel/plugin-proposal-private-property-in-object" is already in your
|
|
688
|
+
node_modules folder for unrelated reasons, but it may break at any time.
|
|
689
|
+
|
|
690
|
+
babel-preset-react-app is part of the create-react-app project, which
|
|
691
|
+
is not maintianed anymore. It is thus unlikely that this bug will
|
|
692
|
+
ever be fixed. Add "@babel/plugin-proposal-private-property-in-object" to
|
|
693
|
+
your devDependencies to work around this error. This will make this message
|
|
694
|
+
go away.
|
|
695
|
+
|
|
696
|
+
Compiled successfully.
|
|
697
|
+
|
|
698
|
+
File sizes after gzip:
|
|
699
|
+
|
|
700
|
+
46.65 kB build\static\js\main.9b7ec296.js
|
|
701
|
+
1.77 kB build\static\js\453.8ab44547.chunk.js
|
|
702
|
+
513 B build\static\css\main.f855e6bc.css
|
|
703
|
+
|
|
704
|
+
The project was built assuming it is hosted at /.
|
|
705
|
+
You can control this with the homepage field in your package.json.
|
|
706
|
+
|
|
707
|
+
The build folder is ready to be deployed.
|
|
708
|
+
You may serve it with a static server:
|
|
709
|
+
|
|
710
|
+
npm install -g serve
|
|
711
|
+
serve -s build
|
|
712
|
+
|
|
713
|
+
Find out more about deployment here:
|
|
714
|
+
|
|
715
|
+
https://cra.link/deployment
|
|
716
|
+
|
|
717
|
+
PS C:\Users\lligu\OneDrive\GitHub\matterbridge\frontend>
|
|
718
|
+
*/
|
|
719
|
+
//# sourceMappingURL=matterbridge.js.map
|