@limrun/ui 0.13.3-rc.1 → 0.13.4
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/components/remote-control.d.ts +144 -0
- 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/index.cjs +1 -1
- package/dist/index.js +1336 -1089
- 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/components/remote-control.tsx +726 -0
- 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
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Real Device Install Demo - @limrun/ui</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="./demo.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -17,11 +17,7 @@ export {
|
|
|
17
17
|
type StartInstallRelayOptions,
|
|
18
18
|
type StartPairingRelayOptions,
|
|
19
19
|
} from '../core/device-install/operations';
|
|
20
|
-
export {
|
|
21
|
-
getPairRecord,
|
|
22
|
-
normalizeUDID,
|
|
23
|
-
putPairRecord,
|
|
24
|
-
} from '../core/device-install/storage';
|
|
20
|
+
export { getPairRecord, normalizeUDID, putPairRecord } from '../core/device-install/storage';
|
|
25
21
|
export type {
|
|
26
22
|
DeviceHello,
|
|
27
23
|
DeviceInstallLog,
|
|
@@ -36,11 +32,15 @@ export async function requestUSBAccess(options: RequestUSBAccessOptions = {}) {
|
|
|
36
32
|
return requestCoreUSBAccess({ log: options.log ?? noopLog });
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
export async function pairDevice(
|
|
35
|
+
export async function pairDevice(
|
|
36
|
+
options: Omit<StartPairingRelayOptions, 'log'> & { log?: DeviceInstallLog },
|
|
37
|
+
) {
|
|
40
38
|
return startPairingRelay({ ...options, log: options.log ?? noopLog });
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
export async function startDeviceInstall(
|
|
41
|
+
export async function startDeviceInstall(
|
|
42
|
+
options: Omit<StartInstallRelayOptions, 'log'> & { log?: DeviceInstallLog },
|
|
43
|
+
) {
|
|
44
44
|
return startInstallRelay({ ...options, log: options.log ?? noopLog });
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -48,12 +48,18 @@ export function useDeviceInstallRelay({
|
|
|
48
48
|
const [pairConfirmationRequired, setPairConfirmationRequired] = useState(false);
|
|
49
49
|
const relayRef = useRef<RelayClient | undefined>(undefined);
|
|
50
50
|
const deviceRef = useRef<DeviceRelayTarget | undefined>(undefined);
|
|
51
|
+
// Keep the logger in a ref so callbacks below don't change identity when the
|
|
52
|
+
// consumer passes an unmemoized `log`. Otherwise every render would recreate
|
|
53
|
+
// cleanupDeviceAccess, re-run the unmount effect, and close the USB device
|
|
54
|
+
// mid-claim (surfacing as "Unable to claim interface" / "operation in progress").
|
|
55
|
+
const logRef = useRef(log);
|
|
56
|
+
logRef.current = log;
|
|
51
57
|
|
|
52
58
|
const cleanupDeviceAccess = useCallback(async () => {
|
|
53
59
|
relayRef.current?.close();
|
|
54
60
|
relayRef.current = undefined;
|
|
55
|
-
await closeDeviceRelayTarget(deviceRef.current,
|
|
56
|
-
}, [
|
|
61
|
+
await closeDeviceRelayTarget(deviceRef.current, logRef.current);
|
|
62
|
+
}, []);
|
|
57
63
|
|
|
58
64
|
useEffect(() => {
|
|
59
65
|
deviceRef.current = device;
|
|
@@ -72,14 +78,17 @@ export function useDeviceInstallRelay({
|
|
|
72
78
|
let target: DeviceRelayTarget | undefined;
|
|
73
79
|
try {
|
|
74
80
|
await cleanupDeviceAccess();
|
|
75
|
-
target = await requestUSBAccess({ log });
|
|
81
|
+
target = await requestUSBAccess({ log: logRef.current });
|
|
76
82
|
const storedPairRecord = await getPairRecord(target.hello.serialNumber);
|
|
77
83
|
setDevice(target);
|
|
78
84
|
setPairRecord(storedPairRecord);
|
|
79
|
-
|
|
85
|
+
logRef.current(
|
|
86
|
+
storedPairRecord ? 'Pair record found' : 'No pair record found',
|
|
87
|
+
target.hello.serialNumber,
|
|
88
|
+
);
|
|
80
89
|
return target;
|
|
81
90
|
} catch (caught) {
|
|
82
|
-
await closeDeviceRelayTarget(target,
|
|
91
|
+
await closeDeviceRelayTarget(target, logRef.current);
|
|
83
92
|
setDevice(undefined);
|
|
84
93
|
setPairRecord(undefined);
|
|
85
94
|
setError(errorMessage(caught));
|
|
@@ -87,7 +96,7 @@ export function useDeviceInstallRelay({
|
|
|
87
96
|
} finally {
|
|
88
97
|
setBusyAction(undefined);
|
|
89
98
|
}
|
|
90
|
-
}, [cleanupDeviceAccess
|
|
99
|
+
}, [cleanupDeviceAccess]);
|
|
91
100
|
|
|
92
101
|
const pairBrowser = useCallback(async () => {
|
|
93
102
|
if (!apiUrl || !device) {
|
|
@@ -101,27 +110,27 @@ export function useDeviceInstallRelay({
|
|
|
101
110
|
const result = await pairDevice({
|
|
102
111
|
limbuildApiUrl: apiUrl,
|
|
103
112
|
token,
|
|
104
|
-
log,
|
|
113
|
+
log: logRef.current,
|
|
105
114
|
target: device,
|
|
106
115
|
});
|
|
107
116
|
const stored = await putPairRecord(result.pairRecord, {
|
|
108
117
|
productName: device.hello.productName,
|
|
109
118
|
});
|
|
110
119
|
result.relay.close();
|
|
111
|
-
await closeDeviceRelayTarget(device,
|
|
120
|
+
await closeDeviceRelayTarget(device, logRef.current);
|
|
112
121
|
setPairRecord(stored);
|
|
113
122
|
setPairConfirmationRequired(false);
|
|
114
|
-
|
|
123
|
+
logRef.current('Device paired', 'The pair record was stored locally in this browser.');
|
|
115
124
|
return stored;
|
|
116
125
|
} catch (caught) {
|
|
117
|
-
await closeDeviceRelayTarget(device,
|
|
126
|
+
await closeDeviceRelayTarget(device, logRef.current);
|
|
118
127
|
setPairConfirmationRequired(true);
|
|
119
128
|
setError(errorMessage(caught));
|
|
120
129
|
return undefined;
|
|
121
130
|
} finally {
|
|
122
131
|
setBusyAction(undefined);
|
|
123
132
|
}
|
|
124
|
-
}, [apiUrl, cleanupDeviceAccess, device,
|
|
133
|
+
}, [apiUrl, cleanupDeviceAccess, device, token]);
|
|
125
134
|
|
|
126
135
|
const startInstallation = useCallback(async () => {
|
|
127
136
|
if (!apiUrl || !device || !pairRecord) {
|
|
@@ -134,25 +143,25 @@ export function useDeviceInstallRelay({
|
|
|
134
143
|
relayRef.current = await startDeviceInstall({
|
|
135
144
|
limbuildApiUrl: apiUrl,
|
|
136
145
|
token,
|
|
137
|
-
log,
|
|
146
|
+
log: logRef.current,
|
|
138
147
|
target: device,
|
|
139
148
|
pairRecord,
|
|
140
149
|
});
|
|
141
|
-
|
|
150
|
+
logRef.current('Device install started', 'Installation will continue through the connected iPhone.');
|
|
142
151
|
return relayRef.current;
|
|
143
152
|
} catch (caught) {
|
|
144
|
-
await closeDeviceRelayTarget(device,
|
|
153
|
+
await closeDeviceRelayTarget(device, logRef.current);
|
|
145
154
|
setError(errorMessage(caught));
|
|
146
155
|
return undefined;
|
|
147
156
|
} finally {
|
|
148
157
|
setBusyAction(undefined);
|
|
149
158
|
}
|
|
150
|
-
}, [apiUrl, cleanupDeviceAccess, device,
|
|
159
|
+
}, [apiUrl, cleanupDeviceAccess, device, pairRecord, token]);
|
|
151
160
|
|
|
152
161
|
const stopRelay = useCallback(() => {
|
|
153
162
|
void cleanupDeviceAccess();
|
|
154
|
-
|
|
155
|
-
}, [cleanupDeviceAccess
|
|
163
|
+
logRef.current('Device relay stopped');
|
|
164
|
+
}, [cleanupDeviceAccess]);
|
|
156
165
|
|
|
157
166
|
return {
|
|
158
167
|
device,
|
package/src/index.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";const b="limbuild-device-pairing";const l="pairRecords",m="limbuild-device-signing";const c="signingAssets";function d(e){return(e??"").replace(/-/g,"").replace(/[^a-fA-F0-9]/g,"")}function u(e){return(e??"").trim()}async function h(e){const n=d(e);if(!n)return;const r=await g(b,1,l,"udid");return p(r.transaction(l,"readonly").objectStore(l).get(n))}async function E(e,n={}){const r=d(e.udid);if(!r)throw new Error("Cannot store pair record without a UDID.");const t={...e,udid:r,productName:n.productName,updatedAt:new Date().toISOString()},i=await g(b,1,l,"udid");return await p(i.transaction(l,"readwrite").objectStore(l).put(t)),t}async function x({deviceUDID:e,bundleID:n,signingMode:r="development"}){const t=u(n);if(!t)return;const i=d(e),s=await v(I("bundle",t,r));if(s)return s;if(i){const o=await v(I(i,t,r));if(o)return o}return(await O(t,r))[0]}async function _(e="development"){return(await A()).filter(r=>(r.signingMode??"development")===e).sort((r,t)=>new Date(t.updatedAt).getTime()-new Date(r.updatedAt).getTime())[0]}async function R(e,n="development"){return(await A()).filter(t=>!t.certificateID||!t.certificateP12Base64||(t.signingMode??"development")!==n?!1:!e||!t.teamID||t.teamID===e).sort((t,i)=>new Date(i.updatedAt).getTime()-new Date(t.updatedAt).getTime())[0]}async function G(e){const n=u(e.bundleID);if(!n)throw new Error("Cannot store signing assets without a bundle ID.");const r=d(e.deviceUDID),t=e.signingMode??"development",i=I("bundle",n,t),s={...e,id:i,deviceUDID:r||void 0,signingMode:t,bundleID:n,updatedAt:new Date().toISOString()},a=await g(m,1,c,"id");return await p(a.transaction(c,"readwrite").objectStore(c).put(s)),s}async function O(e,n="development"){const r=u(e);return r?(await A()).filter(i=>i.bundleID===r&&(i.signingMode??"development")===n):[]}function z(e,n){const r=d(n);return!!r&&e.provisionedDevices.some(t=>d(t)===r)}function U(e,n){const r=u(n),t=u(e.bundleID);if(!r||!t)return!1;if(t===r||t==="*")return!0;if(!t.endsWith(".*"))return!1;const i=t.slice(0,-1);return r.startsWith(i)}async function V(e){return w(new Uint8Array(await e.arrayBuffer()))}function C(e){const n=atob(e),r=new Uint8Array(n.length);for(let t=0;t<n.length;t+=1)r[t]=n.charCodeAt(t);return w(r)}function w(e){const n=new TextDecoder("latin1").decode(e),r=n.indexOf("<?xml"),t=n.indexOf("</plist>");if(r<0||t<r)throw new Error("Provisioning profile plist not found.");const i=n.slice(r,t+8),s=new DOMParser().parseFromString(i,"application/xml");if(s.querySelector("parsererror"))throw new Error("Provisioning profile plist could not be parsed.");const a=s.querySelector("plist > dict");if(!a)throw new Error("Provisioning profile plist dictionary not found.");const o=S(a);if(!P(o))throw new Error("Provisioning profile plist has an unexpected shape.");const D=P(o.Entitlements)?o.Entitlements:{},y=f(D["application-identifier"]),B=M(y);return{name:f(o.Name),uuid:f(o.UUID),teamID:f(D["com.apple.developer.team-identifier"])??N(o.TeamIdentifier)[0],applicationIdentifier:y,bundleID:B,provisionedDevices:N(o.ProvisionedDevices),getTaskAllow:j(D["get-task-allow"]),expirationDate:f(o.ExpirationDate)}}async function v(e){if(!e)return;const n=await g(m,1,c,"id");return p(n.transaction(c,"readonly").objectStore(c).get(e))}async function A(){const e=await g(m,1,c,"id");return p(e.transaction(c,"readonly").objectStore(c).getAll())}function I(e,n,r){return`${r}:${e}:${n}`}function M(e){if(!e)return;const n=e.indexOf(".");return n>=0?e.slice(n+1):void 0}function S(e){switch(e.tagName){case"dict":return T(e);case"array":return Array.from(e.children).map(S);case"string":case"date":return e.textContent??"";default:return e.textContent??""}}function T(e){const n={},r=Array.from(e.children);for(let t=0;t<r.length;t+=2){const i=r[t],s=r[t+1];!i||i.tagName!=="key"||!s||(n[i.textContent??""]=S(s))}return n}function f(e){return typeof e=="string"&&e?e:void 0}function N(e){return Array.isArray(e)?e.filter(n=>typeof n=="string"):[]}function j(e){return typeof e=="boolean"?e:void 0}function P(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function g(e,n,r,t){return new Promise((i,s)=>{const a=indexedDB.open(e,n);a.onupgradeneeded=()=>{const o=a.result;o.objectStoreNames.contains(r)||o.createObjectStore(r,{keyPath:t})},a.onsuccess=()=>i(a.result),a.onerror=()=>s(a.error??new Error("Open IndexedDB failed"))})}function p(e){return new Promise((n,r)=>{e.onsuccess=()=>n(e.result),e.onerror=()=>r(e.error??new Error("IndexedDB request failed"))})}exports.getLatestSigningAssets=_;exports.getLatestSigningAssetsWithCertificate=R;exports.getPairRecord=h;exports.getSigningAssets=x;exports.normalizeBundleID=u;exports.normalizeUDID=d;exports.parseProvisioningProfile=V;exports.parseProvisioningProfileBase64=C;exports.parseProvisioningProfileBytes=w;exports.profileContainsDevice=z;exports.profileMatchesBundleID=U;exports.putPairRecord=E;exports.putSigningAssets=G;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";async function u(o,e){const a=new URL(`${o}/info`);e&&a.searchParams.set("token",e);const t=await fetch(a.toString(),{headers:e?{Authorization:`Bearer ${e}`}:void 0});if(!t.ok){const n=await t.text();throw new Error(`Info request failed: HTTP ${t.status} ${n}`)}return await t.json()}async function l({limbuildApiUrl:o,token:e,certificateP12Base64:a,certificatePassword:t,provisioningProfileBase64:n}){const d=new URL(`${o}/exec`);e&&d.searchParams.set("token",e);const r=await fetch(d.toString(),{method:"POST",headers:{"Content-Type":"application/json",...e?{Authorization:`Bearer ${e}`}:{}},body:JSON.stringify({command:"xcodebuild",xcodebuild:{sdk:"iphoneos"},signing:{certificateP12Base64:a,...t?{certificatePassword:t}:{},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:e,token:a}){const t=new URL(`${o}/exec/${encodeURIComponent(e)}/ios/ota`);a&&t.searchParams.set("token",a);const n=await fetch(t.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:e,token:a,onLine:t,onStatus:n,onError:d}){const r=new URL(`${o}/exec/${e}/events`);a&&r.searchParams.set("token",a);const s=new EventSource(r.toString());return n("running"),s.addEventListener("command",i=>t({type:"command",data:i.data})),s.addEventListener("stdout",i=>t({type:"stdout",data:i.data})),s.addEventListener("stderr",i=>t({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(),d?.(new Error("Build log stream closed before completion."))},()=>s.close()}exports.fetchLimbuildInfo=u;exports.getIOSOTAInstall=f;exports.startSignedDeviceBuild=l;exports.watchBuildLogEvents=h;
|