@limrun/ui 0.13.7 → 0.14.0-rc.1

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 (47) hide show
  1. package/dist/app-store-relay/index.cjs +3 -3
  2. package/dist/app-store-relay/index.d.ts +3 -5
  3. package/dist/app-store-relay/index.js +56 -58
  4. package/dist/app-store-relay/react.cjs +1 -1
  5. package/dist/app-store-relay/react.d.ts +2 -2
  6. package/dist/app-store-relay/react.js +35 -34
  7. package/dist/client-BtC4n_qf.js +1 -0
  8. package/dist/client-CBX-hLnL.mjs +189 -0
  9. package/dist/core/device-install/apple/client.d.ts +5 -4
  10. package/dist/core/device-install/apple/relay.d.ts +24 -13
  11. package/dist/core/device-install/operations/limbuild-client.d.ts +2 -1
  12. package/dist/core/device-install/operations/operations.d.ts +19 -4
  13. package/dist/core/device-install/operations/relay-client.d.ts +2 -1
  14. package/dist/device-build/index.cjs +1 -1
  15. package/dist/device-build/index.js +1 -1
  16. package/dist/device-build/react.cjs +1 -1
  17. package/dist/device-build/react.d.ts +1 -0
  18. package/dist/device-build/react.js +34 -33
  19. package/dist/device-install/index.cjs +1 -1
  20. package/dist/device-install/index.d.ts +1 -1
  21. package/dist/device-install/index.js +121 -119
  22. package/dist/device-install/react.cjs +1 -1
  23. package/dist/device-install/react.d.ts +4 -3
  24. package/dist/device-install/react.js +58 -54
  25. package/dist/index.cjs +1 -1
  26. package/dist/index.js +519 -531
  27. package/dist/limbuild-client-BagK_FWE.mjs +83 -0
  28. package/dist/limbuild-client-CVDwq54z.js +1 -0
  29. package/package.json +1 -1
  30. package/src/app-store-relay/index.test.ts +19 -29
  31. package/src/app-store-relay/index.ts +6 -9
  32. package/src/app-store-relay/react.ts +5 -4
  33. package/src/components/remote-control.tsx +1 -28
  34. package/src/core/device-install/apple/client.ts +22 -33
  35. package/src/core/device-install/apple/relay.ts +108 -148
  36. package/src/core/device-install/operations/limbuild-client.ts +3 -0
  37. package/src/core/device-install/operations/operations.ts +21 -9
  38. package/src/core/device-install/operations/relay-client.ts +3 -2
  39. package/src/device-build/react.ts +2 -0
  40. package/src/device-install/README.md +5 -5
  41. package/src/device-install/demo/demo.tsx +26 -28
  42. package/src/device-install/index.ts +1 -0
  43. package/src/device-install/react.ts +12 -6
  44. package/dist/client-ASLtHFLh.js +0 -1
  45. package/dist/client-CGdh8yoJ.mjs +0 -234
  46. package/dist/limbuild-client-CgEncrgx.js +0 -1
  47. package/dist/limbuild-client-D7U7w342.mjs +0 -81
