npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

homebridge-meural-cognito

v1.4.5

Published

Control Meural canvases via HomeKit using Netgear Cognito authentication.

Readme

Homebridge Meural Cognito

Maintained edition of Mike Knoop's homebridge-meural plugin. It preserves the original HomeKit accessories and local canvas controls while replacing Meural's retired legacy login with the current Netgear Accounts and AWS Cognito flow.

The original MIT license and Git history are retained. Upstream: https://github.com/mikeknoop/homebridge-meural.

Features

  • Canvas discovery through https://api.meural.com/v1
  • Netgear Accounts / AWS Cognito CUSTOM_AUTH
  • USER_PASSWORD_AUTH fallback used by account migration
  • Email, SMS, software-token, and custom challenge responses
  • Automatic access-token refresh
  • Refresh-token persistence with file mode 0600
  • HomeKit power and remote controls
  • Control Center remote actions: previous/next photo and sync now (the canvas advertises the TV category so the remote glyph appears; an already-paired canvas may need to be re-added to pick it up)
  • Single TV accessory in Home app with playlist inputs and remote controls
  • Optional Night Mode: per-canvas day and night iCloud Shared Albums swapped on a built-in schedule, plus a HomeKit switch, so a canvas can appear off at night without ever sleeping — or set night_sleep to simply blank the panel at night with no album to maintain
  • Docker image for amd64 and arm64-capable Node base images

Architecture

HomeKit -> Homebridge plugin -> Meural v1 cloud API
                         |
                         +-> Netgear Accounts -> AWS Cognito
                         |
                         +-> Canvas LAN HTTP controls

Cloud authentication and discovery use HTTPS. Power, brightness, remote keys, and artwork selection continue to call each canvas on the local network, matching the upstream plugin.

Authentication

The production web client currently:

  1. Starts Cognito CUSTOM_AUTH in eu-west-1.
  2. Answers password and, when required, OTP/MFA custom challenges.
  3. Falls back to USER_PASSWORD_AUTH for the observed account-migration error.
  4. Exchanges the Cognito access token for a Netgear authorization code.
  5. Exchanges that code for Meural access, ID, and refresh tokens.
  6. Sends Authorization: Token <access token> to the Meural API.
  7. Refreshes through Netgear Accounts with the Meural refresh token and public app key.

No PKCE, browser cookie, CSRF token, hosted UI, or SRP exchange was observed in the Meural web flow. See docs/auth-flow.md.

Credentials are needed only when no usable token file exists. After the first successful login, remove MEURAL_PASSWORD and MEURAL_CHALLENGE_RESPONSE from the environment.

Sign in from the Homebridge UI (recommended)

Open the plugin's settings in the Homebridge UI and use the Meural Account Sign-In panel. Enter your email and password; if Netgear emails a one-time code, type it into the panel right away. The whole exchange completes in one process while the Cognito challenge session is still valid, the resulting tokens are written to the token file, and the running plugin picks them up on its next request — no Homebridge restart and no password left in the config.

Or enter the code in the config form — also no restart. If you'd rather not use the panel, leave the settings open after the plugin requests a code, paste the code into the Challenge Response field, and save. The plugin persists each challenge (with its live Cognito session) the moment it's issued and watches config.json, so when you save the code it finishes the same pending sign-in in place — within a few seconds, without a restart, and without requesting a new code. Enter the code within a few minutes of the email (the Cognito session expires quickly), then clear the field once it succeeds.

Earlier versions required a Homebridge restart to apply a code typed into the config form, and that restart started a fresh login that emailed another code — an endless loop. Both entry paths now complete in place; a restart is no longer part of the flow.

The headless environment variable MEURAL_CHALLENGE_RESPONSE still works for fully unattended setups, but because it's only read at startup it keeps the old restart-and-race behavior; prefer the UI panel or the config form for interactive setups.

Login attempt throttling

A full login can make Netgear email a one-time code, so the plugin only ever runs one login at a time and pauses automatic logins after a failure: until a sign-in completes or Homebridge restarts when user action is needed (for example a code was requested), or for one minute after a transient network/server error. Without this, every HomeKit poll while unauthenticated would trigger its own login — and its own email.

The plugin also persists the Cognito trust identifier (<token file>.trust) so Netgear sees the same client identity across restarts instead of a brand-new device every time, which is what allows the service to skip the one-time code for a recognized client.

Homebridge Configuration

Install homebridge-meural-cognito from the Homebridge UI plugin search, then configure:

