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:
@@ -69,10 +69,13 @@ final class Playback: NSObject {
|
||||
lastSaved = 0
|
||||
|
||||
// An AVURLAsset does not read the shared cookie store by itself, and the cookie is how
|
||||
// ipx knows whose file this is.
|
||||
let asset = AVURLAsset(url: item.url, options: [
|
||||
// ipx knows whose file this is, unless the credentials are set, which do not expire.
|
||||
// The header option has no constant in the SDK; AVFoundation reads it by this name.
|
||||
var options: [String: Any] = [
|
||||
AVURLAssetHTTPCookiesKey: HTTPCookieStorage.shared.cookies(for: item.url) ?? [],
|
||||
])
|
||||
]
|
||||
if Credentials.isSet { options["AVURLAssetHTTPHeaderFieldsKey"] = Credentials.headers }
|
||||
let asset = AVURLAsset(url: item.url, options: options)
|
||||
let playerItem = AVPlayerItem(asset: asset)
|
||||
let player = AVPlayer(playerItem: playerItem)
|
||||
player.volume = volume
|
||||
@@ -92,7 +95,8 @@ final class Playback: NSObject {
|
||||
// second has something to do about it.
|
||||
let msg = pi.error?.localizedDescription ?? "the file could not be played"
|
||||
let status = await self.request("GET", "/api/me")
|
||||
self.onError?(status == 401 ? "Sign in again" : msg)
|
||||
let expired = Credentials.isSet ? "Check the tokens in Settings" : "Sign in again"
|
||||
self.onError?(status == 401 || status == 403 ? expired : msg)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
@@ -254,7 +258,9 @@ final class Playback: NSObject {
|
||||
|
||||
guard let art = item.artwork, let url = Server.resolve(art) else { return }
|
||||
Task {
|
||||
guard let (data, _) = try? await session.data(from: url),
|
||||
var artReq = URLRequest(url: url)
|
||||
Credentials.headers.forEach { artReq.setValue($1, forHTTPHeaderField: $0) }
|
||||
guard let (data, _) = try? await session.data(for: artReq),
|
||||
let image = UIImage(data: data), self.item?.guid == item.guid else { return }
|
||||
// Read back, not `info`: by the time the picture arrives that elapsed time is stale,
|
||||
// and writing it would jerk the progress bar back.
|
||||
@@ -285,13 +291,14 @@ final class Playback: NSObject {
|
||||
return "/api/entries/\(esc(item.feedId))/\(esc(item.guid))/\(what)"
|
||||
}
|
||||
|
||||
/// One call to ipx, signed in by the web view's cookies; its status, or nil if it never
|
||||
/// One call to ipx, signed in by the credentials or else the web view's cookies; its status, or nil if it never
|
||||
/// arrived. A position that does not save is saved again ten seconds on.
|
||||
@discardableResult
|
||||
private func request(_ method: String, _ path: String, _ json: [String: Any]? = nil) async -> Int? {
|
||||
guard let url = Server.resolve(path) else { return nil }
|
||||
var req = URLRequest(url: url)
|
||||
req.httpMethod = method
|
||||
Credentials.headers.forEach { req.setValue($1, forHTTPHeaderField: $0) }
|
||||
if let json {
|
||||
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
req.httpBody = try? JSONSerialization.data(withJSONObject: json)
|
||||
|
||||
Reference in New Issue
Block a user