@limrun/ui 0.9.0-rc.5 → 0.9.0-rc.7

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 (73) hide show
  1. package/README.md +9 -0
  2. package/dist/components/device-install/device-install-dialog.d.ts +5 -0
  3. package/dist/components/device-install/index.d.ts +2 -0
  4. package/dist/components/inspect-overlay.d.ts +1 -0
  5. package/dist/components/remote-control.d.ts +13 -2
  6. package/dist/core/ax-tree.d.ts +2 -0
  7. package/dist/core/device-install/apple/client.d.ts +17 -0
  8. package/dist/core/device-install/apple/crypto.d.ts +20 -0
  9. package/dist/core/device-install/apple/gsa-srp.d.ts +26 -0
  10. package/dist/core/device-install/apple/index.d.ts +5 -0
  11. package/dist/core/device-install/apple/provisioning.d.ts +161 -0
  12. package/dist/core/device-install/apple/relay.d.ts +29 -0
  13. package/dist/core/device-install/index.d.ts +4 -0
  14. package/dist/core/device-install/operations/index.d.ts +6 -0
  15. package/dist/core/device-install/operations/limbuild-client.d.ts +28 -0
  16. package/dist/core/device-install/operations/operations.d.ts +32 -0
  17. package/dist/core/device-install/operations/relay-client.d.ts +25 -0
  18. package/dist/core/device-install/operations/relay-protocol.d.ts +27 -0
  19. package/dist/core/device-install/operations/usbmux.d.ts +32 -0
  20. package/dist/core/device-install/operations/webusb.d.ts +21 -0
  21. package/dist/core/device-install/storage/browser-storage.d.ts +44 -0
  22. package/dist/core/device-install/storage/index.d.ts +1 -0
  23. package/dist/core/device-install/types.d.ts +48 -0
  24. package/dist/device-install/index.cjs +1 -0
  25. package/dist/device-install/index.d.ts +3 -0
  26. package/dist/device-install/index.js +78 -0
  27. package/dist/device-install/react.cjs +1 -0
  28. package/dist/device-install/react.d.ts +1 -0
  29. package/dist/device-install/react.js +4 -0
  30. package/dist/device-install-dialog-86RDdoK9.js +2 -0
  31. package/dist/device-install-dialog-CnyDWf0q.mjs +462 -0
  32. package/dist/device-install-dialog.css +1 -0
  33. package/dist/hooks/index.d.ts +1 -0
  34. package/dist/hooks/use-device-install.d.ts +73 -0
  35. package/dist/index.cjs +1 -1
  36. package/dist/index.css +1 -1
  37. package/dist/index.d.ts +3 -1
  38. package/dist/index.js +737 -703
  39. package/dist/use-device-install-CbGVvwPp.js +31 -0
  40. package/dist/use-device-install-j1Gekpl4.mjs +13623 -0
  41. package/package.json +15 -2
  42. package/src/components/device-install/device-install-dialog.css +325 -0
  43. package/src/components/device-install/device-install-dialog.tsx +513 -0
  44. package/src/components/device-install/index.ts +2 -0
  45. package/src/components/inspect-overlay.css +6 -0
  46. package/src/components/inspect-overlay.tsx +46 -15
  47. package/src/components/remote-control.tsx +16 -2
  48. package/src/core/ax-tree.test.ts +124 -0
  49. package/src/core/ax-tree.ts +107 -0
  50. package/src/core/device-install/apple/client.ts +152 -0
  51. package/src/core/device-install/apple/crypto.ts +202 -0
  52. package/src/core/device-install/apple/gsa-srp.ts +127 -0
  53. package/src/core/device-install/apple/index.ts +5 -0
  54. package/src/core/device-install/apple/provisioning.ts +298 -0
  55. package/src/core/device-install/apple/relay.ts +221 -0
  56. package/src/core/device-install/index.ts +4 -0
  57. package/src/core/device-install/operations/index.ts +6 -0
  58. package/src/core/device-install/operations/limbuild-client.ts +104 -0
  59. package/src/core/device-install/operations/operations.ts +217 -0
  60. package/src/core/device-install/operations/relay-client.ts +255 -0
  61. package/src/core/device-install/operations/relay-protocol.ts +71 -0
  62. package/src/core/device-install/operations/usbmux.ts +270 -0
  63. package/src/core/device-install/operations/webusb-dom.d.ts +54 -0
  64. package/src/core/device-install/operations/webusb.ts +105 -0
  65. package/src/core/device-install/storage/browser-storage.ts +263 -0
  66. package/src/core/device-install/storage/index.ts +1 -0
  67. package/src/core/device-install/types.ts +65 -0
  68. package/src/device-install/index.ts +3 -0
  69. package/src/device-install/react.ts +1 -0
  70. package/src/hooks/index.ts +1 -0
  71. package/src/hooks/use-device-install.ts +1210 -0
  72. package/src/index.ts +4 -0
  73. package/vite.config.ts +6 -2