The source repository is private; releases are published to the public npm registry, so the Homebridge UI installs and updates the plugin as usual. See Releases for how versions reach npm.

{
  "platform": "MeuralCanvas",
  "account_email": "",
  "account_password": "",
  "challenge_response": "",
  "token_path": "/homebridge/meural/tokens.json",
  "control_center_remote": true,
  "exclude_devices": [],
  "shared_album_refresh_minutes": 0,
  "night_mode_name": "Night Mode",
  "night_sleep": false,
  "day_album_url": "",
  "night_album_url": "",
  "day_backlight": 50,
  "night_backlight": 0,
  "night_start": "",
  "night_end": "",
  "day_gallery": "",
  "night_gallery": "",
  "night_mode_devices": []
}

For backward compatibility, account_email and account_password still work. Environment variables take precedence and are preferred:

| Variable | Default | | --- | --- | | MEURAL_USERNAME | Homebridge account_email | | MEURAL_PASSWORD | Homebridge account_password | | MEURAL_CHALLENGE_RESPONSE | unset | | MEURAL_TOKEN_PATH | Homebridge storage directory | | MEURAL_API_BASE_URL | https://api.meural.com | | MEURAL_AWS_REGION | discovered public value | | MEURAL_COGNITO_CLIENT_ID | discovered public value | | MEURAL_COGNITO_USER_POOL_ID | discovered public value |

The public AWS and application identifiers are built in, so normal deployments do not need to set them.

Night Mode (day/night albums, appear off without sleeping)

A Meural Canvas disconnects from Wi-Fi when it sleeps, which kills all local control (Next Photo, brightness, remote keys); only cloud actions keep working. So scheduling the canvas to sleep overnight makes it unreachable, and a nightly reboot doesn't help — it just sleeps again.

Night Mode sidesteps this: instead of sleeping, each canvas has a Day Album and a Night Album — two public iCloud Shared Albums — and the plugin swaps between them on a schedule while dropping the backlight at night. Point the Night Album at an all-black photo and the canvas looks off while staying awake and reachable the whole time. Each canvas also gets its own HomeKit switch for manual/automation control.

Because a Meural only displays artwork stored in its own cloud account, the plugin uploads each album's photos into a dedicated Meural gallery it owns, then pushes the right gallery to the canvas at each boundary. Syncs are incremental and idempotent (a GUID → item map is persisted under <storage>/meural/, mode 0600), so only new photos are uploaded and photos removed from an album are removed from the gallery; your other Meural galleries are left alone.

Setup

  1. In Photos, create two shared albums and enable Public Website on each: one for your daytime pictures, and one containing a single pure-black image for night (fill the frame so no border shows). Copy each https://www.icloud.com/sharedalbum/#... link.
  2. Set day_album_url and night_album_url (globally as defaults, and/or per canvas — see below). Optionally tune day_backlight (default 50) and night_backlight (default 0, the darkest the panel allows — it clamps low values up to ~9, which is why the black image does the real work).
  3. Set shared_album_refresh_minutes to re-pull new photos automatically (0 = only on Homebridge start).
  4. Restart Homebridge. A Night Mode <canvas name> switch appears for each canvas. Turning it On shows the Night Album and lowers the backlight; Off restores the Day Album and backlight.

Both a day source and a night source must resolve for the feature to activate. A "source" is normally an album URL, but you can instead point at an existing Meural gallery — see Using an existing gallery.

Scheduling

Set night_start (Night Time) and night_end (Day Time) to 24-hour local times (e.g. "22:00" and "07:00") and the plugin swaps albums itself — no Home app automation needed. The window may wrap past midnight, and the plugin asserts the correct state on startup (so restarting mid-night lands dark). The switch still works for manual override; the schedule re-asserts at the next boundary. Leave the times blank to control the switch only manually or with your own Home app automations.

Per-canvas albums, schedule, and backlight

With multiple canvases, each can show its own day/night albums on its own timer. Under night_mode_devices, add an entry per canvas (matched by serial_number or by name, the canvas alias) and set only the fields that differ; anything omitted falls back to the top-level defaults. This is the single place you customize each frame:

"night_mode_devices": [
  {
    "name": "Living Room",
    "day_album_url": "https://www.icloud.com/sharedalbum/#LIVING_DAY",
    "night_album_url": "https://www.icloud.com/sharedalbum/#BLACK",
    "night_start": "22:30",
    "night_end": "07:00"
  },
  {
    "serial_number": "6JS20B7PA06F4",
    "day_album_url": "https://www.icloud.com/sharedalbum/#BEDROOM_DAY",
    "night_album_url": "https://www.icloud.com/sharedalbum/#BLACK",
    "night_backlight": 0,
    "night_start": "21:00",
    "night_end": "06:30"
  }
]

