iPhone Installer Store Bridge 0.4.1
===================================

Purpose
-------
A local, persistent JSON-lines adapter around a pinned ipatool library.
It signs in to the user's own Apple Account, lists every mobile/legacy record
returned by Apple's purchase-history service, and downloads selected apps.
An explicit direct-download operation accepts a known numeric App Store ID even
when the history response omits it; Apple still verifies the current account's
license. Delisted/hidden apps are not guaranteed to appear in the history or to
remain available for download from Apple.
It does not purchase apps, transfer purchases, remove FairPlay, or re-sign IPA files.

Upstream and source
-------------------
Repository: https://github.com/majd/ipatool
Pinned upstream commit: d9c0247daea564d3ec7d04a9b015d0a29af66b04
Upstream commit date: 2026-10-25 02:20:01 +0200
Source archive: ipatool_bridge_source.zip
The source archive is a complete clean working source snapshot without .git or
module/build caches. It includes go.mod, go.sum, upstream sources and tests,
generated mock test helpers, the bridge, and the local extension/tests.

Changes relative to upstream:
1. New bridge/ executable with JSONL input/output and fixed public error codes.
2. Exported appstore.OwnedAppsAll helper. The normal upstream paging API remains
   unchanged; the helper performs the same full fetch/merge/sort once and skips
   local page slicing. Repeated network fetches for every 100 entries are avoided.
3. A regression test covers 251 purchases returned by one complete fetch.
4. Bridge 0.4 adds explicit download_direct without a local history-membership
   requirement. Both download paths call the same authenticated Download and
   identity-verification pipeline. No Purchase or Lookup call is added.
5. Bridge 0.4.1 connects a bounded DownloadInput.OnProgress observer to actual
   HTTP-file writes. It replaces temporary-file polling with measured byte
   counts and validated Content-Length/Content-Range totals, and exposes
   processing phases after the body transfer. The existing progress-bar API,
   authentication, license requests, identity checks and payload bytes remain
   unchanged.

Build provenance
----------------
Built with the official Go 1.27.3 Linux amd64 toolchain:
https://go.dev/dl/go1.27.3.linux-amd64.tar.gz
Verified archive SHA256:
65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40

Windows x64 build (from the extracted source root):
  GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -buildvcs=false -ldflags="-s -w" -o store_bridge.exe ./bridge

Linux build:
  go build -trimpath -buildvcs=false -ldflags="-s -w" -o store_bridge ./bridge

On PowerShell, set GOOS, GOARCH and CGO_ENABLED through the corresponding
$env: variables before the go build command. Go 1.26.5 or newer is required
by the pinned upstream module. No Go compiler is required on the user's computer.

manifest.json gives the hash and size of the delivered Windows executable.
It is cross-compiled, and the PE machine field was verified as AMD64 (0x8664).
The Windows executable has not been executed on real Windows in this environment.

Protocol 1
----------
One JSON object per UTF-8 line on stdin and stdout. Maximum request: 64 KiB.
Every request needs an id matching [A-Za-z0-9_.:-]{1,100}.
Requests are sequential. All events and results carry the same request id.
A result has type "result", ok true and data, or ok false and error {code,message}.
Progress events have type "progress" and stage. Download events carry integer
current bytes, including a validated resume offset; total is the full size from
validated HTTP Content-Length/Content-Range when known. The total field is absent
for unknown/chunked lengths. No guessed percent is sent. Counts advance only
after a successful file write. Updates are limited to four per second, plus the
initial and final byte counts. The final byte count alone is not a successful
download or installation result.
Non-transfer phases carry no byte count: waiting (Apple/HTTP response), verify
(package checks), package (artwork/metadata/license preparation), and save
(publishing the checked IPA). Python performs its own verify/SHA phase afterward.
Phase and diagnostic text is selected from fixed strings; no URL, token, header,
account metadata, raw response or raw error is included in progress.

Operations:
- hello: local/offline information; does not initialize a session or contact Apple.
  Returns bridge_version "0.4.1", protocol 1, and
  capabilities {"direct_download":true,"download_progress":true};
  purchase_supported remains false.
- login: email/password; on two_factor_required, repeat login with auth_code only.
- list: retrieves all returned history records once, merges the upstream
  storefront responses, and filters explicitly non-mobile platforms.
  iPhone, iPad, empty/unknown legacy platforms remain visible.
  data.apps records: id, name, bundle_id, version, purchase_date, platforms.
  count and total_count both equal the returned mobile/legacy record count.
  These counts describe this service response, not proof of every past purchase.
- download: app_id and an absolute, new output_path ending in .ipa.
  The existing parent directory must be an ordinary directory, not a symlink.
  Only an id from the last successful list for the current session is allowed.
  No Lookup, Purchase, or automatic new-license acquisition is performed.
