Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
},
"web_accessible_resources": [
{
"resources": ["pages/media.html", "scripts/media.js", "scripts/logger.js"],
"resources": ["pages/media.html", "scripts/media.js", "scripts/logger.js", "settings.json"],
"matches": ["<all_urls>"]
}
],
Expand Down
2 changes: 2 additions & 0 deletions client/pages/media.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ <h1 class="main__header">Не закрывайте, не обновляйте э
</div>
</div>
</div>
<div class="ready-to-upload-container">
</div>
</section>
<script type="module" src="../scripts/media.js"></script>
</body>
Expand Down
142 changes: 142 additions & 0 deletions client/scripts/common.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,115 @@
import { logClientAction } from "./logger.js";

export const getAvailableDiskSpace = async () => {
const estimate = await navigator.storage.estimate();
const freeSpace = estimate.quota - estimate.usage;
logClientAction({ action: "Check available disk space", freeSpace });
return freeSpace;
};

export const getFormattedDateString = (date) => {
logClientAction({ action: "Generate human-readable date string" });

const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();

const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');

return `${hours}:${minutes}:${seconds}, ${day}.${month}.${year}`;
};

export const getDifferenceInTime = (date1, date2) => {
const diff = Math.abs(Math.floor(date2.getTime() / 1000) - Math.floor(date1.getTime() / 1000)); // ms
const totalSeconds = Math.floor(diff);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;

// Для удобного представления
const formattedHours = String(hours).padStart(2, '0');
const formattedMinutes = String(minutes).padStart(2, '0');
const formattedSeconds = String(seconds).padStart(2, '0');

logClientAction({ action: "Calculate difference in time" });
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
};

export function generateObjectId() {
const bytes = new Uint8Array(12);
const timestamp = Math.floor(Date.now() / 1000);
const view = new DataView(bytes.buffer);
view.setUint32(0, timestamp, false);
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
crypto.getRandomValues(bytes.subarray(4));
} else {
for (let i = 4; i < 12; i++) {
bytes[i] = Math.floor(Math.random() * 256);
}
}
logClientAction({ action: "Generate ObjectId" });

return Array.from(bytes)
.map(byte => byte.toString(16).padStart(2, '0'))
.join('');
}

export function getBrowserFingerprint() {
const fingerprint = {
browserVersion: navigator.userAgent.match(/Chrome\/([0-9.]+)/)?.[1] || 'unknown',
userAgent: navigator.userAgent,
language: navigator.language || navigator.userLanguage || 'unknown',
cpuCores: navigator.hardwareConcurrency || 'unknown',
screenResolution: `${window.screen.width}x${window.screen.height}`,
availableScreenResolution: `${window.screen.availWidth}x${window.screen.availHeight}`,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'unknown',
timestamp: new Date().toISOString(),
cookiesEnabled: navigator.cookieEnabled ? 'yes' : 'no',
windowSize: `${window.innerWidth}x${window.innerHeight}`,
doNotTrack: navigator.doNotTrack || window.doNotTrack || 'unknown'
};

logClientAction({ action: "Get browser fingerprint", fingerprint});

return fingerprint;
}

export async function requestClearLogs() {
await new Promise((resolve) => {
chrome.runtime.sendMessage({ action: "clearLogs" }, (response) => {
if (response.success) {
//ЗДЕСЬ НЕ НАДО ЛОГГИРОВАТЬ
//logClientAction({ action: "Clear logs" });
console.log("Логи очищены перед завершением");
} else {
//logClientAction({ action: "Error while clearing logs", error: response.error });
console.error("Ошибка очистки логов:", response.error);
}
resolve();
});
});
}

export async function saveBlobToFile(blob, filename) {
const url = URL.createObjectURL(blob);
filename = filename.replaceAll(":", "_");

try {
await chrome.downloads.download({
url: url,
filename: filename,
saveAs: true
});

setTimeout(() => URL.revokeObjectURL(url), 1000);
} catch (error) {
console.error('Download failed:', error);
URL.revokeObjectURL(url);
}
}