Each album syncs into its own auto-named gallery (HomeKit Day <serial> / HomeKit Night <serial>) so canvases never share content, and shared_album_refresh_minutes refreshes all of them. A single all-black night_album_url shared across canvases (or set once as the global default) is fine — set it as the default and only override the Day Albums per canvas.

Because the canvas never sleeps, it stays on the network and every other control keeps working overnight. First, disable the Meural app's own sleep schedule and light-sensor sleep, or the firmware will still drop Wi-Fi regardless of Night Mode.

Sleep mode (blank the panel instead of a black album)

If you'd rather not maintain a black album at all, set night_sleep: true (globally, or per canvas under night_mode_devices). Night Mode then suspends the canvas at night — the same soft power-off the Home app tile's power button uses, which fully blanks the panel — and resumes it in the morning, restoring day_backlight. No album, gallery, upload, or sync is involved:

{
  "night_sleep": true,
  "night_start": "22:00",
  "night_end": "07:00"
}

This is distinct from the Meural's own deep power-sleep (its built-in sleep schedule / light-sensor sleep) that drops Wi-Fi and kills local control — still disable those. The suspend command keeps the canvas reachable, so the morning resume works over the LAN. If a wake ever fails (a canvas that went unreachable), the plugin logs it and the schedule re-asserts at the next boundary; you can also tap the tile on. night_sleep overrides the album/gallery path for whichever canvases have it on, so you don't need any *_album_url or *_gallery for those.

Using an existing gallery

If you'd rather not use an iCloud album for a phase, set day_gallery / night_gallery (globally or per canvas) to the name — or numeric id — of a Meural gallery you already curate in the Meural app, and leave that phase's album URL blank. The plugin then just switches to that gallery instead of syncing an album. You can mix modes (e.g. a day album with an existing black gallery at night). An explicit day_gallery/night_gallery alongside an album URL pins the gallery name the album syncs into.

The iCloud Shared Album and Meural upload APIs are both unofficial/undocumented and may change without notice. Set shared_album_refresh_minutes conservatively (for example 60) to avoid excessive uploads.

Switching galleries uses a cloud "load gallery onto device" call, so it can take a few seconds per canvas; the switch responds to HomeKit immediately and does the work in the background.

Existing-gallery names are resolved to ids once and cached for the life of the process. If you rename (or delete and recreate) such a gallery, restart Homebridge so the new id is picked up — or reference the gallery by its numeric id instead.

Legacy: standalone always-on album (no schedule)

Earlier versions had a separate "sync one iCloud album and play it all day" feature via the shared_album_url, shared_album_gallery_name, and shared_album_devices keys (with a per-canvas Sync Album input). Those keys still work if present in config.json — the plugin reads them for backward compatibility, syncing the album and pushing it to the canvas on start, on the refresh interval, and via the Sync Album input — but they are no longer shown in the Homebridge config UI, which now centers on the day/night model above.

If you want a single album playing all day with no night switching, keep using these keys (edit config.json directly); they remain the simplest path for that. The day/night model, by contrast, only puts content on screen at a schedule boundary or switch toggle, so it always needs both a day and a night source to activate. If you'd rather move fully to the new model, give the canvas a day_album_url and a night_album_url (a black album) plus Night Time / Day Time, and remove the legacy keys.

Docker Compose

cp .env.example .env
mkdir -p homebridge
cp examples/config.json homebridge/config.json
docker compose build
docker compose up -d

Prefer signing in through the Homebridge UI's Meural Account Sign-In panel (see Authentication) — it completes the one-time code exchange without a restart. For fully headless setups, put the username and password in .env for the first start; if Netgear requests an OTP, enter it in the plugin's Challenge Response field or set MEURAL_CHALLENGE_RESPONSE and restart immediately (the code expires within a few minutes of the email). Clear the OTP after login succeeds. After homebridge/meural/tokens.json exists, clear the password as well.

The container:

  • runs as the unprivileged node user
  • mounts /homebridge for configuration, Homebridge state, and tokens
  • uses host networking for HomeKit/mDNS discovery
  • includes a Homebridge TCP health check
  • adds no-new-privileges

Synology

In Container Manager, create a project from docker-compose.yml, bind a persistent folder to /homebridge, and use host networking. Ensure the mounted folder is writable by UID/GID 1000. Use project environment variables or a protected .env file for the one-time login.

