@limrun/ui 0.9.0-rc.1 → 0.9.0-rc.2
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/apple/client.d.ts +1 -0
- package/dist/core/device-install/apple/provisioning.d.ts +42 -31
- package/dist/core/device-install/apple/relay.d.ts +5 -9
- package/dist/core/device-install/storage/browser-storage.d.ts +18 -0
- package/dist/device-install/index.cjs +1 -9
- package/dist/device-install/index.js +75 -210
- package/dist/device-install/react.cjs +1 -1
- package/dist/device-install/react.js +1 -1
- package/dist/device-install-dialog-CSwQgbBm.js +2 -0
- package/dist/device-install-dialog-nThj775b.mjs +395 -0
- package/dist/hooks/use-device-install.d.ts +18 -3
- package/dist/index.cjs +1 -1
- package/dist/index.js +3 -3
- package/dist/use-device-install-C1uVac59.mjs +13541 -0
- package/dist/use-device-install-Ca4jcVKU.js +31 -0
- package/package.json +1 -1
- package/src/components/device-install/device-install-dialog.tsx +82 -13
- package/src/core/device-install/apple/client.ts +92 -4
- package/src/core/device-install/apple/provisioning.ts +67 -24
- package/src/core/device-install/apple/relay.ts +121 -205
- package/src/core/device-install/storage/browser-storage.ts +14 -1
- package/src/hooks/use-device-install.ts +583 -38
- package/dist/device-install-dialog-CTwVViYY.js +0 -2
- package/dist/device-install-dialog-zzKJu7SM.mjs +0 -328
- package/dist/use-device-install-CgrOKKyi.mjs +0 -13042
- package/dist/use-device-install-DDKRf6IL.js +0 -23
|
@@ -1,23 +1,40 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
closeDeviceRelayTarget,
|
|
4
|
+
createBundleIDRequest,
|
|
5
|
+
createDevelopmentProfileRequest,
|
|
6
|
+
downloadCertificateRequest,
|
|
7
|
+
downloadProfileRequest,
|
|
8
|
+
exportAppleCertificateP12,
|
|
4
9
|
fetchLimbuildInfo,
|
|
10
|
+
findDevelopmentCertificatesRequest,
|
|
11
|
+
findBundleIDRequest,
|
|
12
|
+
findDeviceRequest,
|
|
13
|
+
findDevelopmentProfilesRequest,
|
|
14
|
+
generateAppleSigningKeyAndCSR,
|
|
5
15
|
getPairRecord,
|
|
6
16
|
getLatestSigningAssets,
|
|
7
17
|
getReusableAppleSigningAssets,
|
|
8
18
|
listTeamsRequest,
|
|
9
19
|
parseProvisioningProfile,
|
|
20
|
+
parseProvisioningProfileBase64,
|
|
10
21
|
profileContainsDevice,
|
|
11
22
|
proxyProvisioningRequest,
|
|
23
|
+
putAppleGeneratedSigningAssets,
|
|
12
24
|
putPairRecord,
|
|
13
25
|
putSigningAssets,
|
|
26
|
+
registerDeviceRequest,
|
|
14
27
|
requestUSBAccess as requestDeviceUSBAccess,
|
|
15
28
|
startBrowserOwnedAppleIDLogin,
|
|
16
29
|
startSignedDeviceBuild,
|
|
17
30
|
startInstallRelay,
|
|
18
31
|
startPairingRelay,
|
|
32
|
+
submitDevelopmentCSRRequest,
|
|
19
33
|
watchBuildLogEvents,
|
|
20
34
|
type AppleIDLoginResult,
|
|
35
|
+
type AppleDeveloperPortalDevice,
|
|
36
|
+
type AppleDeveloperPortalAppID,
|
|
37
|
+
type AppleDeveloperPortalResponse,
|
|
21
38
|
type AppleDeveloperPortalTeam,
|
|
22
39
|
type BuildLogLine,
|
|
23
40
|
type DeviceInstallBuildStatus,
|
|
@@ -49,20 +66,31 @@ export type UseDeviceInstallResult = {
|
|
|
49
66
|
buildStatus: DeviceInstallBuildStatus;
|
|
50
67
|
appleSigningStatus: DeviceInstallAppleSigningStatus;
|
|
51
68
|
appleTeams: AppleDeveloperPortalTeam[];
|
|
69
|
+
appleDevices: AppleDeveloperPortalDevice[];
|
|
70
|
+
appleAppIDs: AppleDeveloperPortalAppID[];
|
|
71
|
+
applePortalSummary?: ApplePortalSummary;
|
|
52
72
|
selectedAppleTeamID?: string;
|
|
73
|
+
selectedAppleDeviceIDs: string[];
|
|
74
|
+
connectedAppleDeviceRegistered: boolean;
|
|
75
|
+
appleBundleID: string;
|
|
53
76
|
buildLogPanelOpen: boolean;
|
|
54
77
|
busyAction?: DeviceInstallBusyAction;
|
|
55
78
|
error?: string;
|
|
56
79
|
canBuild: boolean;
|
|
80
|
+
canPrepareAppleSigningAssets: boolean;
|
|
57
81
|
canRequestUSBAccess: boolean;
|
|
58
82
|
canPairBrowser: boolean;
|
|
59
83
|
canInstall: boolean;
|
|
60
84
|
setSigningFiles: (files: DeviceInstallSigningFiles) => void;
|
|
85
|
+
setAppleBundleID: (bundleID: string) => void;
|
|
86
|
+
setSelectedAppleDeviceIDs: (deviceIDs: string[]) => void;
|
|
61
87
|
setBuildLogPanelOpen: (open: boolean) => void;
|
|
62
88
|
startAppleIDLogin: (input: DeviceInstallAppleIDLoginInput) => Promise<void>;
|
|
63
89
|
submitAppleTwoFactorCode: (code: string) => Promise<void>;
|
|
64
90
|
setSelectedAppleTeamID: (teamID: string | undefined) => void;
|
|
65
|
-
|
|
91
|
+
clearAppleIDLogin: () => void;
|
|
92
|
+
registerConnectedAppleDevice: () => Promise<void>;
|
|
93
|
+
prepareAppleSigningAssets: () => Promise<void>;
|
|
66
94
|
startDeviceBuild: () => Promise<void>;
|
|
67
95
|
requestUSBAccess: () => Promise<void>;
|
|
68
96
|
pairBrowser: () => Promise<void>;
|
|
@@ -76,6 +104,7 @@ export type DeviceInstallAppleSigningStatus =
|
|
|
76
104
|
| 'two-factor-required'
|
|
77
105
|
| 'authenticated'
|
|
78
106
|
| 'preparing-assets'
|
|
107
|
+
| 'assets-ready'
|
|
79
108
|
| 'using-cached-profile'
|
|
80
109
|
| 'error';
|
|
81
110
|
|
|
@@ -96,6 +125,11 @@ export type DeviceInstallAppleIDLoginInput = {
|
|
|
96
125
|
password: string;
|
|
97
126
|
};
|
|
98
127
|
|
|
128
|
+
export type ApplePortalSummary = {
|
|
129
|
+
certificateCount: number;
|
|
130
|
+
profileCount: number;
|
|
131
|
+
};
|
|
132
|
+
|
|
99
133
|
const initialStepStatuses: DeviceInstallStepStatuses = {
|
|
100
134
|
build: 'idle',
|
|
101
135
|
usb: 'idle',
|
|
@@ -119,7 +153,12 @@ export function useDeviceInstall({
|
|
|
119
153
|
const [buildStatus, setBuildStatus] = useState<DeviceInstallBuildStatus>('idle');
|
|
120
154
|
const [appleSigningStatus, setAppleSigningStatus] = useState<DeviceInstallAppleSigningStatus>('idle');
|
|
121
155
|
const [appleTeams, setAppleTeams] = useState<AppleDeveloperPortalTeam[]>([]);
|
|
156
|
+
const [appleDevices, setAppleDevices] = useState<AppleDeveloperPortalDevice[]>([]);
|
|
157
|
+
const [appleAppIDs, setAppleAppIDs] = useState<AppleDeveloperPortalAppID[]>([]);
|
|
158
|
+
const [selectedAppleDeviceIDs, setSelectedAppleDeviceIDs] = useState<string[]>([]);
|
|
159
|
+
const [applePortalSummary, setApplePortalSummary] = useState<ApplePortalSummary | undefined>();
|
|
122
160
|
const [selectedAppleTeamID, setSelectedAppleTeamID] = useState<string | undefined>();
|
|
161
|
+
const [appleBundleID, setAppleBundleID] = useState('');
|
|
123
162
|
const [buildLogPanelOpen, setBuildLogPanelOpen] = useState(false);
|
|
124
163
|
const [busyAction, setBusyAction] = useState<DeviceInstallBusyAction | undefined>();
|
|
125
164
|
const [error, setError] = useState<string | undefined>();
|
|
@@ -128,7 +167,7 @@ export function useDeviceInstall({
|
|
|
128
167
|
const relayRef = useRef<RelayClient | undefined>(undefined);
|
|
129
168
|
const selectedDeviceRef = useRef<DeviceRelayTarget | undefined>(undefined);
|
|
130
169
|
const stopBuildWatcherRef = useRef<(() => void) | undefined>(undefined);
|
|
131
|
-
const
|
|
170
|
+
const appleIDLoginRef = useRef<AppleIDLoginResult | undefined>(undefined);
|
|
132
171
|
|
|
133
172
|
const log = useCallback((message: string, detail?: string) => {
|
|
134
173
|
const line = detail ? `${message}\n${detail}` : message;
|
|
@@ -144,6 +183,15 @@ export function useDeviceInstall({
|
|
|
144
183
|
setSigningAssets(undefined);
|
|
145
184
|
}, []);
|
|
146
185
|
|
|
186
|
+
const selectedDeveloperTeamID = useCallback(() => {
|
|
187
|
+
return developerPortalTeamID(appleTeams.find((team) => appleTeamSelectionID(team) === selectedAppleTeamID));
|
|
188
|
+
}, [appleTeams, selectedAppleTeamID]);
|
|
189
|
+
|
|
190
|
+
const connectedAppleDevice = selectedDevice?.hello.serialNumber
|
|
191
|
+
? appleDevices.find((device) => normalizeAppleUDID(device.deviceNumber) === normalizeAppleUDID(selectedDevice.hello.serialNumber))
|
|
192
|
+
: undefined;
|
|
193
|
+
const connectedAppleDeviceRegistered = !!connectedAppleDevice?.deviceId;
|
|
194
|
+
|
|
147
195
|
useEffect(() => {
|
|
148
196
|
selectedDeviceRef.current = selectedDevice;
|
|
149
197
|
}, [selectedDevice]);
|
|
@@ -157,16 +205,19 @@ export function useDeviceInstall({
|
|
|
157
205
|
useEffect(() => {
|
|
158
206
|
return () => {
|
|
159
207
|
stopBuildWatcherRef.current?.();
|
|
160
|
-
void
|
|
208
|
+
void appleIDLoginRef.current?.close();
|
|
209
|
+
appleIDLoginRef.current = undefined;
|
|
161
210
|
void cleanupDeviceAccess();
|
|
162
211
|
};
|
|
163
212
|
}, [cleanupDeviceAccess]);
|
|
164
213
|
|
|
165
214
|
const resolveSigningAssetsForBuild = useCallback(async () => {
|
|
166
|
-
const
|
|
167
|
-
|
|
215
|
+
const requestedBundleID = appleBundleID.trim();
|
|
216
|
+
const info = requestedBundleID ? undefined : apiUrl ? await fetchStoredBuildInfo(apiUrl, token).catch(() => undefined) : undefined;
|
|
217
|
+
const bundleID = requestedBundleID || info?.lastBuildConfig?.bundleId;
|
|
218
|
+
if (bundleID) {
|
|
168
219
|
const cached = await getReusableAppleSigningAssets({
|
|
169
|
-
bundleID
|
|
220
|
+
bundleID,
|
|
170
221
|
deviceUDID: selectedDevice?.hello.serialNumber,
|
|
171
222
|
teamID: selectedAppleTeamID,
|
|
172
223
|
});
|
|
@@ -213,7 +264,7 @@ export function useDeviceInstall({
|
|
|
213
264
|
setSigningAssets(storedAssets);
|
|
214
265
|
log('Signing assets stored locally', storageBundleId);
|
|
215
266
|
return storedAssets;
|
|
216
|
-
}, [apiUrl, log, selectedAppleTeamID, selectedDevice?.hello.serialNumber, signingFiles, token]);
|
|
267
|
+
}, [apiUrl, appleBundleID, log, selectedAppleTeamID, selectedDevice?.hello.serialNumber, signingFiles, token]);
|
|
217
268
|
|
|
218
269
|
const startAppleIDLogin = useCallback(
|
|
219
270
|
async ({ accountName, password }: DeviceInstallAppleIDLoginInput) => {
|
|
@@ -222,12 +273,21 @@ export function useDeviceInstall({
|
|
|
222
273
|
setError(undefined);
|
|
223
274
|
setAppleSigningStatus('authenticating');
|
|
224
275
|
try {
|
|
225
|
-
await
|
|
276
|
+
await appleIDLoginRef.current?.close().catch(() => undefined);
|
|
226
277
|
const session = await startBrowserOwnedAppleIDLogin({ limbuildApiUrl: apiUrl, token, accountName, password });
|
|
227
|
-
|
|
278
|
+
appleIDLoginRef.current = session;
|
|
228
279
|
if (!session.requiresTwoFactor) {
|
|
229
|
-
await session.finalize();
|
|
230
|
-
await refreshAppleTeams(
|
|
280
|
+
const accountSession = await session.finalize().catch(() => undefined);
|
|
281
|
+
const teamID = await refreshAppleTeams(
|
|
282
|
+
apiUrl,
|
|
283
|
+
session.appleSessionId,
|
|
284
|
+
token,
|
|
285
|
+
setAppleTeams,
|
|
286
|
+
setSelectedAppleTeamID,
|
|
287
|
+
accountSession?.body as AppleDeveloperPortalResponse | undefined,
|
|
288
|
+
);
|
|
289
|
+
await refreshAppleAppIDs(apiUrl, session.appleSessionId, token, teamID, setAppleAppIDs, setAppleBundleID);
|
|
290
|
+
await refreshApplePortalSummary(apiUrl, session.appleSessionId, token, teamID, setApplePortalSummary, log);
|
|
231
291
|
}
|
|
232
292
|
setAppleSigningStatus(session.requiresTwoFactor ? 'two-factor-required' : 'authenticated');
|
|
233
293
|
log(
|
|
@@ -248,7 +308,7 @@ export function useDeviceInstall({
|
|
|
248
308
|
|
|
249
309
|
const submitAppleTwoFactorCode = useCallback(
|
|
250
310
|
async (code: string) => {
|
|
251
|
-
const session =
|
|
311
|
+
const session = appleIDLoginRef.current;
|
|
252
312
|
if (!session) {
|
|
253
313
|
throw new Error('Start Apple ID login before submitting a two-factor code.');
|
|
254
314
|
}
|
|
@@ -256,9 +316,18 @@ export function useDeviceInstall({
|
|
|
256
316
|
setError(undefined);
|
|
257
317
|
try {
|
|
258
318
|
await session.finishTwoFactor(code);
|
|
259
|
-
await session.finalize();
|
|
260
319
|
if (apiUrl) {
|
|
261
|
-
await
|
|
320
|
+
const accountSession = await session.finalize().catch(() => undefined);
|
|
321
|
+
const teamID = await refreshAppleTeams(
|
|
322
|
+
apiUrl,
|
|
323
|
+
session.appleSessionId,
|
|
324
|
+
token,
|
|
325
|
+
setAppleTeams,
|
|
326
|
+
setSelectedAppleTeamID,
|
|
327
|
+
accountSession?.body as AppleDeveloperPortalResponse | undefined,
|
|
328
|
+
);
|
|
329
|
+
await refreshAppleAppIDs(apiUrl, session.appleSessionId, token, teamID, setAppleAppIDs, setAppleBundleID);
|
|
330
|
+
await refreshApplePortalSummary(apiUrl, session.appleSessionId, token, teamID, setApplePortalSummary, log);
|
|
262
331
|
}
|
|
263
332
|
setAppleSigningStatus('authenticated');
|
|
264
333
|
log('Apple ID two-factor authentication accepted');
|
|
@@ -274,15 +343,165 @@ export function useDeviceInstall({
|
|
|
274
343
|
[apiUrl, log, token],
|
|
275
344
|
);
|
|
276
345
|
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
346
|
+
const clearAppleIDLogin = useCallback(() => {
|
|
347
|
+
void appleIDLoginRef.current?.close();
|
|
348
|
+
appleIDLoginRef.current = undefined;
|
|
280
349
|
setAppleTeams([]);
|
|
350
|
+
setAppleDevices([]);
|
|
351
|
+
setAppleAppIDs([]);
|
|
352
|
+
setSelectedAppleDeviceIDs([]);
|
|
353
|
+
setApplePortalSummary(undefined);
|
|
281
354
|
setSelectedAppleTeamID(undefined);
|
|
282
355
|
setAppleSigningStatus('idle');
|
|
283
|
-
log('Apple ID
|
|
356
|
+
log('Apple ID login state cleared');
|
|
284
357
|
}, [log]);
|
|
285
358
|
|
|
359
|
+
const selectAppleTeam = useCallback(
|
|
360
|
+
(teamID: string | undefined) => {
|
|
361
|
+
setSelectedAppleTeamID(teamID);
|
|
362
|
+
setAppleAppIDs([]);
|
|
363
|
+
setAppleDevices([]);
|
|
364
|
+
setSelectedAppleDeviceIDs([]);
|
|
365
|
+
setApplePortalSummary(undefined);
|
|
366
|
+
setAppleBundleID('');
|
|
367
|
+
setSigningAssets(undefined);
|
|
368
|
+
const session = appleIDLoginRef.current;
|
|
369
|
+
if (!apiUrl || !session || !teamID) return;
|
|
370
|
+
const developerTeamID = developerPortalTeamID(appleTeams.find((team) => appleTeamSelectionID(team) === teamID));
|
|
371
|
+
if (!developerTeamID) return;
|
|
372
|
+
void (async () => {
|
|
373
|
+
try {
|
|
374
|
+
await refreshAppleAppIDs(apiUrl, session.appleSessionId, token, developerTeamID, setAppleAppIDs, setAppleBundleID);
|
|
375
|
+
await refreshApplePortalSummary(apiUrl, session.appleSessionId, token, developerTeamID, setApplePortalSummary, log);
|
|
376
|
+
if (selectedDeviceRef.current?.hello.serialNumber) {
|
|
377
|
+
await refreshAppleDevices({
|
|
378
|
+
apiUrl,
|
|
379
|
+
token,
|
|
380
|
+
appleSessionId: session.appleSessionId,
|
|
381
|
+
teamID: developerTeamID,
|
|
382
|
+
connectedUDID: selectedDeviceRef.current.hello.serialNumber,
|
|
383
|
+
setAppleDevices,
|
|
384
|
+
setSelectedAppleDeviceIDs,
|
|
385
|
+
log,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
} catch (caught) {
|
|
389
|
+
const message = errorMessage(caught);
|
|
390
|
+
setError(message);
|
|
391
|
+
log('Apple team refresh failed', message);
|
|
392
|
+
}
|
|
393
|
+
})();
|
|
394
|
+
},
|
|
395
|
+
[apiUrl, appleTeams, log, token],
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
const registerConnectedAppleDevice = useCallback(async () => {
|
|
399
|
+
const teamID = selectedDeveloperTeamID();
|
|
400
|
+
if (!apiUrl || !appleIDLoginRef.current || !selectedDevice?.hello.serialNumber || !teamID) return;
|
|
401
|
+
setBusyAction('build');
|
|
402
|
+
setError(undefined);
|
|
403
|
+
try {
|
|
404
|
+
const normalizedUDID = normalizeAppleUDID(selectedDevice.hello.serialNumber);
|
|
405
|
+
const created = await proxyProvisioningRequest<AppleDeveloperPortalResponse>(
|
|
406
|
+
apiUrl,
|
|
407
|
+
appleIDLoginRef.current.appleSessionId,
|
|
408
|
+
registerDeviceRequest({
|
|
409
|
+
deviceUDID: normalizedUDID,
|
|
410
|
+
teamID,
|
|
411
|
+
name: selectedDevice.hello.productName ?? 'Limrun iPhone',
|
|
412
|
+
}),
|
|
413
|
+
token,
|
|
414
|
+
);
|
|
415
|
+
assertApplePortalResponseOK(created.body, 'Apple device registration');
|
|
416
|
+
await refreshAppleDevices({
|
|
417
|
+
apiUrl,
|
|
418
|
+
token,
|
|
419
|
+
appleSessionId: appleIDLoginRef.current.appleSessionId,
|
|
420
|
+
teamID,
|
|
421
|
+
connectedUDID: selectedDevice.hello.serialNumber,
|
|
422
|
+
setAppleDevices,
|
|
423
|
+
setSelectedAppleDeviceIDs,
|
|
424
|
+
log,
|
|
425
|
+
});
|
|
426
|
+
log('Connected iPhone registered with Apple Developer', normalizedUDID);
|
|
427
|
+
} catch (caught) {
|
|
428
|
+
const message = errorMessage(caught);
|
|
429
|
+
setError(message);
|
|
430
|
+
log('Apple device registration failed', message);
|
|
431
|
+
} finally {
|
|
432
|
+
setBusyAction(undefined);
|
|
433
|
+
}
|
|
434
|
+
}, [apiUrl, log, selectedDeveloperTeamID, selectedDevice?.hello.productName, selectedDevice?.hello.serialNumber, token]);
|
|
435
|
+
|
|
436
|
+
const prepareAppleSigningAssets = useCallback(async () => {
|
|
437
|
+
if (!apiUrl || !appleIDLoginRef.current || !selectedDevice?.hello.serialNumber) return;
|
|
438
|
+
const bundleID = appleBundleID.trim();
|
|
439
|
+
if (!bundleID) {
|
|
440
|
+
throw new Error('Enter a bundle ID before preparing signing assets.');
|
|
441
|
+
}
|
|
442
|
+
if (!selectedAppleTeamID) {
|
|
443
|
+
throw new Error('Select an Apple Developer team before preparing signing assets.');
|
|
444
|
+
}
|
|
445
|
+
const teamID = selectedDeveloperTeamID();
|
|
446
|
+
if (!teamID) {
|
|
447
|
+
throw new Error('Selected Apple team does not include a Developer Portal team ID.');
|
|
448
|
+
}
|
|
449
|
+
if (selectedAppleDeviceIDs.length === 0) {
|
|
450
|
+
throw new Error('Select at least one Apple Developer device before preparing signing assets.');
|
|
451
|
+
}
|
|
452
|
+
if (!signingFiles.certificatePassword) {
|
|
453
|
+
throw new Error('Enter a .p12 password before preparing signing assets.');
|
|
454
|
+
}
|
|
455
|
+
setBusyAction('build');
|
|
456
|
+
setError(undefined);
|
|
457
|
+
setAppleSigningStatus('preparing-assets');
|
|
458
|
+
try {
|
|
459
|
+
const cached = await getReusableAppleSigningAssets({
|
|
460
|
+
bundleID,
|
|
461
|
+
deviceUDID: selectedDevice.hello.serialNumber,
|
|
462
|
+
teamID,
|
|
463
|
+
});
|
|
464
|
+
if (cached) {
|
|
465
|
+
setSigningAssets(cached);
|
|
466
|
+
setAppleSigningStatus('assets-ready');
|
|
467
|
+
log('Using cached Apple signing assets', bundleID);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const assets = await prepareAppleSigningAssetsForDevice({
|
|
471
|
+
apiUrl,
|
|
472
|
+
token,
|
|
473
|
+
appleSessionId: appleIDLoginRef.current.appleSessionId,
|
|
474
|
+
teamID,
|
|
475
|
+
bundleID,
|
|
476
|
+
deviceUDID: selectedDevice.hello.serialNumber,
|
|
477
|
+
deviceIDs: selectedAppleDeviceIDs,
|
|
478
|
+
certificatePassword: signingFiles.certificatePassword,
|
|
479
|
+
});
|
|
480
|
+
setSigningAssets(assets);
|
|
481
|
+
setAppleSigningStatus('assets-ready');
|
|
482
|
+
log('Apple signing assets stored locally', `${bundleID} for ${selectedDevice.hello.serialNumber}`);
|
|
483
|
+
} catch (caught) {
|
|
484
|
+
const message = errorMessage(caught);
|
|
485
|
+
setError(message);
|
|
486
|
+
setAppleSigningStatus('error');
|
|
487
|
+
log('Apple signing asset preparation failed', message);
|
|
488
|
+
} finally {
|
|
489
|
+
setBusyAction(undefined);
|
|
490
|
+
}
|
|
491
|
+
}, [
|
|
492
|
+
apiUrl,
|
|
493
|
+
appleBundleID,
|
|
494
|
+
appleTeams,
|
|
495
|
+
log,
|
|
496
|
+
selectedAppleTeamID,
|
|
497
|
+
selectedAppleDeviceIDs,
|
|
498
|
+
selectedDeveloperTeamID,
|
|
499
|
+
selectedDevice?.hello.productName,
|
|
500
|
+
selectedDevice?.hello.serialNumber,
|
|
501
|
+
signingFiles.certificatePassword,
|
|
502
|
+
token,
|
|
503
|
+
]);
|
|
504
|
+
|
|
286
505
|
const startDeviceBuild = useCallback(async () => {
|
|
287
506
|
if (!apiUrl) return;
|
|
288
507
|
setBusyAction('build');
|
|
@@ -316,7 +535,7 @@ export function useDeviceInstall({
|
|
|
316
535
|
setBuildStatus(status);
|
|
317
536
|
if (status === 'succeeded') {
|
|
318
537
|
setStepStatus('build', 'complete');
|
|
319
|
-
setCurrentStep('usb');
|
|
538
|
+
setCurrentStep(selectedDeviceRef.current ? (pairRecord ? 'install' : 'pair') : 'usb');
|
|
320
539
|
} else if (status === 'failed' || status === 'cancelled') {
|
|
321
540
|
setStepStatus('build', 'error');
|
|
322
541
|
}
|
|
@@ -336,7 +555,7 @@ export function useDeviceInstall({
|
|
|
336
555
|
} finally {
|
|
337
556
|
setBusyAction(undefined);
|
|
338
557
|
}
|
|
339
|
-
}, [apiUrl, log, resolveSigningAssetsForBuild, setStepStatus, token]);
|
|
558
|
+
}, [apiUrl, log, pairRecord, resolveSigningAssetsForBuild, setStepStatus, token]);
|
|
340
559
|
|
|
341
560
|
const requestUSBAccess = useCallback(async () => {
|
|
342
561
|
setBusyAction('usb');
|
|
@@ -357,6 +576,21 @@ export function useDeviceInstall({
|
|
|
357
576
|
}
|
|
358
577
|
setSigningAssets(storedSigningAssets);
|
|
359
578
|
}
|
|
579
|
+
if (apiUrl && appleIDLoginRef.current) {
|
|
580
|
+
const teamID = selectedDeveloperTeamID();
|
|
581
|
+
if (teamID) {
|
|
582
|
+
await refreshAppleDevices({
|
|
583
|
+
apiUrl,
|
|
584
|
+
token,
|
|
585
|
+
appleSessionId: appleIDLoginRef.current.appleSessionId,
|
|
586
|
+
teamID,
|
|
587
|
+
connectedUDID: target.hello.serialNumber,
|
|
588
|
+
setAppleDevices,
|
|
589
|
+
setSelectedAppleDeviceIDs,
|
|
590
|
+
log,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
360
594
|
setStepStatus('usb', 'complete');
|
|
361
595
|
setCurrentStep(storedPairRecord ? 'install' : 'pair');
|
|
362
596
|
log(storedPairRecord ? 'Pair record found' : 'No pair record found', target.hello.serialNumber);
|
|
@@ -368,7 +602,7 @@ export function useDeviceInstall({
|
|
|
368
602
|
} finally {
|
|
369
603
|
setBusyAction(undefined);
|
|
370
604
|
}
|
|
371
|
-
}, [cleanupDeviceAccess, log, setStepStatus]);
|
|
605
|
+
}, [apiUrl, cleanupDeviceAccess, log, selectedDeveloperTeamID, setStepStatus, token]);
|
|
372
606
|
|
|
373
607
|
const pairBrowser = useCallback(async () => {
|
|
374
608
|
if (!apiUrl || !selectedDevice) return;
|
|
@@ -452,20 +686,39 @@ export function useDeviceInstall({
|
|
|
452
686
|
buildStatus,
|
|
453
687
|
appleSigningStatus,
|
|
454
688
|
appleTeams,
|
|
689
|
+
appleDevices,
|
|
690
|
+
appleAppIDs,
|
|
691
|
+
applePortalSummary,
|
|
455
692
|
selectedAppleTeamID,
|
|
693
|
+
selectedAppleDeviceIDs,
|
|
694
|
+
connectedAppleDeviceRegistered,
|
|
695
|
+
appleBundleID,
|
|
456
696
|
buildLogPanelOpen,
|
|
457
697
|
busyAction,
|
|
458
698
|
error,
|
|
459
|
-
canBuild: !!apiUrl && !busyAction,
|
|
460
|
-
|
|
699
|
+
canBuild: !!apiUrl && !busyAction && !!signingAssets,
|
|
700
|
+
canPrepareAppleSigningAssets:
|
|
701
|
+
!!apiUrl &&
|
|
702
|
+
!busyAction &&
|
|
703
|
+
!!appleIDLoginRef.current &&
|
|
704
|
+
!!selectedDevice &&
|
|
705
|
+
!!appleBundleID.trim() &&
|
|
706
|
+
!!selectedDeveloperTeamID() &&
|
|
707
|
+
selectedAppleDeviceIDs.length > 0 &&
|
|
708
|
+
!!signingFiles.certificatePassword,
|
|
709
|
+
canRequestUSBAccess: !busyAction && appleSigningStatus === 'authenticated' && !!appleBundleID.trim(),
|
|
461
710
|
canPairBrowser: !!apiUrl && !busyAction && !!selectedDevice,
|
|
462
711
|
canInstall: !!apiUrl && !busyAction && !!selectedDevice && !!pairRecord,
|
|
463
712
|
setSigningFiles,
|
|
713
|
+
setAppleBundleID,
|
|
714
|
+
setSelectedAppleDeviceIDs,
|
|
464
715
|
setBuildLogPanelOpen,
|
|
465
716
|
startAppleIDLogin,
|
|
466
717
|
submitAppleTwoFactorCode,
|
|
467
|
-
setSelectedAppleTeamID,
|
|
468
|
-
|
|
718
|
+
setSelectedAppleTeamID: selectAppleTeam,
|
|
719
|
+
clearAppleIDLogin,
|
|
720
|
+
registerConnectedAppleDevice,
|
|
721
|
+
prepareAppleSigningAssets,
|
|
469
722
|
startDeviceBuild,
|
|
470
723
|
requestUSBAccess,
|
|
471
724
|
pairBrowser,
|
|
@@ -484,29 +737,321 @@ async function refreshAppleTeams(
|
|
|
484
737
|
token: string | undefined,
|
|
485
738
|
setAppleTeams: (teams: AppleDeveloperPortalTeam[]) => void,
|
|
486
739
|
setSelectedAppleTeamID: (teamID: string | undefined) => void,
|
|
740
|
+
accountSession?: AppleDeveloperPortalResponse,
|
|
487
741
|
) {
|
|
488
|
-
const response = await proxyProvisioningRequest<
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
provider?: AppleDeveloperPortalTeam;
|
|
492
|
-
}>(apiUrl, appleSessionId, listTeamsRequest(), token);
|
|
493
|
-
const teams = [
|
|
742
|
+
const response = await proxyProvisioningRequest<AppleDeveloperPortalResponse>(apiUrl, appleSessionId, listTeamsRequest(), token);
|
|
743
|
+
assertApplePortalResponseOK(response.body, 'Apple Developer team list');
|
|
744
|
+
const teams = uniqueAppleTeams([
|
|
494
745
|
...(response.body?.teams ?? []),
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
];
|
|
746
|
+
]);
|
|
747
|
+
void accountSession;
|
|
498
748
|
setAppleTeams(teams);
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
749
|
+
const firstDeveloperTeam = teams.find((team) => developerPortalTeamID(team));
|
|
750
|
+
const firstSelectionID = appleTeamSelectionID(firstDeveloperTeam ?? teams[0]);
|
|
751
|
+
if (firstSelectionID) {
|
|
752
|
+
setSelectedAppleTeamID(firstSelectionID);
|
|
753
|
+
}
|
|
754
|
+
if (teams.length === 0) {
|
|
755
|
+
throw new Error('Apple Developer account did not return any teams or providers.');
|
|
502
756
|
}
|
|
757
|
+
return teams.map(developerPortalTeamID).find((teamID) => !!teamID);
|
|
503
758
|
}
|
|
504
759
|
|
|
505
|
-
function
|
|
760
|
+
function uniqueAppleTeams(teams: AppleDeveloperPortalTeam[]) {
|
|
761
|
+
const seen = new Set<string>();
|
|
762
|
+
const result: AppleDeveloperPortalTeam[] = [];
|
|
763
|
+
for (const team of teams) {
|
|
764
|
+
const id = appleTeamSelectionID(team);
|
|
765
|
+
const key = id ?? team.name ?? JSON.stringify(team);
|
|
766
|
+
if (seen.has(key)) continue;
|
|
767
|
+
seen.add(key);
|
|
768
|
+
result.push(team);
|
|
769
|
+
}
|
|
770
|
+
return result;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
async function refreshAppleAppIDs(
|
|
774
|
+
apiUrl: string,
|
|
775
|
+
appleSessionId: string,
|
|
776
|
+
token: string | undefined,
|
|
777
|
+
teamID: string | undefined,
|
|
778
|
+
setAppleAppIDs: (appIDs: AppleDeveloperPortalAppID[]) => void,
|
|
779
|
+
setAppleBundleID: (bundleID: string) => void,
|
|
780
|
+
) {
|
|
781
|
+
if (!teamID) return;
|
|
782
|
+
const response = await proxyProvisioningRequest<AppleDeveloperPortalResponse>(
|
|
783
|
+
apiUrl,
|
|
784
|
+
appleSessionId,
|
|
785
|
+
findBundleIDRequest({ bundleID: '', teamID }),
|
|
786
|
+
token,
|
|
787
|
+
);
|
|
788
|
+
assertApplePortalResponseOK(response.body, 'Apple bundle ID list');
|
|
789
|
+
const appIDs = response.body?.appIds ?? [];
|
|
790
|
+
setAppleAppIDs(appIDs);
|
|
791
|
+
const firstBundleID = bundleIDFromAppleAppID(appIDs[0]);
|
|
792
|
+
if (firstBundleID) {
|
|
793
|
+
setAppleBundleID(firstBundleID);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
async function refreshApplePortalSummary(
|
|
798
|
+
apiUrl: string,
|
|
799
|
+
appleSessionId: string,
|
|
800
|
+
token: string | undefined,
|
|
801
|
+
teamID: string | undefined,
|
|
802
|
+
setApplePortalSummary: (summary: ApplePortalSummary | undefined) => void,
|
|
803
|
+
log: (message: string, detail?: string) => void,
|
|
804
|
+
) {
|
|
805
|
+
const [certificates, profiles] = await Promise.all([
|
|
806
|
+
proxyProvisioningRequest<AppleDeveloperPortalResponse>(
|
|
807
|
+
apiUrl,
|
|
808
|
+
appleSessionId,
|
|
809
|
+
findDevelopmentCertificatesRequest(teamID ?? ''),
|
|
810
|
+
token,
|
|
811
|
+
),
|
|
812
|
+
proxyProvisioningRequest<AppleDeveloperPortalResponse>(
|
|
813
|
+
apiUrl,
|
|
814
|
+
appleSessionId,
|
|
815
|
+
findDevelopmentProfilesRequest({ bundleID: '', teamID: teamID ?? '' }),
|
|
816
|
+
token,
|
|
817
|
+
),
|
|
818
|
+
]);
|
|
819
|
+
assertApplePortalResponseOK(certificates.body, 'Apple Developer certificate list');
|
|
820
|
+
assertApplePortalResponseOK(profiles.body, 'Apple Developer profile list');
|
|
821
|
+
const summary = {
|
|
822
|
+
certificateCount: certificates.body?.certRequests?.length ?? 0,
|
|
823
|
+
profileCount: profiles.body?.provisioningProfiles?.length ?? 0,
|
|
824
|
+
};
|
|
825
|
+
setApplePortalSummary(summary);
|
|
826
|
+
log(
|
|
827
|
+
'Apple Developer resources fetched',
|
|
828
|
+
`${summary.certificateCount} certificates, ${summary.profileCount} provisioning profiles`,
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
async function refreshAppleDevices({
|
|
833
|
+
apiUrl,
|
|
834
|
+
token,
|
|
835
|
+
appleSessionId,
|
|
836
|
+
teamID,
|
|
837
|
+
connectedUDID,
|
|
838
|
+
setAppleDevices,
|
|
839
|
+
setSelectedAppleDeviceIDs,
|
|
840
|
+
log,
|
|
841
|
+
}: {
|
|
842
|
+
apiUrl: string;
|
|
843
|
+
token?: string;
|
|
844
|
+
appleSessionId: string;
|
|
845
|
+
teamID: string;
|
|
846
|
+
connectedUDID?: string;
|
|
847
|
+
setAppleDevices: (devices: AppleDeveloperPortalDevice[]) => void;
|
|
848
|
+
setSelectedAppleDeviceIDs: (deviceIDs: string[]) => void;
|
|
849
|
+
log: (message: string, detail?: string) => void;
|
|
850
|
+
}) {
|
|
851
|
+
const response = await proxyProvisioningRequest<AppleDeveloperPortalResponse>(
|
|
852
|
+
apiUrl,
|
|
853
|
+
appleSessionId,
|
|
854
|
+
findDeviceRequest({ deviceUDID: connectedUDID ?? '', teamID }),
|
|
855
|
+
token,
|
|
856
|
+
);
|
|
857
|
+
assertApplePortalResponseOK(response.body, 'Apple device list');
|
|
858
|
+
const devices = response.body?.devices ?? [];
|
|
859
|
+
setAppleDevices(devices);
|
|
860
|
+
const connected = devices.find((device) => normalizeAppleUDID(device.deviceNumber) === normalizeAppleUDID(connectedUDID));
|
|
861
|
+
if (connected?.deviceId) {
|
|
862
|
+
setSelectedAppleDeviceIDs([connected.deviceId]);
|
|
863
|
+
log('Connected iPhone found in Apple Developer devices', connected.name ?? connected.deviceNumber);
|
|
864
|
+
} else {
|
|
865
|
+
setSelectedAppleDeviceIDs([]);
|
|
866
|
+
log('Connected iPhone is not registered with Apple Developer', connectedUDID);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
async function prepareAppleSigningAssetsForDevice({
|
|
871
|
+
apiUrl,
|
|
872
|
+
token,
|
|
873
|
+
appleSessionId,
|
|
874
|
+
teamID,
|
|
875
|
+
bundleID,
|
|
876
|
+
deviceUDID,
|
|
877
|
+
certificatePassword,
|
|
878
|
+
deviceIDs,
|
|
879
|
+
}: {
|
|
880
|
+
apiUrl: string;
|
|
881
|
+
token?: string;
|
|
882
|
+
appleSessionId: string;
|
|
883
|
+
teamID: string;
|
|
884
|
+
bundleID: string;
|
|
885
|
+
deviceUDID: string;
|
|
886
|
+
deviceIDs: string[];
|
|
887
|
+
certificatePassword: string;
|
|
888
|
+
}) {
|
|
889
|
+
const normalizedUDID = deviceUDID.replace(/-/g, '').replace(/[^a-fA-F0-9]/g, '');
|
|
890
|
+
const keyMaterial = await generateAppleSigningKeyAndCSR({
|
|
891
|
+
commonName: `Limrun ${bundleID}`,
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
const appIDID = await findOrCreateAppleBundleID({
|
|
895
|
+
apiUrl,
|
|
896
|
+
token,
|
|
897
|
+
appleSessionId,
|
|
898
|
+
teamID,
|
|
899
|
+
bundleID,
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
const certificateResponse = await proxyProvisioningRequest<AppleDeveloperPortalResponse>(
|
|
903
|
+
apiUrl,
|
|
904
|
+
appleSessionId,
|
|
905
|
+
submitDevelopmentCSRRequest({ csrPEM: keyMaterial.csrPEM, teamID }),
|
|
906
|
+
token,
|
|
907
|
+
);
|
|
908
|
+
assertApplePortalResponseOK(certificateResponse.body, 'Apple Development certificate creation');
|
|
909
|
+
const certificateID =
|
|
910
|
+
stringField(certificateResponse.body?.certRequest, 'certificateId') ??
|
|
911
|
+
stringField(certificateResponse.body?.certRequest, 'certRequestId') ??
|
|
912
|
+
stringField(certificateResponse.body, 'certificateId') ??
|
|
913
|
+
stringField(certificateResponse.body, 'certRequestId');
|
|
914
|
+
if (!certificateID) {
|
|
915
|
+
throw new Error('Apple certificate creation did not return a certificate ID.');
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const downloadedCertificate = await proxyProvisioningRequest(
|
|
919
|
+
apiUrl,
|
|
920
|
+
appleSessionId,
|
|
921
|
+
downloadCertificateRequest(certificateID, teamID),
|
|
922
|
+
token,
|
|
923
|
+
);
|
|
924
|
+
if (downloadedCertificate.status < 200 || downloadedCertificate.status >= 300 || !downloadedCertificate.rawBodyBase64) {
|
|
925
|
+
throw new Error(`Apple certificate download failed: HTTP ${downloadedCertificate.status}`);
|
|
926
|
+
}
|
|
927
|
+
const certificateP12Base64 = exportAppleCertificateP12({
|
|
928
|
+
privateKeyPKCS8Base64: keyMaterial.privateKeyPKCS8Base64,
|
|
929
|
+
certificateBase64: downloadedCertificate.rawBodyBase64,
|
|
930
|
+
password: certificatePassword,
|
|
931
|
+
friendlyName: `Apple Development ${bundleID}`,
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
const profileName = `Limrun ${bundleID}`;
|
|
935
|
+
const profileResponse = await proxyProvisioningRequest<AppleDeveloperPortalResponse>(
|
|
936
|
+
apiUrl,
|
|
937
|
+
appleSessionId,
|
|
938
|
+
createDevelopmentProfileRequest({
|
|
939
|
+
bundleID,
|
|
940
|
+
teamID,
|
|
941
|
+
appIDID,
|
|
942
|
+
certificateID,
|
|
943
|
+
deviceIDs,
|
|
944
|
+
name: profileName,
|
|
945
|
+
}),
|
|
946
|
+
token,
|
|
947
|
+
);
|
|
948
|
+
assertApplePortalResponseOK(profileResponse.body, 'Apple provisioning profile creation');
|
|
949
|
+
const profileID =
|
|
950
|
+
stringField(profileResponse.body?.provisioningProfile, 'provisioningProfileId') ??
|
|
951
|
+
stringField(profileResponse.body, 'provisioningProfileId');
|
|
952
|
+
if (!profileID) {
|
|
953
|
+
throw new Error('Apple provisioning profile creation did not return a profile ID.');
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const downloadedProfile = await proxyProvisioningRequest(
|
|
957
|
+
apiUrl,
|
|
958
|
+
appleSessionId,
|
|
959
|
+
downloadProfileRequest(profileID, teamID),
|
|
960
|
+
token,
|
|
961
|
+
);
|
|
962
|
+
if (downloadedProfile.status < 200 || downloadedProfile.status >= 300 || !downloadedProfile.rawBodyBase64) {
|
|
963
|
+
throw new Error(`Apple provisioning profile download failed: HTTP ${downloadedProfile.status}`);
|
|
964
|
+
}
|
|
965
|
+
const provisioningProfileBase64 = downloadedProfile.rawBodyBase64;
|
|
966
|
+
const profile = parseProvisioningProfileBase64(provisioningProfileBase64);
|
|
967
|
+
|
|
968
|
+
return putAppleGeneratedSigningAssets({
|
|
969
|
+
bundleID,
|
|
970
|
+
deviceUDID: normalizedUDID,
|
|
971
|
+
teamID,
|
|
972
|
+
certificateID,
|
|
973
|
+
certificateP12Base64,
|
|
974
|
+
certificatePassword,
|
|
975
|
+
provisioningProfileBase64,
|
|
976
|
+
profile,
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
async function findOrCreateAppleBundleID({
|
|
981
|
+
apiUrl,
|
|
982
|
+
token,
|
|
983
|
+
appleSessionId,
|
|
984
|
+
teamID,
|
|
985
|
+
bundleID,
|
|
986
|
+
}: {
|
|
987
|
+
apiUrl: string;
|
|
988
|
+
token?: string;
|
|
989
|
+
appleSessionId: string;
|
|
990
|
+
teamID: string;
|
|
991
|
+
bundleID: string;
|
|
992
|
+
}) {
|
|
993
|
+
const existing = await proxyProvisioningRequest<AppleDeveloperPortalResponse>(
|
|
994
|
+
apiUrl,
|
|
995
|
+
appleSessionId,
|
|
996
|
+
findBundleIDRequest({ bundleID, teamID }),
|
|
997
|
+
token,
|
|
998
|
+
);
|
|
999
|
+
assertApplePortalResponseOK(existing.body, 'Apple bundle ID lookup');
|
|
1000
|
+
const found = existing.body?.appIds?.find((app) => stringField(app, 'identifier') === bundleID || stringField(app, 'bundleId') === bundleID);
|
|
1001
|
+
const foundID = stringField(found, 'appIdId') ?? stringField(found, 'appId');
|
|
1002
|
+
if (foundID) return foundID;
|
|
1003
|
+
|
|
1004
|
+
const created = await proxyProvisioningRequest<AppleDeveloperPortalResponse>(
|
|
1005
|
+
apiUrl,
|
|
1006
|
+
appleSessionId,
|
|
1007
|
+
createBundleIDRequest({ bundleID, teamID, name: bundleID }),
|
|
1008
|
+
token,
|
|
1009
|
+
);
|
|
1010
|
+
assertApplePortalResponseOK(created.body, 'Apple bundle ID creation');
|
|
1011
|
+
const createdID =
|
|
1012
|
+
stringField(created.body?.appId, 'appIdId') ??
|
|
1013
|
+
stringField(created.body?.appId, 'appId') ??
|
|
1014
|
+
stringField(created.body, 'appIdId') ??
|
|
1015
|
+
stringField(created.body, 'appId');
|
|
1016
|
+
if (!createdID) {
|
|
1017
|
+
throw new Error('Apple bundle ID creation did not return an App ID.');
|
|
1018
|
+
}
|
|
1019
|
+
return createdID;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function assertApplePortalResponseOK(response: AppleDeveloperPortalResponse | undefined, label: string) {
|
|
1023
|
+
if (!response) {
|
|
1024
|
+
throw new Error(`${label} returned an empty response.`);
|
|
1025
|
+
}
|
|
1026
|
+
if (response.resultCode !== undefined && response.resultCode !== 0) {
|
|
1027
|
+
throw new Error(`${label} failed: ${response.userString ?? response.resultString ?? response.resultCode}`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function stringField(record: Record<string, unknown> | undefined, key: string) {
|
|
1032
|
+
const value = record?.[key];
|
|
1033
|
+
if (typeof value === 'string') return value;
|
|
1034
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
1035
|
+
return undefined;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function normalizeAppleUDID(udid?: string) {
|
|
1039
|
+
return (udid ?? '').replace(/-/g, '').replace(/[^a-fA-F0-9]/g, '').toUpperCase();
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function appleTeamSelectionID(team?: AppleDeveloperPortalTeam) {
|
|
506
1043
|
const value = team?.teamId ?? team?.providerId ?? team?.publicProviderId;
|
|
507
1044
|
return value === undefined || value === '' ? undefined : String(value);
|
|
508
1045
|
}
|
|
509
1046
|
|
|
1047
|
+
function developerPortalTeamID(team?: AppleDeveloperPortalTeam) {
|
|
1048
|
+
return team?.teamId && team.teamId !== '' ? team.teamId : undefined;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function bundleIDFromAppleAppID(appID?: AppleDeveloperPortalAppID) {
|
|
1052
|
+
return appID?.identifier || appID?.bundleId || undefined;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
510
1055
|
async function fileToBase64(file: File) {
|
|
511
1056
|
const buffer = await file.arrayBuffer();
|
|
512
1057
|
let binary = '';
|