@@ -0,0 +1,83 @@
1
+ async function u(r, t) {
2
+ const n = new URL(`${r}/info`);
3
+ t && n.searchParams.set("token", t);
4
+ const e = await fetch(n.toString(), {
5
+ headers: t ? { Authorization: `Bearer ${t}` } : void 0
6
+ });
7
+ if (!e.ok) {
8
+ const s = await e.text();
9
+ throw new Error(`Info request failed: HTTP ${e.status} ${s}`);
10
+ }
11
+ return await e.json();
12
+ }
13
+ async function l({
14
+ limbuildApiUrl: r,
15
+ token: t,
16
+ certificateP12Base64: n,
17
+ certificatePassword: e,
18
+ provisioningProfileBase64: s,
19
+ signedUploadUrl: i
20
+ }) {
21
+ const d = new URL(`${r}/exec`);
22
+ t && d.searchParams.set("token", t);
23
+ const a = await fetch(d.toString(), {
24
+ method: "POST",
25
+ headers: {
26
+ "Content-Type": "application/json",
27
+ ...t ? { Authorization: `Bearer ${t}` } : {}
28
+ },
29
+ body: JSON.stringify({
30
+ command: "xcodebuild",
31
+ xcodebuild: { sdk: "iphoneos" },
32
+ ...i ? { signedUploadUrl: i } : {},
33
+ signing: {
34
+ certificateP12Base64: n,
35
+ ...e ? { certificatePassword: e } : {},
36
+ provisioningProfileBase64: s
37
+ }
38
+ })
39
+ });
40
+ if (!a.ok) {
41
+ const o = await a.text();
42
+ throw new Error(`Build request failed: HTTP ${a.status} ${o}`);
43
+ }
44
+ return await a.json();
45
+ }
46
+ async function f({ limbuildApiUrl: r, execId: t, token: n }) {
47
+ const e = new URL(`${r}/exec/${encodeURIComponent(t)}/ios/ota`);
48
+ n && e.searchParams.set("token", n);
49
+ const s = await fetch(e.toString(), {
50
+ headers: n ? { Authorization: `Bearer ${n}` } : void 0
51
+ });
52
+ if (!s.ok) {
53
+ const i = await s.text();
54
+ throw new Error(`OTA install metadata failed: HTTP ${s.status} ${i}`);
55
+ }
56
+ return await s.json();
57
+ }
58
+ function w({
59
+ limbuildApiUrl: r,
60
+ execId: t,
61
+ token: n,
62
+ onLine: e,
63
+ onStatus: s,
64
+ onError: i
65
+ }) {
66
+ const d = new URL(`${r}/exec/${t}/events`);
67
+ n && d.searchParams.set("token", n);
68
+ const a = new EventSource(d.toString());
69
+ return s("running"), a.addEventListener("command", (o) => e({ type: "command", data: o.data })), a.addEventListener("stdout", (o) => e({ type: "stdout", data: o.data })), a.addEventListener("stderr", (o) => e({ type: "stderr", data: o.data })), a.addEventListener("exitCode", (o) => {
70
+ const c = parseInt(o.data, 10);
71
+ s(
72
+ c === 0 ? "succeeded" : c < 0 ? "cancelled" : "failed"
73
+ ), a.close();
74
+ }), a.onerror = () => {
75
+ a.close(), s("failed"), i?.(new Error("Build log stream closed before completion."));
76
+ }, () => a.close();
77
+ }
78
+ export {
79
+ u as f,
80
+ f as g,
81
+ l as s,
82
+ w
83
+ };
@@ -0,0 +1 @@
1
+ "use strict";async function u(r,t){const n=new URL(`${r}/info`);t&&n.searchParams.set("token",t);const e=await fetch(n.toString(),{headers:t?{Authorization:`Bearer ${t}`}:void 0});if(!e.ok){const s=await e.text();throw new Error(`Info request failed: HTTP ${e.status} ${s}`)}return await e.json()}async function l({limbuildApiUrl:r,token:t,certificateP12Base64:n,certificatePassword:e,provisioningProfileBase64:s,signedUploadUrl:i}){const d=new URL(`${r}/exec`);t&&d.searchParams.set("token",t);const a=await fetch(d.toString(),{method:"POST",headers:{"Content-Type":"application/json",...t?{Authorization:`Bearer ${t}`}:{}},body:JSON.stringify({command:"xcodebuild",xcodebuild:{sdk:"iphoneos"},...i?{signedUploadUrl:i}:{},signing:{certificateP12Base64:n,...e?{certificatePassword:e}:{},provisioningProfileBase64:s}})});if(!a.ok){const o=await a.text();throw new Error(`Build request failed: HTTP ${a.status} ${o}`)}return await a.json()}async function f({limbuildApiUrl:r,execId:t,token:n}){const e=new URL(`${r}/exec/${encodeURIComponent(t)}/ios/ota`);n&&e.searchParams.set("token",n);const s=await fetch(e.toString(),{headers:n?{Authorization:`Bearer ${n}`}:void 0});if(!s.ok){const i=await s.text();throw new Error(`OTA install metadata failed: HTTP ${s.status} ${i}`)}return await s.json()}function h({limbuildApiUrl:r,execId:t,token:n,onLine:e,onStatus:s,onError:i}){const d=new URL(`${r}/exec/${t}/events`);n&&d.searchParams.set("token",n);const a=new EventSource(d.toString());return s("running"),a.addEventListener("command",o=>e({type:"command",data:o.data})),a.addEventListener("stdout",o=>e({type:"stdout",data:o.data})),a.addEventListener("stderr",o=>e({type:"stderr",data:o.data})),a.addEventListener("exitCode",o=>{const c=parseInt(o.data,10);s(c===0?"succeeded":c<0?"cancelled":"failed"),a.close()}),a.onerror=()=>{a.close(),s("failed"),i?.(new Error("Build log stream closed before completion."))},()=>a.close()}exports.fetchLimbuildInfo=u;exports.getIOSOTAInstall=f;exports.startSignedDeviceBuild=l;exports.watchBuildLogEvents=h;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@limrun/ui",
3
- "version": "0.13.7",
3
+ "version": "0.14.0-rc.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -3,58 +3,48 @@
3
3
  import { afterEach, describe, expect, test, vi } from 'vitest';