export async function deleteFilesFromTempList() {
const tempFiles = (await chrome.storage.local.get('tempFiles'))['tempFiles'] || [];
if (tempFiles.length > 0) {
Expand Down Expand Up @@ -174,6 +284,30 @@ export function waitForNotificationSuppression(timeout = 300) {
});
}

export function setReadyToUploadContainer(container, files) {
container.innerHTML = "";

const titleElement = document.createElement("div");
titleElement.id = "ready-to-upload-container-title";
titleElement.innerHTML = `Файлов, доступных для выгрузки: ${files.length}`;

container.appendChild(titleElement);

if (files.length > 0) {
const filesElement = document.createElement("div");
filesElement.id = "ready-to-upload-container-files";

files.forEach((fileName, index) => {
const el = document.createElement("div");
el.innerHTML = `${index + 1}. ${fileName}`;

filesElement.appendChild(el);
})

container.appendChild(filesElement);
}
}

export function buttonsStatesSave(state) {
chrome.storage.local.set({'bState': state});
logClientAction({ action: "Save buttons states"});
Expand All @@ -193,4 +327,12 @@ export function getCurrentDateString(date) {
logClientAction({ action: "Generate current date string" });
return `${date.getFullYear()}-${String(date.getMonth()+1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}T` +
`${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`;
}

export function parseDateString(str) {
const [datePart, timePart] = str.split("T");
const [year, month, day] = datePart.split("-").map(Number);
const [hours, minutes, seconds] = timePart.split(":").map(Number);

return new Date(year, month - 1, day, hours, minutes, seconds);
}
91 changes: 39 additions & 52 deletions client/scripts/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { buttonsStatesSave, deleteFiles, getCurrentDateString, showModalNotify } from "./common.js";
import { logClientAction, checkAndCleanLogs, clearLogs } from "./logger.js";
import { buttonsStatesSave, deleteFiles, getCurrentDateString, showModalNotify, saveBlobToFile } from "./common.js";
import { logClientAction, checkAndCleanLogs, clearLogs, prepareLogs } from "./logger.js";

const noPatronymicCheckbox = document.querySelector('#no_patronymic_checkbox');
const permissionsStatus = document.querySelector('#permissions-status');
Expand Down Expand Up @@ -395,36 +395,32 @@ buttonElements.permissions.addEventListener('click', async () => {
activateMediaTab: true
});
logClientAction({ action: "Send message", messageType: "getPermissions" });

invalidStop = (await chrome.storage.local.get('invalidStop'))['invalidStop'] || false;
if (server_connection && !invalidStop && bState == "failedUpload") {
inputElements.link.value = "";
saveInputValues();
logClientAction("Clear link field");
// TODO: Общий сброс. Например, когда прерванный прокторинг пользователь не захочет продолжать.
// chrome.storage.local.set({ 'sessionId': null });
}
});

buttonElements.upload.addEventListener('click', async () => {
logClientAction({ action: "Click upload button" });

if (!server_connection) return;
const files = (await chrome.storage.local.get('tempFiles'))['tempFiles'];
if (!files) {
buttonsStatesSave('needPermissions');
updateButtonsStates();
}
logClientAction({ action: "Start uploading video" });
uploadVideo()
.then(() => {

const files = (await chrome.storage.local.get('tempFiles'))['tempFiles'];

if (!files) {
logClientAction({ action: "No files have found to upload" });
buttonsStatesSave('needPermissions');
updateButtonsStates();
//await showModalNotify(["Запись успешно отправлена на сервер."], "Запись отправлена");
})
.catch(() => {
buttonsStatesSave('failedUpload');
updateButtonsStates();
});

} else {
logClientAction({ action: "Start uploading video" });
uploadVideo(files)
.then(() => {
buttonsStatesSave('needPermissions');
updateButtonsStates();
})
.catch(() => {
buttonsStatesSave('failedUpload');
updateButtonsStates();
});
}
});