@@ -0,0 +1,127 @@
1
+ import { Hash, Mode, Srp, util } from '@foxt/js-srp';
2
+
3
+ export type AppleSRPProtocol = 's2k' | 's2k_fo';
4
+
5
+ export type AppleSRPInitRequest = {
6
+ a: string;
7
+ accountName: string;
8
+ protocols: AppleSRPProtocol[];
9
+ };
10
+
11
+ export type AppleSRPInitResponse = {
12
+ iteration: number;
13
+ salt: string;
14
+ protocol: AppleSRPProtocol;
15
+ b: string;
16
+ c: string;
17
+ };
18
+
19
+ export type AppleSRPCompleteProof = {
20
+ accountName: string;
21
+ m1: string;
22
+ m2: string;
23
+ c: string;
24
+ };
25
+
26
+ const srp = new Srp(Mode.GSA, Hash.SHA256, 2048);
27
+
28
+ export class AppleGsaSrpClient {
29
+ private srpClient?: Awaited<ReturnType<typeof srp.newClient>>;
30
+
31
+ constructor(private readonly accountName: string) {}
32
+
33
+ async init(): Promise<AppleSRPInitRequest> {
34
+ if (this.srpClient) {
35
+ throw new Error('SRP client is already initialized.');
36
+ }
37
+ this.srpClient = await srp.newClient(stringToBytes(this.accountName), new Uint8Array());
38
+ return {
39
+ accountName: this.accountName,
40
+ protocols: ['s2k', 's2k_fo'],
41
+ a: bytesToBase64(util.bytesFromBigint(this.srpClient.A)),
42
+ };
43
+ }
44
+
45
+ async complete(password: string, serverData: AppleSRPInitResponse): Promise<AppleSRPCompleteProof> {
46
+ if (!this.srpClient) {
47
+ throw new Error('SRP client is not initialized.');
48
+ }
49
+ if (serverData.protocol !== 's2k' && serverData.protocol !== 's2k_fo') {
50
+ throw new Error(`Unsupported Apple SRP protocol ${serverData.protocol}.`);
51
+ }
52
+ const salt = base64ToBytes(serverData.salt);
53
+ const serverPublicKey = base64ToBytes(serverData.b);
54
+ const derivedPassword = await deriveApplePassword(
55
+ serverData.protocol,
56
+ password,
57
+ salt,
58
+ serverData.iteration,
59
+ );
60
+ this.srpClient.p = derivedPassword;
61
+ await this.srpClient.generate(salt, serverPublicKey);
62
+ const m2 = await this.srpClient.generateM2();
63
+ return {
64
+ accountName: this.accountName,
65
+ c: serverData.c,
66
+ m1: bytesToBase64(this.srpClient._M),
67
+ m2: bytesToBase64(m2),
68
+ };
69
+ }
70
+ }
71
+
72
+ async function deriveApplePassword(
73
+ protocol: AppleSRPProtocol,
74
+ password: string,
75
+ salt: Uint8Array,
76
+ iterations: number,
77
+ ) {
78
+ let passHash = new Uint8Array(await util.hash(srp.h, toArrayBuffer(stringToBytes(password))));
79
+ if (protocol === 's2k_fo') {
80
+ passHash = stringToBytes(util.toHex(passHash));
81
+ }
82
+ const imported = await crypto.subtle.importKey(
83
+ 'raw',
84
+ toArrayBuffer(passHash),
85
+ { name: 'PBKDF2' },
86
+ false,
87
+ ['deriveBits'],
88
+ );
89
+ const derived = await crypto.subtle.deriveBits(
90
+ {
91
+ name: 'PBKDF2',
92
+ hash: { name: 'SHA-256' },
93
+ iterations,
94
+ salt: toArrayBuffer(salt),
95
+ },
96
+ imported,
97
+ 256,
98
+ );
99
+ return new Uint8Array(derived);
100
+ }
101
+
102
+ function stringToBytes(value: string) {
103
+ return new TextEncoder().encode(value);
104
+ }
105
+
106
+ function bytesToBase64(bytes: Uint8Array) {
107
+ let binary = '';
108
+ for (const byte of bytes) {
109
+ binary += String.fromCharCode(byte);
110
+ }
111
+ return btoa(binary);
112
+ }
113
+
114
+ function base64ToBytes(value: string) {
115
+ const binary = atob(value);
116
+ const bytes = new Uint8Array(binary.length);
117
+ for (let index = 0; index < binary.length; index += 1) {
118
+ bytes[index] = binary.charCodeAt(index);
119
+ }
120
+ return bytes;
121
+ }
122
+
123
+ function toArrayBuffer(bytes: Uint8Array): ArrayBuffer {
124
+ const copy = new Uint8Array(bytes.byteLength);
125
+ copy.set(bytes);
126
+ return copy.buffer;
127
+ }
@@ -0,0 +1,5 @@
1
+ export * from './client';
2
+ export * from './crypto';
3
+ export * from './gsa-srp';
4
+ export * from './provisioning';
5
+ export * from './relay';
@@ -0,0 +1,298 @@
1
+ import type { ProvisioningProfileInfo, StoredSigningAssets } from '../types';
2
+ import {
3
+ getSigningAssets,
4
+ normalizeBundleID,
5
+ normalizeUDID,
6
+ profileContainsDevice,
7
+ profileMatchesBundleID,
8
+ putSigningAssets,
9
+ } from '../storage';
10
+ import type { AppleProvisioningRequest } from './relay';
11
+
12
+ export type AppleDeveloperPortalTeam = {
13
+ name?: string;
14
+ teamId?: string;
15
+ providerId?: string | number;
16
+ publicProviderId?: string;
17
+ type?: string;
18
+ subType?: string;
19
+ };
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
+
40
+ export type AppleDeveloperPortalResponse = {
41
+ resultCode?: number;
42
+ resultString?: string;
43
+ userString?: string;
44
+ teams?: AppleDeveloperPortalTeam[];
45
+ provider?: AppleDeveloperPortalTeam;
46
+ availableProviders?: AppleDeveloperPortalTeam[];
47
+ appIds?: AppleDeveloperPortalAppID[];
48
+ devices?: AppleDeveloperPortalDevice[];
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>;
54
+ provisioningProfiles?: Array<Record<string, unknown>>;
55
+ };
56
+
57
+ export type AppleProvisioningContext = {
58
+ bundleID: string;
59
+ deviceUDID: string;
60
+ teamID?: string;
61
+ };
62
+
63
+ export type AppleSigningAssetCacheInput = {
64
+ bundleID: string;
65
+ deviceUDID?: string;
66
+ teamID?: string;
67
+ };
68
+
69
+ export type PutAppleGeneratedSigningAssetsInput = {
70
+ bundleID: string;
71
+ deviceUDID?: string;
72
+ teamID?: string;
73
+ certificateID?: string;
74
+ certificateP12Base64: string;
75
+ certificatePassword: string;
76
+ provisioningProfileBase64: string;
77
+ profile: ProvisioningProfileInfo;
78
+ };
79
+
80
+ export function listTeamsRequest(): AppleProvisioningRequest {
81
+ return {
82
+ method: 'POST',
83
+ path: '/account/listTeams.action',
84
+ payload: {},
85
+ };
86
+ }
87
+
88
+ export function findBundleIDRequest({ bundleID, teamID = '' }: Pick<AppleProvisioningContext, 'bundleID' | 'teamID'>) {
89
+ void bundleID;
90
+ return pagedRequest('/account/ios/identifiers/listAppIds.action', teamID, { sort: 'name=asc' });
91
+ }
92
+
93
+ export function findDeviceRequest({ deviceUDID, teamID = '' }: Pick<AppleProvisioningContext, 'deviceUDID' | 'teamID'>) {
94
+ void deviceUDID;
95
+ return pagedRequest('/account/ios/device/listDevices.action', teamID, {
96
+ sort: 'name=asc',
97
+ includeRemovedDevices: false,
98
+ });
99
+ }
100
+
101
+ export function findDevelopmentCertificatesRequest(teamID = '') {
102
+ return pagedRequest('/account/ios/certificate/listCertRequests.action', teamID, {
103
+ sort: 'name=asc',
104
+ types: '83Q87W3TGH,5QPB9NHCEI',
105
+ });
106
+ }
107
+
108
+ export function findDevelopmentProfilesRequest({
109
+ bundleID,
110
+ teamID = '',
111
+ }: Pick<AppleProvisioningContext, 'bundleID' | 'teamID'>) {
112
+ return pagedRequest('/account/ios/profile/listProvisioningProfiles.action', teamID, {
113
+ search: bundleID,
114
+ sort: 'name=asc',
115
+ });
116
+ }
117
+
118
+ export function registerDeviceRequest({
119
+ deviceUDID,
120
+ teamID = '',
121
+ name = 'Limrun iPhone',
122
+ }: Pick<AppleProvisioningContext, 'deviceUDID' | 'teamID'> & { name?: string }) {
123
+ return {
124
+ method: 'POST',
125
+ path: '/account/ios/device/addDevices.action',
126
+ payload: {
127
+ teamId: teamID,
128
+ deviceNames: name,
129
+ deviceNumbers: normalizeUDID(deviceUDID),
130
+ deviceClasses: 'iphone',
131
+ register: 'single',
132
+ },
133
+ } satisfies AppleProvisioningRequest;
134
+ }
135
+
136
+ export function createBundleIDRequest({
137
+ bundleID,
138
+ teamID = '',
139
+ name,
140
+ }: Pick<AppleProvisioningContext, 'bundleID' | 'teamID'> & { name?: string }) {
141
+ return {
142
+ method: 'POST',
143
+ path: '/account/ios/identifiers/addAppId.action',
144
+ payload: {
145
+ teamId: teamID,
146
+ name: name ?? bundleID,
147
+ identifier: bundleID,
148
+ type: 'explicit',
149
+ },
150
+ } satisfies AppleProvisioningRequest;
151
+ }
152
+
153
+ export function submitDevelopmentCSRRequest({
154
+ csrPEM,
155
+ teamID = '',
156
+ }: {
157
+ csrPEM: string;
158
+ teamID?: string;
159
+ }) {
160
+ return {
161
+ method: 'POST',
162
+ path: '/account/ios/certificate/submitCertificateRequest.action',
163
+ payload: {
164
+ teamId: teamID,
165
+ type: '83Q87W3TGH',
166
+ csrContent: csrPEM,
167
+ },
168
+ } satisfies AppleProvisioningRequest;
169
+ }
170
+
171
+ export function downloadCertificateRequest(certificateID: string, teamID = '') {
172
+ return {
173
+ method: 'GET',
174
+ path: '/account/ios/certificate/downloadCertificateContent.action',
175
+ payload: {
176
+ teamId: teamID,
177
+ certificateId: certificateID,
178
+ type: '83Q87W3TGH',
179
+ },
180
+ } satisfies AppleProvisioningRequest;
181
+ }
182
+
183
+ export function createDevelopmentProfileRequest({
184
+ bundleID,
185
+ teamID = '',
186
+ appIDID,
187
+ certificateID,
188
+ deviceIDs,
189
+ name,
190
+ }: Pick<AppleProvisioningContext, 'bundleID' | 'teamID'> & {
191
+ appIDID: string;
192
+ certificateID: string;
193
+ deviceIDs: string[];
194
+ name?: string;
195
+ }) {
196
+ return {
197
+ method: 'POST',
198
+ path: '/account/ios/profile/createProvisioningProfile.action',
199
+ payload: {
200
+ teamId: teamID,
201
+ provisioningProfileName: name ?? `Limrun ${bundleID}`,
202
+ certificateIds: [certificateID],
203
+ appIdId: appIDID,
204
+ deviceIds: deviceIDs,
205
+ distributionType: 'limited',
206
+ subPlatform: 'ios',
207
+ },
208
+ } satisfies AppleProvisioningRequest;
209
+ }
210
+
211
+ export function downloadProfileRequest(profileID: string, teamID = '') {
212
+ return {
213
+ method: 'GET',
214
+ path: '/account/ios/profile/downloadProfileContent',
215
+ payload: {
216
+ teamId: teamID,
217
+ provisioningProfileId: profileID,
218
+ },
219
+ } satisfies AppleProvisioningRequest;
220
+ }
221
+
222
+ export async function getReusableAppleSigningAssets({
223
+ bundleID,
224
+ deviceUDID,
225
+ teamID,
226
+ }: AppleSigningAssetCacheInput) {
227
+ const stored = await getSigningAssets({ bundleID, deviceUDID });
228
+ if (!stored || !storedSigningAssetsReusable(stored, { bundleID, deviceUDID, teamID })) {
229
+ return undefined;
230
+ }
231
+ return stored;
232
+ }
233
+
234
+ export async function putAppleGeneratedSigningAssets(input: PutAppleGeneratedSigningAssetsInput) {
235
+ return putSigningAssets({
236
+ ...input,
237
+ certificateFileName: input.certificateID ? `${input.certificateID}.p12` : 'apple-development.p12',
238
+ profileFileName: input.profile.uuid ? `${input.profile.uuid}.mobileprovision` : 'apple-development.mobileprovision',
239
+ });
240
+ }
241
+
242
+ export function storedSigningAssetsReusable(
243
+ stored: StoredSigningAssets,
244
+ { bundleID, deviceUDID, teamID }: AppleSigningAssetCacheInput,
245
+ ) {
246
+ if (!profileMatchesBundleID(stored.profile, bundleID)) {
247
+ return false;
248
+ }
249
+ if (teamID && stored.teamID && stored.teamID !== teamID) {
250
+ return false;
251
+ }
252
+ if (deviceUDID && !profileContainsDevice(stored.profile, deviceUDID)) {
253
+ return false;
254
+ }
255
+ if (stored.profile.expirationDate && new Date(stored.profile.expirationDate).getTime() <= Date.now()) {
256
+ return false;
257
+ }
258
+ return normalizeBundleID(stored.bundleID) === normalizeBundleID(bundleID);
259
+ }
260
+
261
+ export function selectDeveloperPortalTeam(body: unknown): AppleDeveloperPortalTeam | undefined {
262
+ const response = body as AppleDeveloperPortalResponse | undefined;
263
+ return response?.teams?.[0] ?? response?.provider ?? response?.availableProviders?.[0];
264
+ }
265
+
266
+ export function teamIDCandidates(body: unknown): string[] {
267
+ const response = body as AppleDeveloperPortalResponse | undefined;
268
+ const teams = [
269
+ ...(response?.teams ?? []),
270
+ ...(response?.provider ? [response.provider] : []),
271
+ ...(response?.availableProviders ?? []),
272
+ ];
273
+ const ids = new Set<string>();
274
+ for (const team of teams) {
275
+ for (const value of [team.teamId, team.providerId, team.publicProviderId]) {
276
+ if (value !== undefined && value !== '') {
277
+ ids.add(String(value));
278
+ }
279
+ }
280
+ }
281
+ return [...ids];
282
+ }
283
+
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
+ }
293
+ return {
294
+ method: 'POST',
295
+ path,
296
+ payload: basePayload,
297
+ } satisfies AppleProvisioningRequest;
298
+ }
@@ -0,0 +1,221 @@
1
+ import type { AppleSRPCompleteProof, AppleSRPInitRequest, AppleSRPInitResponse } from './gsa-srp';
2
+
3
+ export type AppleRelayResponse<T = unknown> = {
4
+ status: number;
5
+ statusText: string;
6
+ headers?: Record<string, string>;
7
+ body?: T;
8
+ rawBody?: string;
9
+ rawBodyBase64?: string;
10
+ };
11
+
12
+ export type AppleProvisioningRequest = {
13
+ method?: 'GET' | 'POST';
14
+ path: string;
15
+ payload?: unknown;
16
+ };
17
+
18
+ export async function createAppleRelaySession(limbuildApiUrl: string, token?: string) {
19
+ const response = await fetch(limbuildURL(limbuildApiUrl, '/apple/auth/session', token), {
20
+ method: 'POST',
21
+ headers: authHeaders(token),
22
+ });
23
+ if (!response.ok) {
24
+ throw new Error(`Apple relay session failed: HTTP ${response.status} ${await response.text()}`);
25
+ }
26
+ return (await response.json()) as { appleSessionId: string };
27
+ }
28
+
29
+ export async function deleteAppleRelaySession(limbuildApiUrl: string, appleSessionId: string, token?: string) {
30
+ const response = await fetch(limbuildURL(limbuildApiUrl, '/apple/auth/session/delete', token), {
31
+ method: 'POST',
32
+ headers: jsonHeaders(token),
33
+ body: JSON.stringify({ appleSessionId }),
34
+ });
35
+ if (!response.ok) {
36
+ throw new Error(`Apple relay session delete failed: HTTP ${response.status} ${await response.text()}`);
37
+ }
38
+ }
39
+
40
+ export async function proxySrpInit(
41
+ limbuildApiUrl: string,
42
+ appleSessionId: string,
43
+ payload: AppleSRPInitRequest,
44
+ token?: string,
45
+ ) {
46
+ return postAppleProxy<AppleSRPInitResponse>(
47
+ limbuildApiUrl,
48
+ '/apple/auth/srp/init',
49
+ appleSessionId,
50
+ payload,
51
+ token,
52
+ );
53
+ }
54
+
55
+ export async function proxySrpComplete(
56
+ limbuildApiUrl: string,
57
+ appleSessionId: string,
58
+ payload: AppleSRPCompleteProof & {
59
+ rememberMe: boolean;
60
+ trustTokens: string[];
61
+ },
62
+ token?: string,
63
+ ) {
64
+ return postAppleProxy(limbuildApiUrl, '/apple/auth/srp/complete', appleSessionId, payload, token);
65
+ }
66
+
67
+ export async function triggerTrustedDeviceTwoFactor(
68
+ limbuildApiUrl: string,
69
+ appleSessionId: string,
70
+ token?: string,
71
+ ) {
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;
81
+ }
82
+
83
+ export async function triggerPhoneTwoFactor(
84
+ limbuildApiUrl: string,
85
+ appleSessionId: string,
86
+ phoneNumberId: number,
87
+ mode = 'sms',
88
+ token?: string,
89
+ ) {
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;
99
+ }
100
+
101
+ export async function proxyTwoFactorCode(
102
+ limbuildApiUrl: string,
103
+ appleSessionId: string,
104
+ code: string,
105
+ token?: string,
106
+ ) {
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()}`);
114
+ }
115
+ return (await response.json()) as AppleRelayResponse;
116
+ }
117
+
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()}`);
133
+ }
134
+ return (await response.json()) as AppleRelayResponse;
135
+ }
136
+
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;
151
+ }
152
+
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()}`);
166
+ }
167
+ return normalizeAppleProxyResponse<T>((await response.json()) as AppleRelayResponse<T>);
168
+ }
169
+
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()}`);
184
+ }
185
+ return normalizeAppleProxyResponse<T>((await response.json()) as AppleRelayResponse<T>);
186
+ }
187
+
188
+ function normalizeAppleProxyResponse<T>(response: AppleRelayResponse<T>) {
189
+ if (response.body !== undefined || !response.rawBody) {
190
+ return response;
191
+ }
192
+ try {
193
+ return {
194
+ ...response,
195
+ body: JSON.parse(response.rawBody) as T,
196
+ };
197
+ } catch {
198
+ return response;
199
+ }
200
+ }
201
+
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);
208
+ }
209
+ return url;
210
+ }
211
+
212
+ function jsonHeaders(token?: string): Record<string, string> {
213
+ return {
214
+ 'Content-Type': 'application/json',
215
+ ...authHeaders(token),
216
+ };
217
+ }
218
+
219
+ function authHeaders(token?: string): Record<string, string> {
220
+ return token ? { Authorization: `Bearer ${token}` } : {};
221
+ }
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export * from './apple';
3
+ export * from './operations';
4
+ export * from './storage';
@@ -0,0 +1,6 @@
1
+ export * from './limbuild-client';
2
+ export * from './operations';
3
+ export * from './relay-client';
4
+ export * from './relay-protocol';
5
+ export * from './usbmux';
6
+ export * from './webusb';