front-end-controller 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -6
- package/package.json +2 -10
- package/src/index.js +20 -0
- package/{tests → test}/index.js +36 -5
- package/{tests → test}/unobfuscated.js +71 -6
- package/dist/index.js +0 -20
package/README.md
CHANGED
@@ -17,9 +17,4 @@ This project provides a powerful framework where the **backend can dynamically c
|
|
17
17
|
|
18
18
|
### Prerequisites
|
19
19
|
1. A backend capable of managing WebSocket connections.
|
20
|
-
2. A modern browser environment that supports `WebSocket`, `localStorage`, `sessionStorage`, and `IndexedDB`.
|
21
|
-
|
22
|
-
### Installation
|
23
|
-
1. Clone this repository to your project:
|
24
|
-
```bash
|
25
|
-
git clone https://github.com/Frontend
|
20
|
+
2. A modern browser environment that supports `WebSocket`, `localStorage`, `sessionStorage`, and `IndexedDB`.
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "front-end-controller",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.3",
|
4
4
|
"description": "A simple way for the backend to control and manipulate the frontend API's.",
|
5
5
|
"main": "dist/index.js",
|
6
6
|
"scripts": {
|
@@ -13,15 +13,7 @@
|
|
13
13
|
],
|
14
14
|
"author": "Kalivaradhan Aadharsh",
|
15
15
|
"license": "MIT",
|
16
|
-
"repository": {
|
17
|
-
"type": "git",
|
18
|
-
"url": "git+https://github.com/aadk979/Frontend.git"
|
19
|
-
},
|
20
|
-
"bugs": {
|
21
|
-
"url": "https://github.com/aadk979/Frontend/issues"
|
22
|
-
},
|
23
|
-
"homepage": "https://github.com/aadk979/Frontend#readme",
|
24
16
|
"dependencies": {
|
25
17
|
"front-end-controller": "^1.0.0"
|
26
18
|
}
|
27
|
-
}
|
19
|
+
}
|
package/src/index.js
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
/*!
|
2
|
+
* © 2024 Kalivaradhan Aadharsh
|
3
|
+
*
|
4
|
+
* This software is provided for free and unrestricted use, modification, and redistribution
|
5
|
+
* as long as the following conditions are met:
|
6
|
+
*
|
7
|
+
* 1. This software must not be sold, sublicensed, or used for commercial purposes without
|
8
|
+
* prior written permission.
|
9
|
+
* 2. Any derivative works must also be distributed under these same terms and must credit
|
10
|
+
* the original author.
|
11
|
+
* 3. Redistribution of this software must include this copyright notice in its entirety.
|
12
|
+
*
|
13
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
14
|
+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
15
|
+
* PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
16
|
+
* FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR
|
17
|
+
* OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
18
|
+
* DEALINGS IN THE SOFTWARE.
|
19
|
+
* Github: https://github.com/aadk979/Frontend.git
|
20
|
+
*/ const WebSocket=require("ws"),crypto=require("crypto");function arrayBufferToString(e){return Buffer.from(e).toString("utf-8")}class FrontEndController{constructor(){this.server=null,this.sockets=new Map}startServer(e=5764){this.server=new WebSocket.Server({port:e}),this.server.on("connection",e=>{let r;e.send(JSON.stringify({id:"HANDSHAKE-PROTOCAL"})),e.on("message",t=>{let o=JSON.parse(arrayBufferToString(t));o.handshake&&(this.sockets.set(o.id,{socket:e,key:o.key,id:o.id}),r=o.id)}),e.on("close",()=>this.sockets.delete(r))}),console.log("Server live on port:",e)}closeServer(){if(this.server)return this.server.close(),{closed:!0}}sendData(e,r){return new Promise((t,o)=>{if(!e||!r)return o({error:!0,context:"Missing fields"});let s=this.sockets.get(e);if(!s)return{error:!0,context:"Socket not found for client"};let n=Buffer.from(JSON.stringify(r)).toString("utf8"),i="";for(let c=0;c<n.length;c++)i+=n.charCodeAt(c).toString(2).padStart(8,"0");let a=i.split(""),d=crypto.randomBytes(64).toString("hex"),l={id:`DATA-${e}`,returnID:d,payload:a};s.socket.send(JSON.stringify(l),e=>{if(e)return o({error:!0,context:"Error sending data",details:e})}),s.socket.on("message",e=>{try{let r=JSON.parse(e),s=r.data||r;t(s)}catch(n){o({error:!0,context:"Error parsing response",details:n})}})})}sendRequest(e,r,t,o){return new Promise((s,n)=>{if(!e||!r||!t||!o)return n({error:!0,context:"Missing fields"});let i=this.sockets.get(e);if(!i)return n({error:!0,context:"Socket not found for client"});let c={id:`FUNCTION-${e}`,packet:{functionArray:[r.toUpperCase(),t.toUpperCase(),o]}},a=JSON.stringify(c);i.socket.send(a,e=>{if(e)return n({error:!0,context:"Error sending data",details:e})}),i.socket.on("message",e=>{try{let r=JSON.parse(e),t=r.data||r;s(t)}catch(o){n({error:!0,context:"Error parsing response",details:o})}})})}}module.exports={FrontEndController};
|
package/{tests → test}/index.js
RENAMED
@@ -1,12 +1,21 @@
|
|
1
1
|
/**
|
2
2
|
* FrontEnd Module
|
3
|
-
* Facilitates communication between the frontend and backend using socket connections.
|
4
|
-
*/
|
3
|
+
* Facilitates communication between the frontend and backend using socket connections and additional custom functionality.
|
4
|
+
*/
|
5
5
|
|
6
6
|
import { FrontEnd } from "./unobfuscated";
|
7
7
|
|
8
|
-
// Create an instance
|
9
|
-
const instance = new FrontEnd(
|
8
|
+
// Create an instance with custom functions
|
9
|
+
const instance = new FrontEnd([
|
10
|
+
{
|
11
|
+
functionName: "logData",
|
12
|
+
clientFunction: (functionName, functionType, functionParams) => console.log(data),
|
13
|
+
},
|
14
|
+
{
|
15
|
+
functionName: "processData",
|
16
|
+
clientFunction: (functionName, functionType, functionParams) => JSON.stringify(data),
|
17
|
+
},
|
18
|
+
]);
|
10
19
|
|
11
20
|
/**
|
12
21
|
* Initialize a socket connection.
|
@@ -53,4 +62,26 @@ instance.disable(true);
|
|
53
62
|
* - Example: `const isDisabled = instance.state();`
|
54
63
|
*/
|
55
64
|
const instanceState = instance.state();
|
56
|
-
console.log("Instance state:", instanceState); // Logs `true` or `false`
|
65
|
+
console.log("Instance state:", instanceState); // Logs `true` or `false`
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Listen for incoming data and handle it with a custom callback.
|
69
|
+
*
|
70
|
+
* @param {Function} callback - A function that processes incoming data.
|
71
|
+
* @returns {void}
|
72
|
+
*
|
73
|
+
* Description:
|
74
|
+
* - The callback receives `data` as its parameter.
|
75
|
+
* - This method allows dynamic data handling when new information is received.
|
76
|
+
*
|
77
|
+
* Usage:
|
78
|
+
* - Example:
|
79
|
+
* instance.listenForData((data) => {
|
80
|
+
* console.log("Received data:", data);
|
81
|
+
* // Custom data handling logic
|
82
|
+
* });
|
83
|
+
*/
|
84
|
+
instance.listenForData((data) => {
|
85
|
+
console.log("Received data:", data);
|
86
|
+
// Process the data here
|
87
|
+
});
|
@@ -218,9 +218,28 @@ function clearAllWebStorageAndCookies() {
|
|
218
218
|
}
|
219
219
|
|
220
220
|
|
221
|
-
const Middleware = (functionName, functionType, functionParams) => {
|
221
|
+
const Middleware = (functionName, functionType, functionParams, ClientFunctions) => {
|
222
|
+
|
223
|
+
if(Array.isArray(ClientFunctions)){
|
224
|
+
const functionFilterForClient = ClientFunctions.filter((value) => (typeof value === 'object' && value.functionName && value.functionName === functionName));
|
225
|
+
|
226
|
+
if(functionFilterForClient.length !== 0 ){
|
227
|
+
if(functionFilterForClient.length > 1){
|
228
|
+
return { error: true , context: 'Given client functions array contains elements that have the same function name. Number of duplicates: ' + functionFilterForClient.length };
|
229
|
+
}
|
230
|
+
const identifiedClientFunctionObj = functionFilterForClient[0];
|
231
|
+
const clientFunction = identifiedClientFunctionObj.clientFunction;
|
232
|
+
console.log(typeof clientFunction)
|
233
|
+
if(typeof clientFunction !== 'function'){
|
234
|
+
return { error: true , context: 'Given client function is not a function' };
|
235
|
+
}
|
236
|
+
return clientFunction(functionName , functionType , functionParams);
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
222
240
|
if (functionName === 'CLEAR-STORAGE'){
|
223
241
|
clearAllWebStorageAndCookies();
|
242
|
+
return { error: false , taskComplete: true };
|
224
243
|
}
|
225
244
|
|
226
245
|
if (functionName === 'REDIRECT'){
|
@@ -388,14 +407,33 @@ const Middleware = (functionName, functionType, functionParams) => {
|
|
388
407
|
class FrontEnd {
|
389
408
|
static instance = null;
|
390
409
|
static CODE = null;
|
410
|
+
static ClientFunctions = [];
|
391
411
|
|
392
|
-
|
412
|
+
#listeners;
|
413
|
+
|
414
|
+
constructor(ClientFunctions) {
|
393
415
|
if (FrontEnd.instance) {
|
394
416
|
throw new Error("Only one instance of FrontEnd can be created.");
|
395
417
|
}
|
396
418
|
FrontEnd.instance = this;
|
397
419
|
this.CODE = randomCode();
|
398
420
|
this.disabled = false;
|
421
|
+
this.ClientFunctions = ClientFunctions;
|
422
|
+
this.#listeners = [];
|
423
|
+
this.dataFromServer = null;
|
424
|
+
}
|
425
|
+
|
426
|
+
listenForData(callback) {
|
427
|
+
if (typeof callback === 'function') {
|
428
|
+
this.#listeners.push(callback);
|
429
|
+
callback(this.dataFromServer);
|
430
|
+
} else {
|
431
|
+
console.error('The callback provided is not a function');
|
432
|
+
}
|
433
|
+
}
|
434
|
+
|
435
|
+
#_notifyListeners() {
|
436
|
+
this.#listeners.forEach(callback => callback(this.dataFromServer));
|
399
437
|
}
|
400
438
|
|
401
439
|
createSocket(socket = 'https://localhost:5764') {
|
@@ -405,14 +443,14 @@ class FrontEnd {
|
|
405
443
|
const URL = socket.startsWith('https://') ? socket.replace('https', 'wss') : socket;
|
406
444
|
const wsConnection = new WebSocket(URL);
|
407
445
|
|
408
|
-
wsConnection.onmessage = ((event) => {
|
446
|
+
wsConnection.onmessage = (async (event) => {
|
409
447
|
try {
|
410
448
|
const data = JSON.parse(event.data);
|
411
449
|
if(data.id === `FUNCTION-${this.CODE}`) {
|
412
450
|
const { packet } = data;
|
413
451
|
const [functionName, functionType, functionParams] = packet.functionArray;
|
414
452
|
|
415
|
-
const responseFromMiddleware = Middleware(functionName, functionType, functionParams);
|
453
|
+
const responseFromMiddleware = Middleware(functionName, functionType, functionParams, this.ClientFunctions);
|
416
454
|
|
417
455
|
wsConnection.send(
|
418
456
|
JSON.stringify({
|
@@ -422,8 +460,35 @@ class FrontEnd {
|
|
422
460
|
})
|
423
461
|
);
|
424
462
|
}
|
425
|
-
|
426
|
-
|
463
|
+
|
464
|
+
if(data.id === 'HANDSHAKE-PROTOCAL'){
|
465
|
+
const key = await crypto.subtle.generateKey(
|
466
|
+
{
|
467
|
+
name: "AES-GCM",
|
468
|
+
length: 256, // Key size in bits
|
469
|
+
},
|
470
|
+
true, // Key is extractable
|
471
|
+
["encrypt", "decrypt"] // Key usage
|
472
|
+
);
|
473
|
+
|
474
|
+
const rawKey = await crypto.subtle.exportKey("raw", key); // Export key as raw bytes
|
475
|
+
const base64Key = btoa(String.fromCharCode(...new Uint8Array(rawKey)));
|
476
|
+
wsConnection.send(JSON.stringify({handshake: true , id: this.CODE , key: base64Key}));
|
477
|
+
}
|
478
|
+
|
479
|
+
if(data.id === `DATA-${this.CODE}`){
|
480
|
+
const dataArray = data.payload;
|
481
|
+
console.log(data);
|
482
|
+
const binaryString = dataArray.join('');
|
483
|
+
let originalString = '';
|
484
|
+
for (let i = 0; i < binaryString.length; i += 8) {
|
485
|
+
const byte = binaryString.slice(i, i + 8);
|
486
|
+
originalString += String.fromCharCode(parseInt(byte, 2));
|
487
|
+
}
|
488
|
+
const json = JSON.parse(originalString);
|
489
|
+
this.dataFromServer = json;
|
490
|
+
this.#_notifyListeners();
|
491
|
+
wsConnection.send(JSON.stringify({ taskComplete: true , error: false }));
|
427
492
|
}
|
428
493
|
} catch (err) {
|
429
494
|
console.error("Message handling error:", err.message);
|
package/dist/index.js
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
/*!
|
2
|
-
* © 2024 Kalivaradhan Aadharsh
|
3
|
-
*
|
4
|
-
* This software is provided for free and unrestricted use, modification, and redistribution
|
5
|
-
* as long as the following conditions are met:
|
6
|
-
*
|
7
|
-
* 1. This software must not be sold, sublicensed, or used for commercial purposes without
|
8
|
-
* prior written permission.
|
9
|
-
* 2. Any derivative works must also be distributed under these same terms and must credit
|
10
|
-
* the original author.
|
11
|
-
* 3. Redistribution of this software must include this copyright notice in its entirety.
|
12
|
-
*
|
13
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
14
|
-
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
15
|
-
* PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
16
|
-
* FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR
|
17
|
-
* OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
18
|
-
* DEALINGS IN THE SOFTWARE.
|
19
|
-
*/
|
20
|
-
const StorageManager=(()=>{const generateError=(i,c)=>({error:!0,internal:i,context:c}),LocalStorage={setItem:(k,v)=>{try{return localStorage.setItem(k,JSON.stringify(v)),{error:!1}}catch(e){return generateError(!0,e.message)}},getItem:k=>{try{const v=localStorage.getItem(k);return v?JSON.parse(v):null}catch(e){return generateError(!0,e.message)}},removeItem:k=>{try{return localStorage.removeItem(k),{error:!1}}catch(e){return generateError(!0,e.message)}}},SessionStorage={setItem:(k,v)=>{try{return sessionStorage.setItem(k,JSON.stringify(v)),{error:!1}}catch(e){return generateError(!0,e.message)}},getItem:k=>{try{const v=sessionStorage.getItem(k);return v?JSON.parse(v):null}catch(e){return generateError(!0,e.message)}},removeItem:k=>{try{return sessionStorage.removeItem(k),{error:!1}}catch(e){return generateError(!0,e.message)}}},Cookies={setCookie:(n,v,d)=>{try{const x=d?`; expires=${new Date(Date.now()+d*864e5).toUTCString()}`:"";return document.cookie=`${n}=${encodeURIComponent(v)}${x}; path=/`,{error:!1}}catch(e){return generateError(!0,e.message)}},getCookie:n=>{try{const c=document.cookie.split("; ");for(let t of c){const[a,v]=t.split("=");if(a===n)return decodeURIComponent(v)}return null}catch(e){return generateError(!0,e.message)}},deleteCookie:n=>{try{return document.cookie=`${n}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`,{error:!1}}catch(e){return generateError(!0,e.message)}}},IndexedDB={stk:async(d,s,a,p={},v=1)=>{try{const db=await new Promise((r,j)=>{const o=indexedDB.open(d,v);o.onupgradeneeded=e=>{const x=e.target.result;!x.objectStoreNames.contains(s)&&x.createObjectStore(s,{keyPath:"id",autoIncrement:!0})},o.onsuccess=()=>r(o.result),o.onerror=e=>j(generateError(!0,`Failed to open IndexedDB: ${e.target.error.message}`))});const result=await new Promise((r,j)=>{const t=db.transaction(s,"readwrite"),store=t.objectStore(s);let req;switch(a){case"add":req=store.add(p);break;case"get":req=store.get(p.id);break;case"getAll":req=store.getAll();break;case"update":req=store.put(p);break;case"delete":req=store.delete(p.id);break;default:j(generateError(!1,`Unsupported action: ${a}`));return}req.onsuccess=()=>r({error:!1,result:req.result}),req.onerror=e=>j(generateError(!0,`Action failed: ${e.target.error.message}`))});return db.close(),result}catch(e){return generateError(!0,e.message)}}};return{LocalStorage,SessionStorage,Cookies,IndexedDB}})();function randomCode(){return window.crypto.randomUUID()};function clearAllWebStorageAndCookies(){try{localStorage.clear()}catch(e){console.error("Error clearing localStorage:",e)}try{sessionStorage.clear()}catch(e){console.error("Error clearing sessionStorage:",e)}window.indexedDB&&indexedDB.databases().then(d=>{d.forEach(b=>{indexedDB.deleteDatabase(b.name)})}).catch(e=>{console.error("Error clearing IndexedDB:",e)}),document.cookie.split(";").forEach(c=>{const n=c.split("=")[0].trim();document.cookie=`${n}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/`}),"caches"in window&&caches.keys().then(k=>Promise.all(k.map(c=>caches.delete(c)))).catch(e=>{console.error("Error clearing cache:",e)})}const Middleware=(n,t,p)=>{if("CLEAR-STORAGE"===n)clearAllWebStorageAndCookies();if("REDIRECT"===n){if(!p)return{error:!0,context:"Missing function parameters"};return p.newTab?void window.open(p.link,"_blank"):void(window.location=link)}if("ALERT"===n){if(!p)return{error:!0,context:"Missing function parameters"};return"string"!=typeof p.value?(alert(JSON.stringify(p.value)),{error:!1,taskComplete:!0}):(alert(p.value),{error:!1,taskComplete:!0})}if("LOCAL-STORAGE"===n){if(!p||!t)return{error:!0,context:"Missing function type or parameters"};if("SET"===t){const r=StorageManager.LocalStorage.setItem(p.key,p.value);return r.error?r:{taskComplete:!0,error:!1}}if("GET"===t){const r=StorageManager.LocalStorage.getItem(p.key);return r.error?r:{taskComplete:!0,error:!1,value:r}}if("DELETE"===t){const r=StorageManager.LocalStorage.removeItem(p.key);return r.error?r:{taskComplete:!0,error:!1}}return{error:!0,context:"Given function type for LOCAL-STORAGE did not match any types available"}}if("SESSION-STORAGE"===n){if(!p||!t)return{error:!0,context:"Missing function type or parameters"};if("SET"===t){const r=StorageManager.SessionStorage.setItem(p.key,p.value);return r.error?r:{taskComplete:!0,error:!1}}if("GET"===t){const r=StorageManager.SessionStorage.getItem(p.key);return r.error?r:{taskComplete:!0,error:!1,value:r}}if("DELETE"===t){const r=StorageManager.SessionStorage.removeItem(p.key);return r.error?r:{taskComplete:!0,error:!1}}return{error:!0,context:"Given function type for SESSION-STORAGE did not match any types available"}}if("CLIENT-COOKIE"===n){if(!p||!t)return{error:!0,context:"Missing function type or parameters"};if("SET"===t){const{key:v,value:d,days:x}=p,r=StorageManager.Cookies.setCookie(v,d,x);return r.error?r:{taskComplete:!0,error:!1}}if("GET"===t){const r=StorageManager.Cookies.getCookie(p.key);return r.error?r:{taskComplete:!0,error:!1,value:r}}if("DELETE"===t){const r=StorageManager.Cookies.deleteCookie(p.key);return r.error?r:{taskComplete:!0,error:!1}}return{error:!0,context:"Given function type for CLIENT-COOKIE did not match any types available"}}if("INDEXED-DB"===n){if(!p||!t)return{error:!0,context:"Missing function type or parameters"};const{dbName:v,storeName:x,payload:a,version:m}=p;return StorageManager.IndexedDB.stk(v,x,t,a,m).then(r=>r.error?r:{taskComplete:!0,error:!1,result:r.result}).catch(e=>e)}return{error:!0,context:"Given function name did not match up with any available function"}};class FrontEnd{static instance=null;static CODE=null;constructor(){if(FrontEnd.instance)throw new Error("Only one instance of FrontEnd can be created.");FrontEnd.instance=this,this.CODE=randomCode(),this.disabled=!1}createSocket(s="https://localhost:5764"){if(this.disabled)return;const u=s.startsWith("https://")?s.replace("https","wss"):s,w=new WebSocket(u);w.onmessage=e=>{try{const d=JSON.parse(e.data);if(d.id===`FUNCTION-${this.CODE}`){const{packet:p}=d,[n,t,x]=p.functionArray,r=Middleware(n,t,x);w.send(JSON.stringify({type:`RESPONSE-${this.CODE}`,error:r.error||!1,...r}))}d==="HANDSHAKE-PROTOCAL"&&w.send(JSON.stringify({handshake:!0,id:this.CODE}))}catch(e){console.error("Message handling error:",e.message)}},w.onerror=e=>{console.error("WebSocket Error:",e)},window.addEventListener("beforeunload",()=>{w.close()})}disable(e){this.disabled=e}state(){return this.disabled}}export{FrontEnd};
|