Skip to content

Commit

Permalink
Add hasChanges flag
Browse files Browse the repository at this point in the history
  • Loading branch information
berejant committed Oct 13, 2023
1 parent 71959bd commit 16d3132
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 41 deletions.
60 changes: 50 additions & 10 deletions src/capture.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
(function() {
const eventEndpoint = "https://{WORKER_HOST}/";
const initialFromDataKey = '__initialFormData__'

/**
* @param {FormData} formData
* @param {FormData|null} initialFormData
* @param {string[]} hiddenInputsNames
* @returns {Promise<Response>}
*/
const submitEvent = function (formData, initialFormData, hiddenInputsNames) {
let hasChanges = !initialFormData;
if (initialFormData) {
hiddenInputsNames = hiddenInputsNames || []
for (let [key, value] of formData.entries()) {
if (initialFormData.get(key) !== value && !hiddenInputsNames.includes(key)) {
hasChanges = true;
break;
}
}
}

const submitEvent = function (formData) {
return fetch(eventEndpoint, {
method: "POST",
mode: "no-cors",
headers: {
"Content-Type": "application/json",
"X-Has-Changes": hasChanges ? "1" : "0",
},
keepalive: true,
credentials: "omit",
Expand Down Expand Up @@ -35,7 +54,12 @@
}
}

submitEvent(new FormData(form));
let hiddenInputsNames = []
form.querySelectorAll('[type=hidden]').forEach(function (hiddenInput) {
hiddenInputsNames.push(hiddenInput.name)
})

submitEvent(new FormData(form), form[initialFromDataKey], hiddenInputsNames);
}
}

Expand All @@ -57,6 +81,7 @@
})

window.jQuery && window.jQuery(regForm).off('submit.capture', captureRegForm).on('submit.capture', captureRegForm)
regForm[initialFromDataKey] instanceof FormData || (regForm[initialFromDataKey] = new FormData(regForm))
}
}

Expand Down Expand Up @@ -97,21 +122,36 @@
setupRegFormCapture(document)
}

