mirror of
https://github.com/lbr77/SideImpactor.git
synced 2026-05-06 11:14:01 -04:00
more refactor
This commit is contained in:
@@ -11,10 +11,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@lbr77/anisette-js": "0.1.3",
|
||||
"@lbr77/zsign-wasm-resigner-wrapper": "^0.1.5",
|
||||
"@lbr77/zsign-wasm-resigner-wrapper": "workspace:*",
|
||||
"altsign.js": "^0.1.2",
|
||||
"fflate": "^0.8.2",
|
||||
"jszip": "^3.10.1",
|
||||
"libcurl.js": "workspace:*",
|
||||
"node-forge": "^1.3.3",
|
||||
"webmuxd": "workspace:*"
|
||||
},
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,9 +1,11 @@
|
||||
import type { HttpClient } from "@lbr77/anisette-js"
|
||||
import { initLibcurl, libcurl } from "./anisette-libcurl-init"
|
||||
import { initLibcurl } from "./anisette-libcurl-init"
|
||||
import { requireLibcurl } from "./wasm/libcurl"
|
||||
|
||||
export class LibcurlHttpClient implements HttpClient {
|
||||
async get(url: string, headers: Record<string, string>): Promise<Uint8Array> {
|
||||
await initLibcurl()
|
||||
const libcurl = requireLibcurl()
|
||||
|
||||
const response = await libcurl.fetch(url, {
|
||||
method: "GET",
|
||||
@@ -21,6 +23,7 @@ export class LibcurlHttpClient implements HttpClient {
|
||||
|
||||
async post(url: string, body: string, headers: Record<string, string>): Promise<Uint8Array> {
|
||||
await initLibcurl()
|
||||
const libcurl = requireLibcurl()
|
||||
|
||||
const response = await libcurl.fetch(url, {
|
||||
method: "POST",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// @ts-ignore
|
||||
import { libcurl } from "../public/anisette/libcurl_full.mjs"
|
||||
import { loadLibcurl, libcurl } from "./wasm/libcurl"
|
||||
|
||||
let initialized = false
|
||||
let initPromise: Promise<void> | null = null
|
||||
@@ -13,10 +12,11 @@ export async function initLibcurl(): Promise<void> {
|
||||
}
|
||||
|
||||
initPromise = (async () => {
|
||||
const loadedLibcurl = await loadLibcurl()
|
||||
const wsProto = location.protocol === "https:" ? "wss:" : "ws:"
|
||||
const wsUrl = `${wsProto}//${location.host}/wisp/`
|
||||
libcurl.set_websocket(wsUrl)
|
||||
await libcurl.load_wasm()
|
||||
loadedLibcurl.set_websocket(wsUrl)
|
||||
await loadedLibcurl.load_wasm()
|
||||
initialized = true
|
||||
})()
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { strFromU8, unzipSync } from "fflate"
|
||||
import {
|
||||
type AnisetteData,
|
||||
type AppleAPI,
|
||||
type AppID,
|
||||
type Certificate,
|
||||
type Device,
|
||||
type Team,
|
||||
import type {
|
||||
AnisetteData,
|
||||
AppleAPI,
|
||||
AppID,
|
||||
Certificate,
|
||||
Device,
|
||||
Team,
|
||||
} from "altsign.js"
|
||||
import { initLibcurl, libcurl } from "./anisette-libcurl-init"
|
||||
import { initLibcurl } from "./anisette-libcurl-init"
|
||||
import { requireLibcurl } from "./wasm/libcurl"
|
||||
|
||||
const SIGNING_IDENTITY_STORAGE_KEY = "webmuxd:signing-identities"
|
||||
const PRIMARY_APP_INFO_PLIST_RE = /^Payload\/[^/]+\.app\/Info\.plist$/
|
||||
@@ -129,6 +130,7 @@ async function getAppleApi(): Promise<AppleAPI> {
|
||||
}
|
||||
const { AppleAPI, Fetch } = await loadAltsignModule()
|
||||
const appleFetch = new Fetch(initLibcurl, async (url, options) => {
|
||||
const libcurl = requireLibcurl()
|
||||
const response = await libcurl.fetch(url, {
|
||||
method: options.method,
|
||||
headers: options.headers,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export function setupCounter(element: HTMLButtonElement) {
|
||||
let counter = 0
|
||||
const setCounter = (count: number) => {
|
||||
counter = count
|
||||
element.innerHTML = `count is ${counter}`
|
||||
}
|
||||
element.addEventListener('click', () => setCounter(counter + 1))
|
||||
setCounter(0)
|
||||
}
|
||||
@@ -1,121 +1,35 @@
|
||||
import "./style.css"
|
||||
import * as webmuxdModule from "webmuxd"
|
||||
import {
|
||||
initAnisette,
|
||||
getAnisetteData,
|
||||
provisionAnisette,
|
||||
type AnisetteData,
|
||||
} from "./anisette-service"
|
||||
import {
|
||||
loginAppleDeveloperAccount,
|
||||
refreshAppleDeveloperContext,
|
||||
signIpaWithAppleContext,
|
||||
type AppleDeveloperContext,
|
||||
} from "./apple-signing"
|
||||
DirectUsbMuxClient,
|
||||
LOCKDOWN_PORT,
|
||||
WebUsbTransport,
|
||||
createHostId,
|
||||
createOpenSslWasmTlsFactory,
|
||||
createSystemBuid,
|
||||
decodeStoredPairRecord,
|
||||
encodeStoredPairRecord,
|
||||
generatePairRecordWithOpenSslWasm,
|
||||
installIpaViaInstProxy,
|
||||
sanitizeIpaFileName,
|
||||
type PairRecord,
|
||||
type StoredPairRecordPayload,
|
||||
} from "webmuxd"
|
||||
import type { AnisetteData } from "./anisette-service"
|
||||
import type { AppleDeveloperContext } from "./apple-signing"
|
||||
|
||||
interface WebUsbTransportInstance {
|
||||
readonly isOpen: boolean
|
||||
open(): Promise<void>
|
||||
close(): Promise<void>
|
||||
send(data: ArrayBuffer): Promise<void>
|
||||
setDataHandler(handler: ((data: ArrayBuffer) => void) | null): void
|
||||
setDisconnectHandler(handler: ((reason?: unknown) => void) | null): void
|
||||
}
|
||||
type WasmPairRecordPayload = Pick<
|
||||
PairRecord,
|
||||
| "hostId"
|
||||
| "systemBuid"
|
||||
| "hostCertificatePem"
|
||||
| "hostPrivateKeyPem"
|
||||
| "rootCertificatePem"
|
||||
| "rootPrivateKeyPem"
|
||||
| "deviceCertificatePem"
|
||||
>
|
||||
|
||||
interface WebUsbTransportCtor {
|
||||
supported(): boolean
|
||||
requestAppleDevice(): Promise<WebUsbTransportInstance>
|
||||
}
|
||||
|
||||
interface PairRecord {
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
hostCertificatePem: string
|
||||
hostPrivateKeyPem: string
|
||||
rootCertificatePem: string
|
||||
rootPrivateKeyPem: string
|
||||
deviceCertificatePem: string
|
||||
devicePublicKey: Uint8Array
|
||||
escrowBag?: Uint8Array
|
||||
}
|
||||
|
||||
interface StoredPairRecordPayload {
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
hostCertificatePem: string
|
||||
hostPrivateKeyPem: string
|
||||
rootCertificatePem: string
|
||||
rootPrivateKeyPem: string
|
||||
deviceCertificatePem: string
|
||||
devicePublicKey: string
|
||||
escrowBag: string | null
|
||||
}
|
||||
|
||||
interface StartSessionResult {
|
||||
sessionId: string
|
||||
enableSessionSsl: boolean
|
||||
}
|
||||
|
||||
interface DirectUsbMuxClient {
|
||||
readonly isHandshakeComplete: boolean
|
||||
readonly isLockdownConnected: boolean
|
||||
readonly isSessionStarted: boolean
|
||||
readonly isSessionSslEnabled: boolean
|
||||
readonly isTlsActive: boolean
|
||||
readonly isPaired: boolean
|
||||
loadPairRecord(record: PairRecord | null): void
|
||||
openAndHandshake(): Promise<void>
|
||||
connectLockdown(port?: number): Promise<void>
|
||||
getOrFetchDeviceUdid(): Promise<string>
|
||||
getOrFetchDeviceName(): Promise<string | null>
|
||||
pairDevice(hostId: string, systemBuid: string): Promise<PairRecord>
|
||||
startSession(hostId: string, systemBuid: string): Promise<StartSessionResult>
|
||||
close(): Promise<void>
|
||||
}
|
||||
|
||||
interface DirectUsbMuxClientCtor {
|
||||
new (
|
||||
transport: WebUsbTransportInstance,
|
||||
options?: {
|
||||
log?: (message: string) => void
|
||||
onStateChange?: () => void
|
||||
lockdownLabel?: string
|
||||
tlsFactory?: {
|
||||
ensureReady?: () => Promise<void>
|
||||
createConnection(request: {
|
||||
serverName: string
|
||||
caCertificatePem: string
|
||||
certificatePem: string
|
||||
privateKeyPem: string
|
||||
}): {
|
||||
is_handshaking(): boolean
|
||||
write_plaintext(data: Uint8Array): void
|
||||
feed_tls(data: Uint8Array): void
|
||||
take_tls_out(): Uint8Array
|
||||
take_plain_out(): Uint8Array
|
||||
free(): void
|
||||
}
|
||||
}
|
||||
pairRecordFactory?: {
|
||||
createPairRecord(request: {
|
||||
devicePublicKey: Uint8Array
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
}): Promise<PairRecord>
|
||||
}
|
||||
},
|
||||
): DirectUsbMuxClient
|
||||
}
|
||||
|
||||
interface WasmPairRecordPayload {
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
hostCertificatePem: string
|
||||
hostPrivateKeyPem: string
|
||||
rootCertificatePem: string
|
||||
rootPrivateKeyPem: string
|
||||
deviceCertificatePem: string
|
||||
}
|
||||
type AnisetteServiceModule = typeof import("./anisette-service")
|
||||
type AppleSigningModule = typeof import("./apple-signing")
|
||||
|
||||
interface PairedDeviceInfo {
|
||||
udid: string
|
||||
@@ -162,8 +76,6 @@ interface StoredAccountSessionPayload {
|
||||
|
||||
type AppPage = "login" | "sign"
|
||||
|
||||
const LOCKDOWN_PORT = 62078
|
||||
|
||||
const HOST_ID_STORAGE_KEY = "webmuxd:host-id"
|
||||
const SYSTEM_BUID_STORAGE_KEY = "webmuxd:system-buid"
|
||||
const PAIR_RECORDS_STORAGE_KEY = "webmuxd:pair-records-by-udid"
|
||||
@@ -177,67 +89,24 @@ const SELECTED_DEVICE_UDID_STORAGE_KEY = "webmuxd:selected-device-udid"
|
||||
const LOGIN_PAGE_HASH = "#/login"
|
||||
const SIGN_PAGE_HASH = "#/sign"
|
||||
|
||||
const webmuxdModuleValue = webmuxdModule as unknown as Record<string, unknown>
|
||||
let anisetteServicePromise: Promise<AnisetteServiceModule> | null = null
|
||||
let appleSigningModulePromise: Promise<AppleSigningModule> | null = null
|
||||
|
||||
const WebUsbTransport = resolveWebmuxdExport<WebUsbTransportCtor>(
|
||||
webmuxdModuleValue,
|
||||
"WebUsbTransport",
|
||||
)
|
||||
const WebmuxdDirectUsbMuxClient = resolveWebmuxdExport<DirectUsbMuxClientCtor>(
|
||||
webmuxdModuleValue,
|
||||
"DirectUsbMuxClient",
|
||||
)
|
||||
const webmuxdInstallIpaViaInstProxy = resolveWebmuxdExport<
|
||||
(
|
||||
client: DirectUsbMuxClient,
|
||||
ipaData: Uint8Array,
|
||||
fileName: string,
|
||||
onLog?: (message: string) => void,
|
||||
) => Promise<void>
|
||||
>(webmuxdModuleValue, "installIpaViaInstProxy")
|
||||
const webmuxdSanitizeIpaFileName = resolveWebmuxdExport<(fileName: string) => string>(
|
||||
webmuxdModuleValue,
|
||||
"sanitizeIpaFileName",
|
||||
)
|
||||
const webmuxdCreateHostId = resolveWebmuxdExport<() => string>(
|
||||
webmuxdModuleValue,
|
||||
"createHostId",
|
||||
)
|
||||
const webmuxdCreateSystemBuid = resolveWebmuxdExport<() => string>(
|
||||
webmuxdModuleValue,
|
||||
"createSystemBuid",
|
||||
)
|
||||
const webmuxdEncodeStoredPairRecord = resolveWebmuxdExport<
|
||||
(record: PairRecord) => StoredPairRecordPayload
|
||||
>(webmuxdModuleValue, "encodeStoredPairRecord")
|
||||
const webmuxdDecodeStoredPairRecord = resolveWebmuxdExport<
|
||||
(payload: StoredPairRecordPayload) => PairRecord | null
|
||||
>(webmuxdModuleValue, "decodeStoredPairRecord")
|
||||
const webmuxdCreateOpenSslWasmTlsFactory = resolveWebmuxdExport<
|
||||
() => {
|
||||
ensureReady?(): Promise<void>
|
||||
createConnection(request: {
|
||||
serverName: string
|
||||
caCertificatePem: string
|
||||
certificatePem: string
|
||||
privateKeyPem: string
|
||||
}): {
|
||||
is_handshaking(): boolean
|
||||
write_plaintext(data: Uint8Array): void
|
||||
feed_tls(data: Uint8Array): void
|
||||
take_tls_out(): Uint8Array
|
||||
take_plain_out(): Uint8Array
|
||||
free(): void
|
||||
}
|
||||
const loadAnisetteService = async (): Promise<AnisetteServiceModule> => {
|
||||
if (!anisetteServicePromise) {
|
||||
anisetteServicePromise = import("./anisette-service")
|
||||
}
|
||||
>(webmuxdModuleValue, "createOpenSslWasmTlsFactory")
|
||||
const webmuxdGeneratePairRecordWithOpenSslWasm = resolveWebmuxdExport<
|
||||
(request: {
|
||||
devicePublicKey: Uint8Array
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
}) => Promise<string>
|
||||
>(webmuxdModuleValue, "generatePairRecordWithOpenSslWasm")
|
||||
|
||||
return await anisetteServicePromise
|
||||
}
|
||||
|
||||
const loadAppleSigningModule = async (): Promise<AppleSigningModule> => {
|
||||
if (!appleSigningModulePromise) {
|
||||
appleSigningModulePromise = import("./apple-signing")
|
||||
}
|
||||
|
||||
return await appleSigningModulePromise
|
||||
}
|
||||
|
||||
const app = document.querySelector<HTMLDivElement>("#app")
|
||||
if (!app) {
|
||||
@@ -586,11 +455,11 @@ const ensureClientSelected = async (): Promise<DirectUsbMuxClient> => {
|
||||
}
|
||||
|
||||
const transport = await WebUsbTransport.requestAppleDevice()
|
||||
directClient = new WebmuxdDirectUsbMuxClient(transport, {
|
||||
directClient = new DirectUsbMuxClient(transport, {
|
||||
log: addLog,
|
||||
onStateChange: refreshUi,
|
||||
lockdownLabel: "webmuxd.frontend",
|
||||
tlsFactory: webmuxdCreateOpenSslWasmTlsFactory(),
|
||||
tlsFactory: createOpenSslWasmTlsFactory(),
|
||||
pairRecordFactory: {
|
||||
createPairRecord: async (request) => {
|
||||
return await createPairRecord(request.devicePublicKey, request.hostId, request.systemBuid)
|
||||
@@ -681,19 +550,20 @@ const ensureAnisetteData = async (): Promise<AnisetteData> => {
|
||||
return anisetteData
|
||||
}
|
||||
|
||||
const anisette = await initAnisette()
|
||||
const anisetteService = await loadAnisetteService()
|
||||
const anisette = await anisetteService.initAnisette()
|
||||
const alreadyProvisioned = anisette.isProvisioned
|
||||
anisetteProvisioned = alreadyProvisioned
|
||||
if (alreadyProvisioned) {
|
||||
addLog("login: anisette already provisioned")
|
||||
} else {
|
||||
addLog("login: preparing anisette environment...")
|
||||
await provisionAnisette()
|
||||
await anisetteService.provisionAnisette()
|
||||
anisetteProvisioned = true
|
||||
addLog("login: anisette provisioned")
|
||||
}
|
||||
|
||||
anisetteData = await getAnisetteData()
|
||||
anisetteData = await anisetteService.getAnisetteData()
|
||||
addLog(`login: anisette ready (${shortToken(anisetteData.machineID)})`)
|
||||
refreshUi()
|
||||
return anisetteData
|
||||
@@ -701,7 +571,8 @@ const ensureAnisetteData = async (): Promise<AnisetteData> => {
|
||||
|
||||
const syncAnisetteProvisionedStatus = async (): Promise<void> => {
|
||||
try {
|
||||
const anisette = await initAnisette()
|
||||
const anisetteService = await loadAnisetteService()
|
||||
const anisette = await anisetteService.initAnisette()
|
||||
anisetteProvisioned = anisette.isProvisioned
|
||||
refreshUi()
|
||||
} catch (error) {
|
||||
@@ -727,7 +598,8 @@ const loginAndSignFlow = async (): Promise<void> => {
|
||||
|
||||
const anisette = await ensureAnisetteData()
|
||||
addLog("login: authenticating Apple account...")
|
||||
const context = await loginAppleDeveloperAccount({
|
||||
const appleSigning = await loadAppleSigningModule()
|
||||
const context = await appleSigning.loginAppleDeveloperAccount({
|
||||
anisetteData: anisette,
|
||||
credentials: { appleId, password },
|
||||
onLog: addLog,
|
||||
@@ -736,7 +608,7 @@ const loginAndSignFlow = async (): Promise<void> => {
|
||||
},
|
||||
})
|
||||
|
||||
loginContext = await refreshAppleDeveloperContext(context, addLog)
|
||||
loginContext = await appleSigning.refreshAppleDeveloperContext(context, addLog)
|
||||
accountContextMap.set(accountKey(loginContext.appleId, loginContext.team.identifier), loginContext)
|
||||
persistAccountSummary(loginContext)
|
||||
if (rememberSessionInput.checked) {
|
||||
@@ -781,7 +653,8 @@ const signSelectedIpa = async (): Promise<File> => {
|
||||
},
|
||||
}
|
||||
|
||||
const refreshed = await refreshAppleDeveloperContext(loginContext, addLog)
|
||||
const appleSigning = await loadAppleSigningModule()
|
||||
const refreshed = await appleSigning.refreshAppleDeveloperContext(loginContext, addLog)
|
||||
loginContext = refreshed
|
||||
accountContextMap.set(accountKey(refreshed.appleId, refreshed.team.identifier), refreshed)
|
||||
persistAccountSummary(refreshed)
|
||||
@@ -790,7 +663,7 @@ const signSelectedIpa = async (): Promise<File> => {
|
||||
}
|
||||
|
||||
addLog("sign: preparing ipa...")
|
||||
const result = await signIpaWithAppleContext({
|
||||
const result = await appleSigning.signIpaWithAppleContext({
|
||||
ipaFile: selectedIpaFile,
|
||||
context: refreshed,
|
||||
deviceUdid: targetUdid,
|
||||
@@ -868,8 +741,8 @@ const installFlow = async (): Promise<void> => {
|
||||
|
||||
addLog("install: uploading and installing...")
|
||||
const bytes = new Uint8Array(await upload.arrayBuffer())
|
||||
const safeName = webmuxdSanitizeIpaFileName(upload.name)
|
||||
await webmuxdInstallIpaViaInstProxy(client, bytes, safeName, addLog)
|
||||
const safeName = sanitizeIpaFileName(upload.name)
|
||||
await installIpaViaInstProxy(client, bytes, safeName, addLog)
|
||||
addLog("install: complete")
|
||||
setInstallProgress(100, "complete")
|
||||
} catch (error) {
|
||||
@@ -1405,7 +1278,7 @@ function getOrCreateHostId(): string {
|
||||
if (existing && existing.trim().length > 0) {
|
||||
return existing
|
||||
}
|
||||
const created = webmuxdCreateHostId()
|
||||
const created = createHostId()
|
||||
saveText(HOST_ID_STORAGE_KEY, created)
|
||||
return created
|
||||
}
|
||||
@@ -1415,7 +1288,7 @@ function getOrCreateSystemBuid(): string {
|
||||
if (existing && existing.trim().length > 0) {
|
||||
return existing
|
||||
}
|
||||
const created = webmuxdCreateSystemBuid()
|
||||
const created = createSystemBuid()
|
||||
saveText(SYSTEM_BUID_STORAGE_KEY, created)
|
||||
return created
|
||||
}
|
||||
@@ -1446,7 +1319,7 @@ function savePairRecordForUdid(udid: string, record: PairRecord): void {
|
||||
return
|
||||
}
|
||||
const map = readPairRecordMap()
|
||||
map[normalizedUdid] = webmuxdEncodeStoredPairRecord(record)
|
||||
map[normalizedUdid] = encodeStoredPairRecord(record)
|
||||
writePairRecordMap(map)
|
||||
}
|
||||
|
||||
@@ -1457,7 +1330,7 @@ function loadLegacyPairRecord(): PairRecord | null {
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(text) as StoredPairRecordPayload
|
||||
return webmuxdDecodeStoredPairRecord(parsed)
|
||||
return decodeStoredPairRecord(parsed)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
@@ -1473,7 +1346,7 @@ function loadPairRecordForUdid(udid: string): PairRecord | null {
|
||||
const fromMap = map[normalizedUdid]
|
||||
if (fromMap) {
|
||||
try {
|
||||
return webmuxdDecodeStoredPairRecord(fromMap)
|
||||
return decodeStoredPairRecord(fromMap)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
@@ -1492,7 +1365,7 @@ async function createPairRecord(
|
||||
hostId: string,
|
||||
systemBuid: string,
|
||||
): Promise<PairRecord> {
|
||||
const payloadText = await webmuxdGeneratePairRecordWithOpenSslWasm({
|
||||
const payloadText = await generatePairRecordWithOpenSslWasm({
|
||||
devicePublicKey: devicePublicKeyBytes,
|
||||
hostId,
|
||||
systemBuid,
|
||||
@@ -1763,20 +1636,3 @@ function formatError(error: unknown): string {
|
||||
}
|
||||
return String(error)
|
||||
}
|
||||
|
||||
function resolveWebmuxdExport<T>(moduleValue: Record<string, unknown>, key: string): T {
|
||||
const direct = moduleValue[key]
|
||||
if (direct !== undefined) {
|
||||
return direct as T
|
||||
}
|
||||
|
||||
const defaultValue = moduleValue.default
|
||||
if (defaultValue && typeof defaultValue === "object") {
|
||||
const fromDefault = (defaultValue as Record<string, unknown>)[key]
|
||||
if (fromDefault !== undefined) {
|
||||
return fromDefault as T
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`webmuxd export ${key} is unavailable`)
|
||||
}
|
||||
|
||||
2
frontend/src/node-forge.d.ts
vendored
2
frontend/src/node-forge.d.ts
vendored
@@ -1,2 +0,0 @@
|
||||
declare module "node-forge"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#007ACC" d="M0 128v128h256V0H0z"></path><path fill="#FFF" d="m56.612 128.85l-.081 10.483h33.32v94.68h23.568v-94.68h33.321v-10.28c0-5.69-.122-10.444-.284-10.566c-.122-.162-20.4-.244-44.983-.203l-44.74.122l-.121 10.443Zm149.955-10.742c6.501 1.625 11.459 4.51 16.01 9.224c2.357 2.52 5.851 7.111 6.136 8.208c.08.325-11.053 7.802-17.798 11.988c-.244.162-1.22-.894-2.317-2.52c-3.291-4.795-6.745-6.867-12.028-7.233c-7.76-.528-12.759 3.535-12.718 10.321c0 1.992.284 3.17 1.097 4.795c1.707 3.536 4.876 5.649 14.832 9.956c18.326 7.883 26.168 13.084 31.045 20.48c5.445 8.249 6.664 21.415 2.966 31.208c-4.063 10.646-14.14 17.879-28.323 20.276c-4.388.772-14.79.65-19.504-.203c-10.28-1.828-20.033-6.908-26.047-13.572c-2.357-2.6-6.949-9.387-6.664-9.874c.122-.163 1.178-.813 2.356-1.504c1.138-.65 5.446-3.129 9.509-5.485l7.355-4.267l1.544 2.276c2.154 3.29 6.867 7.801 9.712 9.305c8.167 4.307 19.383 3.698 24.909-1.26c2.357-2.153 3.332-4.388 3.332-7.68c0-2.966-.366-4.266-1.91-6.501c-1.99-2.845-6.054-5.242-17.595-10.24c-13.206-5.69-18.895-9.224-24.096-14.832c-3.007-3.25-5.852-8.452-7.03-12.8c-.975-3.617-1.22-12.678-.447-16.335c2.723-12.76 12.353-21.659 26.25-24.3c4.51-.853 14.994-.528 19.424.569Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
6
frontend/src/wasm/libcurl-entry.d.ts
vendored
Normal file
6
frontend/src/wasm/libcurl-entry.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export const libcurl: {
|
||||
fetch(input: string | URL, init?: Record<string, unknown>): Promise<Response>
|
||||
load_wasm(url?: string): Promise<void>
|
||||
set_websocket(url: string): void
|
||||
readonly ready?: boolean
|
||||
}
|
||||
1
frontend/src/wasm/libcurl-entry.js
Normal file
1
frontend/src/wasm/libcurl-entry.js
Normal file
@@ -0,0 +1 @@
|
||||
export { libcurl } from "libcurl.js/bundled"
|
||||
53
frontend/src/wasm/libcurl.ts
Normal file
53
frontend/src/wasm/libcurl.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
export interface LibcurlApi {
|
||||
fetch(input: string | URL, init?: Record<string, unknown>): Promise<Response>
|
||||
load_wasm(url?: string): Promise<void>
|
||||
set_websocket(url: string): void
|
||||
readonly ready?: boolean
|
||||
}
|
||||
|
||||
let libcurlModulePromise: Promise<LibcurlApi> | null = null
|
||||
export let libcurl: LibcurlApi
|
||||
|
||||
const toLibcurl = (moduleValue: unknown): LibcurlApi => {
|
||||
if (!moduleValue || typeof moduleValue !== "object") {
|
||||
throw new Error("libcurl module did not return an object")
|
||||
}
|
||||
|
||||
const candidate = moduleValue as Record<string, unknown>
|
||||
if (!candidate.libcurl || typeof candidate.libcurl !== "object") {
|
||||
throw new Error("libcurl module is missing the libcurl export")
|
||||
}
|
||||
|
||||
const loadedLibcurl = candidate.libcurl as Partial<LibcurlApi>
|
||||
if (typeof loadedLibcurl.fetch !== "function") {
|
||||
throw new Error("libcurl export is missing fetch")
|
||||
}
|
||||
if (typeof loadedLibcurl.load_wasm !== "function") {
|
||||
throw new Error("libcurl export is missing load_wasm")
|
||||
}
|
||||
if (typeof loadedLibcurl.set_websocket !== "function") {
|
||||
throw new Error("libcurl export is missing set_websocket")
|
||||
}
|
||||
|
||||
return loadedLibcurl as LibcurlApi
|
||||
}
|
||||
|
||||
export const loadLibcurl = async (): Promise<LibcurlApi> => {
|
||||
if (!libcurlModulePromise) {
|
||||
libcurlModulePromise = import("./libcurl-entry.js").then((moduleValue) => {
|
||||
const loadedLibcurl = toLibcurl(moduleValue)
|
||||
libcurl = loadedLibcurl
|
||||
return loadedLibcurl
|
||||
})
|
||||
}
|
||||
|
||||
return await libcurlModulePromise
|
||||
}
|
||||
|
||||
export const requireLibcurl = (): LibcurlApi => {
|
||||
if (!libcurl) {
|
||||
throw new Error("libcurl is not ready. Call initLibcurl() first.")
|
||||
}
|
||||
|
||||
return libcurl
|
||||
}
|
||||
75
frontend/src/wasm/openssl-webmuxd.js
Normal file
75
frontend/src/wasm/openssl-webmuxd.js
Normal file
@@ -0,0 +1,75 @@
|
||||
let opensslWasmModule = null
|
||||
let opensslWasmModulePromise = null
|
||||
let opensslWasmInitPromise = null
|
||||
|
||||
const loadOpenSslWasmModule = async () => {
|
||||
if (!opensslWasmModulePromise) {
|
||||
opensslWasmModulePromise = import("../../../wasm/openssl/dist/index.mjs").then(
|
||||
(moduleValue) => {
|
||||
if (!moduleValue || typeof moduleValue !== "object") {
|
||||
throw new Error("OpenSSL wasm module did not return an object")
|
||||
}
|
||||
const candidate = moduleValue
|
||||
if (typeof candidate.default !== "function") {
|
||||
throw new Error("OpenSSL wasm module is missing its default initializer")
|
||||
}
|
||||
if (typeof candidate.OpensslClient !== "function") {
|
||||
throw new Error("OpenSSL wasm module is missing OpensslClient")
|
||||
}
|
||||
if (typeof candidate.libimobiledevice_generate_pair_record !== "function") {
|
||||
throw new Error("OpenSSL wasm module is missing pair record generation")
|
||||
}
|
||||
|
||||
opensslWasmModule = candidate
|
||||
return candidate
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return await opensslWasmModulePromise
|
||||
}
|
||||
|
||||
const requireOpenSslWasmModule = () => {
|
||||
if (!opensslWasmModule) {
|
||||
throw new Error("OpenSSL wasm is not ready. Call ensureOpenSslWasmReady() first.")
|
||||
}
|
||||
|
||||
return opensslWasmModule
|
||||
}
|
||||
|
||||
export const ensureOpenSslWasmReady = async () => {
|
||||
if (!opensslWasmInitPromise) {
|
||||
opensslWasmInitPromise = loadOpenSslWasmModule().then(async (moduleValue) => {
|
||||
await moduleValue.default()
|
||||
})
|
||||
}
|
||||
|
||||
await opensslWasmInitPromise
|
||||
}
|
||||
|
||||
export const createOpenSslWasmConnection = (request) => {
|
||||
const moduleValue = requireOpenSslWasmModule()
|
||||
return new moduleValue.OpensslClient(
|
||||
request.serverName,
|
||||
request.caCertificatePem,
|
||||
request.certificatePem,
|
||||
request.privateKeyPem,
|
||||
)
|
||||
}
|
||||
|
||||
export const createOpenSslWasmTlsFactory = () => {
|
||||
return {
|
||||
ensureReady: ensureOpenSslWasmReady,
|
||||
createConnection: createOpenSslWasmConnection,
|
||||
}
|
||||
}
|
||||
|
||||
export const generatePairRecordWithOpenSslWasm = async (request) => {
|
||||
await ensureOpenSslWasmReady()
|
||||
const moduleValue = requireOpenSslWasmModule()
|
||||
return moduleValue.libimobiledevice_generate_pair_record(
|
||||
new Uint8Array(request.devicePublicKey),
|
||||
request.hostId,
|
||||
request.systemBuid,
|
||||
)
|
||||
}
|
||||
120
frontend/src/webmuxd-browser.d.ts
vendored
Normal file
120
frontend/src/webmuxd-browser.d.ts
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
declare module "webmuxd" {
|
||||
export interface WebUsbTransportInstance {
|
||||
readonly isOpen: boolean
|
||||
open(): Promise<void>
|
||||
close(): Promise<void>
|
||||
send(data: ArrayBuffer): Promise<void>
|
||||
setDataHandler(handler: ((data: ArrayBuffer) => void) | null): void
|
||||
setDisconnectHandler(handler: ((reason?: unknown) => void) | null): void
|
||||
}
|
||||
|
||||
export interface PairRecord {
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
hostCertificatePem: string
|
||||
hostPrivateKeyPem: string
|
||||
rootCertificatePem: string
|
||||
rootPrivateKeyPem: string
|
||||
deviceCertificatePem: string
|
||||
devicePublicKey: Uint8Array
|
||||
escrowBag?: Uint8Array
|
||||
}
|
||||
|
||||
export interface StoredPairRecordPayload {
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
hostCertificatePem: string
|
||||
hostPrivateKeyPem: string
|
||||
rootCertificatePem: string
|
||||
rootPrivateKeyPem: string
|
||||
deviceCertificatePem: string
|
||||
devicePublicKey: string
|
||||
escrowBag: string | null
|
||||
}
|
||||
|
||||
export interface TlsConnection {
|
||||
is_handshaking(): boolean
|
||||
write_plaintext(data: Uint8Array): void
|
||||
feed_tls(data: Uint8Array): void
|
||||
take_tls_out(): Uint8Array
|
||||
take_plain_out(): Uint8Array
|
||||
free(): void
|
||||
}
|
||||
|
||||
export interface TlsConnectionFactory {
|
||||
ensureReady?(): Promise<void>
|
||||
createConnection(request: {
|
||||
serverName: string
|
||||
caCertificatePem: string
|
||||
certificatePem: string
|
||||
privateKeyPem: string
|
||||
}): TlsConnection
|
||||
}
|
||||
|
||||
export class WebUsbTransport implements WebUsbTransportInstance {
|
||||
constructor(device: unknown, options?: { logger?: unknown; transferSize?: number })
|
||||
readonly isOpen: boolean
|
||||
static supported(): boolean
|
||||
static requestAppleDevice(logger?: unknown): Promise<WebUsbTransport>
|
||||
open(): Promise<void>
|
||||
close(): Promise<void>
|
||||
send(data: ArrayBuffer): Promise<void>
|
||||
setDataHandler(handler: ((data: ArrayBuffer) => void) | null): void
|
||||
setDisconnectHandler(handler: ((reason?: unknown) => void) | null): void
|
||||
}
|
||||
|
||||
export class DirectUsbMuxClient {
|
||||
constructor(
|
||||
transport: WebUsbTransportInstance,
|
||||
options?: {
|
||||
log?: (message: string) => void
|
||||
onStateChange?: () => void
|
||||
lockdownLabel?: string
|
||||
tlsFactory?: TlsConnectionFactory
|
||||
pairRecordFactory?: {
|
||||
createPairRecord(request: {
|
||||
devicePublicKey: Uint8Array
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
}): Promise<PairRecord>
|
||||
}
|
||||
},
|
||||
)
|
||||
readonly isHandshakeComplete: boolean
|
||||
readonly isLockdownConnected: boolean
|
||||
readonly isSessionStarted: boolean
|
||||
readonly isSessionSslEnabled: boolean
|
||||
readonly isTlsActive: boolean
|
||||
readonly isPaired: boolean
|
||||
loadPairRecord(record: PairRecord | null): void
|
||||
openAndHandshake(): Promise<void>
|
||||
connectLockdown(port?: number): Promise<void>
|
||||
getOrFetchDeviceUdid(): Promise<string>
|
||||
getOrFetchDeviceName(): Promise<string | null>
|
||||
pairDevice(hostId: string, systemBuid: string): Promise<PairRecord>
|
||||
startSession(hostId: string, systemBuid: string): Promise<{
|
||||
sessionId: string
|
||||
enableSessionSsl: boolean
|
||||
}>
|
||||
close(): Promise<void>
|
||||
}
|
||||
|
||||
export const LOCKDOWN_PORT: number
|
||||
export function installIpaViaInstProxy(
|
||||
client: DirectUsbMuxClient,
|
||||
ipaData: Uint8Array,
|
||||
fileName: string,
|
||||
onLog?: (message: string) => void,
|
||||
): Promise<void>
|
||||
export function sanitizeIpaFileName(fileName: string): string
|
||||
export function createHostId(): string
|
||||
export function createSystemBuid(): string
|
||||
export function encodeStoredPairRecord(record: PairRecord): StoredPairRecordPayload
|
||||
export function decodeStoredPairRecord(parsed: StoredPairRecordPayload): PairRecord | null
|
||||
export function createOpenSslWasmTlsFactory(): TlsConnectionFactory
|
||||
export function generatePairRecordWithOpenSslWasm(request: {
|
||||
devicePublicKey: Uint8Array
|
||||
hostId: string
|
||||
systemBuid: string
|
||||
}): Promise<string>
|
||||
}
|
||||
15
frontend/src/webmuxd-browser.js
Normal file
15
frontend/src/webmuxd-browser.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export { WebUsbTransport } from "../../dependencies/webmuxd/src/core/webusb-transport.ts"
|
||||
export {
|
||||
DirectUsbMuxClient,
|
||||
LOCKDOWN_PORT,
|
||||
installIpaViaInstProxy,
|
||||
sanitizeIpaFileName,
|
||||
createHostId,
|
||||
createSystemBuid,
|
||||
encodeStoredPairRecord,
|
||||
decodeStoredPairRecord,
|
||||
} from "../../dependencies/webmuxd/src/core/imobiledevice-client.ts"
|
||||
export {
|
||||
createOpenSslWasmTlsFactory,
|
||||
generatePairRecordWithOpenSslWasm,
|
||||
} from "./wasm/openssl-webmuxd.js"
|
||||
4
frontend/src/webmuxd-shim.d.ts
vendored
4
frontend/src/webmuxd-shim.d.ts
vendored
@@ -1,4 +0,0 @@
|
||||
declare module "webmuxd" {
|
||||
const webmuxd: Record<string, unknown>
|
||||
export default webmuxd
|
||||
}
|
||||
@@ -18,16 +18,22 @@ export default defineConfig({
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
webmuxd: resolve(repoRootDir, "dependencies/webmuxd/lib/webmuxd.js"),
|
||||
webmuxd: resolve(frontendDir, "src/webmuxd-browser.js"),
|
||||
},
|
||||
preserveSymlinks: true,
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ["@lbr77/anisette-js/browser"],
|
||||
exclude: [
|
||||
"altsign.js",
|
||||
"@lbr77/anisette-js",
|
||||
"@lbr77/anisette-js/browser",
|
||||
"@lbr77/zsign-wasm-resigner-wrapper",
|
||||
"libcurl.js",
|
||||
"libcurl.js/bundled",
|
||||
],
|
||||
},
|
||||
build: {
|
||||
commonjsOptions: {
|
||||
include: [/node_modules/, /\/lib\/webmuxd\.js/, /\/lib\/core\/.*\.js/],
|
||||
include: [/node_modules/],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user