@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.
Files changed (80) hide show
  1. package/README.md +25 -8
  2. package/dist/app-store-relay/index.cjs +9 -0
  3. package/dist/app-store-relay/index.d.ts +102 -0
  4. package/dist/app-store-relay/index.js +486 -0
  5. package/dist/app-store-relay/react.cjs +1 -0
  6. package/dist/app-store-relay/react.d.ts +15 -0
  7. package/dist/app-store-relay/react.js +65 -0
  8. package/dist/browser-storage-Cx9eW-bm.mjs +205 -0
  9. package/dist/browser-storage-jJaIht4K.js +1 -0
  10. package/dist/client-ASLtHFLh.js +1 -0
  11. package/dist/client-CGdh8yoJ.mjs +234 -0
  12. package/dist/components/device-install/index.d.ts +1 -2
  13. package/dist/core/device-install/apple/provisioning.d.ts +58 -7
  14. package/dist/core/device-install/operations/limbuild-client.d.ts +15 -1
  15. package/dist/core/device-install/storage/browser-storage.d.ts +9 -5
  16. package/dist/core/device-install/types.d.ts +4 -1
  17. package/dist/device-build/index.cjs +1 -0
  18. package/dist/device-build/index.d.ts +4 -0
  19. package/dist/device-build/index.js +84 -0
  20. package/dist/device-build/react.cjs +1 -0
  21. package/dist/device-build/react.d.ts +20 -0
  22. package/dist/device-build/react.js +68 -0
  23. package/dist/device-build/signing.d.ts +20 -0
  24. package/dist/device-install/demo/demo.d.ts +0 -0
  25. package/dist/device-install/index.cjs +1 -1
  26. package/dist/device-install/index.d.ts +18 -3
  27. package/dist/device-install/index.js +583 -76
  28. package/dist/device-install/react.cjs +1 -1
  29. package/dist/device-install/react.d.ts +23 -1
  30. package/dist/device-install/react.js +98 -2
  31. package/dist/hooks/index.d.ts +1 -1
  32. package/dist/index-CMeQfhYy.js +22 -0
  33. package/dist/index-DFdCvi2_.mjs +11543 -0
  34. package/dist/index.cjs +1 -1
  35. package/dist/index.d.ts +0 -2
  36. package/dist/index.js +502 -495
  37. package/dist/limbuild-client-CgEncrgx.js +1 -0
  38. package/dist/limbuild-client-D7U7w342.mjs +81 -0
  39. package/dist/provisioning-DKbkBO-p.mjs +239 -0
  40. package/dist/provisioning-Va0PH4Kk.js +1 -0
  41. package/package.json +21 -1
  42. package/src/app-store-relay/index.test.ts +70 -0
  43. package/src/app-store-relay/index.ts +514 -0
  44. package/src/app-store-relay/react.ts +129 -0
  45. package/src/components/device-install/index.ts +1 -2
  46. package/src/core/device-install/apple/client.ts +29 -22
  47. package/src/core/device-install/apple/crypto.ts +9 -6
  48. package/src/core/device-install/apple/gsa-srp.ts +3 -7
  49. package/src/core/device-install/apple/provisioning.test.ts +84 -0
  50. package/src/core/device-install/apple/provisioning.ts +98 -16
  51. package/src/core/device-install/apple/relay.ts +5 -1
  52. package/src/core/device-install/operations/limbuild-client.ts +41 -4
  53. package/src/core/device-install/operations/operations.ts +3 -1
  54. package/src/core/device-install/operations/relay-client.ts +57 -27
  55. package/src/core/device-install/operations/usbmux.ts +3 -3
  56. package/src/core/device-install/operations/webusb.ts +9 -4
  57. package/src/core/device-install/storage/browser-storage.ts +43 -14
  58. package/src/core/device-install/types.ts +6 -8
  59. package/src/device-build/index.ts +42 -0
  60. package/src/device-build/react.ts +131 -0
  61. package/src/device-build/signing.ts +94 -0
  62. package/src/device-install/README.md +423 -0
  63. package/src/device-install/demo/demo.css +493 -0
  64. package/src/device-install/demo/demo.tsx +1069 -0
  65. package/src/device-install/demo/index.html +12 -0
  66. package/src/device-install/index.ts +49 -3
  67. package/src/device-install/react.ts +189 -1
  68. package/src/hooks/index.ts +1 -1
  69. package/src/index.ts +0 -3
  70. package/vite.config.ts +4 -0
  71. package/dist/components/device-install/device-install-dialog.d.ts +0 -5
  72. package/dist/device-install-dialog-CqxDEH85.mjs +0 -443
  73. package/dist/device-install-dialog-jvqQ-fuo.js +0 -2
  74. package/dist/device-install-dialog.css +0 -1
  75. package/dist/hooks/use-device-install.d.ts +0 -73
  76. package/dist/use-device-install-CAPli9MR.js +0 -31
  77. package/dist/use-device-install-H8dqqvbR.mjs +0 -13624
  78. package/src/components/device-install/device-install-dialog.css +0 -325
  79. package/src/components/device-install/device-install-dialog.tsx +0 -495
  80. package/src/hooks/use-device-install.ts +0 -1219
