diff --git a/app/models/Document.js b/app/models/Document.js index e64fd7aa..762c1d08 100644 --- a/app/models/Document.js +++ b/app/models/Document.js @@ -1,6 +1,5 @@ // @flow import { action, set, observable, computed } from 'mobx'; -import pkg from 'rich-markdown-editor/package.json'; import addDays from 'date-fns/add_days'; import invariant from 'invariant'; import { client } from 'utils/ApiClient'; @@ -180,7 +179,6 @@ export default class Document extends BaseModel { try { if (isCreating) { return await this.store.create({ - editorVersion: pkg.version, parentDocumentId: this.parentDocumentId, collectionId: this.collectionId, title: this.title, @@ -194,7 +192,6 @@ export default class Document extends BaseModel { title: this.title, text: this.text, lastRevision: this.revision, - editorVersion: pkg.version, ...options, }); } finally { diff --git a/app/utils/ApiClient.js b/app/utils/ApiClient.js index 11b39b2f..dfcaa147 100644 --- a/app/utils/ApiClient.js +++ b/app/utils/ApiClient.js @@ -1,4 +1,5 @@ // @flow +import pkg from 'rich-markdown-editor/package.json'; import { map, trim } from 'lodash'; import invariant from 'invariant'; import stores from 'stores'; @@ -41,6 +42,7 @@ class ApiClient { Accept: 'application/json', 'Content-Type': 'application/json', 'cache-control': 'no-cache', + 'x-editor-version': pkg.version, pragma: 'no-cache', }); if (stores.auth.authenticated) { @@ -100,6 +102,11 @@ class ApiClient { // we're trying to parse an error so JSON may not be valid } + if (response.status === 400 && error.error === 'editor_update_required') { + window.location.reload(true); + return; + } + throw error; }; diff --git a/server/api/documents.js b/server/api/documents.js index fb071556..872ae511 100644 --- a/server/api/documents.js +++ b/server/api/documents.js @@ -692,12 +692,13 @@ router.post('documents.create', auth(), async ctx => { const { title = '', text = '', - editorVersion, publish, collectionId, parentDocumentId, index, } = ctx.body; + const editorVersion = ctx.headers['x-editor-version']; + ctx.assertUuid(collectionId, 'collectionId must be an uuid'); if (parentDocumentId) { ctx.assertUuid(parentDocumentId, 'parentDocumentId must be an uuid'); @@ -787,10 +788,11 @@ router.post('documents.update', auth(), async ctx => { publish, autosave, done, - editorVersion, lastRevision, append, } = ctx.body; + const editorVersion = ctx.headers['x-editor-version']; + ctx.assertPresent(id, 'id is required'); ctx.assertPresent(title || text, 'title or text is required'); if (append) ctx.assertPresent(text, 'Text is required while appending'); diff --git a/server/api/index.js b/server/api/index.js index 82e34d4b..8380694a 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -25,6 +25,7 @@ import validation from '../middlewares/validation'; import methodOverride from '../middlewares/methodOverride'; import cache from './middlewares/cache'; import apiWrapper from './middlewares/apiWrapper'; +import editor from './middlewares/editor'; const api = new Koa(); const router = new Router(); @@ -36,6 +37,7 @@ api.use(methodOverride()); api.use(cache()); api.use(validation()); api.use(apiWrapper()); +api.use(editor()); // routes router.use('/', auth.routes()); diff --git a/server/api/middlewares/editor.js b/server/api/middlewares/editor.js new file mode 100644 index 00000000..da6ac4e5 --- /dev/null +++ b/server/api/middlewares/editor.js @@ -0,0 +1,17 @@ +// @flow +import { type Context } from 'koa'; +import pkg from 'rich-markdown-editor/package.json'; +import { EditorUpdateError } from '../../errors'; + +export default function editor() { + return async function editorMiddleware(ctx: Context, next: () => Promise<*>) { + const editorVersion = ctx.headers['x-editor-version']; + + // As the client can only ever be behind the server there's no need for a + // more strict check of version infront/behind here + if (editorVersion && editorVersion !== pkg.version) { + throw new EditorUpdateError(); + } + return next(); + }; +} diff --git a/server/errors.js b/server/errors.js index b35589ab..26a1111c 100644 --- a/server/errors.js +++ b/server/errors.js @@ -45,3 +45,9 @@ export function ParamRequiredError( export function ValidationError(message: string = 'Validation failed') { return httpErrors(400, message, { id: 'validation_error' }); } + +export function EditorUpdateError( + message: string = 'The client editor is out of date and must be reloaded' +) { + return httpErrors(400, message, { id: 'editor_update_required' }); +}