From 511f0fcca63ac02e5a755a76309ede494a34ae2a Mon Sep 17 00:00:00 2001 From: Paul Taylor <60293767+Y0ursTruly@users.noreply.github.com> Date: Sat, 27 Jul 2024 16:51:16 -0400 Subject: [PATCH 1/3] Update serial.js --- serial.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/serial.js b/serial.js index 9619e9b..1b8d0da 100644 --- a/serial.js +++ b/serial.js @@ -141,7 +141,7 @@ } continue; } - let Path=[...PATH,key], path=data[1]=2? [data[1],key]: Path; + let Path=[...PATH,key], path=data[2]>0&&(data[1]=2)? [data[1],key]: Path; let notSame=!same( obj[key],clone[key] ), temp=map.get(item) if((typeof obj[key]!=="object"||!obj[key]) && (typeof clone[key]==="object"&&clone[key])) @@ -301,4 +301,4 @@ -})() \ No newline at end of file +})() From 4cfcf88711542ba9070d86a9a07fae2836d1b2e3 Mon Sep 17 00:00:00 2001 From: Paul Taylor <60293767+Y0ursTruly@users.noreply.github.com> Date: Sat, 27 Jul 2024 17:55:57 -0400 Subject: [PATCH 2/3] Update serial.js against some intentionally odd edge cases --- serial.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/serial.js b/serial.js index 1b8d0da..7eb32fe 100644 --- a/serial.js +++ b/serial.js @@ -2,10 +2,7 @@ - //every index in an objToString used to be {path,value,reference,delete} - //now it's [[indicator,...path],otherArgument] - //for otherArgument: 0 means delete(no otherArgument), 1 means reference, no indicator means value - const {Object,JSON,WeakMap,ReferenceError,TypeError,RangeError}=global + const {Object,JSON,WeakMap,ReferenceError,TypeError}=global const {keys,getOwnPropertyDescriptor:describe}=Object const {stringify:str,parse}=JSON, CACHE=new WeakMap() @@ -13,6 +10,7 @@ //see if an enumerable property(of key) exists(in obj) function includes(obj,key){ + if(!obj) return false; if( (obj instanceof Array) && (key==="length") ) return true; let existing=describe(obj,key) return existing?existing.enumerable:false @@ -82,12 +80,12 @@ } if(typeof item!=="object") return item; //numbers, strings //also functions but these get ignored either way - let shell=item instanceof Array? ([]): ({}), keys=Object.keys(item); - for(let i=0;i Date: Sat, 27 Jul 2024 22:28:45 +0000 Subject: [PATCH 3/3] serialisation based crash testing applied --- README.md | 2 +- for_browser.js | 19 +++++++++++-------- for_browser.min.js | 2 +- package-lock.json | 8 ++++++-- package.json | 2 +- tests.js | 21 +++++++++++++++++++++ 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 715c80e..f05ffa4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Share (and sync) Objects Online with the power of websockets. Keys, Values AND R
**Please note**: - To view example usage of the modules this library provides, please refer to the _[tests](https://github.com/Y0ursTruly/webject/blob/main/tests.js)_ -- TypeScript Definitions Added >:D +- Supporting the `Date` type soon. In the meantime it is advised to `Date` instances into their string or number equivalent # Installation Multiple ways diff --git a/for_browser.js b/for_browser.js index 1f8c9d4..4b3fabb 100644 --- a/for_browser.js +++ b/for_browser.js @@ -13,6 +13,7 @@ //see if an enumerable property(of key) exists(in obj) function includes(obj,key){ + if(!obj) return false; if( (obj instanceof Array) && (key==="length") ) return true; let existing=describe(obj,key) return existing?existing.enumerable:false @@ -80,12 +81,12 @@ } if(typeof item!=="object") return item; //numbers, strings //also functions but these get ignored either way - let shell=item instanceof Array? ([]): ({}), keys=Object.keys(item); - for(let i=0;i{const{Object:t,JSON:e,WeakMap:n,ReferenceError:r,TypeError:o,RangeError:i}=window,{keys:l,getOwnPropertyDescriptor:u}=t,{stringify:a,parse:c}=e,f=new n;function s(t,e){if(t instanceof Array&&"length"===e)return!0;let n=u(t,e);return!!n&&n.enumerable}function y(t,e){if(t===e)return!0;if("symbol"==typeof t&&"symbol"==typeof e)return t.description===e.description;let n=typeof t==typeof e,r=t instanceof Array==e instanceof Array,o=(t?t[Symbol.toStringTag]:null)===(e?e[Symbol.toStringTag]:null),i=n&&r&&o&&"object"==typeof t;return i&&t[Symbol.toStringTag]?.includes("Array")?t.length===e.length:i}function g(t,e,n=0){if(!t||!t.length)return e;var o=e,i=0;for(i=0;i128)throw new i("Given object goes too many levels inward (>128)");let f=l(t),g=l(e),p=n.get(t);c&&a.set(t,!0),t instanceof Array&&(f.push("length"),g.push("length"));for(let i=0;i=2?[p[1],l]:g,S=!y(t[l],e[l]),A=n.get(c);"object"==typeof t[l]&&t[l]||"object"!=typeof e[l]||!e[l]||w(n,e[l]);let m=!s(e,l)||S;if(!m&&A&&(A[2]=0),m)if((S||void 0===c)&&(e[l]=A?A[3]:d(c,!0)),A){A[4]++;let[t,e,n]=A;n?t=e:(A[2]=2,A[1]=r.length),r.push([h,t,n])}else if(null===c||void 0!==c&&!c[Symbol.toStringTag])r.push([h,d(c)]);else{c&&"bigint"!=typeof c&&a.set(c,!0);let t=c||"bigint"==typeof c?c[Symbol.toStringTag]:"undefined";r.push([h,d(c),0,t])}("symbol"==typeof c||"object"==typeof c&&null!==c)&&(A||(n.set(c,[g,r.length-1,m?1:0,e[l],1]),n.set(e[l],c)),a.get(c)||(a.set(c,!0),b(t[l],e[l],n,r,g,u+1,a)))}}const S={__proto__:null,1:function(){return!1},2:function(t,e){if(1===t.length)return!1;try{return g(t[0],e),!1}catch{return!0}},3:function(){return!0}};let A=[];for(let t=0;t<256;t++)A[t]=String.fromCharCode(t);window.objToString=function(t,e){if(Array.isArray(t)||"object"!=typeof t||null===t)throw new o("root element of data MUST be an OBJECT");if(e)var r=d(t,!0);else var{clone:r,map:i}=f.get(t)||{};const l=[[[],i?{}:d(t)]],u=[],c=t&&t[Symbol.toStringTag]||null;if(p[c]&&l[0].push(0,c),!i){r=d(t,!0);var i=new n;f.set(t,{clone:r,map:i})}return i.set(t,[u,0,1,r,1]),b(t,r,i,l,u,1,new n,!0),a(l)},window.stringToObj=function(e,n,r=3){if(1===r||"1"===r)return n;let o="function"==typeof r?r:S[r];var i=c("string"==typeof e?e:function(t){let e=new Uint8Array(t),n="";for(let t=0;t0?i[f][s-1]:f,n);else{let t=g(c,n,1);y(t[h],l[1])||(t[h]=p[l[3]](l[1]))}}return n},window.objValueFrom=g,window.partFilter=function(t,e=!1){Array.isArray(t)||(t=[t]);for(let e=0;e{var t=objToString({});WebSocket.prototype.on=WebSocket.prototype.addEventListener;let e=new Map,n=(t,e)=>crypto.getRandomValues(new Uint32Array(1))[0]%(t-e)+e;var r="abcdefgjiklmnopqrstuvwxyz-_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ :;.,\\/\"'?!(){}[]@<>=+*#$&`|~^%".split("");function o(t,o){"string"!=typeof o&&(o="webject_");do{var i="",l=n(2*t,t);for(let t=0;t:|");if(!0===u&&(u=t=>e(n,r,i,l,u)),"function"!=typeof u&&u)throw new Error("If you choose the optional parameter onFail, it must be a function >:|");if(l&&"object"==typeof l&&("function"!=typeof l.encoder||"function"!=typeof l.decoder))throw new TypeError("If coding parameter is used, it MUST be an object with both 'encoder' and 'decoder' functions");let a=null,c=null,f=null,s=null,y=!1,g=new WebSocket(n),p=new Promise(((t,e)=>(a=t,c=e)));function h(t,e){if(y)return null;let n=t,r=null;isNaN(n)&&(n=t.code),r=1006==n?"closed ABNORMALLY: either you or the server just LOST connection :|":1002==n?"authToken ENCODING FAULT: incorrect encoding/faulty protocol used, according to the server":1001==n?"authToken LOCKED: this is a correct key, but it takes no new connections 0_0":"closed PURPOSEFULLY: check your location and token parameters, OR you got BOOTED :/";let o=`connection with server is OVER due to event: ${e}\n${r}`;a?c(o):console.error(o),clearInterval(f),clearInterval(s),u&&(a?u().then(a):u())}return g.onerror=t=>{console.error("Attempting to connect to a websocket using the location parameter produced the following error :/\n~",t.message),u&&(a?u().then(a):u())},g.on("open",(async()=>{g.send(JSON.stringify(l?[r,await l.encoder(JSON.stringify([o(32,""),Date.now()]))]:r));let e=!1,n=Date.now();async function u(){let e=objToString(i);e!==t&&g.send(l?await l.encoder(e):e)}s=setInterval((function(){Date.now()-n>32768&&(y=!0,h(1006,"Connection Broken"))}),5e3),g.on("message",(async function(t){if(void 0!==t.data&&(t=t.data),"PING"==t)return n=Date.now();i=stringToObj(l?await l.decoder(t):t,i),e||(objToString(i),f=setInterval(u,20),e=!0,a(i))})),g.on("disconnect",(t=>h(t,"disconnect"))),g.on("close",(t=>h(t,"close"))),g.on("error",(t=>h(t,"error")))})),await p}})(); \ No newline at end of file +(()=>{const{Object:t,JSON:e,WeakMap:n,ReferenceError:r,TypeError:o,RangeError:i}=window,{keys:l,getOwnPropertyDescriptor:u}=t,{stringify:f,parse:a}=e,c=new n;function s(t,e){if(!t)return!1;if(t instanceof Array&&"length"===e)return!0;let n=u(t,e);return!!n&&n.enumerable}function y(t,e){if(t===e)return!0;if("symbol"==typeof t&&"symbol"==typeof e)return t.description===e.description;let n=typeof t==typeof e,r=t instanceof Array==e instanceof Array,o=(t?t[Symbol.toStringTag]:null)===(e?e[Symbol.toStringTag]:null),i=n&&r&&o&&"object"==typeof t;return i&&t[Symbol.toStringTag]?.includes("Array")?t.length===e.length:i}function g(t,e,n=0){if(!t||!t.length)return e;var o=e,i=0;for(i=0;i128)throw new i("Given object goes too many levels inward (>128)");let c=l(t),g=l(e),p=n.get(t);a&&f.set(t,!0),t instanceof Array&&(c.push("length"),g.push("length"));for(let i=0;i=2?[p[1],l]:g,S=!y(t[l],e[l]),A=n.get(a);"object"==typeof t[l]&&t[l]||"object"!=typeof e[l]||!e[l]||w(n,e[l]);let m=!s(e,l)||S;if(!m&&A&&(A[2]=0),m)if((S||void 0===a)&&(e[l]=A?A[3]:d(a,!0)),A){A[4]++;let[t,e,n]=A;n?t=e:(A[2]=2,A[1]=r.length),r.push([h,t,n])}else if(null===a||void 0!==a&&!a[Symbol.toStringTag])r.push([h,d(a)]);else{a&&"bigint"!=typeof a&&f.set(a,!0);let t=a||"bigint"==typeof a?a[Symbol.toStringTag]:"undefined";r.push([h,d(a),0,t])}if("symbol"==typeof a||"object"==typeof a&&null!==a){if(!A){const t=n.get(e[l]);t&&n.delete(t),n.set(a,[g,r.length-1,m?1:0,e[l],1]),n.set(e[l],a)}f.get(a)||(f.set(a,!0),b(t[l],e[l],n,r,g,u+1,f))}}}const S={__proto__:null,1:function(){return!1},2:function(t,e){if(1===t.length)return!1;try{return g(t[0],e),!1}catch{return!0}},3:function(){return!0}};let A=[];for(let t=0;t<256;t++)A[t]=String.fromCharCode(t);window.objToString=function(t,e){if(Array.isArray(t)||"object"!=typeof t||null===t)throw new o("root element of data MUST be an OBJECT");if(e)var r=d(t,!0);else var{clone:r,map:i}=c.get(t)||{};const l=[[[],i?{}:d(t)]],u=[],a=t&&t[Symbol.toStringTag]||null;if(p[a]&&l[0].push(0,a),!i){r=d(t,!0);var i=new n;c.set(t,{clone:r,map:i})}return i.set(t,[u,0,1,r,1]),b(t,r,i,l,u,1,new n,!0),f(l)},window.stringToObj=function(e,n,r=3){if(1===r||"1"===r)return n;let o="function"==typeof r?r:S[r];var i=a("string"==typeof e?e:function(t){let e=new Uint8Array(t),n="";for(let t=0;t0?i[c][s-1]:c,n);else{let t=g(a,n,1);y(t[h],l[1])||(t[h]=p[l[3]](l[1]))}}return n},window.objValueFrom=g,window.partFilter=function(t,e=!1){Array.isArray(t)||(t=[t]);for(let e=0;e{var t=objToString({});WebSocket.prototype.on=WebSocket.prototype.addEventListener;let e=new Map,n=(t,e)=>crypto.getRandomValues(new Uint32Array(1))[0]%(t-e)+e;var r="abcdefgjiklmnopqrstuvwxyz-_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ :;.,\\/\"'?!(){}[]@<>=+*#$&`|~^%".split("");function o(t,o){"string"!=typeof o&&(o="webject_");do{var i="",l=n(2*t,t);for(let t=0;t:|");if(!0===u&&(u=t=>e(n,r,i,l,u)),"function"!=typeof u&&u)throw new Error("If you choose the optional parameter onFail, it must be a function >:|");if(l&&"object"==typeof l&&("function"!=typeof l.encoder||"function"!=typeof l.decoder))throw new TypeError("If coding parameter is used, it MUST be an object with both 'encoder' and 'decoder' functions");let f=null,a=null,c=null,s=null,y=!1,g=new WebSocket(n),p=new Promise(((t,e)=>(f=t,a=e)));function h(t,e){if(y)return null;let n=t,r=null;isNaN(n)&&(n=t.code),r=1006==n?"closed ABNORMALLY: either you or the server just LOST connection :|":1002==n?"authToken ENCODING FAULT: incorrect encoding/faulty protocol used, according to the server":1001==n?"authToken LOCKED: this is a correct key, but it takes no new connections 0_0":"closed PURPOSEFULLY: check your location and token parameters, OR you got BOOTED :/";let o=`connection with server is OVER due to event: ${e}\n${r}`;f?a(o):console.error(o),clearInterval(c),clearInterval(s),u&&(f?u().then(f):u())}return g.onerror=t=>{console.error("Attempting to connect to a websocket using the location parameter produced the following error :/\n~",t.message),u&&(f?u().then(f):u())},g.on("open",(async()=>{g.send(JSON.stringify(l?[r,await l.encoder(JSON.stringify([o(32,""),Date.now()]))]:r));let e=!1,n=Date.now();async function u(){let e=objToString(i);e!==t&&g.send(l?await l.encoder(e):e)}s=setInterval((function(){Date.now()-n>32768&&(y=!0,h(1006,"Connection Broken"))}),5e3),g.on("message",(async function(t){if(void 0!==t.data&&(t=t.data),"PING"==t)return n=Date.now();i=stringToObj(l?await l.decoder(t):t,i),e||(objToString(i),c=setInterval(u,20),e=!0,f(i))})),g.on("disconnect",(t=>h(t,"disconnect"))),g.on("close",(t=>h(t,"close"))),g.on("error",(t=>h(t,"error")))})),await p}})(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ea59e14..580087e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,19 @@ { "name": "webject", - "version": "1.4.7", + "version": "1.4.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "webject", - "version": "1.4.7", + "version": "1.4.8", "license": "Apache-2.0", "dependencies": { "ws": "^8.17.1" + }, + "engines": { + "node": ">= 16.20.2", + "npm": ">= 8.19.4" } }, "node_modules/ws": { diff --git a/package.json b/package.json index f438fad..3f208cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webject", - "version": "1.4.7", + "version": "1.4.8", "description": "Share Objects Online with the power of websockets. Keys, Values AND references. Webject is short for Web Object and it really is a system for sharing objects on the web. Someone can host an object, and make authTokens for others online to share this object", "main": "webject.js", "scripts": { diff --git a/tests.js b/tests.js index fb52a21..36b5793 100644 --- a/tests.js +++ b/tests.js @@ -74,6 +74,27 @@ const {serve, connect, sync, desync, objToString, stringToObj, partFilter, objVa assert.strictEqual(testObj.a.b.f,undefined) assert.strictEqual(testObj.a.b.c,3) }) + await t.test("Serialisation Crash Testing",async function(){ + const date=new Date(), obj={events:[{date,arr:['value1']}]} + const clone=stringToObj(objToString(obj)), {events}=obj + const standard={"events":[{"date":date-0,"arr":["value1","value2","value2","value2","value2","value2"]}]} + let update=_=>stringToObj(objToString(obj),clone); + for(let i=0;i<5;i++){ + obj.events[0].date-=0; + update() + obj.events=[{date}]; + update() + obj.events[0].date-=0; + update() + obj.events=null; + update() + events[0].arr.push("value"+(events.length+1)); + obj.events=events; + update() //the call that crashes it + } + assert.deepStrictEqual(obj,clone) + assert.deepStrictEqual(obj,standard) + }) }) //third set of tests