- download_direct: positive numeric app_id, optional bundle_id, and an absolute,
  new output_path ending in .ipa. Requires a successful login, but a prior list
  request or membership in that returned list is not required. The app_id is
  sent unchanged to Apple's authenticated download service. A missing license
  is rejected as license_required. A supplied bundle_id must match the package.
  An omitted bundle_id is resolved only from the downloaded package metadata;
  itemId must still match exactly. No public catalog lookup, acquisition,
  entitlement transfer, or DRM removal takes place.
- logout: drops the in-memory account, cookies, keychain and ownership allowlist.
- quit: logout and exit.

login, list, download and download_direct results include session_id. A caller
may supply it on list/download/download_direct; a mismatch is rejected.
Both download operations return path, app_id, bundle_id, version and session_id.
The Python parent additionally tracks
process/session generation so cancelled responses cannot affect a later session.

Session storage
---------------
The bridge does not call the ordinary ipatool CLI or its disk keyring.
It injects a mutex-protected memory Keychain and standard in-memory CookieJar
with public-suffix rules. CookieJar.Save is a no-op.
The Apple password is retained only while completing login/2FA, then removed
from the bridge account and overwritten in the in-memory keychain account.
Tokens and cookies remain only in process memory until logout/process exit.
No password, token, code, raw server response or raw error is written to
stdout, stderr, a log, a command line, or an application credential file.

This describes deliberate application storage. It does not claim secure RAM
erasure against OS paging, operating-system crash dumps or a compromised OS.
An IPA itself contains Apple-issued license metadata, which can include the
account email; an IPA is not an anonymous file.

Pinned ipatool runtime code can cache downloaded Apple framework assets and
the Unicorn runtime under the normal OS cache directory. These are program
assets, not login sessions. Asset downloads use upstream-pinned hashes.
The first authenticated operation therefore needs internet and can take time.

Download safety and cancellation
--------------------------------
The download goes into a private .iphone-download-* directory adjacent to the
requested output. Apple-issued sinfs are added through the actual ipatool
ReplicateSinf function. Before publishing, the bridge checks the downloaded
iTunesMetadata itemId against the selected numeric ID and verifies consistency
with the main app's CFBundleIdentifier. A known/supplied bundle identifier must
match. A newly available version is allowed; a different app identity is not.
The final file is created with O_EXCL so an existing file is never overwritten.
Failures clean only the bridge's own staging directory and a file it created.

Cancellation is performed by terminating the bridge process from the Python
parent. That discards the login session. A forced termination may leave a
partial file/staging directory in that job's private download directory; the
Python parent owns cleanup of that job. A completed result is emitted only
after identity checks and final file sync/close.

Verification
------------
- Bridge tests: offline hello/JSONL, 2FA continuation, password removal, no secret
  output, complete >100 history, unknown legacy platform, rejection of partial
  history, ownership allowlist, expired/stale session, exact downloaded identity,
  latest-version acceptance, license errors, cleanup, and overwrite protection.
  Direct-download tests cover an ID omitted from history (including no prior
  list request), an optional bundle, missing license, incorrect itemId/bundle,
  missing/stale authentication, invalid input, and cleanup after failure.
- Real streaming loopback HTTP fixtures cover known and chunked/unknown sizes,
  exact on-disk intermediate byte counts, resume offsets, server restart,
  short bodies, context cancellation and bounded event frequency. A real
  DownloadInput-to-downloader test checks processing phase order; bridge tests
  check request IDs, callback wiring and suppression of untrusted fields.
- Upstream OwnedApps regression suite, including the new 251-item test.
- Real Linux executable offline JSONL smoke: hello, unauthenticated
  list/download/download_direct, logout, quit. No Apple account or real device
  was used.
- An actual authenticated Windows download and actual iPhone launch still need
  to be checked with the user's account and selected app.

Tests from source:
  go test ./bridge
  go test ./pkg/appstore -run "Test(DownloadProgress|DownloadInputObserver|DownloadFileResumes|DownloadFileValidates)"
  go generate ./pkg/...
  go test ./pkg/appstore -run "^TestAppStore$" -ginkgo.focus="AppStore \(OwnedApps\)"

Licenses
--------
ipatool is MIT-licensed: LICENSE-ipatool.txt.
Bridge additions are MIT-licensed: LICENSE-bridge.txt.
The application's Python/device backend distribution has its own licenses.
licenses/modules.json records the pinned Go modules and included license files.
licenses/Go-LICENSE is the Go standard-library license.
licenses/Unicorn-COPYING records the license of the upstream runtime that ipatool
downloads separately; this package does not redistribute a Unicorn DLL or
Apple framework binaries.

