iOS: player tokens in the keychain, and a build that uploads (#419)
The player rode only on the web view's Cloudflare Access cookie, so it stopped with "Sign in again" when that session ended, and CarPlay (#420), which has no web view, would have had nothing at all. Credentials.swift keeps an ipx API token and an Access service token in the keychain, and the player sends them (Authorization: Bearer, CF-Access-Client-Id and -Secret) on its audio, position, read-flag and artwork requests. Without all three it uses the cookie as before. Shake, Player Tokens enters them. project.yml gets what TestFlight needs: the team, a numeric version (App Store Connect refuses 0.1.0-dev), upside-down portrait (an iPad build fails validation without every orientation) and ITSAppUsesNonExemptEncryption. Resources/Info.plist is generated by XcodeGen and now ignored. Not run on a phone: the header option on AVURLAsset is read by name, as the SDK has no constant for it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,9 @@ final class WebViewController: UIViewController, WKNavigationDelegate, WKUIDeleg
|
||||
f.clearButtonMode = .whileEditing
|
||||
}
|
||||
if Server.url != nil { alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) }
|
||||
alert.addAction(UIAlertAction(title: "Player Tokens…", style: .default) { [weak self] _ in
|
||||
self?.askForCredentials()
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "Open", style: .default) { [weak self, weak alert] _ in
|
||||
guard let self else { return }
|
||||
let text = alert?.textFields?.first?.text ?? ""
|
||||
@@ -78,6 +81,40 @@ final class WebViewController: UIViewController, WKNavigationDelegate, WKUIDeleg
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
/// The tokens the player and CarPlay use in place of the page's cookie (#419). Left as they
|
||||
/// are when a field is empty; all three emptied clears them.
|
||||
private func askForCredentials() {
|
||||
guard presentedViewController == nil else { return }
|
||||
let alert = UIAlertController(
|
||||
title: "Player tokens",
|
||||
message: "An ipx API token (Settings, API tokens) and a Cloudflare Access service token, "
|
||||
+ "so playback and CarPlay keep working when the page's sign-in ends. "
|
||||
+ (Credentials.isSet ? "Set now; leave a field empty to keep it." : "Not set."),
|
||||
preferredStyle: .alert)
|
||||
let fields: [(String, Credentials.Field)] = [
|
||||
("ipx API token", .apiToken), ("Access client ID", .accessId),
|
||||
("Access client secret", .accessSecret),
|
||||
]
|
||||
for (placeholder, _) in fields {
|
||||
alert.addTextField { f in
|
||||
f.placeholder = placeholder
|
||||
f.isSecureTextEntry = true
|
||||
f.autocapitalizationType = .none
|
||||
f.autocorrectionType = .no
|
||||
}
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
alert.addAction(UIAlertAction(title: "Clear", style: .destructive) { _ in
|
||||
Credentials.Field.allCases.forEach { Credentials[$0] = nil }
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "Save", style: .default) { [weak alert] _ in
|
||||
for (i, (_, field)) in fields.enumerated() {
|
||||
if let v = alert?.textFields?[i].text, !v.isEmpty { Credentials[field] = v }
|
||||
}
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - the page
|
||||
|
||||
/// Sign-in leaves the server's address (Cloudflare Access, its identity provider) and must
|
||||
|
||||
Reference in New Issue
Block a user