@@ -0,0 +1,514 @@
1
+ import {
2
+ createAdHocProfileRequest,
3
+ createBundleIDRequest,
4
+ createDevelopmentProfileRequest,
5
+ downloadCertificateRequest,
6
+ downloadDistributionCertificateRequest,
7
+ downloadProfileRequest,
8
+ findAdHocProfilesRequest,
9
+ findBundleIDRequest,
10
+ findDevelopmentCertificatesRequest,
11
+ findDevelopmentProfilesRequest,
12
+ findDeviceRequest,
13
+ findDistributionCertificatesRequest,
14
+ listTeamsRequest,
15
+ proxyProvisioningRequest,
16
+ registerDeviceRequest,
17
+ submitDevelopmentCSRRequest,
18
+ submitDistributionCSRRequest,
19
+ type AppleDeveloperPortalAppID,
20
+ type AppleDeveloperPortalDevice,
21
+ type AppleDeveloperPortalResponse,
22
+ type AppleDeveloperPortalTeam,
23
+ type AppleProvisioningRequest,
24
+ type AppleRelayResponse,
25
+ } from '../core/device-install/apple';
26
+
27
+ export * from '../core/device-install/apple/client';
28
+ export * from '../core/device-install/apple/crypto';
29
+ export * from '../core/device-install/apple/gsa-srp';
30
+ export type {
31
+ AppleDeveloperPortalAppID,
32
+ AppleDeveloperPortalDevice,
33
+ AppleDeveloperPortalResponse,
34
+ AppleDeveloperPortalTeam,
35
+ AppleProvisioningRequest,
36
+ AppleRelayResponse,
37
+ } from '../core/device-install/apple';
38
+ export {
39
+ createAppleRelaySession,
40
+ deleteAppleRelaySession,
41
+ fetchAppleAccountSession,
42
+ proxyPhoneTwoFactorCode,
43
+ proxySrpComplete,
44
+ proxySrpInit,
45
+ proxyTwoFactorCode,
46
+ triggerPhoneTwoFactor,
47
+ triggerTrustedDeviceTwoFactor,
48
+ } from '../core/device-install/apple';
49
+
50
+ export type AppleRelayClientOptions = {
51
+ apiUrl: string;
52
+ token?: string;
53
+ appleSessionId: string;
54
+ };
55
+
56
+ export type AppleTeamScopedOptions = AppleRelayClientOptions & {
57
+ teamId?: string;
58
+ };
59
+
60
+ export type AppleCertificateKind = 'development' | 'distribution';
61
+
62
+ export type AppleProfileKind = 'development' | 'adhoc';
63
+
64
+ export type RequestAppleProvisioningOptions<T = unknown> = AppleRelayClientOptions & {
65
+ request: AppleProvisioningRequest;
66
+ label?: string;
67
+ validatePortalResponse?: boolean;
68
+ mapBody?: (body: AppleDeveloperPortalResponse | undefined, response: AppleRelayResponse<T>) => T;
69
+ };
70
+
71
+ export type MappedAppleProvisioningOptions<T> = RequestAppleProvisioningOptions<T> & {
72
+ mapBody: (body: AppleDeveloperPortalResponse | undefined, response: AppleRelayResponse<T>) => T;
73
+ };
74
+
75
+ export type ListAppleCertificatesOptions = AppleTeamScopedOptions & {
76
+ certificateKind?: AppleCertificateKind;
77
+ };
78
+
79
+ export type CreateAppleCertificateOptions = AppleTeamScopedOptions & {
80
+ certificateKind?: AppleCertificateKind;
81
+ csrPEM: string;
82
+ };
83
+
84
+ export type DownloadAppleCertificateOptions = AppleTeamScopedOptions & {
85
+ certificateKind?: AppleCertificateKind;
86
+ certificateId: string;
87
+ };
88
+
89
+ export type DeleteAppleCertificateOptions = DownloadAppleCertificateOptions;
90
+
91
+ export type ListAppleBundleIDsOptions = AppleTeamScopedOptions & {
92
+ search?: string;
93
+ };
94
+
95
+ export type CreateAppleBundleIDOptions = AppleTeamScopedOptions & {
96
+ bundleId: string;
97
+ name?: string;
98
+ };
99
+
100
+ export type UpdateAppleBundleIDOptions = AppleTeamScopedOptions & {
101
+ appIdId: string;
102
+ bundleId?: string;
103
+ name?: string;
104
+ };
105
+
106
+ export type DeleteAppleBundleIDOptions = AppleTeamScopedOptions & {
107
+ appIdId: string;
108
+ };
109
+
110
+ export type ListAppleDevicesOptions = AppleTeamScopedOptions & {
111
+ deviceUDID?: string;
112
+ };
113
+
114
+ export type RegisterAppleDeviceOptions = AppleTeamScopedOptions & {
115
+ deviceUDID: string;
116
+ name?: string;
117
+ };
118
+
119
+ export type UpdateAppleDeviceOptions = AppleTeamScopedOptions & {
120
+ deviceId: string;
121
+ name?: string;
122
+ };
123
+
124
+ export type DeleteAppleDeviceOptions = AppleTeamScopedOptions & {
125
+ deviceId: string;
126
+ };
127
+
128
+ export type ListAppleProfilesOptions = AppleTeamScopedOptions & {
129
+ profileKind?: AppleProfileKind;
130
+ bundleId?: string;
131
+ };
132
+
133
+ export type CreateAppleProfileOptions = AppleTeamScopedOptions & {
134
+ profileKind?: AppleProfileKind;
135
+ bundleId: string;
136
+ appIdId: string;
137
+ certificateIds: string[];
138
+ deviceIds: string[];
139
+ name?: string;
140
+ };
141
+
142
+ export type DownloadAppleProfileOptions = AppleTeamScopedOptions & {
143
+ profileId: string;
144
+ };
145
+
146
+ export type DeleteAppleProfileOptions = DownloadAppleProfileOptions;
147
+
148
+ export function requestAppleProvisioning<T>(options: MappedAppleProvisioningOptions<T>): Promise<T>;
149
+ export function requestAppleProvisioning<T = AppleDeveloperPortalResponse>(
150
+ options: RequestAppleProvisioningOptions<T>,
151
+ ): Promise<AppleRelayResponse<T>>;
152
+ export async function requestAppleProvisioning<T = AppleDeveloperPortalResponse>({
153
+ apiUrl,
154
+ token,
155
+ appleSessionId,
156
+ request,
157
+ label = 'Apple Developer Portal request',
158
+ validatePortalResponse = true,
159
+ mapBody,
160
+ }: RequestAppleProvisioningOptions<T>) {
161
+ const response = await proxyProvisioningRequest<T>(apiUrl, appleSessionId, request, token);
162
+ if (validatePortalResponse) {
163
+ assertAppleDeveloperPortalResponseOK(response.body as AppleDeveloperPortalResponse | undefined, label);
164
+ }
165
+ return mapBody ? mapBody(response.body as AppleDeveloperPortalResponse | undefined, response) : response;
166
+ }
167
+
168
+ export async function listAppleTeams(options: AppleRelayClientOptions) {
169
+ return requestAppleProvisioning<AppleDeveloperPortalTeam[]>({
170
+ ...options,
171
+ request: listTeamsRequest(),
172
+ label: 'Apple Developer team list',
173
+ mapBody: (body) =>
174
+ uniqueAppleTeams([
175
+ ...(body?.teams ?? []),
176
+ ...(body?.availableProviders ?? []),
177
+ ...(body?.provider ? [body.provider] : []),
178
+ ]),
179
+ });
180
+ }
181
+
182
+ export async function listAppleCertificates({
183
+ certificateKind = 'development',
184
+ teamId = '',
185
+ ...options
186
+ }: ListAppleCertificatesOptions) {
187
+ return requestAppleProvisioning<Array<Record<string, unknown>>>({
188
+ ...options,
189
+ request:
190
+ certificateKind === 'distribution' ?
191
+ findDistributionCertificatesRequest(teamId)
192
+ : findDevelopmentCertificatesRequest(teamId),
193
+ label: 'Apple Developer certificate list',
194
+ mapBody: (body) => body?.certRequests ?? [],
195
+ });
196
+ }
197
+
198
+ export async function createAppleCertificate({
199
+ certificateKind = 'development',
200
+ teamId = '',
201
+ csrPEM,
202
+ ...options
203
+ }: CreateAppleCertificateOptions) {
204
+ return requestAppleProvisioning<Record<string, unknown> | undefined>({
205
+ ...options,
206
+ request:
207
+ certificateKind === 'distribution' ?
208
+ submitDistributionCSRRequest({ csrPEM, teamID: teamId })
209
+ : submitDevelopmentCSRRequest({ csrPEM, teamID: teamId }),
210
+ label:
211
+ certificateKind === 'distribution' ?
212
+ 'Apple Distribution certificate creation'
213
+ : 'Apple Development certificate creation',
214
+ mapBody: (body) => body?.certRequest,
215
+ });
216
+ }
217
+
218
+ export async function downloadAppleCertificate({
219
+ certificateKind = 'development',
220
+ certificateId,
221
+ teamId = '',
222
+ ...options
223
+ }: DownloadAppleCertificateOptions) {
224
+ return requestAppleProvisioning<AppleRelayResponse>({
225
+ ...options,
226
+ request:
227
+ certificateKind === 'distribution' ?
228
+ downloadDistributionCertificateRequest(certificateId, teamId)
229
+ : downloadCertificateRequest(certificateId, teamId),
230
+ label: 'Apple certificate download',
231
+ validatePortalResponse: false,
232
+ mapBody: (_body, response) => response,
233
+ });
234
+ }
235
+
236
+ export async function deleteAppleCertificate({
237
+ certificateKind = 'development',
238
+ certificateId,
239
+ teamId = '',
240
+ ...options
241
+ }: DeleteAppleCertificateOptions) {
242
+ return requestAppleProvisioning<AppleDeveloperPortalResponse | undefined>({
243
+ ...options,
244
+ request: {
245
+ method: 'POST',
246
+ path: '/account/ios/certificate/revokeCertificate.action',
247
+ payload: {
248
+ teamId,
249
+ certificateId,
250
+ type: certificateKind === 'distribution' ? 'WXV89964HE' : '83Q87W3TGH',
251
+ },
252
+ },
253
+ label: 'Apple certificate deletion',
254
+ mapBody: (body) => body,
255
+ });
256
+ }
257
+
258
+ export async function listAppleBundleIDs({
259
+ teamId = '',
260
+ search = '',
261
+ ...options
262
+ }: ListAppleBundleIDsOptions) {
263
+ const query = search.trim().toLowerCase();
264
+ return requestAppleProvisioning<AppleDeveloperPortalAppID[]>({
265
+ ...options,
266
+ request: findBundleIDRequest({ bundleID: search, teamID: teamId }),
267
+ label: 'Apple bundle ID list',
268
+ // Apple's listAppIds.action returns the full paginated list and does not
269
+ // honor a server-side search filter, so apply the `search` filter here.
270
+ mapBody: (body) => {
271
+ const appIds = body?.appIds ?? [];
272
+ if (!query) return appIds;
273
+ return appIds.filter((appId) =>
274
+ [appId.identifier, appId.bundleId, appId.name].some(
275
+ (value) => typeof value === 'string' && value.toLowerCase().includes(query),
276
+ ),
277
+ );
278
+ },
279
+ });
280
+ }
281
+
282
+ export async function createAppleBundleID({
283
+ teamId = '',
284
+ bundleId,
285
+ name,
286
+ ...options
287
+ }: CreateAppleBundleIDOptions) {
288
+ return requestAppleProvisioning<Record<string, unknown> | undefined>({
289
+ ...options,
290
+ request: createBundleIDRequest({ bundleID: bundleId, teamID: teamId, name }),
291
+ label: 'Apple bundle ID creation',
292
+ mapBody: (body) => body?.appId,
293
+ });
294
+ }
295
+
296
+ export async function updateAppleBundleID({
297
+ teamId = '',
298
+ appIdId,
299
+ bundleId,
300
+ name,
301
+ ...options
302
+ }: UpdateAppleBundleIDOptions) {
303
+ return requestAppleProvisioning<Record<string, unknown> | undefined>({
304
+ ...options,
305
+ request: {
306
+ method: 'POST',
307
+ path: '/account/ios/identifiers/updateAppId.action',
308
+ payload: {
309
+ teamId,
310
+ appIdId,
311
+ ...(bundleId ? { identifier: bundleId } : {}),
312
+ ...(name ? { name } : {}),
313
+ },
314
+ },
315
+ label: 'Apple bundle ID update',
316
+ mapBody: (body) => body?.appId,
317
+ });
318
+ }
319
+
320
+ export async function deleteAppleBundleID({ teamId = '', appIdId, ...options }: DeleteAppleBundleIDOptions) {
321
+ return requestAppleProvisioning<AppleDeveloperPortalResponse | undefined>({
322
+ ...options,
323
+ request: {
324
+ method: 'POST',
325
+ path: '/account/ios/identifiers/deleteAppId.action',
326
+ payload: { teamId, appIdId },
327
+ },
328
+ label: 'Apple bundle ID deletion',
329
+ mapBody: (body) => body,
330
+ });
331
+ }
332
+
333
+ export async function listAppleDevices({
334
+ teamId = '',
335
+ deviceUDID = '',
336
+ ...options
337
+ }: ListAppleDevicesOptions) {
338
+ const wantedUDID = normalizeAppleUDID(deviceUDID);
339
+ return requestAppleProvisioning<AppleDeveloperPortalDevice[]>({
340
+ ...options,
341
+ request: findDeviceRequest({ deviceUDID, teamID: teamId }),
342
+ label: 'Apple device list',
343
+ // Apple's listDevices.action returns the full paginated list and does not
344
+ // honor a server-side UDID filter, so apply the `deviceUDID` filter here.
345
+ mapBody: (body) => {
346
+ const devices = body?.devices ?? [];
347
+ if (!wantedUDID) return devices;
348
+ return devices.filter((device) => normalizeAppleUDID(device.deviceNumber) === wantedUDID);
349
+ },
350
+ });
351
+ }
352
+
353
+ function normalizeAppleUDID(udid?: string) {
354
+ return (udid ?? '')
355
+ .replace(/-/g, '')
356
+ .replace(/[^a-fA-F0-9]/g, '')
357
+ .toUpperCase();
358
+ }
359
+
360
+ export async function registerAppleDevice({
361
+ teamId = '',
362
+ deviceUDID,
363
+ name,
364
+ ...options
365
+ }: RegisterAppleDeviceOptions) {
366
+ return requestAppleProvisioning<Record<string, unknown> | undefined>({
367
+ ...options,
368
+ request: registerDeviceRequest({ deviceUDID, teamID: teamId, name }),
369
+ label: 'Apple device registration',
370
+ mapBody: (body) => body?.device,
371
+ });
372
+ }
373
+
374
+ export async function updateAppleDevice({
375
+ teamId = '',
376
+ deviceId,
377
+ name,
378
+ ...options
379
+ }: UpdateAppleDeviceOptions) {
380
+ return requestAppleProvisioning<Record<string, unknown> | undefined>({
381
+ ...options,
382
+ request: {
383
+ method: 'POST',
384
+ path: '/account/ios/device/updateDevice.action',
385
+ payload: { teamId, deviceId, ...(name ? { name } : {}) },
386
+ },
387
+ label: 'Apple device update',
388
+ mapBody: (body) => body?.device,
389
+ });
390
+ }
391
+
392
+ export async function deleteAppleDevice({ teamId = '', deviceId, ...options }: DeleteAppleDeviceOptions) {
393
+ return requestAppleProvisioning<AppleDeveloperPortalResponse | undefined>({
394
+ ...options,
395
+ request: {
396
+ method: 'POST',
397
+ path: '/account/ios/device/deleteDevice.action',
398
+ payload: { teamId, deviceId },
399
+ },
400
+ label: 'Apple device deletion',
401
+ mapBody: (body) => body,
402
+ });
403
+ }
404
+
405
+ export async function listAppleProfiles({
406
+ profileKind = 'development',
407
+ teamId = '',
408
+ bundleId = '',
409
+ ...options
410
+ }: ListAppleProfilesOptions) {
411
+ return requestAppleProvisioning<Array<Record<string, unknown>>>({
412
+ ...options,
413
+ request:
414
+ profileKind === 'adhoc' ?
415
+ findAdHocProfilesRequest({ bundleID: bundleId, teamID: teamId })
416
+ : findDevelopmentProfilesRequest({ bundleID: bundleId, teamID: teamId }),
417
+ label: 'Apple provisioning profile list',
418
+ mapBody: (body) => body?.provisioningProfiles ?? [],
419
+ });
420
+ }
421
+
422
+ export async function createAppleProfile({
423
+ profileKind = 'development',
424
+ teamId = '',
425
+ bundleId,
426
+ appIdId,
427
+ certificateIds,
428
+ deviceIds,
429
+ name,
430
+ ...options
431
+ }: CreateAppleProfileOptions) {
432
+ const [certificateId] = certificateIds;
433
+ if (!certificateId) {
434
+ throw new Error('At least one certificate ID is required to create an Apple provisioning profile.');
435
+ }
436
+ return requestAppleProvisioning<Record<string, unknown> | undefined>({
437
+ ...options,
438
+ request:
439
+ profileKind === 'adhoc' ?
440
+ createAdHocProfileRequest({
441
+ bundleID: bundleId,
442
+ teamID: teamId,
443
+ appIDID: appIdId,
444
+ certificateID: certificateId,
445
+ deviceIDs: deviceIds,
446
+ name,
447
+ })
448
+ : createDevelopmentProfileRequest({
449
+ bundleID: bundleId,
450
+ teamID: teamId,
451
+ appIDID: appIdId,
452
+ certificateID: certificateId,
453
+ deviceIDs: deviceIds,
454
+ name,
455
+ }),
456
+ label: 'Apple provisioning profile creation',
457
+ mapBody: (body) => body?.provisioningProfile,
458
+ });
459
+ }
460
+
461
+ export async function downloadAppleProfile({
462
+ teamId = '',
463
+ profileId,
464
+ ...options
465
+ }: DownloadAppleProfileOptions) {
466
+ return requestAppleProvisioning<AppleRelayResponse>({
467
+ ...options,
468
+ request: downloadProfileRequest(profileId, teamId),
469
+ label: 'Apple provisioning profile download',
470
+ validatePortalResponse: false,
471
+ mapBody: (_body, response) => response,
472
+ });
473
+ }
474
+
475
+ export async function deleteAppleProfile({ teamId = '', profileId, ...options }: DeleteAppleProfileOptions) {
476
+ return requestAppleProvisioning<AppleDeveloperPortalResponse | undefined>({
477
+ ...options,
478
+ request: {
479
+ method: 'POST',
480
+ path: '/account/ios/profile/deleteProvisioningProfile.action',
481
+ payload: { teamId, provisioningProfileId: profileId },
482
+ },
483
+ label: 'Apple provisioning profile deletion',
484
+ mapBody: (body) => body,
485
+ });
486
+ }
487
+
488
+ export function assertAppleDeveloperPortalResponseOK(
489
+ response: AppleDeveloperPortalResponse | undefined,
490
+ label: string,
491
+ ) {
492
+ if (!response) {
493
+ throw new Error(`${label} returned an empty response.`);
494
+ }
495
+ if (response.resultCode !== undefined && response.resultCode !== 0) {
496
+ throw new Error(
497
+ `${label} failed: ${response.userString ?? response.resultString ?? response.resultCode}`,
498
+ );
499
+ }
500
+ }
501
+
502
+ function uniqueAppleTeams(teams: AppleDeveloperPortalTeam[]) {
503
+ const seen = new Set<string>();
504
+ const result: AppleDeveloperPortalTeam[] = [];
505
+ for (const team of teams) {
506
+ const key = String(
507
+ team.teamId ?? team.providerId ?? team.publicProviderId ?? team.name ?? JSON.stringify(team),
508
+ );
509
+ if (seen.has(key)) continue;
510
+ seen.add(key);
511
+ result.push(team);
512
+ }
513
+ return result;
514
+ }
@@ -0,0 +1,129 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import {
3
+ startBrowserOwnedAppleIDLogin,
4
+ type AppleIDLoginInput,
5
+ type AppleIDLoginResult,
6
+ type AppleRelayResponse,
7
+ } from './index';
8
+
9
+ export type UseAppleIDLoginOptions = Pick<AppleIDLoginInput, 'limbuildApiUrl' | 'token'>;
10
+
11
+ export type AppleIDLoginStatus =
12
+ | 'idle'
13
+ | 'authenticating'
14
+ | 'two-factor-required'
15
+ | 'authenticated'
16
+ | 'error';
17
+
18
+ export type UseAppleIDLoginResult = {
19
+ status: AppleIDLoginStatus;
20
+ session?: AppleIDLoginResult;
21
+ completeResponse?: AppleRelayResponse;
22
+ twoFactorChallengeResponse?: AppleRelayResponse;
23
+ error?: string;
24
+ startLogin: (
25
+ input: Pick<AppleIDLoginInput, 'accountName' | 'password'>,
26
+ ) => Promise<AppleIDLoginResult | undefined>;
27
+ submitTwoFactorCode: (code: string) => Promise<AppleRelayResponse | undefined>;
28
+ finalize: () => Promise<AppleRelayResponse | undefined>;
29
+ close: () => Promise<void>;
30
+ };
31
+
32
+ export function useAppleIDLogin({ limbuildApiUrl, token }: UseAppleIDLoginOptions): UseAppleIDLoginResult {
33
+ const [status, setStatus] = useState<AppleIDLoginStatus>('idle');
34
+ const [session, setSession] = useState<AppleIDLoginResult | undefined>();
35
+ const [completeResponse, setCompleteResponse] = useState<AppleRelayResponse | undefined>();
36
+ const [twoFactorChallengeResponse, setTwoFactorChallengeResponse] = useState<
37
+ AppleRelayResponse | undefined
38
+ >();
39
+ const [error, setError] = useState<string | undefined>();
40
+ const sessionRef = useRef<AppleIDLoginResult | undefined>(undefined);
41
+
42
+ const close = useCallback(async () => {
43
+ await sessionRef.current?.close().catch(() => undefined);
44
+ sessionRef.current = undefined;
45
+ setSession(undefined);
46
+ setCompleteResponse(undefined);
47
+ setTwoFactorChallengeResponse(undefined);
48
+ setError(undefined);
49
+ setStatus('idle');
50
+ }, []);
51
+
52
+ useEffect(() => {
53
+ return () => {
54
+ void sessionRef.current?.close();
55
+ sessionRef.current = undefined;
56
+ };
57
+ }, []);
58
+
59
+ const startLogin = useCallback(
60
+ async ({ accountName, password }: Pick<AppleIDLoginInput, 'accountName' | 'password'>) => {
61
+ setStatus('authenticating');
62
+ setError(undefined);
63
+ await sessionRef.current?.close().catch(() => undefined);
64
+ try {
65
+ const next = await startBrowserOwnedAppleIDLogin({
66
+ limbuildApiUrl,
67
+ token,
68
+ accountName,
69
+ password,
70
+ });
71
+ sessionRef.current = next;
72
+ setSession(next);
73
+ setCompleteResponse(next.completeResponse);
74
+ setTwoFactorChallengeResponse(next.twoFactorChallengeResponse);
75
+ setStatus(next.requiresTwoFactor ? 'two-factor-required' : 'authenticated');
76
+ return next;
77
+ } catch (caught) {
78
+ setSession(undefined);
79
+ sessionRef.current = undefined;
80
+ setError(errorMessage(caught));
81
+ setStatus('error');
82
+ return undefined;
83
+ }
84
+ },
85
+ [limbuildApiUrl, token],
86
+ );
87
+
88
+ const submitTwoFactorCode = useCallback(async (code: string) => {
89
+ const current = sessionRef.current;
90
+ if (!current) {
91
+ throw new Error('Start Apple ID login before submitting a two-factor code.');
92
+ }
93
+ setStatus('authenticating');
94
+ setError(undefined);
95
+ try {
96
+ const response = await current.finishTwoFactor(code);
97
+ setStatus('authenticated');
98
+ return response;
99
+ } catch (caught) {
100
+ setError(errorMessage(caught));
101
+ setStatus('error');
102
+ return undefined;
103
+ }
104
+ }, []);
105
+
106
+ const finalize = useCallback(async () => {
107
+ const current = sessionRef.current;
108
+ if (!current) {
109
+ throw new Error('Start Apple ID login before finalizing the Apple session.');
110
+ }
111
+ return current.finalize();
112
+ }, []);
113
+
114
+ return {
115
+ status,
116
+ session,
117
+ completeResponse,
118
+ twoFactorChallengeResponse,
119
+ error,
120
+ startLogin,
121
+ submitTwoFactorCode,
122
+ finalize,
123
+ close,
124
+ };
125
+ }
126
+
127
+ function errorMessage(error: unknown) {
128
+ return error instanceof Error ? error.message : String(error);
129
+ }
@@ -1,2 +1 @@
1
- export { DeviceInstallDialog, DeviceInstallDialog as DeviceInstallRelay } from './device-install-dialog';
2
- export type { DeviceInstallDialogProps } from './device-install-dialog';
1
+ export {};