This document is the full plan for shipping Flo to the Mac App Store (MAS). It covers what Apple requires, what features of Flo conflict with MAS rules, what certificates/profiles to create in the Apple Developer portal, and the end-to-end build + upload flow.
Status: in progress. The repo now has a
maselectron-builder target and sandbox entitlements, but the App Store Connect record, provisioning profile, and distribution certificates must still be created manually by the developer.
The Mac App Store is not the same channel as the notarized-DMG distribution Flo already ships. Different certs, different entitlements, different review, different update mechanism.
Hard MAS rules that affect Flo:
- App must be sandboxed.
com.apple.security.app-sandboxis mandatory. - No third-party auto-update.
electron-updater/ Squirrel / Sparkle are all forbidden. The App Store handles updates. - No raw USB/serial access without a special Apple-granted entitlement
(
com.apple.developer.driverkit.*). Thermal printer drivers that talk to USB directly will be rejected. Network printers (IPP / raw 9100) are fine. - Local network servers are allowed but restricted. Binding TCP with
com.apple.security.network.serveris fine; Bonjour/mDNS advertising is fine with the multicast entitlement but often flagged at review if the user value is not explained. - File writes outside the container require
files.user-selected.*entitlements and must be user-initiated (NSOpenPanel / NSSavePanel). DB files live in~/Library/Containers/com.flo.desktop/Data/…automatically. - Hardened runtime is implied. Sandbox is stricter than hardened-only.
- Bundle ID must match the App Store Connect record exactly.
- Provisioning profile must be embedded inside the
.app.
| Feature | MAS-compatible? | What we do in the MAS build |
|---|---|---|
electron-updater (GitHub releases) |
No | Disabled at runtime when MAS_BUILD=1. App Store does updates. |
node-thermal-printer over USB |
No | Disabled in MAS build. Only network (TCP) printers supported. |
node-thermal-printer over TCP |
Yes | Allowed with network.client. |
Express server on :3001 + KDS on :3002 |
Yes | Allowed with network.server. |
bonjour-service (mDNS) |
Yes, with caveat | Needs com.apple.security.network.server + multicast. Kept on, but we should be ready to explain the POS/KDS pairing flow in the review notes. |
better-sqlite3 (native) |
Yes | Must be signed with hardened runtime + sandbox. DB path moves into the container. |
| Backup/restore to user-chosen location | Yes | Must use dialog.showSaveDialog / showOpenDialog — already does. Requires files.user-selected.read-write. |
| Tray icon | Yes | No entitlement needed. |
| DevTools / JIT | Yes | Sandboxed Electron apps still need com.apple.security.cs.allow-jit + allow-unsigned-executable-memory in the inherited entitlements for the helper processes. |
Do these in a browser while logged in as the team agent of Codify Apps Private Limited (BKDY677XJA).
Create/download and install into login keychain:
- Mac App Distribution — signs the
.appbundle.- Also called 3rd Party Mac Developer Application in older UIs.
- Mac Installer Distribution — signs the outer
.pkguploaded to Apple.- Also called 3rd Party Mac Developer Installer.
- Keep the existing Developer ID Application cert for the DMG channel.
After installing, confirm with:
security find-identity -v -p codesigning | grep -E "3rd Party Mac|Mac App"You should see one Mac App Distribution (or 3rd Party Mac Developer Application)
and one Mac Installer Distribution (or 3rd Party Mac Developer Installer)
identity with the team ID BKDY677XJA.
- Create an App ID with bundle ID
com.flo.desktop(must matchbuild.appIdin package.json exactly). - Enable any capabilities we actually use. For now:
- App Sandbox (implied for MAS)
- (nothing exotic — no iCloud, no Push, no HealthKit, no DriverKit)
- Create a Mac App Store distribution provisioning profile bound to the
com.flo.desktopApp ID and the Mac App Distribution certificate. - Download it and save it at the repo root (or anywhere) as
build/flo.provisionprofile. electron-builder will embed it automatically ifmac.provisioningProfileis set.
- In App Store Connect → My Apps → “+” create a new macOS app.
- Platform: macOS. Bundle ID:
com.flo.desktop. SKU:flo-desktop. - Fill metadata (name, subtitle, category = Business, pricing, screenshots).
- Create the first version, e.g.
1.5.6, matchingpackage.json.
- Users and Access → Integrations → App Store Connect API.
- Generate a key with role Developer (or App Manager).
- Download the
.p8file and note the Key ID and Issuer ID. - Store locally, never commit:
~/.appstoreconnect/private_keys/AuthKey_<KEY_ID>.p8 - Export env vars when uploading:
export APPLE_API_KEY_ID=<KEY_ID> export APPLE_API_ISSUER=<ISSUER_ID> export APPLE_API_KEY=~/.appstoreconnect/private_keys/AuthKey_<KEY_ID>.p8
Alternative: use an app-specific password (Apple ID → Sign-In and Security →
App-Specific Passwords) with xcrun altool --upload-app -u ... -p .... The
API key is preferred.
Added to package.json under build.mac.target as a separate
entry, with its own entitlements:
build/entitlements.mas.plist— applied to the main.app.build/entitlements.mas.inherit.plist— applied to all Electron helper processes (Renderer, GPU, Plugin). Required; without it the helpers crash under sandbox.build.mac.provisioningProfile = "build/flo.provisionprofile".
A new env var MAS_BUILD=1 is passed at build time. The main process reads
it and:
- Does not call
setupAutoUpdater()orcheckForUpdates(). - Does not initialize the thermal printer driver over USB.
- Hides the “Check for Updates” menu item.
This keeps a single codebase but two very different binaries.
npm run build:mas # builds & signs a .pkg ready for App Store ConnectOutput lands in release/mas/Flo-<version>.pkg.
After build:mas completes:
xcrun altool --upload-app \
--type macos \
--file "release/mas/Flo-1.5.6.pkg" \
--apiKey "$APPLE_API_KEY_ID" \
--apiIssuer "$APPLE_API_ISSUER"Or, the GUI path: open Transporter.app (from the Mac App Store), drag in
the .pkg, click Deliver.
Build shows up in App Store Connect → TestFlight tab within ~15–60 minutes after processing. Attach it to the version in the App Store tab and submit for review.
Flo uses only exempt cryptography:
bcryptjs— password hashing (a hash, not encryption — exempt).jsonwebtoken— HMAC-SHA256 signing for auth tokens (authentication-only, exempt under 15 CFR §742.15(b)(4)).- No proprietary crypto, no user-data encryption for transport/storage beyond what the OS provides.
The MAS build sets ITSAppUsesNonExemptEncryption = false in Info.plist
(via mac.extendInfo in package.json), so App Store Connect will not
prompt for the Export Compliance form on every upload.
If crypto usage ever changes (e.g. adding TLS with custom protocols, or encrypting user data at rest with a proprietary scheme), this flag must be re-evaluated and possibly removed; an annual self-classification report to the US Bureau of Industry and Security may be required.
- Bundle version sync: App Store Connect requires each upload to have a
strictly higher
CFBundleVersion. electron-builder usespackage.jsonversionfor bothCFBundleShortVersionStringandCFBundleVersion. Bumpversionbefore every re-upload or Apple rejects the.pkg. - Private APIs: Apple’s static analyzer will reject builds that import unexported symbols. Electron itself is clean, but any new native addon should be vetted.
- Crash on launch in sandbox: usually means a helper is missing the
inherited entitlements or trying to write outside the container. Check
~/Library/Logs/DiagnosticReports/Flo-*.ips. - “No matching provisioning profiles found”: the cert the profile was
created with must match the cert electron-builder uses to sign. Both must
be under team
BKDY677XJA. - Review time: first submission 24–72h typical. Rejections are common for POS apps — be ready to explain the local-network/KDS feature in the App Review notes.
- Create the two MAS distribution certs (§3a) and install to keychain.
- Create
com.flo.desktopApp ID (§3b). - Create and download the MAS provisioning profile, save as
build/flo.provisionprofile(§3c). - Create the App Store Connect app record for
com.flo.desktop(§3d). - Create an App Store Connect API key and export the env vars (§3e).
- Run
npm run build:mas, then upload withxcrun altoolor Transporter.app (§4c, §4d). - Submit for review inside App Store Connect.