async function startRecCallback() {
Expand Down Expand Up @@ -559,15 +555,15 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
}
});

async function uploadVideo() {
async function uploadVideo(files) {
chrome.storage.local.get(['sessionId', 'extension_logs'], async ({ sessionId, extension_logs }) => {
if (!sessionId) {
console.error("Session ID не найден в хранилище");
logClientAction({ action: `Upload fails due to missing session ID ${sessionId}` });
return;
}

const files = (await chrome.storage.local.get('tempFiles'))['tempFiles'] || [];
console.log(files);
if (!files.length) {
logClientAction("Ошибка при поиске записей");
throw new Error(`Ошибка при поиске записей`);
Expand All @@ -577,45 +573,32 @@ async function uploadVideo() {
const rootDirectory = await navigator.storage.getDirectory();

for (const filename of files) {
const blob = await (await rootDirectory.getFileHandle(filename, {create: false})).getFile();

if (filename.includes('screen')) {
formData.append('screen_video', await (await rootDirectory.getFileHandle(filename, {create: false})).getFile(), filename);
formData.append('screen_video', blob, filename);
} else {
formData.append('camera_video', await (await rootDirectory.getFileHandle(filename, {create: false})).getFile(), filename);
formData.append('camera_video', blob, filename);
}

logClientAction(`File ${filename} saved localy`);

await saveBlobToFile(blob, filename);
}

formData.append("id", sessionId);
const metadata = (await chrome.storage.local.get('metadata'))['metadata'] || {};
formData.append("metadata", metadata);
formData.append("metadata", JSON.stringify(metadata));

//logClientAction({ action: "Prepare upload payload", sessionId: sessionId, fileNames: [combinedFileName, cameraFileName] });

if (extension_logs) {
let logsToSend;
if (typeof extension_logs === "string") {
try {
logsToSend = JSON.parse(extension_logs);
} catch (e) {
console.error("Ошибка парсинга логов:", e);
logsToSend = [{ error: "Invalid logs", raw_data: extension_logs }];
logClientAction({ action: "Parse logs error", error: e.message });
}
} else {
logsToSend = extension_logs;
}

const logsBlob = new Blob([JSON.stringify(logsToSend, null, 2)], { type: 'application/json' });
let logs = prepareLogs(extension_logs);
const logsBlob = new Blob([JSON.stringify(logs, null, 2)], { type: 'application/json' });
formData.append("logs", logsBlob, "extension_logs.json");

const logsFileName = `extension_logs_${sessionId}_${getCurrentDateString(new Date())}.json`;
const url = URL.createObjectURL(logsBlob);
const link = document.createElement('a');
link.href = url;
link.download = logsFileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
await saveBlobToFile(logsBlob, logsFileName);

logClientAction({ action: "Download logs file", fileName: logsFileName });
}
Expand All @@ -634,6 +617,10 @@ async function uploadVideo() {
// TODO Fix notify showing #142, ибо если закрыть popup здесь ничего не произойдет
await showModalNotify([`Статус: ${data.message}`,
`Отправка завершена на 100 %`], "Записи успешно отправлены", true, true);

await chrome.storage.local.remove("metadata");
await chrome.storage.local.set({"session_status" : "need_init"});

inputElements.link.value = "";
inputElements.link.classList.remove('input-valid', 'input-invalid');
saveInputValues();
Expand Down
18 changes: 18 additions & 0 deletions client/scripts/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,22 @@ export async function checkAndCleanLogs() {
logClientAction({ action: "Clean old logs" });
}
}
}

export function prepareLogs(extension_logs) {
let logsToSend;

if (typeof extension_logs === "string") {
try {
logsToSend = JSON.parse(extension_logs);
} catch (e) {
console.error("Ошибка парсинга логов:", e);
logsToSend = [{ error: "Invalid logs", raw_data: extension_logs }];
logClientAction({ action: "Parse logs error", error: e.message });
}
} else {
logsToSend = extension_logs;
}

return logsToSend;
}
Loading