@limrun/ui 0.9.0-rc.1 → 0.9.0-rc.4

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 (30) hide show
  1. package/dist/core/device-install/apple/client.d.ts +1 -0
  2. package/dist/core/device-install/apple/provisioning.d.ts +42 -31
  3. package/dist/core/device-install/apple/relay.d.ts +5 -9
  4. package/dist/core/device-install/storage/browser-storage.d.ts +19 -0
  5. package/dist/core/device-install/types.d.ts +2 -2
  6. package/dist/device-install/index.cjs +1 -9
  7. package/dist/device-install/index.js +76 -210
  8. package/dist/device-install/react.cjs +1 -1
  9. package/dist/device-install/react.js +1 -1
  10. package/dist/device-install-dialog-86RDdoK9.js +2 -0
  11. package/dist/device-install-dialog-CnyDWf0q.mjs +462 -0
  12. package/dist/device-install-dialog.css +1 -1
  13. package/dist/hooks/use-device-install.d.ts +21 -3
  14. package/dist/index.cjs +1 -1
  15. package/dist/index.js +3 -3
  16. package/dist/use-device-install-CbGVvwPp.js +31 -0
  17. package/dist/use-device-install-j1Gekpl4.mjs +13623 -0
  18. package/package.json +1 -1
  19. package/src/components/device-install/device-install-dialog.css +82 -1
  20. package/src/components/device-install/device-install-dialog.tsx +337 -187
  21. package/src/core/device-install/apple/client.ts +92 -4
  22. package/src/core/device-install/apple/provisioning.ts +67 -24
  23. package/src/core/device-install/apple/relay.ts +121 -205
  24. package/src/core/device-install/storage/browser-storage.ts +26 -1
  25. package/src/core/device-install/types.ts +2 -2
  26. package/src/hooks/use-device-install.ts +748 -60
  27. package/dist/device-install-dialog-CTwVViYY.js +0 -2
  28. package/dist/device-install-dialog-zzKJu7SM.mjs +0 -328
  29. package/dist/use-device-install-CgrOKKyi.mjs +0 -13042
  30. package/dist/use-device-install-DDKRf6IL.js +0 -23
@@ -2,10 +2,13 @@ import { AppleGsaSrpClient } from './gsa-srp';
2
2
  import {
3
3
  createAppleRelaySession,
4
4
  deleteAppleRelaySession,
5
- finalizeAppleRelaySession,
5
+ fetchAppleAccountSession,
6
+ proxyPhoneTwoFactorCode,
6
7
  proxySrpComplete,
7
8
  proxySrpInit,
8
9
  proxyTwoFactorCode,
10
+ triggerPhoneTwoFactor,
11
+ triggerTrustedDeviceTwoFactor,
9
12
  type AppleRelayResponse,
10
13
  } from './relay';
11
14
 
