@limrun/ui 0.13.3-rc.1 → 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.
- package/dist/app-store-relay/index.cjs +4 -4
- package/dist/app-store-relay/index.js +175 -119
- package/dist/app-store-relay/react.cjs +1 -1
- package/dist/app-store-relay/react.js +2 -2
- package/dist/{browser-storage-BJ__DGJ6.mjs → browser-storage-Cx9eW-bm.mjs} +48 -45
- package/dist/browser-storage-jJaIht4K.js +1 -0
- package/dist/{client-CnbCWvCs.js → client-ASLtHFLh.js} +1 -1
- package/dist/{client-Bk1N3XIF.mjs → client-CGdh8yoJ.mjs} +33 -27
- package/dist/core/device-install/apple/provisioning.d.ts +4 -4
- package/dist/device-build/index.cjs +1 -1
- package/dist/device-build/index.js +5 -5
- package/dist/device-build/react.cjs +1 -1
- package/dist/device-build/react.js +15 -13
- package/dist/device-install/demo/demo.d.ts +0 -0
- package/dist/device-install/index.cjs +1 -1
- package/dist/device-install/index.d.ts +1 -1
- package/dist/device-install/index.js +134 -121
- package/dist/device-install/react.cjs +1 -1
- package/dist/device-install/react.js +55 -50
- package/dist/{index-BXg7HdIs.mjs → index-DFdCvi2_.mjs} +3 -7
- package/dist/limbuild-client-CgEncrgx.js +1 -0
- package/dist/{limbuild-client-CFJhYsRx.mjs → limbuild-client-D7U7w342.mjs} +4 -2
- package/dist/{provisioning-CdseoMJQ.mjs → provisioning-DKbkBO-p.mjs} +11 -11
- package/dist/{provisioning-D2ZZQeyX.js → provisioning-Va0PH4Kk.js} +1 -1
- package/package.json +1 -1
- package/src/app-store-relay/index.test.ts +1 -5
- package/src/app-store-relay/index.ts +109 -42
- package/src/app-store-relay/react.ts +6 -2
- package/src/core/device-install/apple/client.ts +29 -22
- package/src/core/device-install/apple/crypto.ts +9 -6
- package/src/core/device-install/apple/gsa-srp.ts +3 -7
- package/src/core/device-install/apple/provisioning.ts +16 -18
- package/src/core/device-install/apple/relay.ts +5 -1
- package/src/core/device-install/operations/limbuild-client.ts +8 -1
- package/src/core/device-install/operations/operations.ts +3 -1
- package/src/core/device-install/operations/relay-client.ts +57 -27
- package/src/core/device-install/operations/usbmux.ts +3 -3
- package/src/core/device-install/operations/webusb.ts +9 -4
- package/src/core/device-install/storage/browser-storage.ts +20 -6
- package/src/core/device-install/types.ts +1 -7
- package/src/device-build/react.ts +4 -1
- package/src/device-install/README.md +423 -0
- package/src/device-install/demo/demo.css +493 -0
- package/src/device-install/demo/demo.tsx +1069 -0
- package/src/device-install/demo/index.html +12 -0
- package/src/device-install/index.ts +7 -7
- package/src/device-install/react.ts +26 -17
- package/src/index.ts +1 -1
- package/dist/browser-storage-C1jQLXat.js +0 -1
- package/dist/limbuild-client-C_CMNLYV.js +0 -1
|
@@ -1,88 +1,93 @@
|
|
|
1
|
-
import { useState as
|
|
2
|
-
import { closeDeviceRelayTarget as
|
|
3
|
-
import { j as T, k as j } from "../browser-storage-
|
|
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
4
|
function F({
|
|
5
5
|
apiUrl: a,
|
|
6
6
|
token: v,
|
|
7
|
-
log:
|
|
7
|
+
log: R = x
|
|
8
8
|
}) {
|
|
9
|
-
const [
|
|
10
|
-
|
|
11
|
-
|
|
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);
|
|
13
|
+
}, []);
|
|
12
14
|
D(() => {
|
|
13
|
-
|
|
14
|
-
}, [
|
|
15
|
-
|
|
16
|
-
}, [
|
|
17
|
-
const A =
|
|
18
|
-
|
|
15
|
+
b.current = e;
|
|
16
|
+
}, [e]), D(() => () => {
|
|
17
|
+
c();
|
|
18
|
+
}, [c]);
|
|
19
|
+
const A = l(async () => {
|
|
20
|
+
s("usb"), i(void 0), f(!1);
|
|
19
21
|
let t;
|
|
20
22
|
try {
|
|
21
|
-
await
|
|
22
|
-
const
|
|
23
|
-
return m(t), y(
|
|
24
|
-
|
|
25
|
-
|
|
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(
|
|
26
|
+
o ? "Pair record found" : "No pair record found",
|
|
27
|
+
t.hello.serialNumber
|
|
28
|
+
), t;
|
|
29
|
+
} catch (o) {
|
|
30
|
+
await d(t, r.current), m(void 0), y(void 0), i(p(o));
|
|
26
31
|
return;
|
|
27
32
|
} finally {
|
|
28
|
-
|
|
33
|
+
s(void 0);
|
|
29
34
|
}
|
|
30
|
-
}, [
|
|
31
|
-
if (!a || !
|
|
35
|
+
}, [c]), B = l(async () => {
|
|
36
|
+
if (!a || !e)
|
|
32
37
|
throw new Error("Select a USB device before pairing.");
|
|
33
|
-
|
|
38
|
+
s("pair"), i(void 0), f(!1);
|
|
34
39
|
try {
|
|
35
|
-
await
|
|
40
|
+
await c();
|
|
36
41
|
const t = await q({
|
|
37
42
|
limbuildApiUrl: a,
|
|
38
43
|
token: v,
|
|
39
|
-
log:
|
|
40
|
-
target:
|
|
41
|
-
}),
|
|
42
|
-
productName:
|
|
44
|
+
log: r.current,
|
|
45
|
+
target: e
|
|
46
|
+
}), o = await j(t.pairRecord, {
|
|
47
|
+
productName: e.hello.productName
|
|
43
48
|
});
|
|
44
|
-
return t.relay.close(), await
|
|
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;
|
|
45
50
|
} catch (t) {
|
|
46
|
-
await
|
|
51
|
+
await d(e, r.current), f(!0), i(p(t));
|
|
47
52
|
return;
|
|
48
53
|
} finally {
|
|
49
|
-
|
|
54
|
+
s(void 0);
|
|
50
55
|
}
|
|
51
|
-
}, [a,
|
|
52
|
-
if (!a || !
|
|
56
|
+
}, [a, c, e, v]), E = l(async () => {
|
|
57
|
+
if (!a || !e || !n)
|
|
53
58
|
throw new Error("Select and pair a USB device before starting installation.");
|
|
54
|
-
|
|
59
|
+
s("install"), i(void 0);
|
|
55
60
|
try {
|
|
56
|
-
return await
|
|
61
|
+
return await c(), w.current = await C({
|
|
57
62
|
limbuildApiUrl: a,
|
|
58
63
|
token: v,
|
|
59
|
-
log:
|
|
60
|
-
target:
|
|
61
|
-
pairRecord:
|
|
62
|
-
}),
|
|
64
|
+
log: r.current,
|
|
65
|
+
target: e,
|
|
66
|
+
pairRecord: n
|
|
67
|
+
}), r.current("Device install started", "Installation will continue through the connected iPhone."), w.current;
|
|
63
68
|
} catch (t) {
|
|
64
|
-
await
|
|
69
|
+
await d(e, r.current), i(p(t));
|
|
65
70
|
return;
|
|
66
71
|
} finally {
|
|
67
|
-
|
|
72
|
+
s(void 0);
|
|
68
73
|
}
|
|
69
|
-
}, [a,
|
|
70
|
-
|
|
71
|
-
}, [
|
|
74
|
+
}, [a, c, e, n, v]), I = l(() => {
|
|
75
|
+
c(), r.current("Device relay stopped");
|
|
76
|
+
}, [c]);
|
|
72
77
|
return {
|
|
73
|
-
device:
|
|
74
|
-
pairRecord:
|
|
75
|
-
busyAction:
|
|
78
|
+
device: e,
|
|
79
|
+
pairRecord: n,
|
|
80
|
+
busyAction: g,
|
|
76
81
|
error: P,
|
|
77
82
|
pairConfirmationRequired: S,
|
|
78
|
-
hasPairRecord: !!
|
|
79
|
-
canPair: !!a && !
|
|
80
|
-
canInstall: !!a && !
|
|
83
|
+
hasPairRecord: !!n,
|
|
84
|
+
canPair: !!a && !g && !!e,
|
|
85
|
+
canInstall: !!a && !g && !!e && !!n,
|
|
81
86
|
requestUSBAccess: A,
|
|
82
87
|
pairBrowser: B,
|
|
83
88
|
startInstallation: E,
|
|
84
89
|
stopRelay: I,
|
|
85
|
-
clearError: () =>
|
|
90
|
+
clearError: () => i(void 0)
|
|
86
91
|
};
|
|
87
92
|
}
|
|
88
93
|
function x() {
|
|
@@ -228,13 +228,9 @@ class kn {
|
|
|
228
228
|
async function vn(t, e, r, d) {
|
|
229
229
|
let T = new Uint8Array(await wt.hash(Na.h, Vt(Ir(e))));
|
|
230
230
|
t === "s2k_fo" && (T = Ir(wt.toHex(T)));
|
|
231
|
-
const x = await crypto.subtle.importKey(
|
|
232
|
-
"
|
|
233
|
-
|
|
234
|
-
{ name: "PBKDF2" },
|
|
235
|
-
!1,
|
|
236
|
-
["deriveBits"]
|
|
237
|
-
), l = await crypto.subtle.deriveBits(
|
|
231
|
+
const x = await crypto.subtle.importKey("raw", Vt(T), { name: "PBKDF2" }, !1, [
|
|
232
|
+
"deriveBits"
|
|
233
|
+
]), l = await crypto.subtle.deriveBits(
|
|
238
234
|
{
|
|
239
235
|
name: "PBKDF2",
|
|
240
236
|
hash: { name: "SHA-256" },
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";async function l(o,t){const a=new URL(`${o}/info`);t&&a.searchParams.set("token",t);const e=await fetch(a.toString(),{headers:t?{Authorization:`Bearer ${t}`}:void 0});if(!e.ok){const n=await e.text();throw new Error(`Info request failed: HTTP ${e.status} ${n}`)}return await e.json()}async function u({limbuildApiUrl:o,token:t,certificateP12Base64:a,certificatePassword:e,provisioningProfileBase64:n}){const d=new URL(`${o}/exec`);t&&d.searchParams.set("token",t);const r=await fetch(d.toString(),{method:"POST",headers:{"Content-Type":"application/json",...t?{Authorization:`Bearer ${t}`}:{}},body:JSON.stringify({command:"xcodebuild",xcodebuild:{sdk:"iphoneos"},signing:{certificateP12Base64:a,...e?{certificatePassword:e}:{},provisioningProfileBase64:n}})});if(!r.ok){const s=await r.text();throw new Error(`Build request failed: HTTP ${r.status} ${s}`)}return await r.json()}async function f({limbuildApiUrl:o,execId:t,token:a}){const e=new URL(`${o}/exec/${encodeURIComponent(t)}/ios/ota`);a&&e.searchParams.set("token",a);const n=await fetch(e.toString(),{headers:a?{Authorization:`Bearer ${a}`}:void 0});if(!n.ok){const d=await n.text();throw new Error(`OTA install metadata failed: HTTP ${n.status} ${d}`)}return await n.json()}function h({limbuildApiUrl:o,execId:t,token:a,onLine:e,onStatus:n,onError:d}){const r=new URL(`${o}/exec/${t}/events`);a&&r.searchParams.set("token",a);const s=new EventSource(r.toString());return n("running"),s.addEventListener("command",i=>e({type:"command",data:i.data})),s.addEventListener("stdout",i=>e({type:"stdout",data:i.data})),s.addEventListener("stderr",i=>e({type:"stderr",data:i.data})),s.addEventListener("exitCode",i=>{const c=parseInt(i.data,10);n(c===0?"succeeded":c<0?"cancelled":"failed"),s.close()}),s.onerror=()=>{s.close(),n("failed"),d?.(new Error("Build log stream closed before completion."))},()=>s.close()}exports.fetchLimbuildInfo=l;exports.getIOSOTAInstall=f;exports.startSignedDeviceBuild=u;exports.watchBuildLogEvents=h;
|
|
@@ -66,9 +66,11 @@ function w({
|
|
|
66
66
|
const s = new EventSource(r.toString());
|
|
67
67
|
return n("running"), s.addEventListener("command", (i) => e({ type: "command", data: i.data })), s.addEventListener("stdout", (i) => e({ type: "stdout", data: i.data })), s.addEventListener("stderr", (i) => e({ type: "stderr", data: i.data })), s.addEventListener("exitCode", (i) => {
|
|
68
68
|
const c = parseInt(i.data, 10);
|
|
69
|
-
n(
|
|
69
|
+
n(
|
|
70
|
+
c === 0 ? "succeeded" : c < 0 ? "cancelled" : "failed"
|
|
71
|
+
), s.close();
|
|
70
72
|
}), s.onerror = () => {
|
|
71
|
-
s.close(), d?.(new Error("Build log stream closed before completion."));
|
|
73
|
+
s.close(), n("failed"), d?.(new Error("Build log stream closed before completion."));
|
|
72
74
|
}, () => s.close();
|
|
73
75
|
}
|
|
74
76
|
export {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { e as c, a as u, n as d, b as f, c as l, i as r } from "./browser-storage-
|
|
1
|
+
import { e as c, a as u, n as d, b as f, c as l, i as r } from "./browser-storage-Cx9eW-bm.mjs";
|
|
2
2
|
function g() {
|
|
3
3
|
return {
|
|
4
4
|
method: "POST",
|
|
@@ -6,10 +6,16 @@ function g() {
|
|
|
6
6
|
payload: {}
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
|
-
function v({
|
|
9
|
+
function v({
|
|
10
|
+
bundleID: e,
|
|
11
|
+
teamID: i = ""
|
|
12
|
+
}) {
|
|
10
13
|
return n("/account/ios/identifiers/listAppIds.action", i, { sort: "name=asc" });
|
|
11
14
|
}
|
|
12
|
-
function P({
|
|
15
|
+
function P({
|
|
16
|
+
deviceUDID: e,
|
|
17
|
+
teamID: i = ""
|
|
18
|
+
}) {
|
|
13
19
|
return n("/account/ios/device/listDevices.action", i, {
|
|
14
20
|
sort: "name=asc",
|
|
15
21
|
includeRemovedDevices: !1
|
|
@@ -79,10 +85,7 @@ function q({
|
|
|
79
85
|
}
|
|
80
86
|
};
|
|
81
87
|
}
|
|
82
|
-
function b({
|
|
83
|
-
csrPEM: e,
|
|
84
|
-
teamID: i = ""
|
|
85
|
-
}) {
|
|
88
|
+
function b({ csrPEM: e, teamID: i = "" }) {
|
|
86
89
|
return {
|
|
87
90
|
method: "POST",
|
|
88
91
|
path: "/account/ios/certificate/submitCertificateRequest.action",
|
|
@@ -93,10 +96,7 @@ function b({
|
|
|
93
96
|
}
|
|
94
97
|
};
|
|
95
98
|
}
|
|
96
|
-
function C({
|
|
97
|
-
csrPEM: e,
|
|
98
|
-
teamID: i = ""
|
|
99
|
-
}) {
|
|
99
|
+
function C({ csrPEM: e, teamID: i = "" }) {
|
|
100
100
|
return {
|
|
101
101
|
method: "POST",
|
|
102
102
|
path: "/account/ios/certificate/submitCertificateRequest.action",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const a=require("./browser-storage-
|
|
1
|
+
"use strict";const a=require("./browser-storage-jJaIht4K.js");function c(){return{method:"POST",path:"/account/listTeams.action",payload:{}}}function u({bundleID:e,teamID:t=""}){return s("/account/ios/identifiers/listAppIds.action",t,{sort:"name=asc"})}function d({deviceUDID:e,teamID:t=""}){return s("/account/ios/device/listDevices.action",t,{sort:"name=asc",includeRemovedDevices:!1})}function f(e=""){return s("/account/ios/certificate/listCertRequests.action",e,{sort:"name=asc",types:"83Q87W3TGH,5QPB9NHCEI"})}function l(e=""){return s("/account/ios/certificate/listCertRequests.action",e,{sort:"name=asc",types:"WXV89964HE,R58UK2EWSO"})}function p({bundleID:e,teamID:t=""}){return s("/account/ios/profile/listProvisioningProfiles.action",t,{search:e,sort:"name=asc"})}function m({bundleID:e,teamID:t=""}){return s("/account/ios/profile/listProvisioningProfiles.action",t,{search:e,sort:"name=asc",distributionType:"adhoc"})}function R({deviceUDID:e,teamID:t="",name:i="Limrun iPhone"}){return{method:"POST",path:"/account/ios/device/addDevices.action",payload:{teamId:t,deviceNames:i,deviceNumbers:a.normalizeUDID(e),deviceClasses:"iphone",register:"single"}}}function g({bundleID:e,teamID:t="",name:i}){return{method:"POST",path:"/account/ios/identifiers/addAppId.action",payload:{teamId:t,name:i??e,identifier:e,type:"explicit"}}}function q({csrPEM:e,teamID:t=""}){return{method:"POST",path:"/account/ios/certificate/submitCertificateRequest.action",payload:{teamId:t,type:"83Q87W3TGH",csrContent:e}}}function v({csrPEM:e,teamID:t=""}){return{method:"POST",path:"/account/ios/certificate/submitCertificateRequest.action",payload:{teamId:t,type:"WXV89964HE",csrContent:e}}}function P(e,t=""){return{method:"GET",path:"/account/ios/certificate/downloadCertificateContent.action",payload:{teamId:t,certificateId:e,type:"83Q87W3TGH"}}}function D(e,t=""){return{method:"GET",path:"/account/ios/certificate/downloadCertificateContent.action",payload:{teamId:t,certificateId:e,type:"WXV89964HE"}}}function I({bundleID:e,teamID:t="",appIDID:i,certificateID:o,deviceIDs:n,name:r}){return{method:"POST",path:"/account/ios/profile/createProvisioningProfile.action",payload:{teamId:t,provisioningProfileName:r??`Limrun ${e}`,certificateIds:[o],appIdId:i,deviceIds:n,distributionType:"limited",subPlatform:"ios"}}}function h({bundleID:e,teamID:t="",appIDID:i,certificateID:o,deviceIDs:n,name:r}){return{method:"POST",path:"/account/ios/profile/createProvisioningProfile.action",payload:{teamId:t,provisioningProfileName:r??`Limrun Ad Hoc ${e}`,certificateIds:[o],appIdId:i,deviceIds:n,distributionType:"adhoc",subPlatform:"ios"}}}function C(e,t=""){return{method:"GET",path:"/account/ios/profile/downloadProfileContent",payload:{teamId:t,provisioningProfileId:e}}}async function b({bundleID:e,deviceUDID:t,teamID:i,signingMode:o}){const n=await a.getSigningAssets({bundleID:e,deviceUDID:t,signingMode:o});if(!(!n||!S(n,{bundleID:e,deviceUDID:t,teamID:i,signingMode:o})))return n}async function y(e){return a.putSigningAssets({...e,certificateFileName:e.certificateID?`${e.certificateID}.p12`:`apple-${e.signingMode??"development"}.p12`,certificatePassword:e.certificatePassword||void 0,signingMode:e.signingMode,profileFileName:e.profile.uuid?`${e.profile.uuid}.mobileprovision`:`${e.signingMode??"development"}.mobileprovision`})}function S(e,{bundleID:t,deviceUDID:i,teamID:o,signingMode:n="development"}){return(e.signingMode??"development")!==n||!a.profileMatchesBundleID(e.profile,t)||o&&e.teamID&&e.teamID!==o||i&&!a.profileContainsDevice(e.profile,i)||e.profile.expirationDate&&new Date(e.profile.expirationDate).getTime()<=Date.now()?!1:a.normalizeBundleID(e.bundleID)===a.normalizeBundleID(t)}function s(e,t,i={}){const o={pageNumber:1,pageSize:200,...i};return t&&(o.teamId=t),{method:"POST",path:e,payload:o}}exports.createAdHocProfileRequest=h;exports.createBundleIDRequest=g;exports.createDevelopmentProfileRequest=I;exports.downloadCertificateRequest=P;exports.downloadDistributionCertificateRequest=D;exports.downloadProfileRequest=C;exports.findAdHocProfilesRequest=m;exports.findBundleIDRequest=u;exports.findDevelopmentCertificatesRequest=f;exports.findDevelopmentProfilesRequest=p;exports.findDeviceRequest=d;exports.findDistributionCertificatesRequest=l;exports.getReusableAppleSigningAssets=b;exports.listTeamsRequest=c;exports.putAppleGeneratedSigningAssets=y;exports.registerDeviceRequest=R;exports.submitDevelopmentCSRRequest=q;exports.submitDistributionCSRRequest=v;
|
package/package.json
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
|
|
3
3
|
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
4
|
-
import {
|
|
5
|
-
assertAppleDeveloperPortalResponseOK,
|
|
6
|
-
deleteAppleProfile,
|
|
7
|
-
listAppleTeams,
|
|
8
|
-
} from './index';
|
|
4
|
+
import { assertAppleDeveloperPortalResponseOK, deleteAppleProfile, listAppleTeams } from './index';
|
|
9
5
|
|
|
10
6
|
describe('App Store relay primitives', () => {
|
|
11
7
|
afterEach(() => {
|
|
@@ -170,7 +170,12 @@ export async function listAppleTeams(options: AppleRelayClientOptions) {
|
|
|
170
170
|
...options,
|
|
171
171
|
request: listTeamsRequest(),
|
|
172
172
|
label: 'Apple Developer team list',
|
|
173
|
-
mapBody: (body) =>
|
|
173
|
+
mapBody: (body) =>
|
|
174
|
+
uniqueAppleTeams([
|
|
175
|
+
...(body?.teams ?? []),
|
|
176
|
+
...(body?.availableProviders ?? []),
|
|
177
|
+
...(body?.provider ? [body.provider] : []),
|
|
178
|
+
]),
|
|
174
179
|
});
|
|
175
180
|
}
|
|
176
181
|
|
|
@@ -182,9 +187,9 @@ export async function listAppleCertificates({
|
|
|
182
187
|
return requestAppleProvisioning<Array<Record<string, unknown>>>({
|
|
183
188
|
...options,
|
|
184
189
|
request:
|
|
185
|
-
certificateKind === 'distribution'
|
|
186
|
-
|
|
187
|
-
|
|
190
|
+
certificateKind === 'distribution' ?
|
|
191
|
+
findDistributionCertificatesRequest(teamId)
|
|
192
|
+
: findDevelopmentCertificatesRequest(teamId),
|
|
188
193
|
label: 'Apple Developer certificate list',
|
|
189
194
|
mapBody: (body) => body?.certRequests ?? [],
|
|
190
195
|
});
|
|
@@ -199,10 +204,13 @@ export async function createAppleCertificate({
|
|
|
199
204
|
return requestAppleProvisioning<Record<string, unknown> | undefined>({
|
|
200
205
|
...options,
|
|
201
206
|
request:
|
|
202
|
-
certificateKind === 'distribution'
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
label:
|
|
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',
|
|
206
214
|
mapBody: (body) => body?.certRequest,
|
|
207
215
|
});
|
|
208
216
|
}
|
|
@@ -216,9 +224,9 @@ export async function downloadAppleCertificate({
|
|
|
216
224
|
return requestAppleProvisioning<AppleRelayResponse>({
|
|
217
225
|
...options,
|
|
218
226
|
request:
|
|
219
|
-
certificateKind === 'distribution'
|
|
220
|
-
|
|
221
|
-
|
|
227
|
+
certificateKind === 'distribution' ?
|
|
228
|
+
downloadDistributionCertificateRequest(certificateId, teamId)
|
|
229
|
+
: downloadCertificateRequest(certificateId, teamId),
|
|
222
230
|
label: 'Apple certificate download',
|
|
223
231
|
validatePortalResponse: false,
|
|
224
232
|
mapBody: (_body, response) => response,
|
|
@@ -247,16 +255,36 @@ export async function deleteAppleCertificate({
|
|
|
247
255
|
});
|
|
248
256
|
}
|
|
249
257
|
|
|
250
|
-
export async function listAppleBundleIDs({
|
|
258
|
+
export async function listAppleBundleIDs({
|
|
259
|
+
teamId = '',
|
|
260
|
+
search = '',
|
|
261
|
+
...options
|
|
262
|
+
}: ListAppleBundleIDsOptions) {
|
|
263
|
+
const query = search.trim().toLowerCase();
|
|
251
264
|
return requestAppleProvisioning<AppleDeveloperPortalAppID[]>({
|
|
252
265
|
...options,
|
|
253
266
|
request: findBundleIDRequest({ bundleID: search, teamID: teamId }),
|
|
254
267
|
label: 'Apple bundle ID list',
|
|
255
|
-
|
|
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
|
+
},
|
|
256
279
|
});
|
|
257
280
|
}
|
|
258
281
|
|
|
259
|
-
export async function createAppleBundleID({
|
|
282
|
+
export async function createAppleBundleID({
|
|
283
|
+
teamId = '',
|
|
284
|
+
bundleId,
|
|
285
|
+
name,
|
|
286
|
+
...options
|
|
287
|
+
}: CreateAppleBundleIDOptions) {
|
|
260
288
|
return requestAppleProvisioning<Record<string, unknown> | undefined>({
|
|
261
289
|
...options,
|
|
262
290
|
request: createBundleIDRequest({ bundleID: bundleId, teamID: teamId, name }),
|
|
@@ -302,16 +330,39 @@ export async function deleteAppleBundleID({ teamId = '', appIdId, ...options }:
|
|
|
302
330
|
});
|
|
303
331
|
}
|
|
304
332
|
|
|
305
|
-
export async function listAppleDevices({
|
|
333
|
+
export async function listAppleDevices({
|
|
334
|
+
teamId = '',
|
|
335
|
+
deviceUDID = '',
|
|
336
|
+
...options
|
|
337
|
+
}: ListAppleDevicesOptions) {
|
|
338
|
+
const wantedUDID = normalizeAppleUDID(deviceUDID);
|
|
306
339
|
return requestAppleProvisioning<AppleDeveloperPortalDevice[]>({
|
|
307
340
|
...options,
|
|
308
341
|
request: findDeviceRequest({ deviceUDID, teamID: teamId }),
|
|
309
342
|
label: 'Apple device list',
|
|
310
|
-
|
|
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
|
+
},
|
|
311
350
|
});
|
|
312
351
|
}
|
|
313
352
|
|
|
314
|
-
|
|
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) {
|
|
315
366
|
return requestAppleProvisioning<Record<string, unknown> | undefined>({
|
|
316
367
|
...options,
|
|
317
368
|
request: registerDeviceRequest({ deviceUDID, teamID: teamId, name }),
|
|
@@ -320,7 +371,12 @@ export async function registerAppleDevice({ teamId = '', deviceUDID, name, ...op
|
|
|
320
371
|
});
|
|
321
372
|
}
|
|
322
373
|
|
|
323
|
-
export async function updateAppleDevice({
|
|
374
|
+
export async function updateAppleDevice({
|
|
375
|
+
teamId = '',
|
|
376
|
+
deviceId,
|
|
377
|
+
name,
|
|
378
|
+
...options
|
|
379
|
+
}: UpdateAppleDeviceOptions) {
|
|
324
380
|
return requestAppleProvisioning<Record<string, unknown> | undefined>({
|
|
325
381
|
...options,
|
|
326
382
|
request: {
|
|
@@ -355,9 +411,9 @@ export async function listAppleProfiles({
|
|
|
355
411
|
return requestAppleProvisioning<Array<Record<string, unknown>>>({
|
|
356
412
|
...options,
|
|
357
413
|
request:
|
|
358
|
-
profileKind === 'adhoc'
|
|
359
|
-
|
|
360
|
-
|
|
414
|
+
profileKind === 'adhoc' ?
|
|
415
|
+
findAdHocProfilesRequest({ bundleID: bundleId, teamID: teamId })
|
|
416
|
+
: findDevelopmentProfilesRequest({ bundleID: bundleId, teamID: teamId }),
|
|
361
417
|
label: 'Apple provisioning profile list',
|
|
362
418
|
mapBody: (body) => body?.provisioningProfiles ?? [],
|
|
363
419
|
});
|
|
@@ -380,29 +436,33 @@ export async function createAppleProfile({
|
|
|
380
436
|
return requestAppleProvisioning<Record<string, unknown> | undefined>({
|
|
381
437
|
...options,
|
|
382
438
|
request:
|
|
383
|
-
profileKind === 'adhoc'
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
+
}),
|
|
400
456
|
label: 'Apple provisioning profile creation',
|
|
401
457
|
mapBody: (body) => body?.provisioningProfile,
|
|
402
458
|
});
|
|
403
459
|
}
|
|
404
460
|
|
|
405
|
-
export async function downloadAppleProfile({
|
|
461
|
+
export async function downloadAppleProfile({
|
|
462
|
+
teamId = '',
|
|
463
|
+
profileId,
|
|
464
|
+
...options
|
|
465
|
+
}: DownloadAppleProfileOptions) {
|
|
406
466
|
return requestAppleProvisioning<AppleRelayResponse>({
|
|
407
467
|
...options,
|
|
408
468
|
request: downloadProfileRequest(profileId, teamId),
|
|
@@ -425,12 +485,17 @@ export async function deleteAppleProfile({ teamId = '', profileId, ...options }:
|
|
|
425
485
|
});
|
|
426
486
|
}
|
|
427
487
|
|
|
428
|
-
export function assertAppleDeveloperPortalResponseOK(
|
|
488
|
+
export function assertAppleDeveloperPortalResponseOK(
|
|
489
|
+
response: AppleDeveloperPortalResponse | undefined,
|
|
490
|
+
label: string,
|
|
491
|
+
) {
|
|
429
492
|
if (!response) {
|
|
430
493
|
throw new Error(`${label} returned an empty response.`);
|
|
431
494
|
}
|
|
432
495
|
if (response.resultCode !== undefined && response.resultCode !== 0) {
|
|
433
|
-
throw new Error(
|
|
496
|
+
throw new Error(
|
|
497
|
+
`${label} failed: ${response.userString ?? response.resultString ?? response.resultCode}`,
|
|
498
|
+
);
|
|
434
499
|
}
|
|
435
500
|
}
|
|
436
501
|
|
|
@@ -438,7 +503,9 @@ function uniqueAppleTeams(teams: AppleDeveloperPortalTeam[]) {
|
|
|
438
503
|
const seen = new Set<string>();
|
|
439
504
|
const result: AppleDeveloperPortalTeam[] = [];
|
|
440
505
|
for (const team of teams) {
|
|
441
|
-
const key = String(
|
|
506
|
+
const key = String(
|
|
507
|
+
team.teamId ?? team.providerId ?? team.publicProviderId ?? team.name ?? JSON.stringify(team),
|
|
508
|
+
);
|
|
442
509
|
if (seen.has(key)) continue;
|
|
443
510
|
seen.add(key);
|
|
444
511
|
result.push(team);
|
|
@@ -21,7 +21,9 @@ export type UseAppleIDLoginResult = {
|
|
|
21
21
|
completeResponse?: AppleRelayResponse;
|
|
22
22
|
twoFactorChallengeResponse?: AppleRelayResponse;
|
|
23
23
|
error?: string;
|
|
24
|
-
startLogin: (
|
|
24
|
+
startLogin: (
|
|
25
|
+
input: Pick<AppleIDLoginInput, 'accountName' | 'password'>,
|
|
26
|
+
) => Promise<AppleIDLoginResult | undefined>;
|
|
25
27
|
submitTwoFactorCode: (code: string) => Promise<AppleRelayResponse | undefined>;
|
|
26
28
|
finalize: () => Promise<AppleRelayResponse | undefined>;
|
|
27
29
|
close: () => Promise<void>;
|
|
@@ -31,7 +33,9 @@ export function useAppleIDLogin({ limbuildApiUrl, token }: UseAppleIDLoginOption
|
|
|
31
33
|
const [status, setStatus] = useState<AppleIDLoginStatus>('idle');
|
|
32
34
|
const [session, setSession] = useState<AppleIDLoginResult | undefined>();
|
|
33
35
|
const [completeResponse, setCompleteResponse] = useState<AppleRelayResponse | undefined>();
|
|
34
|
-
const [twoFactorChallengeResponse, setTwoFactorChallengeResponse] = useState<
|
|
36
|
+
const [twoFactorChallengeResponse, setTwoFactorChallengeResponse] = useState<
|
|
37
|
+
AppleRelayResponse | undefined
|
|
38
|
+
>();
|
|
35
39
|
const [error, setError] = useState<string | undefined>();
|
|
36
40
|
const sessionRef = useRef<AppleIDLoginResult | undefined>(undefined);
|
|
37
41
|
|
|
@@ -29,9 +29,7 @@ export type AppleIDLoginResult = {
|
|
|
29
29
|
close: () => Promise<void>;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
type TwoFactorMethod =
|
|
33
|
-
| { type: 'trustedDevice' }
|
|
34
|
-
| { type: 'phone'; phoneNumberId: number; mode: string };
|
|
32
|
+
type TwoFactorMethod = { type: 'trustedDevice' } | { type: 'phone'; phoneNumberId: number; mode: string };
|
|
35
33
|
|
|
36
34
|
export async function startBrowserOwnedAppleIDLogin({
|
|
37
35
|
limbuildApiUrl,
|
|
@@ -44,7 +42,9 @@ export async function startBrowserOwnedAppleIDLogin({
|
|
|
44
42
|
const srp = new AppleGsaSrpClient(accountName);
|
|
45
43
|
const initResponse = await proxySrpInit(limbuildApiUrl, appleSessionId, await srp.init(), token);
|
|
46
44
|
if (initResponse.status < 200 || initResponse.status >= 300) {
|
|
47
|
-
throw new Error(
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Apple SRP init failed: HTTP ${initResponse.status} ${initResponse.rawBody ?? ''}`.trim(),
|
|
47
|
+
);
|
|
48
48
|
}
|
|
49
49
|
if (!initResponse.body) {
|
|
50
50
|
throw new Error('Apple SRP init response did not include a body.');
|
|
@@ -66,17 +66,20 @@ export async function startBrowserOwnedAppleIDLogin({
|
|
|
66
66
|
if (requiresTwoFactor) {
|
|
67
67
|
twoFactorChallengeResponse = await triggerTrustedDeviceTwoFactor(limbuildApiUrl, appleSessionId, token);
|
|
68
68
|
const phone = trustedPhoneNumberFromChallenge(twoFactorChallengeResponse.body);
|
|
69
|
-
|
|
69
|
+
// Only route codes through the phone/SMS endpoint when Apple actually
|
|
70
|
+
// falls back to phone verification (HTTP 412). The trusted-device
|
|
71
|
+
// challenge body frequently includes trusted-phone metadata even for a
|
|
72
|
+
// normal device push; routing those codes to the phone endpoint breaks
|
|
73
|
+
// trusted-device login.
|
|
74
|
+
if (twoFactorChallengeResponse.status === 412) {
|
|
75
|
+
if (!phone) {
|
|
76
|
+
throw new Error('Apple requested phone verification but did not include a trusted phone number.');
|
|
77
|
+
}
|
|
70
78
|
twoFactorMethod = {
|
|
71
79
|
type: 'phone',
|
|
72
80
|
phoneNumberId: phone.id,
|
|
73
81
|
mode: phone.pushMode ?? 'sms',
|
|
74
82
|
};
|
|
75
|
-
}
|
|
76
|
-
if (twoFactorChallengeResponse.status === 412) {
|
|
77
|
-
if (!phone) {
|
|
78
|
-
throw new Error('Apple requested phone verification but did not include a trusted phone number.');
|
|
79
|
-
}
|
|
80
83
|
twoFactorChallengeResponse = await triggerPhoneTwoFactor(
|
|
81
84
|
limbuildApiUrl,
|
|
82
85
|
appleSessionId,
|
|
@@ -93,7 +96,9 @@ export async function startBrowserOwnedAppleIDLogin({
|
|
|
93
96
|
);
|
|
94
97
|
}
|
|
95
98
|
} else if (completeResponse.status < 200 || completeResponse.status >= 300) {
|
|
96
|
-
throw new Error(
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Apple SRP complete failed: HTTP ${completeResponse.status} ${completeResponse.rawBody ?? ''}`.trim(),
|
|
101
|
+
);
|
|
97
102
|
}
|
|
98
103
|
return {
|
|
99
104
|
appleSessionId,
|
|
@@ -102,18 +107,20 @@ export async function startBrowserOwnedAppleIDLogin({
|
|
|
102
107
|
requiresTwoFactor,
|
|
103
108
|
finishTwoFactor: async (code) => {
|
|
104
109
|
const response =
|
|
105
|
-
twoFactorMethod.type === 'phone'
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
twoFactorMethod.type === 'phone' ?
|
|
111
|
+
await proxyPhoneTwoFactorCode(
|
|
112
|
+
limbuildApiUrl,
|
|
113
|
+
appleSessionId,
|
|
114
|
+
twoFactorMethod.phoneNumberId,
|
|
115
|
+
code,
|
|
116
|
+
twoFactorMethod.mode,
|
|
117
|
+
token,
|
|
118
|
+
)
|
|
119
|
+
: await proxyTwoFactorCode(limbuildApiUrl, appleSessionId, code, token);
|
|
115
120
|
if (response.status < 200 || response.status >= 300) {
|
|
116
|
-
throw new Error(
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Apple two-factor code failed: HTTP ${response.status} ${response.rawBody ?? ''}`.trim(),
|
|
123
|
+
);
|
|
117
124
|
}
|
|
118
125
|
return response;
|
|
119
126
|
},
|