@limrun/ui 0.14.0-rc.4 → 0.14.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/app-store-relay/index.cjs +3 -3
  2. package/dist/app-store-relay/index.d.ts +108 -4
  3. package/dist/app-store-relay/index.js +353 -160
  4. package/dist/app-store-relay/react.cjs +1 -1
  5. package/dist/app-store-relay/react.js +1 -1
  6. package/dist/apple-credentials/index.cjs +1 -1
  7. package/dist/apple-credentials/index.d.ts +59 -8
  8. package/dist/apple-credentials/index.js +197 -133
  9. package/dist/client-C9z_sgOf.js +1 -0
  10. package/dist/{client-DSuNtWDv.mjs → client-CFDB4mX5.mjs} +37 -30
  11. package/dist/core/device-install/apple/provisioning.d.ts +25 -0
  12. package/dist/core/device-install/apple/relay.d.ts +13 -0
  13. package/dist/core/device-install/storage/secret-store.d.ts +24 -1
  14. package/dist/device-build/index.cjs +1 -1
  15. package/dist/device-build/index.js +1 -1
  16. package/dist/device-install/index.cjs +1 -1
  17. package/dist/device-install/index.js +175 -172
  18. package/dist/{provisioning-B_DefBEp.mjs → provisioning-CDPvxd1h.mjs} +63 -32
  19. package/dist/provisioning-CbC4h--7.js +1 -0
  20. package/package.json +1 -1
  21. package/src/app-store-relay/index.ts +358 -23
  22. package/src/apple-credentials/index.test.ts +22 -0
  23. package/src/apple-credentials/index.ts +161 -14
  24. package/src/core/device-install/apple/client.ts +13 -6
  25. package/src/core/device-install/apple/provisioning.test.ts +30 -0
  26. package/src/core/device-install/apple/provisioning.ts +39 -0
  27. package/src/core/device-install/apple/relay.ts +20 -0
  28. package/src/core/device-install/operations/operations.ts +6 -1
  29. package/src/core/device-install/storage/secret-store.ts +36 -2
  30. package/dist/client-D1drDXxE.js +0 -1
  31. package/dist/provisioning-BlQMccX_.js +0 -1
@@ -7,12 +7,19 @@
7
7
  */
8
8
  import {
9
9
  createAppleCertificate,
10
+ createAppStoreConnectApiKey,
10
11
  downloadAppleCertificate,
11
12
  downloadAppleProfile,
13
+ downloadAppStoreConnectApiKeyPrivateKey,
12
14
  exportAppleCertificateP12,
13
15
  generateAppleSigningKeyAndCSR,
14
16
  listAppleCertificates,
15
17
  listAppleProfiles,
18
+ listAppStoreConnectApiKeys,
19
+ switchAppStoreConnectProvider,
20
+ type AppleCertificateKind,
21
+ type AppleProfileKind,
22
+ type AppStoreConnectApiKeyRole,
16
23
  } from '../app-store-relay';
17
24
  import type { AppleRelayWebSocketClient } from '../core/device-install/apple';
