mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
ac71743ca4
Share ticketed media URL resolution between iOS and macOS, retaining the connected gateway encoded context path and transport-owned TLS and credential policy. Remove the duplicate native URL and playback resolvers. Fixes #130746. Related to Android #129957.
109 lines
4.5 KiB
Swift
109 lines
4.5 KiB
Swift
import Foundation
|
|
import OpenClawChatUI
|
|
import OpenClawKit
|
|
import OpenClawProtocol
|
|
|
|
extension GatewayConnection {
|
|
func loadMediaArtifact(
|
|
sessionKey: String,
|
|
agentID: String?,
|
|
artifactId: String,
|
|
kind: OpenClawChatMediaKind,
|
|
playback: OpenClawChatPlaybackMode?,
|
|
ifCurrentServerLease lease: ServerLease) async throws -> OpenClawChatLoadedMedia?
|
|
{
|
|
guard kind.acceptsManagedArtifactID(artifactId) else { return nil }
|
|
let request = OpenClawChatGatewayRequests.artifactDownload(
|
|
sessionKey: sessionKey,
|
|
agentID: agentID,
|
|
artifactId: artifactId)
|
|
let responseData = try await self.request(
|
|
method: request.method,
|
|
params: request.params,
|
|
timeoutMs: request.timeoutMs,
|
|
ifCurrentServerLease: lease)
|
|
let response = try JSONDecoder().decode(ArtifactsDownloadResult.self, from: responseData)
|
|
let maximumBytes = Self.maximumManagedMediaBytes(for: kind)
|
|
let declaredMIME = response.artifact.mimetype?.lowercased()
|
|
if playback != .transcode,
|
|
let encoded = response.data?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
!encoded.isEmpty
|
|
{
|
|
guard response.encoding == "base64",
|
|
let declaredMIME,
|
|
declaredMIME.hasPrefix(kind.mimeTypePrefix),
|
|
let data = Data(base64Encoded: encoded),
|
|
data.count <= maximumBytes
|
|
else { return nil }
|
|
guard await self.isCurrentServerLease(lease) else {
|
|
throw OpenClawChatTransportSendError.notDispatched
|
|
}
|
|
return .data(OpenClawChatMediaData(data: data, mimeType: declaredMIME))
|
|
}
|
|
guard let ticketedPath = response.url?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
let url = OpenClawChatMediaURL.resolve(
|
|
gatewayURL: lease.route.url,
|
|
ticketedPath: ticketedPath,
|
|
playback: playback)
|
|
else { return nil }
|
|
|
|
let canStreamDirectly = kind == .video &&
|
|
url.scheme?.lowercased() == "https" &&
|
|
lease.route.tls == nil &&
|
|
declaredMIME?.hasPrefix(kind.mimeTypePrefix) == true
|
|
if canStreamDirectly, playback != .transcode, let declaredMIME {
|
|
guard await self.isCurrentServerLease(lease) else {
|
|
throw OpenClawChatTransportSendError.notDispatched
|
|
}
|
|
return .stream(OpenClawChatMediaStream(
|
|
url: url,
|
|
mimeType: declaredMIME,
|
|
sizeBytes: response.artifact.sizebytes))
|
|
}
|
|
|
|
var urlRequest = URLRequest(url: url)
|
|
urlRequest.timeoutInterval = kind == .video ? 60 : 20
|
|
urlRequest.setValue("\(kind.rawValue)/*", forHTTPHeaderField: "Accept")
|
|
if canStreamDirectly {
|
|
urlRequest.setValue("bytes=0-0", forHTTPHeaderField: "Range")
|
|
}
|
|
// Native macOS has no per-Gateway proxy-header configuration surface today. If one is
|
|
// added, carry its immutable snapshot on Route so the socket and ticket GET cannot diverge.
|
|
let tls = lease.route.tls?.params ?? GatewayTLSParams(
|
|
required: false,
|
|
expectedFingerprint: nil,
|
|
allowTOFU: false,
|
|
storeKey: nil)
|
|
let session = GatewayTLSPinningSession(params: tls)
|
|
defer { session.finishTasksAndInvalidate() }
|
|
let (data, urlResponse) = try await session.data(
|
|
for: urlRequest,
|
|
maximumBytes: maximumBytes)
|
|
guard await self.isCurrentServerLease(lease) else {
|
|
throw OpenClawChatTransportSendError.notDispatched
|
|
}
|
|
guard let http = urlResponse as? HTTPURLResponse else { return nil }
|
|
if http.statusCode == 202 {
|
|
return .preparing
|
|
}
|
|
guard (200..<300).contains(http.statusCode),
|
|
let mimeType = http.mimeType?.lowercased(),
|
|
mimeType.hasPrefix(kind.mimeTypePrefix)
|
|
else { return nil }
|
|
if canStreamDirectly {
|
|
return .stream(OpenClawChatMediaStream(
|
|
url: url,
|
|
mimeType: mimeType,
|
|
sizeBytes: response.artifact.sizebytes))
|
|
}
|
|
return .data(OpenClawChatMediaData(data: data, mimeType: mimeType))
|
|
}
|
|
|
|
private static func maximumManagedMediaBytes(for kind: OpenClawChatMediaKind) -> Int {
|
|
switch kind {
|
|
case .image: 12 * 1024 * 1024
|
|
case .audio, .video: 16 * 1024 * 1024
|
|
}
|
|
}
|
|
}
|