@@ -19,12 +22,17 @@ export type AppleIDLoginInput = {
19
22
  export type AppleIDLoginResult = {
20
23
  appleSessionId: string;
21
24
  completeResponse: AppleRelayResponse;
25
+ twoFactorChallengeResponse?: AppleRelayResponse;
22
26
  requiresTwoFactor: boolean;
23
27
  finishTwoFactor: (code: string) => Promise<AppleRelayResponse>;
24
28
  finalize: () => Promise<AppleRelayResponse>;
25
29
  close: () => Promise<void>;
26
30
  };
27
31
 
32
+ type TwoFactorMethod =
33
+ | { type: 'trustedDevice' }
34
+ | { type: 'phone'; phoneNumberId: number; mode: string };
35
+
28
36
  export async function startBrowserOwnedAppleIDLogin({
29
37
  limbuildApiUrl,
30
38
  accountName,
@@ -35,6 +43,9 @@ export async function startBrowserOwnedAppleIDLogin({
35
43
  try {
36
44
  const srp = new AppleGsaSrpClient(accountName);
37
45
  const initResponse = await proxySrpInit(limbuildApiUrl, appleSessionId, await srp.init(), token);
46
+ if (initResponse.status < 200 || initResponse.status >= 300) {
47
+ throw new Error(`Apple SRP init failed: HTTP ${initResponse.status} ${initResponse.rawBody ?? ''}`.trim());
48
+ }
38
49
  if (!initResponse.body) {
39
50
  throw new Error('Apple SRP init response did not include a body.');
40
51
  }
@@ -49,12 +60,64 @@ export async function startBrowserOwnedAppleIDLogin({
49
60
  },
50
61
  token,
51
62
  );
63
+ const requiresTwoFactor = completeResponse.status === 409;
64
+ let twoFactorChallengeResponse: AppleRelayResponse | undefined;
65
+ let twoFactorMethod: TwoFactorMethod = { type: 'trustedDevice' };
66
+ if (requiresTwoFactor) {
67
+ twoFactorChallengeResponse = await triggerTrustedDeviceTwoFactor(limbuildApiUrl, appleSessionId, token);
68
+ const phone = trustedPhoneNumberFromChallenge(twoFactorChallengeResponse.body);
69
+ if (phone) {
70
+ twoFactorMethod = {
71
+ type: 'phone',
72
+ phoneNumberId: phone.id,
73
+ mode: phone.pushMode ?? 'sms',
74
+ };
75
+ }
76
+ if (twoFactorChallengeResponse.status === 412) {
77
+ if (!phone) {
78
+ throw new Error('Apple requested phone verification but did not include a trusted phone number.');
79
+ }
80
+ twoFactorChallengeResponse = await triggerPhoneTwoFactor(
81
+ limbuildApiUrl,
82
+ appleSessionId,
83
+ phone.id,
84
+ phone.pushMode ?? 'sms',
85
+ token,
86
+ );
87
+ }
88
+ if (twoFactorChallengeResponse.status < 200 || twoFactorChallengeResponse.status >= 300) {
89
+ throw new Error(
90
+ `Apple two-factor challenge failed: HTTP ${twoFactorChallengeResponse.status} ${
91
+ twoFactorChallengeResponse.rawBody ?? ''
92
+ }`.trim(),
93
+ );
94
+ }
95
+ } else if (completeResponse.status < 200 || completeResponse.status >= 300) {
96
+ throw new Error(`Apple SRP complete failed: HTTP ${completeResponse.status} ${completeResponse.rawBody ?? ''}`.trim());
97
+ }
52
98
  return {
53
99
  appleSessionId,
54
100
  completeResponse,
55
- requiresTwoFactor: completeResponse.status === 409,
56
- finishTwoFactor: (code) => proxyTwoFactorCode(limbuildApiUrl, appleSessionId, code, token),
57
- finalize: () => finalizeAppleRelaySession(limbuildApiUrl, appleSessionId, token),
101
+ twoFactorChallengeResponse,
102
+ requiresTwoFactor,
103
+ finishTwoFactor: async (code) => {
104
+ const response =
105
+ twoFactorMethod.type === 'phone'
106
+ ? await proxyPhoneTwoFactorCode(
107
+ limbuildApiUrl,
108
+ appleSessionId,
109
+ twoFactorMethod.phoneNumberId,
110
+ code,
111
+ twoFactorMethod.mode,
112
+ token,
113
+ )
114
+ : await proxyTwoFactorCode(limbuildApiUrl, appleSessionId, code, token);
115
+ if (response.status < 200 || response.status >= 300) {
116
+ throw new Error(`Apple two-factor code failed: HTTP ${response.status} ${response.rawBody ?? ''}`.trim());
117
+ }
118
+ return response;
119
+ },
120
+ finalize: async () => fetchAppleAccountSession(limbuildApiUrl, appleSessionId, token),
58
121
  close: () => deleteAppleRelaySession(limbuildApiUrl, appleSessionId, token),
59
122
  };
60
123
  } catch (error) {
@@ -62,3 +125,28 @@ export async function startBrowserOwnedAppleIDLogin({
62
125
  throw error;
63
126
  }
64
127
  }
128
+
129
+ function trustedPhoneNumberFromChallenge(body: unknown) {
130
+ if (!isRecord(body)) return undefined;
131
+ const verification = isRecord(body.phoneNumberVerification) ? body.phoneNumberVerification : undefined;
132
+ const trustedPhoneNumber =
133
+ recordValue(verification?.trustedPhoneNumber) ??
134
+ recordValue(body.trustedPhoneNumber) ??
135
+ recordValue(body.phoneNumber);
136
+ if (!trustedPhoneNumber) return undefined;
137
+ const id = trustedPhoneNumber.id;
138
+ if (typeof id !== 'number') return undefined;
139
+ const pushMode =
140
+ typeof trustedPhoneNumber.pushMode === 'string' ? trustedPhoneNumber.pushMode
141
+ : typeof body.mode === 'string' ? body.mode
142
+ : undefined;
143
+ return { id, pushMode };
144
+ }
145
+
146
+ function recordValue(value: unknown) {
147
+ return isRecord(value) ? value : undefined;
148
+ }
149
+
150
+ function isRecord(value: unknown): value is Record<string, unknown> {
151
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
152
+ }
@@ -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
  }