18
25
  import {
@@ -20,25 +27,31 @@ import {
20
27
  parseProvisioningProfileBase64,
21
28
  } from '../core/device-install/storage/browser-storage';
22
29
  import {
30
+ APP_STORE_CONNECT_API_KEY_SECRET_TYPE,
23
31
  APPLE_CERTIFICATE_SECRET_TYPE,
24
32
  putAppleCertificateSecret,
25
33
  putAppleProvisioningProfileSecret,
34
+ putAppStoreConnectApiKeySecret,
26
35
  type AppleCertificateSecretData,
27
36
  type AppleCertificateType,
37
+ type AppStoreConnectApiKeySecretData,
28
38
  type SigningSecret,
29
39
  type SigningSecretStore,
30
40
  } from '../core/device-install/storage/secret-store';
31
41
 
32
42
  export {
43
+ APP_STORE_CONNECT_API_KEY_SECRET_TYPE,
33
44
  APPLE_CERTIFICATE_SECRET_TYPE,
34
45
  APPLE_PROVISIONING_PROFILE_SECRET_TYPE,
35
46
  createBrowserSecretStore,
36
47
  createLimrunSecretStore,
37
48
  putAppleCertificateSecret,
38
49
  putAppleProvisioningProfileSecret,
50
+ putAppStoreConnectApiKeySecret,
39
51
  type AppleCertificateSecretData,
40
52
  type AppleCertificateType,
41
53
  type AppleProvisioningProfileSecretData,
54
+ type AppStoreConnectApiKeySecretData,
42
55
  type LimrunSecretStoreOptions,
43
56
  type SigningSecret,
44
57
  type SigningSecretData,
@@ -61,6 +74,11 @@ export type EnsureAppleCertificateInput = {
61
74
  relay: AppleRelayWebSocketClient;
62
75
  teamId: string;
63
76
  secretStore: SigningSecretStore;
77
+ /**
78
+ * Which portal certificate kind to ensure. Ad-hoc and App Store signing
79
+ * both use the distribution certificate. Defaults to development.
80
+ */
81
+ certificateKind?: AppleCertificateKind;
64
82
  /** Common name used when minting a new certificate. */
65
83
  commonName?: string;
66
84
  log?: (message: string, detail?: string) => void;
@@ -74,10 +92,11 @@ export type EnsureAppleCertificateResult = {
74
92
  };
75
93
 
76
94
  /**
77
- * Returns a usable development certificate secret for the team, reusing the
78
- * stored one when its certificate is still on the team and minting a new
79
- * one otherwise. Apple caps development certificates at 2 and never returns
80
- * private keys, so reuse of the stored p12 is strongly preferred.
95
+ * Returns a usable certificate secret of the requested kind for the team,
96
+ * reusing the stored one when its certificate is still on the team and
97
+ * minting a new one otherwise. Apple caps certificates per kind (2 for
98
+ * development, 3 for distribution) and never returns private keys, so
99
+ * reuse of the stored p12 is strongly preferred.
81
100
  *
82
101
  * Durability of the stored material is the secret store's concern:
83
102
  * implementors who want retries or fallbacks build them into their
@@ -87,14 +106,16 @@ export async function ensureAppleCertificateSecret({
87
106
  relay,
88
107
  teamId,
89
108
  secretStore,
109
+ certificateKind = 'development',
90
110
  commonName,
91
111
  log = () => {},
92
112
  }: EnsureAppleCertificateInput): Promise<EnsureAppleCertificateResult> {
93
- // The browser flow mints via the portal's development kind, which is
94
- // the DEVELOPMENT certificate type in Apple's App Store Connect enum.
95
- const certificateType: AppleCertificateType = 'DEVELOPMENT';
113
+ // The portal kinds map onto Apple's App Store Connect certificate type
114
+ // enum, which is what consumers of the stored secret filter on.
115
+ const certificateType: AppleCertificateType =
116
+ certificateKind === 'distribution' ? 'DISTRIBUTION' : 'DEVELOPMENT';
96
117
  const secretName = appleCertificateSecretName(teamId, certificateType);
97
- const current = await listAppleCertificates({ relay, teamId, certificateKind: 'development' });
118
+ const current = await listAppleCertificates({ relay, teamId, certificateKind });
98
119
  const findOnTeam = (certificateId: string | undefined) =>
99
120
  certificateId === undefined ? undefined : (
100
121
  current.find(
@@ -125,7 +146,7 @@ export async function ensureAppleCertificateSecret({
125
146
  const certificate = await createAppleCertificate({
126
147
  relay,
127
148
  teamId,
128
- certificateKind: 'development',
149
+ certificateKind,
129
150
  csrPEM: key.csrPEM,
130
151
  });
131
152
  const certificateId =
@@ -136,7 +157,7 @@ export async function ensureAppleCertificateSecret({
136
157
  const downloaded = await downloadAppleCertificate({
137
158
  relay,
138
159
  teamId,
139
- certificateKind: 'development',
160
+ certificateKind,
140
161
  certificateId,
141
162
  });
142
163
  if (!downloaded.rawBodyBase64) {
@@ -147,7 +168,7 @@ export async function ensureAppleCertificateSecret({
147
168
  privateKeyPKCS8Base64: key.privateKeyPKCS8Base64,
148
169
  certificateBase64: downloaded.rawBodyBase64,
149
170
  password: '',
150
- friendlyName: `Apple Development ${teamId}`,
171
+ friendlyName: `Apple ${certificateKind === 'distribution' ? 'Distribution' : 'Development'} ${teamId}`,
151
172
  }),
152
173
  certificateType,
153
174
  teamID: teamId,
@@ -222,14 +243,140 @@ export async function saveAppleProfileSecret({
222
243
  return secret;
223
244
  }
224
245
 
246
+ /**
247
+ * Conventional secret name of a team's App Store Connect API key: one
248
+ * shared key per team.
249
+ */
250
+ export function appStoreConnectApiKeySecretName(teamId: string) {
251
+ return `${teamId}/APP_STORE_CONNECT_API_KEY`;
252
+ }
253
+
254
+ export type EnsureAppStoreConnectApiKeyInput = {
255
+ relay: AppleRelayWebSocketClient;
256
+ teamId: string;
257
+ /**
258
+ * Numeric provider ID of the team from the Apple team list. When given,
259
+ * the App Store Connect session is switched to this provider first;
260
+ * required for accounts that belong to multiple teams.
261
+ */
262
+ providerId?: string | number;
263
+ secretStore: SigningSecretStore;
264
+ /** Display name for a newly minted key. Required; there is no default. */
265
+ nickname: string;
266
+ /** Roles of a newly minted key. Defaults to APP_MANAGER. */
267
+ roles?: AppStoreConnectApiKeyRole[];
268
+ log?: (message: string, detail?: string) => void;
269
+ };
270
+
271
+ export type EnsureAppStoreConnectApiKeyResult = {
272
+ secret: SigningSecret;
273
+ keyId: string;
274
+ /** True when a new key was minted instead of reusing the stored one. */
275
+ created: boolean;
276
+ };
277
+
278
+ /**
279
+ * Returns a usable App Store Connect API key secret for the team, reusing
280
+ * the stored one when its key is still active and minting a new one
281
+ * otherwise. Apple serves a key's private half exactly once (at creation
282
+ * time through the sparse fieldset download), so reuse of the stored .p8
283
+ * is strongly preferred.
284
+ *
285
+ * The session user must be a team Admin to list or create keys.
286
+ */
287
+ export async function ensureAppStoreConnectApiKeySecret({
288
+ relay,
289
+ teamId,
290
+ providerId,
291
+ secretStore,
292
+ nickname,
293
+ roles,
294
+ log = () => {},
295
+ }: EnsureAppStoreConnectApiKeyInput): Promise<EnsureAppStoreConnectApiKeyResult> {
296
+ if (!nickname) {
297
+ throw new Error('A nickname is required to ensure an App Store Connect API key.');
298
+ }
299
+ if (providerId !== undefined) {
300
+ await switchAppStoreConnectProvider({ relay, providerId });
301
+ }
302
+ const secretName = appStoreConnectApiKeySecretName(teamId);
303
+ // Listing also primes the session's CSRF context, which Apple requires
304
+ // on the creation POST below.
305
+ const current = await listAppStoreConnectApiKeys({ relay });
306
+
307
+ const stored = await secretStore.get(APP_STORE_CONNECT_API_KEY_SECRET_TYPE, secretName);
308
+ const storedKeyId = stringField(stored?.data, 'keyId');
309
+ if (stored && storedKeyId && stored.data.privateKeyP8Base64) {
310
+ const matched = current.find((item) => item.id === storedKeyId && !keyRevoked(item.attributes));
311
+ if (matched) {
312
+ log('Reusing stored App Store Connect API key', storedKeyId);
313
+ return { secret: stored, keyId: storedKeyId, created: false };
314
+ }
315
+ log('Stored App Store Connect API key is no longer active', 'Minting a new one.');
316
+ }
317
+
318
+ const created = await createAppStoreConnectApiKey({ relay, nickname, roles });
319
+ const keyId = created.id!;
320
+ const downloaded = await downloadAppStoreConnectApiKeyPrivateKey({ relay, keyId });
321
+ const data: AppStoreConnectApiKeySecretData = {
322
+ privateKeyP8Base64: base64FromText(downloaded.privateKeyPem),
323
+ keyId,
324
+ issuerId: downloaded.issuerId,
325
+ nickname,
326
+ teamID: teamId,
327
+ };
328
+
329
+ let secret: SigningSecret;
330
+ try {
331
+ secret = await putAppStoreConnectApiKeySecret(secretStore, secretName, data);
332
+ } catch (error) {
333
+ // Apple never serves the private key again; once this throw unwinds
334
+ // the downloaded copy is gone and the portal key is unusable.
335
+ throw new Error(
336
+ `App Store Connect API key ${keyId} was created but saving its private key to the secret ` +
337
+ `store failed: ${errorText(error)}. The key cannot be downloaded again; revoke key ` +
338
+ `${keyId} in App Store Connect before retrying, or the retry will mint another key.`,
339
+ );
340
+ }
341
+ log('App Store Connect API key stored', keyId);
342
+ return { secret, keyId, created: true };
343
+ }
344
+
345
+ function keyRevoked(attributes: Record<string, unknown> | undefined) {
346
+ if (!attributes) return false;
347
+ if (attributes.isActive === false) return true;
348
+ const revokingDate = attributes.revokingDate ?? attributes.revocationDate;
349
+ return typeof revokingDate === 'string' && revokingDate !== '';
350
+ }
351
+
352
+ function base64FromText(text: string) {
353
+ const bytes = new TextEncoder().encode(text);
354
+ let binary = '';
355
+ for (const byte of bytes) {
356
+ binary += String.fromCharCode(byte);
357
+ }
358
+ return btoa(binary);
359
+ }
360
+
225
361
  export type ListTeamProfilesInput = {
226
362
  relay: AppleRelayWebSocketClient;
227
363
  teamId: string;
364
+ /**
365
+ * Which portal profile listing to use. The development listing is
366
+ * unfiltered on the portal side and returns profiles of every
367
+ * distribution method; adhoc narrows to ad-hoc profiles. Defaults to
368
+ * development.
369
+ */
370
+ profileKind?: AppleProfileKind;
228
371
  };
229
372
 
230
- /** Lists the team's development provisioning profiles from the portal. */
231
- export async function listTeamAppleProfiles({ relay, teamId }: ListTeamProfilesInput) {
232
- return listAppleProfiles({ relay, teamId, profileKind: 'development' });
373
+ /** Lists the team's provisioning profiles from the portal. */
374
+ export async function listTeamAppleProfiles({
375
+ relay,
376
+ teamId,
377
+ profileKind = 'development',
378
+ }: ListTeamProfilesInput) {
379
+ return listAppleProfiles({ relay, teamId, profileKind });
233
380
  }
234
381
 
235
382
  /** Read a string-ish value from a loosely typed portal record. */
@@ -63,12 +63,15 @@ export async function startBrowserOwnedAppleIDLogin({
63
63
  if (requiresTwoFactor) {
64
64
  twoFactorChallengeResponse = await triggerTrustedDeviceTwoFactor(relay);
65
65
  const phone = trustedPhoneNumberFromChallenge(twoFactorChallengeResponse.body);
66
- // Only route codes through the phone/SMS endpoint when Apple actually
67
- // falls back to phone verification (HTTP 412). The trusted-device
68
- // challenge body frequently includes trusted-phone metadata even for a
69
- // normal device push; routing those codes to the phone endpoint breaks
70
- // trusted-device login.
71
- if (twoFactorChallengeResponse.status === 412) {
66
+ // Route codes through the phone/SMS endpoint only when Apple actually
67
+ // falls back to phone verification: HTTP 412 on the trusted-device
68
+ // trigger, or a 2xx challenge whose body says noTrustedDevices (seen on
69
+ // managed Apple accounts). The trusted-device challenge body frequently
70
+ // includes trusted-phone metadata even for a normal device push;
71
+ // routing those codes to the phone endpoint breaks trusted-device
72
+ // login, while submitting an SMS-fallback code to the trusted-device
73
+ // endpoint returns 201 without completing authentication.
74
+ if (twoFactorChallengeResponse.status === 412 || challengeHasNoTrustedDevices(twoFactorChallengeResponse.body)) {
72
75
  if (!phone) {
73
76
  throw new Error('Apple requested phone verification but did not include a trusted phone number.');
74
77
  }
@@ -117,6 +120,10 @@ export async function startBrowserOwnedAppleIDLogin({
117
120
  }
118
121
  }
119
122
 
123
+ function challengeHasNoTrustedDevices(body: unknown) {
124
+ return isRecord(body) && body.noTrustedDevices === true;
125
+ }
126
+
120
127
  function trustedPhoneNumberFromChallenge(body: unknown) {
121
128
  if (!isRecord(body)) return undefined;
122
129
  const verification = isRecord(body.phoneNumberVerification) ? body.phoneNumberVerification : undefined;
@@ -3,9 +3,11 @@
3
3
  import { describe, expect, test } from 'vitest';
4
4
  import {
5
5
  createAdHocProfileRequest,
6
+ createAppStoreProfileRequest,
6
7
  createDevelopmentProfileRequest,
7
8
  downloadDistributionCertificateRequest,
8
9
  findAdHocProfilesRequest,
10
+ findAppStoreProfilesRequest,
9
11
  findDevelopmentCertificatesRequest,
10
12
  findDistributionCertificatesRequest,
11
13
  submitDistributionCSRRequest,
@@ -81,4 +83,32 @@ describe('Apple provisioning request helpers', () => {
81
83
  distributionType: 'adhoc',
82
84
  });
83
85
  });
86
+
87
+ test('builds App Store profile payloads without devices and with the caller-provided name', () => {
88
+ expect(
89
+ createAppStoreProfileRequest({
90
+ teamID: 'TEAM',
91
+ appIDID: 'APP',
92
+ certificateID: 'CERT',
93
+ name: 'Acme App Store com.example.app',
94
+ }).payload,
95
+ ).toEqual({
96
+ teamId: 'TEAM',
97
+ provisioningProfileName: 'Acme App Store com.example.app',
98
+ certificateIds: ['CERT'],
99
+ appIdId: 'APP',
100
+ distributionType: 'store',
101
+ subPlatform: 'ios',
102
+ });
103
+ });
104
+
105
+ test('filters App Store profile list requests by store distribution type', () => {
106
+ expect(
107
+ findAppStoreProfilesRequest({ bundleID: 'com.example.app', teamID: 'TEAM' }).payload,
108
+ ).toMatchObject({
109
+ teamId: 'TEAM',
110
+ search: 'com.example.app',
111
+ distributionType: 'store',
112
+ });
113
+ });
84
114
  });
@@ -141,6 +141,17 @@ export function findAdHocProfilesRequest({
141
141
  });
142
142
  }
143
143
 
144
+ export function findAppStoreProfilesRequest({
145
+ bundleID,
146
+ teamID = '',
147
+ }: Pick<AppleProvisioningContext, 'bundleID' | 'teamID'>) {
148
+ return pagedRequest('/account/ios/profile/listProvisioningProfiles.action', teamID, {
149
+ search: bundleID,
150
+ sort: 'name=asc',
151
+ distributionType: 'store',
152
+ });
153
+ }
154
+
144
155
  export function registerDeviceRequest({
145
156
  deviceUDID,
146
157
  teamID = '',
@@ -280,6 +291,34 @@ export function createAdHocProfileRequest({
280
291
  } satisfies AppleProvisioningRequest;
281
292
  }
282
293
 
294
+ /**
295
+ * App Store distribution profiles bind no devices, and the name is required
296
+ * because implementors brand profiles themselves; there is no default.
297
+ */
298
+ export function createAppStoreProfileRequest({
299
+ teamID = '',
300
+ appIDID,
301
+ certificateID,
302
+ name,
303
+ }: Pick<AppleProvisioningContext, 'teamID'> & {
304
+ appIDID: string;
305
+ certificateID: string;
306
+ name: string;
307
+ }) {
308
+ return {
309
+ method: 'POST',
310
+ path: '/account/ios/profile/createProvisioningProfile.action',
311
+ payload: {
312
+ teamId: teamID,
313
+ provisioningProfileName: name,
314
+ certificateIds: [certificateID],
315
+ appIdId: appIDID,
316
+ distributionType: 'store',
317
+ subPlatform: 'ios',
318
+ },
319
+ } satisfies AppleProvisioningRequest;
320
+ }
321
+
283
322
  export function downloadProfileRequest(profileID: string, teamID = '') {
284
323
  return {
285
324
  method: 'GET',
@@ -15,6 +15,19 @@ export type AppleProvisioningRequest = {
15
15
  payload?: unknown;
16
16
  };
17
17
 
18
+ /**
19
+ * A session-authenticated App Store Connect request proxied by the relay.
20
+ * Unlike provisioning requests, payloads pass through as JSON and GET
21
+ * parameters go into an explicit query map because the App Store Connect
22
+ * API uses bracketed JSON:API keys like fields[apiKeys].
23
+ */
24
+ export type AppStoreConnectRequest = {
25
+ method?: 'GET' | 'POST';
26
+ path: string;
27
+ query?: Record<string, string>;
28
+ payload?: unknown;
29
+ };
30
+
18
31
  type AppleRelayWebSocketMessage<T = unknown> = {
19
32
  id: string;
20
33
  ok: boolean;
@@ -154,6 +167,13 @@ export async function proxyProvisioningRequest<T = unknown>(
154
167
  return relay.request<T>('provisioning', request);
155
168
  }
156
169
 
170
+ export async function proxyAppStoreConnectRequest<T = unknown>(
171
+ relay: AppleRelayWebSocketClient,
172
+ request: AppStoreConnectRequest,
173
+ ) {
174
+ return relay.request<T>('appstoreconnect', request);
175
+ }
176
+
157
177
  function normalizeAppleProxyResponse<T>(response: AppleRelayResponse<T>) {
158
178
  if (response.body !== undefined || !response.rawBody) {
159
179
  return response;
@@ -180,7 +180,12 @@ async function claimBestUsbmuxCandidate(target: DeviceRelayTarget, log: DeviceIn
180
180
  }
181
181
  }
182
182
  }
183
- throw lastError instanceof Error ? lastError : new Error('Unable to claim any Apple usbmux interface.');
183
+ const detail = lastError ? ` (${errorMessage(lastError)})` : '';
184
+ throw new Error(
185
+ 'Could not get exclusive USB access to the iPhone' +
186
+ `${detail}. Another app on this computer is likely connected to it. ` +
187
+ 'Close other browser tabs that use this device, quit iPhone Mirroring, Xcode, eject iPhone in Finder windows showing the phone and try again.',
188
+ );
184
189
  }
185
190
 
186
191
  function pickUsbmuxCandidate(device: USBDevice) {
@@ -10,10 +10,12 @@
10
10
 
11
11
  export const APPLE_CERTIFICATE_SECRET_TYPE = 'appleCertificate';
12
12
  export const APPLE_PROVISIONING_PROFILE_SECRET_TYPE = 'appleProvisioningProfile';
13
+ export const APP_STORE_CONNECT_API_KEY_SECRET_TYPE = 'appStoreConnectApiKey';
13
14
 
14
15
  export type SigningSecretType =
15
16
  | typeof APPLE_CERTIFICATE_SECRET_TYPE
16
- | typeof APPLE_PROVISIONING_PROFILE_SECRET_TYPE;
17
+ | typeof APPLE_PROVISIONING_PROFILE_SECRET_TYPE
18
+ | typeof APP_STORE_CONNECT_API_KEY_SECRET_TYPE;
17
19
 
18
20
  export type SigningSecretMetadata = {
19
21
  type: string;
@@ -94,6 +96,24 @@ export type AppleProvisioningProfileSecretData = {
94
96
  expirationDate?: string;
95
97
  };
96
98
 
99
+ /**
100
+ * Data payload of an appStoreConnectApiKey secret: the private half of an
101
+ * App Store Connect API key plus the identifiers needed to sign JWTs with
102
+ * it. Apple serves the private key exactly once, so the stored copy is the
103
+ * only one.
104
+ */
105
+ export type AppStoreConnectApiKeySecretData = {
106
+ /** Base64 of the .p8 private key PEM. */
107
+ privateKeyP8Base64: string;
108
+ /** Apple's key ID, e.g. 2X9R4HXF34. */
109
+ keyId: string;
110
+ /** Issuer ID for team keys. Absent for individual keys. */
111
+ issuerId?: string;
112
+ nickname?: string;
113
+ teamID?: string;
114
+ expirationDate?: string;
115
+ };
116
+
97
117
  function compactData(data: Record<string, string | undefined>): SigningSecretData {
98
118
  const result: SigningSecretData = {};
99
119
  for (const [key, value] of Object.entries(data)) {
@@ -124,6 +144,18 @@ export async function putAppleProvisioningProfileSecret(
124
144
  return store.put(APPLE_PROVISIONING_PROFILE_SECRET_TYPE, name, compactData(data));
125
145
  }
126
146
 
147
+ /**
148
+ * Stores an App Store Connect API key, conventionally named
149
+ * `${teamID}/APP_STORE_CONNECT_API_KEY`: one shared key per team.
150
+ */
151
+ export async function putAppStoreConnectApiKeySecret(
152
+ store: SigningSecretStore,
153
+ name: string,
154
+ data: AppStoreConnectApiKeySecretData,
155
+ ) {
156
+ return store.put(APP_STORE_CONNECT_API_KEY_SECRET_TYPE, name, compactData(data));
157
+ }
158
+
127
159
  export type LimrunSecretStoreOptions = {
128
160
  /** Base URL of the Limrun backend API, e.g. https://api.limrun.com */
129
161
  apiUrl: string;
@@ -200,7 +232,9 @@ export function createLimrunSecretStore(options: LimrunSecretStoreOptions): Sign
200
232
  return result
201
233
  .filter(
202
234
  (s) =>
203
- s.type === APPLE_CERTIFICATE_SECRET_TYPE || s.type === APPLE_PROVISIONING_PROFILE_SECRET_TYPE,
235
+ s.type === APPLE_CERTIFICATE_SECRET_TYPE ||
236
+ s.type === APPLE_PROVISIONING_PROFILE_SECRET_TYPE ||
237
+ s.type === APP_STORE_CONNECT_API_KEY_SECRET_TYPE,
204
238
  )
205
239
  .map((s) => ({ type: s.type, name: s.name, createdAt: s.createdAt }));
206
240
  },
@@ -1 +0,0 @@
1
- "use strict";const F=require("./gsa-srp-CCIfUz7Y.js");class g{constructor(t,r,o){this.apiUrl=t,this.token=r,this.organizationId=o}socket;nextId=1;pending=new Map;async connect(){if(this.socket&&this.socket.readyState===WebSocket.OPEN)return;const t=new WebSocket(E(this.apiUrl,this.token,this.organizationId));this.socket=t,t.onmessage=r=>this.handleMessage(r),t.onclose=()=>this.rejectPending(new Error("Apple relay WebSocket closed")),t.onerror=()=>this.rejectPending(new Error("Apple relay WebSocket failed")),await new Promise((r,o)=>{t.onopen=()=>r(),t.onerror=()=>o(new Error("Apple relay WebSocket connection failed"))})}request(t,r){const o=this.socket;if(!o||o.readyState!==WebSocket.OPEN)throw new Error("Apple relay WebSocket is not connected.");const a=String(this.nextId++),n=new Promise((p,i)=>{this.pending.set(a,{resolve:h=>p(q(h)),reject:i})});return o.send(JSON.stringify({id:a,type:t,...r===void 0?{}:{payload:r}})),n}close(){this.rejectPending(new Error("Apple relay WebSocket closed")),this.socket?.close(),this.socket=void 0}handleMessage(t){const r=JSON.parse(String(t.data)),o=this.pending.get(r.id);if(o){if(this.pending.delete(r.id),!r.ok){o.reject(new Error(r.error||"Apple relay request failed"));return}if(!r.response){o.reject(new Error("Apple relay response was empty."));return}o.resolve(r.response)}}rejectPending(t){for(const r of this.pending.values())r.reject(t);this.pending.clear()}}async function m(e,t,r){const o=new g(e,t,r);return await o.connect(),o}async function S(e,t){return e.request("srpInit",t)}async function A(e,t){return e.request("srpComplete",t)}async function P(e){return e.request("triggerTrustedDevice2FA")}async function b(e,t,r="sms"){return e.request("triggerPhone2FA",{phoneNumberId:t,mode:r})}async function k(e,t){return e.request("submitTrustedDevice2FA",{code:t})}async function T(e,t,r,o="sms"){return e.request("submitPhone2FA",{phoneNumberId:t,mode:o,code:r})}async function v(e){return e.request("finalize")}async function R(e,t){return e.request("provisioning",t)}function q(e){if(e.body!==void 0||!e.rawBody)return e;try{return{...e,body:JSON.parse(e.rawBody)}}catch{return e}}function E(e,t,r){const o=new URL(e);return o.protocol=o.protocol==="https:"?"wss:":"ws:",o.pathname=`${o.pathname.replace(/\/$/,"")}/ios/appstoreconnect/ws`,t&&o.searchParams.set("token",t),r&&o.searchParams.set("organization",r),o.toString()}async function N({registryApiUrl:e,accountName:t,password:r,token:o,organizationId:a}){const n=await m(e,o,a);try{const p=new F.AppleGsaSrpClient(t),i=await S(n,await p.init());if(i.status<200||i.status>=300)throw new Error(`Apple SRP init failed: HTTP ${i.status} ${i.rawBody??""}`.trim());if(!i.body)throw new Error("Apple SRP init response did not include a body.");const h=await p.complete(r,i.body),u=await A(n,{...h,rememberMe:!1,trustTokens:[]}),y=u.status===409;let s,d={type:"trustedDevice"};if(y){s=await P(n);const c=x(s.body);if(s.status===412){if(!c)throw new Error("Apple requested phone verification but did not include a trusted phone number.");d={type:"phone",phoneNumberId:c.id,mode:c.pushMode??"sms"},s=await b(n,c.id,c.pushMode??"sms")}if(s.status<200||s.status>=300)throw new Error(`Apple two-factor challenge failed: HTTP ${s.status} ${s.rawBody??""}`.trim())}else if(u.status<200||u.status>=300)throw new Error(`Apple SRP complete failed: HTTP ${u.status} ${u.rawBody??""}`.trim());return{relay:n,completeResponse:u,twoFactorChallengeResponse:s,requiresTwoFactor:y,finishTwoFactor:async c=>{const l=d.type==="phone"?await T(n,d.phoneNumberId,c,d.mode):await k(n,c);if(l.status<200||l.status>=300)throw new Error(`Apple two-factor code failed: HTTP ${l.status} ${l.rawBody??""}`.trim());return l},finalize:async()=>v(n),close:async()=>n.close()}}catch(p){throw n.close(),p}}function x(e){if(!f(e))return;const t=f(e.phoneNumberVerification)?e.phoneNumberVerification:void 0,r=w(t?.trustedPhoneNumber)??w(e.trustedPhoneNumber)??w(e.phoneNumber);if(!r)return;const o=r.id;if(typeof o!="number")return;const a=typeof r.pushMode=="string"?r.pushMode:typeof e.mode=="string"?e.mode:void 0;return{id:o,pushMode:a}}function w(e){return f(e)?e:void 0}function f(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}exports.AppleRelayWebSocketClient=g;exports.fetchAppleAccountSession=v;exports.openAppleRelayWebSocket=m;exports.proxyPhoneTwoFactorCode=T;exports.proxyProvisioningRequest=R;exports.proxySrpComplete=A;exports.proxySrpInit=S;exports.proxyTwoFactorCode=k;exports.startBrowserOwnedAppleIDLogin=N;exports.triggerPhoneTwoFactor=b;exports.triggerTrustedDeviceTwoFactor=P;
@@ -1 +0,0 @@
1
- "use strict";const a=require("./browser-storage-DLxIqZk5.js");function c(){return{method:"POST",path:"/account/listTeams.action",payload:{}}}function u({bundleID:e,teamID:t=""}){return s("/account/ios/identifiers/listAppIds.action",t,{sort:"name=asc"})}function d({deviceUDID:e,teamID:t=""}){return s("/account/ios/device/listDevices.action",t,{sort:"name=asc",includeRemovedDevices:!1})}function f(e=""){return s("/account/ios/certificate/listCertRequests.action",e,{sort:"name=asc",types:"83Q87W3TGH,5QPB9NHCEI"})}function l(e=""){return s("/account/ios/certificate/listCertRequests.action",e,{sort:"name=asc",types:"WXV89964HE,R58UK2EWSO"})}function p({bundleID:e,teamID:t=""}){return s("/account/ios/profile/listProvisioningProfiles.action",t,{search:e,sort:"name=asc"})}function m({bundleID:e,teamID:t=""}){return s("/account/ios/profile/listProvisioningProfiles.action",t,{search:e,sort:"name=asc",distributionType:"adhoc"})}function R({deviceUDID:e,teamID:t="",name:i="Limrun iPhone"}){return{method:"POST",path:"/account/ios/device/addDevices.action",payload:{teamId:t,deviceNames:i,deviceNumbers:a.normalizeUDID(e),deviceClasses:"iphone",register:"single"}}}function g({bundleID:e,teamID:t="",name:i}){return{method:"POST",path:"/account/ios/identifiers/addAppId.action",payload:{teamId:t,name:i??e,identifier:e,type:"explicit"}}}function q({csrPEM:e,teamID:t=""}){return{method:"POST",path:"/account/ios/certificate/submitCertificateRequest.action",payload:{teamId:t,type:"83Q87W3TGH",csrContent:e}}}function v({csrPEM:e,teamID:t=""}){return{method:"POST",path:"/account/ios/certificate/submitCertificateRequest.action",payload:{teamId:t,type:"WXV89964HE",csrContent:e}}}function P(e,t=""){return{method:"GET",path:"/account/ios/certificate/downloadCertificateContent.action",payload:{teamId:t,certificateId:e,type:"83Q87W3TGH"}}}function D(e,t=""){return{method:"GET",path:"/account/ios/certificate/downloadCertificateContent.action",payload:{teamId:t,certificateId:e,type:"WXV89964HE"}}}function I({bundleID:e,teamID:t="",appIDID:i,certificateID:o,deviceIDs:n,name:r}){return{method:"POST",path:"/account/ios/profile/createProvisioningProfile.action",payload:{teamId:t,provisioningProfileName:r??`Limrun ${e}`,certificateIds:[o],appIdId:i,deviceIds:n,distributionType:"limited",subPlatform:"ios"}}}function h({bundleID:e,teamID:t="",appIDID:i,certificateID:o,deviceIDs:n,name:r}){return{method:"POST",path:"/account/ios/profile/createProvisioningProfile.action",payload:{teamId:t,provisioningProfileName:r??`Limrun Ad Hoc ${e}`,certificateIds:[o],appIdId:i,deviceIds:n,distributionType:"adhoc",subPlatform:"ios"}}}function C(e,t=""){return{method:"GET",path:"/account/ios/profile/downloadProfileContent",payload:{teamId:t,provisioningProfileId:e}}}async function b({bundleID:e,deviceUDID:t,teamID:i,signingMode:o}){const n=await a.getSigningAssets({bundleID:e,deviceUDID:t,signingMode:o});if(!(!n||!S(n,{bundleID:e,deviceUDID:t,teamID:i,signingMode:o})))return n}async function y(e){return a.putSigningAssets({...e,certificateFileName:e.certificateID?`${e.certificateID}.p12`:`apple-${e.signingMode??"development"}.p12`,certificatePassword:e.certificatePassword||void 0,signingMode:e.signingMode,profileFileName:e.profile.uuid?`${e.profile.uuid}.mobileprovision`:`${e.signingMode??"development"}.mobileprovision`})}function S(e,{bundleID:t,deviceUDID:i,teamID:o,signingMode:n="development"}){return(e.signingMode??"development")!==n||!a.profileMatchesBundleID(e.profile,t)||o&&e.teamID&&e.teamID!==o||i&&!a.profileContainsDevice(e.profile,i)||e.profile.expirationDate&&new Date(e.profile.expirationDate).getTime()<=Date.now()?!1:a.normalizeBundleID(e.bundleID)===a.normalizeBundleID(t)}function s(e,t,i={}){const o={pageNumber:1,pageSize:200,...i};return t&&(o.teamId=t),{method:"POST",path:e,payload:o}}exports.createAdHocProfileRequest=h;exports.createBundleIDRequest=g;exports.createDevelopmentProfileRequest=I;exports.downloadCertificateRequest=P;exports.downloadDistributionCertificateRequest=D;exports.downloadProfileRequest=C;exports.findAdHocProfilesRequest=m;exports.findBundleIDRequest=u;exports.findDevelopmentCertificatesRequest=f;exports.findDevelopmentProfilesRequest=p;exports.findDeviceRequest=d;exports.findDistributionCertificatesRequest=l;exports.getReusableAppleSigningAssets=b;exports.listTeamsRequest=c;exports.putAppleGeneratedSigningAssets=y;exports.registerDeviceRequest=R;exports.submitDevelopmentCSRRequest=q;exports.submitDistributionCSRRequest=v;