4
4
  import { assertAppleDeveloperPortalResponseOK, deleteAppleProfile, listAppleTeams } from './index';
5
5
 
6
- describe('App Store relay primitives', () => {
6
+ describe('Registry relay primitives', () => {
7
7
  afterEach(() => {
8
8
  vi.restoreAllMocks();
9
9
  });
10
10
 
11
11
  test('sends explicit CRUD requests through the provisioning proxy', async () => {
12
- const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
13
- new Response(JSON.stringify({ status: 200, body: { resultCode: 0 } }), {
14
- status: 200,
15
- }),
16
- );
12
+ const relay = { request: vi.fn().mockResolvedValue({ status: 200, body: { resultCode: 0 } }) };
17
13
 
18
14
  await deleteAppleProfile({
19
- apiUrl: 'https://limbuild.example',
20
- token: 'token',
21
- appleSessionId: 'apple-session',
15
+ relay,
22
16
  teamId: 'TEAM',
23
17
  profileId: 'PROFILE',
24
18
  });
25
19
 
26
- expect(fetchMock).toHaveBeenCalledOnce();
27
- const [url, init] = fetchMock.mock.calls[0];
28
- expect(String(url)).toBe('https://limbuild.example/apple/provisioning?token=token');
29
- expect(JSON.parse(String(init?.body))).toMatchObject({
30
- appleSessionId: 'apple-session',
20
+ expect(relay.request).toHaveBeenCalledOnce();
21
+ expect(relay.request.mock.calls[0]).toMatchObject([
22
+ 'provisioning',
23
+ {
31
24
  method: 'POST',
32
25
  path: '/account/ios/profile/deleteProvisioningProfile.action',
33
26
  payload: {
34
27
  teamId: 'TEAM',
35
28
  provisioningProfileId: 'PROFILE',
36
29
  },
37
- });
30
+ },
31
+ ]);
38
32
  });
39
33
 
40
34
  test('maps team list responses to teams', async () => {
41
- vi.spyOn(globalThis, 'fetch').mockResolvedValue(
42
- new Response(
43
- JSON.stringify({
44
- status: 200,
45
- body: {
46
- resultCode: 0,
47
- teams: [{ name: 'Team One', teamId: 'TEAM1' }],
48
- },
49
- }),
50
- { status: 200 },
51
- ),
52
- );
35
+ const relay = {
36
+ request: vi.fn().mockResolvedValue({
37
+ status: 200,
38
+ body: {
39
+ resultCode: 0,
40
+ teams: [{ name: 'Team One', teamId: 'TEAM1' }],
41
+ },
42
+ }),
43
+ };
53
44
 
54
45
  await expect(
55
46
  listAppleTeams({
56
- apiUrl: 'https://limbuild.example',
57
- appleSessionId: 'apple-session',
47
+ relay,
58
48
  }),
59
49
  ).resolves.toEqual([{ name: 'Team One', teamId: 'TEAM1' }]);
60
50
  });
@@ -22,6 +22,7 @@ import {
22
22
  type AppleDeveloperPortalTeam,
23
23
  type AppleProvisioningRequest,
24
24
  type AppleRelayResponse,
25
+ type AppleRelayWebSocketClient,
25
26
  } from '../core/device-install/apple';
26
27
 
27
28
  export * from '../core/device-install/apple/client';
@@ -36,9 +37,9 @@ export type {
36
37
  AppleRelayResponse,
37
38
  } from '../core/device-install/apple';
38
39
  export {
39
- createAppleRelaySession,
40
- deleteAppleRelaySession,
40
+ AppleRelayWebSocketClient,
41
41
  fetchAppleAccountSession,
42
+ openAppleRelayWebSocket,
42
43
  proxyPhoneTwoFactorCode,
43
44
  proxySrpComplete,
44
45
  proxySrpInit,
@@ -48,9 +49,7 @@ export {
48
49
  } from '../core/device-install/apple';
49
50
 
50
51
  export type AppleRelayClientOptions = {
51
- apiUrl: string;
52
- token?: string;
53
- appleSessionId: string;
52
+ relay: AppleRelayWebSocketClient;
54
53
  };
55
54
 
56
55
  export type AppleTeamScopedOptions = AppleRelayClientOptions & {
@@ -150,15 +149,13 @@ export function requestAppleProvisioning<T = AppleDeveloperPortalResponse>(
150
149
  options: RequestAppleProvisioningOptions<T>,
151
150
  ): Promise<AppleRelayResponse<T>>;
152
151
  export async function requestAppleProvisioning<T = AppleDeveloperPortalResponse>({
153
- apiUrl,
154
- token,
155
- appleSessionId,
152
+ relay,
156
153
  request,
157
154
  label = 'Apple Developer Portal request',
158
155
  validatePortalResponse = true,
159
156
  mapBody,
160
157
  }: RequestAppleProvisioningOptions<T>) {
161
- const response = await proxyProvisioningRequest<T>(apiUrl, appleSessionId, request, token);
158
+ const response = await proxyProvisioningRequest<T>(relay, request);
162
159
  if (validatePortalResponse) {
163
160
  assertAppleDeveloperPortalResponseOK(response.body as AppleDeveloperPortalResponse | undefined, label);
164
161
  }
@@ -6,7 +6,7 @@ import {
6
6
  type AppleRelayResponse,
7
7
  } from './index';
8
8
 
9
- export type UseAppleIDLoginOptions = Pick<AppleIDLoginInput, 'limbuildApiUrl' | 'token'>;
9
+ export type UseAppleIDLoginOptions = Pick<AppleIDLoginInput, 'registryApiUrl' | 'token' | 'organizationId'>;
10
10
 
11
11
  export type AppleIDLoginStatus =
12
12
  | 'idle'
@@ -29,7 +29,7 @@ export type UseAppleIDLoginResult = {
29
29
  close: () => Promise<void>;
30
30
  };
31
31
 
32
- export function useAppleIDLogin({ limbuildApiUrl, token }: UseAppleIDLoginOptions): UseAppleIDLoginResult {
32
+ export function useAppleIDLogin({ registryApiUrl, token, organizationId }: UseAppleIDLoginOptions): UseAppleIDLoginResult {
33
33
  const [status, setStatus] = useState<AppleIDLoginStatus>('idle');
34
34
  const [session, setSession] = useState<AppleIDLoginResult | undefined>();
35
35
  const [completeResponse, setCompleteResponse] = useState<AppleRelayResponse | undefined>();
@@ -63,8 +63,9 @@ export function useAppleIDLogin({ limbuildApiUrl, token }: UseAppleIDLoginOption
63
63
  await sessionRef.current?.close().catch(() => undefined);
64
64
  try {
65
65
  const next = await startBrowserOwnedAppleIDLogin({
66
- limbuildApiUrl,
66
+ registryApiUrl,
67
67
  token,
68
+ organizationId,
68
69
  accountName,
69
70
  password,
70
71
  });
@@ -82,7 +83,7 @@ export function useAppleIDLogin({ limbuildApiUrl, token }: UseAppleIDLoginOption
82
83
  return undefined;
83
84
  }
84
85
  },
85
- [limbuildApiUrl, token],
86
+ [registryApiUrl, organizationId, token],
86
87
  );
87
88
 
88
89
  const submitTwoFactorCode = useCallback(async (code: string) => {
@@ -345,7 +345,6 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
345
345
  const videoRef = useRef<HTMLVideoElement>(null);
346
346
  const frameRef = useRef<HTMLImageElement>(null);
347
347
  const [videoLoaded, setVideoLoaded] = useState(false);
348
- const [unmuted, setUnmuted] = useState(false);
349
348
  const [retryExhausted, setRetryExhausted] = useState(false);
350
349
  const [isLandscape, setIsLandscape] = useState(false);
351
350
  const [useAndroidTabletFrame, setUseAndroidTabletFrame] = useState(false);
@@ -478,33 +477,11 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
478
477
  const platform = useMemo(() => detectPlatform(url), [url]);
479
478
  const config = deviceConfig[platform];
480
479
 
481
- useEffect(() => {
482
- setUnmuted(false);
483
- if (videoRef.current) {
484
- videoRef.current.muted = true;
485
- }
486
- }, [platform, url]);
487
-
488
480
  const updateStatus = (message: string) => {
489
481
  // Use the wrapper for conditional logging
490
482
  debugLog(message);
491
483
  };
492
484
 
493
- const unmute = () => {
494
- if (unmuted) {
495
- return;
496
- }
497
-
498
- setUnmuted(true);
499
- const video = videoRef.current;
500
- if (video) {
501
- video.muted = false;
502
- video.play().catch((err: unknown) => {
503
- debugWarn('Unable to unmute after user interaction:', err);
504
- });
505
- }
506
- };
507
-
508
485
  const sendBinaryControlMessage = (data: ArrayBuffer) => {
509
486
  if (!dataChannelRef.current || dataChannelRef.current.readyState !== 'open') {
510
487
  return;
@@ -987,10 +964,6 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
987
964
  event.preventDefault();
988
965
  event.stopPropagation();
989
966
 
990
- if (event.type === 'mousedown' || event.type === 'touchstart') {
991
- unmute();
992
- }
993
-
994
967
  // Compute mapping context once per event (reused for all pointers)
995
968
  const ctx = computeVideoMappingContext();
996
969
 
@@ -2532,7 +2505,7 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
2532
2505
  }}
2533
2506
  autoPlay
2534
2507
  playsInline
2535
- muted={!unmuted}
2508
+ muted
2536
2509
  tabIndex={0}
2537
2510
  onKeyDown={handleKeyboard}
2538
2511
  onKeyUp={handleKeyboard}
@@ -1,7 +1,6 @@
1
1
  import { AppleGsaSrpClient } from './gsa-srp';
2
2
  import {
3
- createAppleRelaySession,
4
- deleteAppleRelaySession,
3
+ openAppleRelayWebSocket,
5
4
  fetchAppleAccountSession,
6
5
  proxyPhoneTwoFactorCode,
7
6
  proxySrpComplete,
@@ -9,18 +8,20 @@ import {
9
8
  proxyTwoFactorCode,
10
9
  triggerPhoneTwoFactor,
11
10
  triggerTrustedDeviceTwoFactor,
11
+ type AppleRelayWebSocketClient,
12
12
  type AppleRelayResponse,
13
13
  } from './relay';
14
14
 
15
15
  export type AppleIDLoginInput = {
16
- limbuildApiUrl: string;
16
+ registryApiUrl: string;
17
17
  accountName: string;
18
18
  password: string;
19
19
  token?: string;
20
+ organizationId?: string;
20
21
  };
21
22
 
22
23
  export type AppleIDLoginResult = {
23
- appleSessionId: string;
24
+ relay: AppleRelayWebSocketClient;
24
25
  completeResponse: AppleRelayResponse;
25
26
  twoFactorChallengeResponse?: AppleRelayResponse;
26
27
  requiresTwoFactor: boolean;
@@ -32,15 +33,16 @@ export type AppleIDLoginResult = {
32
33
  type TwoFactorMethod = { type: 'trustedDevice' } | { type: 'phone'; phoneNumberId: number; mode: string };
33
34
 
34
35
  export async function startBrowserOwnedAppleIDLogin({
35
- limbuildApiUrl,
36
+ registryApiUrl,
36
37
  accountName,
37
38
  password,
38
39
  token,
40
+ organizationId,
39
41
  }: AppleIDLoginInput): Promise<AppleIDLoginResult> {
40
- const { appleSessionId } = await createAppleRelaySession(limbuildApiUrl, token);
42
+ const relay = await openAppleRelayWebSocket(registryApiUrl, token, organizationId);
41
43
  try {
42
44
  const srp = new AppleGsaSrpClient(accountName);
43
- const initResponse = await proxySrpInit(limbuildApiUrl, appleSessionId, await srp.init(), token);
45
+ const initResponse = await proxySrpInit(relay, await srp.init());
44
46
  if (initResponse.status < 200 || initResponse.status >= 300) {
45
47
  throw new Error(
46
48
  `Apple SRP init failed: HTTP ${initResponse.status} ${initResponse.rawBody ?? ''}`.trim(),
@@ -50,21 +52,16 @@ export async function startBrowserOwnedAppleIDLogin({
50
52
  throw new Error('Apple SRP init response did not include a body.');
51
53
  }
52
54
  const proof = await srp.complete(password, initResponse.body);
53
- const completeResponse = await proxySrpComplete(
54
- limbuildApiUrl,
55
- appleSessionId,
56
- {
57
- ...proof,
58
- rememberMe: false,
59
- trustTokens: [],
60
- },
61
- token,
62
- );
55
+ const completeResponse = await proxySrpComplete(relay, {
56
+ ...proof,
57
+ rememberMe: false,
58
+ trustTokens: [],
59
+ });
63
60
  const requiresTwoFactor = completeResponse.status === 409;
64
61
  let twoFactorChallengeResponse: AppleRelayResponse | undefined;
65
62
  let twoFactorMethod: TwoFactorMethod = { type: 'trustedDevice' };
66
63
  if (requiresTwoFactor) {
67
- twoFactorChallengeResponse = await triggerTrustedDeviceTwoFactor(limbuildApiUrl, appleSessionId, token);
64
+ twoFactorChallengeResponse = await triggerTrustedDeviceTwoFactor(relay);
68
65
  const phone = trustedPhoneNumberFromChallenge(twoFactorChallengeResponse.body);
69
66
  // Only route codes through the phone/SMS endpoint when Apple actually
70
67
  // falls back to phone verification (HTTP 412). The trusted-device
@@ -80,13 +77,7 @@ export async function startBrowserOwnedAppleIDLogin({
80
77
  phoneNumberId: phone.id,
81
78
  mode: phone.pushMode ?? 'sms',
82
79
  };
83
- twoFactorChallengeResponse = await triggerPhoneTwoFactor(
84
- limbuildApiUrl,
85
- appleSessionId,
86
- phone.id,
87
- phone.pushMode ?? 'sms',
88
- token,
89
- );
80
+ twoFactorChallengeResponse = await triggerPhoneTwoFactor(relay, phone.id, phone.pushMode ?? 'sms');
90
81
  }
91
82
  if (twoFactorChallengeResponse.status < 200 || twoFactorChallengeResponse.status >= 300) {
92
83
  throw new Error(
@@ -101,7 +92,7 @@ export async function startBrowserOwnedAppleIDLogin({
101
92
  );
102
93
  }
103
94
  return {
104
- appleSessionId,
95
+ relay,
105
96
  completeResponse,
106
97
  twoFactorChallengeResponse,
107
98
  requiresTwoFactor,
@@ -109,14 +100,12 @@ export async function startBrowserOwnedAppleIDLogin({
109
100
  const response =
110
101
  twoFactorMethod.type === 'phone' ?
111
102
  await proxyPhoneTwoFactorCode(
112
- limbuildApiUrl,
113
- appleSessionId,
103
+ relay,
114
104
  twoFactorMethod.phoneNumberId,
115
105
  code,
116
106
  twoFactorMethod.mode,
117
- token,
118
107
  )
119
- : await proxyTwoFactorCode(limbuildApiUrl, appleSessionId, code, token);
108
+ : await proxyTwoFactorCode(relay, code);
120
109
  if (response.status < 200 || response.status >= 300) {
121
110
  throw new Error(
122
111
  `Apple two-factor code failed: HTTP ${response.status} ${response.rawBody ?? ''}`.trim(),
@@ -124,11 +113,11 @@ export async function startBrowserOwnedAppleIDLogin({
124
113
  }
125
114
  return response;
126
115
  },
127
- finalize: async () => fetchAppleAccountSession(limbuildApiUrl, appleSessionId, token),
128
- close: () => deleteAppleRelaySession(limbuildApiUrl, appleSessionId, token),
116
+ finalize: async () => fetchAppleAccountSession(relay),
117
+ close: async () => relay.close(),
129
118
  };
130
119
  } catch (error) {
131
- await deleteAppleRelaySession(limbuildApiUrl, appleSessionId, token).catch(() => undefined);
120
+ relay.close();
132
121
  throw error;
133
122
  }
134
123
  }