# Project export: Mirror Mirror On The Wall

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: Shrek is love, Shrek is life. Inspired by the Magic Mirror in the Dreamworks classic, we crafted this smart mirror from scratch, powered by a Raspberry Pi via a monitor looking through a 1-way glass.
- Devpost: https://devpost.com/software/mirror-mirror-on-the-wall-gu1or5
- GitHub: https://github.com/TriMPham98/MagicMirror
- Team: 1 GitHub contributor(s) — Tri (9 commits)

## Devpost submission (written by the team)

### Inspiration

Shrek is love, Shrek is life. Inspired by the Magic Mirror in the Dreamworks classic, we crafted this smart mirror from scratch, powered by a Raspberry Pi via a monitor looking through a 1-way glass.

### What it does

Our intention for this project was to build something beautiful and modern to display in our home. This mirror provides an elegant interface to view your favorite widgets (weather, stocks, calendar, etc). The Raspberry Pi features a microphone and camera module for hands-free interaction. If you ever wanted to feel what Lord Farquaad felt like in Shrek, this is the next best solution!

### How we built it

We started with a 3-D blueprint of the wooden computer frame and housing. This provided the dimensions needed to rip the lumber lengthwise with a table saw and cut the wood widthwise with a circular saw. From there, we used the Kreg Pocket Hole Jig to drill angled corner holes to join the wood pieces into a rectangle. Finally, we fastened the picture frame and supporting rectangle frame together. In the middle layer, we replaced the stock transparent glass with a 1-way mirror that lets you see your reflection but also light from the monitor behind it. For the monitor, we stripped all the plastic pieces to leave only the screen and sandwiched everything together. Finally, we designed the mirror's interface with the open-source library MagicMirror. The documentation provided many modular 3rd party widgets that are plug-and-play for the mirror's interface.

### Challenges we ran into

The primary challenges we encountered were during the initial phases of woodworking and during the final stages of using the Alexa module. We underestimated the width of the supporting wood required to not have the wood screws crack the wood. Our initial prototype had a width of 1.5", and the wood screws split the lumber right at the corner. Widening the beams to 2" solved this problem. Our second main challenge nearing the completion of the project is the integration of the Alexa Assistant and its corresponding MagicMirror module. The debugging process for physical hardware items presents a new dimension of things that can go wrong. In addition, some terminal commands were outdated and bottlenecked us from proceeding.

### Accomplishments we're proud of

This was our first Raspberry Pi and woodworking project! As Computer Science majors, this was our first opportunity to work with hardware and integrate the full-stack experience of the user interface, accessing backend APIs, and housing the technology in a presentable package. For a two-person team, we've successfully built an integrated and modern piece of fine furniture in 48 hours.

### What we learned

We learned how to craft lumber and code all in a new platform of the Raspberry Pi. This was a phenomenal hands-on project!

### What's next

We have a Raspberry Pi Camera module attached and on standby! We're looking forward to precisely drilling the camera port and using OpenCV to allow users to interact with the mirror through gestures and voice.

## README (from the GitHub repository)

![MagicMirror²: The open source modular smart mirror platform. ](.github/header.png)

<p style="text-align: center">
  <a href="https://choosealicense.com/licenses/mit">
		<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License">
	</a>
	<img src="https://img.shields.io/github/actions/workflow/status/magicmirrororg/magicmirror/automated-tests.yaml" alt="GitHub Actions">
	<img src="https://img.shields.io/github/check-runs/magicmirrororg/magicmirror/master" alt="Build Status">
	<a href="https://github.com/MagicMirrorOrg/MagicMirror">
		<img src="https://img.shields.io/github/stars/magicmirrororg/magicmirror?style=social">
	</a>
</p>

