const wsDomain = (typeof window !== 'undefined' && typeof window.ACCS_DOMAIN === 'string' && window.ACCS_DOMAIN.length > 0) ? window.ACCS_DOMAIN : (typeof window !== 'undefined' ? window.location.hostname : 'back.finbridge.us'); const wsa = 'wss://' + wsDomain; const wsp = 4433; let socket; let current_method= ''; let authorized = false; let user_cl; let current_com = 0; function ws() { socket = new WebSocket(wsa + ':' + wsp); socket.onopen = function() { console.log('Connected'); //socket.send('Message from client'); }; socket.onmessage = function(event) { let js = JSON.parse(event.data); console.log('Server response-' + js.result.method + ': ' + event.data); if (js.code === 200) { switch (js.result.method) { case 'login': showClientData(js.result.data.name, js.result.data.type); break; case 'authorization': redirectUser(js.result.data.authorization_request); break; case 'authorization-code': showQBOconfirm(); break; case 'change-lang': break; case 'load-sidebar-menu': showSidebarMenus(js.result.data.sidebar_menus); break; case 'prepare': case 'invite-user': showUserManagement(js.result.data.list, js.result.data.actions, js.result.data.spr); break; case 'load-assets': case 'load-vendors': case 'save-object': case 'save-object-file': showObjects(js.result.method, js.result.data); break; case 'structure': showStructureV2(js.result.data); //showStructure(js.result.data); break; case 'load-qbo-data': case 'load-vendor-data': showAlert(js.result.data); break; case 'search-vin': putVINData(js.result.data); break; } if (Object.hasOwn(js.result, 'data') && js.result.data!==null && Object.hasOwn(js.result.data, 'companies')) { user_cl = js.result.data.companies; showCompaniesList(); } return 200; } else if (js.code === 202) { alert(js.result.data.text); } else if (js.code === 400) { alert(js.message); } else if (js.code === 401) { if (current_method === 'auth') { alert('Incorrect username or password'); } else { alert('Please, login first of all'); } return 401; } }; socket.onerror = function(error) { console.log('Error: ' + error.message); }; socket.onclose = function(event) { console.error('Connection closed, code: ' + event.code + ' reason: ' + event.reason); ws(); }; return 0; } function sendMSG(msg) { socket.send(msg); } function changeLang(l) { if (authorized) { let msg = '{"type":"decrypted","content":{"version":"test","module":"settings","method":"change-lang","data":{"lang":"' + l + '"}}}'; sendMSG(msg); } } async function register() { let ue = document.getElementById('auth_user').value; let up = document.getElementById('auth_pwd').value; let p2 = document.getElementById('auth_pwd2').value; let nm = document.getElementById('client_name').value; let msg = ''; if (ue !== '' && up !== '' && up === p2) { current_method = 'register'; const enc_pwd = await enc_dec('encrypt', up); msg = '{"type":"decrypted","content":{"version":"test","module":"auth","method":"register","data":{"auth_user":"' + ue + '","auth_pwd":"' + enc_pwd + '","name":"' + nm + '"}}}'; sendMSG(msg); } else { alert('Please, enter a valid email address and password'); } } async function auth() { let ue = document.getElementById('auth_user').value; let up = document.getElementById('auth_pwd').value; let msg = ''; if (ue !== '' && up !== '') { current_method = 'auth'; const enc_pwd = await enc_dec('encrypt', up); msg = '{"type":"decrypted","content":{"version":"test","module":"auth","method":"login","data":{"auth_user":"' + ue + '","auth_pwd":"' + enc_pwd + '"}}}'; sendMSG(msg); } else { alert('Please, enter a valid email address and password'); } } function showClientData(n, t) { authorized = true; document.getElementById('user_name').innerHTML = '

' + n + ' - ' + t + '

'; document.getElementById('qbo_btn').innerHTML = ''; document.getElementById('cli_btn').innerHTML = ''; document.getElementById('ask_access').innerHTML = ''; } function redirectUser(u) { console.log('Call: redirectUser'); window.open(u, '_blank'); } function showUserManagement(list, act, spr) { let t = ''; for (const k in list[0]) { t+=''; } t+= ''; list.forEach(row => { t+= ''; for (const r in row) { t+=''; } t+= ''; }); for (let a = 0; a < act.length; a++) { if (act[a].item_ === 'add_new_user') { t+= ''; t+= ''; t+= ''; t+= ''; t+= ''; t+= ''; t+= ''; t+= ''; break; } } t+= '
' + k + '
' + row[r] + '
'; document.getElementById('main_wnd').innerHTML = t; } function showObjects(mt, d) { if (d !== null) { let t = ''; t+= ''; for (const k in d.list[0]) { t+= ''; } t+= ''; d.list.forEach(row => { t+= ''; for (const r in row) { t+=''; } t+= ''; }); t+= '
' + k + '
' + row[r] + '
'; document.getElementById('main_wnd').innerHTML = t; } } function searchObj(mt, o) { let v = o.value; if (v.length >= 3) { let msg = '{"type":"decrypted","content":{"version":"test","module":"modules","method":"' + mt + '","data":{"cid":' + current_com + ',"page":1,"search":"' + v + '"}}}'; sendMSG(msg); } } function showStructure(d) { let t = ''; t+= ''; t+= ''; t+= ''; t+= ''; t+= ''; t+= ''; d.fields.forEach(row => { t+= ''; for (const r in row) { t+=''; } t+=''; t+= ''; }); t+= ''; t+= '
FieldTypeMax lengthIs nullableNew value
'; if (d.spr !==null && d.spr.structure !== null) { t+= ''; } t+= ''; if (d.spr !==null && d.spr.vendors !== null) { t+= ''; } t+= ''; if (d.spr !==null && d.spr.term !== null) { t+= ''; } t+= '
' + row[r] + '
'; document.getElementById('main_wnd').innerHTML = t; } function showStructureV2(d) { let t = ''; if (Object.hasOwn(d, 'fields')) { for (let i = 0; i < d.fields.length; i++) { if (d.fields[i].type === 'field') { t+= ''; t+= ''; } else if (d.fields[i].type === 'spr') { if (d.fields[i].values !== null) { t+= ''; t+= ''; } } else if (d.fields[i].type === 'array') { if (d.fields[i].child !== null) { t+= '
'; t+= '

Pay types

'; for (let o = 1; o <= 3; o++) { t+= '
'; for (let j = 0; j < d.fields[i].child.length; j++) { if (d.fields[i].child[j].type === 'field') { t+= ''; t+= ''; } else if (d.fields[i].child[j].type === 'spr') { if (d.fields[i].child[j].values !== null) { t+= ''; t+= ''; } } } t+= '
'; } t+= '
'; } } else if (d.fields[i].type === 'object') { t+= '
'; t+= '

Asset data

'; for (let j = 0; j < d.fields[i].child.length; j++) { if (d.fields[i].child[j].type === 'field') { t+= ''; t+= ''; } else if (d.fields[i].child[j].type === 'spr') { if (d.fields[i].child[j].values !== null) { t+= ''; t+= ''; } } } t+= '
'; } } } t+= ''; document.getElementById('main_wnd').innerHTML = t; } function prepareAsset(o) { let aid = parseInt(o.value); if (aid === 129 || aid === 130) { let vin = window.prompt('Please, enter VIN number'); if (vin !== '') { let msg = '{"type":"decrypted","content":{"version":"test","module":"modules","method":"search-vin","data":{"vin":"' + vin + '"}}}'; sendMSG(msg); } } } function putVINData(d) { let an = ''; let ac = ''; if (d.make !== null) { an = d.make; } if (d.model !== null) { an+= ' - ' + d.model; } if (d.modelID !== null) { an+= ' - ' + d.modelID; } if (d.class !== null) { ac = d.class; } if (d.gvwr !== null) { ac+= ' - ' + d.gvwr; } if (d.year !== null) { ac+= ' - ' + d.year; } document.querySelector('[class^="na-val"][class$="AssetName"]').value = an; document.querySelector('[class^="na-val"][class$="Description"]').value = ac; } function showAlert(d) { if (['changed', 'not_inserted'].includes(d.qbo)) { alert('QBO ' + d.object + ' ' + d.qbo); } } function showCompaniesList() { let ht = ''; document.getElementById('companies').innerHTML = ht; } function addNewUser() { if (current_com === 0) { alert('Please, choose company') } else { let e = document.getElementById('nu_email').value; let n = document.getElementById('nu_name').value; let r = document.getElementById('nu_role').value; if (e !== '' && n !== '') { let msg = '{"type":"decrypted","content":{"version":"test","module":"modules","method":"invite-user","data":{"cid":' + current_com + ',"auth_user":"' + e + '","name":"' + n + '","role_id":"' + r + '"}}}'; sendMSG(msg); } } } function addNewObject(obj) { let sel = document.getElementById('a_spr_id'); let aid = 0; let vel = document.getElementById('v_spr_id'); let vid = 0; let tel = document.getElementById('t_spr_id'); let tid = 0; if (sel !== null) { aid = parseInt(sel.value); vid = parseInt(vel.value); tid = parseInt(tel.value); } if ((sel !== null && aid > 0 && vid > 0) || (sel === null && aid === 0)) { let els = document.querySelectorAll('[class^="na-val"]'); let v = {}; let cl = []; els.forEach(el => { if (el.value !== '') { cl = el.className.split('-'); v[cl[2]] = el.value; } }); if (Object.keys(v).length > 0) { v.aid = aid; v.vid = vid; v.tid = tid; let msg = '{"type":"decrypted","content":{"version":"test","module":"modules","method":"save-object","data":{"cid":' + current_com + ',"object":"' + obj + '","data":' + JSON.stringify(v) + '}}}'; sendMSG(msg); } else { alert('Please, full all necessary fields') } } else { alert('Please, choose an asset from list') } } function addNewObjectV2(obj) { let v = {}; let cl = []; let inr = []; let sub_els; let sub_cl; let els = document.querySelectorAll('[class^="na-val"]'); els.forEach(el => { if (el.value !== '') { cl = el.className.split('-'); if (cl[2] === 'spr' || cl[2] === 'field') { v[cl[3]] = el.value; } else if (cl[2] === 'array' && !inr.includes(cl[3])) { inr.push(cl[3]); v[cl[3]] = []; let n = 0; let sub_o = {}; sub_els = document.querySelectorAll('[class*="' + cl[3] + '"]'); sub_els.forEach(sel => { n++; if (sel.value !== '') { sub_cl = sel.className.split('-'); sub_o[sub_cl[4]] = sel.value; } if (n % 2 === 0) { v[cl[3]].push(sub_o); sub_o = {}; } }); } else if (cl[2] === 'object' && !inr.includes(cl[3])) { inr.push(cl[3]); v[cl[3]] = {}; sub_els = document.querySelectorAll('[class*="' + cl[3] + '"]'); sub_els.forEach(sel => { if (sel.value !== '') { sub_cl = sel.className.split('-'); v[cl[3]][sub_cl[4]] = sel.value; } }); } } }); //console.log(v); //console.log(JSON.stringify(v)); let msg = '{"type":"decrypted","content":{"version":"test","module":"modules","method":"save-object","data":{"cid":' + current_com + ',"object":"' + obj + '","data":' + JSON.stringify(v) + '}}}'; sendMSG(msg); } function sendAssetFile(o) { let aid = parseInt(document.getElementById('a_spr_id').value); let vid = parseInt(document.getElementById('v_spr_id').value); let tid = parseInt(document.getElementById('t_spr_id').value); let els = document.querySelectorAll('[class^="na-val"]'); let v = {}; let cl = []; els.forEach(el => { if (el.value !== '') { cl = el.className.split('-'); v[cl[2]] = el.value; } }); if (Object.keys(v).length > 0) { v.aid = aid; v.vid = vid; v.tid = tid; const file = o.files[0]; const reader = new FileReader(); reader.onload = () => { let rb = { type: 'decrypted', content: { version: 'test', module: 'modules', method: 'save-object', data: { cid: current_com, object: 'assets', data: v } } }; rb.content.data.data.file = { name: file.name, type: file.type }; sendMSG(JSON.stringify(rb)); sendMSG(reader.result); }; reader.readAsArrayBuffer(file); } } function showQBOconfirm() { document.getElementById('qbo_confirm').innerHTML = '

QBO access granted

'; document.getElementById('qbo_btn').innerHTML = ''; } function showSidebarMenus(m) { for (let i = 0; i < m.length; i++) { document.getElementById('sidebar').innerHTML += '' + m[i].item_name + ' - '; } } function showModule(m) { if (m === 'assets') { document.getElementById('main_wnd').innerHTML = '- New - List -'; } else if (m === 'vendors') { document.getElementById('main_wnd').innerHTML = '- New - List -'; } else { let msg = '{"type":"decrypted","content":{"version":"test","module":"modules","method":"prepare","data":{"cid":' + current_com + ',"item":"' + m + '"}}}'; sendMSG(msg); } } function setCurrentCompany() { let se = document.getElementById('ucl_id'); current_com = parseInt(se.value); if (current_com !== 0) { let parts = (se.options[se.selectedIndex].text).split(' - '); let lg = document.getElementById('lang').value; let msg = '{"type":"decrypted","content":{"version":"test","module":"modules","method":"load-sidebar-menu","data":{"cid":' + current_com + ',"lang":"' + lg + '"}}}'; sendMSG(msg); } } function getStruct(m) { let msg = '{"type":"decrypted","content":{"version":"test","module":"modules","method":"structure","data":{"object":"' + m + '"}}}'; sendMSG(msg); } function loadAssets() { if (current_com === 0) { alert('Please, choose company') } else { let msg = '{"type":"decrypted","content":{"version":"test","module":"modules","method":"load-assets","data":{"cid":' + current_com + ',"page":1}}}'; sendMSG(msg); } } function loadVendors() { if (current_com === 0) { alert('Please, choose company') } else { let msg = '{"type":"decrypted","content":{"version":"test","module":"modules","method":"load-vendors","data":{"cid":' + current_com + ',"page":1}}}'; sendMSG(msg); } } function qbo() { current_method = 'qbo'; let msg = '{"type":"decrypted","content":{"version":"test","module":"qbo","method":"authorization"}}'; sendMSG(msg); } function gci() { if (current_com === 0) { alert('Please, choose company') } else { current_method = 'gci'; let msg = '{"type":"decrypted","content":{"version":"test","module":"callback","method":"company-info","data":{"cid":' + current_com + '}}}'; sendMSG(msg); } } function askAccess() { let em = document.getElementById('owner_email').value; if (em.length > 0) { current_method = 'askAccess'; let msg = '{"type":"decrypted","content":{"version":"test","module":"auth","method":"ask-access","data":{"owner_user":"' + em + '"}}}'; sendMSG(msg); } else { alert('Please, enter company owner\'s email'); } } function registration_wnd() { document.getElementById('main_wnd').innerHTML = '

Register

' + '
' + ' ' + '
' + ' ' + '
' + ' ' + '
' + ' ' + '
' + ' ' + '
' + ' Authorize'; } function auth_wnd() { document.getElementById('main_wnd').innerHTML = '

Authorize

' + '
' + ' ' + '
' + ' ' + '
' + ' ' + '
' + ' Register'; } /*============================*/ async function enc_dec(action, string) { try { const keyHex = '45e1384336adc74eb4c9770d3dcf99d75cbd8e1cba5135663379db986550191b'; // Конвертируем hex-ключ в бинарный формат const keyBytes = hexToBytes(keyHex); // Импортируем ключ для AES-CBC const key = await crypto.subtle.importKey( 'raw', keyBytes, { name: 'AES-CBC', length: 256 }, false, ['encrypt', 'decrypt'] ); if (action === 'encrypt') { // Генерируем случайный IV (16 байт) const iv = crypto.getRandomValues(new Uint8Array(16)); // Шифруем данные const encoder = new TextEncoder(); const data = encoder.encode(string); const ciphertext = await crypto.subtle.encrypt( { name: 'AES-CBC', iv: iv }, key, data ); // Вычисляем HMAC const hmacKey = await crypto.subtle.importKey( 'raw', keyBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ); const combined = new Uint8Array(iv.length + ciphertext.byteLength); combined.set(iv, 0); combined.set(new Uint8Array(ciphertext), iv.length); const hmac = await crypto.subtle.sign('HMAC', hmacKey, combined); // Объединяем: IV + HMAC + Ciphertext const result = new Uint8Array( iv.length + hmac.byteLength + ciphertext.byteLength ); result.set(iv, 0); result.set(new Uint8Array(hmac), iv.length); result.set(new Uint8Array(ciphertext), iv.length + hmac.byteLength); // Возвращаем base64 return arrayBufferToBase64(result); } else if (action === 'decrypt') { // Декодируем base64 const data = base64ToArrayBuffer(string); // Извлекаем компоненты const iv = data.slice(0, 16); const hmac = data.slice(16, 48); const ciphertext = data.slice(48); // Проверяем HMAC const hmacKey = await crypto.subtle.importKey( 'raw', keyBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify'] ); const combined = new Uint8Array(iv.length + ciphertext.length); combined.set(iv, 0); combined.set(ciphertext, iv.length); const isValid = await crypto.subtle.verify( 'HMAC', hmacKey, hmac, combined ); if (!isValid) { throw new Error('HMAC verification failed'); } // Дешифруем const decrypted = await crypto.subtle.decrypt( { name: 'AES-CBC', iv: iv }, key, ciphertext ); // Конвертируем в строку const decoder = new TextDecoder(); return decoder.decode(decrypted); } return false; } catch (error) { console.error('Encryption/Decryption error:', error); return false; } } // Вспомогательные функции function hexToBytes(hex) { const bytes = new Uint8Array(hex.length / 2); for (let i = 0; i < hex.length; i += 2) { bytes[i / 2] = parseInt(hex.substr(i, 2), 16); } return bytes; } function arrayBufferToBase64(buffer) { let binary = ''; const bytes = new Uint8Array(buffer); for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } function base64ToArrayBuffer(base64) { const binary = atob(base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes; } ws();