Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9a1cb57
Upd. Add and init indexedDB
veronika-tseleva-cleantalk Jan 9, 2026
e90e6b2
Upd. Saving data to storage. Added data clearing.
veronika-tseleva-cleantalk Jan 12, 2026
3f4de90
Merge branch 'dev' into indexeddb-websocket
veronika-tseleva-cleantalk Jan 15, 2026
e880c69
Merge latest changes
veronika-tseleva-cleantalk Jan 15, 2026
bf2d0d4
Upd. Initialized a basic template for a websocket
veronika-tseleva-cleantalk Jan 15, 2026
4792f4f
Upd. Edited a basic template for a websocket
veronika-tseleva-cleantalk Jan 15, 2026
d812014
Fix. Added a check for duplicate databases.
veronika-tseleva-cleantalk Jan 16, 2026
4822284
Merge branch 'dev' into indexeddb
veronika-tseleva-cleantalk Jan 19, 2026
869eb6f
Fix. Fixed merge mistakes
veronika-tseleva-cleantalk Jan 19, 2026
bc443c9
Merge branch 'indexeddb' into indexeddb-websocket
veronika-tseleva-cleantalk Jan 19, 2026
1a37d68
Fix. Fixed merge mistakes
veronika-tseleva-cleantalk Jan 19, 2026
bcfa266
Upd. Add socket link
veronika-tseleva-cleantalk Jan 20, 2026
3532456
Upd. Edited socket.onmessage handlers
veronika-tseleva-cleantalk Jan 20, 2026
caa486f
Fix. socket.close calls
veronika-tseleva-cleantalk Jan 20, 2026
420798b
Fix. Fixed indexedDB, fixed socket handlers
veronika-tseleva-cleantalk Jan 20, 2026
1d8e655
Merge branch 'dev' into indexeddb-websocket
veronika-tseleva-cleantalk Jan 20, 2026
7dfdfbd
Fix. Fixed merge mistakes
veronika-tseleva-cleantalk Jan 20, 2026
4e05eea
Fix. Fixed local data removal
veronika-tseleva-cleantalk Jan 20, 2026
5018348
Upd. Added project token to websocket
veronika-tseleva-cleantalk Jan 21, 2026
b342aa2
Upd. Added cleaning for indexedDB
veronika-tseleva-cleantalk Jan 22, 2026
91a8297
Fix. Removed comments
veronika-tseleva-cleantalk Jan 22, 2026
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
363 changes: 350 additions & 13 deletions dist/doboard-widget-bundle.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/doboard-widget-bundle.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/doboard-widget-bundle.min.js.map

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ let browserSync = require('browser-sync').create();
function bundle_src_js() {
const cssStream = processCSS();
const jsStream = gulp.src([
'js/src/localDB.js',
'js/src/api.js',
'js/src/websocket.js',
'js/src/constants.js',
'js/src/handlers.js',
'js/src/widget.js',
Expand Down Expand Up @@ -54,7 +56,7 @@ function processCSS() {
const {deleteSync} = await import('del');
deleteSync('temp');
})
;
;
}

gulp.task('compress-js', gulp.series(bundle_src_js, minify_js));
Expand Down
15 changes: 12 additions & 3 deletions js/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,9 @@ const logoutUserDoboard = async (projectToken, accountId) => {
}

const result = await spotfixApiCall(data, 'user_unauthorize', accountId);

if (result.operation_status === 'SUCCESS') {
await deleteDB();
clearLocalstorageOnLogout();
}
}
Expand All @@ -231,15 +233,15 @@ const getTasksDoboard = async (projectToken, sessionId, accountId, projectId, us
const tasks = result.tasks.map(task => ({
taskId: task.task_id,
taskTitle: task.name,
userId: task.user_id,
taskLastUpdate: task.updated,
taskCreated: task.created,
taskCreatorTaskUser: task.creator_user_id,
taskMeta: task.meta,
taskStatus: task.status,
}));

await spotfixIndexedDB.clearPut(TABLE_TASKS, tasks);
storageSaveTasksCount(tasks);

return tasks;
}

