Files
Felitendo 0c38693f05 feat: merge ftstore-manager into the monorepo
the manager lands in manager/, next to ios/ and web/. it is a fork of
this repo's own app, not of feather, so scripts/apply-rebrand.sh
excludes it the same way it excludes web/ — the generic feather-to-
ftstore rule would rewrite its app.ftstore.manager identity and mangle
the lineage notes in its docs.

zsign and idevicekitten were vendored copies in the old repo. they are
registered as submodules here instead, pinned to the same commits ios/
uses and verified byte-identical to what was vendored. that keeps 71 MB
of duplicated source out of the tree and leaves the xcode project's
relative paths intact, so project.pbxproj needed no edits.

the server moves to ftstore.app in both apps: managerrepoclient, the
seeded default source and managerconfig's fallback. downloadmanager
keeps the old admin host in its user-agent allowlist — that check is permissive,
not identifying, and dropping it would stop sending the header to
installs whose stored override still names the old host.

the rest of the admin rename lands too: the client id, the base-url
environment variable, the info.plist key, and user-facing errors. the
userdefaults override key is renamed with a
one-time migration read, so an existing install keeps the address its
owner configured instead of falling back to the default.

tags gain a third namespace: manager-v* for releases, manager-dev for
the rolling prerelease. both repos published to a tag called `dev`
before; sharing it here would make each build overwrite the other's
release assets.
2026-08-03 13:21:48 +02:00

74 lines
2.6 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// UIApplication+open.swift
// Feather
//
// Created by samara on 21.04.2025.
//
import UIKit.UIApplication
extension UIApplication {
/// Opens an app with an identifier
/// - Parameter identifier: Application identifier
static public func openApp(with identifier: String) {
let classNameBase64 = "TFNBcHBsaWNhdGlvbldvcmtzcGFjZQ==" // LSApplicationWorkspace
let defaultSelectorBase64 = "ZGVmYXVsdFdvcmtzcGFjZQ==" // defaultWorkspace
let openSelectorBase64 = "b3BlbkFwcGxpY2F0aW9uV2l0aEJ1bmRsZUlEOg==" // openApplicationWithBundleID:
guard
let classNameData = Data(base64Encoded: classNameBase64),
let defaultSelectorData = Data(base64Encoded: defaultSelectorBase64),
let openSelectorData = Data(base64Encoded: openSelectorBase64),
let className = String(data: classNameData, encoding: .utf8),
let defaultSelector = String(data: defaultSelectorData, encoding: .utf8),
let openSelector = String(data: openSelectorData, encoding: .utf8)
else {
return
}
guard
let workspaceClass = NSClassFromString(className) as? NSObject.Type,
let workspace = workspaceClass.perform(NSSelectorFromString(defaultSelector))?.takeUnretainedValue()
else {
return
}
_ = workspace.perform(NSSelectorFromString(openSelector), with: identifier)
}
/// Returns install progress for a bundle identifier (0.0 – 1.0)
/// - Parameters:
/// - identifier: Bundle identifier
/// - synchronous: Whether the call should block
/// - Returns: Progress value if available
static public func installProgress(
for identifier: String,
makeSynchronous synchronous: Bool = true
) -> Double? {
let classNameBase64 = "TFNBcHBsaWNhdGlvbldvcmtzcGFjZQ==" // LSApplicationWorkspace
let defaultSelectorBase64 = "ZGVmYXVsdFdvcmtzcGFjZQ==" // defaultWorkspace
let progressSelectorBase64 = "aW5zdGFsbFByb2dyZXNzRm9yQnVuZGxlSUQ6bWFrZVN5bmNocm9ub3VzOg==" // installProgressForBundleID:makeSynchronous:
guard
let className = String(data: Data(base64Encoded: classNameBase64)!, encoding: .utf8),
let defaultSelector = String(data: Data(base64Encoded: defaultSelectorBase64)!, encoding: .utf8),
let progressSelector = String(data: Data(base64Encoded: progressSelectorBase64)!, encoding: .utf8),
let workspaceClass = NSClassFromString(className) as? NSObject.Type,
let workspace = workspaceClass.perform(NSSelectorFromString(defaultSelector))?.takeUnretainedValue()
else { return nil }
let result = workspace.perform(
NSSelectorFromString(progressSelector),
with: identifier,
with: synchronous
)?.takeUnretainedValue()
if let number = result as? Progress {
return number.fractionCompleted
}
return nil
}
}