From 0f5f3174c90d3e93c5546a8419eda28c4011490c Mon Sep 17 00:00:00 2001 From: Morten Moeller Date: Sun, 17 Mar 2024 10:59:50 -0500 Subject: [PATCH 1/8] check if header is available before calling `xhr.getResponseHeader` to avoid Chrome console warning --- src/RESTController.js | 7 +++++-- src/__tests__/ParseObject-test.js | 20 ++++++++++++++++++++ src/__tests__/RESTController-test.js | 6 ++++++ src/__tests__/test_helpers/mockXHR.js | 3 +++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/RESTController.js b/src/RESTController.js index 75b30a759..96dc07c43 100644 --- a/src/RESTController.js +++ b/src/RESTController.js @@ -110,11 +110,14 @@ const RESTController = { let response; try { response = JSON.parse(xhr.responseText); + const availableHeaders = xhr.getAllResponseHeaders() || ""; headers = {}; - if (typeof xhr.getResponseHeader === 'function' && xhr.getResponseHeader('access-control-expose-headers')) { + if (typeof xhr.getResponseHeader === 'function' && availableHeaders.indexOf('access-control-expose-headers') >= 0) { const responseHeaders = xhr.getResponseHeader('access-control-expose-headers').split(', '); responseHeaders.forEach(header => { - headers[header] = xhr.getResponseHeader(header.toLowerCase()); + if (availableHeaders.indexOf(header.toLowerCase()) >= 0) { + headers[header] = xhr.getResponseHeader(header.toLowerCase()); + } }); } } catch (e) { diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index c67c4a3e1..69cb60114 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -1684,6 +1684,7 @@ describe('ParseObject', () => { it('can make changes while in the process of a save', async () => { const xhr = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -1717,6 +1718,7 @@ describe('ParseObject', () => { setRequestHeader: jest.fn(), open: jest.fn(), send: jest.fn(), + getAllResponseHeaders: jest.fn(), }; xhrs.push(xhr); return xhr; @@ -1827,6 +1829,7 @@ describe('ParseObject', () => { RESTController._setXHR(function () { const xhr = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -1957,6 +1960,7 @@ describe('ParseObject', () => { const xhrs = []; RESTController._setXHR(function () { const xhr = { + getAllResponseHeaders: jest.fn(), setRequestHeader: jest.fn(), open: jest.fn(), send: jest.fn(), @@ -2156,6 +2160,7 @@ describe('ParseObject', () => { send: jest.fn(), status: 200, readyState: 4, + getAllResponseHeaders: jest.fn(), }; xhrs.push(xhr); return xhr; @@ -2294,6 +2299,7 @@ describe('ParseObject', () => { it('can destroy an object', async () => { const xhr = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2358,6 +2364,7 @@ describe('ParseObject', () => { it('can save an array of objects', (done) => { const xhr = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2398,6 +2405,7 @@ describe('ParseObject', () => { for (let i = 0; i < 2; i++) { xhrs[i] = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -2459,6 +2467,7 @@ describe('ParseObject', () => { for (let i = 0; i < 2; i++) { xhrs[i] = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -2520,6 +2529,7 @@ describe('ParseObject', () => { for (let i = 0; i < 2; i++) { xhrs[i] = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -2580,6 +2590,7 @@ describe('ObjectController', () => { const objectController = CoreManager.getObjectController(); const xhr = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2651,6 +2662,7 @@ describe('ObjectController', () => { const objectController = CoreManager.getObjectController(); const xhr = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2697,6 +2709,7 @@ describe('ObjectController', () => { const objectController = CoreManager.getObjectController(); const xhr = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2751,6 +2764,7 @@ describe('ObjectController', () => { for (let i = 0; i < 3; i++) { xhrs[i] = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2833,6 +2847,7 @@ describe('ObjectController', () => { for (let i = 0; i < 3; i++) { xhrs[i] = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2940,6 +2955,7 @@ describe('ObjectController', () => { const objectController = CoreManager.getObjectController(); const xhr = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2982,6 +2998,7 @@ describe('ObjectController', () => { for (let i = 0; i < 4; i++) { xhrs[i] = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -3023,6 +3040,7 @@ describe('ObjectController', () => { for (let i = 0; i < 3; i++) { xhrs[i] = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -3108,6 +3126,7 @@ describe('ObjectController', () => { for (let i = 0; i < 2; i++) { xhrs[i] = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -3277,6 +3296,7 @@ describe('ParseObject (unique instance mode)', () => { it('can save an array of objects', async () => { const xhr = { setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 02d72e94d..4d4b0e37f 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -221,6 +221,9 @@ describe('RESTController', () => { getResponseHeader: function (header) { return headers[header]; }, + getAllResponseHeaders: function() { + return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\n'); + }, send: function () { this.status = 200; this.responseText = '{}'; @@ -241,6 +244,9 @@ describe('RESTController', () => { getResponseHeader: function (header) { return headers[header]; }, + getAllResponseHeaders: function() { + return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\n'); + }, send: function () { this.status = 200; this.responseText = '{}'; diff --git a/src/__tests__/test_helpers/mockXHR.js b/src/__tests__/test_helpers/mockXHR.js index 3b5fd06c0..b64767754 100644 --- a/src/__tests__/test_helpers/mockXHR.js +++ b/src/__tests__/test_helpers/mockXHR.js @@ -19,6 +19,9 @@ function mockXHR(results, options = {}) { getRequestHeader: function (key) { return headers[key]; }, + getAllResponseHeaders: function() { + return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\n'); + }, upload: function () {}, send: function () { this.status = results[attempts].status; From 7360318ba47cf9674372b60a0f667efcb8afc58d Mon Sep 17 00:00:00 2001 From: Morten Moeller Date: Sat, 13 Apr 2024 09:27:10 -0500 Subject: [PATCH 2/8] avoid requiring `getAllResponseHeaders` on xhr. Clean tests from requiring it. --- src/RESTController.js | 4 ++-- src/__tests__/ParseObject-test.js | 20 -------------------- src/__tests__/test_helpers/mockXHR.js | 3 --- 3 files changed, 2 insertions(+), 25 deletions(-) diff --git a/src/RESTController.js b/src/RESTController.js index 96dc07c43..69880136b 100644 --- a/src/RESTController.js +++ b/src/RESTController.js @@ -110,9 +110,9 @@ const RESTController = { let response; try { response = JSON.parse(xhr.responseText); - const availableHeaders = xhr.getAllResponseHeaders() || ""; + const availableHeaders = typeof xhr.getAllResponseHeaders === 'function' ? xhr.getAllResponseHeaders() : ""; headers = {}; - if (typeof xhr.getResponseHeader === 'function' && availableHeaders.indexOf('access-control-expose-headers') >= 0) { + if (typeof xhr.getResponseHeader === 'function' && availableHeaders && availableHeaders.indexOf('access-control-expose-headers') >= 0) { const responseHeaders = xhr.getResponseHeader('access-control-expose-headers').split(', '); responseHeaders.forEach(header => { if (availableHeaders.indexOf(header.toLowerCase()) >= 0) { diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index 69cb60114..c67c4a3e1 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -1684,7 +1684,6 @@ describe('ParseObject', () => { it('can make changes while in the process of a save', async () => { const xhr = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -1718,7 +1717,6 @@ describe('ParseObject', () => { setRequestHeader: jest.fn(), open: jest.fn(), send: jest.fn(), - getAllResponseHeaders: jest.fn(), }; xhrs.push(xhr); return xhr; @@ -1829,7 +1827,6 @@ describe('ParseObject', () => { RESTController._setXHR(function () { const xhr = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -1960,7 +1957,6 @@ describe('ParseObject', () => { const xhrs = []; RESTController._setXHR(function () { const xhr = { - getAllResponseHeaders: jest.fn(), setRequestHeader: jest.fn(), open: jest.fn(), send: jest.fn(), @@ -2160,7 +2156,6 @@ describe('ParseObject', () => { send: jest.fn(), status: 200, readyState: 4, - getAllResponseHeaders: jest.fn(), }; xhrs.push(xhr); return xhr; @@ -2299,7 +2294,6 @@ describe('ParseObject', () => { it('can destroy an object', async () => { const xhr = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2364,7 +2358,6 @@ describe('ParseObject', () => { it('can save an array of objects', (done) => { const xhr = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2405,7 +2398,6 @@ describe('ParseObject', () => { for (let i = 0; i < 2; i++) { xhrs[i] = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -2467,7 +2459,6 @@ describe('ParseObject', () => { for (let i = 0; i < 2; i++) { xhrs[i] = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -2529,7 +2520,6 @@ describe('ParseObject', () => { for (let i = 0; i < 2; i++) { xhrs[i] = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -2590,7 +2580,6 @@ describe('ObjectController', () => { const objectController = CoreManager.getObjectController(); const xhr = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2662,7 +2651,6 @@ describe('ObjectController', () => { const objectController = CoreManager.getObjectController(); const xhr = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2709,7 +2697,6 @@ describe('ObjectController', () => { const objectController = CoreManager.getObjectController(); const xhr = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2764,7 +2751,6 @@ describe('ObjectController', () => { for (let i = 0; i < 3; i++) { xhrs[i] = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2847,7 +2833,6 @@ describe('ObjectController', () => { for (let i = 0; i < 3; i++) { xhrs[i] = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2955,7 +2940,6 @@ describe('ObjectController', () => { const objectController = CoreManager.getObjectController(); const xhr = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; @@ -2998,7 +2982,6 @@ describe('ObjectController', () => { for (let i = 0; i < 4; i++) { xhrs[i] = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -3040,7 +3023,6 @@ describe('ObjectController', () => { for (let i = 0; i < 3; i++) { xhrs[i] = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -3126,7 +3108,6 @@ describe('ObjectController', () => { for (let i = 0; i < 2; i++) { xhrs[i] = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), status: 200, @@ -3296,7 +3277,6 @@ describe('ParseObject (unique instance mode)', () => { it('can save an array of objects', async () => { const xhr = { setRequestHeader: jest.fn(), - getAllResponseHeaders: jest.fn(), open: jest.fn(), send: jest.fn(), }; diff --git a/src/__tests__/test_helpers/mockXHR.js b/src/__tests__/test_helpers/mockXHR.js index b64767754..3b5fd06c0 100644 --- a/src/__tests__/test_helpers/mockXHR.js +++ b/src/__tests__/test_helpers/mockXHR.js @@ -19,9 +19,6 @@ function mockXHR(results, options = {}) { getRequestHeader: function (key) { return headers[key]; }, - getAllResponseHeaders: function() { - return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\n'); - }, upload: function () {}, send: function () { this.status = results[attempts].status; From 535e2f719ff27d4b1540ac9d598af478d95dbc82 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Mon, 15 Apr 2024 19:14:53 +0200 Subject: [PATCH 3/8] Update RESTController.js Co-authored-by: Diamond Lewis Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- src/RESTController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RESTController.js b/src/RESTController.js index 9ffda5235..59090e76e 100644 --- a/src/RESTController.js +++ b/src/RESTController.js @@ -113,7 +113,7 @@ const RESTController = { response = JSON.parse(xhr.responseText); const availableHeaders = typeof xhr.getAllResponseHeaders === 'function' ? xhr.getAllResponseHeaders() : ""; headers = {}; - if (typeof xhr.getResponseHeader === 'function' && availableHeaders && availableHeaders.indexOf('access-control-expose-headers') >= 0) { + if (typeof xhr.getResponseHeader === 'function' && availableHeaders?.indexOf('access-control-expose-headers') >= 0) { const responseHeaders = xhr.getResponseHeader('access-control-expose-headers').split(', '); responseHeaders.forEach(header => { if (availableHeaders.indexOf(header.toLowerCase()) >= 0) { From b27155b38e8eed7912739c65e7774d8c7703f804 Mon Sep 17 00:00:00 2001 From: Morten Moeller Date: Wed, 17 Apr 2024 20:50:02 -0500 Subject: [PATCH 4/8] Test for Chrome issue --- src/__tests__/RESTController-test.js | 57 ++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 4d4b0e37f..03cc34908 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -259,6 +259,63 @@ describe('RESTController', () => { expect(response._headers['X-Parse-Push-Status-Id']).toBe('5678'); }); + it('Dont call getRequestHeader with no headers or no getAllResponseHeaders', async () => { + const XHR = function () {}; + XHR.prototype = { + open: function () {}, + setRequestHeader: function () {}, + getResponseHeader: jest.fn(), + send: function () { + this.status = 200; + this.responseText = '{"result":"hello"}'; + this.readyState = 4; + this.onreadystatechange(); + }, + }; + RESTController._setXHR(XHR); + await RESTController.request('GET', 'classes/MyObject', {}, {}); + expect(XHR.prototype.getResponseHeader.mock.calls.length).toBe(0); + + XHR.prototype.getAllResponseHeaders = jest.fn(); + await RESTController.request('GET', 'classes/MyObject', {}, {}); + expect(XHR.prototype.getAllResponseHeaders.mock.calls.length).toBe(1); + expect(XHR.prototype.getResponseHeader.mock.calls.length).toBe(0); + }); + + it('mimic chromes console error on getResponseHeader', async () => { + const headers = { + 'access-control-expose-headers': 'a, b, c', + 'a' : 'value', + 'b' : 'value', + 'c' : 'value', + } + const XHR = function () {}; + XHR.prototype = { + open: function () {}, + setRequestHeader: function () {}, + getResponseHeader: jest.fn(key => { + if (Object.keys(headers).includes(key)) { + return headers[key]; + } + throw new Error("Chrome creates a console error here."); + }), + getAllResponseHeaders: jest.fn(() => { + return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\r\n'); + }), + send: function () { + this.status = 200; + this.responseText = '{"result":"hello"}'; + this.readyState = 4; + this.onreadystatechange(); + }, + }; + RESTController._setXHR(XHR); + await RESTController.request('GET', 'classes/MyObject', {}, {}); + expect(XHR.prototype.getAllResponseHeaders.mock.calls.length).toBe(1); + expect(XHR.prototype.getResponseHeader.mock.calls.length).toBe(4); + }); + + it('handles invalid header', async () => { const XHR = function () {}; XHR.prototype = { From b777ded7e2833b74c60dfb5777ffa9f56c2a6a4c Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:36:48 +0200 Subject: [PATCH 5/8] Update src/__tests__/RESTController-test.js Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- src/__tests__/RESTController-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 03cc34908..28e597a3f 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -259,7 +259,7 @@ describe('RESTController', () => { expect(response._headers['X-Parse-Push-Status-Id']).toBe('5678'); }); - it('Dont call getRequestHeader with no headers or no getAllResponseHeaders', async () => { + it('does not call getRequestHeader with no headers or no getAllResponseHeaders', async () => { const XHR = function () {}; XHR.prototype = { open: function () {}, From 50f087d1bf7b6e07e88a8b8d6ec5e003aeec5bd8 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:36:54 +0200 Subject: [PATCH 6/8] Update src/__tests__/RESTController-test.js Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- src/__tests__/RESTController-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 28e597a3f..e7c1c90ef 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -282,7 +282,7 @@ describe('RESTController', () => { expect(XHR.prototype.getResponseHeader.mock.calls.length).toBe(0); }); - it('mimic chromes console error on getResponseHeader', async () => { + it('mimics Chrome browser console error on getResponseHeader', async () => { const headers = { 'access-control-expose-headers': 'a, b, c', 'a' : 'value', From 43dc7b967600b198f5008ea7ecee1e85644e09cc Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:37:55 +0200 Subject: [PATCH 7/8] Update src/__tests__/RESTController-test.js Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- src/__tests__/RESTController-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index e7c1c90ef..6694b1ba0 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -282,7 +282,7 @@ describe('RESTController', () => { expect(XHR.prototype.getResponseHeader.mock.calls.length).toBe(0); }); - it('mimics Chrome browser console error on getResponseHeader', async () => { + it('does not invoke Chrome browser console error on getResponseHeader', async () => { const headers = { 'access-control-expose-headers': 'a, b, c', 'a' : 'value', From ac3e1ddcfa5eba8d22aab271a04db9ef64d3e91b Mon Sep 17 00:00:00 2001 From: Morten Moeller Date: Fri, 19 Apr 2024 14:16:33 -0500 Subject: [PATCH 8/8] Lint issues --- src/__tests__/RESTController-test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 6694b1ba0..640d83bc5 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -294,13 +294,13 @@ describe('RESTController', () => { open: function () {}, setRequestHeader: function () {}, getResponseHeader: jest.fn(key => { - if (Object.keys(headers).includes(key)) { - return headers[key]; - } - throw new Error("Chrome creates a console error here."); + if (Object.keys(headers).includes(key)) { + return headers[key]; + } + throw new Error("Chrome creates a console error here."); }), getAllResponseHeaders: jest.fn(() => { - return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\r\n'); + return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\r\n'); }), send: function () { this.status = 200;