@limrun/ui 0.9.0-rc.14 → 0.9.0-rc.15

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 (30) hide show
  1. package/dist/components/remote-control.d.ts +125 -1
  2. package/dist/core/device-install/apple/provisioning.d.ts +55 -4
  3. package/dist/core/device-install/operations/limbuild-client.d.ts +15 -1
  4. package/dist/core/device-install/storage/browser-storage.d.ts +9 -5
  5. package/dist/core/device-install/types.d.ts +4 -1
  6. package/dist/device-install/index.cjs +1 -1
  7. package/dist/device-install/index.js +70 -64
  8. package/dist/device-install/react.cjs +1 -1
  9. package/dist/device-install/react.js +1 -1
  10. package/dist/device-install-dialog-DY35un0b.js +9 -0
  11. package/dist/device-install-dialog-chNLeiiL.mjs +2000 -0
  12. package/dist/device-install-dialog.css +1 -1
  13. package/dist/hooks/use-device-install.d.ts +5 -1
  14. package/dist/index.cjs +1 -1
  15. package/dist/index.js +1286 -1116
  16. package/dist/use-device-install-BIrl0v-k.js +31 -0
  17. package/dist/{use-device-install-sDVvby1V.mjs → use-device-install-LGfEdqyM.mjs} +4388 -4271
  18. package/package.json +4 -2
  19. package/src/components/device-install/device-install-dialog.css +29 -0
  20. package/src/components/device-install/device-install-dialog.tsx +91 -30
  21. package/src/components/remote-control.tsx +535 -5
  22. package/src/core/device-install/apple/provisioning.test.ts +84 -0
  23. package/src/core/device-install/apple/provisioning.ts +91 -7
  24. package/src/core/device-install/operations/limbuild-client.ts +32 -2
  25. package/src/core/device-install/storage/browser-storage.ts +29 -14
  26. package/src/core/device-install/types.ts +5 -1
  27. package/src/hooks/use-device-install.ts +135 -59
  28. package/dist/device-install-dialog-CjH25hnN.js +0 -2
  29. package/dist/device-install-dialog-W5Xv9kWL.mjs +0 -443
  30. package/dist/use-device-install-Y1u6vIBB.js +0 -31
