@limrun/ui 0.9.0-rc.12 → 0.9.0-rc.13
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/README.md +9 -0
- package/dist/components/device-install/device-install-dialog.d.ts +5 -0
- package/dist/components/device-install/index.d.ts +2 -0
- package/dist/core/device-install/apple/client.d.ts +17 -0
- package/dist/core/device-install/apple/crypto.d.ts +20 -0
- package/dist/core/device-install/apple/gsa-srp.d.ts +26 -0
- package/dist/core/device-install/apple/index.d.ts +5 -0
- package/dist/core/device-install/apple/provisioning.d.ts +161 -0
- package/dist/core/device-install/apple/relay.d.ts +29 -0
- package/dist/core/device-install/index.d.ts +4 -0
- package/dist/core/device-install/operations/index.d.ts +6 -0
- package/dist/core/device-install/operations/limbuild-client.d.ts +28 -0
- package/dist/core/device-install/operations/operations.d.ts +32 -0
- package/dist/core/device-install/operations/relay-client.d.ts +25 -0
- package/dist/core/device-install/operations/relay-protocol.d.ts +27 -0
- package/dist/core/device-install/operations/usbmux.d.ts +32 -0
- package/dist/core/device-install/operations/webusb.d.ts +21 -0
- package/dist/core/device-install/storage/browser-storage.d.ts +44 -0
- package/dist/core/device-install/storage/index.d.ts +1 -0
- package/dist/core/device-install/types.d.ts +48 -0
- package/dist/device-install/index.cjs +1 -0
- package/dist/device-install/index.d.ts +3 -0
- package/dist/device-install/index.js +78 -0
- package/dist/device-install/react.cjs +1 -0
- package/dist/device-install/react.d.ts +1 -0
- package/dist/device-install/react.js +4 -0
- package/dist/device-install-dialog-CjH25hnN.js +2 -0
- package/dist/device-install-dialog-W5Xv9kWL.mjs +443 -0
- package/dist/device-install-dialog.css +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/use-device-install.d.ts +73 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +568 -575
- package/dist/use-device-install-Y1u6vIBB.js +31 -0
- package/dist/use-device-install-sDVvby1V.mjs +13627 -0
- package/package.json +15 -2
- package/src/components/device-install/device-install-dialog.css +325 -0
- package/src/components/device-install/device-install-dialog.tsx +495 -0
- package/src/components/device-install/index.ts +2 -0
- package/src/core/device-install/apple/client.ts +152 -0
- package/src/core/device-install/apple/crypto.ts +202 -0
- package/src/core/device-install/apple/gsa-srp.ts +127 -0
- package/src/core/device-install/apple/index.ts +5 -0
- package/src/core/device-install/apple/provisioning.ts +298 -0
- package/src/core/device-install/apple/relay.ts +221 -0
- package/src/core/device-install/index.ts +4 -0
- package/src/core/device-install/operations/index.ts +6 -0
- package/src/core/device-install/operations/limbuild-client.ts +104 -0
- package/src/core/device-install/operations/operations.ts +217 -0
- package/src/core/device-install/operations/relay-client.ts +255 -0
- package/src/core/device-install/operations/relay-protocol.ts +71 -0
- package/src/core/device-install/operations/usbmux.ts +270 -0
- package/src/core/device-install/operations/webusb-dom.d.ts +54 -0
- package/src/core/device-install/operations/webusb.ts +105 -0
- package/src/core/device-install/storage/browser-storage.ts +263 -0
- package/src/core/device-install/storage/index.ts +1 -0
- package/src/core/device-install/types.ts +65 -0
- package/src/device-install/index.ts +3 -0
- package/src/device-install/react.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-device-install.ts +1221 -0
- package/src/index.ts +3 -0
- package/vite.config.ts +6 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/// <reference path="./webusb-dom.d.ts" />
|
|
2
|
+
|
|
3
|
+
export type UsbEndpoint = {
|
|
4
|
+
endpointNumber: number;
|
|
5
|
+
direction: 'in' | 'out';
|
|
6
|
+
type: string;
|
|
7
|
+
packetSize: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type UsbmuxCandidate = {
|
|
11
|
+
configurationValue: number;
|
|
12
|
+
interfaceNumber: number;
|
|
13
|
+
alternateSetting: number;
|
|
14
|
+
endpoints: UsbEndpoint[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function requestAppleDevice() {
|
|
18
|
+
if (!navigator.usb) {
|
|
19
|
+
throw new Error('WebUSB is not available in this browser.');
|
|
20
|
+
}
|
|
21
|
+
return navigator.usb.requestDevice({ filters: [{ vendorId: 0x05ac }] });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function findUsbmuxCandidates(device: USBDevice): UsbmuxCandidate[] {
|
|
25
|
+
const candidates: UsbmuxCandidate[] = [];
|
|
26
|
+
const activeConfigurationValue = device.configuration?.configurationValue;
|
|
27
|
+
const activeConfiguration = device.configurations.find(
|
|
28
|
+
(configuration) => configuration.configurationValue === activeConfigurationValue,
|
|
29
|
+
);
|
|
30
|
+
if (activeConfiguration) {
|
|
31
|
+
candidates.push(...findConfigurationUsbmuxCandidates(activeConfiguration));
|
|
32
|
+
}
|
|
33
|
+
for (const configuration of device.configurations) {
|
|
34
|
+
if (configuration.configurationValue === activeConfigurationValue) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
candidates.push(...findConfigurationUsbmuxCandidates(configuration));
|
|
38
|
+
}
|
|
39
|
+
return candidates;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function findConfigurationUsbmuxCandidates(configuration: USBDevice['configurations'][number]): UsbmuxCandidate[] {
|
|
43
|
+
const candidates: UsbmuxCandidate[] = [];
|
|
44
|
+
for (const usbInterface of configuration.interfaces) {
|
|
45
|
+
for (const alternate of usbInterface.alternates) {
|
|
46
|
+
if (
|
|
47
|
+
alternate.interfaceClass === 0xff &&
|
|
48
|
+
alternate.interfaceSubclass === 0xfe &&
|
|
49
|
+
alternate.interfaceProtocol === 0x02
|
|
50
|
+
) {
|
|
51
|
+
candidates.push({
|
|
52
|
+
configurationValue: configuration.configurationValue,
|
|
53
|
+
interfaceNumber: usbInterface.interfaceNumber,
|
|
54
|
+
alternateSetting: alternate.alternateSetting,
|
|
55
|
+
endpoints: alternate.endpoints,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return candidates;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function claimUsbmux(device: USBDevice, candidate: UsbmuxCandidate) {
|
|
64
|
+
if (!device.opened) {
|
|
65
|
+
await device.open();
|
|
66
|
+
}
|
|
67
|
+
if (!device.configuration || device.configuration.configurationValue !== candidate.configurationValue) {
|
|
68
|
+
await device.selectConfiguration(candidate.configurationValue);
|
|
69
|
+
}
|
|
70
|
+
await device.claimInterface(candidate.interfaceNumber);
|
|
71
|
+
if (candidate.alternateSetting !== 0) {
|
|
72
|
+
await device.selectAlternateInterface(candidate.interfaceNumber, candidate.alternateSetting);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getBulkEndpoints(candidate: UsbmuxCandidate) {
|
|
77
|
+
const outEndpoint = candidate.endpoints.find(
|
|
78
|
+
(endpoint) => endpoint.direction === 'out' && endpoint.type === 'bulk',
|
|
79
|
+
);
|
|
80
|
+
const inEndpoint = candidate.endpoints.find(
|
|
81
|
+
(endpoint) => endpoint.direction === 'in' && endpoint.type === 'bulk',
|
|
82
|
+
);
|
|
83
|
+
if (!outEndpoint || !inEndpoint) {
|
|
84
|
+
throw new Error('Could not find usbmux bulk endpoints.');
|
|
85
|
+
}
|
|
86
|
+
return { outEndpoint, inEndpoint };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function transferOutWithZlp(device: USBDevice, endpoint: UsbEndpoint, bytes: Uint8Array) {
|
|
90
|
+
const result = await device.transferOut(endpoint.endpointNumber, bytes);
|
|
91
|
+
if (result.status !== 'ok') {
|
|
92
|
+
throw new Error(`USB transferOut failed: ${result.status}`);
|
|
93
|
+
}
|
|
94
|
+
if (bytes.byteLength % endpoint.packetSize === 0) {
|
|
95
|
+
await device.transferOut(endpoint.endpointNumber, new Uint8Array());
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function transferIn(device: USBDevice, endpoint: UsbEndpoint, size = 16384) {
|
|
100
|
+
const result = await device.transferIn(endpoint.endpointNumber, size);
|
|
101
|
+
if (result.status !== 'ok' || !result.data) {
|
|
102
|
+
throw new Error(`USB transferIn failed: ${result.status}`);
|
|
103
|
+
}
|
|
104
|
+
return new Uint8Array(result.data.buffer, result.data.byteOffset, result.data.byteLength);
|
|
105
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ProvisioningProfileInfo,
|
|
3
|
+
PutSigningAssetsInput,
|
|
4
|
+
StoredPairRecord,
|
|
5
|
+
StoredSigningAssets,
|
|
6
|
+
} from '../types';
|
|
7
|
+
import type { PairRecordPayload } from '../types';
|
|
8
|
+
|
|
9
|
+
const PAIRING_DB_NAME = 'limbuild-device-pairing';
|
|
10
|
+
const PAIRING_DB_VERSION = 1;
|
|
11
|
+
const PAIRING_STORE_NAME = 'pairRecords';
|
|
12
|
+
const SIGNING_DB_NAME = 'limbuild-device-signing';
|
|
13
|
+
const SIGNING_DB_VERSION = 1;
|
|
14
|
+
const SIGNING_STORE_NAME = 'signingAssets';
|
|
15
|
+
|
|
16
|
+
export function normalizeUDID(udid?: string) {
|
|
17
|
+
return (udid ?? '').replace(/-/g, '').replace(/[^a-fA-F0-9]/g, '');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function normalizeBundleID(bundleID?: string) {
|
|
21
|
+
return (bundleID ?? '').trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getPairRecord(udid?: string) {
|
|
25
|
+
const normalized = normalizeUDID(udid);
|
|
26
|
+
if (!normalized) return undefined;
|
|
27
|
+
const db = await openDB(PAIRING_DB_NAME, PAIRING_DB_VERSION, PAIRING_STORE_NAME, 'udid');
|
|
28
|
+
return requestToPromise<StoredPairRecord | undefined>(
|
|
29
|
+
db.transaction(PAIRING_STORE_NAME, 'readonly').objectStore(PAIRING_STORE_NAME).get(normalized),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function putPairRecord(record: PairRecordPayload, metadata: { productName?: string } = {}) {
|
|
34
|
+
const normalized = normalizeUDID(record.udid);
|
|
35
|
+
if (!normalized) throw new Error('Cannot store pair record without a UDID.');
|
|
36
|
+
const stored: StoredPairRecord = {
|
|
37
|
+
...record,
|
|
38
|
+
udid: normalized,
|
|
39
|
+
productName: metadata.productName,
|
|
40
|
+
updatedAt: new Date().toISOString(),
|
|
41
|
+
};
|
|
42
|
+
const db = await openDB(PAIRING_DB_NAME, PAIRING_DB_VERSION, PAIRING_STORE_NAME, 'udid');
|
|
43
|
+
await requestToPromise(
|
|
44
|
+
db.transaction(PAIRING_STORE_NAME, 'readwrite').objectStore(PAIRING_STORE_NAME).put(stored),
|
|
45
|
+
);
|
|
46
|
+
return stored;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function getSigningAssets({
|
|
50
|
+
deviceUDID,
|
|
51
|
+
bundleID,
|
|
52
|
+
}: {
|
|
53
|
+
deviceUDID?: string;
|
|
54
|
+
bundleID?: string;
|
|
55
|
+
}) {
|
|
56
|
+
const normalizedBundleID = normalizeBundleID(bundleID);
|
|
57
|
+
if (!normalizedBundleID) return undefined;
|
|
58
|
+
const normalizedUDID = normalizeUDID(deviceUDID);
|
|
59
|
+
const bundleScoped = await getSigningAssetsByID(signingAssetID('bundle', normalizedBundleID));
|
|
60
|
+
if (bundleScoped) return bundleScoped;
|
|
61
|
+
if (normalizedUDID) {
|
|
62
|
+
const exact = await getSigningAssetsByID(signingAssetID(normalizedUDID, normalizedBundleID));
|
|
63
|
+
if (exact) return exact;
|
|
64
|
+
}
|
|
65
|
+
const candidates = await findSigningAssetsForBundle(normalizedBundleID);
|
|
66
|
+
return candidates[0];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function getLatestSigningAssets() {
|
|
70
|
+
const all = await getAllSigningAssets();
|
|
71
|
+
return all.sort(
|
|
72
|
+
(left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime(),
|
|
73
|
+
)[0];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function getLatestSigningAssetsWithCertificate(teamID?: string) {
|
|
77
|
+
const all = await getAllSigningAssets();
|
|
78
|
+
return all
|
|
79
|
+
.filter((asset) => {
|
|
80
|
+
if (!asset.certificateID || !asset.certificateP12Base64 || !asset.certificatePassword) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return !teamID || !asset.teamID || asset.teamID === teamID;
|
|
84
|
+
})
|
|
85
|
+
.sort((left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime())[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function putSigningAssets(input: PutSigningAssetsInput) {
|
|
89
|
+
const normalizedBundleID = normalizeBundleID(input.bundleID);
|
|
90
|
+
if (!normalizedBundleID) {
|
|
91
|
+
throw new Error('Cannot store signing assets without a bundle ID.');
|
|
92
|
+
}
|
|
93
|
+
const normalizedUDID = normalizeUDID(input.deviceUDID);
|
|
94
|
+
const id = signingAssetID('bundle', normalizedBundleID);
|
|
95
|
+
const stored: StoredSigningAssets = {
|
|
96
|
+
...input,
|
|
97
|
+
id,
|
|
98
|
+
deviceUDID: normalizedUDID || undefined,
|
|
99
|
+
bundleID: normalizedBundleID,
|
|
100
|
+
updatedAt: new Date().toISOString(),
|
|
101
|
+
};
|
|
102
|
+
const db = await openDB(SIGNING_DB_NAME, SIGNING_DB_VERSION, SIGNING_STORE_NAME, 'id');
|
|
103
|
+
await requestToPromise(
|
|
104
|
+
db.transaction(SIGNING_STORE_NAME, 'readwrite').objectStore(SIGNING_STORE_NAME).put(stored),
|
|
105
|
+
);
|
|
106
|
+
return stored;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function findSigningAssetsForBundle(bundleID?: string) {
|
|
110
|
+
const normalized = normalizeBundleID(bundleID);
|
|
111
|
+
if (!normalized) return [];
|
|
112
|
+
const all = await getAllSigningAssets();
|
|
113
|
+
return all.filter((asset) => asset.bundleID === normalized);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function profileContainsDevice(profile: ProvisioningProfileInfo, deviceUDID?: string) {
|
|
117
|
+
const normalized = normalizeUDID(deviceUDID);
|
|
118
|
+
return !!normalized && profile.provisionedDevices.some((device) => normalizeUDID(device) === normalized);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function profileMatchesBundleID(profile: ProvisioningProfileInfo, bundleID?: string) {
|
|
122
|
+
const expected = normalizeBundleID(bundleID);
|
|
123
|
+
const profileBundleID = normalizeBundleID(profile.bundleID);
|
|
124
|
+
if (!expected || !profileBundleID) return false;
|
|
125
|
+
if (profileBundleID === expected) return true;
|
|
126
|
+
if (profileBundleID === '*') return true;
|
|
127
|
+
if (!profileBundleID.endsWith('.*')) return false;
|
|
128
|
+
const prefix = profileBundleID.slice(0, -1);
|
|
129
|
+
return expected.startsWith(prefix);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function parseProvisioningProfile(file: File) {
|
|
133
|
+
return parseProvisioningProfileBytes(new Uint8Array(await file.arrayBuffer()));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function parseProvisioningProfileBase64(base64: string) {
|
|
137
|
+
const binary = atob(base64);
|
|
138
|
+
const bytes = new Uint8Array(binary.length);
|
|
139
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
140
|
+
bytes[index] = binary.charCodeAt(index);
|
|
141
|
+
}
|
|
142
|
+
return parseProvisioningProfileBytes(bytes);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function parseProvisioningProfileBytes(bytes: Uint8Array) {
|
|
146
|
+
const text = new TextDecoder('latin1').decode(bytes);
|
|
147
|
+
const start = text.indexOf('<?xml');
|
|
148
|
+
const end = text.indexOf('</plist>');
|
|
149
|
+
if (start < 0 || end < start) {
|
|
150
|
+
throw new Error('Provisioning profile plist not found.');
|
|
151
|
+
}
|
|
152
|
+
const xml = text.slice(start, end + '</plist>'.length);
|
|
153
|
+
const doc = new DOMParser().parseFromString(xml, 'application/xml');
|
|
154
|
+
if (doc.querySelector('parsererror')) {
|
|
155
|
+
throw new Error('Provisioning profile plist could not be parsed.');
|
|
156
|
+
}
|
|
157
|
+
const dict = doc.querySelector('plist > dict');
|
|
158
|
+
if (!dict) {
|
|
159
|
+
throw new Error('Provisioning profile plist dictionary not found.');
|
|
160
|
+
}
|
|
161
|
+
const value = readPlistValue(dict);
|
|
162
|
+
if (!isRecord(value)) {
|
|
163
|
+
throw new Error('Provisioning profile plist has an unexpected shape.');
|
|
164
|
+
}
|
|
165
|
+
const entitlements = isRecord(value.Entitlements) ? value.Entitlements : {};
|
|
166
|
+
const applicationIdentifier = stringValue(entitlements['application-identifier']);
|
|
167
|
+
const bundleID = bundleIDFromApplicationIdentifier(applicationIdentifier);
|
|
168
|
+
return {
|
|
169
|
+
name: stringValue(value.Name),
|
|
170
|
+
uuid: stringValue(value.UUID),
|
|
171
|
+
teamID:
|
|
172
|
+
stringValue(entitlements['com.apple.developer.team-identifier']) ??
|
|
173
|
+
stringArrayValue(value.TeamIdentifier)[0],
|
|
174
|
+
applicationIdentifier,
|
|
175
|
+
bundleID,
|
|
176
|
+
provisionedDevices: stringArrayValue(value.ProvisionedDevices),
|
|
177
|
+
expirationDate: stringValue(value.ExpirationDate),
|
|
178
|
+
} satisfies ProvisioningProfileInfo;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function getSigningAssetsByID(id?: string) {
|
|
182
|
+
if (!id) return undefined;
|
|
183
|
+
const db = await openDB(SIGNING_DB_NAME, SIGNING_DB_VERSION, SIGNING_STORE_NAME, 'id');
|
|
184
|
+
return requestToPromise<StoredSigningAssets | undefined>(
|
|
185
|
+
db.transaction(SIGNING_STORE_NAME, 'readonly').objectStore(SIGNING_STORE_NAME).get(id),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function getAllSigningAssets() {
|
|
190
|
+
const db = await openDB(SIGNING_DB_NAME, SIGNING_DB_VERSION, SIGNING_STORE_NAME, 'id');
|
|
191
|
+
return requestToPromise<StoredSigningAssets[]>(
|
|
192
|
+
db.transaction(SIGNING_STORE_NAME, 'readonly').objectStore(SIGNING_STORE_NAME).getAll(),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function signingAssetID(deviceUDID: string, bundleID: string) {
|
|
197
|
+
return `${deviceUDID}:${bundleID}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function bundleIDFromApplicationIdentifier(applicationIdentifier?: string) {
|
|
201
|
+
if (!applicationIdentifier) return undefined;
|
|
202
|
+
const dot = applicationIdentifier.indexOf('.');
|
|
203
|
+
return dot >= 0 ? applicationIdentifier.slice(dot + 1) : undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function readPlistValue(element: Element): unknown {
|
|
207
|
+
switch (element.tagName) {
|
|
208
|
+
case 'dict':
|
|
209
|
+
return readPlistDict(element);
|
|
210
|
+
case 'array':
|
|
211
|
+
return Array.from(element.children).map(readPlistValue);
|
|
212
|
+
case 'string':
|
|
213
|
+
case 'date':
|
|
214
|
+
return element.textContent ?? '';
|
|
215
|
+
default:
|
|
216
|
+
return element.textContent ?? '';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function readPlistDict(dict: Element) {
|
|
221
|
+
const result: Record<string, unknown> = {};
|
|
222
|
+
const children = Array.from(dict.children);
|
|
223
|
+
for (let index = 0; index < children.length; index += 2) {
|
|
224
|
+
const key = children[index];
|
|
225
|
+
const value = children[index + 1];
|
|
226
|
+
if (!key || key.tagName !== 'key' || !value) continue;
|
|
227
|
+
result[key.textContent ?? ''] = readPlistValue(value);
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function stringValue(value: unknown) {
|
|
233
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function stringArrayValue(value: unknown) {
|
|
237
|
+
return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
241
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function openDB(dbName: string, dbVersion: number, storeName: string, keyPath: string) {
|
|
245
|
+
return new Promise<IDBDatabase>((resolve, reject) => {
|
|
246
|
+
const request = indexedDB.open(dbName, dbVersion);
|
|
247
|
+
request.onupgradeneeded = () => {
|
|
248
|
+
const db = request.result;
|
|
249
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
250
|
+
db.createObjectStore(storeName, { keyPath });
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
request.onsuccess = () => resolve(request.result);
|
|
254
|
+
request.onerror = () => reject(request.error ?? new Error('Open IndexedDB failed'));
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function requestToPromise<T = unknown>(request: IDBRequest<T>) {
|
|
259
|
+
return new Promise<T>((resolve, reject) => {
|
|
260
|
+
request.onsuccess = () => resolve(request.result);
|
|
261
|
+
request.onerror = () => reject(request.error ?? new Error('IndexedDB request failed'));
|
|
262
|
+
});
|
|
263
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './browser-storage';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export type DeviceInstallLog = (message: string, detail?: string) => void;
|
|
2
|
+
|
|
3
|
+
export type DeviceInstallStep = 'signing' | 'connect' | 'build' | 'install';
|
|
4
|
+
|
|
5
|
+
export type DeviceInstallStepStatus = 'idle' | 'active' | 'complete' | 'error';
|
|
6
|
+
|
|
7
|
+
export type DeviceInstallBusyAction = 'signing' | 'usb' | 'pair' | 'build' | 'install';
|
|
8
|
+
|
|
9
|
+
export type DeviceInstallBuildStatus =
|
|
10
|
+
| 'idle'
|
|
11
|
+
| 'queued'
|
|
12
|
+
| 'running'
|
|
13
|
+
| 'succeeded'
|
|
14
|
+
| 'failed'
|
|
15
|
+
| 'cancelled';
|
|
16
|
+
|
|
17
|
+
export type BuildLogLine = {
|
|
18
|
+
type: 'command' | 'stdout' | 'stderr' | 'meta';
|
|
19
|
+
data: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type DeviceHello = {
|
|
23
|
+
serialNumber?: string;
|
|
24
|
+
productName?: string;
|
|
25
|
+
manufacturerName?: string;
|
|
26
|
+
productId: number;
|
|
27
|
+
vendorId: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type PairRecordPayload = {
|
|
31
|
+
udid: string;
|
|
32
|
+
pairRecordBase64: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type StoredPairRecord = PairRecordPayload & {
|
|
36
|
+
productName?: string;
|
|
37
|
+
updatedAt: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type ProvisioningProfileInfo = {
|
|
41
|
+
name?: string;
|
|
42
|
+
uuid?: string;
|
|
43
|
+
teamID?: string;
|
|
44
|
+
applicationIdentifier?: string;
|
|
45
|
+
bundleID?: string;
|
|
46
|
+
provisionedDevices: string[];
|
|
47
|
+
expirationDate?: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type StoredSigningAssets = {
|
|
51
|
+
id: string;
|
|
52
|
+
deviceUDID?: string;
|
|
53
|
+
teamID?: string;
|
|
54
|
+
bundleID: string;
|
|
55
|
+
certificateID?: string;
|
|
56
|
+
certificateP12Base64: string;
|
|
57
|
+
certificateFileName?: string;
|
|
58
|
+
certificatePassword: string;
|
|
59
|
+
provisioningProfileBase64: string;
|
|
60
|
+
profileFileName?: string;
|
|
61
|
+
profile: ProvisioningProfileInfo;
|
|
62
|
+
updatedAt: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type PutSigningAssetsInput = Omit<StoredSigningAssets, 'id' | 'updatedAt'>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useDeviceInstall } from '../hooks/use-device-install';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './use-device-install';
|