commit: 0ea23a03ce9e9337d5db64250471c874eab58de9
parent 25a015b4715b7e85af310bae0ed360f98aef6205
Author: lain <lain@soykaf.club>
Date: Thu, 23 Jul 2020 13:17:44 +0000
Merge branch 'feat/idempotency' into 'develop'
Status posting Idempotency
See merge request pleroma/pleroma-fe!1194
Diffstat:
4 files changed, 56 insertions(+), 34 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -51,6 +51,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Videos are not cropped awkwardly in the uploads section anymore
- Reply filtering options in Settings -> Filtering now work again using filtering on server
- Don't show just blank-screen when cookies are disabled
+- Add status idempotency to prevent accidental double posting when posting returns an error
## [2.0.3] - 2020-05-02
### Fixed
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
@@ -66,6 +66,7 @@ const PostStatusForm = {
StatusContent
},
mounted () {
+ this.updateIdempotencyKey()
this.resize(this.$refs.textarea)
if (this.replyTo) {
@@ -116,7 +117,8 @@ const PostStatusForm = {
dropStopTimeout: null,
preview: null,
previewLoading: false,
- emojiInputShown: false
+ emojiInputShown: false,
+ idempotencyKey: ''
}
},
computed: {
@@ -211,14 +213,43 @@ const PostStatusForm = {
})
},
watch: {
- 'newStatus.contentType': function () {
- this.autoPreview()
- },
- 'newStatus.spoilerText': function () {
- this.autoPreview()
+ 'newStatus': {
+ deep: true,
+ handler () {
+ this.statusChanged()
+ }
}
},
methods: {
+ statusChanged () {
+ this.autoPreview()
+ this.updateIdempotencyKey()
+ },
+ clearStatus () {
+ const newStatus = this.newStatus
+ this.newStatus = {
+ status: '',
+ spoilerText: '',
+ files: [],
+ visibility: newStatus.visibility,
+ contentType: newStatus.contentType,
+ poll: {},
+ mediaDescriptions: {}
+ }
+ this.pollFormVisible = false
+ this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
+ this.clearPollForm()
+ if (this.preserveFocus) {
+ this.$nextTick(() => {
+ this.$refs.textarea.focus()
+ })
+ }
+ let el = this.$el.querySelector('textarea')
+ el.style.height = 'auto'
+ el.style.height = undefined
+ this.error = null
+ if (this.preview) this.previewStatus()
+ },
async postStatus (event, newStatus, opts = {}) {
if (this.posting) { return }
if (this.disableSubmit) { return }
@@ -258,36 +289,16 @@ const PostStatusForm = {
store: this.$store,
inReplyToStatusId: this.replyTo,
contentType: newStatus.contentType,
- poll
+ poll,
+ idempotencyKey: this.idempotencyKey
}
const postHandler = this.postHandler ? this.postHandler : statusPoster.postStatus
postHandler(postingOptions).then((data) => {
if (!data.error) {
- this.newStatus = {
- status: '',
- spoilerText: '',
- files: [],
- visibility: newStatus.visibility,
- contentType: newStatus.contentType,
- poll: {},
- mediaDescriptions: {}
- }
- this.pollFormVisible = false
- this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
- this.clearPollForm()
+ this.clearStatus()
this.$emit('posted', data)
- if (this.preserveFocus) {
- this.$nextTick(() => {
- this.$refs.textarea.focus()
- })
- }
- let el = this.$el.querySelector('textarea')
- el.style.height = 'auto'
- el.style.height = undefined
- this.error = null
- if (this.preview) this.previewStatus()
} else {
this.error = data.error
}
@@ -404,7 +415,6 @@ const PostStatusForm = {
}
},
onEmojiInputInput (e) {
- this.autoPreview()
this.$nextTick(() => {
this.resize(this.$refs['textarea'])
})
@@ -542,6 +552,9 @@ const PostStatusForm = {
},
handleEmojiInputShow (value) {
this.emojiInputShown = value
+ },
+ updateIdempotencyKey () {
+ this.idempotencyKey = Date.now().toString()
}
}
}
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
@@ -631,7 +631,8 @@ const postStatus = ({
mediaIds = [],
inReplyToStatusId,
contentType,
- preview
+ preview,
+ idempotencyKey
}) => {
const form = new FormData()
const pollOptions = poll.options || []
@@ -665,10 +666,15 @@ const postStatus = ({
form.append('preview', 'true')
}
+ let postHeaders = authHeaders(credentials)
+ if (idempotencyKey) {
+ postHeaders['idempotency-key'] = idempotencyKey
+ }
+
return fetch(MASTODON_POST_STATUS_URL, {
body: form,
method: 'POST',
- headers: authHeaders(credentials)
+ headers: postHeaders
})
.then((response) => {
return response.json()
diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js
@@ -11,7 +11,8 @@ const postStatus = ({
media = [],
inReplyToStatusId = undefined,
contentType = 'text/plain',
- preview = false
+ preview = false,
+ idempotencyKey = ''
}) => {
const mediaIds = map(media, 'id')
@@ -25,7 +26,8 @@ const postStatus = ({
inReplyToStatusId,
contentType,
poll,
- preview
+ preview,
+ idempotencyKey
})
.then((data) => {
if (!data.error && !preview) {