Development

Prerequisites

Setup

git clone https://github.com/iambriansreed/easy-otp.git && cd easy-otp && npm install

The build/ folder isn't committed, so generate the app icon once before packaging:

npm run make-icons

This creates build/icon.icns from assets/icon.svg using macOS's iconutil and sharp. To use a different icon, replace the SVG and run it again.

Running the app

npm start

Compiles the TypeScript into dist/ and launches Electron. The Easy OTP icon appears in the menu bar.

npm run dev

Restarts the app whenever a file in src/ changes, and runs the website's dev server alongside it. Press Control-C to stop both.

Only one copy of Easy OTP can run at a time. If another copy is already open, a new launch quits straight away, so close the installed app first.

Demo mode

npm run demo

Runs the app with the sample accounts in scripts/demo-data.txt instead of your saved ones, and restarts it when src/ changes. Use it for screenshots and for testing without real secrets.

A demo run keeps its data in a separate temporary folder, so it never reads or changes your real accounts, and it can run alongside the installed app. To load a different file, run npm run demo -- path/to/urls.txt.

Building

npm run pack

Builds an unpacked .app in dist/. This is the quickest way to test the packaged app.

npm run build

Builds the distributable .dmg in dist/.

Releasing

npm run release

Bumps the patch version and pushes the commit and its tag. The tag starts a GitHub Actions workflow that builds an unsigned .dmg and attaches it to a GitHub release.

How it works

Storage

Accounts are saved as accounts.enc in the app's user data folder, encrypted with Electron's safeStorage. If that file can't be decrypted, it is deleted and the app starts with no accounts.

Codes

src/otp.ts generates RFC 6238 codes with 6 digits, a 30-second step, and HMAC-SHA1. The algorithm, digits, and period parameters of an otpauth:// URL are ignored.

Icons

src/favicon.ts turns an issuer name into likely domains and asks DuckDuckGo's icon service for each one, then Google's if DuckDuckGo had nothing. Electron can't read .ico files, so the file includes its own ICO decoder. Icons are stored with the account as 32x32 PNG data URLs, and lookups are cached until the app quits.

The menu

The menu bar dropdown is a frameless panel window (src/menu.html) rather than a native menu. A native menu can't show grey icons that take on color when highlighted, or use custom row spacing. The comments in src/app.ts explain the focus and Spaces handling this needs, and are worth reading before changing it.

Scripts

ScriptDescription
npm startCompile and launch the app
npm run devRelaunch the app on changes and run the website dev server
npm run demoRun the app with the sample accounts in scripts/demo-data.txt
npm run packBuild an unpacked .app for testing
npm run buildBuild a distributable .dmg
npm run make-iconsGenerate build/icon.icns from assets/icon.svg
npm run releaseBump the patch version and push a release tag

Back to home