Expand All @@ -251,7 +253,7 @@ const getTasksCommentsDoboard = async (sessionId, accountId, projectToken, statu
status: status
}
const result = await spotfixApiCall(data, 'comment_get', accountId);
return result.comments.map(comment => ({
const comments = result.comments.map(comment => ({
taskId: comment.task_id,
commentId: comment.comment_id,
userId: comment.user_id,
Expand All @@ -260,6 +262,8 @@ const getTasksCommentsDoboard = async (sessionId, accountId, projectToken, statu
status: comment.status,
issueTitle: comment.task_name,
}));
await spotfixIndexedDB.clearPut(TABLE_COMMENTS, comments);
return comments;
};

const getUserDoboard = async (sessionId, projectToken, accountId, userId) => {
Expand All @@ -270,6 +274,11 @@ const getUserDoboard = async (sessionId, projectToken, accountId, userId) => {
if (userId) data.user_id = userId;

const result = await spotfixApiCall(data, 'user_get', accountId);
if (data.user_id) {
await spotfixIndexedDB.put(TABLE_USERS, result.users);
} else {
await spotfixIndexedDB.clearPut(TABLE_USERS, result.users);
}
return result.users;

// @ToDo Need to handle these two different answers?
Expand Down
25 changes: 17 additions & 8 deletions js/src/handlers.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@

async function confirmUserEmail(emailConfirmationToken, params) {
const result = await userConfirmEmailDoboard(emailConfirmationToken);
// Save session data to LS
localStorage.setItem('spotfix_email', result.email);
localStorage.setItem('spotfix_session_id', result.sessionId);
localStorage.setItem('spotfix_user_id', result.userId);
await spotfixIndexedDB.init();

// Get pending task from LS
const pendingTaskRaw = localStorage.getItem('spotfix_pending_task');
Expand Down Expand Up @@ -39,8 +41,10 @@ async function confirmUserEmail(emailConfirmationToken, params) {
async function getTasksFullDetails(params, tasks, currentActiveTaskId) {
if (tasks.length > 0) {
const sessionId = localStorage.getItem('spotfix_session_id');
const comments = await getTasksCommentsDoboard(sessionId, params.accountId, params.projectToken);
const users = await getUserDoboard(sessionId, params.projectToken, params.accountId);
await getTasksCommentsDoboard(sessionId, params.accountId, params.projectToken);
const comments = await spotfixIndexedDB.getAll(TABLE_COMMENTS);
await getUserDoboard(sessionId, params.projectToken, params.accountId);
const users = await spotfixIndexedDB.getAll(TABLE_USERS);
const foundTask = tasks.find(item => +item.taskId === +currentActiveTaskId);

return {
Expand All @@ -55,8 +59,9 @@ async function getUserDetails(params) {
const sessionId = localStorage.getItem('spotfix_session_id');
const currentUserId = localStorage.getItem('spotfix_user_id');
if(currentUserId) {
const users = await getUserDoboard(sessionId, params.projectToken, params.accountId, currentUserId);
return users[0] || {};
await getUserDoboard(sessionId, params.projectToken, params.accountId, currentUserId);
const users = await spotfixIndexedDB.getAll(TABLE_USERS);
return users.find(user => +user.user_id === +currentUserId) || {};
}
}

Expand All @@ -83,14 +88,15 @@ async function addTaskComment(params, taskId, commentText) {
return await createTaskCommentDoboard(params.accountId, sessionId, taskId, commentText, params.projectToken);
}

function getUserTasks(params) {
async function getUserTasks(params) {
if (!localStorage.getItem('spotfix_session_id')) {
return {};
}
const projectToken = params.projectToken;
const sessionId = localStorage.getItem('spotfix_session_id');
const userId = localStorage.getItem('spotfix_user_id');
return getTasksDoboard(projectToken, sessionId, params.accountId, params.projectId, userId);
await getTasksDoboard(projectToken, sessionId, params.accountId, params.projectId, userId);
return await spotfixIndexedDB.getAll(TABLE_TASKS, 'userId', userId);
}

async function getAllTasks(params) {
Expand All @@ -99,8 +105,8 @@ async function getAllTasks(params) {
}
const projectToken = params.projectToken;
const sessionId = localStorage.getItem('spotfix_session_id');
const tasksData = await getTasksDoboard(projectToken, sessionId, params.accountId, params.projectId);

await getTasksDoboard(projectToken, sessionId, params.accountId, params.projectId);
const tasksData = await spotfixIndexedDB.getAll(TABLE_TASKS);
// Get only tasks with metadata
const filteredTaskData = tasksData.filter(task => {
return task.taskMeta;
Expand Down Expand Up @@ -206,6 +212,7 @@ function registerUser(taskDetails) {
localStorage.setItem('spotfix_session_id', response.sessionId);
localStorage.setItem('spotfix_user_id', response.userId);
localStorage.setItem('spotfix_email', response.email);
spotfixIndexedDB.init();
userUpdate(projectToken, accountId);
} else if (response.operationStatus === 'SUCCESS' && response.operationMessage && response.operationMessage.length > 0) {
if (response.operationMessage == 'Waiting for email confirmation') {
Expand Down Expand Up @@ -235,6 +242,7 @@ function loginUser(taskDetails) {
localStorage.setItem('spotfix_session_id', response.sessionId);
localStorage.setItem('spotfix_user_id', response.userId);
localStorage.setItem('spotfix_email', userEmail);
spotfixIndexedDB.init();
} else if (response.operationStatus === 'SUCCESS' && response.operationMessage && response.operationMessage.length > 0) {
if (typeof showMessageCallback === 'function') {
showMessageCallback(response.operationMessage, 'notice');
Expand Down Expand Up @@ -283,6 +291,7 @@ function setToggleStatus(rootElement){
const clickHandler = () => {
const timer = setTimeout(() => {
localStorage.setItem('spotfix_widget_is_closed', '1');
wsSpotfix.close();
rootElement.hide();
clearTimeout(timer);
}, 300);
Expand Down
174 changes: 174 additions & 0 deletions js/src/localDB.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
const INDEXED_DB_NAME = 'spotfix-localDB';
const indexedDBVersion = 1;

const TABLE_USERS = 'users';
const TABLE_TASKS = 'tasks';
const TABLE_COMMENTS = 'comments';

const LOCAL_DATA_BASE_TABLE = [
{name: TABLE_USERS, keyPath: 'user_id'},
{name: TABLE_TASKS, keyPath: 'taskId'},
{name: TABLE_COMMENTS, keyPath: 'commentId'},
];

async function openIndexedDB(name, version = indexedDBVersion) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, version);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
request.onupgradeneeded = (e) => resolve(request.result);
});
}

async function deleteDB() {
try {
const dbs = await window.indexedDB.databases();
for (const db of dbs) {
await new Promise((resolve) => {
const deleteReq = indexedDB.deleteDatabase(db.name);
deleteReq.onsuccess = () => resolve();
deleteReq.onerror = () => resolve();
});
}
} catch (err) {
console.warn('deleteDB error', err);
}
}

const spotfixIndexedDB = {
getIndexedDBName: () =>
`${INDEXED_DB_NAME}_${localStorage.getItem('spotfix_session_id') || localStorage.getItem('spotfix_project_token')}`,

error: (request, error) => {
console.error('IndexedDB error', request, error);
},

init: async () => {
const sessionId = localStorage.getItem('spotfix_session_id');
const projectToken = localStorage.getItem('spotfix_project_token');

if (!sessionId && !projectToken) return { needInit: false };

const dbName = spotfixIndexedDB.getIndexedDBName();

await deleteDB();

return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, indexedDBVersion);

request.onupgradeneeded = (e) => {
const db = e.target.result;
LOCAL_DATA_BASE_TABLE.forEach((item) => {
if (!db.objectStoreNames.contains(item.name)) {
const store = db.createObjectStore(item.name, { keyPath: item.keyPath });
if (item.name === TABLE_COMMENTS) store.createIndex('taskId', 'taskId');
if (item.name === TABLE_TASKS) store.createIndex('userId', 'userId');
}
});
resolve({ needInit: true });
};

request.onsuccess = (e) => {
const db = e.target.result;
const missingStores = LOCAL_DATA_BASE_TABLE.filter(
(item) => !db.objectStoreNames.contains(item.name)
);

if (missingStores.length === 0) {
db.close();
resolve({ needInit: true });
} else {
const newVersion = db.version + 1;
db.close();
const upgradeRequest = indexedDB.open(dbName, newVersion);
upgradeRequest.onupgradeneeded = (e2) => {
const db2 = e2.target.result;
missingStores.forEach((item) => {
const store = db2.createObjectStore(item.name, { keyPath: item.keyPath });
if (item.name === TABLE_COMMENTS) store.createIndex('taskId', 'taskId');
if (item.name === TABLE_TASKS) store.createIndex('userId', 'userId');
});
};
upgradeRequest.onsuccess = () => resolve({ needInit: true });
upgradeRequest.onerror = (err) => reject(err);
}
};

request.onerror = (err) => reject(err);
});
},

withStore: async (table, mode = 'readwrite', callback) => {
const dbName = spotfixIndexedDB.getIndexedDBName();
const db = await openIndexedDB(dbName, indexedDBVersion);
return new Promise((resolve, reject) => {
try {
const transaction = db.transaction(table, mode);
const store = transaction.objectStore(table);

const result = callback(store);

transaction.oncomplete = () => {
db.close();
resolve(result);
};
transaction.onerror = (e) => {
db.close();
reject(e.target.error);
};
} catch (err) {
db.close();
reject(err);
}
});
},

put: async (table, data) => {
return spotfixIndexedDB.withStore(table, 'readwrite', (store) => {
if (Array.isArray(data)) {
data.forEach((item) => store.put(item));
} else {
store.put(data);
}
});
},

delete: async (table, key) => {
return spotfixIndexedDB.withStore(table, 'readwrite', (store) => {
store.delete(key);
});
},

clearTable: async (table) => {
return spotfixIndexedDB.withStore(table, 'readwrite', (store) => store.clear());
},

clearPut: async (table, data) => {
await spotfixIndexedDB.clearTable(table);
await spotfixIndexedDB.put(table, data);
},

getAll: async (table, indexName, value) => {
return spotfixIndexedDB.withStore(table, 'readonly', (store) => {
return new Promise((resolve, reject) => {
let request;
if (indexName && value !== undefined) {
request = store.index(indexName).getAll(value);
} else {
request = store.getAll();
}
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
});
},

getTable: async (table) => {
if (!localStorage.getItem('spotfix_session_id') && !localStorage.getItem('spotfix_project_token')) return [];
return spotfixIndexedDB.getAll(table);
},

deleteTable: async (table, key) => {
return spotfixIndexedDB.delete(table, key);
},
};
3 changes: 3 additions & 0 deletions js/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ if( document.readyState !== 'loading' ) {
}

function spotFixInit() {
spotfixIndexedDB.init();
wsSpotfix.connect();
wsSpotfix.subscribe();
new SpotFixSourcesLoader();
new CleanTalkWidgetDoboard({}, 'wrap');
loadBotDetector()
Expand Down
7 changes: 7 additions & 0 deletions js/src/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ function storageWidgetCloseIsSet() {
*/
function storageSetWidgetIsClosed(visible) {
localStorage.setItem('spotfix_widget_is_closed', visible ? '1' : '0');
if(visible) {
wsSpotfix.close();
} else {
wsSpotfix.connect();
wsSpotfix.subscribe();
}
}

/**
Expand Down Expand Up @@ -184,4 +190,5 @@ function clearLocalstorageOnLogout () {
localStorage.removeItem('spotfix_session_id');
localStorage.removeItem('spotfix_user_id');
localStorage.setItem('spotfix_widget_is_closed', '1');
wsSpotfix.close();
}
Loading