@limrun/ui 0.13.9 → 0.14.0-rc.2
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.
- package/dist/app-store-relay/index.cjs +3 -3
- package/dist/app-store-relay/index.d.ts +3 -5
- package/dist/app-store-relay/index.js +56 -58
- package/dist/app-store-relay/react.cjs +1 -1
- package/dist/app-store-relay/react.d.ts +2 -2
- package/dist/app-store-relay/react.js +35 -34
- package/dist/client-BtC4n_qf.js +1 -0
- package/dist/client-CBX-hLnL.mjs +189 -0
- package/dist/core/device-install/apple/client.d.ts +5 -4
- package/dist/core/device-install/apple/relay.d.ts +24 -13
- package/dist/core/device-install/operations/limbuild-client.d.ts +2 -1
- package/dist/core/device-install/operations/operations.d.ts +19 -4
- package/dist/core/device-install/operations/relay-client.d.ts +2 -1
- package/dist/device-build/index.cjs +1 -1
- package/dist/device-build/index.js +1 -1
- package/dist/device-build/react.cjs +1 -1
- package/dist/device-build/react.d.ts +1 -0
- package/dist/device-build/react.js +34 -33
- package/dist/device-install/index.cjs +1 -1
- package/dist/device-install/index.d.ts +1 -1
- package/dist/device-install/index.js +121 -119
- package/dist/device-install/react.cjs +1 -1
- package/dist/device-install/react.d.ts +4 -3
- package/dist/device-install/react.js +58 -54
- package/dist/limbuild-client-BagK_FWE.mjs +83 -0
- package/dist/limbuild-client-CVDwq54z.js +1 -0
- package/package.json +1 -1
- package/src/app-store-relay/index.test.ts +19 -29
- package/src/app-store-relay/index.ts +6 -9
- package/src/app-store-relay/react.ts +5 -4
- package/src/core/device-install/apple/client.ts +22 -33
- package/src/core/device-install/apple/relay.ts +108 -148
- package/src/core/device-install/operations/limbuild-client.ts +3 -0
- package/src/core/device-install/operations/operations.ts +21 -9
- package/src/core/device-install/operations/relay-client.ts +3 -2
- package/src/device-build/react.ts +2 -0
- package/src/device-install/README.md +4 -4
- package/src/device-install/demo/demo.tsx +26 -28
- package/src/device-install/index.ts +1 -0
- package/src/device-install/react.ts +12 -6
- package/dist/client-ASLtHFLh.js +0 -1
- package/dist/client-CGdh8yoJ.mjs +0 -234
- package/dist/limbuild-client-CgEncrgx.js +0 -1
- package/dist/limbuild-client-D7U7w342.mjs +0 -81
|
@@ -1,100 +1,104 @@
|
|
|
1
|
-
import { useState as u, useRef as
|
|
2
|
-
import { closeDeviceRelayTarget as d, requestUSBAccess as
|
|
3
|
-
import { j
|
|
4
|
-
function
|
|
5
|
-
apiUrl:
|
|
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
|
-
|
|
7
|
+
organizationId: f,
|
|
8
|
+
log: m = L
|
|
8
9
|
}) {
|
|
9
|
-
const [e,
|
|
10
|
-
r.current =
|
|
11
|
-
const
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
}, [e]),
|
|
17
|
-
|
|
18
|
-
}, [
|
|
19
|
-
const
|
|
20
|
-
s("usb"), i(void 0),
|
|
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
|
|
24
|
-
const o = await
|
|
25
|
-
return
|
|
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),
|
|
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
|
-
}, [
|
|
36
|
-
if (!
|
|
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),
|
|
39
|
+
s("pair"), i(void 0), w(!1);
|
|
39
40
|
try {
|
|
40
|
-
await
|
|
41
|
-
const t = await
|
|
42
|
-
|
|
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
|
|
48
|
+
}), o = await x(t.pairRecord, {
|
|
47
49
|
productName: e.hello.productName
|
|
48
50
|
});
|
|
49
|
-
return t.relay.close(), await d(e, r.current),
|
|
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),
|
|
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
|
-
}, [
|
|
57
|
-
if (!
|
|
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
|
|
62
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
}, [
|
|
75
|
-
|
|
76
|
-
}, [
|
|
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:
|
|
81
|
-
error:
|
|
82
|
-
pairConfirmationRequired:
|
|
84
|
+
busyAction: p,
|
|
85
|
+
error: b,
|
|
86
|
+
pairConfirmationRequired: A,
|
|
83
87
|
hasPairRecord: !!n,
|
|
84
|
-
canPair: !!
|
|
85
|
-
canInstall: !!
|
|
86
|
-
requestUSBAccess:
|
|
87
|
-
pairBrowser:
|
|
88
|
-
startInstallation:
|
|
89
|
-
stopRelay:
|
|
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
|
|
97
|
+
function L() {
|
|
94
98
|
}
|
|
95
|
-
function
|
|
96
|
-
return
|
|
99
|
+
function R(c) {
|
|
100
|
+
return c instanceof Error ? c.message : String(c);
|
|
97
101
|
}
|
|
98
102
|
export {
|
|
99
|
-
|
|
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
|
@@ -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('
|
|
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
|
|
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
|
-
|
|
20
|
-
token: 'token',
|
|
21
|
-
appleSessionId: 'apple-session',
|
|
15
|
+
relay,
|
|
22
16
|
teamId: 'TEAM',
|
|
23
17
|
profileId: 'PROFILE',
|
|
24
18
|
});
|
|
25
19
|
|
|
26
|
-
expect(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>(
|
|
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, '
|
|
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({
|
|
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
|
-
|
|
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
|
-
[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
registryApiUrl,
|
|
36
37
|
accountName,
|
|
37
38
|
password,
|
|
38
39
|
token,
|
|
40
|
+
organizationId,
|
|
39
41
|
}: AppleIDLoginInput): Promise<AppleIDLoginResult> {
|
|
40
|
-
const
|
|
42
|
+
const relay = await openAppleRelayWebSocket(registryApiUrl, token, organizationId);
|
|
41
43
|
try {
|
|
42
44
|
const srp = new AppleGsaSrpClient(accountName);
|
|
43
|
-
const initResponse = await proxySrpInit(
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
appleSessionId,
|
|
103
|
+
relay,
|
|
114
104
|
twoFactorMethod.phoneNumberId,
|
|
115
105
|
code,
|
|
116
106
|
twoFactorMethod.mode,
|
|
117
|
-
token,
|
|
118
107
|
)
|
|
119
|
-
: await proxyTwoFactorCode(
|
|
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(
|
|
128
|
-
close: () =>
|
|
116
|
+
finalize: async () => fetchAppleAccountSession(relay),
|
|
117
|
+
close: async () => relay.close(),
|
|
129
118
|
};
|
|
130
119
|
} catch (error) {
|
|
131
|
-
|
|
120
|
+
relay.close();
|
|
132
121
|
throw error;
|
|
133
122
|
}
|
|
134
123
|
}
|