@limrun/ui 0.9.0-rc.2 → 0.9.0-rc.4
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.
- package/dist/core/device-install/storage/browser-storage.d.ts +1 -0
- package/dist/core/device-install/types.d.ts +2 -2
- package/dist/device-install/index.cjs +1 -1
- package/dist/device-install/index.js +45 -44
- package/dist/device-install/react.cjs +1 -1
- package/dist/device-install/react.js +1 -1
- package/dist/device-install-dialog-86RDdoK9.js +2 -0
- package/dist/device-install-dialog-CnyDWf0q.mjs +462 -0
- package/dist/device-install-dialog.css +1 -1
- package/dist/hooks/use-device-install.d.ts +3 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +3 -3
- package/dist/use-device-install-CbGVvwPp.js +31 -0
- package/dist/{use-device-install-C1uVac59.mjs → use-device-install-j1Gekpl4.mjs} +2866 -2784
- package/package.json +1 -1
- package/src/components/device-install/device-install-dialog.css +82 -1
- package/src/components/device-install/device-install-dialog.tsx +329 -248
- package/src/core/device-install/storage/browser-storage.ts +12 -0
- package/src/core/device-install/types.ts +2 -2
- package/src/hooks/use-device-install.ts +227 -84
- package/dist/device-install-dialog-CSwQgbBm.js +0 -2
- package/dist/device-install-dialog-nThj775b.mjs +0 -395
- package/dist/use-device-install-Ca4jcVKU.js +0 -31
|
@@ -73,6 +73,18 @@ export async function getLatestSigningAssets() {
|
|
|
73
73
|
)[0];
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
export async function getLatestSigningAssetsWithCertificate(teamID?: string) {
|
|
77
|
+
const all = await getAllSigningAssets();
|
|
78
|
+
return all
|
|
79
|
+
.filter((asset) => {
|
|
80
|
+
if (!asset.certificateID || !asset.certificateP12Base64 || !asset.certificatePassword) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return !teamID || !asset.teamID || asset.teamID === teamID;
|
|
84
|
+
})
|
|
85
|
+
.sort((left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime())[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
export async function putSigningAssets(input: PutSigningAssetsInput) {
|
|
77
89
|
const normalizedBundleID = normalizeBundleID(input.bundleID);
|
|
78
90
|
if (!normalizedBundleID) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export type DeviceInstallLog = (message: string, detail?: string) => void;
|
|
2
2
|
|
|
3
|
-
export type DeviceInstallStep = '
|
|
3
|
+
export type DeviceInstallStep = 'signing' | 'connect' | 'build' | 'install';
|
|
4
4
|
|
|
5
5
|
export type DeviceInstallStepStatus = 'idle' | 'active' | 'complete' | 'error';
|
|
6
6
|
|
|
7
|
-
export type DeviceInstallBusyAction = '
|
|
7
|
+
export type DeviceInstallBusyAction = 'signing' | 'usb' | 'pair' | 'build' | 'install';
|
|
8
8
|
|
|
9
9
|
export type DeviceInstallBuildStatus =
|
|
10
10
|
| 'idle'
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
generateAppleSigningKeyAndCSR,
|
|
15
15
|
getPairRecord,
|
|
16
16
|
getLatestSigningAssets,
|
|
17
|
+
getLatestSigningAssetsWithCertificate,
|
|
17
18
|
getReusableAppleSigningAssets,
|
|
18
19
|
listTeamsRequest,
|
|
19
20
|
parseProvisioningProfile,
|
|
@@ -49,6 +50,13 @@ import type { RelayClient } from '../core/device-install/operations';
|
|
|
49
50
|
|
|
50
51
|
type DeviceInstallStepStatuses = Record<DeviceInstallStep, DeviceInstallStepStatus>;
|
|
51
52
|
|
|
53
|
+
type ReusableAppleCertificate = Pick<
|
|
54
|
+
StoredSigningAssets,
|
|
55
|
+
'certificateID' | 'certificateP12Base64' | 'certificatePassword' | 'teamID'
|
|
56
|
+
> & {
|
|
57
|
+
certificateID: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
52
60
|
export type UseDeviceInstallOptions = {
|
|
53
61
|
apiUrl?: string;
|
|
54
62
|
token?: string;
|
|
@@ -60,6 +68,7 @@ export type UseDeviceInstallResult = {
|
|
|
60
68
|
device?: DeviceInstallDevice;
|
|
61
69
|
hasPairRecord: boolean;
|
|
62
70
|
hasSigningAssets: boolean;
|
|
71
|
+
hasSigningInputs: boolean;
|
|
63
72
|
pairConfirmationRequired: boolean;
|
|
64
73
|
logs: string[];
|
|
65
74
|
buildLogs: BuildLogLine[];
|
|
@@ -72,6 +81,8 @@ export type UseDeviceInstallResult = {
|
|
|
72
81
|
selectedAppleTeamID?: string;
|
|
73
82
|
selectedAppleDeviceIDs: string[];
|
|
74
83
|
connectedAppleDeviceRegistered: boolean;
|
|
84
|
+
connectedDeviceInProfile?: boolean;
|
|
85
|
+
hasReusableAppleCertificate: boolean;
|
|
75
86
|
appleBundleID: string;
|
|
76
87
|
buildLogPanelOpen: boolean;
|
|
77
88
|
busyAction?: DeviceInstallBusyAction;
|
|
@@ -131,9 +142,9 @@ export type ApplePortalSummary = {
|
|
|
131
142
|
};
|
|
132
143
|
|
|
133
144
|
const initialStepStatuses: DeviceInstallStepStatuses = {
|
|
145
|
+
signing: 'idle',
|
|
146
|
+
connect: 'idle',
|
|
134
147
|
build: 'idle',
|
|
135
|
-
usb: 'idle',
|
|
136
|
-
pair: 'idle',
|
|
137
148
|
install: 'idle',
|
|
138
149
|
};
|
|
139
150
|
|
|
@@ -141,13 +152,13 @@ export function useDeviceInstall({
|
|
|
141
152
|
apiUrl,
|
|
142
153
|
token,
|
|
143
154
|
}: UseDeviceInstallOptions): UseDeviceInstallResult {
|
|
144
|
-
const [currentStep, setCurrentStep] = useState<DeviceInstallStep>('
|
|
155
|
+
const [currentStep, setCurrentStep] = useState<DeviceInstallStep>('signing');
|
|
145
156
|
const [stepStatuses, setStepStatuses] = useState<DeviceInstallStepStatuses>(initialStepStatuses);
|
|
146
157
|
const [selectedDevice, setSelectedDevice] = useState<DeviceRelayTarget | undefined>();
|
|
147
158
|
const [pairRecord, setPairRecord] = useState<StoredPairRecord | undefined>();
|
|
148
159
|
const [signingAssets, setSigningAssets] = useState<StoredSigningAssets | undefined>();
|
|
149
160
|
const [logs, setLogs] = useState<string[]>([
|
|
150
|
-
'Ready.
|
|
161
|
+
'Ready. Prepare signing assets, connect and pair the iPhone, build, then install.',
|
|
151
162
|
]);
|
|
152
163
|
const [buildLogs, setBuildLogs] = useState<BuildLogLine[]>([]);
|
|
153
164
|
const [buildStatus, setBuildStatus] = useState<DeviceInstallBuildStatus>('idle');
|
|
@@ -159,6 +170,7 @@ export function useDeviceInstall({
|
|
|
159
170
|
const [applePortalSummary, setApplePortalSummary] = useState<ApplePortalSummary | undefined>();
|
|
160
171
|
const [selectedAppleTeamID, setSelectedAppleTeamID] = useState<string | undefined>();
|
|
161
172
|
const [appleBundleID, setAppleBundleID] = useState('');
|
|
173
|
+
const [reusableAppleCertificate, setReusableAppleCertificate] = useState<ReusableAppleCertificate | undefined>();
|
|
162
174
|
const [buildLogPanelOpen, setBuildLogPanelOpen] = useState(false);
|
|
163
175
|
const [busyAction, setBusyAction] = useState<DeviceInstallBusyAction | undefined>();
|
|
164
176
|
const [error, setError] = useState<string | undefined>();
|
|
@@ -179,18 +191,67 @@ export function useDeviceInstall({
|
|
|
179
191
|
}, []);
|
|
180
192
|
|
|
181
193
|
const setSigningFiles = useCallback((files: DeviceInstallSigningFiles) => {
|
|
182
|
-
setSigningFilesState((current) =>
|
|
194
|
+
setSigningFilesState((current) => {
|
|
195
|
+
const next = { ...current, ...files };
|
|
196
|
+
const ready = !!next.certificateFile && !!next.provisioningProfileFile && !!next.certificatePassword;
|
|
197
|
+
setStepStatus('signing', ready ? 'complete' : 'active');
|
|
198
|
+
if (ready) {
|
|
199
|
+
setAppleSigningStatus('assets-ready');
|
|
200
|
+
setCurrentStep('connect');
|
|
201
|
+
}
|
|
202
|
+
return next;
|
|
203
|
+
});
|
|
183
204
|
setSigningAssets(undefined);
|
|
184
|
-
}, []);
|
|
205
|
+
}, [setStepStatus]);
|
|
206
|
+
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
let cancelled = false;
|
|
209
|
+
void getLatestSigningAssets().then((stored) => {
|
|
210
|
+
if (cancelled || !stored) return;
|
|
211
|
+
setSigningAssets(stored);
|
|
212
|
+
setAppleBundleID(stored.bundleID);
|
|
213
|
+
setAppleSigningStatus('using-cached-profile');
|
|
214
|
+
setStepStatus('signing', 'complete');
|
|
215
|
+
setCurrentStep('connect');
|
|
216
|
+
log('Using stored signing assets', stored.bundleID);
|
|
217
|
+
});
|
|
218
|
+
return () => {
|
|
219
|
+
cancelled = true;
|
|
220
|
+
};
|
|
221
|
+
}, [log, setStepStatus]);
|
|
185
222
|
|
|
186
223
|
const selectedDeveloperTeamID = useCallback(() => {
|
|
187
224
|
return developerPortalTeamID(appleTeams.find((team) => appleTeamSelectionID(team) === selectedAppleTeamID));
|
|
188
225
|
}, [appleTeams, selectedAppleTeamID]);
|
|
189
226
|
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
const teamID = selectedDeveloperTeamID();
|
|
229
|
+
if (!teamID) {
|
|
230
|
+
setReusableAppleCertificate(undefined);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
let cancelled = false;
|
|
234
|
+
void findReusableAppleCertificate(teamID).then((certificate) => {
|
|
235
|
+
if (!cancelled) {
|
|
236
|
+
setReusableAppleCertificate(certificate);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
return () => {
|
|
240
|
+
cancelled = true;
|
|
241
|
+
};
|
|
242
|
+
}, [selectedDeveloperTeamID]);
|
|
243
|
+
|
|
190
244
|
const connectedAppleDevice = selectedDevice?.hello.serialNumber
|
|
191
245
|
? appleDevices.find((device) => normalizeAppleUDID(device.deviceNumber) === normalizeAppleUDID(selectedDevice.hello.serialNumber))
|
|
192
246
|
: undefined;
|
|
193
247
|
const connectedAppleDeviceRegistered = !!connectedAppleDevice?.deviceId;
|
|
248
|
+
const manualSigningFilesReady =
|
|
249
|
+
!!signingFiles.certificateFile && !!signingFiles.provisioningProfileFile && !!signingFiles.certificatePassword;
|
|
250
|
+
const signingInputsReady = !!signingAssets || manualSigningFilesReady;
|
|
251
|
+
const connectedDeviceInProfile =
|
|
252
|
+
selectedDevice?.hello.serialNumber && signingAssets
|
|
253
|
+
? profileContainsDevice(signingAssets.profile, selectedDevice.hello.serialNumber)
|
|
254
|
+
: undefined;
|
|
194
255
|
|
|
195
256
|
useEffect(() => {
|
|
196
257
|
selectedDeviceRef.current = selectedDevice;
|
|
@@ -269,8 +330,10 @@ export function useDeviceInstall({
|
|
|
269
330
|
const startAppleIDLogin = useCallback(
|
|
270
331
|
async ({ accountName, password }: DeviceInstallAppleIDLoginInput) => {
|
|
271
332
|
if (!apiUrl) return;
|
|
272
|
-
setBusyAction('
|
|
333
|
+
setBusyAction('signing');
|
|
273
334
|
setError(undefined);
|
|
335
|
+
setCurrentStep('signing');
|
|
336
|
+
setStepStatus('signing', 'active');
|
|
274
337
|
setAppleSigningStatus('authenticating');
|
|
275
338
|
try {
|
|
276
339
|
await appleIDLoginRef.current?.close().catch(() => undefined);
|
|
@@ -287,6 +350,17 @@ export function useDeviceInstall({
|
|
|
287
350
|
accountSession?.body as AppleDeveloperPortalResponse | undefined,
|
|
288
351
|
);
|
|
289
352
|
await refreshAppleAppIDs(apiUrl, session.appleSessionId, token, teamID, setAppleAppIDs, setAppleBundleID);
|
|
353
|
+
if (teamID) {
|
|
354
|
+
await refreshAppleDevices({
|
|
355
|
+
apiUrl,
|
|
356
|
+
token,
|
|
357
|
+
appleSessionId: session.appleSessionId,
|
|
358
|
+
teamID,
|
|
359
|
+
setAppleDevices,
|
|
360
|
+
setSelectedAppleDeviceIDs,
|
|
361
|
+
log,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
290
364
|
await refreshApplePortalSummary(apiUrl, session.appleSessionId, token, teamID, setApplePortalSummary, log);
|
|
291
365
|
}
|
|
292
366
|
setAppleSigningStatus(session.requiresTwoFactor ? 'two-factor-required' : 'authenticated');
|
|
@@ -303,7 +377,7 @@ export function useDeviceInstall({
|
|
|
303
377
|
setBusyAction(undefined);
|
|
304
378
|
}
|
|
305
379
|
},
|
|
306
|
-
[apiUrl, log, token],
|
|
380
|
+
[apiUrl, log, setStepStatus, token],
|
|
307
381
|
);
|
|
308
382
|
|
|
309
383
|
const submitAppleTwoFactorCode = useCallback(
|
|
@@ -312,8 +386,10 @@ export function useDeviceInstall({
|
|
|
312
386
|
if (!session) {
|
|
313
387
|
throw new Error('Start Apple ID login before submitting a two-factor code.');
|
|
314
388
|
}
|
|
315
|
-
setBusyAction('
|
|
389
|
+
setBusyAction('signing');
|
|
316
390
|
setError(undefined);
|
|
391
|
+
setCurrentStep('signing');
|
|
392
|
+
setStepStatus('signing', 'active');
|
|
317
393
|
try {
|
|
318
394
|
await session.finishTwoFactor(code);
|
|
319
395
|
if (apiUrl) {
|
|
@@ -327,6 +403,17 @@ export function useDeviceInstall({
|
|
|
327
403
|
accountSession?.body as AppleDeveloperPortalResponse | undefined,
|
|
328
404
|
);
|
|
329
405
|
await refreshAppleAppIDs(apiUrl, session.appleSessionId, token, teamID, setAppleAppIDs, setAppleBundleID);
|
|
406
|
+
if (teamID) {
|
|
407
|
+
await refreshAppleDevices({
|
|
408
|
+
apiUrl,
|
|
409
|
+
token,
|
|
410
|
+
appleSessionId: session.appleSessionId,
|
|
411
|
+
teamID,
|
|
412
|
+
setAppleDevices,
|
|
413
|
+
setSelectedAppleDeviceIDs,
|
|
414
|
+
log,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
330
417
|
await refreshApplePortalSummary(apiUrl, session.appleSessionId, token, teamID, setApplePortalSummary, log);
|
|
331
418
|
}
|
|
332
419
|
setAppleSigningStatus('authenticated');
|
|
@@ -340,7 +427,7 @@ export function useDeviceInstall({
|
|
|
340
427
|
setBusyAction(undefined);
|
|
341
428
|
}
|
|
342
429
|
},
|
|
343
|
-
[apiUrl, log, token],
|
|
430
|
+
[apiUrl, log, setStepStatus, token],
|
|
344
431
|
);
|
|
345
432
|
|
|
346
433
|
const clearAppleIDLogin = useCallback(() => {
|
|
@@ -352,9 +439,11 @@ export function useDeviceInstall({
|
|
|
352
439
|
setSelectedAppleDeviceIDs([]);
|
|
353
440
|
setApplePortalSummary(undefined);
|
|
354
441
|
setSelectedAppleTeamID(undefined);
|
|
442
|
+
setReusableAppleCertificate(undefined);
|
|
355
443
|
setAppleSigningStatus('idle');
|
|
444
|
+
setStepStatus('signing', 'idle');
|
|
356
445
|
log('Apple ID login state cleared');
|
|
357
|
-
}, [log]);
|
|
446
|
+
}, [log, setStepStatus]);
|
|
358
447
|
|
|
359
448
|
const selectAppleTeam = useCallback(
|
|
360
449
|
(teamID: string | undefined) => {
|
|
@@ -373,18 +462,16 @@ export function useDeviceInstall({
|
|
|
373
462
|
try {
|
|
374
463
|
await refreshAppleAppIDs(apiUrl, session.appleSessionId, token, developerTeamID, setAppleAppIDs, setAppleBundleID);
|
|
375
464
|
await refreshApplePortalSummary(apiUrl, session.appleSessionId, token, developerTeamID, setApplePortalSummary, log);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
});
|
|
387
|
-
}
|
|
465
|
+
await refreshAppleDevices({
|
|
466
|
+
apiUrl,
|
|
467
|
+
token,
|
|
468
|
+
appleSessionId: session.appleSessionId,
|
|
469
|
+
teamID: developerTeamID,
|
|
470
|
+
connectedUDID: selectedDeviceRef.current?.hello.serialNumber,
|
|
471
|
+
setAppleDevices,
|
|
472
|
+
setSelectedAppleDeviceIDs,
|
|
473
|
+
log,
|
|
474
|
+
});
|
|
388
475
|
} catch (caught) {
|
|
389
476
|
const message = errorMessage(caught);
|
|
390
477
|
setError(message);
|
|
@@ -398,7 +485,7 @@ export function useDeviceInstall({
|
|
|
398
485
|
const registerConnectedAppleDevice = useCallback(async () => {
|
|
399
486
|
const teamID = selectedDeveloperTeamID();
|
|
400
487
|
if (!apiUrl || !appleIDLoginRef.current || !selectedDevice?.hello.serialNumber || !teamID) return;
|
|
401
|
-
setBusyAction('
|
|
488
|
+
setBusyAction('signing');
|
|
402
489
|
setError(undefined);
|
|
403
490
|
try {
|
|
404
491
|
const normalizedUDID = normalizeAppleUDID(selectedDevice.hello.serialNumber);
|
|
@@ -434,7 +521,7 @@ export function useDeviceInstall({
|
|
|
434
521
|
}, [apiUrl, log, selectedDeveloperTeamID, selectedDevice?.hello.productName, selectedDevice?.hello.serialNumber, token]);
|
|
435
522
|
|
|
436
523
|
const prepareAppleSigningAssets = useCallback(async () => {
|
|
437
|
-
if (!apiUrl || !appleIDLoginRef.current
|
|
524
|
+
if (!apiUrl || !appleIDLoginRef.current) return;
|
|
438
525
|
const bundleID = appleBundleID.trim();
|
|
439
526
|
if (!bundleID) {
|
|
440
527
|
throw new Error('Enter a bundle ID before preparing signing assets.');
|
|
@@ -449,21 +536,30 @@ export function useDeviceInstall({
|
|
|
449
536
|
if (selectedAppleDeviceIDs.length === 0) {
|
|
450
537
|
throw new Error('Select at least one Apple Developer device before preparing signing assets.');
|
|
451
538
|
}
|
|
452
|
-
if (!signingFiles.certificatePassword) {
|
|
539
|
+
if (!reusableAppleCertificate && !signingFiles.certificatePassword) {
|
|
453
540
|
throw new Error('Enter a .p12 password before preparing signing assets.');
|
|
454
541
|
}
|
|
455
|
-
|
|
542
|
+
const selectedPortalDevice = appleDevices.find((device) => selectedAppleDeviceIDs.includes(device.deviceId ?? ''));
|
|
543
|
+
const signingDeviceUDID = selectedDevice?.hello.serialNumber ?? selectedPortalDevice?.deviceNumber;
|
|
544
|
+
if (!signingDeviceUDID) {
|
|
545
|
+
throw new Error('Select an Apple Developer device before preparing signing assets.');
|
|
546
|
+
}
|
|
547
|
+
setBusyAction('signing');
|
|
456
548
|
setError(undefined);
|
|
549
|
+
setCurrentStep('signing');
|
|
550
|
+
setStepStatus('signing', 'active');
|
|
457
551
|
setAppleSigningStatus('preparing-assets');
|
|
458
552
|
try {
|
|
459
553
|
const cached = await getReusableAppleSigningAssets({
|
|
460
554
|
bundleID,
|
|
461
|
-
deviceUDID:
|
|
555
|
+
deviceUDID: signingDeviceUDID,
|
|
462
556
|
teamID,
|
|
463
557
|
});
|
|
464
558
|
if (cached) {
|
|
465
559
|
setSigningAssets(cached);
|
|
466
560
|
setAppleSigningStatus('assets-ready');
|
|
561
|
+
setStepStatus('signing', 'complete');
|
|
562
|
+
setCurrentStep('connect');
|
|
467
563
|
log('Using cached Apple signing assets', bundleID);
|
|
468
564
|
return;
|
|
469
565
|
}
|
|
@@ -473,13 +569,16 @@ export function useDeviceInstall({
|
|
|
473
569
|
appleSessionId: appleIDLoginRef.current.appleSessionId,
|
|
474
570
|
teamID,
|
|
475
571
|
bundleID,
|
|
476
|
-
deviceUDID:
|
|
572
|
+
deviceUDID: signingDeviceUDID,
|
|
477
573
|
deviceIDs: selectedAppleDeviceIDs,
|
|
478
574
|
certificatePassword: signingFiles.certificatePassword,
|
|
575
|
+
reusableCertificate: reusableAppleCertificate,
|
|
479
576
|
});
|
|
480
577
|
setSigningAssets(assets);
|
|
481
578
|
setAppleSigningStatus('assets-ready');
|
|
482
|
-
|
|
579
|
+
setStepStatus('signing', 'complete');
|
|
580
|
+
setCurrentStep('connect');
|
|
581
|
+
log('Apple signing assets stored locally', `${bundleID} for ${signingDeviceUDID}`);
|
|
483
582
|
} catch (caught) {
|
|
484
583
|
const message = errorMessage(caught);
|
|
485
584
|
setError(message);
|
|
@@ -491,13 +590,15 @@ export function useDeviceInstall({
|
|
|
491
590
|
}, [
|
|
492
591
|
apiUrl,
|
|
493
592
|
appleBundleID,
|
|
593
|
+
appleDevices,
|
|
494
594
|
appleTeams,
|
|
495
595
|
log,
|
|
496
596
|
selectedAppleTeamID,
|
|
497
597
|
selectedAppleDeviceIDs,
|
|
498
598
|
selectedDeveloperTeamID,
|
|
499
|
-
selectedDevice?.hello.productName,
|
|
500
599
|
selectedDevice?.hello.serialNumber,
|
|
600
|
+
setStepStatus,
|
|
601
|
+
reusableAppleCertificate,
|
|
501
602
|
signingFiles.certificatePassword,
|
|
502
603
|
token,
|
|
503
604
|
]);
|
|
@@ -535,7 +636,7 @@ export function useDeviceInstall({
|
|
|
535
636
|
setBuildStatus(status);
|
|
536
637
|
if (status === 'succeeded') {
|
|
537
638
|
setStepStatus('build', 'complete');
|
|
538
|
-
setCurrentStep(
|
|
639
|
+
setCurrentStep('install');
|
|
539
640
|
} else if (status === 'failed' || status === 'cancelled') {
|
|
540
641
|
setStepStatus('build', 'error');
|
|
541
642
|
}
|
|
@@ -560,8 +661,8 @@ export function useDeviceInstall({
|
|
|
560
661
|
const requestUSBAccess = useCallback(async () => {
|
|
561
662
|
setBusyAction('usb');
|
|
562
663
|
setError(undefined);
|
|
563
|
-
setCurrentStep('
|
|
564
|
-
setStepStatus('
|
|
664
|
+
setCurrentStep('connect');
|
|
665
|
+
setStepStatus('connect', 'active');
|
|
565
666
|
try {
|
|
566
667
|
await cleanupDeviceAccess();
|
|
567
668
|
const target = await requestDeviceUSBAccess({ log });
|
|
@@ -569,7 +670,7 @@ export function useDeviceInstall({
|
|
|
569
670
|
setPairConfirmationRequired(false);
|
|
570
671
|
const storedPairRecord = await getPairRecord(target.hello.serialNumber);
|
|
571
672
|
setPairRecord(storedPairRecord);
|
|
572
|
-
const storedSigningAssets = await getLatestSigningAssets();
|
|
673
|
+
const storedSigningAssets = manualSigningFilesReady ? undefined : await getLatestSigningAssets();
|
|
573
674
|
if (storedSigningAssets) {
|
|
574
675
|
if (!profileContainsDevice(storedSigningAssets.profile, target.hello.serialNumber)) {
|
|
575
676
|
throw new Error('Stored provisioning profile does not include the selected iPhone.');
|
|
@@ -591,26 +692,26 @@ export function useDeviceInstall({
|
|
|
591
692
|
});
|
|
592
693
|
}
|
|
593
694
|
}
|
|
594
|
-
setStepStatus('
|
|
595
|
-
setCurrentStep(storedPairRecord ? '
|
|
695
|
+
setStepStatus('connect', storedPairRecord ? 'complete' : 'active');
|
|
696
|
+
setCurrentStep(storedPairRecord ? 'build' : 'connect');
|
|
596
697
|
log(storedPairRecord ? 'Pair record found' : 'No pair record found', target.hello.serialNumber);
|
|
597
698
|
} catch (caught) {
|
|
598
699
|
const message = errorMessage(caught);
|
|
599
700
|
setError(message);
|
|
600
|
-
setStepStatus('
|
|
701
|
+
setStepStatus('connect', 'error');
|
|
601
702
|
log('USB access failed', message);
|
|
602
703
|
} finally {
|
|
603
704
|
setBusyAction(undefined);
|
|
604
705
|
}
|
|
605
|
-
}, [apiUrl, cleanupDeviceAccess, log, selectedDeveloperTeamID, setStepStatus, token]);
|
|
706
|
+
}, [apiUrl, cleanupDeviceAccess, log, manualSigningFilesReady, selectedDeveloperTeamID, setStepStatus, token]);
|
|
606
707
|
|
|
607
708
|
const pairBrowser = useCallback(async () => {
|
|
608
709
|
if (!apiUrl || !selectedDevice) return;
|
|
609
710
|
setBusyAction('pair');
|
|
610
711
|
setError(undefined);
|
|
611
712
|
setPairConfirmationRequired(false);
|
|
612
|
-
setCurrentStep('
|
|
613
|
-
setStepStatus('
|
|
713
|
+
setCurrentStep('connect');
|
|
714
|
+
setStepStatus('connect', 'active');
|
|
614
715
|
try {
|
|
615
716
|
await cleanupDeviceAccess();
|
|
616
717
|
const result = await startPairingRelay({
|
|
@@ -626,15 +727,15 @@ export function useDeviceInstall({
|
|
|
626
727
|
await closeDeviceRelayTarget(selectedDevice, log);
|
|
627
728
|
setPairRecord(stored);
|
|
628
729
|
setPairConfirmationRequired(false);
|
|
629
|
-
setStepStatus('
|
|
630
|
-
setCurrentStep('
|
|
730
|
+
setStepStatus('connect', 'complete');
|
|
731
|
+
setCurrentStep('build');
|
|
631
732
|
log('Device paired', 'The pair record was stored locally in this browser.');
|
|
632
733
|
} catch (caught) {
|
|
633
734
|
await closeDeviceRelayTarget(selectedDevice, log);
|
|
634
735
|
const message = errorMessage(caught);
|
|
635
736
|
setPairConfirmationRequired(true);
|
|
636
737
|
setError('Unlock the iPhone, tap Trust, then confirm the pair record.');
|
|
637
|
-
setStepStatus('
|
|
738
|
+
setStepStatus('connect', 'error');
|
|
638
739
|
log('Device pairing failed', message);
|
|
639
740
|
} finally {
|
|
640
741
|
setBusyAction(undefined);
|
|
@@ -680,6 +781,7 @@ export function useDeviceInstall({
|
|
|
680
781
|
device: selectedDevice?.hello,
|
|
681
782
|
hasPairRecord: !!pairRecord,
|
|
682
783
|
hasSigningAssets: !!signingAssets,
|
|
784
|
+
hasSigningInputs: signingInputsReady,
|
|
683
785
|
pairConfirmationRequired,
|
|
684
786
|
logs,
|
|
685
787
|
buildLogs,
|
|
@@ -692,21 +794,28 @@ export function useDeviceInstall({
|
|
|
692
794
|
selectedAppleTeamID,
|
|
693
795
|
selectedAppleDeviceIDs,
|
|
694
796
|
connectedAppleDeviceRegistered,
|
|
797
|
+
connectedDeviceInProfile,
|
|
798
|
+
hasReusableAppleCertificate: !!reusableAppleCertificate,
|
|
695
799
|
appleBundleID,
|
|
696
800
|
buildLogPanelOpen,
|
|
697
801
|
busyAction,
|
|
698
802
|
error,
|
|
699
|
-
canBuild:
|
|
803
|
+
canBuild:
|
|
804
|
+
!!apiUrl &&
|
|
805
|
+
!busyAction &&
|
|
806
|
+
!!selectedDevice &&
|
|
807
|
+
!!pairRecord &&
|
|
808
|
+
signingInputsReady &&
|
|
809
|
+
connectedDeviceInProfile !== false,
|
|
700
810
|
canPrepareAppleSigningAssets:
|
|
701
811
|
!!apiUrl &&
|
|
702
812
|
!busyAction &&
|
|
703
813
|
!!appleIDLoginRef.current &&
|
|
704
|
-
!!selectedDevice &&
|
|
705
814
|
!!appleBundleID.trim() &&
|
|
706
815
|
!!selectedDeveloperTeamID() &&
|
|
707
816
|
selectedAppleDeviceIDs.length > 0 &&
|
|
708
|
-
!!signingFiles.certificatePassword,
|
|
709
|
-
canRequestUSBAccess: !busyAction &&
|
|
817
|
+
(!!reusableAppleCertificate || !!signingFiles.certificatePassword),
|
|
818
|
+
canRequestUSBAccess: !busyAction && signingInputsReady,
|
|
710
819
|
canPairBrowser: !!apiUrl && !busyAction && !!selectedDevice,
|
|
711
820
|
canInstall: !!apiUrl && !busyAction && !!selectedDevice && !!pairRecord,
|
|
712
821
|
setSigningFiles,
|
|
@@ -731,6 +840,19 @@ async function fetchStoredBuildInfo(apiUrl: string, token?: string) {
|
|
|
731
840
|
return fetchLimbuildInfo(apiUrl, token);
|
|
732
841
|
}
|
|
733
842
|
|
|
843
|
+
async function findReusableAppleCertificate(teamID: string): Promise<ReusableAppleCertificate | undefined> {
|
|
844
|
+
const stored = await getLatestSigningAssetsWithCertificate(teamID);
|
|
845
|
+
if (!stored?.certificateID || !stored.certificateP12Base64 || !stored.certificatePassword) {
|
|
846
|
+
return undefined;
|
|
847
|
+
}
|
|
848
|
+
return {
|
|
849
|
+
certificateID: stored.certificateID,
|
|
850
|
+
certificateP12Base64: stored.certificateP12Base64,
|
|
851
|
+
certificatePassword: stored.certificatePassword,
|
|
852
|
+
teamID: stored.teamID,
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
734
856
|
async function refreshAppleTeams(
|
|
735
857
|
apiUrl: string,
|
|
736
858
|
appleSessionId: string,
|
|
@@ -791,6 +913,11 @@ async function refreshAppleAppIDs(
|
|
|
791
913
|
const firstBundleID = bundleIDFromAppleAppID(appIDs[0]);
|
|
792
914
|
if (firstBundleID) {
|
|
793
915
|
setAppleBundleID(firstBundleID);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
const info = await fetchStoredBuildInfo(apiUrl, token).catch(() => undefined);
|
|
919
|
+
if (info?.lastBuildConfig?.bundleId) {
|
|
920
|
+
setAppleBundleID(info.lastBuildConfig.bundleId);
|
|
794
921
|
}
|
|
795
922
|
}
|
|
796
923
|
|
|
@@ -857,12 +984,18 @@ async function refreshAppleDevices({
|
|
|
857
984
|
assertApplePortalResponseOK(response.body, 'Apple device list');
|
|
858
985
|
const devices = response.body?.devices ?? [];
|
|
859
986
|
setAppleDevices(devices);
|
|
987
|
+
const firstDeviceID = devices.find((device) => !!device.deviceId)?.deviceId;
|
|
988
|
+
if (!connectedUDID) {
|
|
989
|
+
setSelectedAppleDeviceIDs(firstDeviceID ? [firstDeviceID] : []);
|
|
990
|
+
log('Apple Developer devices fetched', `${devices.length} devices`);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
860
993
|
const connected = devices.find((device) => normalizeAppleUDID(device.deviceNumber) === normalizeAppleUDID(connectedUDID));
|
|
861
994
|
if (connected?.deviceId) {
|
|
862
995
|
setSelectedAppleDeviceIDs([connected.deviceId]);
|
|
863
996
|
log('Connected iPhone found in Apple Developer devices', connected.name ?? connected.deviceNumber);
|
|
864
997
|
} else {
|
|
865
|
-
setSelectedAppleDeviceIDs([]);
|
|
998
|
+
setSelectedAppleDeviceIDs(firstDeviceID ? [firstDeviceID] : []);
|
|
866
999
|
log('Connected iPhone is not registered with Apple Developer', connectedUDID);
|
|
867
1000
|
}
|
|
868
1001
|
}
|
|
@@ -876,6 +1009,7 @@ async function prepareAppleSigningAssetsForDevice({
|
|
|
876
1009
|
deviceUDID,
|
|
877
1010
|
certificatePassword,
|
|
878
1011
|
deviceIDs,
|
|
1012
|
+
reusableCertificate,
|
|
879
1013
|
}: {
|
|
880
1014
|
apiUrl: string;
|
|
881
1015
|
token?: string;
|
|
@@ -884,13 +1018,10 @@ async function prepareAppleSigningAssetsForDevice({
|
|
|
884
1018
|
bundleID: string;
|
|
885
1019
|
deviceUDID: string;
|
|
886
1020
|
deviceIDs: string[];
|
|
887
|
-
certificatePassword
|
|
1021
|
+
certificatePassword?: string;
|
|
1022
|
+
reusableCertificate?: ReusableAppleCertificate;
|
|
888
1023
|
}) {
|
|
889
1024
|
const normalizedUDID = deviceUDID.replace(/-/g, '').replace(/[^a-fA-F0-9]/g, '');
|
|
890
|
-
const keyMaterial = await generateAppleSigningKeyAndCSR({
|
|
891
|
-
commonName: `Limrun ${bundleID}`,
|
|
892
|
-
});
|
|
893
|
-
|
|
894
1025
|
const appIDID = await findOrCreateAppleBundleID({
|
|
895
1026
|
apiUrl,
|
|
896
1027
|
token,
|
|
@@ -899,37 +1030,49 @@ async function prepareAppleSigningAssetsForDevice({
|
|
|
899
1030
|
bundleID,
|
|
900
1031
|
});
|
|
901
1032
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1033
|
+
let certificateID = reusableCertificate?.certificateID;
|
|
1034
|
+
let certificateP12Base64 = reusableCertificate?.certificateP12Base64;
|
|
1035
|
+
let storedCertificatePassword = reusableCertificate?.certificatePassword;
|
|
1036
|
+
if (!certificateID || !certificateP12Base64 || !storedCertificatePassword) {
|
|
1037
|
+
if (!certificatePassword) {
|
|
1038
|
+
throw new Error('Enter a .p12 password before preparing signing assets.');
|
|
1039
|
+
}
|
|
1040
|
+
const keyMaterial = await generateAppleSigningKeyAndCSR({
|
|
1041
|
+
commonName: `Limrun ${bundleID}`,
|
|
1042
|
+
});
|
|
1043
|
+
const certificateResponse = await proxyProvisioningRequest<AppleDeveloperPortalResponse>(
|
|
1044
|
+
apiUrl,
|
|
1045
|
+
appleSessionId,
|
|
1046
|
+
submitDevelopmentCSRRequest({ csrPEM: keyMaterial.csrPEM, teamID }),
|
|
1047
|
+
token,
|
|
1048
|
+
);
|
|
1049
|
+
assertApplePortalResponseOK(certificateResponse.body, 'Apple Development certificate creation');
|
|
1050
|
+
certificateID =
|
|
1051
|
+
stringField(certificateResponse.body?.certRequest, 'certificateId') ??
|
|
1052
|
+
stringField(certificateResponse.body?.certRequest, 'certRequestId') ??
|
|
1053
|
+
stringField(certificateResponse.body, 'certificateId') ??
|
|
1054
|
+
stringField(certificateResponse.body, 'certRequestId');
|
|
1055
|
+
if (!certificateID) {
|
|
1056
|
+
throw new Error('Apple certificate creation did not return a certificate ID.');
|
|
1057
|
+
}
|
|
917
1058
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1059
|
+
const downloadedCertificate = await proxyProvisioningRequest(
|
|
1060
|
+
apiUrl,
|
|
1061
|
+
appleSessionId,
|
|
1062
|
+
downloadCertificateRequest(certificateID, teamID),
|
|
1063
|
+
token,
|
|
1064
|
+
);
|
|
1065
|
+
if (downloadedCertificate.status < 200 || downloadedCertificate.status >= 300 || !downloadedCertificate.rawBodyBase64) {
|
|
1066
|
+
throw new Error(`Apple certificate download failed: HTTP ${downloadedCertificate.status}`);
|
|
1067
|
+
}
|
|
1068
|
+
certificateP12Base64 = exportAppleCertificateP12({
|
|
1069
|
+
privateKeyPKCS8Base64: keyMaterial.privateKeyPKCS8Base64,
|
|
1070
|
+
certificateBase64: downloadedCertificate.rawBodyBase64,
|
|
1071
|
+
password: certificatePassword,
|
|
1072
|
+
friendlyName: `Apple Development ${bundleID}`,
|
|
1073
|
+
});
|
|
1074
|
+
storedCertificatePassword = certificatePassword;
|
|
926
1075
|
}
|
|
927
|
-
const certificateP12Base64 = exportAppleCertificateP12({
|
|
928
|
-
privateKeyPKCS8Base64: keyMaterial.privateKeyPKCS8Base64,
|
|
929
|
-
certificateBase64: downloadedCertificate.rawBodyBase64,
|
|
930
|
-
password: certificatePassword,
|
|
931
|
-
friendlyName: `Apple Development ${bundleID}`,
|
|
932
|
-
});
|
|
933
1076
|
|
|
934
1077
|
const profileName = `Limrun ${bundleID}`;
|
|
935
1078
|
const profileResponse = await proxyProvisioningRequest<AppleDeveloperPortalResponse>(
|
|
@@ -971,7 +1114,7 @@ async function prepareAppleSigningAssetsForDevice({
|
|
|
971
1114
|
teamID,
|
|
972
1115
|
certificateID,
|
|
973
1116
|
certificateP12Base64,
|
|
974
|
-
certificatePassword,
|
|
1117
|
+
certificatePassword: storedCertificatePassword,
|
|
975
1118
|
provisioningProfileBase64,
|
|
976
1119
|
profile,
|
|
977
1120
|
});
|