Skip to content

Commit a38ba02

Browse files
authored
docs: clean up types and add API docs site (#199)
1 parent 69f17aa commit a38ba02

File tree

11 files changed

+384
-82
lines changed

11 files changed

+384
-82
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ node_modules
22
lib
33
*.log
44
src/example.ts
5+
docs
6+
.vscode

.prettierrc.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,13 @@
33
"tabWidth": 2,
44
"singleQuote": true,
55
"printWidth": 100,
6-
"parser": "typescript"
6+
"parser": "typescript",
7+
"overrides": [
8+
{
9+
"files": ["*.json", "*.jsonc", "*.json5"],
10+
"options": {
11+
"parser": "json"
12+
}
13+
}
14+
]
715
}

README.md

Lines changed: 132 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,102 +9,174 @@ Electron Notarize
99
## Installation
1010

1111
```bash
12-
# npm
1312
npm install @electron/notarize --save-dev
14-
15-
# yarn
16-
yarn add @electron/notarize --dev
1713
```
1814

1915
## What is app "notarization"?
2016

2117
From Apple's docs in XCode:
2218

23-
> A notarized app is a macOS app that was uploaded to Apple for processing before it was distributed. When you export a notarized app from Xcode, it code signs the app with a Developer ID certificate and staples a ticket from Apple to the app. The ticket confirms that you previously uploaded the app to Apple.
19+
> A notarized app is a macOS app that was uploaded to Apple for processing before it was distributed.
20+
> When you export a notarized app from Xcode, it code signs the app with a Developer ID certificate
21+
> and staples a ticket from Apple to the app. The ticket confirms that you previously uploaded the app to Apple.
2422
25-
> On macOS 10.14 and later, the user can launch notarized apps when Gatekeeper is enabled. When the user first launches a notarized app, Gatekeeper looks for the app’s ticket online. If the user is offline, Gatekeeper looks for the ticket that was stapled to the app.
23+
> On macOS 10.14 and later, the user can launch notarized apps when Gatekeeper is enabled.
24+
> When the user first launches a notarized app, Gatekeeper looks for the app’s ticket online.
25+
> If the user is offline, Gatekeeper looks for the ticket that was stapled to the app.
2626
27-
Apple has made this a hard requirement as of 10.15 (Catalina).
27+
As macOS 10.15 (Catalina), Apple has made notarization a hard requirement for all applications
28+
distributed outside of the Mac App Store. App Store applications do not need to be notarized.
2829

2930
## Prerequisites
3031

3132
For notarization, you need the following things:
3233

