@limrun/ui 0.9.0-rc.1 → 0.9.0-rc.11
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/components/inspect-overlay.d.ts +33 -0
- package/dist/components/remote-control.d.ts +86 -0
- package/dist/core/ax-fetcher.d.ts +49 -0
- package/dist/core/ax-tree.d.ts +99 -0
- 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 +19 -0
- package/dist/core/device-install/types.d.ts +2 -2
- package/dist/device-install/index.cjs +1 -9
- package/dist/device-install/index.js +76 -210
- package/dist/device-install/react.cjs +1 -1
- package/dist/device-install/react.js +1 -1
- package/dist/device-install-dialog-CjH25hnN.js +2 -0
- package/dist/device-install-dialog-W5Xv9kWL.mjs +443 -0
- package/dist/device-install-dialog.css +1 -1
- package/dist/hooks/use-device-install.d.ts +21 -3
- package/dist/index.cjs +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1485 -778
- package/dist/use-device-install-Y1u6vIBB.js +31 -0
- package/dist/use-device-install-sDVvby1V.mjs +13627 -0
- package/package.json +7 -3
- package/src/components/device-install/device-install-dialog.css +82 -1
- package/src/components/device-install/device-install-dialog.tsx +319 -187
- package/src/components/inspect-overlay.css +223 -0
- package/src/components/inspect-overlay.tsx +437 -0
- package/src/components/remote-control.tsx +547 -9
- package/src/core/ax-fetcher.test.ts +418 -0
- package/src/core/ax-fetcher.ts +377 -0
- package/src/core/ax-tree.test.ts +491 -0
- package/src/core/ax-tree.ts +416 -0
- 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 +26 -1
- package/src/core/device-install/types.ts +2 -2
- package/src/demo.tsx +93 -10
- package/src/hooks/use-device-install.ts +766 -67
- package/src/index.ts +19 -1
- package/vitest.config.ts +23 -0
- 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
|
@@ -18,13 +18,39 @@ export type AppleDeveloperPortalTeam = {
|
|
|
18
18
|
subType?: string;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export type AppleDeveloperPortalDevice = {
|
|
22
|
+
deviceId?: string;
|
|
23
|
+
name?: string;
|
|
24
|
+
deviceNumber?: string;
|
|
25
|
+
deviceClass?: string;
|
|
26
|
+
model?: string;
|
|
27
|
+
status?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type AppleDeveloperPortalAppID = {
|
|
31
|
+
appId?: string;
|
|
32
|
+
appIdId?: string;
|
|
33
|
+
identifier?: string;
|
|
34
|
+
bundleId?: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
prefix?: string;
|
|
37
|
+
platform?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
21
40
|
export type AppleDeveloperPortalResponse = {
|
|
41
|
+
resultCode?: number;
|
|
42
|
+
resultString?: string;
|
|
43
|
+
userString?: string;
|
|
22
44
|
teams?: AppleDeveloperPortalTeam[];
|
|
23
45
|
provider?: AppleDeveloperPortalTeam;
|
|
24
46
|
availableProviders?: AppleDeveloperPortalTeam[];
|
|
25
|
-
appIds?:
|
|
26
|
-
devices?:
|
|
47
|
+
appIds?: AppleDeveloperPortalAppID[];
|
|
48
|
+
devices?: AppleDeveloperPortalDevice[];
|
|
27
49
|
certRequests?: Array<Record<string, unknown>>;
|
|
50
|
+
certRequest?: Record<string, unknown>;
|
|
51
|
+
appId?: Record<string, unknown>;
|
|
52
|
+
device?: Record<string, unknown>;
|
|
53
|
+
provisioningProfile?: Record<string, unknown>;
|
|
28
54
|
provisioningProfiles?: Array<Record<string, unknown>>;
|
|
29
55
|
};
|
|
30
56
|
|
|
@@ -60,37 +86,49 @@ export function listTeamsRequest(): AppleProvisioningRequest {
|
|
|
60
86
|
}
|
|
61
87
|
|
|
62
88
|
export function findBundleIDRequest({ bundleID, teamID = '' }: Pick<AppleProvisioningContext, 'bundleID' | 'teamID'>) {
|
|
63
|
-
|
|
89
|
+
void bundleID;
|
|
90
|
+
return pagedRequest('/account/ios/identifiers/listAppIds.action', teamID, { sort: 'name=asc' });
|
|
64
91
|
}
|
|
65
92
|
|
|
66
93
|
export function findDeviceRequest({ deviceUDID, teamID = '' }: Pick<AppleProvisioningContext, 'deviceUDID' | 'teamID'>) {
|
|
67
|
-
|
|
94
|
+
void deviceUDID;
|
|
95
|
+
return pagedRequest('/account/ios/device/listDevices.action', teamID, {
|
|
96
|
+
sort: 'name=asc',
|
|
97
|
+
includeRemovedDevices: false,
|
|
98
|
+
});
|
|
68
99
|
}
|
|
69
100
|
|
|
70
101
|
export function findDevelopmentCertificatesRequest(teamID = '') {
|
|
71
|
-
return pagedRequest('/account/ios/certificate/listCertRequests.action', teamID
|
|
102
|
+
return pagedRequest('/account/ios/certificate/listCertRequests.action', teamID, {
|
|
103
|
+
sort: 'name=asc',
|
|
104
|
+
types: '83Q87W3TGH,5QPB9NHCEI',
|
|
105
|
+
});
|
|
72
106
|
}
|
|
73
107
|
|
|
74
108
|
export function findDevelopmentProfilesRequest({
|
|
75
109
|
bundleID,
|
|
76
110
|
teamID = '',
|
|
77
111
|
}: Pick<AppleProvisioningContext, 'bundleID' | 'teamID'>) {
|
|
78
|
-
return pagedRequest('/account/ios/profile/listProvisioningProfiles.action', teamID, {
|
|
112
|
+
return pagedRequest('/account/ios/profile/listProvisioningProfiles.action', teamID, {
|
|
113
|
+
search: bundleID,
|
|
114
|
+
sort: 'name=asc',
|
|
115
|
+
});
|
|
79
116
|
}
|
|
80
117
|
|
|
81
118
|
export function registerDeviceRequest({
|
|
82
119
|
deviceUDID,
|
|
83
120
|
teamID = '',
|
|
84
121
|
name = 'Limrun iPhone',
|
|
85
|
-
}: AppleProvisioningContext & { name?: string }) {
|
|
122
|
+
}: Pick<AppleProvisioningContext, 'deviceUDID' | 'teamID'> & { name?: string }) {
|
|
86
123
|
return {
|
|
87
124
|
method: 'POST',
|
|
88
|
-
path: '/account/ios/device/
|
|
125
|
+
path: '/account/ios/device/addDevices.action',
|
|
89
126
|
payload: {
|
|
90
127
|
teamId: teamID,
|
|
91
|
-
name,
|
|
92
|
-
|
|
93
|
-
|
|
128
|
+
deviceNames: name,
|
|
129
|
+
deviceNumbers: normalizeUDID(deviceUDID),
|
|
130
|
+
deviceClasses: 'iphone',
|
|
131
|
+
register: 'single',
|
|
94
132
|
},
|
|
95
133
|
} satisfies AppleProvisioningRequest;
|
|
96
134
|
}
|
|
@@ -121,9 +159,10 @@ export function submitDevelopmentCSRRequest({
|
|
|
121
159
|
}) {
|
|
122
160
|
return {
|
|
123
161
|
method: 'POST',
|
|
124
|
-
path: '/account/ios/certificate/
|
|
162
|
+
path: '/account/ios/certificate/submitCertificateRequest.action',
|
|
125
163
|
payload: {
|
|
126
164
|
teamId: teamID,
|
|
165
|
+
type: '83Q87W3TGH',
|
|
127
166
|
csrContent: csrPEM,
|
|
128
167
|
},
|
|
129
168
|
} satisfies AppleProvisioningRequest;
|
|
@@ -131,11 +170,12 @@ export function submitDevelopmentCSRRequest({
|
|
|
131
170
|
|
|
132
171
|
export function downloadCertificateRequest(certificateID: string, teamID = '') {
|
|
133
172
|
return {
|
|
134
|
-
method: '
|
|
173
|
+
method: 'GET',
|
|
135
174
|
path: '/account/ios/certificate/downloadCertificateContent.action',
|
|
136
175
|
payload: {
|
|
137
176
|
teamId: teamID,
|
|
138
177
|
certificateId: certificateID,
|
|
178
|
+
type: '83Q87W3TGH',
|
|
139
179
|
},
|
|
140
180
|
} satisfies AppleProvisioningRequest;
|
|
141
181
|
}
|
|
@@ -158,11 +198,11 @@ export function createDevelopmentProfileRequest({
|
|
|
158
198
|
path: '/account/ios/profile/createProvisioningProfile.action',
|
|
159
199
|
payload: {
|
|
160
200
|
teamId: teamID,
|
|
161
|
-
|
|
201
|
+
provisioningProfileName: name ?? `Limrun ${bundleID}`,
|
|
162
202
|
certificateIds: [certificateID],
|
|
203
|
+
appIdId: appIDID,
|
|
163
204
|
deviceIds: deviceIDs,
|
|
164
|
-
|
|
165
|
-
name: name ?? `Limrun ${bundleID}`,
|
|
205
|
+
distributionType: 'limited',
|
|
166
206
|
subPlatform: 'ios',
|
|
167
207
|
},
|
|
168
208
|
} satisfies AppleProvisioningRequest;
|
|
@@ -170,8 +210,8 @@ export function createDevelopmentProfileRequest({
|
|
|
170
210
|
|
|
171
211
|
export function downloadProfileRequest(profileID: string, teamID = '') {
|
|
172
212
|
return {
|
|
173
|
-
method: '
|
|
174
|
-
path: '/account/ios/profile/downloadProfileContent
|
|
213
|
+
method: 'GET',
|
|
214
|
+
path: '/account/ios/profile/downloadProfileContent',
|
|
175
215
|
payload: {
|
|
176
216
|
teamId: teamID,
|
|
177
217
|
provisioningProfileId: profileID,
|
|
@@ -242,14 +282,17 @@ export function teamIDCandidates(body: unknown): string[] {
|
|
|
242
282
|
}
|
|
243
283
|
|
|
244
284
|
function pagedRequest(path: string, teamID: string, payload: Record<string, unknown> = {}) {
|
|
285
|
+
const basePayload: Record<string, unknown> = {
|
|
286
|
+
pageNumber: 1,
|
|
287
|
+
pageSize: 200,
|
|
288
|
+
...payload,
|
|
289
|
+
};
|
|
290
|
+
if (teamID) {
|
|
291
|
+
basePayload.teamId = teamID;
|
|
292
|
+
}
|
|
245
293
|
return {
|
|
246
294
|
method: 'POST',
|
|
247
295
|
path,
|
|
248
|
-
payload:
|
|
249
|
-
pageNumber: 1,
|
|
250
|
-
pageSize: 200,
|
|
251
|
-
teamId: teamID,
|
|
252
|
-
...payload,
|
|
253
|
-
},
|
|
296
|
+
payload: basePayload,
|
|
254
297
|
} satisfies AppleProvisioningRequest;
|
|
255
298
|
}
|
|
@@ -6,14 +6,7 @@ export type AppleRelayResponse<T = unknown> = {
|
|
|
6
6
|
headers?: Record<string, string>;
|
|
7
7
|
body?: T;
|
|
8
8
|
rawBody?: string;
|
|
9
|
-
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export type AppleRelayRequest = {
|
|
13
|
-
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
14
|
-
url: string;
|
|
15
|
-
headers?: Record<string, string>;
|
|
16
|
-
body?: BodyInit;
|
|
9
|
+
rawBodyBase64?: string;
|
|
17
10
|
};
|
|
18
11
|
|
|
19
12
|
export type AppleProvisioningRequest = {
|
|
@@ -33,11 +26,7 @@ export async function createAppleRelaySession(limbuildApiUrl: string, token?: st
|
|
|
33
26
|
return (await response.json()) as { appleSessionId: string };
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
export async function deleteAppleRelaySession(
|
|
37
|
-
limbuildApiUrl: string,
|
|
38
|
-
appleSessionId: string,
|
|
39
|
-
token?: string,
|
|
40
|
-
) {
|
|
29
|
+
export async function deleteAppleRelaySession(limbuildApiUrl: string, appleSessionId: string, token?: string) {
|
|
41
30
|
const response = await fetch(limbuildURL(limbuildApiUrl, '/apple/auth/session/delete', token), {
|
|
42
31
|
method: 'POST',
|
|
43
32
|
headers: jsonHeaders(token),
|
|
@@ -48,38 +37,17 @@ export async function deleteAppleRelaySession(
|
|
|
48
37
|
}
|
|
49
38
|
}
|
|
50
39
|
|
|
51
|
-
export async function relayAppleRequest<T = unknown>(
|
|
52
|
-
limbuildApiUrl: string,
|
|
53
|
-
appleSessionId: string,
|
|
54
|
-
request: AppleRelayRequest,
|
|
55
|
-
token?: string,
|
|
56
|
-
) {
|
|
57
|
-
const response = await fetch(appleRelayURL(limbuildApiUrl, appleSessionId, request.url, token), {
|
|
58
|
-
method: request.method ?? 'GET',
|
|
59
|
-
headers: {
|
|
60
|
-
...(request.headers ?? {}),
|
|
61
|
-
...authHeaders(token),
|
|
62
|
-
},
|
|
63
|
-
body: request.body,
|
|
64
|
-
});
|
|
65
|
-
return responseToAppleRelayResponse<T>(response);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
40
|
export async function proxySrpInit(
|
|
69
41
|
limbuildApiUrl: string,
|
|
70
42
|
appleSessionId: string,
|
|
71
43
|
payload: AppleSRPInitRequest,
|
|
72
44
|
token?: string,
|
|
73
45
|
) {
|
|
74
|
-
return
|
|
46
|
+
return postAppleProxy<AppleSRPInitResponse>(
|
|
75
47
|
limbuildApiUrl,
|
|
48
|
+
'/apple/auth/srp/init',
|
|
76
49
|
appleSessionId,
|
|
77
|
-
|
|
78
|
-
method: 'POST',
|
|
79
|
-
url: 'https://idmsa.apple.com/appleauth/auth/signin/init',
|
|
80
|
-
headers: jsonContentHeaders(),
|
|
81
|
-
body: JSON.stringify(payload),
|
|
82
|
-
},
|
|
50
|
+
payload,
|
|
83
51
|
token,
|
|
84
52
|
);
|
|
85
53
|
}
|
|
@@ -93,213 +61,161 @@ export async function proxySrpComplete(
|
|
|
93
61
|
},
|
|
94
62
|
token?: string,
|
|
95
63
|
) {
|
|
96
|
-
|
|
97
|
-
return relayAppleRequest(
|
|
98
|
-
limbuildApiUrl,
|
|
99
|
-
appleSessionId,
|
|
100
|
-
{
|
|
101
|
-
method: 'POST',
|
|
102
|
-
url: 'https://idmsa.apple.com/appleauth/auth/signin/complete?isRememberMeEnabled=false',
|
|
103
|
-
headers: {
|
|
104
|
-
...jsonContentHeaders(),
|
|
105
|
-
...(hashcash ? { 'X-Apple-HC': hashcash } : {}),
|
|
106
|
-
},
|
|
107
|
-
body: JSON.stringify(payload),
|
|
108
|
-
},
|
|
109
|
-
token,
|
|
110
|
-
);
|
|
64
|
+
return postAppleProxy(limbuildApiUrl, '/apple/auth/srp/complete', appleSessionId, payload, token);
|
|
111
65
|
}
|
|
112
66
|
|
|
113
|
-
export async function
|
|
67
|
+
export async function triggerTrustedDeviceTwoFactor(
|
|
114
68
|
limbuildApiUrl: string,
|
|
115
69
|
appleSessionId: string,
|
|
116
|
-
code: string,
|
|
117
70
|
token?: string,
|
|
118
71
|
) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
{
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
token,
|
|
129
|
-
);
|
|
72
|
+
const response = await fetch(limbuildURL(limbuildApiUrl, '/apple/auth/2fa/trigger', token), {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: jsonHeaders(token),
|
|
75
|
+
body: JSON.stringify({ appleSessionId }),
|
|
76
|
+
});
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw new Error(`Apple 2FA trigger failed: HTTP ${response.status} ${await response.text()}`);
|
|
79
|
+
}
|
|
80
|
+
return (await response.json()) as AppleRelayResponse;
|
|
130
81
|
}
|
|
131
82
|
|
|
132
|
-
export async function
|
|
83
|
+
export async function triggerPhoneTwoFactor(
|
|
133
84
|
limbuildApiUrl: string,
|
|
134
85
|
appleSessionId: string,
|
|
86
|
+
phoneNumberId: number,
|
|
87
|
+
mode = 'sms',
|
|
135
88
|
token?: string,
|
|
136
89
|
) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
);
|
|
90
|
+
const response = await fetch(limbuildURL(limbuildApiUrl, '/apple/auth/2fa/phone/trigger', token), {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: jsonHeaders(token),
|
|
93
|
+
body: JSON.stringify({ appleSessionId, phoneNumberId, mode }),
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new Error(`Apple phone 2FA trigger failed: HTTP ${response.status} ${await response.text()}`);
|
|
97
|
+
}
|
|
98
|
+
return (await response.json()) as AppleRelayResponse;
|
|
147
99
|
}
|
|
148
100
|
|
|
149
|
-
export async function
|
|
101
|
+
export async function proxyTwoFactorCode(
|
|
150
102
|
limbuildApiUrl: string,
|
|
151
103
|
appleSessionId: string,
|
|
152
|
-
|
|
104
|
+
code: string,
|
|
153
105
|
token?: string,
|
|
154
106
|
) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
{
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
Accept: 'application/json',
|
|
163
|
-
...(request.payload ? { 'Content-Type': 'application/x-www-form-urlencoded' } : {}),
|
|
164
|
-
},
|
|
165
|
-
body: request.payload ? formEncode(request.payload) : undefined,
|
|
166
|
-
},
|
|
167
|
-
token,
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function limbuildURL(limbuildApiUrl: string, path: string, token?: string) {
|
|
172
|
-
const url = new URL(path, limbuildApiUrl);
|
|
173
|
-
if (token) {
|
|
174
|
-
url.searchParams.set('token', token);
|
|
107
|
+
const response = await fetch(limbuildURL(limbuildApiUrl, '/apple/auth/2fa', token), {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: jsonHeaders(token),
|
|
110
|
+
body: JSON.stringify({ appleSessionId, code }),
|
|
111
|
+
});
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new Error(`Apple 2FA proxy failed: HTTP ${response.status} ${await response.text()}`);
|
|
175
114
|
}
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function appleRelayURL(limbuildApiUrl: string, appleSessionId: string, appleURL: string, token?: string) {
|
|
180
|
-
const url = limbuildURL(limbuildApiUrl, '/apple/relay', token);
|
|
181
|
-
url.searchParams.set('appleSessionId', appleSessionId);
|
|
182
|
-
url.searchParams.set('url', appleURL);
|
|
183
|
-
return url;
|
|
115
|
+
return (await response.json()) as AppleRelayResponse;
|
|
184
116
|
}
|
|
185
117
|
|
|
186
|
-
function
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
118
|
+
export async function proxyPhoneTwoFactorCode(
|
|
119
|
+
limbuildApiUrl: string,
|
|
120
|
+
appleSessionId: string,
|
|
121
|
+
phoneNumberId: number,
|
|
122
|
+
code: string,
|
|
123
|
+
mode = 'sms',
|
|
124
|
+
token?: string,
|
|
125
|
+
) {
|
|
126
|
+
const response = await fetch(limbuildURL(limbuildApiUrl, '/apple/auth/2fa/phone', token), {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
headers: jsonHeaders(token),
|
|
129
|
+
body: JSON.stringify({ appleSessionId, phoneNumberId, mode, code }),
|
|
130
|
+
});
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
throw new Error(`Apple phone 2FA proxy failed: HTTP ${response.status} ${await response.text()}`);
|
|
192
133
|
}
|
|
193
|
-
return
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function authHeaders(token?: string): Record<string, string> {
|
|
197
|
-
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
134
|
+
return (await response.json()) as AppleRelayResponse;
|
|
198
135
|
}
|
|
199
136
|
|
|
200
|
-
function
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
137
|
+
export async function fetchAppleAccountSession(
|
|
138
|
+
limbuildApiUrl: string,
|
|
139
|
+
appleSessionId: string,
|
|
140
|
+
token?: string,
|
|
141
|
+
) {
|
|
142
|
+
const response = await fetch(limbuildURL(limbuildApiUrl, '/apple/auth/finalize', token), {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: jsonHeaders(token),
|
|
145
|
+
body: JSON.stringify({ appleSessionId }),
|
|
146
|
+
});
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
throw new Error(`Apple session finalization failed: HTTP ${response.status} ${await response.text()}`);
|
|
149
|
+
}
|
|
150
|
+
return (await response.json()) as AppleRelayResponse;
|
|
206
151
|
}
|
|
207
152
|
|
|
208
|
-
async function
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
153
|
+
export async function proxyProvisioningRequest<T = unknown>(
|
|
154
|
+
limbuildApiUrl: string,
|
|
155
|
+
appleSessionId: string,
|
|
156
|
+
request: AppleProvisioningRequest,
|
|
157
|
+
token?: string,
|
|
158
|
+
) {
|
|
159
|
+
const response = await fetch(limbuildURL(limbuildApiUrl, '/apple/provisioning', token), {
|
|
160
|
+
method: 'POST',
|
|
161
|
+
headers: jsonHeaders(token),
|
|
162
|
+
body: JSON.stringify({ appleSessionId, ...request }),
|
|
163
|
+
});
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
throw new Error(`Apple provisioning proxy failed: HTTP ${response.status} ${await response.text()}`);
|
|
215
166
|
}
|
|
216
|
-
return
|
|
217
|
-
status: response.status,
|
|
218
|
-
statusText: `${response.status} ${response.statusText}`.trim(),
|
|
219
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
220
|
-
body,
|
|
221
|
-
rawBody,
|
|
222
|
-
bodyBase64: bytesToBase64(new TextEncoder().encode(rawBody)),
|
|
223
|
-
};
|
|
167
|
+
return normalizeAppleProxyResponse<T>((await response.json()) as AppleRelayResponse<T>);
|
|
224
168
|
}
|
|
225
169
|
|
|
226
|
-
async function
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
token,
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
appleSessionId,
|
|
241
|
-
{
|
|
242
|
-
method: 'GET',
|
|
243
|
-
url: `https://idmsa.apple.com/appleauth/auth/signin${widgetKey ? `?widgetKey=${encodeURIComponent(widgetKey)}` : ''}`,
|
|
244
|
-
headers: { Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' },
|
|
245
|
-
},
|
|
246
|
-
token,
|
|
247
|
-
);
|
|
248
|
-
const bits = response.headers?.['x-apple-hc-bits'];
|
|
249
|
-
const challenge = response.headers?.['x-apple-hc-challenge'];
|
|
250
|
-
if (!bits || !challenge) {
|
|
251
|
-
return undefined;
|
|
170
|
+
async function postAppleProxy<T>(
|
|
171
|
+
limbuildApiUrl: string,
|
|
172
|
+
path: string,
|
|
173
|
+
appleSessionId: string,
|
|
174
|
+
payload: unknown,
|
|
175
|
+
token?: string,
|
|
176
|
+
) {
|
|
177
|
+
const response = await fetch(limbuildURL(limbuildApiUrl, path, token), {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: jsonHeaders(token),
|
|
180
|
+
body: JSON.stringify({ appleSessionId, payload }),
|
|
181
|
+
});
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
throw new Error(`Apple proxy ${path} failed: HTTP ${response.status} ${await response.text()}`);
|
|
252
184
|
}
|
|
253
|
-
return
|
|
185
|
+
return normalizeAppleProxyResponse<T>((await response.json()) as AppleRelayResponse<T>);
|
|
254
186
|
}
|
|
255
187
|
|
|
256
|
-
|
|
257
|
-
if (
|
|
258
|
-
return
|
|
188
|
+
function normalizeAppleProxyResponse<T>(response: AppleRelayResponse<T>) {
|
|
189
|
+
if (response.body !== undefined || !response.rawBody) {
|
|
190
|
+
return response;
|
|
259
191
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
192
|
+
try {
|
|
193
|
+
return {
|
|
194
|
+
...response,
|
|
195
|
+
body: JSON.parse(response.rawBody) as T,
|
|
196
|
+
};
|
|
197
|
+
} catch {
|
|
198
|
+
return response;
|
|
267
199
|
}
|
|
268
200
|
}
|
|
269
201
|
|
|
270
|
-
function
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
return byte >> (8 - bits) === 0;
|
|
202
|
+
function limbuildURL(limbuildApiUrl: string, path: string, token?: string) {
|
|
203
|
+
const base = limbuildApiUrl.replace(/\/$/, '');
|
|
204
|
+
const suffix = path.startsWith('/') ? path : `/${path}`;
|
|
205
|
+
const url = new URL(`${base}${suffix}`);
|
|
206
|
+
if (token) {
|
|
207
|
+
url.searchParams.set('token', token);
|
|
279
208
|
}
|
|
280
|
-
return
|
|
209
|
+
return url;
|
|
281
210
|
}
|
|
282
211
|
|
|
283
|
-
function
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
for (const [key, value] of Object.entries(payload)) {
|
|
289
|
-
if (value === undefined || value === null) continue;
|
|
290
|
-
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
291
|
-
params.set(key, String(value));
|
|
292
|
-
} else {
|
|
293
|
-
params.set(key, JSON.stringify(value));
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return params;
|
|
212
|
+
function jsonHeaders(token?: string): Record<string, string> {
|
|
213
|
+
return {
|
|
214
|
+
'Content-Type': 'application/json',
|
|
215
|
+
...authHeaders(token),
|
|
216
|
+
};
|
|
297
217
|
}
|
|
298
218
|
|
|
299
|
-
function
|
|
300
|
-
|
|
301
|
-
for (const byte of bytes) {
|
|
302
|
-
binary += String.fromCharCode(byte);
|
|
303
|
-
}
|
|
304
|
-
return btoa(binary);
|
|
219
|
+
function authHeaders(token?: string): Record<string, string> {
|
|
220
|
+
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
305
221
|
}
|
|
@@ -73,6 +73,18 @@ export async function getLatestSigningAssets() {
|
|
|
73
73
|
)[0];
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
export async function getLatestSigningAssetsWithCertificate(teamID?: string) {
|
|
77
|
+
const all = await getAllSigningAssets();
|
|
78
|
+
return all
|
|
79
|
+
.filter((asset) => {
|
|
80
|
+
if (!asset.certificateID || !asset.certificateP12Base64 || !asset.certificatePassword) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return !teamID || !asset.teamID || asset.teamID === teamID;
|
|
84
|
+
})
|
|
85
|
+
.sort((left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime())[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
export async function putSigningAssets(input: PutSigningAssetsInput) {
|
|
77
89
|
const normalizedBundleID = normalizeBundleID(input.bundleID);
|
|
78
90
|
if (!normalizedBundleID) {
|
|
@@ -118,7 +130,20 @@ export function profileMatchesBundleID(profile: ProvisioningProfileInfo, bundleI
|
|
|
118
130
|
}
|
|
119
131
|
|
|
120
132
|
export async function parseProvisioningProfile(file: File) {
|
|
121
|
-
|
|
133
|
+
return parseProvisioningProfileBytes(new Uint8Array(await file.arrayBuffer()));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function parseProvisioningProfileBase64(base64: string) {
|
|
137
|
+
const binary = atob(base64);
|
|
138
|
+
const bytes = new Uint8Array(binary.length);
|
|
139
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
140
|
+
bytes[index] = binary.charCodeAt(index);
|
|
141
|
+
}
|
|
142
|
+
return parseProvisioningProfileBytes(bytes);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function parseProvisioningProfileBytes(bytes: Uint8Array) {
|
|
146
|
+
const text = new TextDecoder('latin1').decode(bytes);
|
|
122
147
|
const start = text.indexOf('<?xml');
|
|
123
148
|
const end = text.indexOf('</plist>');
|
|
124
149
|
if (start < 0 || end < start) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export type DeviceInstallLog = (message: string, detail?: string) => void;
|
|
2
2
|
|
|
3
|
-
export type DeviceInstallStep = '
|
|
3
|
+
export type DeviceInstallStep = 'signing' | 'connect' | 'build' | 'install';
|
|
4
4
|
|
|
5
5
|
export type DeviceInstallStepStatus = 'idle' | 'active' | 'complete' | 'error';
|
|
6
6
|
|
|
7
|
-
export type DeviceInstallBusyAction = '
|
|
7
|
+
export type DeviceInstallBusyAction = 'signing' | 'usb' | 'pair' | 'build' | 'install';
|
|
8
8
|
|
|
9
9
|
export type DeviceInstallBuildStatus =
|
|
10
10
|
| 'idle'
|