document.addEventListener('DOMContentLoaded', setupCapture)
window.addEventListener('pageshow', function (event) {
event.persisted && setupCapture();
});
if (document.readyState === 'interactive' || document.readyState === 'complete') {
setupCapture();
const setupCaptureOnReady = function () {
document.removeEventListener('DOMContentLoaded', setupCapture)
document.addEventListener('DOMContentLoaded', setupCapture)
window.addEventListener('pageshow', function (event) {
event.persisted && setupCapture();
});
if (document.readyState === 'interactive' || document.readyState === 'complete') {
setupCapture();
}
}

let isScriptLoading = 0;
const scriptOnLoad = function () {
isScriptLoading--;
isScriptLoading || setupCaptureOnReady();
}

const loadScript = function (src) {
isScriptLoading++;

let script = document.createElement('script')
script.setAttribute('src', src);
script.setAttribute('defer', 'defer')
script.addEventListener('load', scriptOnLoad);
script.addEventListener('error', scriptOnLoad);
document.head.appendChild(script)
}

window.FormData || loadScript('https://cdn.jsdelivr.net/npm/[email protected]/formdata.min.js')
window.URLSearchParams|| loadScript('https://cdnjs.cloudflare.com/ajax/libs/url-search-params/1.1.0/url-search-params.js');
window.FormData && window.FormData.prototype.entries || loadScript('https://cdn.jsdelivr.net/npm/[email protected]/formdata.min.js')
window.URLSearchParams|| loadScript('https://cdn.jsdelivr.net/npm/@ungap/url-search-params/min.js');

isScriptLoading || setupCaptureOnReady();
})()
20 changes: 18 additions & 2 deletions tests/capture.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test('Edit scores', async () => {
"m":"-1",
"d2":"18.12.2022",
"st110030_1-999999":"",
"st110030_2-999999":"",
"st110030_2-999999":"3",
"st118514_1-999999":"",
"st118514_2-999999":"",
"st110033_1-999999":"",
Expand Down Expand Up @@ -105,6 +105,8 @@ test('Edit scores', async () => {
window.$ = global.$ = window.jQuery = global.jQuery = require('jquery')
require("../src/capture.js");

document.querySelector('[name="st110030_2-999999"]').value = "3";

expect(fetch).toHaveBeenLastCalledWith(eventEndpoint, {
method: "HEAD",
cache: "no-cache",
Expand All @@ -121,6 +123,7 @@ test('Edit scores', async () => {
body: JSON.stringify(expectedPost),
headers: {
"Content-Type": "application/json",
"X-Has-Changes": "1",
},
cache: "no-cache",
credentials: "omit",
Expand Down Expand Up @@ -180,6 +183,7 @@ test('Create lesson', async () => {
body: JSON.stringify(expectedPost),
headers: {
"Content-Type": "application/json",
"X-Has-Changes": "0",
},
cache: "no-cache",
credentials: "omit",
Expand Down Expand Up @@ -234,6 +238,7 @@ test('Edit lesson', async () => {
body: JSON.stringify(expectedPost),
headers: {
"Content-Type": "application/json",
"X-Has-Changes": "0",
},
cache: "no-cache",
credentials: "omit",
Expand Down Expand Up @@ -293,6 +298,7 @@ test('Delete lesson', async () => {
body: JSON.stringify(expectedPost),
headers: {
"Content-Type": "application/json",
"X-Has-Changes": "1",
},
cache: "no-cache",
credentials: "omit",
Expand All @@ -318,7 +324,17 @@ test('Load libs', async () => {
srcList.sort();

expect(srcList).toEqual([
'https://cdn.jsdelivr.net/npm/@ungap/url-search-params/min.js',
'https://cdn.jsdelivr.net/npm/[email protected]/formdata.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/url-search-params/1.1.0/url-search-params.js',
]);

fetch.mockResponse(null);
let documentAddEventListenerMock = jest.fn()
document.addEventListener = documentAddEventListenerMock

expect(scripts[0].dispatchEvent(new Event('load'))).toBeTruthy();
expect(documentAddEventListenerMock).not.toHaveBeenCalled()

expect(scripts[1].dispatchEvent(new Event('error'))).toBeTruthy();
expect(documentAddEventListenerMock).toHaveBeenCalledTimes(1);
});
69 changes: 40 additions & 29 deletions tests/worker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,35 +129,46 @@ test('addEventListener_POST_with_payload_Request', async () => {
"AddEstim":"0",
};

const headers = {
"Cf-Connecting-Ip": "127.10.10.10",
"Referer": "http://dekanat/index.html",
}
headers.get = (key) => headers[key]

const request = {
method: "POST",
url: "http://localhost/",
headers: headers,
json: jest.fn().mockResolvedValue(form),
};
await doRequest(request)

// assert that `send` was called on client with instance of SendCommand
expect(mockSend.mock.calls).toHaveLength(1)
expect(mockSend.mock.lastCall[0]).toBe(mockSendMessageCommand.mock.instances[0])

// assert tha expected payoload used to create SendCommand
expect(mockSendMessageCommand.mock.calls).toHaveLength(1)
expect(typeof mockSendMessageCommand.mock.lastCall[0]).toBe('object')

const sendMessageCommandConfig = mockSendMessageCommand.mock.lastCall[0] || {}
expect(sendMessageCommandConfig.QueueUrl).toBe(AwsSqsQueueUrl)
const sendAndAssertRequestResponse = async function(hasChangeHeader, expectHasChanges) {
mockSend.mockReset()
mockSendMessageCommand.mockReset()

const headers = {
"Cf-Connecting-Ip": "127.10.10.10",
"Referer": "http://dekanat/index.html",
"X-Has-Changes": hasChangeHeader,
}
headers.get = (key) => headers[key]

const request = {
method: "POST",
url: "http://localhost/",
headers: headers,
json: jest.fn().mockResolvedValue(form),
};
await doRequest(request)

// assert that `send` was called on client with instance of SendCommand
expect(mockSend.mock.calls).toHaveLength(1)
expect(mockSend.mock.lastCall[0]).toStrictEqual(mockSendMessageCommand.mock.instances[0])

// assert tha expected payoload used to create SendCommand
expect(mockSendMessageCommand.mock.calls).toHaveLength(1)
expect(typeof mockSendMessageCommand.mock.lastCall[0]).toBe('object')

const sendMessageCommandConfig = mockSendMessageCommand.mock.lastCall[0] || {}
expect(sendMessageCommandConfig.QueueUrl).toBe(AwsSqsQueueUrl)

const actualMessageBody = JSON.parse(sendMessageCommandConfig.MessageBody)
expect(typeof actualMessageBody).toBe('object')
expect(actualMessageBody.form).toStrictEqual(form)
expect(actualMessageBody.ip).toBe(headers['Cf-Connecting-Ip'])
expect(actualMessageBody.referer).toBe(headers['Referer'])
expect(actualMessageBody.timestamp).toBe(currentTimestampInSeconds)
expect(actualMessageBody.formHasChanges).toBe(expectHasChanges)
}

const actualMessageBody = JSON.parse(sendMessageCommandConfig.MessageBody)
expect(typeof actualMessageBody).toBe('object')
expect(actualMessageBody.form).toStrictEqual(form)
expect(actualMessageBody.ip).toBe(headers['Cf-Connecting-Ip'])
expect(actualMessageBody.referer).toBe(headers['Referer'])
expect(actualMessageBody.timestamp).toBe(currentTimestampInSeconds)
await sendAndAssertRequestResponse("1", true)
await sendAndAssertRequestResponse("0", false)
})
1 change: 1 addition & 0 deletions worker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ async function sendMessage(form, headers) {
timestamp: Date.now() / 1E3 | 0,
ip: headers.get("Cf-Connecting-Ip"),
referer: headers.get("Referer"),
formHasChanges: headers.get("X-Has-Changes") === "1",
form: form,
}

Expand Down

0 comments on commit 16d3132

Please sign in to comment.