3334
1. Xcode 13 or later installed on your Mac.
34-
2. An [Apple Developer](https://developer.apple.com/) account.
35-
3. [An app-specific password for your ADC account’s Apple ID](https://support.apple.com/HT204397).
36-
4. Your app may need to be signed with `hardened-runtime`, including the following entitlement:
37-
1. `com.apple.security.cs.allow-jit`
35+
1. An [Apple Developer](https://developer.apple.com/) account.
36+
1. [An app-specific password for your ADC account’s Apple ID](https://support.apple.com/HT204397).
37+
1. Your app may need to be signed with `hardenedRuntime: true` option, with the `com.apple.security.cs.allow-jit` entitlement.
3838

39-
If you are using Electron 11 or below, you must add the `com.apple.security.cs.allow-unsigned-executable-memory` entitlement too.
40-
When using version 12+, this entitlement should not be applied as it increases your app's attack surface.
39+
> [!NOTE]
40+
> If you are using Electron 11 or below, you must add the `com.apple.security.cs.allow-unsigned-executable-memory` entitlement too.
41+
> When using version 12+, this entitlement should not be applied as it increases your app's attack surface.
4142
4243
## API
4344

44-
### Method: `notarize(opts): Promise<void>`
45-
46-
* `options` Object
47-
* `tool` String - The notarization tool to use, default is `notarytool`. Previously, the value `legacy` used `altool`, which [**stopped working** on November 1st 2023](https://developer.apple.com/news/?id=y5mjxqmn).
48-
* `appPath` String - The absolute path to your `.app` file
49-
* There are three authentication methods available:
50-
* user name with password:
51-
* `appleId` String - The username of your Apple Developer account
52-
* `appleIdPassword` String - The [app-specific password](https://support.apple.com/HT204397) (not your Apple ID password).
53-
* `teamId` String - The [team ID](https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/) you want to notarize under.
54-
* ... or apiKey with apiIssuer:
55-
* `appleApiKey` String - Absolute path to the `.p8` file containing the key. Required for JWT authentication. See Note on JWT authentication below.
56-
* `appleApiKeyId` String - App Store Connect API key ID, for example, `T9GPZ92M7K`. Required for JWT authentication. See Note on JWT authentication below.
57-
* `appleApiIssuer` String - Your App Store Connect API key issuer, for example, `c055ca8c-e5a8-4836-b61d-aa5794eeb3f4`. Required if `appleApiKey` is specified.
58-
* ... or keychain with keychainProfile:
59-
* `keychain` String (optional) - The name of the keychain or path to the keychain you stored notarization credentials in. If omitted, iCloud keychain is used by default.
60-
* `keychainProfile` String - The name of the profile you provided when storing notarization credentials.
61-
62-
## Safety when using `appleIdPassword`
63-
64-
1. Never hard code your password into your packaging scripts, use an environment
65-
variable at a minimum.
66-
2. It is possible to provide a keychain reference instead of your actual password (assuming that you have already logged into
67-
the Application Loader from Xcode). For example:
45+
`@electron/notarize` exposes a single `notarize` function that accepts the following parameters:
46+
* `appPath` — the absolute path to your codesigned and packaged Electron application.
47+
* additional options required for authenticating your Apple ID (see below)
48+
49+
The method returns a void Promise once app notarization is complete. Please note that notarization may take
50+
many minutes.
51+
52+
If the notarization process is unusually log for your application, see Apple Developer's docs to
53+
[Avoid long notarization response times and size limits](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow#3561440).
54+
55+
### Usage with app-specific password
56+
57+
You can generate an [app-specific password](https://support.apple.com/en-us/102654) for your Apple ID
58+
to notarize your Electron applications.
59+
60+
This method also requires you to specify the [Team ID](https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/)
61+
of the Developer Team you want to notarize under. An Apple ID may be part of multiple Teams.
6862

6963
```javascript
70-
const password = `@keychain:"Application Loader: ${appleId}"`;
64+
import { notarize } from '@electron/notarize';
65+
66+
await notarize({
67+
appPath,
68+
appleId, // Login name of your Apple Developer account
69+
appleIdPassword, // App-specific password
70+
teamId, // Team ID for your developer team
71+
});
7172
```
7273

73-
Another option is that you can add a new keychain item using either the Keychain Access app or from the command line using the `security` utility:
74+
> [!IMPORTANT]
75+
> **Never hard code your app-specific password into your packaging scripts.** Use an environment
76+
> variable at a minimum.
7477
75-
```bash
76-
security add-generic-password -a "AC_USERNAME" -w <app_specific_password> -s "AC_PASSWORD"
77-
```
78-
where `AC_USERNAME` should be replaced with your Apple ID, and then in your code you can use:
78+
### Usage with App Store Connect API key
79+
80+
Alternatively, you can also authenticate via JSON Web Token (JWT) with App Store Connect.
81+
82+
You can obtain an API key from [App Store Connect](https://appstoreconnect.apple.com/access/integrations/api).
83+
Create a **Team Key** (not an _Individual Key_) with **App Manager** access.
84+
85+
Note down the Issuer ID (UUID format) and Key ID (10-character alphanumeric string),
86+
and download the `.p8` API key file (`AuthKey_<appleApiKeyId>.p8`).
87+
For security purposes, the private key can only be downloaded once.
88+
89+
Provide the absolute path to your API key as the `appleApiKey` argument.
7990

8091
```javascript
81-
const password = `@keychain:AC_PASSWORD`;
92+
import { notarize } from '@electron/notarize';
93+
94+
await notarize({
95+
appPath,
96+
appleApiKey, // Absolute path to API key (e.g. `/path/to/AuthKey_X0X0X0X0X0.p8`)
97+
appleApiIssuer, // Issuer ID (e.g. `d5631714-a680-4b4b-8156-b4ed624c0845`)
98+
});
8299
```
83100

84-
## Notes on JWT authentication
101+
### Usage with Keychain credentials
85102

86-
You can obtain an API key from [App Store Connect](https://appstoreconnect.apple.com/access/api). Create a _Team Key_ (not an _Individual Key_) with _App Manager_ access. Note down the Issuer ID and download the `.p8` file. This file is your API key and comes with the name of `AuthKey_<appleApiKeyId>.p8`. Provide the path to this file as the `appleApiKey` argument.
103+
As an alternative to passing authentication options, you can also store your authentication
104+
credentials (for both API key and app-specific password strategies) in the macOS Keychain
105+
via the `xcrun notarytool` command-line utility.
87106

88-
## Notes on your teamId
107+
This method has the advantage of validating your notarization credentials before submitting
108+
your application for notarization.
89109

90-
To get your `teamId` value, go to your [Apple Developer Account](https://developer.apple.com/account), then click on "Membership details", and there you will find your Team ID.
110+
For example:
91111

92-
## Debug
112+
```sh
113+
# App-specific password strategy
114+
xcrun notarytool store-credentials "my-app-password-profile"
115+
--apple-id "<AppleID>"
116+
--team-id <DeveloperTeamID>
117+
--password <app_specific_password>
118+
```
93119

94-
[`debug`](https://www.npmjs.com/package/debug) is used to display logs and messages. You can use `export DEBUG=electron-notarize*` to log additional debug information from this module.
120+
```sh
121+
# App Store Connect API key strategy
122+
xcrun notarytool store-credentials "my-api-key-profile"
123+
--key "<PathToAPIKey>"
124+
--key-id <KeyID>
125+
--issuer <IssuerID>
126+
```
95127

96-
## Example Usage
128+
Successful storage of your credentials will look like this:
129+
130+
```
131+
This process stores your credentials securely in the Keychain. You reference these credentials later using a profile name.
132+
133+
Validating your credentials...
134+
Success. Credentials validated.
135+
Credentials saved to Keychain.
136+
To use them, specify `--keychain-profile "my-api-key-profile"`
137+
```
138+
139+
After successfully storing your credentials, pass the keychain profile name into
140+
the `keychainProfile` parameter.
97141

98142
```javascript
99143
import { notarize } from '@electron/notarize';
100144

101-
async function packageTask () {
102-
// Package your app here, and code sign with hardened runtime
103-
await notarize({
104-
appPath,
105-
appleId,
106-
appleIdPassword,
107-
teamId,
108-
});
109-
}
145+
await notarize({
146+
appPath,
147+
keychainProfile,
148+
});
149+
```
150+
## Troubleshooting
151+
152+
### Debug logging
153+
154+
[`debug`](https://www.npmjs.com/package/debug) is used to display logs and messages.
155+
Run your notarization scripts with the `DEBUG=electron-notarize*` environment variable to log additional
156+
debug information from this module.
157+
158+
### Validating credentials
159+
160+
When notarizing your application, you may run into issues with validating your notarization
161+
credentials.
162+
163+
```
164+
Error: HTTP status code: 401. Invalid credentials. Username or password is incorrect.
165+
Use the app-specific password generated at appleid.apple.com. Ensure that all authentication arguments are correct.
110166
```
167+
168+
[Storing your credentials in Keychain](#usage-with-keychain-credentials) will validate your credentials before
169+
even GitHub.
170+
171+
### Validating app notarization
172+
173+
To validate that notarization worked, you can use the `stapler` command-line utility:
174+
175+
```sh
176+
stapler validate path/to/notarized.app
177+
```
178+
179+
### Apple documentation
180+
181+
Apple also provides additional debugging documentation on
182+
[Resolving common notarization issues](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/resolving_common_notarization_issues).

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@
3535
"jest": "^29.0.0",
3636
"prettier": "^1.18.2",
3737
"ts-jest": "^29.0.0",
38-
"typescript": "^4.8.4"
38+
"typedoc": "~0.25.13",
39+
"typedoc-plugin-missing-exports": "^2.2.0",
40+
"typescript": "4.9.3"
3941
},
4042
"dependencies": {
4143
"debug": "^4.1.1",

src/check-signature.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import * as path from 'path';
22

33
import { spawn } from './spawn';
4-
import { NotarizeStapleOptions } from './types';
4+
import { NotaryToolNotarizeAppOptions } from './types';
55
import debug from 'debug';
66
const d = debug('electron-notarize');
77

8-
const codesignDisplay = async (opts: NotarizeStapleOptions) => {
8+
const codesignDisplay = async (opts: NotaryToolNotarizeAppOptions) => {
99
const result = await spawn('codesign', ['-dv', '-vvvv', '--deep', path.basename(opts.appPath)], {
1010
cwd: path.dirname(opts.appPath),
1111
});
1212
return result;
1313
};
1414

15-
const codesign = async (opts: NotarizeStapleOptions) => {
15+
const codesign = async (opts: NotaryToolNotarizeAppOptions) => {
1616
d('attempting to check codesign of app:', opts.appPath);
1717
const result = await spawn(
1818
'codesign',
@@ -24,7 +24,7 @@ const codesign = async (opts: NotarizeStapleOptions) => {
2424

2525
return result;
2626
};
27-
export async function checkSignatures(opts: NotarizeStapleOptions): Promise<void> {
27+
export async function checkSignatures(opts: NotaryToolNotarizeAppOptions): Promise<void> {
2828
const fileExt = path.extname(opts.appPath);
2929
if (fileExt === '.dmg' || fileExt === '.pkg') {
3030
d('skipping codesign check for dmg or pkg file');

src/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,21 @@ export { NotarizeOptions };
1717

1818
export { validateNotaryToolAuthorizationArgs as validateAuthorizationArgs } from './validate-args';
1919

20+
/**
21+
* Sends your app to Apple for notarization with `notarytool` and staples a successful
22+
* notarization result to the app bundle. This includes your {@link NotaryToolNotarizeAppOptions.appPath | appPath}
23+
* as well as one of three valid credential authentication strategies.
24+
*
25+
* See {@link NotaryToolCredentials} for authentication options.
26+
*
27+
* @category Core
28+
* @param args Options for notarization
29+
* @returns The Promise resolves once notarization is complete. Note that this may take a few minutes.
30+
*/
2031
async function notarize(args: NotarizeOptionsNotaryTool): Promise<void>;
21-
/** @deprecated */
32+
/**
33+
* @deprecated
34+
*/
2235
async function notarize(args: NotarizeOptionsLegacy): Promise<void>;
2336

2437
async function notarize({ appPath, ...otherOptions }: NotarizeOptions) {

src/staple.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import debug from 'debug';
22
import * as path from 'path';
33

44
import { spawn } from './spawn';
5-
import { NotarizeStapleOptions } from './types';
5+
import { NotaryToolNotarizeAppOptions } from './types';
66

77
const d = debug('electron-notarize:staple');
88

9-
export async function stapleApp(opts: NotarizeStapleOptions): Promise<void> {
9+
export async function stapleApp(opts: NotaryToolNotarizeAppOptions): Promise<void> {
1010
d('attempting to staple app:', opts.appPath);
1111
const result = await spawn('xcrun', ['stapler', 'staple', '-v', path.basename(opts.appPath)], {
1212
cwd: path.dirname(opts.appPath),

0 commit comments

Comments
 (0)