@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.
Files changed (46) hide show
  1. package/dist/components/inspect-overlay.d.ts +33 -0
  2. package/dist/components/remote-control.d.ts +86 -0
  3. package/dist/core/ax-fetcher.d.ts +49 -0
  4. package/dist/core/ax-tree.d.ts +99 -0
  5. package/dist/core/device-install/apple/client.d.ts +1 -0
  6. package/dist/core/device-install/apple/provisioning.d.ts +42 -31
  7. package/dist/core/device-install/apple/relay.d.ts +5 -9
  8. package/dist/core/device-install/storage/browser-storage.d.ts +19 -0
  9. package/dist/core/device-install/types.d.ts +2 -2
  10. package/dist/device-install/index.cjs +1 -9
  11. package/dist/device-install/index.js +76 -210
  12. package/dist/device-install/react.cjs +1 -1
  13. package/dist/device-install/react.js +1 -1
  14. package/dist/device-install-dialog-CjH25hnN.js +2 -0
  15. package/dist/device-install-dialog-W5Xv9kWL.mjs +443 -0
  16. package/dist/device-install-dialog.css +1 -1
  17. package/dist/hooks/use-device-install.d.ts +21 -3
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.css +1 -1
  20. package/dist/index.d.ts +3 -0
  21. package/dist/index.js +1485 -778
  22. package/dist/use-device-install-Y1u6vIBB.js +31 -0
  23. package/dist/use-device-install-sDVvby1V.mjs +13627 -0
  24. package/package.json +7 -3
  25. package/src/components/device-install/device-install-dialog.css +82 -1
  26. package/src/components/device-install/device-install-dialog.tsx +319 -187
  27. package/src/components/inspect-overlay.css +223 -0
  28. package/src/components/inspect-overlay.tsx +437 -0
  29. package/src/components/remote-control.tsx +547 -9
  30. package/src/core/ax-fetcher.test.ts +418 -0
  31. package/src/core/ax-fetcher.ts +377 -0
  32. package/src/core/ax-tree.test.ts +491 -0
  33. package/src/core/ax-tree.ts +416 -0
  34. package/src/core/device-install/apple/client.ts +92 -4
  35. package/src/core/device-install/apple/provisioning.ts +67 -24
  36. package/src/core/device-install/apple/relay.ts +121 -205
  37. package/src/core/device-install/storage/browser-storage.ts +26 -1
  38. package/src/core/device-install/types.ts +2 -2
  39. package/src/demo.tsx +93 -10
  40. package/src/hooks/use-device-install.ts +766 -67
  41. package/src/index.ts +19 -1
  42. package/vitest.config.ts +23 -0
  43. package/dist/device-install-dialog-CTwVViYY.js +0 -2
  44. package/dist/device-install-dialog-zzKJu7SM.mjs +0 -328
  45. package/dist/use-device-install-CgrOKKyi.mjs +0 -13042
  46. 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?: Array<Record<string, unknown>>;
