@limrun/ui 0.13.8 → 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 (44) 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/limbuild-client-BagK_FWE.mjs +83 -0
  26. package/dist/limbuild-client-CVDwq54z.js +1 -0
  27. package/package.json +1 -1
  28. package/src/app-store-relay/index.test.ts +19 -29
  29. package/src/app-store-relay/index.ts +6 -9
  30. package/src/app-store-relay/react.ts +5 -4
  31. package/src/core/device-install/apple/client.ts +22 -33
  32. package/src/core/device-install/apple/relay.ts +108 -148
  33. package/src/core/device-install/operations/limbuild-client.ts +3 -0
  34. package/src/core/device-install/operations/operations.ts +21 -9
  35. package/src/core/device-install/operations/relay-client.ts +3 -2
  36. package/src/device-build/react.ts +2 -0
  37. package/src/device-install/README.md +5 -5
  38. package/src/device-install/demo/demo.tsx +26 -28
  39. package/src/device-install/index.ts +1 -0
  40. package/src/device-install/react.ts +12 -6
  41. package/dist/client-ASLtHFLh.js +0 -1
  42. package/dist/client-CGdh8yoJ.mjs +0 -234
  43. package/dist/limbuild-client-CgEncrgx.js +0 -1
  44. package/dist/limbuild-client-D7U7w342.mjs +0 -81
