@limrun/ui 0.9.0-rc.9 → 0.13.3
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 +486 -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-Cx9eW-bm.mjs +205 -0
- package/dist/browser-storage-jJaIht4K.js +1 -0
- package/dist/client-ASLtHFLh.js +1 -0
- package/dist/client-CGdh8yoJ.mjs +234 -0
- package/dist/components/device-install/index.d.ts +1 -2
- package/dist/core/device-install/apple/provisioning.d.ts +58 -7
- 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 +68 -0
- package/dist/device-build/signing.d.ts +20 -0
- package/dist/device-install/demo/demo.d.ts +0 -0
- package/dist/device-install/index.cjs +1 -1
- package/dist/device-install/index.d.ts +18 -3
- package/dist/device-install/index.js +583 -76
- package/dist/device-install/react.cjs +1 -1
- package/dist/device-install/react.d.ts +23 -1
- package/dist/device-install/react.js +98 -2
- package/dist/hooks/index.d.ts +1 -1
- package/dist/index-CMeQfhYy.js +22 -0
- package/dist/index-DFdCvi2_.mjs +11543 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +502 -495
- package/dist/limbuild-client-CgEncrgx.js +1 -0
- package/dist/limbuild-client-D7U7w342.mjs +81 -0
- package/dist/provisioning-DKbkBO-p.mjs +239 -0
- package/dist/provisioning-Va0PH4Kk.js +1 -0
- package/package.json +21 -1
- package/src/app-store-relay/index.test.ts +70 -0
- package/src/app-store-relay/index.ts +514 -0
- package/src/app-store-relay/react.ts +129 -0
- package/src/components/device-install/index.ts +1 -2
- package/src/core/device-install/apple/client.ts +29 -22
- package/src/core/device-install/apple/crypto.ts +9 -6
- package/src/core/device-install/apple/gsa-srp.ts +3 -7
- package/src/core/device-install/apple/provisioning.test.ts +84 -0
- package/src/core/device-install/apple/provisioning.ts +98 -16
- package/src/core/device-install/apple/relay.ts +5 -1
- package/src/core/device-install/operations/limbuild-client.ts +41 -4
- package/src/core/device-install/operations/operations.ts +3 -1
- package/src/core/device-install/operations/relay-client.ts +57 -27
- package/src/core/device-install/operations/usbmux.ts +3 -3
- package/src/core/device-install/operations/webusb.ts +9 -4
- package/src/core/device-install/storage/browser-storage.ts +43 -14
- package/src/core/device-install/types.ts +6 -8
- package/src/device-build/index.ts +42 -0
- package/src/device-build/react.ts +131 -0
- package/src/device-build/signing.ts +94 -0
- package/src/device-install/README.md +423 -0
- package/src/device-install/demo/demo.css +493 -0
- package/src/device-install/demo/demo.tsx +1069 -0
- package/src/device-install/demo/index.html +12 -0
- package/src/device-install/index.ts +49 -3
- package/src/device-install/react.ts +189 -1
- package/src/hooks/index.ts +1 -1
- package/src/index.ts +0 -3
- 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
|
@@ -39,7 +39,9 @@ export function findUsbmuxCandidates(device: USBDevice): UsbmuxCandidate[] {
|
|
|
39
39
|
return candidates;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function findConfigurationUsbmuxCandidates(
|
|
42
|
+
function findConfigurationUsbmuxCandidates(
|
|
43
|
+
configuration: USBDevice['configurations'][number],
|
|
44
|
+
): UsbmuxCandidate[] {
|
|
43
45
|
const candidates: UsbmuxCandidate[] = [];
|
|
44
46
|
for (const usbInterface of configuration.interfaces) {
|
|
45
47
|
for (const alternate of usbInterface.alternates) {
|
|
@@ -68,9 +70,12 @@ export async function claimUsbmux(device: USBDevice, candidate: UsbmuxCandidate)
|
|
|
68
70
|
await device.selectConfiguration(candidate.configurationValue);
|
|
69
71
|
}
|
|
70
72
|
await device.claimInterface(candidate.interfaceNumber);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
// Always select the alternate (even alt 0). On macOS Chrome the
|
|
74
|
+
// interface's endpoints are not treated as active until the alternate
|
|
75
|
+
// is explicitly selected, which otherwise surfaces as
|
|
76
|
+
// "endpoint is not part of a claimed and selected alternate interface"
|
|
77
|
+
// on the first transferIn/transferOut.
|
|
78
|
+
await device.selectAlternateInterface(candidate.interfaceNumber, candidate.alternateSetting);
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
export function getBulkEndpoints(candidate: UsbmuxCandidate) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
DeviceInstallSigningMode,
|
|
2
3
|
ProvisioningProfileInfo,
|
|
3
4
|
PutSigningAssetsInput,
|
|
4
5
|
StoredPairRecord,
|
|
@@ -49,35 +50,43 @@ 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((left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime())[0];
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
export async function getLatestSigningAssetsWithCertificate(
|
|
79
|
+
export async function getLatestSigningAssetsWithCertificate(
|
|
80
|
+
teamID?: string,
|
|
81
|
+
signingMode: DeviceInstallSigningMode = 'development',
|
|
82
|
+
) {
|
|
77
83
|
const all = await getAllSigningAssets();
|
|
78
84
|
return all
|
|
79
85
|
.filter((asset) => {
|
|
80
|
-
if (!asset.certificateID || !asset.certificateP12Base64
|
|
86
|
+
if (!asset.certificateID || !asset.certificateP12Base64) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if ((asset.signingMode ?? 'development') !== signingMode) {
|
|
81
90
|
return false;
|
|
82
91
|
}
|
|
83
92
|
return !teamID || !asset.teamID || asset.teamID === teamID;
|
|
@@ -91,11 +100,13 @@ export async function putSigningAssets(input: PutSigningAssetsInput) {
|
|
|
91
100
|
throw new Error('Cannot store signing assets without a bundle ID.');
|
|
92
101
|
}
|
|
93
102
|
const normalizedUDID = normalizeUDID(input.deviceUDID);
|
|
94
|
-
const
|
|
103
|
+
const signingMode = input.signingMode ?? 'development';
|
|
104
|
+
const id = signingAssetID('bundle', normalizedBundleID, signingMode);
|
|
95
105
|
const stored: StoredSigningAssets = {
|
|
96
106
|
...input,
|
|
97
107
|
id,
|
|
98
108
|
deviceUDID: normalizedUDID || undefined,
|
|
109
|
+
signingMode,
|
|
99
110
|
bundleID: normalizedBundleID,
|
|
100
111
|
updatedAt: new Date().toISOString(),
|
|
101
112
|
};
|
|
@@ -106,11 +117,16 @@ export async function putSigningAssets(input: PutSigningAssetsInput) {
|
|
|
106
117
|
return stored;
|
|
107
118
|
}
|
|
108
119
|
|
|
109
|
-
export async function findSigningAssetsForBundle(
|
|
120
|
+
export async function findSigningAssetsForBundle(
|
|
121
|
+
bundleID?: string,
|
|
122
|
+
signingMode: DeviceInstallSigningMode = 'development',
|
|
123
|
+
) {
|
|
110
124
|
const normalized = normalizeBundleID(bundleID);
|
|
111
125
|
if (!normalized) return [];
|
|
112
126
|
const all = await getAllSigningAssets();
|
|
113
|
-
return all.filter(
|
|
127
|
+
return all.filter(
|
|
128
|
+
(asset) => asset.bundleID === normalized && (asset.signingMode ?? 'development') === signingMode,
|
|
129
|
+
);
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
export function profileContainsDevice(profile: ProvisioningProfileInfo, deviceUDID?: string) {
|
|
@@ -150,6 +166,14 @@ export function parseProvisioningProfileBytes(bytes: Uint8Array) {
|
|
|
150
166
|
throw new Error('Provisioning profile plist not found.');
|
|
151
167
|
}
|
|
152
168
|
const xml = text.slice(start, end + '</plist>'.length);
|
|
169
|
+
// Defensively reject entity declarations before parsing: they are the vector
|
|
170
|
+
// for XML entity-expansion (billion-laughs) attacks and never appear in a
|
|
171
|
+
// real provisioning profile. The standard Apple `<!DOCTYPE plist PUBLIC ...>`
|
|
172
|
+
// (an external DTD reference with no internal entity subset) is benign —
|
|
173
|
+
// browsers' DOMParser does not fetch it — so it is intentionally allowed.
|
|
174
|
+
if (/<!ENTITY/i.test(xml)) {
|
|
175
|
+
throw new Error('Provisioning profile plist contains a disallowed ENTITY declaration.');
|
|
176
|
+
}
|
|
153
177
|
const doc = new DOMParser().parseFromString(xml, 'application/xml');
|
|
154
178
|
if (doc.querySelector('parsererror')) {
|
|
155
179
|
throw new Error('Provisioning profile plist could not be parsed.');
|
|
@@ -174,6 +198,7 @@ export function parseProvisioningProfileBytes(bytes: Uint8Array) {
|
|
|
174
198
|
applicationIdentifier,
|
|
175
199
|
bundleID,
|
|
176
200
|
provisionedDevices: stringArrayValue(value.ProvisionedDevices),
|
|
201
|
+
getTaskAllow: booleanValue(entitlements['get-task-allow']),
|
|
177
202
|
expirationDate: stringValue(value.ExpirationDate),
|
|
178
203
|
} satisfies ProvisioningProfileInfo;
|
|
179
204
|
}
|
|
@@ -193,8 +218,8 @@ async function getAllSigningAssets() {
|
|
|
193
218
|
);
|
|
194
219
|
}
|
|
195
220
|
|
|
196
|
-
function signingAssetID(deviceUDID: string, bundleID: string) {
|
|
197
|
-
return `${deviceUDID}:${bundleID}`;
|
|
221
|
+
function signingAssetID(deviceUDID: string, bundleID: string, signingMode: DeviceInstallSigningMode) {
|
|
222
|
+
return `${signingMode}:${deviceUDID}:${bundleID}`;
|
|
198
223
|
}
|
|
199
224
|
|
|
200
225
|
function bundleIDFromApplicationIdentifier(applicationIdentifier?: string) {
|
|
@@ -237,6 +262,10 @@ function stringArrayValue(value: unknown) {
|
|
|
237
262
|
return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
|
|
238
263
|
}
|
|
239
264
|
|
|
265
|
+
function booleanValue(value: unknown) {
|
|
266
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
267
|
+
}
|
|
268
|
+
|
|
240
269
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
241
270
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
242
271
|
}
|
|
@@ -1,18 +1,14 @@
|
|
|
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';
|
|
6
8
|
|
|
7
9
|
export type DeviceInstallBusyAction = 'signing' | 'usb' | 'pair' | 'build' | 'install';
|
|
8
10
|
|
|
9
|
-
export type DeviceInstallBuildStatus =
|
|
10
|
-
| 'idle'
|
|
11
|
-
| 'queued'
|
|
12
|
-
| 'running'
|
|
13
|
-
| 'succeeded'
|
|
14
|
-
| 'failed'
|
|
15
|
-
| 'cancelled';
|
|
11
|
+
export type DeviceInstallBuildStatus = 'idle' | 'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled';
|
|
16
12
|
|
|
17
13
|
export type BuildLogLine = {
|
|
18
14
|
type: 'command' | 'stdout' | 'stderr' | 'meta';
|
|
@@ -44,6 +40,7 @@ export type ProvisioningProfileInfo = {
|
|
|
44
40
|
applicationIdentifier?: string;
|
|
45
41
|
bundleID?: string;
|
|
46
42
|
provisionedDevices: string[];
|
|
43
|
+
getTaskAllow?: boolean;
|
|
47
44
|
expirationDate?: string;
|
|
48
45
|
};
|
|
49
46
|
|
|
@@ -51,11 +48,12 @@ export type StoredSigningAssets = {
|
|
|
51
48
|
id: string;
|
|
52
49
|
deviceUDID?: string;
|
|
53
50
|
teamID?: string;
|
|
51
|
+
signingMode?: DeviceInstallSigningMode;
|
|
54
52
|
bundleID: string;
|
|
55
53
|
certificateID?: string;
|
|
56
54
|
certificateP12Base64: string;
|
|
57
55
|
certificateFileName?: string;
|
|
58
|
-
certificatePassword
|
|
56
|
+
certificatePassword?: string;
|
|
59
57
|
provisioningProfileBase64: string;
|
|
60
58
|
profileFileName?: string;
|
|
61
59
|
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,131 @@
|
|
|
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) => {
|
|
104
|
+
setStatus('failed');
|
|
105
|
+
setError(caught.message);
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
return result.execId;
|
|
109
|
+
} catch (caught) {
|
|
110
|
+
setStatus('failed');
|
|
111
|
+
setError(errorMessage(caught));
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
[apiUrl, loadOTAInstall, signingAssets, token],
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
status,
|
|
120
|
+
logs,
|
|
121
|
+
execId,
|
|
122
|
+
otaInstall,
|
|
123
|
+
error,
|
|
124
|
+
startBuild,
|
|
125
|
+
reset,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function errorMessage(error: unknown) {
|
|
130
|
+
return error instanceof Error ? error.message : String(error);
|
|
131
|
+
}
|
|
@@ -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
|
+
}
|