logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe
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:

MCHANGELOG.md1+
Msrc/components/post_status_form/post_status_form.js73+++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/services/api/api.service.js10++++++++--
Msrc/services/status_poster/status_poster.service.js6++++--
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) {