@@ -1,100 +1,104 @@
1
- import { useState as u, useRef as h, useCallback as l, useEffect as D } from "react";
2
- import { closeDeviceRelayTarget as d, requestUSBAccess as N, pairDevice as q, startDeviceInstall as C } from "./index.js";
3
- import { j as T, k as j } from "../browser-storage-Cx9eW-bm.mjs";
4
- function F({
5
- apiUrl: a,
1
+ import { useState as u, useRef as g, useCallback as l, useEffect as S } from "react";
2
+ import { closeDeviceRelayTarget as d, requestUSBAccess as C, pairDevice as I, startDeviceInstall as T } from "./index.js";
3
+ import { j, k as x } from "../browser-storage-Cx9eW-bm.mjs";
4
+ function H({
5
+ apiUrl: c,
6
6
  token: v,
7
- log: R = x
7
+ organizationId: f,
8
+ log: m = L
8
9
  }) {
9
- const [e, m] = u(), [n, y] = u(), [g, s] = u(), [P, i] = u(), [S, f] = u(!1), w = h(void 0), b = h(void 0), r = h(R);
10
- r.current = R;
11
- const c = l(async () => {
12
- w.current?.close(), w.current = void 0, await d(b.current, r.current);
10
+ const [e, D] = u(), [n, h] = u(), [p, s] = u(), [b, i] = u(), [A, w] = u(!1), y = g(void 0), P = g(void 0), r = g(m);
11
+ r.current = m;
12
+ const a = l(async () => {
13
+ y.current?.close(), y.current = void 0, await d(P.current, r.current);
13
14
  }, []);
14
- D(() => {
15
- b.current = e;
16
- }, [e]), D(() => () => {
17
- c();
18
- }, [c]);
19
- const A = l(async () => {
20
- s("usb"), i(void 0), f(!1);
15
+ S(() => {
16
+ P.current = e;
17
+ }, [e]), S(() => () => {
18
+ a();
19
+ }, [a]);
20
+ const B = l(async () => {
21
+ s("usb"), i(void 0), w(!1);
21
22
  let t;
22
23
  try {
23
- await c(), t = await N({ log: r.current });
24
- const o = await T(t.hello.serialNumber);
25
- return m(t), y(o), r.current(
24
+ await a(), t = await C({ log: r.current });
25
+ const o = await j(t.hello.serialNumber);
26
+ return D(t), h(o), r.current(
26
27
  o ? "Pair record found" : "No pair record found",
27
28
  t.hello.serialNumber
28
29
  ), t;
29
30
  } catch (o) {
30
- await d(t, r.current), m(void 0), y(void 0), i(p(o));
31
+ await d(t, r.current), D(void 0), h(void 0), i(R(o));
31
32
  return;
32
33
  } finally {
33
34
  s(void 0);
34
35
  }
35
- }, [c]), B = l(async () => {
36
- if (!a || !e)
36
+ }, [a]), E = l(async () => {
37
+ if (!c || !e)
37
38
  throw new Error("Select a USB device before pairing.");
38
- s("pair"), i(void 0), f(!1);
39
+ s("pair"), i(void 0), w(!1);
39
40
  try {
40
- await c();
41
- const t = await q({
42
- limbuildApiUrl: a,
41
+ await a();
42
+ const t = await I({
43
+ registryApiUrl: c,
43
44
  token: v,
45
+ organizationId: f,
44
46
  log: r.current,
45
47
  target: e
46
- }), o = await j(t.pairRecord, {
48
+ }), o = await x(t.pairRecord, {
47
49
  productName: e.hello.productName
48
50
  });
49
- return t.relay.close(), await d(e, r.current), y(o), f(!1), r.current("Device paired", "The pair record was stored locally in this browser."), o;
51
+ return t.relay.close(), await d(e, r.current), h(o), w(!1), r.current("Device paired", "The pair record was stored locally in this browser."), o;
50
52
  } catch (t) {
51
- await d(e, r.current), f(!0), i(p(t));
53
+ await d(e, r.current), w(!0), i(R(t));
52
54
  return;
53
55
  } finally {
54
56
  s(void 0);
55
57
  }
56
- }, [a, c, e, v]), E = l(async () => {
57
- if (!a || !e || !n)
58
+ }, [c, a, e, f, v]), N = l(async (t) => {
59
+ if (!c || !e || !n)
58
60
  throw new Error("Select and pair a USB device before starting installation.");
59
61
  s("install"), i(void 0);
60
62
  try {
61
- return await c(), w.current = await C({
62
- limbuildApiUrl: a,
63
+ return await a(), y.current = await T({
64
+ registryApiUrl: c,
63
65
  token: v,
66
+ organizationId: f,
64
67
  log: r.current,
65
68
  target: e,
66
- pairRecord: n
67
- }), r.current("Device install started", "Installation will continue through the connected iPhone."), w.current;
68
- } catch (t) {
69
- await d(e, r.current), i(p(t));
69
+ pairRecord: n,
70
+ installSource: t
71
+ }), r.current("Device install started", "Installation will continue through the connected iPhone."), y.current;
72
+ } catch (o) {
73
+ await d(e, r.current), i(R(o));
70
74
  return;
71
75
  } finally {
72
76
  s(void 0);
73
77
  }
74
- }, [a, c, e, n, v]), I = l(() => {
75
- c(), r.current("Device relay stopped");
76
- }, [c]);
78
+ }, [c, a, e, f, n, v]), q = l(() => {
79
+ a(), r.current("Device relay stopped");
80
+ }, [a]);
77
81
  return {
78
82
  device: e,
79
83
  pairRecord: n,
80
- busyAction: g,
81
- error: P,
82
- pairConfirmationRequired: S,
84
+ busyAction: p,
85
+ error: b,
86
+ pairConfirmationRequired: A,
83
87
  hasPairRecord: !!n,
84
- canPair: !!a && !g && !!e,
85
- canInstall: !!a && !g && !!e && !!n,
86
- requestUSBAccess: A,
87
- pairBrowser: B,
88
- startInstallation: E,
89
- stopRelay: I,
88
+ canPair: !!c && !p && !!e,
89
+ canInstall: !!c && !p && !!e && !!n,
90
+ requestUSBAccess: B,
91
+ pairBrowser: E,
92
+ startInstallation: N,
93
+ stopRelay: q,
90
94
  clearError: () => i(void 0)
91
95
  };
92
96
  }
93
- function x() {
97
+ function L() {
94
98
  }
95
- function p(a) {
96
- return a instanceof Error ? a.message : String(a);
99
+ function R(c) {
100
+ return c instanceof Error ? c.message : String(c);
97
101
  }
98
102
  export {
99
- F as useDeviceInstallRelay
103
+ H as useDeviceInstallRelay
100
104
  };
@@ -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.8",
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) => {
@@ -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
  }