feat: add Idempotency-Key` header for status

This commit is contained in:
CMK 2021-08-09 17:54:11 +08:00
parent 3570c7108c
commit d3c77ee6cf
7 changed files with 69 additions and 3 deletions

View File

@ -70,7 +70,7 @@ extension ComposeStatusPollItem {
hasher.combine(id) hasher.combine(id)
} }
enum ExpiresOption: Equatable, Hashable, CaseIterable { enum ExpiresOption: String, Equatable, Hashable, CaseIterable {
case thirtyMinutes case thirtyMinutes
case oneHour case oneHour
case sixHours case sixHours

View File

@ -107,6 +107,8 @@ extension ComposeViewModel.PublishState {
return subscriptions return subscriptions
}() }()
let idempotencyKey = viewModel.idempotencyKey.value
publishingSubscription = Publishers.MergeMany(updateMediaQuerySubscriptions) publishingSubscription = Publishers.MergeMany(updateMediaQuerySubscriptions)
.collect() .collect()
.flatMap { attachments -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> in .flatMap { attachments -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> in
@ -122,6 +124,7 @@ extension ComposeViewModel.PublishState {
) )
return viewModel.context.apiService.publishStatus( return viewModel.context.apiService.publishStatus(
domain: domain, domain: domain,
idempotencyKey: idempotencyKey,
query: query, query: query,
mastodonAuthenticationBox: mastodonAuthenticationBox mastodonAuthenticationBox: mastodonAuthenticationBox
) )

View File

@ -58,7 +58,10 @@ final class ComposeViewModel: NSObject {
}() }()
private(set) lazy var publishStateMachinePublisher = CurrentValueSubject<PublishState?, Never>(nil) private(set) lazy var publishStateMachinePublisher = CurrentValueSubject<PublishState?, Never>(nil)
private(set) var publishDate = Date() // update it when enter Publishing state private(set) var publishDate = Date() // update it when enter Publishing state
// TODO: group post material into Hashable class
var idempotencyKey = CurrentValueSubject<String, Never>(UUID().uuidString)
// UI & UX // UI & UX
let title: CurrentValueSubject<String, Never> let title: CurrentValueSubject<String, Never>
let shouldDismiss = CurrentValueSubject<Bool, Never>(true) let shouldDismiss = CurrentValueSubject<Bool, Never>(true)
@ -383,6 +386,56 @@ final class ComposeViewModel: NSObject {
self.isPollToolbarButtonEnabled.value = !shouldPollDisable self.isPollToolbarButtonEnabled.value = !shouldPollDisable
}) })
.store(in: &disposeBag) .store(in: &disposeBag)
// calculate `Idempotency-Key`
let content = Publishers.CombineLatest3(
composeStatusAttribute.isContentWarningComposing,
composeStatusAttribute.contentWarningContent,
composeStatusAttribute.composeContent
)
.map { isContentWarningComposing, contentWarningContent, composeContent -> String in
if isContentWarningComposing {
return contentWarningContent + (composeContent ?? "")
} else {
return composeContent ?? ""
}
}
let attachmentIDs = attachmentServices.map { attachments -> String in
let attachmentIDs = attachments.compactMap { $0.attachment.value?.id }
return attachmentIDs.joined(separator: ",")
}
let pollOptionsAndDuration = Publishers.CombineLatest3(
isPollComposing,
pollOptionAttributes,
pollExpiresOptionAttribute.expiresOption
)
.map { isPollComposing, pollOptionAttributes, expiresOption -> String in
guard isPollComposing else {
return ""
}
let pollOptions = pollOptionAttributes.map { $0.option.value }.joined(separator: ",")
return pollOptions + expiresOption.rawValue
}
Publishers.CombineLatest4(
content,
attachmentIDs,
pollOptionsAndDuration,
selectedStatusVisibility
)
.map { content, attachmentIDs, pollOptionsAndDuration, selectedStatusVisibility -> String in
var hasher = Hasher()
hasher.combine(content)
hasher.combine(attachmentIDs)
hasher.combine(pollOptionsAndDuration)
hasher.combine(selectedStatusVisibility.visibility.rawValue)
let hashValue = hasher.finalize()
return "\(hashValue)"
}
.assign(to: \.value, on: idempotencyKey)
.store(in: &disposeBag)
} }
deinit { deinit {

View File

@ -16,6 +16,7 @@ extension APIService {
func publishStatus( func publishStatus(
domain: String, domain: String,
idempotencyKey: String?,
query: Mastodon.API.Statuses.PublishStatusQuery, query: Mastodon.API.Statuses.PublishStatusQuery,
mastodonAuthenticationBox: MastodonAuthenticationBox mastodonAuthenticationBox: MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> { ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
@ -24,6 +25,7 @@ extension APIService {
return Mastodon.API.Statuses.publishStatus( return Mastodon.API.Statuses.publishStatus(
session: session, session: session,
domain: domain, domain: domain,
idempotencyKey: idempotencyKey,
query: query, query: query,
authorization: authorization authorization: authorization
) )

View File

@ -55,9 +55,12 @@ final class SendPostIntentHandler: NSObject, SendPostIntentHandling {
spoilerText: nil, spoilerText: nil,
visibility: visibility visibility: visibility
) )
let idempotencyKey = UUID().uuidString
APIService.shared.publishStatus( APIService.shared.publishStatus(
domain: box.domain, domain: box.domain,
idempotencyKey: idempotencyKey,
query: query, query: query,
mastodonAuthenticationBox: box mastodonAuthenticationBox: box
) )

View File

@ -77,14 +77,18 @@ extension Mastodon.API.Statuses {
public static func publishStatus( public static func publishStatus(
session: URLSession, session: URLSession,
domain: String, domain: String,
idempotencyKey: String?,
query: PublishStatusQuery, query: PublishStatusQuery,
authorization: Mastodon.API.OAuth.Authorization? authorization: Mastodon.API.OAuth.Authorization?
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> { ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
let request = Mastodon.API.post( var request = Mastodon.API.post(
url: publishNewStatusEndpointURL(domain: domain), url: publishNewStatusEndpointURL(domain: domain),
query: query, query: query,
authorization: authorization authorization: authorization
) )
if let idempotencyKey = idempotencyKey {
request.setValue(idempotencyKey, forHTTPHeaderField: "Idempotency-Key")
}
return session.dataTaskPublisher(for: request) return session.dataTaskPublisher(for: request)
.tryMap { data, response in .tryMap { data, response in
let value = try Mastodon.API.decode(type: Mastodon.Entity.Status.self, from: data, response: response) let value = try Mastodon.API.decode(type: Mastodon.Entity.Status.self, from: data, response: response)

View File

@ -346,6 +346,7 @@ extension ShareViewModel {
) )
return APIService.shared.publishStatus( return APIService.shared.publishStatus(
domain: domain, domain: domain,
idempotencyKey: nil, // FIXME:
query: query, query: query,
mastodonAuthenticationBox: mastodonAuthenticationBox mastodonAuthenticationBox: mastodonAuthenticationBox
) )