How to notarize electron app

Issue #430

Use electron builder

1
npm install electron-builder@latest --save-dev

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
"name": "icon_generator",
"version": "1.3.0",
"description": "A macOS app to generate app icons",
"main": "babel/main.js",
"repository": "https://github.com/onmyway133/IconGenerator",
"author": "Khoa Pham",
"license": "MIT",
"scripts": {
"start": "npm run babel && electron .",
"babel": "babel ./src --out-dir ./babel --copy-files",
"dist": "npm run babel && electron-builder"
},
"build": {
"appId": "com.onmyway133.IconGenerator",
"buildVersion": "20",
"productName": "Icon Generator",
"icon": "./Icon/Icon.icns",
"mac": {
"category": "public.app-category.productivity",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "./entitlements.plist",
"entitlementsInherit": "./entitlements.plist"
},
"win": {
"target": "msi"
},
"linux": {
"target": [
"AppImage",
"deb"
]
},
"afterSign": "./afterSignHook.js"
}
}

Declare entitlements

entitlements.plist

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>

Use electron-notarize

afterSignHook.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const fs = require('fs');
const path = require('path');
var electron_notarize = require('electron-notarize');

module.exports = async function (params) {
// Only notarize the app on Mac OS only.
if (process.platform !== 'darwin') {
return;
}
console.log('afterSign hook triggered', params);

// Same appId in electron-builder.
let appId = 'com.onmyway133.IconGenerator'

let appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`);
if (!fs.existsSync(appPath)) {
throw new Error(`Cannot find application at: ${appPath}`);
}

console.log(`Notarizing ${appId} found at ${appPath}`);

try {
await electron_notarize.notarize({
appBundleId: appId,
appPath: appPath,
appleId: process.env.appleId,
appleIdPassword: process.env.appleIdPassword,
});
} catch (error) {
console.error(error);
}

console.log(`Done notarizing ${appId}`);
};

Run

Generate password for Apple Id because of 2FA

1
2
3
export appleId=onmyway133@gmail.com
export appleIdPassword=1234-abcd-efgh-7890
npm run dist

Check

1
spctl --assess --verbose Icon\ Generator.app

Troubleshooting

babel

  • Since electron-builder create dist folder for distribution, for example dist/mac/Icon Generator, I’ve renamed babel generated code to babel directory

babel 6 regeneratorRuntime is not defined

It is because of afterSignHook. Ignore in .babelrc not work

1
2
3
4
5
6
7
8
9
{
"plugins": [
"transform-react-jsx-source"
],
"presets": ["env", "react"],
"ignore": [
"afterSignHook.js"
]
}

Should use babel 7 with babel.config.js

1
2
npm install --save @babel/runtime 
npm install --save-dev @babel/plugin-transform-runtime

Use electron-forge

https://httptoolkit.tech/blog/notarizing-electron-apps-with-electron-forge/

Read more

How to fix electron issues

Issue #415

Electron require() is not defined

https://stackoverflow.com/questions/44391448/electron-require-is-not-defined

1
2
3
4
5
6
7
8
9
10
11
12
function createWindow () {
win = new BrowserWindow({
title: 'MyApp',
width: 600,
height: 500,
resizable: false,
icon: __dirname + '/Icon/Icon.icns',
webPreferences: {
nodeIntegration: true
}
})
}

DevTools was disconnected from the page

1
2
npm install babel-cli@latest --save-dev
npm install react@16.2.0
1
win.openDevTools()

This leads to Cannot find module 'react/lib/ReactComponentTreeHook'
If we’re using binary, then rebuild, it is the problem that cause devTools not work

1
npx electron-builder

This goes to Cannot read property injection of undefined at react-tap-event-plugin

https://github.com/zilverline/react-tap-event-plugin/issues/121

1
npm uninstall react-tap-event-plugin

This goes to Unknown event handler property onTouchTap in EnhancedButton in material-ui

Update material-ui

https://material-ui.com/
https://material-ui.com/guides/migration-v0x/#raised-button

1
npm install @material-ui/core

From

1
2
3
4
import RadioButtonGroup from '@material-ui/core/RadioButton/RadioButtonGroup'
import RadioButton from '@material-ui/core/RadioButton'
import RaisedButton from '@material-ui/core/RaisedButton'
import CardText from '@material-ui/core/Card/CardText'

to

1
2
3
4
import RadioButtonGroup from '@material-ui/core/RadioGroup'
import RadioButton from '@material-ui/core/Radio'
import RaisedButton from '@material-ui/core/Button'
import CardText from '@material-ui/core/DialogContentText'

Use FormControlLabel https://material-ui.com/components/radio-buttons/

1
2
3
4
5
6
7
<RadioGroup 
style={styles.group}
defaultselected={this.state.choice}
onChange={this.handleChoiceChange}
children={choiceElements} />
{this.makeGenerateButton()}
/>

How to use React JSX with Babel in Electron

Issue #352

For a normal electron app created with npm init, we can use all features of ES6, but not the JSX syntax for React. We can use just Babel to transpile JSX, as used in IconGenerator

.babelrc

1
2
3
4
5
6
{
"plugins": [
"transform-react-jsx-source"
],
"presets": ["env", "react"]
}

And in package.json, call babel to transpile src to dist

1
2
3
4
5
"main": "dist/main.js",
"scripts": {
"start": "npm run babel && electron .",
"babel": "babel ./src --out-dir ./dist --copy-files"
},

Remember to use dist/main.js as our starting point, and in index.html, specify ./dist/renderer.js

1
2
3
4
5
6
<body>
<div id="root" />
<script type="text/javascript">
require('./dist/renderer.js')
</script>
</body>

How to submit electron app to AppStore

Issue #342

Before

Install electron as dev npm install electron --save-dev
Update electron-packager npm install electron-packager@latest --save-dev
Use no space in app name

Package with electron-packager

Follow https://github.com/electron/electron-osx-sign/wiki/Packaging-and-Submitting-an-Electron-App-to-the-Mac-App-Store

1
2
3
npx electron-packager . "MyApp" --app-bundle-id=com.onmyway133.MyApp --helper-bundle-id=com.onmyway133.MyApp.helper --app-version=1.4.0 --build-version=1.0.100 --platform=mas --arch=x64 --icon=Icon/Icon.icns --overwrite
npx electron-osx-sign "MyApp-mas-x64/MyApp.app" --verbose
npx electron-osx-flat "MyApp-mas-x64/MyApp.app" --verbose

If you have multiple developer identities in your keychain:

electron-osx-sign searches your keychain for the first signing certificates that it can locate. If you have multiple certificates then it may not know which cert you want to use for signing and you need to explicitly provide the name:

1
electron-osx-sign "My App-mas-x64/My App.app" --identity="3rd Party Mac Developer Application: My Company, Inc (ABCDEFG1234)" --verbose

Read more

Sign with electron-osx-sign

Read README https://github.com/electron/electron-osx-sign

For distribution in the Mac App Store: Have the provisioning profile for distribution placed in the current working directory and the signing identity installed in the default keychain.

Certificate

On developer.apple.com, create Mac App Distribution certificate. Make sure when we download in Keychain Access, it has associated private key

Manually upload

1
/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/itms/bin/iTMSTransporter -m upload  -assetFile MyApp/MyApp.pkg  -u onmyway133@gmail.com -p mypassword

Use Application Loader

Use Using app-specific passwords

Troubleshooting

electron Bad CFBundleExecutable. Cannot find executable file

ERROR ITMS-90261: “Bad CFBundleExecutable. Cannot find executable file that matches the value of CFBundleExecutable in the nested bundle MyApp [com.onmyway133.MyApp.pkg/Payload/MyApp.app/Contents/Frameworks/MyApp (GPU).app] property list file.”

https://github.com/electron/electron-packager/issues?utf8=%E2%9C%93&q=helper

Try electron 5.0.0 npm install electron@5.0.0 --save-dev

Specifically, we found that when the user closes the main application window there is no menu item to re-open it.

https://stackoverflow.com/questions/35008347/electron-close-w-x-vs-right-click-dock-and-quit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createMenu() {
const application = {
label: "MyApp",
submenu: [
{
label: "New",
accelerator: "Command+N",
click: () => {
if (win === null) {
createWindow()
}
}
}
]
}
}

‘electron-osx-flat@latest’ is not in the npm registry

1
npm install -g electron-osx-sign@latest

App sandbox not enabled

electron-osx-sign Command failed: codesign

1
ran xattr -cr *

Command failed: codesign bundle format is ambiguous

Perhaps you accidentally packaged the previous generated app bundle into your newly packaged app?

Remove dist folder generated by electron-builder