External Accessory Ports

Each Meural canvas is published as an external accessory, so HomeKit assigns it its own TCP port separate from the main bridge. Homebridge picks these from a dynamic range, and on a busy host a chosen port can already be in use, which crashes the child bridge:

EADDRINUSE: address already in use :::37574

This is a port-allocation conflict, not an authentication failure. Reserve a free range for external accessories at the root of config.json, alongside bridge, accessories, and platforms — not inside the Meural platform block:

{
  "bridge": { },
  "ports": {
    "start": 52000,
    "end": 52100
  },
  "platforms": [
    { "platform": "MeuralCanvas" }
  ]
}

In the Homebridge UI the same setting is Settings → Network → Port Range (older versions: Settings → Advanced → External accessory port range). Pick a range that is free on the host and wide enough for every canvas, then restart Homebridge.

Keep the range clear of child-bridge ports

The most common cause of this crash is an external-accessory range that overlaps the ports Homebridge auto-assigns to child bridges. When plugins run as child bridges, each one binds its own port (starting around 37570 and counting up), independently of the external-accessory allocator. If your external range sits in that same band, an external accessory can be handed a port a child bridge already took:

[Dyson]  child bridge ... running on port 37574
[Updater] child bridge ... running on port 37577
...
Error: listen EADDRINUSE: address already in use :::37577   <- Ring camera collides with the Updater bridge

Because child bridges start in parallel in a different order each boot, the victim rotates between plugins, so a different bridge appears to "randomly" fail on each restart. The fix is to move the external-accessory range well above the child-bridge band — for example 52000-52100 — so the two pools never overlap. This affects every plugin that publishes external accessories (cameras, TVs, and these canvases), not just this one.

If a conflict persists after relocating the range, find the process holding the port (lsof -i :52000 or ss -ltnp) and choose another range.

Token Security

  • Token directories are mode 0700; token files are mode 0600.
  • Writes use a temporary file followed by an atomic rename.
  • Passwords are never written to the token file.
  • Tokens and authorization values are redacted by logging helpers.
  • .env, tokens, cookies, browser profiles, and HAR files are ignored by Git and Docker.

Filesystem permissions protect the token file; it is not application-encrypted. Use an encrypted host volume when storage-at-rest encryption is required.

Development

npm ci
npm run check
docker build -t homebridge-meural-cognito:local .

Tests use mocked Cognito, Netgear, Meural, and token-store responses. They do not modify a Meural account or canvas.

Releases

Releases are published to the public npm registry from GitHub Actions using npm trusted publishing (OIDC). There is no long-lived npm token: the workflow authenticates per run via a short-lived OIDC identity, so nothing expires or needs rotating. The private repository means npm provenance attestations are not generated.

Publishing is triggered by pushing a v* tag whose version matches package.json. The workflow runs npm run check (lint, build, tests) via prepublishOnly before publishing.

# bump version in a PR, merge, then on main:
git tag "v$(node -p "require('./package.json').version")"
git push --tags

The Homebridge UI then offers the new version as an update.

Troubleshooting

  • Meural login is required: sign in through the plugin settings panel, provide credentials for one start, or restore the token volume.
  • Netgear sent a one-time code: use the Meural Account Sign-In panel, or paste the code into the Challenge Response field and save — either finishes the sign-in in place, no restart needed.
  • A new Netgear email arrives on every restart: the plugin has no saved session and a login is attempted each start. Complete a sign-in through the settings panel or the config form (both apply the code without a restart); a code from an earlier email will never be accepted.
  • The sign-in panel keeps spinning after you enter the code: this shouldn't happen on 1.1.1+ (requests now time out and surface an error). If it does, close the panel and paste the code into the Challenge Response field instead, then save — the running plugin picks it up within a few seconds.
  • Automatic Meural login is paused: a previous login attempt failed and needs user action; sign in through the settings panel or restart Homebridge to retry once.
  • Refresh failure: the token file is cleared and a new login is required.
  • Canvas cloud discovery works but controls fail: verify Homebridge can reach the canvas local IP.
  • HomeKit cannot discover the bridge: use host networking and confirm TCP port 51826 plus mDNS are not blocked.
  • EADDRINUSE on an external accessory port (e.g. 37574): reserve a free range with a root-level ports block. See External Accessory Ports.

Compatibility

See docs/compatibility-matrix.md. The plugin interface remains MeuralCanvas; no separate proxy API or second plugin fork is required.

License

MIT. See LICENSE.