@limrun/ui 0.9.0-rc.9 → 0.13.3-rc.1
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/README.md +25 -8
- package/dist/app-store-relay/index.cjs +9 -0
- package/dist/app-store-relay/index.d.ts +102 -0
- package/dist/app-store-relay/index.js +430 -0
- package/dist/app-store-relay/react.cjs +1 -0
- package/dist/app-store-relay/react.d.ts +15 -0
- package/dist/app-store-relay/react.js +65 -0
- package/dist/browser-storage-BJ__DGJ6.mjs +202 -0
- package/dist/browser-storage-C1jQLXat.js +1 -0
- package/dist/client-Bk1N3XIF.mjs +228 -0
- package/dist/client-CnbCWvCs.js +1 -0
- package/dist/components/device-install/index.d.ts +1 -2
- package/dist/core/device-install/apple/provisioning.d.ts +55 -4
- package/dist/core/device-install/operations/limbuild-client.d.ts +15 -1
- package/dist/core/device-install/storage/browser-storage.d.ts +9 -5
- package/dist/core/device-install/types.d.ts +4 -1
- package/dist/device-build/index.cjs +1 -0
- package/dist/device-build/index.d.ts +4 -0
- package/dist/device-build/index.js +84 -0
- package/dist/device-build/react.cjs +1 -0
- package/dist/device-build/react.d.ts +20 -0
- package/dist/device-build/react.js +66 -0
- package/dist/device-build/signing.d.ts +20 -0
- package/dist/device-install/index.cjs +1 -1
- package/dist/device-install/index.d.ts +18 -3
- package/dist/device-install/index.js +570 -76
- package/dist/device-install/react.cjs +1 -1
- package/dist/device-install/react.d.ts +23 -1
- package/dist/device-install/react.js +93 -2
- package/dist/hooks/index.d.ts +1 -1
- package/dist/index-BXg7HdIs.mjs +11547 -0
- package/dist/index-CMeQfhYy.js +22 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +502 -495
- package/dist/limbuild-client-CFJhYsRx.mjs +79 -0
- package/dist/limbuild-client-C_CMNLYV.js +1 -0
- package/dist/provisioning-CdseoMJQ.mjs +239 -0
- package/dist/provisioning-D2ZZQeyX.js +1 -0
- package/package.json +21 -1
- package/src/app-store-relay/index.test.ts +74 -0
- package/src/app-store-relay/index.ts +447 -0
- package/src/app-store-relay/react.ts +125 -0
- package/src/components/device-install/index.ts +1 -2
- package/src/core/device-install/apple/provisioning.test.ts +84 -0
- package/src/core/device-install/apple/provisioning.ts +91 -7
- package/src/core/device-install/operations/limbuild-client.ts +33 -3
- package/src/core/device-install/storage/browser-storage.ts +29 -14
- package/src/core/device-install/types.ts +5 -1
- package/src/device-build/index.ts +42 -0
- package/src/device-build/react.ts +128 -0
- package/src/device-build/signing.ts +94 -0
- package/src/device-install/index.ts +49 -3
- package/src/device-install/react.ts +180 -1
- package/src/hooks/index.ts +1 -1
- package/src/index.ts +1 -4
- package/vite.config.ts +4 -0
- package/dist/components/device-install/device-install-dialog.d.ts +0 -5
- package/dist/device-install-dialog-CqxDEH85.mjs +0 -443
- package/dist/device-install-dialog-jvqQ-fuo.js +0 -2
- package/dist/device-install-dialog.css +0 -1
- package/dist/hooks/use-device-install.d.ts +0 -73
- package/dist/use-device-install-CAPli9MR.js +0 -31
- package/dist/use-device-install-H8dqqvbR.mjs +0 -13624
- package/src/components/device-install/device-install-dialog.css +0 -325
- package/src/components/device-install/device-install-dialog.tsx +0 -495
- package/src/hooks/use-device-install.ts +0 -1219
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProvisioningProfileInfo, StoredSigningAssets } from '../types';
|
|
1
|
+
import type { DeviceInstallSigningMode, ProvisioningProfileInfo, StoredSigningAssets } from '../types';
|
|
2
2
|
import {
|
|
3
3
|
getSigningAssets,
|
|
4
4
|
normalizeBundleID,
|
|
@@ -64,15 +64,17 @@ export type AppleSigningAssetCacheInput = {
|
|
|
64
64
|
bundleID: string;
|
|
65
65
|
deviceUDID?: string;
|
|
66
66
|
teamID?: string;
|
|
67
|
+
signingMode?: DeviceInstallSigningMode;
|
|
67
68
|
};
|
|
68
69
|
|
|
69
70
|
export type PutAppleGeneratedSigningAssetsInput = {
|
|
70
71
|
bundleID: string;
|
|
71
72
|
deviceUDID?: string;
|
|
72
73
|
teamID?: string;
|
|
74
|
+
signingMode?: DeviceInstallSigningMode;
|
|
73
75
|
certificateID?: string;
|
|
74
76
|
certificateP12Base64: string;
|
|
75
|
-
certificatePassword
|
|
77
|
+
certificatePassword?: string;
|
|
76
78
|
provisioningProfileBase64: string;
|
|
77
79
|
profile: ProvisioningProfileInfo;
|
|
78
80
|
};
|
|
@@ -105,6 +107,13 @@ export function findDevelopmentCertificatesRequest(teamID = '') {
|
|
|
105
107
|
});
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
export function findDistributionCertificatesRequest(teamID = '') {
|
|
111
|
+
return pagedRequest('/account/ios/certificate/listCertRequests.action', teamID, {
|
|
112
|
+
sort: 'name=asc',
|
|
113
|
+
types: 'WXV89964HE,R58UK2EWSO',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
108
117
|
export function findDevelopmentProfilesRequest({
|
|
109
118
|
bundleID,
|
|
110
119
|
teamID = '',
|
|
@@ -115,6 +124,17 @@ export function findDevelopmentProfilesRequest({
|
|
|
115
124
|
});
|
|
116
125
|
}
|
|
117
126
|
|
|
127
|
+
export function findAdHocProfilesRequest({
|
|
128
|
+
bundleID,
|
|
129
|
+
teamID = '',
|
|
130
|
+
}: Pick<AppleProvisioningContext, 'bundleID' | 'teamID'>) {
|
|
131
|
+
return pagedRequest('/account/ios/profile/listProvisioningProfiles.action', teamID, {
|
|
132
|
+
search: bundleID,
|
|
133
|
+
sort: 'name=asc',
|
|
134
|
+
distributionType: 'adhoc',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
118
138
|
export function registerDeviceRequest({
|
|
119
139
|
deviceUDID,
|
|
120
140
|
teamID = '',
|
|
@@ -168,6 +188,24 @@ export function submitDevelopmentCSRRequest({
|
|
|
168
188
|
} satisfies AppleProvisioningRequest;
|
|
169
189
|
}
|
|
170
190
|
|
|
191
|
+
export function submitDistributionCSRRequest({
|
|
192
|
+
csrPEM,
|
|
193
|
+
teamID = '',
|
|
194
|
+
}: {
|
|
195
|
+
csrPEM: string;
|
|
196
|
+
teamID?: string;
|
|
197
|
+
}) {
|
|
198
|
+
return {
|
|
199
|
+
method: 'POST',
|
|
200
|
+
path: '/account/ios/certificate/submitCertificateRequest.action',
|
|
201
|
+
payload: {
|
|
202
|
+
teamId: teamID,
|
|
203
|
+
type: 'WXV89964HE',
|
|
204
|
+
csrContent: csrPEM,
|
|
205
|
+
},
|
|
206
|
+
} satisfies AppleProvisioningRequest;
|
|
207
|
+
}
|
|
208
|
+
|
|
171
209
|
export function downloadCertificateRequest(certificateID: string, teamID = '') {
|
|
172
210
|
return {
|
|
173
211
|
method: 'GET',
|
|
@@ -180,6 +218,18 @@ export function downloadCertificateRequest(certificateID: string, teamID = '') {
|
|
|
180
218
|
} satisfies AppleProvisioningRequest;
|
|
181
219
|
}
|
|
182
220
|
|
|
221
|
+
export function downloadDistributionCertificateRequest(certificateID: string, teamID = '') {
|
|
222
|
+
return {
|
|
223
|
+
method: 'GET',
|
|
224
|
+
path: '/account/ios/certificate/downloadCertificateContent.action',
|
|
225
|
+
payload: {
|
|
226
|
+
teamId: teamID,
|
|
227
|
+
certificateId: certificateID,
|
|
228
|
+
type: 'WXV89964HE',
|
|
229
|
+
},
|
|
230
|
+
} satisfies AppleProvisioningRequest;
|
|
231
|
+
}
|
|
232
|
+
|
|
183
233
|
export function createDevelopmentProfileRequest({
|
|
184
234
|
bundleID,
|
|
185
235
|
teamID = '',
|
|
@@ -208,6 +258,34 @@ export function createDevelopmentProfileRequest({
|
|
|
208
258
|
} satisfies AppleProvisioningRequest;
|
|
209
259
|
}
|
|
210
260
|
|
|
261
|
+
export function createAdHocProfileRequest({
|
|
262
|
+
bundleID,
|
|
263
|
+
teamID = '',
|
|
264
|
+
appIDID,
|
|
265
|
+
certificateID,
|
|
266
|
+
deviceIDs,
|
|
267
|
+
name,
|
|
268
|
+
}: Pick<AppleProvisioningContext, 'bundleID' | 'teamID'> & {
|
|
269
|
+
appIDID: string;
|
|
270
|
+
certificateID: string;
|
|
271
|
+
deviceIDs: string[];
|
|
272
|
+
name?: string;
|
|
273
|
+
}) {
|
|
274
|
+
return {
|
|
275
|
+
method: 'POST',
|
|
276
|
+
path: '/account/ios/profile/createProvisioningProfile.action',
|
|
277
|
+
payload: {
|
|
278
|
+
teamId: teamID,
|
|
279
|
+
provisioningProfileName: name ?? `Limrun Ad Hoc ${bundleID}`,
|
|
280
|
+
certificateIds: [certificateID],
|
|
281
|
+
appIdId: appIDID,
|
|
282
|
+
deviceIds: deviceIDs,
|
|
283
|
+
distributionType: 'adhoc',
|
|
284
|
+
subPlatform: 'ios',
|
|
285
|
+
},
|
|
286
|
+
} satisfies AppleProvisioningRequest;
|
|
287
|
+
}
|
|
288
|
+
|
|
211
289
|
export function downloadProfileRequest(profileID: string, teamID = '') {
|
|
212
290
|
return {
|
|
213
291
|
method: 'GET',
|
|
@@ -223,9 +301,10 @@ export async function getReusableAppleSigningAssets({
|
|
|
223
301
|
bundleID,
|
|
224
302
|
deviceUDID,
|
|
225
303
|
teamID,
|
|
304
|
+
signingMode,
|
|
226
305
|
}: AppleSigningAssetCacheInput) {
|
|
227
|
-
const stored = await getSigningAssets({ bundleID, deviceUDID });
|
|
228
|
-
if (!stored || !storedSigningAssetsReusable(stored, { bundleID, deviceUDID, teamID })) {
|
|
306
|
+
const stored = await getSigningAssets({ bundleID, deviceUDID, signingMode });
|
|
307
|
+
if (!stored || !storedSigningAssetsReusable(stored, { bundleID, deviceUDID, teamID, signingMode })) {
|
|
229
308
|
return undefined;
|
|
230
309
|
}
|
|
231
310
|
return stored;
|
|
@@ -234,15 +313,20 @@ export async function getReusableAppleSigningAssets({
|
|
|
234
313
|
export async function putAppleGeneratedSigningAssets(input: PutAppleGeneratedSigningAssetsInput) {
|
|
235
314
|
return putSigningAssets({
|
|
236
315
|
...input,
|
|
237
|
-
certificateFileName: input.certificateID ? `${input.certificateID}.p12` : '
|
|
238
|
-
|
|
316
|
+
certificateFileName: input.certificateID ? `${input.certificateID}.p12` : `apple-${input.signingMode ?? 'development'}.p12`,
|
|
317
|
+
certificatePassword: input.certificatePassword || undefined,
|
|
318
|
+
signingMode: input.signingMode,
|
|
319
|
+
profileFileName: input.profile.uuid ? `${input.profile.uuid}.mobileprovision` : `${input.signingMode ?? 'development'}.mobileprovision`,
|
|
239
320
|
});
|
|
240
321
|
}
|
|
241
322
|
|
|
242
323
|
export function storedSigningAssetsReusable(
|
|
243
324
|
stored: StoredSigningAssets,
|
|
244
|
-
{ bundleID, deviceUDID, teamID }: AppleSigningAssetCacheInput,
|
|
325
|
+
{ bundleID, deviceUDID, teamID, signingMode = 'development' }: AppleSigningAssetCacheInput,
|
|
245
326
|
) {
|
|
327
|
+
if ((stored.signingMode ?? 'development') !== signingMode) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
246
330
|
if (!profileMatchesBundleID(stored.profile, bundleID)) {
|
|
247
331
|
return false;
|
|
248
332
|
}
|
|
@@ -12,7 +12,7 @@ export type StartSignedDeviceBuildOptions = {
|
|
|
12
12
|
limbuildApiUrl: string;
|
|
13
13
|
token?: string;
|
|
14
14
|
certificateP12Base64: string;
|
|
15
|
-
certificatePassword
|
|
15
|
+
certificatePassword?: string;
|
|
16
16
|
provisioningProfileBase64: string;
|
|
17
17
|
};
|
|
18
18
|
|
|
@@ -25,6 +25,21 @@ export type BuildLogEventsOptions = {
|
|
|
25
25
|
onError?: (error: Error) => void;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
export type IOSOTAInstall = {
|
|
29
|
+
installUrl: string;
|
|
30
|
+
landingUrl: string;
|
|
31
|
+
manifestUrl: string;
|
|
32
|
+
ipaUrl: string;
|
|
33
|
+
bundleId: string;
|
|
34
|
+
displayName: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type GetIOSOTAInstallOptions = {
|
|
38
|
+
limbuildApiUrl: string;
|
|
39
|
+
execId: string;
|
|
40
|
+
token?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
28
43
|
export async function fetchLimbuildInfo(limbuildApiUrl: string, token?: string) {
|
|
29
44
|
const url = new URL(`${limbuildApiUrl}/info`);
|
|
30
45
|
if (token) {
|
|
@@ -59,10 +74,10 @@ export async function startSignedDeviceBuild({
|
|
|
59
74
|
},
|
|
60
75
|
body: JSON.stringify({
|
|
61
76
|
command: 'xcodebuild',
|
|
62
|
-
xcodebuild: { sdk: 'iphoneos'
|
|
77
|
+
xcodebuild: { sdk: 'iphoneos' },
|
|
63
78
|
signing: {
|
|
64
79
|
certificateP12Base64,
|
|
65
|
-
certificatePassword,
|
|
80
|
+
...(certificatePassword ? { certificatePassword } : {}),
|
|
66
81
|
provisioningProfileBase64,
|
|
67
82
|
},
|
|
68
83
|
}),
|
|
@@ -74,6 +89,21 @@ export async function startSignedDeviceBuild({
|
|
|
74
89
|
return (await response.json()) as { execId?: string };
|
|
75
90
|
}
|
|
76
91
|
|
|
92
|
+
export async function getIOSOTAInstall({ limbuildApiUrl, execId, token }: GetIOSOTAInstallOptions) {
|
|
93
|
+
const url = new URL(`${limbuildApiUrl}/exec/${encodeURIComponent(execId)}/ios/ota`);
|
|
94
|
+
if (token) {
|
|
95
|
+
url.searchParams.set('token', token);
|
|
96
|
+
}
|
|
97
|
+
const response = await fetch(url.toString(), {
|
|
98
|
+
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
const body = await response.text();
|
|
102
|
+
throw new Error(`OTA install metadata failed: HTTP ${response.status} ${body}`);
|
|
103
|
+
}
|
|
104
|
+
return (await response.json()) as IOSOTAInstall;
|
|
105
|
+
}
|
|
106
|
+
|
|
77
107
|
export function watchBuildLogEvents({
|
|
78
108
|
limbuildApiUrl,
|
|
79
109
|
execId,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
DeviceInstallSigningMode,
|
|
2
3
|
ProvisioningProfileInfo,
|
|
3
4
|
PutSigningAssetsInput,
|
|
4
5
|
StoredPairRecord,
|
|
@@ -49,35 +50,42 @@ export async function putPairRecord(record: PairRecordPayload, metadata: { produ
|
|
|
49
50
|
export async function getSigningAssets({
|
|
50
51
|
deviceUDID,
|
|
51
52
|
bundleID,
|
|
53
|
+
signingMode = 'development',
|
|
52
54
|
}: {
|
|
53
55
|
deviceUDID?: string;
|
|
54
56
|
bundleID?: string;
|
|
57
|
+
signingMode?: DeviceInstallSigningMode;
|
|
55
58
|
}) {
|
|
56
59
|
const normalizedBundleID = normalizeBundleID(bundleID);
|
|
57
60
|
if (!normalizedBundleID) return undefined;
|
|
58
61
|
const normalizedUDID = normalizeUDID(deviceUDID);
|
|
59
|
-
const bundleScoped = await getSigningAssetsByID(signingAssetID('bundle', normalizedBundleID));
|
|
62
|
+
const bundleScoped = await getSigningAssetsByID(signingAssetID('bundle', normalizedBundleID, signingMode));
|
|
60
63
|
if (bundleScoped) return bundleScoped;
|
|
61
64
|
if (normalizedUDID) {
|
|
62
|
-
const exact = await getSigningAssetsByID(signingAssetID(normalizedUDID, normalizedBundleID));
|
|
65
|
+
const exact = await getSigningAssetsByID(signingAssetID(normalizedUDID, normalizedBundleID, signingMode));
|
|
63
66
|
if (exact) return exact;
|
|
64
67
|
}
|
|
65
|
-
const candidates = await findSigningAssetsForBundle(normalizedBundleID);
|
|
68
|
+
const candidates = await findSigningAssetsForBundle(normalizedBundleID, signingMode);
|
|
66
69
|
return candidates[0];
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
export async function getLatestSigningAssets() {
|
|
72
|
+
export async function getLatestSigningAssets(signingMode: DeviceInstallSigningMode = 'development') {
|
|
70
73
|
const all = await getAllSigningAssets();
|
|
71
|
-
return all
|
|
72
|
-
(
|
|
73
|
-
|
|
74
|
+
return all
|
|
75
|
+
.filter((asset) => (asset.signingMode ?? 'development') === signingMode)
|
|
76
|
+
.sort(
|
|
77
|
+
(left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime(),
|
|
78
|
+
)[0];
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
export async function getLatestSigningAssetsWithCertificate(teamID?: string) {
|
|
81
|
+
export async function getLatestSigningAssetsWithCertificate(teamID?: string, signingMode: DeviceInstallSigningMode = 'development') {
|
|
77
82
|
const all = await getAllSigningAssets();
|
|
78
83
|
return all
|
|
79
84
|
.filter((asset) => {
|
|
80
|
-
if (!asset.certificateID || !asset.certificateP12Base64
|
|
85
|
+
if (!asset.certificateID || !asset.certificateP12Base64) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if ((asset.signingMode ?? 'development') !== signingMode) {
|
|
81
89
|
return false;
|
|
82
90
|
}
|
|
83
91
|
return !teamID || !asset.teamID || asset.teamID === teamID;
|
|
@@ -91,11 +99,13 @@ export async function putSigningAssets(input: PutSigningAssetsInput) {
|
|
|
91
99
|
throw new Error('Cannot store signing assets without a bundle ID.');
|
|
92
100
|
}
|
|
93
101
|
const normalizedUDID = normalizeUDID(input.deviceUDID);
|
|
94
|
-
const
|
|
102
|
+
const signingMode = input.signingMode ?? 'development';
|
|
103
|
+
const id = signingAssetID('bundle', normalizedBundleID, signingMode);
|
|
95
104
|
const stored: StoredSigningAssets = {
|
|
96
105
|
...input,
|
|
97
106
|
id,
|
|
98
107
|
deviceUDID: normalizedUDID || undefined,
|
|
108
|
+
signingMode,
|
|
99
109
|
bundleID: normalizedBundleID,
|
|
100
110
|
updatedAt: new Date().toISOString(),
|
|
101
111
|
};
|
|
@@ -106,11 +116,11 @@ export async function putSigningAssets(input: PutSigningAssetsInput) {
|
|
|
106
116
|
return stored;
|
|
107
117
|
}
|
|
108
118
|
|
|
109
|
-
export async function findSigningAssetsForBundle(bundleID?: string) {
|
|
119
|
+
export async function findSigningAssetsForBundle(bundleID?: string, signingMode: DeviceInstallSigningMode = 'development') {
|
|
110
120
|
const normalized = normalizeBundleID(bundleID);
|
|
111
121
|
if (!normalized) return [];
|
|
112
122
|
const all = await getAllSigningAssets();
|
|
113
|
-
return all.filter((asset) => asset.bundleID === normalized);
|
|
123
|
+
return all.filter((asset) => asset.bundleID === normalized && (asset.signingMode ?? 'development') === signingMode);
|
|
114
124
|
}
|
|
115
125
|
|
|
116
126
|
export function profileContainsDevice(profile: ProvisioningProfileInfo, deviceUDID?: string) {
|
|
@@ -174,6 +184,7 @@ export function parseProvisioningProfileBytes(bytes: Uint8Array) {
|
|
|
174
184
|
applicationIdentifier,
|
|
175
185
|
bundleID,
|
|
176
186
|
provisionedDevices: stringArrayValue(value.ProvisionedDevices),
|
|
187
|
+
getTaskAllow: booleanValue(entitlements['get-task-allow']),
|
|
177
188
|
expirationDate: stringValue(value.ExpirationDate),
|
|
178
189
|
} satisfies ProvisioningProfileInfo;
|
|
179
190
|
}
|
|
@@ -193,8 +204,8 @@ async function getAllSigningAssets() {
|
|
|
193
204
|
);
|
|
194
205
|
}
|
|
195
206
|
|
|
196
|
-
function signingAssetID(deviceUDID: string, bundleID: string) {
|
|
197
|
-
return `${deviceUDID}:${bundleID}`;
|
|
207
|
+
function signingAssetID(deviceUDID: string, bundleID: string, signingMode: DeviceInstallSigningMode) {
|
|
208
|
+
return `${signingMode}:${deviceUDID}:${bundleID}`;
|
|
198
209
|
}
|
|
199
210
|
|
|
200
211
|
function bundleIDFromApplicationIdentifier(applicationIdentifier?: string) {
|
|
@@ -237,6 +248,10 @@ function stringArrayValue(value: unknown) {
|
|
|
237
248
|
return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
|
|
238
249
|
}
|
|
239
250
|
|
|
251
|
+
function booleanValue(value: unknown) {
|
|
252
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
240
255
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
241
256
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
242
257
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export type DeviceInstallLog = (message: string, detail?: string) => void;
|
|
2
2
|
|
|
3
|
+
export type DeviceInstallSigningMode = 'development' | 'adhoc';
|
|
4
|
+
|
|
3
5
|
export type DeviceInstallStep = 'signing' | 'connect' | 'build' | 'install';
|
|
4
6
|
|
|
5
7
|
export type DeviceInstallStepStatus = 'idle' | 'active' | 'complete' | 'error';
|
|
@@ -44,6 +46,7 @@ export type ProvisioningProfileInfo = {
|
|
|
44
46
|
applicationIdentifier?: string;
|
|
45
47
|
bundleID?: string;
|
|
46
48
|
provisionedDevices: string[];
|
|
49
|
+
getTaskAllow?: boolean;
|
|
47
50
|
expirationDate?: string;
|
|
48
51
|
};
|
|
49
52
|
|
|
@@ -51,11 +54,12 @@ export type StoredSigningAssets = {
|
|
|
51
54
|
id: string;
|
|
52
55
|
deviceUDID?: string;
|
|
53
56
|
teamID?: string;
|
|
57
|
+
signingMode?: DeviceInstallSigningMode;
|
|
54
58
|
bundleID: string;
|
|
55
59
|
certificateID?: string;
|
|
56
60
|
certificateP12Base64: string;
|
|
57
61
|
certificateFileName?: string;
|
|
58
|
-
certificatePassword
|
|
62
|
+
certificatePassword?: string;
|
|
59
63
|
provisioningProfileBase64: string;
|
|
60
64
|
profileFileName?: string;
|
|
61
65
|
profile: ProvisioningProfileInfo;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export {
|
|
2
|
+
fetchLimbuildInfo,
|
|
3
|
+
getIOSOTAInstall,
|
|
4
|
+
startSignedDeviceBuild,
|
|
5
|
+
watchBuildLogEvents,
|
|
6
|
+
type BuildLogEventsOptions,
|
|
7
|
+
type GetIOSOTAInstallOptions,
|
|
8
|
+
type IOSOTAInstall,
|
|
9
|
+
type LimbuildInfo,
|
|
10
|
+
type StartSignedDeviceBuildOptions,
|
|
11
|
+
} from '../core/device-install/operations/limbuild-client';
|
|
12
|
+
export {
|
|
13
|
+
getLatestSigningAssets,
|
|
14
|
+
getLatestSigningAssetsWithCertificate,
|
|
15
|
+
getReusableAppleSigningAssets,
|
|
16
|
+
getSigningAssets,
|
|
17
|
+
parseProvisioningProfile,
|
|
18
|
+
parseProvisioningProfileBase64,
|
|
19
|
+
parseProvisioningProfileBytes,
|
|
20
|
+
profileContainsDevice,
|
|
21
|
+
profileMatchesBundleID,
|
|
22
|
+
putAppleGeneratedSigningAssets,
|
|
23
|
+
putSigningAssets,
|
|
24
|
+
type AppleSigningAssetCacheInput,
|
|
25
|
+
type PutAppleGeneratedSigningAssetsInput,
|
|
26
|
+
} from '../core/device-install';
|
|
27
|
+
export {
|
|
28
|
+
fileToBase64,
|
|
29
|
+
importSigningAssetsFromFiles,
|
|
30
|
+
validateProvisioningProfileForInstall,
|
|
31
|
+
validateSigningAssetsForInstall,
|
|
32
|
+
type ImportSigningAssetsFromFilesInput,
|
|
33
|
+
type ValidateSigningAssetsOptions,
|
|
34
|
+
} from './signing';
|
|
35
|
+
export type {
|
|
36
|
+
BuildLogLine,
|
|
37
|
+
DeviceInstallBuildStatus,
|
|
38
|
+
DeviceInstallSigningMode,
|
|
39
|
+
ProvisioningProfileInfo,
|
|
40
|
+
PutSigningAssetsInput,
|
|
41
|
+
StoredSigningAssets,
|
|
42
|
+
} from '../core/device-install/types';
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
getIOSOTAInstall,
|
|
4
|
+
startSignedDeviceBuild,
|
|
5
|
+
watchBuildLogEvents,
|
|
6
|
+
type BuildLogLine,
|
|
7
|
+
type DeviceInstallBuildStatus,
|
|
8
|
+
type IOSOTAInstall,
|
|
9
|
+
type StoredSigningAssets,
|
|
10
|
+
} from './index';
|
|
11
|
+
|
|
12
|
+
export type UseDeviceBuildOptions = {
|
|
13
|
+
apiUrl?: string;
|
|
14
|
+
token?: string;
|
|
15
|
+
signingAssets?: StoredSigningAssets;
|
|
16
|
+
loadOTAInstall?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type StartDeviceBuildInput = {
|
|
20
|
+
signingAssets?: StoredSigningAssets;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type UseDeviceBuildResult = {
|
|
24
|
+
status: DeviceInstallBuildStatus;
|
|
25
|
+
logs: BuildLogLine[];
|
|
26
|
+
execId?: string;
|
|
27
|
+
otaInstall?: IOSOTAInstall;
|
|
28
|
+
error?: string;
|
|
29
|
+
startBuild: (input?: StartDeviceBuildInput) => Promise<string | undefined>;
|
|
30
|
+
reset: () => void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function useDeviceBuild({
|
|
34
|
+
apiUrl,
|
|
35
|
+
token,
|
|
36
|
+
signingAssets,
|
|
37
|
+
loadOTAInstall = false,
|
|
38
|
+
}: UseDeviceBuildOptions): UseDeviceBuildResult {
|
|
39
|
+
const [status, setStatus] = useState<DeviceInstallBuildStatus>('idle');
|
|
40
|
+
const [logs, setLogs] = useState<BuildLogLine[]>([]);
|
|
41
|
+
const [execId, setExecId] = useState<string | undefined>();
|
|
42
|
+
const [otaInstall, setOTAInstall] = useState<IOSOTAInstall | undefined>();
|
|
43
|
+
const [error, setError] = useState<string | undefined>();
|
|
44
|
+
const stopWatcherRef = useRef<(() => void) | undefined>(undefined);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
return () => {
|
|
48
|
+
stopWatcherRef.current?.();
|
|
49
|
+
stopWatcherRef.current = undefined;
|
|
50
|
+
};
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const reset = useCallback(() => {
|
|
54
|
+
stopWatcherRef.current?.();
|
|
55
|
+
stopWatcherRef.current = undefined;
|
|
56
|
+
setStatus('idle');
|
|
57
|
+
setLogs([]);
|
|
58
|
+
setExecId(undefined);
|
|
59
|
+
setOTAInstall(undefined);
|
|
60
|
+
setError(undefined);
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
const startBuild = useCallback(
|
|
64
|
+
async (input: StartDeviceBuildInput = {}) => {
|
|
65
|
+
if (!apiUrl) {
|
|
66
|
+
throw new Error('apiUrl is required to start a device build.');
|
|
67
|
+
}
|
|
68
|
+
const activeSigningAssets = input.signingAssets ?? signingAssets;
|
|
69
|
+
if (!activeSigningAssets) {
|
|
70
|
+
throw new Error('Signing assets are required to start a device build.');
|
|
71
|
+
}
|
|
72
|
+
stopWatcherRef.current?.();
|
|
73
|
+
setStatus('queued');
|
|
74
|
+
setLogs([]);
|
|
75
|
+
setExecId(undefined);
|
|
76
|
+
setOTAInstall(undefined);
|
|
77
|
+
setError(undefined);
|
|
78
|
+
try {
|
|
79
|
+
const result = await startSignedDeviceBuild({
|
|
80
|
+
limbuildApiUrl: apiUrl,
|
|
81
|
+
token,
|
|
82
|
+
certificateP12Base64: activeSigningAssets.certificateP12Base64,
|
|
83
|
+
certificatePassword: activeSigningAssets.certificatePassword,
|
|
84
|
+
provisioningProfileBase64: activeSigningAssets.provisioningProfileBase64,
|
|
85
|
+
});
|
|
86
|
+
if (!result.execId) {
|
|
87
|
+
throw new Error('Build request did not return an exec ID.');
|
|
88
|
+
}
|
|
89
|
+
setExecId(result.execId);
|
|
90
|
+
stopWatcherRef.current = watchBuildLogEvents({
|
|
91
|
+
limbuildApiUrl: apiUrl,
|
|
92
|
+
token,
|
|
93
|
+
execId: result.execId,
|
|
94
|
+
onLine: (line) => setLogs((current) => [...current, line]),
|
|
95
|
+
onStatus: (nextStatus) => {
|
|
96
|
+
setStatus(nextStatus);
|
|
97
|
+
if (nextStatus === 'succeeded' && loadOTAInstall) {
|
|
98
|
+
void getIOSOTAInstall({ limbuildApiUrl: apiUrl, token, execId: result.execId! })
|
|
99
|
+
.then(setOTAInstall)
|
|
100
|
+
.catch((caught) => setError(errorMessage(caught)));
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
onError: (caught) => setError(caught.message),
|
|
104
|
+
});
|
|
105
|
+
return result.execId;
|
|
106
|
+
} catch (caught) {
|
|
107
|
+
setStatus('failed');
|
|
108
|
+
setError(errorMessage(caught));
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
[apiUrl, loadOTAInstall, signingAssets, token],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
status,
|
|
117
|
+
logs,
|
|
118
|
+
execId,
|
|
119
|
+
otaInstall,
|
|
120
|
+
error,
|
|
121
|
+
startBuild,
|
|
122
|
+
reset,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function errorMessage(error: unknown) {
|
|
127
|
+
return error instanceof Error ? error.message : String(error);
|
|
128
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseProvisioningProfile,
|
|
3
|
+
profileContainsDevice,
|
|
4
|
+
profileMatchesBundleID,
|
|
5
|
+
putSigningAssets,
|
|
6
|
+
} from '../core/device-install/storage';
|
|
7
|
+
import type { DeviceInstallSigningMode, StoredSigningAssets } from '../core/device-install/types';
|
|
8
|
+
|
|
9
|
+
export type ImportSigningAssetsFromFilesInput = {
|
|
10
|
+
certificateFile: File;
|
|
11
|
+
provisioningProfileFile: File;
|
|
12
|
+
certificatePassword?: string;
|
|
13
|
+
bundleId?: string;
|
|
14
|
+
deviceUDID?: string;
|
|
15
|
+
teamId?: string;
|
|
16
|
+
signingMode?: DeviceInstallSigningMode;
|
|
17
|
+
certificateId?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ValidateSigningAssetsOptions = {
|
|
21
|
+
bundleId?: string;
|
|
22
|
+
deviceUDID?: string;
|
|
23
|
+
signingMode?: DeviceInstallSigningMode;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export async function importSigningAssetsFromFiles({
|
|
27
|
+
certificateFile,
|
|
28
|
+
provisioningProfileFile,
|
|
29
|
+
certificatePassword,
|
|
30
|
+
bundleId,
|
|
31
|
+
deviceUDID,
|
|
32
|
+
teamId,
|
|
33
|
+
signingMode = 'development',
|
|
34
|
+
certificateId,
|
|
35
|
+
}: ImportSigningAssetsFromFilesInput) {
|
|
36
|
+
const [certificateP12Base64, provisioningProfileBase64, profile] = await Promise.all([
|
|
37
|
+
fileToBase64(certificateFile),
|
|
38
|
+
fileToBase64(provisioningProfileFile),
|
|
39
|
+
parseProvisioningProfile(provisioningProfileFile),
|
|
40
|
+
]);
|
|
41
|
+
validateProvisioningProfileForInstall({ profile, bundleId, deviceUDID, signingMode });
|
|
42
|
+
return putSigningAssets({
|
|
43
|
+
bundleID: bundleId ?? profile.bundleID ?? profile.applicationIdentifier ?? provisioningProfileFile.name,
|
|
44
|
+
deviceUDID,
|
|
45
|
+
teamID: teamId ?? profile.teamID,
|
|
46
|
+
signingMode,
|
|
47
|
+
certificateID: certificateId,
|
|
48
|
+
certificateP12Base64,
|
|
49
|
+
certificateFileName: certificateFile.name,
|
|
50
|
+
certificatePassword: certificatePassword || undefined,
|
|
51
|
+
provisioningProfileBase64,
|
|
52
|
+
profileFileName: provisioningProfileFile.name,
|
|
53
|
+
profile,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function validateSigningAssetsForInstall(
|
|
58
|
+
assets: StoredSigningAssets,
|
|
59
|
+
options: ValidateSigningAssetsOptions = {},
|
|
60
|
+
) {
|
|
61
|
+
validateProvisioningProfileForInstall({
|
|
62
|
+
profile: assets.profile,
|
|
63
|
+
bundleId: options.bundleId,
|
|
64
|
+
deviceUDID: options.deviceUDID,
|
|
65
|
+
signingMode: options.signingMode ?? assets.signingMode,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function validateProvisioningProfileForInstall({
|
|
70
|
+
profile,
|
|
71
|
+
bundleId,
|
|
72
|
+
deviceUDID,
|
|
73
|
+
signingMode,
|
|
74
|
+
}: Pick<StoredSigningAssets, 'profile'> & ValidateSigningAssetsOptions) {
|
|
75
|
+
if (bundleId && !profileMatchesBundleID(profile, bundleId)) {
|
|
76
|
+
throw new Error(`Provisioning profile does not match bundle ID ${bundleId}.`);
|
|
77
|
+
}
|
|
78
|
+
if (deviceUDID && !profileContainsDevice(profile, deviceUDID)) {
|
|
79
|
+
throw new Error('Provisioning profile does not include the selected iPhone.');
|
|
80
|
+
}
|
|
81
|
+
if (signingMode === 'adhoc' && profile.getTaskAllow) {
|
|
82
|
+
throw new Error('Ad Hoc mode requires an Ad Hoc provisioning profile, not a development profile.');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function fileToBase64(file: File) {
|
|
87
|
+
const buffer = await file.arrayBuffer();
|
|
88
|
+
let binary = '';
|
|
89
|
+
const bytes = new Uint8Array(buffer);
|
|
90
|
+
for (const byte of bytes) {
|
|
91
|
+
binary += String.fromCharCode(byte);
|
|
92
|
+
}
|
|
93
|
+
return btoa(binary);
|
|
94
|
+
}
|
|
@@ -1,3 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
requestUSBAccess as requestCoreUSBAccess,
|
|
3
|
+
startInstallRelay,
|
|
4
|
+
startPairingRelay,
|
|
5
|
+
type RequestUSBAccessOptions as CoreRequestUSBAccessOptions,
|
|
6
|
+
type StartInstallRelayOptions,
|
|
7
|
+
type StartPairingRelayOptions,
|
|
8
|
+
} from '../core/device-install/operations';
|
|
9
|
+
import type { DeviceInstallLog } from '../core/device-install/types';
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
closeDeviceRelayTarget,
|
|
13
|
+
deviceRelayWebSocketUrl,
|
|
14
|
+
startInstallRelay as startDeviceInstallRelay,
|
|
15
|
+
startPairingRelay as startDevicePairingRelay,
|
|
16
|
+
type DeviceRelayTarget,
|
|
17
|
+
type StartInstallRelayOptions,
|
|
18
|
+
type StartPairingRelayOptions,
|
|
19
|
+
} from '../core/device-install/operations';
|
|
20
|
+
export {
|
|
21
|
+
getPairRecord,
|
|
22
|
+
normalizeUDID,
|
|
23
|
+
putPairRecord,
|
|
24
|
+
} from '../core/device-install/storage';
|
|
25
|
+
export type {
|
|
26
|
+
DeviceHello,
|
|
27
|
+
DeviceInstallLog,
|
|
28
|
+
PairRecordPayload,
|
|
29
|
+
StoredPairRecord,
|
|
30
|
+
} from '../core/device-install/types';
|
|
31
|
+
export { RelayClient } from '../core/device-install/operations';
|
|
32
|
+
|
|
33
|
+
export type RequestUSBAccessOptions = Partial<CoreRequestUSBAccessOptions>;
|
|
34
|
+
|
|
35
|
+
export async function requestUSBAccess(options: RequestUSBAccessOptions = {}) {
|
|
36
|
+
return requestCoreUSBAccess({ log: options.log ?? noopLog });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function pairDevice(options: Omit<StartPairingRelayOptions, 'log'> & { log?: DeviceInstallLog }) {
|
|
40
|
+
return startPairingRelay({ ...options, log: options.log ?? noopLog });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function startDeviceInstall(options: Omit<StartInstallRelayOptions, 'log'> & { log?: DeviceInstallLog }) {
|
|
44
|
+
return startInstallRelay({ ...options, log: options.log ?? noopLog });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function noopLog() {
|
|
48
|
+
// Intentionally empty. Consumers can pass a logger for progress messages.
|
|
49
|
+
}
|