@@ -1,13 +1,17 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import {
3
3
  closeDeviceRelayTarget,
4
+ createAdHocProfileRequest,
4
5
  createBundleIDRequest,
5
6
  createDevelopmentProfileRequest,
6
7
  downloadCertificateRequest,
8
+ downloadDistributionCertificateRequest,
7
9
  downloadProfileRequest,
8
10
  exportAppleCertificateP12,
9
11
  fetchLimbuildInfo,
12
+ findAdHocProfilesRequest,
10
13
  findDevelopmentCertificatesRequest,
14
+ findDistributionCertificatesRequest,
11
15
  findBundleIDRequest,
12
16
  findDeviceRequest,
13
17
  findDevelopmentProfilesRequest,
@@ -29,8 +33,10 @@ import {
29
33
  requestUSBAccess as requestDeviceUSBAccess,
30
34
  startBrowserOwnedAppleIDLogin,
31
35
  startSignedDeviceBuild,
36
+ getIOSOTAInstall,
32
37
  startInstallRelay,
33
38
  startPairingRelay,
39
+ submitDistributionCSRRequest,
34
40
  submitDevelopmentCSRRequest,
35
41
  watchBuildLogEvents,
36
42
  type AppleIDLoginResult,
@@ -43,7 +49,9 @@ import {
43
49
  type DeviceInstallBusyAction,
44
50
  type DeviceInstallStep,
45
51
  type DeviceInstallStepStatus,
52
+ type DeviceInstallSigningMode,
46
53
  type DeviceRelayTarget,
54
+ type IOSOTAInstall,
47
55
  type StoredPairRecord,
48
56
  type StoredSigningAssets,
49
57
  } from '../core/device-install';
@@ -74,6 +82,7 @@ export type UseDeviceInstallResult = {
74
82
  logs: string[];
75
83
  buildLogs: BuildLogLine[];
76
84
  buildStatus: DeviceInstallBuildStatus;
85
+ currentExecID?: string;
77
86
  appleSigningStatus: DeviceInstallAppleSigningStatus;
78
87
  appleTeams: AppleDeveloperPortalTeam[];
79
88
  appleDevices: AppleDeveloperPortalDevice[];
@@ -81,10 +90,12 @@ export type UseDeviceInstallResult = {
81
90
  applePortalSummary?: ApplePortalSummary;
82
91
  selectedAppleTeamID?: string;
83
92
  selectedAppleDeviceIDs: string[];
93
+ signingMode: DeviceInstallSigningMode;
84
94
  connectedAppleDeviceRegistered: boolean;
85
95
  connectedDeviceInProfile?: boolean;
86
96
  hasReusableAppleCertificate: boolean;
87
97
  appleBundleID: string;
98
+ otaInstall?: IOSOTAInstall;
88
99
  buildLogPanelOpen: boolean;
89
100
  busyAction?: DeviceInstallBusyAction;
90
101
  error?: string;
@@ -96,6 +107,7 @@ export type UseDeviceInstallResult = {
96
107
  setSigningFiles: (files: DeviceInstallSigningFiles) => void;
97
108
  setAppleBundleID: (bundleID: string) => void;
98
109
  setSelectedAppleDeviceIDs: (deviceIDs: string[]) => void;
110
+ setSigningMode: (mode: DeviceInstallSigningMode) => void;
99
111
  setBuildLogPanelOpen: (open: boolean) => void;
100
112
  startAppleIDLogin: (input: DeviceInstallAppleIDLoginInput) => Promise<void>;
101
113
  submitAppleTwoFactorCode: (code: string) => Promise<void>;
@@ -167,12 +179,15 @@ export function useDeviceInstall({
167
179
  const [appleTeams, setAppleTeams] = useState<AppleDeveloperPortalTeam[]>([]);
168
180
  const [appleDevices, setAppleDevices] = useState<AppleDeveloperPortalDevice[]>([]);
169
181
  const [appleAppIDs, setAppleAppIDs] = useState<AppleDeveloperPortalAppID[]>([]);
182
+ const [signingMode, setSigningModeState] = useState<DeviceInstallSigningMode>('development');
170
183
  const [selectedAppleDeviceIDs, setSelectedAppleDeviceIDs] = useState<string[]>([]);
171
184
  const [applePortalSummary, setApplePortalSummary] = useState<ApplePortalSummary | undefined>();
172
185
  const [selectedAppleTeamID, setSelectedAppleTeamID] = useState<string | undefined>();
173
186
  const [appleBundleID, setAppleBundleID] = useState('');
174
187
  const [reusableAppleCertificate, setReusableAppleCertificate] = useState<ReusableAppleCertificate | undefined>();
175
188
  const [buildLogPanelOpen, setBuildLogPanelOpen] = useState(false);
189
+ const [otaInstall, setOTAInstall] = useState<IOSOTAInstall | undefined>();
190
+ const [currentExecID, setCurrentExecID] = useState<string | undefined>();
176
191
  const [busyAction, setBusyAction] = useState<DeviceInstallBusyAction | undefined>();
177
192
  const [error, setError] = useState<string | undefined>();
178
193
  const [pairConfirmationRequired, setPairConfirmationRequired] = useState(false);
@@ -194,7 +209,7 @@ export function useDeviceInstall({
194
209
  const setSigningFiles = useCallback((files: DeviceInstallSigningFiles) => {
195
210
  setSigningFilesState((current) => {
196
211
  const next = { ...current, ...files };
197
- const ready = !!next.certificateFile && !!next.provisioningProfileFile && !!next.certificatePassword;
212
+ const ready = !!next.certificateFile && !!next.provisioningProfileFile;
198
213
  setStepStatus('signing', ready ? 'complete' : 'active');
199
214
  if (ready) {
200
215
  setAppleSigningStatus('assets-ready');
@@ -205,9 +220,19 @@ export function useDeviceInstall({
205
220
  setSigningAssets(undefined);
206
221
  }, [setStepStatus]);
207
222
 
223
+ const setSigningMode = useCallback((mode: DeviceInstallSigningMode) => {
224
+ setSigningModeState(mode);
225
+ setSigningAssets(undefined);
226
+ setOTAInstall(undefined);
227
+ setCurrentStep('signing');
228
+ setStepStatuses(initialStepStatuses);
229
+ setAppleSigningStatus('idle');
230
+ log(mode === 'adhoc' ? 'Using Ad Hoc signing mode' : 'Using development signing mode');
231
+ }, [log]);
232
+
208
233
  useEffect(() => {
209
234
  let cancelled = false;
210
- void getLatestSigningAssets().then((stored) => {
235
+ void getLatestSigningAssets(signingMode).then((stored) => {
211
236
  if (cancelled || !stored) return;
212
237
  setSigningAssets(stored);
213
238
  setAppleBundleID(stored.bundleID);
@@ -219,7 +244,7 @@ export function useDeviceInstall({
219
244
  return () => {
220
245
  cancelled = true;
221
246
  };
222
- }, [log, setStepStatus]);
247
+ }, [log, setStepStatus, signingMode]);
223
248
 
224
249
  const selectedDeveloperTeamID = useCallback(() => {
225
250
  return developerPortalTeamID(appleTeams.find((team) => appleTeamSelectionID(team) === selectedAppleTeamID));
@@ -232,7 +257,7 @@ export function useDeviceInstall({
232
257
  return;
233
258
  }
234
259
  let cancelled = false;
235
- void findReusableAppleCertificate(teamID).then((certificate) => {
260
+ void findReusableAppleCertificate(teamID, signingMode).then((certificate) => {
236
261
  if (!cancelled) {
237
262
  setReusableAppleCertificate(certificate);
238
263
  }
@@ -240,14 +265,14 @@ export function useDeviceInstall({
240
265
  return () => {
241
266
  cancelled = true;
242
267
  };
243
- }, [selectedDeveloperTeamID]);
268
+ }, [selectedDeveloperTeamID, signingMode]);
244
269
 
245
270
  const connectedAppleDevice = selectedDevice?.hello.serialNumber
246
271
  ? appleDevices.find((device) => normalizeAppleUDID(device.deviceNumber) === normalizeAppleUDID(selectedDevice.hello.serialNumber))
247
272
  : undefined;
248
273
  const connectedAppleDeviceRegistered = !!connectedAppleDevice?.deviceId;
249
274
  const manualSigningFilesReady =
250
- !!signingFiles.certificateFile && !!signingFiles.provisioningProfileFile && !!signingFiles.certificatePassword;
275
+ !!signingFiles.certificateFile && !!signingFiles.provisioningProfileFile;
251
276
  const signingInputsReady = !!signingAssets || manualSigningFilesReady;
252
277
  const connectedDeviceInProfile =
253
278
  selectedDevice?.hello.serialNumber && signingAssets
@@ -288,6 +313,7 @@ export function useDeviceInstall({
288
313
  bundleID,
289
314
  deviceUDID: selectedDevice?.hello.serialNumber,
290
315
  teamID: selectedDeveloperTeamID(),
316
+ signingMode,
291
317
  });
292
318
  if (cached) {
293
319
  setAppleSigningStatus('using-cached-profile');
@@ -296,7 +322,7 @@ export function useDeviceInstall({
296
322
  return cached;
297
323
  }
298
324
  }
299
- const stored = await getLatestSigningAssets();
325
+ const stored = await getLatestSigningAssets(signingMode);
300
326
  if (stored) {
301
327
  log('Using stored signing assets', stored.bundleID);
302
328
  setSigningAssets(stored);
@@ -304,10 +330,9 @@ export function useDeviceInstall({
304
330
  }
305
331
  if (
306
332
  !signingFiles.certificateFile ||
307
- !signingFiles.provisioningProfileFile ||
308
- !signingFiles.certificatePassword
333
+ !signingFiles.provisioningProfileFile
309
334
  ) {
310
- throw new Error('Upload a certificate, provisioning profile, and certificate password.');
335
+ throw new Error('Upload a certificate and provisioning profile.');
311
336
  }
312
337
  log('Preparing signing assets');
313
338
  const [certificateP12Base64, provisioningProfileBase64, profile] = await Promise.all([
@@ -318,13 +343,17 @@ export function useDeviceInstall({
318
343
  if (selectedDevice?.hello.serialNumber && !profileContainsDevice(profile, selectedDevice.hello.serialNumber)) {
319
344
  throw new Error('Provisioning profile does not include the selected iPhone.');
320
345
  }
346
+ if (signingMode === 'adhoc' && profile.getTaskAllow) {
347
+ throw new Error('Ad Hoc mode requires an Ad Hoc provisioning profile, not a development profile.');
348
+ }
321
349
  const storageBundleId = profile.bundleID ?? profile.applicationIdentifier ?? signingFiles.provisioningProfileFile.name;
322
350
  const storedAssets = await putSigningAssets({
323
351
  deviceUDID: selectedDevice?.hello.serialNumber,
352
+ signingMode,
324
353
  bundleID: storageBundleId,
325
354
  certificateP12Base64,
326
355
  certificateFileName: signingFiles.certificateFile.name,
327
- certificatePassword: signingFiles.certificatePassword,
356
+ certificatePassword: signingFiles.certificatePassword || undefined,
328
357
  provisioningProfileBase64,
329
358
  profileFileName: signingFiles.provisioningProfileFile.name,
330
359
  profile,
@@ -332,7 +361,7 @@ export function useDeviceInstall({
332
361
  setSigningAssets(storedAssets);
333
362
  log('Signing assets stored locally', storageBundleId);
334
363
  return storedAssets;
335
- }, [apiUrl, appleBundleID, log, selectedDeveloperTeamID, selectedDevice?.hello.serialNumber, signingAssets, signingFiles, token]);
364
+ }, [apiUrl, appleBundleID, log, selectedDeveloperTeamID, selectedDevice?.hello.serialNumber, signingAssets, signingFiles, signingMode, token]);
336
365
 
337
366
  const startAppleIDLogin = useCallback(
338
367
  async ({ accountName, password }: DeviceInstallAppleIDLoginInput) => {
@@ -368,7 +397,7 @@ export function useDeviceInstall({
368
397
  log,
369
398
  });
370
399
  }
371
- await refreshApplePortalSummary(apiUrl, session.appleSessionId, token, teamID, setApplePortalSummary, log);
400
+ await refreshApplePortalSummary(apiUrl, session.appleSessionId, token, teamID, signingMode, setApplePortalSummary, log);
372
401
  }
373
402
  setAppleSigningStatus(session.requiresTwoFactor ? 'two-factor-required' : 'authenticated');
374
403
  log(
@@ -384,7 +413,7 @@ export function useDeviceInstall({
384
413
  setBusyAction(undefined);
385
414
  }
386
415
  },
387
- [apiUrl, log, setStepStatus, token],
416
+ [apiUrl, log, setStepStatus, signingMode, token],
388
417
  );
389
418
 
390
419
  const submitAppleTwoFactorCode = useCallback(
@@ -421,7 +450,7 @@ export function useDeviceInstall({
421
450
  log,
422
451
  });
423
452
  }
424
- await refreshApplePortalSummary(apiUrl, session.appleSessionId, token, teamID, setApplePortalSummary, log);
453
+ await refreshApplePortalSummary(apiUrl, session.appleSessionId, token, teamID, signingMode, setApplePortalSummary, log);
425
454
  }
426
455
  setAppleSigningStatus('authenticated');
427
456
  log('Apple ID two-factor authentication accepted');
@@ -434,7 +463,7 @@ export function useDeviceInstall({
434
463
  setBusyAction(undefined);
435
464
  }
436
465
  },
437
- [apiUrl, log, setStepStatus, token],
466
+ [apiUrl, log, setStepStatus, signingMode, token],
438
467
  );
439
468
 
440
469
  const clearAppleIDLogin = useCallback(() => {
@@ -468,7 +497,7 @@ export function useDeviceInstall({
468
497
  void (async () => {
469
498
  try {
470
499
  await refreshAppleAppIDs(apiUrl, session.appleSessionId, token, developerTeamID, setAppleAppIDs, setAppleBundleID);
471
- await refreshApplePortalSummary(apiUrl, session.appleSessionId, token, developerTeamID, setApplePortalSummary, log);
500
+ await refreshApplePortalSummary(apiUrl, session.appleSessionId, token, developerTeamID, signingMode, setApplePortalSummary, log);
472
501
  await refreshAppleDevices({
473
502
  apiUrl,
474
503
  token,
@@ -486,7 +515,7 @@ export function useDeviceInstall({
486
515
  }
487
516
  })();
488
517
  },
489
- [apiUrl, appleTeams, log, token],
518
+ [apiUrl, appleTeams, log, signingMode, token],
490
519
  );
491
520
 
492
521
  const registerConnectedAppleDevice = useCallback(async () => {
@@ -543,9 +572,6 @@ export function useDeviceInstall({
543
572
  if (selectedAppleDeviceIDs.length === 0) {
544
573
  throw new Error('Select at least one Apple Developer device before preparing signing assets.');
545
574
  }
546
- if (!reusableAppleCertificate && !signingFiles.certificatePassword) {
547
- throw new Error('Enter a .p12 password before preparing signing assets.');
548
- }
549
575
  const selectedPortalDevice = appleDevices.find(
550
576
  (device) => !!device.deviceNumber && selectedAppleDeviceIDs.includes(device.deviceId ?? ''),
551
577
  );
@@ -560,6 +586,7 @@ export function useDeviceInstall({
560
586
  bundleID,
561
587
  deviceUDID: signingDeviceUDID,
562
588
  teamID,
589
+ signingMode,
563
590
  });
564
591
  if (cached) {
565
592
  setSigningAssets(cached);
@@ -577,8 +604,9 @@ export function useDeviceInstall({
577
604
  bundleID,
578
605
  deviceUDID: signingDeviceUDID,
579
606
  deviceIDs: selectedAppleDeviceIDs,
580
- certificatePassword: signingFiles.certificatePassword,
607
+ certificatePassword: signingFiles.certificatePassword || undefined,
581
608
  reusableCertificate: reusableAppleCertificate,
609
+ signingMode,
582
610
  });
583
611
  setSigningAssets(assets);
584
612
  setAppleSigningStatus('assets-ready');
@@ -609,6 +637,7 @@ export function useDeviceInstall({
609
637
  setStepStatus,
610
638
  reusableAppleCertificate,
611
639
  signingFiles.certificatePassword,
640
+ signingMode,
612
641
  token,
613
642
  ]);
614
643
 
@@ -621,6 +650,8 @@ export function useDeviceInstall({
621
650
  setBuildLogPanelOpen(true);
622
651
  setBuildLogs([]);
623
652
  setBuildStatus('queued');
653
+ setOTAInstall(undefined);
654
+ setCurrentExecID(undefined);
624
655
  stopBuildWatcherRef.current?.();
625
656
  try {
626
657
  const assets = await resolveSigningAssetsForBuild();
@@ -635,18 +666,38 @@ export function useDeviceInstall({
635
666
  if (!result.execId) {
636
667
  throw new Error('Build request did not return an exec ID.');
637
668
  }
638
- log('Signed device build started', result.execId);
669
+ const execID = result.execId;
670
+ setCurrentExecID(execID);
671
+ log('Signed device build started', execID);
639
672
  stopBuildWatcherRef.current = watchBuildLogEvents({
640
673
  limbuildApiUrl: apiUrl,
641
- execId: result.execId,
674
+ execId: execID,
642
675
  token,
643
676
  onLine: (line) => setBuildLogs((current) => [...current, line]),
644
677
  onStatus: (status) => {
645
678
  setBuildStatus(status);
646
679
  if (status === 'succeeded') {
647
680
  setStepStatus('build', 'complete');
648
- setStepStatus('connect', 'active');
649
- setCurrentStep('connect');
681
+ if (signingMode === 'adhoc') {
682
+ setStepStatus('connect', 'complete');
683
+ setStepStatus('install', 'active');
684
+ setCurrentStep('install');
685
+ void getIOSOTAInstall({ limbuildApiUrl: apiUrl, execId: execID, token })
686
+ .then((metadata) => {
687
+ setOTAInstall(metadata);
688
+ setStepStatus('install', 'complete');
689
+ log('Ad Hoc install QR is ready');
690
+ })
691
+ .catch((caught) => {
692
+ const message = errorMessage(caught);
693
+ setError(message);
694
+ setStepStatus('install', 'error');
695
+ log('Ad Hoc install metadata failed', message);
696
+ });
697
+ } else {
698
+ setStepStatus('connect', 'active');
699
+ setCurrentStep('connect');
700
+ }
650
701
  } else if (status === 'failed' || status === 'cancelled') {
651
702
  setStepStatus('build', 'error');
652
703
  }
@@ -666,7 +717,7 @@ export function useDeviceInstall({
666
717
  } finally {
667
718
  setBusyAction(undefined);
668
719
  }
669
- }, [apiUrl, log, resolveSigningAssetsForBuild, setStepStatus, token]);
720
+ }, [apiUrl, log, resolveSigningAssetsForBuild, setStepStatus, signingMode, token]);
670
721
 
671
722
  const requestUSBAccess = useCallback(async () => {
672
723
  setBusyAction('usb');
@@ -679,7 +730,7 @@ export function useDeviceInstall({
679
730
  target = await requestDeviceUSBAccess({ log });
680
731
  setPairConfirmationRequired(false);
681
732
  const storedPairRecord = await getPairRecord(target.hello.serialNumber);
682
- const activeSigningAssets = signingAssets ?? (manualSigningFilesReady ? undefined : await getLatestSigningAssets());
733
+ const activeSigningAssets = signingAssets ?? (manualSigningFilesReady ? undefined : await getLatestSigningAssets(signingMode));
683
734
  if (activeSigningAssets) {
684
735
  if (!profileContainsDevice(activeSigningAssets.profile, target.hello.serialNumber)) {
685
736
  throw new Error('Stored provisioning profile does not include the selected iPhone.');
@@ -717,7 +768,7 @@ export function useDeviceInstall({
717
768
  } finally {
718
769
  setBusyAction(undefined);
719
770
  }
720
- }, [apiUrl, cleanupDeviceAccess, log, manualSigningFilesReady, selectedDeveloperTeamID, setStepStatus, signingAssets, token]);
771
+ }, [apiUrl, cleanupDeviceAccess, log, manualSigningFilesReady, selectedDeveloperTeamID, setStepStatus, signingAssets, signingMode, token]);
721
772
 
722
773
  const pairBrowser = useCallback(async () => {
723
774
  if (!apiUrl || !selectedDevice) return;
@@ -800,6 +851,7 @@ export function useDeviceInstall({
800
851
  logs,
801
852
  buildLogs,
802
853
  buildStatus,
854
+ currentExecID,
803
855
  appleSigningStatus,
804
856
  appleTeams,
805
857
  appleDevices,
@@ -807,10 +859,12 @@ export function useDeviceInstall({
807
859
  applePortalSummary,
808
860
  selectedAppleTeamID,
809
861
  selectedAppleDeviceIDs,
862
+ signingMode,
810
863
  connectedAppleDeviceRegistered,
811
864
  connectedDeviceInProfile,
812
865
  hasReusableAppleCertificate: !!reusableAppleCertificate,
813
866
  appleBundleID,
867
+ otaInstall,
814
868
  buildLogPanelOpen,
815
869
  busyAction,
816
870
  error,
@@ -824,14 +878,14 @@ export function useDeviceInstall({
824
878
  !!appleIDLoginRef.current &&
825
879
  !!appleBundleID.trim() &&
826
880
  !!selectedDeveloperTeamID() &&
827
- selectedAppleDeviceIDs.length > 0 &&
828
- (!!reusableAppleCertificate || !!signingFiles.certificatePassword),
829
- canRequestUSBAccess: !busyAction && buildStatus === 'succeeded',
830
- canPairBrowser: !!apiUrl && !busyAction && buildStatus === 'succeeded' && !!selectedDevice,
831
- canInstall: !!apiUrl && !busyAction && buildStatus === 'succeeded' && !!selectedDevice && !!pairRecord,
881
+ selectedAppleDeviceIDs.length > 0,
882
+ canRequestUSBAccess: signingMode === 'development' && !busyAction && buildStatus === 'succeeded',
883
+ canPairBrowser: signingMode === 'development' && !!apiUrl && !busyAction && buildStatus === 'succeeded' && !!selectedDevice,
884
+ canInstall: signingMode === 'development' && !!apiUrl && !busyAction && buildStatus === 'succeeded' && !!selectedDevice && !!pairRecord,
832
885
  setSigningFiles,
833
886
  setAppleBundleID,
834
887
  setSelectedAppleDeviceIDs,
888
+ setSigningMode,
835
889
  setBuildLogPanelOpen,
836
890
  startAppleIDLogin,
837
891
  submitAppleTwoFactorCode,
@@ -851,9 +905,12 @@ async function fetchStoredBuildInfo(apiUrl: string, token?: string) {
851
905
  return fetchLimbuildInfo(apiUrl, token);
852
906
  }
853
907
 
854
- async function findReusableAppleCertificate(teamID: string): Promise<ReusableAppleCertificate | undefined> {
855
- const stored = await getLatestSigningAssetsWithCertificate(teamID);
856
- if (!stored?.certificateID || !stored.certificateP12Base64 || !stored.certificatePassword) {
908
+ async function findReusableAppleCertificate(
909
+ teamID: string,
910
+ signingMode: DeviceInstallSigningMode,
911
+ ): Promise<ReusableAppleCertificate | undefined> {
912
+ const stored = await getLatestSigningAssetsWithCertificate(teamID, signingMode);
913
+ if (!stored?.certificateID || !stored.certificateP12Base64) {
857
914
  return undefined;
858
915
  }
859
916
  return {
@@ -937,6 +994,7 @@ async function refreshApplePortalSummary(
937
994
  appleSessionId: string,
938
995
  token: string | undefined,
939
996
  teamID: string | undefined,
997
+ signingMode: DeviceInstallSigningMode,
940
998
  setApplePortalSummary: (summary: ApplePortalSummary | undefined) => void,
941
999
  log: (message: string, detail?: string) => void,
942
1000
  ) {
@@ -944,13 +1002,15 @@ async function refreshApplePortalSummary(
944
1002
  proxyProvisioningRequest<AppleDeveloperPortalResponse>(
945
1003
  apiUrl,
946
1004
  appleSessionId,
947
- findDevelopmentCertificatesRequest(teamID ?? ''),
1005
+ signingMode === 'adhoc' ? findDistributionCertificatesRequest(teamID ?? '') : findDevelopmentCertificatesRequest(teamID ?? ''),
948
1006
  token,
949
1007
  ),
950
1008
  proxyProvisioningRequest<AppleDeveloperPortalResponse>(
951
1009
  apiUrl,
952
1010
  appleSessionId,
953
- findDevelopmentProfilesRequest({ bundleID: '', teamID: teamID ?? '' }),
1011
+ signingMode === 'adhoc'
1012
+ ? findAdHocProfilesRequest({ bundleID: '', teamID: teamID ?? '' })
1013
+ : findDevelopmentProfilesRequest({ bundleID: '', teamID: teamID ?? '' }),
954
1014
  token,
955
1015
  ),
956
1016
  ]);
@@ -1021,6 +1081,7 @@ async function prepareAppleSigningAssetsForDevice({
1021
1081
  certificatePassword,
1022
1082
  deviceIDs,
1023
1083
  reusableCertificate,
1084
+ signingMode,
1024
1085
  }: {
1025
1086
  apiUrl: string;
1026
1087
  token?: string;
@@ -1031,6 +1092,7 @@ async function prepareAppleSigningAssetsForDevice({
1031
1092
  deviceIDs: string[];
1032
1093
  certificatePassword?: string;
1033
1094
  reusableCertificate?: ReusableAppleCertificate;
1095
+ signingMode: DeviceInstallSigningMode;
1034
1096
  }) {
1035
1097
  const normalizedUDID = deviceUDID?.replace(/-/g, '').replace(/[^a-fA-F0-9]/g, '') ?? '';
1036
1098
  const appIDID = await findOrCreateAppleBundleID({
@@ -1043,21 +1105,20 @@ async function prepareAppleSigningAssetsForDevice({
1043
1105
 
1044
1106
  let certificateID = reusableCertificate?.certificateID;
1045
1107
  let certificateP12Base64 = reusableCertificate?.certificateP12Base64;
1046
- let storedCertificatePassword = reusableCertificate?.certificatePassword;
1047
- if (!certificateID || !certificateP12Base64 || !storedCertificatePassword) {
1048
- if (!certificatePassword) {
1049
- throw new Error('Enter a .p12 password before preparing signing assets.');
1050
- }
1108
+ let storedCertificatePassword = reusableCertificate?.certificatePassword || undefined;
1109
+ if (!certificateID || !certificateP12Base64) {
1051
1110
  const keyMaterial = await generateAppleSigningKeyAndCSR({
1052
1111
  commonName: `Limrun ${bundleID}`,
1053
1112
  });
1054
1113
  const certificateResponse = await proxyProvisioningRequest<AppleDeveloperPortalResponse>(
1055
1114
  apiUrl,
1056
1115
  appleSessionId,
1057
- submitDevelopmentCSRRequest({ csrPEM: keyMaterial.csrPEM, teamID }),
1116
+ signingMode === 'adhoc'
1117
+ ? submitDistributionCSRRequest({ csrPEM: keyMaterial.csrPEM, teamID })
1118
+ : submitDevelopmentCSRRequest({ csrPEM: keyMaterial.csrPEM, teamID }),
1058
1119
  token,
1059
1120
  );
1060
- assertApplePortalResponseOK(certificateResponse.body, 'Apple Development certificate creation');
1121
+ assertApplePortalResponseOK(certificateResponse.body, signingMode === 'adhoc' ? 'Apple Distribution certificate creation' : 'Apple Development certificate creation');
1061
1122
  certificateID =
1062
1123
  stringField(certificateResponse.body?.certRequest, 'certificateId') ??
1063
1124
  stringField(certificateResponse.body?.certRequest, 'certRequestId') ??
@@ -1070,7 +1131,9 @@ async function prepareAppleSigningAssetsForDevice({
1070
1131
  const downloadedCertificate = await proxyProvisioningRequest(
1071
1132
  apiUrl,
1072
1133
  appleSessionId,
1073
- downloadCertificateRequest(certificateID, teamID),
1134
+ signingMode === 'adhoc'
1135
+ ? downloadDistributionCertificateRequest(certificateID, teamID)
1136
+ : downloadCertificateRequest(certificateID, teamID),
1074
1137
  token,
1075
1138
  );
1076
1139
  if (downloadedCertificate.status < 200 || downloadedCertificate.status >= 300 || !downloadedCertificate.rawBodyBase64) {
@@ -1079,24 +1142,33 @@ async function prepareAppleSigningAssetsForDevice({
1079
1142
  certificateP12Base64 = exportAppleCertificateP12({
1080
1143
  privateKeyPKCS8Base64: keyMaterial.privateKeyPKCS8Base64,
1081
1144
  certificateBase64: downloadedCertificate.rawBodyBase64,
1082
- password: certificatePassword,
1083
- friendlyName: `Apple Development ${bundleID}`,
1145
+ password: certificatePassword ?? '',
1146
+ friendlyName: signingMode === 'adhoc' ? `Apple Distribution ${bundleID}` : `Apple Development ${bundleID}`,
1084
1147
  });
1085
- storedCertificatePassword = certificatePassword;
1148
+ storedCertificatePassword = certificatePassword || undefined;
1086
1149
  }
1087
1150
 
1088
- const profileName = `Limrun ${bundleID}`;
1151
+ const profileName = signingMode === 'adhoc' ? `Limrun Ad Hoc ${bundleID}` : `Limrun ${bundleID}`;
1089
1152
  const profileResponse = await proxyProvisioningRequest<AppleDeveloperPortalResponse>(
1090
1153
  apiUrl,
1091
1154
  appleSessionId,
1092
- createDevelopmentProfileRequest({
1093
- bundleID,
1094
- teamID,
1095
- appIDID,
1096
- certificateID,
1097
- deviceIDs,
1098
- name: profileName,
1099
- }),
1155
+ signingMode === 'adhoc'
1156
+ ? createAdHocProfileRequest({
1157
+ bundleID,
1158
+ teamID,
1159
+ appIDID,
1160
+ certificateID,
1161
+ deviceIDs,
1162
+ name: profileName,
1163
+ })
1164
+ : createDevelopmentProfileRequest({
1165
+ bundleID,
1166
+ teamID,
1167
+ appIDID,
1168
+ certificateID,
1169
+ deviceIDs,
1170
+ name: profileName,
1171
+ }),
1100
1172
  token,
1101
1173
  );
1102
1174
  assertApplePortalResponseOK(profileResponse.body, 'Apple provisioning profile creation');
@@ -1118,11 +1190,15 @@ async function prepareAppleSigningAssetsForDevice({
1118
1190
  }
1119
1191
  const provisioningProfileBase64 = downloadedProfile.rawBodyBase64;
1120
1192
  const profile = parseProvisioningProfileBase64(provisioningProfileBase64);
1193
+ if (signingMode === 'adhoc' && profile.getTaskAllow) {
1194
+ throw new Error('Apple returned a development provisioning profile for Ad Hoc signing.');
1195
+ }
1121
1196
 
1122
1197
  return putAppleGeneratedSigningAssets({
1123
1198
  bundleID,
1124
1199
  deviceUDID: normalizedUDID || undefined,
1125
1200
  teamID,
1201
+ signingMode,
1126
1202
  certificateID,
1127
1203
  certificateP12Base64,
1128
1204
  certificatePassword: storedCertificatePassword,
@@ -1,2 +0,0 @@
1
- "use strict";require('./device-install-dialog.css');const e=require("react/jsx-runtime"),d=require("react"),w=require("./use-device-install-Y1u6vIBB.js");function S(l){var t,a,n="";if(typeof l=="string"||typeof l=="number")n+=l;else if(typeof l=="object")if(Array.isArray(l)){var c=l.length;for(t=0;t<c;t++)l[t]&&(a=S(l[t]))&&(n&&(n+=" "),n+=a)}else for(a in l)l[a]&&(n&&(n+=" "),n+=a);return n}function u(){for(var l,t,a=0,n="",c=arguments.length;a<c;a++)(l=arguments[a])&&(t=S(l))&&(n&&(n+=" "),n+=t);return n}const P=[{id:"signing",title:"Prepare signing",description:"Choose Apple ID login or upload certificates for a registered developer device."},{id:"build",title:"Build for device",description:"Start the signed iPhone build before connecting over USB."},{id:"connect",title:"Connect and pair",description:"After the build succeeds, connect the iPhone with WebUSB and pair this browser."},{id:"install",title:"Start installation",description:"Relay the last successful device build to the paired iPhone."}];function I({disabled:l,...t}){const[a,n]=d.useState(!1),[c,h]=d.useState("signing"),[o,b]=d.useState(),[g,y]=d.useState(""),[m,A]=d.useState(""),[x,C]=d.useState(""),j=d.useId(),i=w.useDeviceInstall(t);d.useEffect(()=>{h(i.currentStep)},[i.currentStep]);const f=(r,p)=>{i.setSigningFiles({[r]:p.currentTarget.files?.[0]})};return e.jsxs("div",{className:"lr-device-install",children:[e.jsx("button",{type:"button",className:"lr-device-install__trigger",disabled:l||!t.apiUrl,onClick:()=>n(!0),children:"Install to iPhone"}),a&&e.jsx("div",{className:"lr-device-install__backdrop",role:"presentation",children:e.jsxs("section",{"aria-labelledby":j,"aria-modal":"true",className:"lr-device-install__dialog",role:"dialog",children:[e.jsxs("header",{className:"lr-device-install__header",children:[e.jsxs("div",{children:[e.jsx("h2",{id:j,children:"Install to a real iPhone"}),e.jsx("p",{children:"Prepare signing, build for the registered device, connect and pair, then install from this browser."})]}),e.jsx("button",{type:"button",className:"lr-device-install__icon-button",onClick:()=>n(!1),children:"Close"})]}),i.error&&e.jsx("div",{className:"lr-device-install__error",children:i.error}),e.jsx("div",{className:"lr-device-install__steps",children:P.map((r,p)=>e.jsxs(T,{index:p+1,step:r,active:i.currentStep===r.id,open:c===r.id,status:i.stepStatuses[r.id],onToggle:()=>h(r.id),children:[r.id==="signing"&&e.jsxs("div",{className:"lr-device-install__step-body",children:[e.jsxs("div",{className:"lr-device-install__choice-grid",children:[e.jsxs("button",{type:"button",className:u("lr-device-install__choice",o==="apple-id"&&"lr-device-install__choice--active"),onClick:()=>b("apple-id"),children:[e.jsx("strong",{children:"Apple ID login"}),e.jsx("span",{children:"Sign in, choose team, bundle ID, and registered devices, then generate signing assets."})]}),e.jsxs("button",{type:"button",className:u("lr-device-install__choice",o==="upload"&&"lr-device-install__choice--active"),onClick:()=>b("upload"),children:[e.jsx("strong",{children:"Upload certificates"}),e.jsx("span",{children:"Use an existing .p12 certificate and provisioning profile."})]})]}),o==="apple-id"&&e.jsxs("div",{className:"lr-device-install__section-panel",children:[e.jsxs("div",{className:"lr-device-install__grid",children:[e.jsxs("label",{className:"lr-device-install__field",children:[e.jsx("span",{children:"Apple ID"}),e.jsx("input",{type:"email",autoComplete:"username",placeholder:"name@example.com",value:g,onChange:s=>y(s.currentTarget.value)})]}),e.jsxs("label",{className:"lr-device-install__field",children:[e.jsx("span",{children:"Apple ID password"}),e.jsx("input",{type:"password",autoComplete:"current-password",placeholder:"Password stays in this browser",value:m,onChange:s=>A(s.currentTarget.value)})]}),!i.hasReusableAppleCertificate&&e.jsxs("label",{className:"lr-device-install__field",children:[e.jsx("span",{children:"Generated .p12 password"}),e.jsx("input",{type:"password",placeholder:"Used when exporting Apple certificate",onChange:s=>i.setSigningFiles({certificatePassword:s.currentTarget.value})})]})]}),e.jsxs("div",{className:"lr-device-install__actions",children:[e.jsx("button",{type:"button",className:"lr-device-install__secondary",disabled:l||!t.apiUrl||!g||!m||i.busyAction==="signing",onClick:()=>void i.startAppleIDLogin({accountName:g,password:m}),children:i.appleSigningStatus==="authenticating"?"Signing in...":"Sign in with Apple ID"}),e.jsxs("span",{className:"lr-device-install__hint",children:["Apple password is used only by browser-side SRP. Status: ",i.appleSigningStatus]})]}),i.appleSigningStatus==="two-factor-required"&&e.jsxs("div",{className:"lr-device-install__grid",children:[e.jsxs("label",{className:"lr-device-install__field",children:[e.jsx("span",{children:"Two-factor code"}),e.jsx("input",{type:"text",inputMode:"numeric",autoComplete:"one-time-code",value:x,onChange:s=>C(s.currentTarget.value)})]}),e.jsx("button",{type:"button",className:"lr-device-install__secondary",disabled:!x||i.busyAction==="signing",onClick:()=>void i.submitAppleTwoFactorCode(x),children:"Submit Apple ID code"})]}),i.appleTeams.length>0&&e.jsxs("label",{className:"lr-device-install__field",children:[e.jsx("span",{children:"Apple Developer team"}),e.jsx("select",{value:i.selectedAppleTeamID??"",onChange:s=>i.setSelectedAppleTeamID(s.currentTarget.value||void 0),children:i.appleTeams.map((s,_)=>{const v=s.teamId??(s.providerId===void 0?void 0:String(s.providerId))??s.publicProviderId??"";return e.jsxs("option",{value:v,children:[s.name??"Apple Developer Team"," ",v?`(${v})`:""]},`${v}-${_}`)})})]}),i.appleDevices.length>0&&e.jsxs("label",{className:"lr-device-install__field",children:[e.jsx("span",{children:"Apple Developer devices"}),e.jsx("select",{multiple:!0,value:i.selectedAppleDeviceIDs,onChange:s=>i.setSelectedAppleDeviceIDs(Array.from(s.currentTarget.selectedOptions).map(_=>_.value)),children:i.appleDevices.map(s=>e.jsxs("option",{value:s.deviceId??"",children:[s.name??s.model??"Apple device"," ",s.deviceNumber??""]},s.deviceId??s.deviceNumber))})]}),i.applePortalSummary&&e.jsxs("p",{className:"lr-device-install__hint",children:["Found ",i.applePortalSummary.certificateCount," certificates and"," ",i.applePortalSummary.profileCount," provisioning profiles."]}),i.hasReusableAppleCertificate&&e.jsx("p",{className:"lr-device-install__hint",children:"Reusing the certificate and private key stored in this browser."}),e.jsx("button",{type:"button",className:"lr-device-install__primary",disabled:l||!i.canPrepareAppleSigningAssets,onClick:()=>void i.prepareAppleSigningAssets(),children:i.appleSigningStatus==="preparing-assets"?"Preparing signing assets...":"Generate certificate and profile"})]}),o==="upload"&&e.jsxs("div",{className:"lr-device-install__section-panel",children:[e.jsxs("div",{className:"lr-device-install__grid",children:[e.jsxs("label",{className:"lr-device-install__field",children:[e.jsx("span",{children:"Certificate (.p12)"}),e.jsx("input",{type:"file",accept:".p12,application/x-pkcs12",onChange:s=>f("certificateFile",s)})]}),e.jsxs("label",{className:"lr-device-install__field",children:[e.jsx("span",{children:"Provisioning profile"}),e.jsx("input",{type:"file",accept:".mobileprovision",onChange:s=>f("provisioningProfileFile",s)})]}),e.jsxs("label",{className:"lr-device-install__field",children:[e.jsx("span",{children:"Uploaded .p12 password"}),e.jsx("input",{type:"password",placeholder:"Export password",onChange:s=>i.setSigningFiles({certificatePassword:s.currentTarget.value})})]})]}),e.jsx("p",{className:"lr-device-install__hint",children:"The provisioning profile will be checked against the connected iPhone before installation."})]}),i.hasSigningAssets&&e.jsx("p",{children:"Signing assets are stored in this browser for the selected bundle and device."})]}),r.id==="connect"&&e.jsxs("div",{className:"lr-device-install__step-body",children:[e.jsx("p",{children:"WebUSB works in Chromium browsers on secure origins. Once the build succeeds, connect the iPhone over USB, approve the browser permission prompt, then pair this browser."}),e.jsxs("div",{className:"lr-device-install__actions",children:[e.jsx("button",{type:"button",className:"lr-device-install__primary",disabled:l||!i.canRequestUSBAccess,onClick:()=>void i.requestUSBAccess(),children:i.busyAction==="usb"?"Selecting iPhone...":"Allow USB access"}),e.jsx("button",{type:"button",className:"lr-device-install__secondary",disabled:l||!i.canPairBrowser,onClick:()=>void i.pairBrowser(),children:i.busyAction==="pair"?"Pairing...":i.pairConfirmationRequired?"Confirm pair record":"Pair browser"})]}),i.device&&e.jsx("div",{className:"lr-device-install__device",children:`${i.device.productName??"iPhone"} ${i.device.serialNumber??""}`.trim()}),i.pairConfirmationRequired&&e.jsxs("p",{children:["Unlock the iPhone and tap ",e.jsx("strong",{children:"Trust"})," in the system dialog, then confirm the pair record."]}),e.jsx("p",{children:i.hasPairRecord?"Pair record is stored locally. Continue to installation.":"Pair this browser once before installing."})]}),r.id==="build"&&e.jsxs("div",{className:"lr-device-install__step-body",children:[e.jsxs("div",{className:"lr-device-install__checklist",children:[e.jsx(N,{label:"Signing assets",ready:i.hasSigningInputs}),e.jsx(N,{label:"Device build",ready:i.buildStatus==="succeeded"?!0:void 0,pendingText:"Not started"})]}),e.jsx("button",{type:"button",className:"lr-device-install__primary",disabled:l||!i.canBuild,onClick:()=>void i.startDeviceBuild(),children:i.busyAction==="build"?"Starting build...":"Start device build"}),e.jsxs("details",{className:"lr-device-install__build-logs",open:i.buildLogPanelOpen,onToggle:s=>i.setBuildLogPanelOpen(s.currentTarget.open),children:[e.jsxs("summary",{children:["Build logs (",i.buildStatus,")"]}),e.jsx("pre",{children:i.buildLogs.length>0?i.buildLogs.filter(s=>s.type!=="meta").map(s=>s.data).join(`
2
- `):"Build logs will appear here while the device build is running."})]})]}),r.id==="install"&&e.jsxs("div",{className:"lr-device-install__step-body",children:[e.jsx("button",{type:"button",className:"lr-device-install__primary",disabled:l||!i.canInstall,onClick:()=>void i.startInstallation(),children:i.busyAction==="install"?"Installing...":"Install last build"}),e.jsx("button",{type:"button",className:"lr-device-install__secondary",onClick:i.stopRelay,children:"Stop relay"})]})]},r.id))}),e.jsxs("footer",{className:"lr-device-install__logs",children:[e.jsx("h3",{children:"Progress"}),e.jsx("ol",{children:i.logs.map((r,p)=>e.jsx("li",{children:r},`${p}-${r.slice(0,24)}`))})]})]})})]})}function T({index:l,step:t,active:a,open:n,status:c,onToggle:h,children:o}){return e.jsxs("article",{className:u("lr-device-install__step",a&&"lr-device-install__step--active"),children:[e.jsxs("button",{type:"button",className:"lr-device-install__step-header","aria-expanded":n,onClick:h,children:[e.jsx("div",{className:"lr-device-install__step-number",children:l}),e.jsxs("div",{children:[e.jsx("h3",{children:t.title}),e.jsx("p",{children:t.description})]}),e.jsx("span",{className:u("lr-device-install__status",`lr-device-install__status--${c}`),children:c==="complete"?"✓ Completed":c})]}),n&&o]})}function N({label:l,ready:t,pendingText:a="Not ready"}){const n=t===void 0?a:t?"Ready":"Needs attention";return e.jsxs("div",{className:"lr-device-install__check-row",children:[e.jsx("span",{children:l}),e.jsx("strong",{children:n})]})}exports.DeviceInstallDialog=I;exports.clsx=u;