matterbridge 2.1.6-dev.6 → 2.1.6-dev.8

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/CHANGELOG.md CHANGED
@@ -33,17 +33,19 @@ matterbridge-zigbee2mqtt v. 2.4.4
33
33
  matterbridge-somfy-tahoma v. 1.2.3
34
34
  matterbridge-hass v. 0.0.8
35
35
 
36
- ## [2.1.6] - 2025-02-18
36
+ ## [2.1.6] - 2025-02-20
37
37
 
38
38
  ### Added
39
39
 
40
40
  - [docker]: Added health check directly in the docker image. No need to change configuration of docker compose.
41
41
  - [platform]: Saving in the storage the selects for faster loading of plugins.
42
42
  - [icon]: Added matterbridge svg icon (thanks: https://github.com/robvanoostenrijk https://github.com/stuntguy3000).
43
- - [frontend]: Added processUptime.
43
+ - [pluginManager]: Refactor PluginManager to optimize memory and load time.
44
44
  - [frontend]: Frontend v.2.4.2.
45
- - [PluginManager]: Refactor PluginManager to optimize memory and load time.
45
+ - [frontend]: Added processUptime.
46
46
  - [frontend]: Added Share fabrics and Stop sharing to the menu. This allows to pair other controllers without the need to share from the first controller.
47
+ - [frontend]: Added subscriptions to QRDiv.
48
+ - [utils]: Optimized memory and loading time.
47
49
 
48
50
  ### Changed
49
51
 
@@ -52,6 +54,10 @@ matterbridge-hass v. 0.0.8
52
54
  - [package]: Update matter.js to 0.12.4-alpha.0-20250215-5af08a8d6
53
55
  - [package]: Update matter.js to 0.12.4-alpha.0-20250217-b0bba5179
54
56
 
57
+ ### Fixed
58
+
59
+ - [matterbridge]: Check endpoint state in /api/devices.
60
+
55
61
  <a href="https://www.buymeacoffee.com/luligugithub">
56
62
  <img src="./yellow-button.png" alt="Buy me a coffee" width="120">
57
63
  </a>
package/README.md CHANGED
@@ -48,6 +48,7 @@ To run Matterbridge, you need either a [Node.js](https://nodejs.org/en) environm
48
48
 
49
49
  If you don't have Node.js already install, please use this method to install it on a debian device: https://github.com/nodesource/distributions.
50
50
  The supported versions of node are 18, 20 and 22. Please install node 22 LTS.
51
+ Node 23 is not supported.
51
52
  Nvm is not a good choice and should not be used for production.
52
53
 
53
54
  If you don't have Docker already install, please use this method to install it on a debian device: https://docs.docker.com/desktop/setup/install/linux/debian/.
package/dist/frontend.js CHANGED
@@ -269,7 +269,7 @@ export class Frontend {
269
269
  this.log.debug('The frontend sent /api/devices');
270
270
  const devices = [];
271
271
  this.matterbridge.devices.forEach(async (device) => {
272
- if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
272
+ if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
273
273
  return;
274
274
  const cluster = this.getClusterTextFromDevice(device);
275
275
  devices.push({
@@ -1117,7 +1117,7 @@ export class Frontend {
1117
1117
  this.matterbridge.devices.forEach(async (device) => {
1118
1118
  if (data.params.pluginName && data.params.pluginName !== device.plugin)
1119
1119
  return;
1120
- if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId)
1120
+ if (!device.plugin || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
1121
1121
  return;
1122
1122
  const cluster = this.getClusterTextFromDevice(device);
1123
1123
  devices.push({
@@ -4,8 +4,8 @@ import { promises as fs } from 'node:fs';
4
4
  import EventEmitter from 'node:events';
5
5
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from './logger/export.js';
6
6
  import { NodeStorageManager } from './storage/export.js';
7
- import { getParameter, getIntParameter, hasParameter } from './utils/export.js';
8
- import { logInterfaces, copyDirectory, getNpmPackageVersion, getGlobalNodeModules } from './utils/utils.js';
7
+ import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout } from './utils/export.js';
8
+ import { logInterfaces, getNpmPackageVersion, getGlobalNodeModules } from './utils/network.js';
9
9
  import { PluginManager } from './pluginManager.js';
10
10
  import { DeviceManager } from './deviceManager.js';
11
11
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
@@ -55,7 +55,8 @@ export class Matterbridge extends EventEmitter {
55
55
  matterbridgeAdvertise: false,
56
56
  bridgeMode: '',
57
57
  restartMode: '',
58
- readOnly: hasParameter('readonly'),
58
+ readOnly: hasParameter('readonly') || hasParameter('shelly'),
59
+ shellyBoard: hasParameter('shelly'),
59
60
  profile: getParameter('profile'),
60
61
  loggerLevel: "info",
61
62
  fileLogger: false,
@@ -1531,20 +1532,6 @@ export class Matterbridge extends EventEmitter {
1531
1532
  if (!matterServerNode)
1532
1533
  return;
1533
1534
  this.log.notice(`Closing ${matterServerNode.id} server node`);
1534
- const withTimeout = (promise, ms) => {
1535
- return new Promise((resolve, reject) => {
1536
- const timer = setTimeout(() => reject(new Error('Operation timed out')), ms);
1537
- promise
1538
- .then((result) => {
1539
- clearTimeout(timer);
1540
- resolve(result);
1541
- })
1542
- .catch((error) => {
1543
- clearTimeout(timer);
1544
- reject(error);
1545
- });
1546
- });
1547
- };
1548
1535
  try {
1549
1536
  await withTimeout(matterServerNode.close(), 30000);
1550
1537
  this.log.info(`Closed ${matterServerNode.id} server node`);
@@ -25,7 +25,7 @@ export class MatterbridgePlatform {
25
25
  this.log = log;
26
26
  this.config = config;
27
27
  if (!isValidString(this.config.name) || this.config.name === '')
28
- return;
28
+ throw new Error('Platform: the plugin name is missing or invalid.');
29
29
  this.log.debug(`Creating storage for plugin ${this.config.name} in ${path.join(this.matterbridge.matterbridgeDirectory, this.config.name)}`);
30
30
  this.storage = new NodeStorageManager({
31
31
  dir: path.join(this.matterbridge.matterbridgeDirectory, this.config.name),
@@ -37,8 +37,8 @@ export class MatterbridgePlatform {
37
37
  this.log.debug(`Creating context for plugin ${this.config.name}`);
38
38
  this.contextReady = this.storage.createStorage('context').then((context) => {
39
39
  this.context = context;
40
+ this.context.remove('endpointMap');
40
41
  this.log.debug(`Created context for plugin ${this.config.name}`);
41
- return context;
42
42
  });
43
43
  this.log.debug(`Loading selectDevice for plugin ${this.config.name}`);
44
44
  this.selectDeviceContextReady = this.storage.createStorage('selectDevice').then(async (context) => {
@@ -83,13 +83,9 @@ export class MatterbridgePlatform {
83
83
  this.selectEntity.clear();
84
84
  this.registeredEndpoints.clear();
85
85
  this.registeredEndpointsByName.clear();
86
- this.contextReady = undefined;
87
- this.selectDeviceContextReady = undefined;
88
- this.selectEntityContextReady = undefined;
89
86
  await this.context?.close();
90
87
  this.context = undefined;
91
88
  await this.storage?.close();
92
- this.storage = undefined;
93
89
  }
94
90
  async onChangeLoggerLevel(logLevel) {
95
91
  this.log.debug(`The plugin doesn't override onChangeLoggerLevel. Logger level set to: ${logLevel}`);
@@ -454,8 +454,6 @@ export class PluginManager {
454
454
  plugin.loaded = true;
455
455
  plugin.registeredDevices = 0;
456
456
  plugin.addedDevices = 0;
457
- plugin.configJson = config;
458
- plugin.schemaJson = await this.loadSchema(plugin);
459
457
  await this.saveToStorage();
460
458
  this.log.notice(`Loaded plugin ${plg}${plugin.name}${nt} type ${typ}${platform.type}${nt} (entrypoint ${UNDERLINE}${pluginEntry}${UNDERLINEOFF})`);
461
459
  if (start)
@@ -0,0 +1,26 @@
1
+ import { AnsiLogger } from '../logger/export.js';
2
+ export async function copyDirectory(srcDir, destDir) {
3
+ const log = new AnsiLogger({ logName: 'Archive', logTimestampFormat: 4, logLevel: "info" });
4
+ const fs = await import('node:fs').then((mod) => mod.promises);
5
+ const path = await import('node:path');
6
+ log.debug(`copyDirectory: copying directory from ${srcDir} to ${destDir}`);
7
+ try {
8
+ await fs.mkdir(destDir, { recursive: true });
9
+ const entries = await fs.readdir(srcDir, { withFileTypes: true });
10
+ for (const entry of entries) {
11
+ const srcPath = path.join(srcDir, entry.name);
12
+ const destPath = path.join(destDir, entry.name);
13
+ if (entry.isDirectory()) {
14
+ await copyDirectory(srcPath, destPath);
15
+ }
16
+ else if (entry.isFile()) {
17
+ await fs.copyFile(srcPath, destPath);
18
+ }
19
+ }
20
+ return true;
21
+ }
22
+ catch (error) {
23
+ log.error(`copyDirectory error copying from ${srcDir} to ${destDir}: ${error instanceof Error ? error.message : error}`);
24
+ return false;
25
+ }
26
+ }
@@ -0,0 +1,69 @@
1
+ import { AnsiLogger } from '../logger/export.js';
2
+ export async function createZip(outputPath, ...sourcePaths) {
3
+ const log = new AnsiLogger({ logName: 'Archive', logTimestampFormat: 4, logLevel: "info" });
4
+ const { default: archiver } = await import('archiver');
5
+ const { glob } = await import('glob');
6
+ const { createWriteStream, statSync } = await import('node:fs');
7
+ const path = await import('node:path');
8
+ log.debug(`creating archive ${outputPath} from ${sourcePaths.join(', ')} ...`);
9
+ return new Promise((resolve, reject) => {
10
+ const output = createWriteStream(outputPath);
11
+ const archive = archiver('zip', {
12
+ zlib: { level: 9 },
13
+ });
14
+ output.on('close', () => {
15
+ log.debug(`archive ${outputPath} closed with ${archive.pointer()} total bytes`);
16
+ resolve(archive.pointer());
17
+ });
18
+ output.on('end', () => {
19
+ log.debug(`archive ${outputPath} data has been drained ${archive.pointer()} total bytes`);
20
+ });
21
+ archive.on('error', (error) => {
22
+ log.error(`archive error: ${error.message}`);
23
+ reject(error);
24
+ });
25
+ archive.on('warning', (error) => {
26
+ if (error.code === 'ENOENT') {
27
+ log.warn(`archive warning: ${error.message}`);
28
+ }
29
+ else {
30
+ log.error(`archive warning: ${error.message}`);
31
+ reject(error);
32
+ }
33
+ });
34
+ archive.on('entry', (entry) => {
35
+ log.debug(`- archive entry: ${entry.name}`);
36
+ });
37
+ archive.pipe(output);
38
+ for (const sourcePath of sourcePaths) {
39
+ let stats;
40
+ try {
41
+ stats = statSync(sourcePath);
42
+ }
43
+ catch (error) {
44
+ if (sourcePath.includes('*')) {
45
+ const files = glob.sync(sourcePath.replace(/\\/g, '/'));
46
+ log.debug(`adding files matching glob pattern: ${sourcePath}`);
47
+ for (const file of files) {
48
+ log.debug(`- glob file: ${file}`);
49
+ archive.file(file, { name: file });
50
+ }
51
+ }
52
+ else {
53
+ log.error(`no files or directory found for pattern ${sourcePath}: ${error}`);
54
+ }
55
+ continue;
56
+ }
57
+ if (stats.isFile()) {
58
+ log.debug(`adding file: ${sourcePath}`);
59
+ archive.file(sourcePath, { name: path.basename(sourcePath) });
60
+ }
61
+ else if (stats.isDirectory()) {
62
+ log.debug(`adding directory: ${sourcePath}`);
63
+ archive.directory(sourcePath, path.basename(sourcePath));
64
+ }
65
+ }
66
+ log.debug(`finalizing archive ${outputPath}...`);
67
+ archive.finalize().catch(reject);
68
+ });
69
+ }
@@ -0,0 +1,35 @@
1
+ export function deepCopy(value) {
2
+ if (typeof value !== 'object' || value === null) {
3
+ return value;
4
+ }
5
+ else if (Array.isArray(value)) {
6
+ return value.map((item) => deepCopy(item));
7
+ }
8
+ else if (value instanceof Date) {
9
+ return new Date(value.getTime());
10
+ }
11
+ else if (value instanceof Map) {
12
+ const mapCopy = new Map();
13
+ value.forEach((val, key) => {
14
+ mapCopy.set(key, deepCopy(val));
15
+ });
16
+ return mapCopy;
17
+ }
18
+ else if (value instanceof Set) {
19
+ const setCopy = new Set();
20
+ value.forEach((item) => {
21
+ setCopy.add(deepCopy(item));
22
+ });
23
+ return setCopy;
24
+ }
25
+ else {
26
+ const proto = Object.getPrototypeOf(value);
27
+ const copy = Object.create(proto);
28
+ for (const key in value) {
29
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
30
+ copy[key] = deepCopy(value[key]);
31
+ }
32
+ }
33
+ return copy;
34
+ }
35
+ }
@@ -0,0 +1,56 @@
1
+ export function deepEqual(a, b, excludeProperties = []) {
2
+ const debug = false;
3
+ const debugLog = (...messages) => {
4
+ if (debug) {
5
+ console.log(...messages);
6
+ }
7
+ };
8
+ if (a === b) {
9
+ return true;
10
+ }
11
+ if (typeof a !== typeof b) {
12
+ debugLog(`deepEqual false for typeof a: ${typeof a} typeof b: ${typeof b}`);
13
+ return false;
14
+ }
15
+ if (a == null || b == null) {
16
+ debugLog('deepEqual false for == null');
17
+ return false;
18
+ }
19
+ if (Array.isArray(a) && Array.isArray(b)) {
20
+ if (a.length !== b.length) {
21
+ debugLog(`deepEqual false for array a.length(${a.length}) !== b.length(${b.length})`);
22
+ return false;
23
+ }
24
+ for (let i = 0; i < a.length; i++) {
25
+ if (!deepEqual(a[i], b[i], excludeProperties)) {
26
+ debugLog('deepEqual false for array !deepEqual(a[i], b[i])');
27
+ debugLog(`- aProps.length(${a[i]}):`, a[i]);
28
+ debugLog(`- bProps.length(${b[i]}):`, b[i]);
29
+ return false;
30
+ }
31
+ }
32
+ return true;
33
+ }
34
+ if (typeof a === 'object' && typeof b === 'object') {
35
+ const aProps = Object.getOwnPropertyNames(a).filter((prop) => !excludeProperties.includes(prop));
36
+ const bProps = Object.getOwnPropertyNames(b).filter((prop) => !excludeProperties.includes(prop));
37
+ if (aProps.length !== bProps.length) {
38
+ debugLog(`deepEqual false for aProps.length(${aProps.length}) !== bProps.length(${bProps.length})`);
39
+ debugLog(`- aProps.length(${aProps.length}):`, aProps);
40
+ debugLog(`- bProps.length(${bProps.length}):`, bProps);
41
+ return false;
42
+ }
43
+ for (const prop of aProps) {
44
+ if (!Object.prototype.hasOwnProperty.call(b, prop)) {
45
+ debugLog(`deepEqual false for !b.hasOwnProperty(${prop})`);
46
+ return false;
47
+ }
48
+ if (!deepEqual(a[prop], b[prop], excludeProperties)) {
49
+ debugLog(`deepEqual false for !deepEqual(a[${prop}], b[${prop}])`);
50
+ return false;
51
+ }
52
+ }
53
+ return true;
54
+ }
55
+ return false;
56
+ }
@@ -1,4 +1,9 @@
1
- export * from './utils.js';
1
+ export * from './network.js';
2
2
  export * from './parameter.js';
3
3
  export * from './isvalid.js';
4
4
  export * from './colorUtils.js';
5
+ export * from './deepCopy.js';
6
+ export * from './deepEqual.js';
7
+ export * from './copyDirectory.js';
8
+ export * from './createZip.js';
9
+ export * from './wait.js';
@@ -0,0 +1,143 @@
1
+ import os from 'node:os';
2
+ import { AnsiLogger, idn, rs } from '../logger/export.js';
3
+ export function getIpv4InterfaceAddress() {
4
+ let ipv4Address;
5
+ const networkInterfaces = os.networkInterfaces();
6
+ for (const interfaceDetails of Object.values(networkInterfaces)) {
7
+ if (!interfaceDetails) {
8
+ break;
9
+ }
10
+ for (const detail of interfaceDetails) {
11
+ if (detail.family === 'IPv4' && !detail.internal && ipv4Address === undefined) {
12
+ ipv4Address = detail.address;
13
+ }
14
+ }
15
+ if (ipv4Address !== undefined) {
16
+ break;
17
+ }
18
+ }
19
+ return ipv4Address;
20
+ }
21
+ export function getIpv6InterfaceAddress() {
22
+ let ipv6Address;
23
+ const networkInterfaces = os.networkInterfaces();
24
+ for (const interfaceDetails of Object.values(networkInterfaces)) {
25
+ if (!interfaceDetails) {
26
+ break;
27
+ }
28
+ for (const detail of interfaceDetails) {
29
+ if (detail.family === 'IPv6' && !detail.internal && ipv6Address === undefined) {
30
+ ipv6Address = detail.address;
31
+ }
32
+ }
33
+ if (ipv6Address !== undefined) {
34
+ break;
35
+ }
36
+ }
37
+ return ipv6Address;
38
+ }
39
+ export function getMacAddress() {
40
+ let macAddress;
41
+ const networkInterfaces = os.networkInterfaces();
42
+ for (const interfaceDetails of Object.values(networkInterfaces)) {
43
+ if (!interfaceDetails) {
44
+ break;
45
+ }
46
+ for (const detail of interfaceDetails) {
47
+ if (detail.family === 'IPv6' && !detail.internal && macAddress === undefined) {
48
+ macAddress = detail.mac;
49
+ }
50
+ }
51
+ if (macAddress !== undefined) {
52
+ break;
53
+ }
54
+ }
55
+ return macAddress;
56
+ }
57
+ export function logInterfaces(debug = true) {
58
+ const log = new AnsiLogger({ logName: 'MatterbridgeUtils', logTimestampFormat: 4, logLevel: "info" });
59
+ log.logLevel = "info";
60
+ log.logName = 'LogInterfaces';
61
+ let ipv6Address;
62
+ const networkInterfaces = os.networkInterfaces();
63
+ if (debug)
64
+ log.info('Available Network Interfaces:');
65
+ for (const [interfaceName, networkInterface] of Object.entries(networkInterfaces)) {
66
+ if (!networkInterface)
67
+ break;
68
+ if (debug)
69
+ log.info(`Interface: ${idn}${interfaceName}${rs}`);
70
+ for (const detail of networkInterface) {
71
+ if (debug)
72
+ log.info('Details:', detail);
73
+ }
74
+ }
75
+ return ipv6Address;
76
+ }
77
+ export async function resolveHostname(hostname, family = 4) {
78
+ const dns = await import('node:dns');
79
+ try {
80
+ const addresses = await dns.promises.lookup(hostname.toLowerCase(), { family });
81
+ return addresses.address;
82
+ }
83
+ catch (error) {
84
+ return null;
85
+ }
86
+ }
87
+ export async function getNpmPackageVersion(packageName, tag = 'latest', timeout = 5000) {
88
+ const https = await import('https');
89
+ return new Promise((resolve, reject) => {
90
+ const url = `https://registry.npmjs.org/${packageName}`;
91
+ const controller = new AbortController();
92
+ const timeoutId = setTimeout(() => {
93
+ controller.abort();
94
+ reject(new Error(`Request timed out after ${timeout / 1000} seconds`));
95
+ }, timeout);
96
+ const req = https.get(url, { signal: controller.signal }, (res) => {
97
+ let data = '';
98
+ if (res.statusCode !== 200) {
99
+ clearTimeout(timeoutId);
100
+ res.resume();
101
+ req.destroy();
102
+ reject(new Error(`Failed to fetch data. Status code: ${res.statusCode}`));
103
+ return;
104
+ }
105
+ res.on('data', (chunk) => {
106
+ data += chunk;
107
+ });
108
+ res.on('end', () => {
109
+ clearTimeout(timeoutId);
110
+ try {
111
+ const jsonData = JSON.parse(data);
112
+ const version = jsonData['dist-tags']?.[tag];
113
+ if (version) {
114
+ resolve(version);
115
+ }
116
+ else {
117
+ reject(new Error(`Tag "${tag}" not found for package "${packageName}"`));
118
+ }
119
+ }
120
+ catch (error) {
121
+ reject(new Error(`Failed to parse response JSON: ${error instanceof Error ? error.message : error}`));
122
+ }
123
+ });
124
+ });
125
+ req.on('error', (error) => {
126
+ clearTimeout(timeoutId);
127
+ reject(new Error(`Request failed: ${error instanceof Error ? error.message : error}`));
128
+ });
129
+ });
130
+ }
131
+ export async function getGlobalNodeModules() {
132
+ const { exec } = await import('node:child_process');
133
+ return new Promise((resolve, reject) => {
134
+ exec('npm root -g', (error, stdout) => {
135
+ if (error) {
136
+ reject(error);
137
+ }
138
+ else {
139
+ resolve(stdout.trim());
140
+ }
141
+ });
142
+ });
143
+ }
@@ -0,0 +1,57 @@
1
+ import { AnsiLogger } from '../logger/export.js';
2
+ const log = new AnsiLogger({ logName: 'MatterbridgeUtils', logTimestampFormat: 4, logLevel: "info" });
3
+ export async function waiter(name, check, exitWithReject = false, resolveTimeout = 5000, resolveInterval = 500, debug = false) {
4
+ log.logLevel = "debug";
5
+ log.logName = 'Waiter';
6
+ if (debug)
7
+ log.debug(`Waiter "${name}" started...`);
8
+ return new Promise((resolve, reject) => {
9
+ const timeoutId = setTimeout(() => {
10
+ if (debug)
11
+ log.debug(`Waiter "${name}" finished for timeout...`);
12
+ clearTimeout(timeoutId);
13
+ clearInterval(intervalId);
14
+ if (exitWithReject)
15
+ reject(new Error(`Waiter "${name}" finished due to timeout`));
16
+ else
17
+ resolve(false);
18
+ }, resolveTimeout);
19
+ const intervalId = setInterval(() => {
20
+ if (check()) {
21
+ if (debug)
22
+ log.debug(`Waiter "${name}" finished for true condition...`);
23
+ clearTimeout(timeoutId);
24
+ clearInterval(intervalId);
25
+ resolve(true);
26
+ }
27
+ }, resolveInterval);
28
+ });
29
+ }
30
+ export async function wait(timeout = 1000, name, debug = false) {
31
+ log.logLevel = "debug";
32
+ log.logName = 'Wait';
33
+ if (debug)
34
+ log.debug(`Wait "${name}" started...`);
35
+ return new Promise((resolve, reject) => {
36
+ const timeoutId = setTimeout(() => {
37
+ if (debug)
38
+ log.debug(`Wait "${name}" finished...`);
39
+ clearTimeout(timeoutId);
40
+ resolve();
41
+ }, timeout);
42
+ });
43
+ }
44
+ export function withTimeout(promise, ms) {
45
+ return new Promise((resolve, reject) => {
46
+ const timer = setTimeout(() => reject(new Error('Operation timed out')), ms);
47
+ promise
48
+ .then((result) => {
49
+ clearTimeout(timer);
50
+ resolve(result);
51
+ })
52
+ .catch((error) => {
53
+ clearTimeout(timer);
54
+ reject(error);
55
+ });
56
+ });
57
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" width="1024" viewBox="0.7 0 86.6 24"><path fill="#4594D1" d="m82 15.5 2.4-6.6h-4.6l-2.3 6.6h-2.6l2.4-6.6h-4.7L70.4 15v.5h-3.7l4.8-13.3H67l-4.7 13.3h-4L63 2.2h-4.5l-4.7 13.3h-7.1c-1.1 0-1.7-.3-1.7-1l.1-.4 1-3H49l-1.7 4.5s3.3-1.3 4.4-1.8c1-.5 1.3-1.4 1.3-2.3 0-.8-.6-1.5-1.7-2-.8-.3-1.7-.4-2.8-.4h-1.8c-1.7 0-3 .4-4.3 1.1-1.3 1-2 2.1-2 3.7 0 .6.1 1.2.4 1.7h-4.3l1.4-4c.2-.5.4-1.1.4-1.5 0-.8-.8-1-1.7-1-1.2 0-2.3.4-3.3 1.2l-3.4 2.8 3.8-10.6H29l-5.4 15h2.6c.6 0 1.3-.2 1.8-.7L33 13l-1 2.7-.1 1c0 .6.5 1 1.4 1h27.3a4 4 0 0 0 1.9-.6c.3.4.8.5 1.6.5h5a4 4 0 0 0 1.9-.5c.3.4.8.5 1.4.5h1c-.6.4-1 .9-1.2 1.5a4.7 4.7 0 0 0-.5 2c0 .8.3 1.5.8 2 .6.7 1.4 1 2.4 1 1.7 0 3.1-.7 4.3-2 .8-1 1.6-2.5 2.3-4.5h3.1c1.4 0 2.3-.6 2.8-2h-5.2ZM75.2 22c-.5-.4-.7-.8-.7-1.4 0-.6.2-1.2.7-1.9.5-.6 1-1 1.6-1L75 22.2ZM22 5l1.5-2.9h-2.8c-1.2 0-1.9.5-1.9 1.6 0 .5.4 1.8 1.1 3.6A13 13 0 0 1 21 12c0 2.2-1.3 4-3.8 5a16 16 0 0 1-6.6 1.3 18 18 0 0 1-6.2-1C2 16.5.7 15 .7 13.3.7 10.3 3 9 7.7 9h6c-.2.6-.6 1.1-1.2 1.5-.6.3-1.2.5-2 .5h-1c-.9 0-1.7.1-2.4.5-.9.4-1.3 1-1.3 1.8 0 1 .6 1.7 1.8 2.3 1 .4 2 .6 3.1.6 1.4 0 2.6-.3 3.5-1 1-.6 1.6-1.6 1.6-3 0-.9-.4-2.1-1-3.8-.7-1.7-1-3-1-4C13.7 1.7 16.1.2 21 0h2c1.2 0 2.2.2 3.1.5 1.2.5 1.8 1.2 1.9 2.2 0 1-.5 1.8-1.5 2.5H22Z"/></svg>
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "files": {
3
3
  "main.css": "./static/css/main.cf25d33e.css",
4
- "main.js": "./static/js/main.438c6c47.js",
4
+ "main.js": "./static/js/main.f3c386d7.js",
5
5
  "static/js/453.abd36b29.chunk.js": "./static/js/453.abd36b29.chunk.js",
6
6
  "static/media/roboto-latin-700-normal.woff2": "./static/media/roboto-latin-700-normal.4535474e1cf8598695ad.woff2",
7
7
  "static/media/roboto-latin-500-normal.woff2": "./static/media/roboto-latin-500-normal.7077203b1982951ecf76.woff2",
@@ -61,11 +61,11 @@
61
61
  "static/media/roboto-greek-ext-400-normal.woff": "./static/media/roboto-greek-ext-400-normal.16eb83b4a3b1ea994243.woff",
62
62
  "index.html": "./index.html",
63
63
  "main.cf25d33e.css.map": "./static/css/main.cf25d33e.css.map",
64
- "main.438c6c47.js.map": "./static/js/main.438c6c47.js.map",
64
+ "main.f3c386d7.js.map": "./static/js/main.f3c386d7.js.map",
65
65
  "453.abd36b29.chunk.js.map": "./static/js/453.abd36b29.chunk.js.map"
66
66
  },
67
67
  "entrypoints": [
68
68
  "static/css/main.cf25d33e.css",
69
- "static/js/main.438c6c47.js"
69
+ "static/js/main.f3c386d7.js"
70
70
  ]
71
71
  }
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="./"><link rel="icon" href="./matterbridge 32x32.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>Matterbridge</title><link rel="manifest" href="./manifest.json"/><script defer="defer" src="./static/js/main.438c6c47.js"></script><link href="./static/css/main.cf25d33e.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="./"><link rel="icon" href="./matterbridge 32x32.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>Matterbridge</title><link rel="manifest" href="./manifest.json"/><script defer="defer" src="./static/js/main.f3c386d7.js"></script><link href="./static/css/main.cf25d33e.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>