**MagicMirror²** is an open source modular smart mirror platform. With a growing list of installable modules, the **MagicMirror²** allows you to convert your hallway or bathroom mirror into your personal assistant. **MagicMirror²** is built by the creator of [the original MagicMirror](https://michaelteeuw.nl/tagged/magicmirror) with the incredible help of a [growing community of contributors](https://github.com/MagicMirrorOrg/MagicMirror/graphs/contributors).

MagicMirror² focuses on a modular plugin system and uses [Electron](https://www.electronjs.org/) as an application wrapper. So no more web server or browser installs necessary!

## Documentation

For the full documentation including **[installation instructions](https://docs.magicmirror.builders/getting-started/installation.html)**, please visit our dedicated documentation website: [https://docs.magicmirror.builders](https://docs.magicmirror.builders).

## Links

- Website: [https://magicmirror.builders](https://magicmirror.builders)
- Documentation: [https://docs.magicmirror.builders](https://docs.magicmirror.builders)
- Forum: [https://forum.magicmirror.builders](https://forum.magicmirror.builders)
  - Technical discussions: https://forum.magicmirror.builders/category/11/core-system
- Discord: [https://discord.gg/J5BAtvx](https://discord.gg/J5BAtvx)
- Blog: [https://michaelteeuw.nl/tagged/magicmirror](https://michaelteeuw.nl/tagged/magicmirror)
- Donations: [https://magicmirror.builders/#donate](https://magicmirror.builders/#donate)

## Contributing Guidelines

Contributions of all kinds are welcome, not only in the form of code but also with regards to

- bug reports
- documentation
- translations

For the full contribution guidelines, check out: [https://docs.magicmirror.builders/about/contributing.html](https://docs.magicmirror.builders/about/contributing.html)

## Enjoying MagicMirror? Consider a donation!

MagicMirror² is Open Source and free. That doesn't mean we don't need any money.

Please consider a donation to help us cover the ongoing costs like webservers and email services.
If we receive enough donations we might even be able to free up some working hours and spend some extra time improving the MagicMirror² core.

To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link.

<p style="text-align: center">
	<a href="https://forum.magicmirror.builders/topic/728/magicmirror-is-voted-number-1-in-the-magpi-top-50"><img src="https://magicmirror.builders/img/magpi-best-watermark-custom.png" width="150" alt="MagPi Top 50"></a>
</p>


## Detected evidence (automated analysis)

Indexed codebase: 212 recognized source files, 734 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (120 of 313)

```
.editorconfig
.gitattributes
.github/CONTRIBUTING.md
.github/dependabot.yaml
.github/FUNDING.yaml
.github/ISSUE_TEMPLATE.md
.github/PULL_REQUEST_TEMPLATE.md
.github/stale.yaml
.github/workflows/automated-tests.yaml
.github/workflows/dep-review.yaml
.github/workflows/electron-rebuild.yaml
.github/workflows/enforce-pullrequest-rules.yaml
.gitignore
.husky/pre-commit
.npmrc
.prettierignore
.prettierrc.json
.stylelintrc.json
CHANGELOG.md
clientonly/index.js
Collaboration.md
config/config.js
config/config.js.sample
cspell.config.json
css/custom.css.sample
css/main.css
eslint.config.mjs
fonts/package.json
fonts/roboto.css
index.html
jest.config.js
js/animateCSS.js
js/app.js
js/check_config.js
js/class.js
js/defaults.js
js/deprecated.js
js/electron.js
js/loader.js
js/logger.js
js/main.js
js/module.js
js/node_helper.js
js/server_functions.js
js/server.js
js/socketclient.js
js/translator.js
js/utils.js
jsconfig.json
LICENSE.md
module-types.ts
modules/default/alert/alert.js
modules/default/alert/notificationFx.js
modules/default/alert/README.md
modules/default/alert/styles/center.css
modules/default/alert/styles/left.css
modules/default/alert/styles/notificationFx.css
modules/default/alert/styles/right.css
modules/default/alert/templates/alert.njk
modules/default/alert/templates/notification.njk
modules/default/alert/translations/bg.json
modules/default/alert/translations/da.json
modules/default/alert/translations/de.json
modules/default/alert/translations/en.json
modules/default/alert/translations/es.json
modules/default/alert/translations/fr.json
modules/default/alert/translations/hu.json
modules/default/alert/translations/nl.json
modules/default/alert/translations/ru.json
modules/default/alert/translations/th.json
modules/default/calendar/calendar.css
modules/default/calendar/calendar.js
modules/default/calendar/calendarfetcher.js
modules/default/calendar/calendarfetcherutils.js
modules/default/calendar/calendarutils.js
modules/default/calendar/debug.js
modules/default/calendar/node_helper.js
modules/default/calendar/README.md
modules/default/calendar/windowsZones.json
modules/default/clock/clock_styles.css
modules/default/clock/clock.js
modules/default/clock/README.md
modules/default/compliments/compliments.js
modules/default/compliments/README.md
modules/default/defaultmodules.js
modules/default/helloworld/helloworld.js
modules/default/helloworld/helloworld.njk
modules/default/helloworld/README.md
modules/default/updatenotification/git_helper.js
modules/default/updatenotification/node_helper.js
modules/default/updatenotification/README.md
modules/default/updatenotification/update_helper.js
modules/default/updatenotification/updatenotification.css
modules/default/updatenotification/updatenotification.js
modules/default/updatenotification/updatenotification.njk
modules/default/utils.js
modules/default/weather/current.njk
modules/default/weather/forecast.njk
modules/default/weather/hourly.njk
modules/default/weather/providers/envcanada.js
modules/default/weather/providers/openmeteo.js
modules/default/weather/providers/openweathermap.js
modules/default/weather/providers/overrideWrapper.js
modules/default/weather/providers/pirateweather.js
modules/default/weather/providers/README.md
modules/default/weather/providers/smhi.js
modules/default/weather/providers/ukmetoffice.js
modules/default/weather/providers/ukmetofficedatahub.js
modules/default/weather/providers/weatherbit.js
modules/default/weather/providers/weatherflow.js
modules/default/weather/providers/weathergov.js
modules/default/weather/providers/yr.js
modules/default/weather/README.md
modules/default/weather/weather.css
modules/default/weather/weather.js
modules/default/weather/weatherobject.js
modules/default/weather/weatherprovider.js
modules/default/weather/weatherutils.js
package.json
README.md
[193 more files omitted for size]
```

### Dependencies

- fonts/package.json: @fontsource/roboto@^5.1.0, @fontsource/roboto-condensed@^5.1.0
- package.json: @eslint/js@^9.11.1, @stylistic/eslint-plugin@^2.8.0, ajv@^8.17.1, ansis@^3.3.2, console-stamp@^3.1.2, cspell@^8.14.4, electron@^31.6.0, envsub@^4.1.0, eslint@^9.11.1, eslint-plugin-jest@^28.8.3, eslint-plugin-jsdoc@^50.3.0, eslint-plugin-package-json@^0.15.3, express@^4.21.0, express-basic-auth@^1.2.1, express-ipfilter@^1.3.2, feedme@^2.0.2, groq-sdk@^0.7.0, helmet@^7.1.0, html-to-text@^9.0.5, husky@^9.1.6, iconv-lite@^0.6.3, jest@^29.7.0, jsdom@^25.0.1, lint-staged@^15.2.10, module-alias@^2.2.3, moment@^2.30.1, node-ical@0.18.0, node-record-lpcm16@^1.0.1, playwright@^1.47.2, pm2@^5.4.2, prettier@^3.3.3, sinon@^19.0.2, socket.io@^4.8.0, stylelint@^16.9.0, stylelint-config-standard@^36.0.1, stylelint-prettier@^5.0.2, suncalc@^1.9.0, systeminformation@^5.23.5

### Recent commits (newest first)

- Add electron options
- Add Bitcoin and Dogecoin tracker
- Rename stock module header
- Add  stock ticker and decrease animation speed
- Add stock ticker for
- Move compliments module down
- Replace APOD with JARVIS animation
- Change weather location to San Jose
- Extend pic refresh time to 5 hours
- Edit greeting messages
- Add default compliment module bottom center
- Add default modules
- Increase image refresh rate
- Comment out APOD module
- Change units to imperial for temperature
- Change weather location to San Francisco
- Update compliments
- Remove newsfeed module
- Remove comments
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### LICENSE.md

```markdown
# The MIT License (MIT)

Copyright © 2016-2024 Michael Teeuw

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

**The software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.**

```

### Collaboration.md

```markdown
This document describes how collaborators of this repository should work together.

## Pull Requests

- never merge your own PR's
- never merge without someone having approved (approving and merging from same person is allowed)
- wait for all approvals requested (or the author decides something different in the comments)
- merge to `master` only for releases or other urgent issues (update notification is only triggered by tags)
- merges to master should be tagged with the "mastermerge" label so that the test runs through

## Issues

- "real" Issues are closed if the problem is solved and the fix is released
- unrelated Issues (e.g. related to a foreign module) are closed immediately with a comment to open an issue in the module repository or to discuss this further in the forum or discord

## Releases

Are done by

- [ ] @rejas
- [ ] @sdetweil
- [ ] @khassel

### Pre-Deployment steps

- [ ] update dependencies (a few days before)

### Deployment steps

- [ ] pull latest `develop` branch
- [ ] update `package.json` and `package-lock.json` to reflect correct version number `2.xx.0`
- [ ] test `develop` branch
- [ ] update `CHANGELOG.md`
  - [ ] add all contributor names: `...`
  - [ ] add min. node version: > ⚠️ This release needs nodejs version `v20` or `v22`, minimum version is `v20.9.0`
- [ ] commit and push all changes
- [ ] after successful test run via github actions: create pull request from `develop` to `master` branch
  - [ ] add label `mastermerge`
  - [ ] title of the PR is `Release 2.xx.0`
  - [ ] description of the PR is the section of the `CHANGELOG.md`
- [ ] after PR tests run without issues, merge PR
- [ ] create new release with
  - [ ] corresponding version tag `v2.xx.0`
  - [ ] a release name: `...`
  - [ ] description of the PR is the section of the `CHANGELOG.md`

### Draft new development release

- [ ] checkout `develop` branch
- [ ] update `package.json` and `package-lock.json` to reflect correct version number `2.xx.0-develop`
- [ ] draft new section in `CHANGELOG.md`
- [ ] commit and publish `develop` branch

### After release

- [ ] publish release notes with link to github release on forum in new locked topic
- [ ] close all issues with label `ready (coming with next release)`
- [ ] release new documentation by merging `develop` on `master` in documentation repository

```

### package.json

```
{
	"name": "magicmirror",
	"version": "2.29.0",
	"description": "The open source modular smart mirror platform.",
	"keywords": [
		"magic mirror",
		"magicmirror",
		"smart mirror",
		"mirror UI",
		"modular"
	],
	"homepage": "https://magicmirror.builders",
	"bugs": {
		"url": "https://github.com/MagicMirrorOrg/MagicMirror/issues"
	},
	"repository": {
		"type": "git",
		"url": "https://github.com/MagicMirrorOrg/MagicMirror"
	},
	"license": "MIT",
	"author": "Michael Teeuw",
	"contributors": [
		"https://github.com/MagicMirrorOrg/MagicMirror/graphs/contributors"
	],
	"main": "js/electron.js",
	"scripts": {
		"start": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js",
		"start:dev": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js dev",
		"server": "node ./serveronly",
		"install-mm": "npm install --no-audit --no-fund --no-update-notifier --only=prod --omit=dev",
		"install-mm:dev": "npm install --no-audit --no-fund --no-update-notifier",
		"install-vendor": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error --no-audit --no-fund --no-update-notifier",
		"install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && npm install --loglevel=error --no-audit --no-fund --no-update-notifier",
		"postinstall": "npm run install-vendor && npm run install-fonts && echo \"MagicMirror² installation finished successfully! \n\"",
		"test": "NODE_ENV=test jest -i --forceExit",
		"test:coverage": "NODE_ENV=test jest --coverage -i --verbose false --forceExit",
		"test:electron": "NODE_ENV=test jest --selectProjects electron -i --forceExit",
		"test:e2e": "NODE_ENV=test jest --selectProjects e2e -i --forceExit",
		"test:unit": "NODE_ENV=test jest --selectProjects unit",
		"test:prettier": "prettier . --check",
		"test:js": "eslint .",
		"test:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json",
		"test:calendar": "node ./modules/default/calendar/debug.js",
		"test:spelling": "cspell . --gitignore",
		"config:check": "node js/check_config.js",
		"lint:prettier": "prettier . --write",
		"lint:js": "eslint . --fix",
		"lint:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json --fix",
		"lint:staged": "lint-staged",
		"prepare": "[ -f node_modules/.bin/husky ] && husky || echo no husky installed."
	},
	"lint-staged": {
		"*": "prettier --write",
		"*.js": "eslint --fix",
		"*.css": "stylelint --fix"
	},
	"dependencies": {
		"ajv": "^8.17.1",
		"ansis": "^3.3.2",
		"console-stamp": "^3.1.2",
		"envsub": "^4.1.0",
		"eslint": "^9.11.1",
		"express": "^4.21.0",
		"express-ipfilter": "^1.3.2",
		"feedme": "^2.0.2",
		"groq-sdk": "^0.7.0",
		"helmet": "^7.1.0",
		"html-to-text": "^9.0.5",
		"iconv-lite": "^0.6.3",
		"module-alias": "^2.2.3",
		"moment": "^2.30.1",
		"node-ical": "0.18.0",
		"node-record-lpcm16": "^1.0.1",
		"pm2": "^5.4.2",
		"socket.io": "^4.8.0",
		"suncalc": "^1.9.0",
		"systeminformation": "^5.23.5"
	},
	"devDependencies": {
		"@eslint/js": "^9.11.1",
		"@stylistic/eslint-plugin": "^2.8.0",
		"cspell": "^8.14.4",
		"eslint-plugin-jest": "^28.8.3",
		"eslint-plugin-jsdoc": "^50.3.0",
		"eslint-plugin-package-json": "^0.15.3",
		"express-basic-auth": "^1.2.1",
		"husky": "^9.1.6",
		"jest": "^29.7.0",
		"jsdom": "^25.0.1",
		"lint-staged": "^15.2.10",
		"playwright": "^1.47.2",
		"prettier": "^3.3.3",
		"sinon": "^19.0.2",
		"stylelint": "^16.9.0",
		"stylelint-config-standard": "^36.0.1",
		"stylelint-prettier": "^5.0.2"
	},
	"optionalDependencies": {
		"electron": "^31.6.0"
	},
	"engines": {
		"node": ">=20.9.0 <21 || 22"
	},
	"_moduleAliases": {
		"node_helper": "js/node_helper.js",
		"logger": "js/logger.js"
	}
}

```

### fonts/package.json

```
{
	"name": "magicmirror-fonts",
	"version": "1.0.0",
	"description": "Package for fonts use by MagicMirror² core.",
	"bugs": {
		"url": "https://github.com/MagicMirrorOrg/MagicMirror/issues"
	},
	"repository": {
		"type": "git",
		"url": "https://github.com/MagicMirrorOrg/MagicMirror"
	},
	"license": "MIT",
	"dependencies": {
		"@fontsource/roboto": "^5.1.0",
		"@fontsource/roboto-condensed": "^5.1.0"
	}
}

```

### serveronly/index.js

```javascript
const app = require("../js/app");
const Log = require("../js/logger");

app.start().then((config) => {
	const bindAddress = config.address ? config.address : "localhost";
	const httpType = config.useHttps ? "https" : "http";
	Log.info(`\n>>>   Ready to go! Please point your browser to: ${httpType}://${bindAddress}:${config.port}   <<<`);
});

```

### js/server.js

```javascript
const fs = require("node:fs");
const http = require("node:http");
const https = require("node:https");
const path = require("node:path");
const express = require("express");
const ipfilter = require("express-ipfilter").IpFilter;
const helmet = require("helmet");
const socketio = require("socket.io");

const Log = require("logger");
const { cors, getConfig, getHtml, getVersion, getStartup, getEnvVars } = require("./server_functions");

/**
 * Server
 * @param {object} config The MM config
 * @class
 */
function Server (config) {
	const app = express();
	const port = process.env.MM_PORT || config.port;
	const serverSockets = new Set();
	let server = null;

	/**
	 * Opens the server for incoming connections
	 * @returns {Promise} A promise that is resolved when the server listens to connections
	 */
	this.open = function () {
		return new Promise((resolve) => {
			if (config.useHttps) {
				const options = {
					key: fs.readFileSync(config.httpsPrivateKey),
					cert: fs.readFileSync(config.httpsCertificate)
				};
				server = https.Server(options, app);
			} else {
				server = http.Server(app);
			}
			const io = socketio(server, {
				cors: {
					origin: /.*$/,
					credentials: true
				},
				allowEIO3: true
			});

			server.on("connection", (socket) => {
				serverSockets.add(socket);
				socket.on("close", () => {
					serverSockets.delete(socket);
				});
			});

			Log.log(`Starting server on port ${port} ... `);
			server.listen(port, config.address || "localhost");

			if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {
				Log.warn("You're using a full whitelist configuration to allow for all IPs");
			}

			app.use(function (req, res, next) {
				ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) {
					if (err === undefined) {
						res.header("Access-Control-Allow-Origin", "*");
						return next();
					}
					Log.log(err.message);
					res.status(403).send("This device is not allowed to access your mirror. <br> Please check your config.js or config.js.sample to change this.");
				});
			});

			app.use(helmet(config.httpHeaders));
			app.use("/js", express.static(__dirname));

			let directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations"];
			if (process.env.JEST_WORKER_ID !== undefined) {
				// add tests directories only when running tests
				directories.push("/tests/configs", "/tests/mocks");
			}
			for (const directory of directories) {
				app.use(directory, express.static(path.resolve(global.root_path + directory)));
			}

			app.get("/cors", async (req, res) => await cors(req, res));

			app.get("/version", (req, res) => getVersion(req, res));

			app.get("/config", (req, res) => getConfig(req, res));

			app.get("/startup", (req, res) => getStartup(req, res));

			app.get("/env", (req, res) => getEnvVars(req, res));

			app.get("/", (req, res) => getHtml(req, res));

			server.on("listening", () => {
				resolve({
					app,
					io
				});
			});
		});
	};

	/**
	 * Closes the server and destroys all lingering connections to it.
	 * @returns {Promise} A promise that resolves when server has successfully shut down
	 */
	this.close = function () {
		return new Promise((resolve) => {
			for (const socket of serverSockets.values()) {
				socket.destroy();
			}
			server.close(resolve);
		});
	};
}

module.exports = Server;

```

### clientonly/index.js

```javascript
"use strict";

// Use separate scope to prevent global scope pollution
(function () {
	const config = {};

	/**
	 * Helper function to get server address/hostname from either the commandline or env
	 */
	function getServerAddress () {

		/**
		 * Get command line parameters
		 * Assumes that a cmdline parameter is defined with `--key [value]`
		 * @param {string} key key to look for at the command line
		 * @param {string} defaultValue value if no key is given at the command line
		 * @returns {string} the value of the parameter
		 */
		function getCommandLineParameter (key, defaultValue = undefined) {
			const index = process.argv.indexOf(`--${key}`);
			const value = index > -1 ? process.argv[index + 1] : undefined;
			return value !== undefined ? String(value) : defaultValue;
		}

		// Prefer command line arguments over environment variables
		["address", "port"].forEach((key) => {
			config[key] = getCommandLineParameter(key, process.env[key.toUpperCase()]);
		});

		// determine if "--use-tls"-flag was provided
		config["tls"] = process.argv.indexOf("--use-tls") > 0;
	}

	/**
	 * Gets the config from the specified server url
	 * @param {string} url location where the server is running.
	 * @returns {Promise} the config
	 */
	function getServerConfig (url) {
		// Return new pending promise
		return new Promise((resolve, reject) => {
			// Select http or https module, depending on requested url
			const lib = url.startsWith("https") ? require("node:https") : require("node:http");
			const request = lib.get(url, (response) => {
				let configData = "";

				// Gather incoming data
				response.on("data", function (chunk) {
					configData += chunk;
				});
				// Resolve promise at the end of the HTTP/HTTPS stream
				response.on("end", function () {
					resolve(JSON.parse(configData));
				});
			});

			request.on("error", function (error) {
				reject(new Error(`Unable to read config from server (${url} (${error.message}`));
			});
		});
	}

	/**
	 * Print a message to the console in case of errors
	 * @param {string} message error message to print
	 * @param {number} code error code for the exit call
	 */
	function fail (message, code = 1) {
		if (message !== undefined && typeof message === "string") {
			console.log(message);
		} else {
			console.log("Usage: 'node clientonly --address 192.168.1.10 --port 8080 [--use-tls]'");
		}
		process.exit(code);
	}

	getServerAddress();

	(config.address && config.port) || fail();
	const prefix = config.tls ? "https://" : "http://";

	// Only start the client if a non-local server was provided
	if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) === -1) {
		getServerConfig(`${prefix}${config.address}:${config.port}/config/`)
			.then(function (configReturn) {
				// Pass along the server config via an environment variable
				const env = Object.create(process.env);
				env.clientonly = true; // set to pass to electron.js
				const options = { env: env };
				configReturn.address = config.address;
				configReturn.port = config.port;
				configReturn.tls = config.tls;
				env.config = JSON.stringify(configReturn);

				// Spawn electron application
				const electron = require("electron");
				const child = require("node:child_process").spawn(electron, ["js/electron.js"], options);

				// Pipe all child process output to current stdout
				child.stdout.on("data", function (buf) {
					process.stdout.write(`Client: ${buf}`);
				});

				// Pipe all child process errors to current stderr
				child.stderr.on("data", function (buf) {
					process.stderr.write(`Client: ${buf}`);
				});

				child.on("error", function (err) {
					process.stdout.write(`Client: ${err}`);
				});

				child.on("close", (code) => {
					if (code !== 0) {
						console.log(`There something wrong. The clientonly is not running code ${code}`);
					}
				});
			})
			.catch(function (reason) {
				fail(`Unable to connect to server: (${reason})`);
			});
	} else {
		fail();
	}
}());

```

### js/app.js

```javascript
// Alias modules mentioned in package.js under _moduleAliases.
require("module-alias/register");

const fs = require("node:fs");
const path = require("node:path");
const envsub = require("envsub");
const Log = require("logger");

const Server = require(`${__dirname}/server`);
const Utils = require(`${__dirname}/utils`);
const defaultModules = require(`${__dirname}/../modules/default/defaultmodules`);
const { getEnvVarsAsObj } = require(`${__dirname}/server_functions`);

// Get version number.
global.version = require(`${__dirname}/../package.json`).version;
Log.log(`Starting MagicMirror: v${global.version}`);

// Log system information.
Utils.logSystemInformation();

// global absolute root path
global.root_path = path.resolve(`${__dirname}/../`);

if (process.env.MM_CONFIG_FILE) {
	global.configuration_file = process.env.MM_CONFIG_FILE.replace(`${global.root_path}/`, "");
}

// FIXME: Hotfix Pull Request
// https://github.com/MagicMirrorOrg/MagicMirror/pull/673
if (process.env.MM_PORT) {
	global.mmPort = process.env.MM_PORT;
}

// The next part is here to prevent a major exception when there
// is no internet connection. This could probable be solved better.
process.on("uncaughtException", function (err) {
	// ignore strange exceptions under aarch64 coming from systeminformation:
	if (!err.stack.includes("node_modules/systeminformation")) {
		Log.error("Whoops! There was an uncaught exception...");
		Log.error(err);
		Log.error("MagicMirror² will not quit, but it might be a good idea to check why this happened. Maybe no internet connection?");
		Log.error("If you think this really is an issue, please open an issue on GitHub: https://github.com/MagicMirrorOrg/MagicMirror/issues");
	}
});

/**
 * The core app.
 * @class
 */
function App () {
	let nodeHelpers = [];
	let httpServer;

	/**
	 * Loads the config file. Combines it with the defaults and returns the config
	 * @async
	 * @returns {Promise<object>} the loaded config or the defaults if something goes wrong
	 */
	async function loadConfig () {
		Log.log("Loading config ...");
		const defaults = require(`${__dirname}/defaults`);
		if (process.env.JEST_WORKER_ID !== undefined) {
			// if we are running with jest
			defaults.address = "0.0.0.0";
		}

		// For this check proposed to TestSuite
		// https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8
		const configFilename = path.resolve(global.configuration_file || `${global.root_path}/config/config.js`);
		let templateFile = `${configFilename}.template`;

		// check if templateFile exists
		try {
			fs.accessSync(templateFile, fs.F_OK);
		} catch (err) {
			templateFile = null;
			Log.log("config template file not exists, no envsubst");
		}

		if (templateFile) {
			// save current config.js
			try {
				if (fs.existsSync(configFilename)) {
					fs.copyFileSync(configFilename, `${configFilename}-old`);
				}
			} catch (err) {
				Log.warn(`Could not copy ${configFilename}: ${err.message}`);
			}

			// check if config.env exists
			const envFiles = [];
			const configEnvFile = `${configFilename.substr(0, configFilename.lastIndexOf("."))}.env`;
			try {
				if (fs.existsSync(configEnvFile)) {
					envFiles.push(configEnvFile);
				}
			} catch (err) {
				Log.log(`${configEnvFile} does not exist. ${err.message}`);
			}

			let options = {
				all: true,
				diff: false,
				envFiles: envFiles,
				protect: false,
				syntax: "default",
				system: true
			};

			// envsubst variables in templateFile and create new config.js
			// naming for envsub must be templateFile and outputFile
			const outputFile = configFilename;
			try {
				await envsub({ templateFile, outputFile, options });
			} catch (err) {
				Log.error(`Could not envsubst variables: ${err.message}`);
			}
		}

		require(`${global.root_path}/js/check_config.js`);

		try {
			fs.accessSync(configFilename, fs.F_OK);
			const c = require(configFilename);
			if (Object.keys(c).length === 0) {
				Log.error("WARNING! Config file appears empty, maybe missing module.exports last line?");
			}
			checkDeprecatedOptions(c);
			return Object.assign(defaults, c);
		} catch (e) {
			if (e.code === "ENOENT") {
				Log.error("WARNING! Could not find config file. Please create one. Starting with default configuration.");
			} else if (e instanceof ReferenceError || e instanceof SyntaxError) {
				Log.error(`WARNING! Could not validate config file. Starting with default configuration. Please correct syntax errors at or above this line: ${e.stack}`);
			} else {
				Log.error(`WARNING! Could not load config file. Starting with default configuration. Error found: ${e}`);
			}
		}

		return defaults;
	}

	/**
	 * Checks the config for deprecated options and throws a warning in the logs
	 * if it encounters one option from the deprecated.js list
	 * @param {object} userConfig The user config
	 */
	function checkDeprecatedOptions (userConfig) {
		const deprecated = require(`${global.root_path}/js/deprecated`);
		const deprecatedOptions = deprecated.configs;

		const usedDeprecated = deprecatedOptions.filter((option) => userConfig.hasOwnProperty(option));
		if (usedDeprecated.length > 0) {
			Log.warn(`WARNING! Your config is using deprecated options: ${usedDeprecated.join(", ")}. Check README and CHANGELOG for more up-to-date ways of getting the same functionality.`);
		}
	}

	/**
	 * Loads a specific module.
	 * @param {string} module The name of the module (including subpath).
	 */
	function loadModule (module) {
		const elements = module.split("/");
		const moduleName = elements[elements.length - 1];
		const env = getEnvVarsAsObj();
		let moduleFolder = `${__dirname}/../${env.modulesDir}/${module}`;

		if (defaultModules.includes(moduleName)) {
			const defaultModuleFolder = `${__dirname}/../modules/default/${module}`;
			if (process.env.JEST_WORKER_ID === undefined) {
				moduleFolder = defaultModuleFolder;
			} else {
				// running in Jest, allow defaultModules placed under moduleDir for testing
				if (env.modules
[truncated — 5839 more characters]
```

### js/main.js

```javascript
/* global Loader, defaults, Translator, addAnimateCSS, removeAnimateCSS, AnimateCSSIn, AnimateCSSOut, modulePositions */

const MM = (function () {
	let modules = [];

	/* Private Methods */

	/**
	 * Create dom objects for all modules that are configured for a specific position.
	 */
	const createDomObjects = function () {
		const domCreationPromises = [];

		modules.forEach(function (module) {
			if (typeof module.data.position !== "string") {
				return;
			}

			let haveAnimateIn = null;
			// check if have valid animateIn in module definition (module.data.animateIn)
			if (module.data.animateIn && AnimateCSSIn.indexOf(module.data.animateIn) !== -1) haveAnimateIn = module.data.animateIn;

			const wrapper = selectWrapper(module.data.position);

			const dom = document.createElement("div");
			dom.id = module.identifier;
			dom.className = module.name;

			if (typeof module.data.classes === "string") {
				dom.className = `module ${dom.className} ${module.data.classes}`;
			}

			dom.opacity = 0;
			wrapper.appendChild(dom);

			const moduleHeader = document.createElement("header");
			moduleHeader.innerHTML = module.getHeader();
			moduleHeader.className = "module-header";
			dom.appendChild(moduleHeader);

			if (typeof module.getHeader() === "undefined" || module.getHeader() !== "") {
				moduleHeader.style.display = "none;";
			} else {
				moduleHeader.style.display = "block;";
			}

			const moduleContent = document.createElement("div");
			moduleContent.className = "module-content";
			dom.appendChild(moduleContent);

			// create the domCreationPromise with AnimateCSS (with animateIn of module definition)
			// or just display it
			var domCreationPromise;
			if (haveAnimateIn) domCreationPromise = updateDom(module, { options: { speed: 1000, animate: { in: haveAnimateIn } } }, true);
			else domCreationPromise = updateDom(module, 0);

			domCreationPromises.push(domCreationPromise);
			domCreationPromise
				.then(function () {
					sendNotification("MODULE_DOM_CREATED", null, null, module);
				})
				.catch(Log.error);
		});

		updateWrapperStates();

		Promise.all(domCreationPromises).then(function () {
			sendNotification("DOM_OBJECTS_CREATED");
		});
	};

	/**
	 * Select the wrapper dom object for a specific position.
	 * @param {string} position The name of the position.
	 * @returns {HTMLElement | void} the wrapper element
	 */
	const selectWrapper = function (position) {
		const classes = position.replace("_", " ");
		const parentWrapper = document.getElementsByClassName(classes);
		if (parentWrapper.length > 0) {
			const wrapper = parentWrapper[0].getElementsByClassName("container");
			if (wrapper.length > 0) {
				return wrapper[0];
			}
		}
	};

	/**
	 * Send a notification to all modules.
	 * @param {string} notification The identifier of the notification.
	 * @param {*} payload The payload of the notification.
	 * @param {Module} sender The module that sent the notification.
	 * @param {Module} [sendTo] The (optional) module to send the notification to.
	 */
	const sendNotification = function (notification, payload, sender, sendTo) {
		for (const m in modules) {
			const module = modules[m];
			if (module !== sender && (!sendTo || module === sendTo)) {
				module.notificationReceived(notification, payload, sender);
			}
		}
	};

	/**
	 * Update the dom for a specific module.
	 * @param {Module} module The module that needs an update.
	 * @param {object|number} [updateOptions] The (optional) number of microseconds for the animation or object with updateOptions (speed/animates)
	 * @param {boolean} [createAnimatedDom] for displaying only animateIn (used on first start of MagicMirror)
	 * @returns {Promise} Resolved when the dom is fully updated.
	 */
	const updateDom = function (module, updateOptions, createAnimatedDom = false) {
		return new Promise(function (resolve) {
			let speed = updateOptions;
			let animateOut = null;
			let animateIn = null;
			if (typeof updateOptions === "object") {
				if (typeof updateOptions.options === "object" && updateOptions.options.speed !== undefined) {
					speed = updateOptions.options.speed;
					Log.debug(`updateDom: ${module.identifier} Has speed in object: ${speed}`);
					if (typeof updateOptions.options.animate === "object") {
						animateOut = updateOptions.options.animate.out;
						animateIn = updateOptions.options.animate.in;
						Log.debug(`updateDom: ${module.identifier} Has animate in object: out->${animateOut}, in->${animateIn}`);
					}
				} else {
					Log.debug(`updateDom: ${module.identifier} Has no speed in object`);
					speed = 0;
				}
			}

			const newHeader = module.getHeader();
			let newContentPromise = module.getDom();

			if (!(newContentPromise instanceof Promise)) {
				// convert to a promise if not already one to avoid if/else's everywhere
				newContentPromise = Promise.resolve(newContentPromise);
			}

			newContentPromise
				.then(function (newContent) {
					const updatePromise = updateDomWithContent(module, speed, newHeader, newContent, animateOut, animateIn, createAnimatedDom);

					updatePromise.then(resolve).catch(Log.error);
				})
				.catch(Log.error);
		});
	};

	/**
	 * Update the dom with the specified content
	 * @param {Module} module The module that needs an update.
	 * @param {number} [speed] The (optional) number of microseconds for the animation.
	 * @param {string} newHeader The new header that is generated.
	 * @param {HTMLElement} newContent The new content that is generated.
	 * @param {string} [animateOut] AnimateCss animation name before hidden
	 * @param {string} [animateIn] AnimateCss animation name on show
	 * @param {boolean} [createAnimatedDom] for displaying only animateIn (used on first start)
	 * @returns {Promise} Resolved when the module dom has been updated.
	 */
	const updateDomWithContent = function (module, speed, newHeader, newContent, animateOut, animateIn, createAnimatedDom = false) {
		return new Promise(function (resolve) {
			if (module.hidden
[truncated — 18997 more characters]
```

### jest.config.js

```javascript
module.exports = async () => {
	return {
		verbose: true,
		testTimeout: 20000,
		testSequencer: "<rootDir>/tests/utils/test_sequencer.js",
		projects: [
			{
				displayName: "unit",
				globalSetup: "<rootDir>/tests/unit/helpers/global-setup.js",
				moduleNameMapper: {
					logger: "<rootDir>/js/logger.js"
				},
				testMatch: ["**/tests/unit/**/*.[jt]s?(x)"],
				testPathIgnorePatterns: ["<rootDir>/tests/unit/mocks", "<rootDir>/tests/unit/helpers"]
			},
			{
				displayName: "electron",
				testMatch: ["**/tests/electron/**/*.[jt]s?(x)"],
				testPathIgnorePatterns: ["<rootDir>/tests/electron/helpers"]
			},
			{
				displayName: "e2e",
				setupFilesAfterEnv: ["<rootDir>/tests/e2e/helpers/mock-console.js"],
				testMatch: ["**/tests/e2e/**/*.[jt]s?(x)"],
				modulePaths: ["<rootDir>/js/"],
				testPathIgnorePatterns: ["<rootDir>/tests/e2e/helpers", "<rootDir>/tests/e2e/mocks"]
			}
		],
		collectCoverageFrom: ["./clientonly/**/*.js", "./js/**/*.js", "./modules/default/**/*.js", "./serveronly/**/*.js"],
		coverageReporters: ["lcov", "text"],
		coverageProvider: "v8"
	};
};

```

[194 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]