26
- devices?: Array<Record<string, unknown>>;
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
- return pagedRequest('/account/ios/identifiers/listAppIds.action', teamID, { search: bundleID });
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
- return pagedRequest('/account/ios/device/listDevices.action', teamID, { search: normalizeUDID(deviceUDID) });
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, { search: bundleID });
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/addDevice.action',
125
+ path: '/account/ios/device/addDevices.action',
89
126
  payload: {
90
127
  teamId: teamID,
91
- name,
92
- deviceNumber: normalizeUDID(deviceUDID),
93
- deviceClass: 'iphone',
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/submitDevelopmentCSR.action',
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: 'POST',
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
- appIdId: appIDID,
201
+ provisioningProfileName: name ?? `Limrun ${bundleID}`,
162
202
  certificateIds: [certificateID],
203
+ appIdId: appIDID,
163
204
  deviceIds: deviceIDs,
164
- distributionMethod: 'development',
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: 'POST',
174
- path: '/account/ios/profile/downloadProfileContent.action',
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
- bodyBase64?: string;
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 relayAppleRequest<AppleSRPInitResponse>(
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
- const hashcash = await fetchAppleHashcash(limbuildApiUrl, appleSessionId, token);
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 proxyTwoFactorCode(
67
+ export async function triggerTrustedDeviceTwoFactor(
114
68
  limbuildApiUrl: string,
115
69
  appleSessionId: string,
116
- code: string,
117
70
  token?: string,
118
71
  ) {
119
- return relayAppleRequest(
120
- limbuildApiUrl,
121
- appleSessionId,
122
- {
123
- method: 'POST',
124
- url: 'https://idmsa.apple.com/appleauth/auth/verify/trusteddevice/securitycode',
125
- headers: jsonContentHeaders(),
126
- body: JSON.stringify({ securityCode: { code } }),
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 finalizeAppleRelaySession(
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
- return relayAppleRequest(
138
- limbuildApiUrl,
139
- appleSessionId,
140
- {
141
- method: 'GET',
142
- url: 'https://appstoreconnect.apple.com/olympus/v1/session',
143
- headers: { Accept: 'application/json' },
144
- },
145
- token,
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 proxyProvisioningRequest<T = unknown>(
101
+ export async function proxyTwoFactorCode(
150
102
  limbuildApiUrl: string,
151
103
  appleSessionId: string,
152
- request: AppleProvisioningRequest,
104
+ code: string,
153
105
  token?: string,
154
106
  ) {
155
- return relayAppleRequest<T>(
156
- limbuildApiUrl,
157
- appleSessionId,
158
- {
159
- method: request.method ?? 'GET',
160
- url: `https://developer.apple.com/services-account/QH65B2${request.path}`,
161
- headers: {
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 url;
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 jsonHeaders(token?: string): Record<string, string> {
187
- const headers: Record<string, string> = {
188
- 'Content-Type': 'application/json',
189
- };
190
- if (token) {
191
- headers.Authorization = `Bearer ${token}`;
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 headers;
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 jsonContentHeaders(): Record<string, string> {
201
- return {
202
- Accept: 'application/json, text/javascript, */*; q=0.01',
203
- 'Content-Type': 'application/json',
204
- 'X-Requested-With': 'XMLHttpRequest',
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 responseToAppleRelayResponse<T>(response: Response): Promise<AppleRelayResponse<T>> {
209
- const rawBody = await response.text();
210
- let body: T | undefined;
211
- try {
212
- body = rawBody ? (JSON.parse(rawBody) as T) : undefined;
213
- } catch {
214
- body = undefined;
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 fetchAppleHashcash(limbuildApiUrl: string, appleSessionId: string, token?: string) {
227
- const config = await relayAppleRequest<{ authServiceKey?: string }>(
228
- limbuildApiUrl,
229
- appleSessionId,
230
- {
231
- method: 'GET',
232
- url: 'https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com',
233
- headers: { Accept: 'application/json' },
234
- },
235
- token,
236
- );
237
- const widgetKey = config.body?.authServiceKey;
238
- const response = await relayAppleRequest(
239
- limbuildApiUrl,
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 makeAppleHashcash(parseInt(bits, 10), challenge);
185
+ return normalizeAppleProxyResponse<T>((await response.json()) as AppleRelayResponse<T>);
254
186
  }
255
187
 
256
- async function makeAppleHashcash(bits: number, challenge: string) {
257
- if (!Number.isFinite(bits) || bits <= 0) {
258
- return undefined;
188
+ function normalizeAppleProxyResponse<T>(response: AppleRelayResponse<T>) {
189
+ if (response.body !== undefined || !response.rawBody) {
190
+ return response;
259
191
  }
260
- const date = new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
261
- for (let counter = 0; ; counter += 1) {
262
- const value = `1:${bits}:${date}:${challenge}::${counter}`;
263
- const digest = new Uint8Array(await crypto.subtle.digest('SHA-1', new TextEncoder().encode(value)));
264
- if (hasLeadingZeroBits(digest, bits)) {
265
- return value;
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 hasLeadingZeroBits(bytes: Uint8Array, bits: number) {
271
- for (const byte of bytes) {
272
- if (bits <= 0) return true;
273
- if (bits >= 8) {
274
- if (byte !== 0) return false;
275
- bits -= 8;
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 bits <= 0;
209
+ return url;
281
210
  }
282
211
 
283
- function formEncode(payload: unknown) {
284
- const params = new URLSearchParams();
285
- if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
286
- return params;
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 bytesToBase64(bytes: Uint8Array) {
300
- let binary = '';
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
- const text = new TextDecoder('latin1').decode(await file.arrayBuffer());
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 = 'build' | 'usb' | 'pair' | 'install';
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 = 'build' | 'usb' | 'pair' | 'install';
7
+ export type DeviceInstallBusyAction = 'signing' | 'usb' | 'pair' | 'build' | 'install';
8
8
 
9
9
  export type DeviceInstallBuildStatus =
10
10
  | 'idle'