A local React + Node.js dashboard for listing and controlling Sonoff/eWeLink devices. Credentials stay in the Express backend and are never delivered to the browser. The backend uses the baugp/ewelink-api fork directly from GitHub.
React / Vite β REST API β Node.js / Express β ewelink-api β eWeLink API β Sonoff devices
Copy backend/.env.example to backend/.env, then pick one of the two login modes. backend/.env is ignored by Git. Do not use VITE_ variables for these values.
http://localhost:3000/api/oauth/callback as its redirect URL.Set its credentials:
EWELINK_APP_ID=your-app-id
EWELINK_APP_SECRET=your-app-secret
The backend stores the resulting tokens in backend/.ewelink-token.json (ignored by Git) and refreshes them automatically. Access tokens last 30 days and refresh tokens 60; if the refresh token expires, the dashboard asks you to connect again. EWELINK_OAUTH_REDIRECT_URL and FRONTEND_URL override the callback URL and where the browser lands afterwards.
eWeLink refuses email/password login for developer apps (error 407), which is why this mode needs the browser step.
EWELINK_REGION=eu
EWELINK_USERNAME=you@example.com
EWELINK_PASSWORD=your-password
This uses the app ID built into ewelink-api, which eWeLink only allows on read paths: listing devices works, but turning them on or off fails with eWeLink error 407.
EWELINK_REGION is the region the account was created in (eu, us, as or cn), which is not necessarily where you live: an account created in Brazil lives in us. If the region is wrong, eWeLink answers the login with the right one and the backend retries there automatically, logging the switch. Authentication failures are logged with the eWeLink error code and message in the backend console; the API responses stay generic.
npm install
npm run dev
The root install script installs backend and frontend dependencies. Open http://localhost:5173. The backend runs on http://localhost:3000.
To run the two services independently:
npm run dev:backend
npm run dev:frontend
Build the frontend with npm run build.
npm test # backend (node:test) and frontend (Vitest) suites, both with coverage
npm run coverage:report # one Markdown table from both lcov reports
The backend uses Nodeβs built-in test runner against the real ewelink-api module with its network layer stubbed, so the eWeLink v2 response shapes (the thingList wrapper, the wrong-region answer, token rejection) are exercised exactly as the cloud sends them. The frontend uses Vitest with jsdom and React Testing Library. Coverage lands in backend/coverage/lcov.info and frontend/coverage/lcov.info.
.github/workflows/build.yml) runs on every pull request: install, frontend build, both test suites, and a coverage table posted to the job summary and as a comment on the pull request..github/workflows/release.yml) runs on every push to main: the same build and tests, then GitVersion computes the next version from the tags on main (GitVersion.yml), the commit is tagged vX.Y.Z, and a GitHub release is created with generated notes and the frontend build attached as a zip.With the backend running, open Scalar API Reference. The raw OpenAPI document is available at http://localhost:3000/openapi.json.
GET /api/health β verifies eWeLink connectivity without revealing secretsGET /api/oauth/login β starts eWeLink authorization (OAuth mode)GET /api/oauth/callback β receives the authorization code from eWeLink and stores the tokensGET /api/devices β lists normalized devicesGET /api/devices/:deviceId β retrieves one devicePOST /api/devices/:deviceId/on β turns a device onPOST /api/devices/:deviceId/off β turns a device off/api development proxyThis is intentionally a PoC. It has no database, user authentication, multi-user support, persistent device configuration, cloud deployment, advanced automation, scheduling, scenes, or device grouping.