aotrautils 0.0.1803 → 0.0.1806

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.
Files changed (2) hide show
  1. aotrautils/aotrautils.build.js +1496 -1574
  2. aotrautils/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- /*utils COMMONS library associated with aotra version : «1_29072022-2359 (11/11/2025-18:19:16)»*/
3
+ /*utils COMMONS library associated with aotra version : «1_29072022-2359 (01/03/2026-01:46:57)»*/
4
4
  /*-----------------------------------------------------------------------------*/
5
5
 
6
6
 
@@ -945,11 +945,8 @@ aotest.run=function(testName=null,scenarioName=null){
945
945
  }
946
946
  });
947
947
 
948
-
949
948
  }
950
949
 
951
-
952
-
953
950
  let currentExecutedTest=window.aotestAllTestsManager[chosenTestParam.name];
954
951
  var functionDefinition=currentExecutedTest.functionDefinition;
955
952
 
@@ -1068,9 +1065,6 @@ aotest.run=function(testName=null,scenarioName=null){
1068
1065
  }
1069
1066
  }
1070
1067
 
1071
-
1072
-
1073
-
1074
1068
  // *****************************************
1075
1069
  // We check function/methods calls here :
1076
1070
  // (overrides all other previous test result calculations)
@@ -1133,7 +1127,6 @@ aotest.run=function(testName=null,scenarioName=null){
1133
1127
  //
1134
1128
  // }
1135
1129
 
1136
-
1137
1130
  // Execution measurment end :
1138
1131
  scenarioResultObjLocal.global.executionEndTime=getNow();
1139
1132
 
@@ -1178,23 +1171,18 @@ aotest.run=function(testName=null,scenarioName=null){
1178
1171
  }
1179
1172
  }
1180
1173
 
1181
-
1182
1174
  // There can be no other case if we are in UI tests case.
1183
1175
  }
1184
1176
 
1185
-
1186
-
1187
1177
  // States comparison test handling :
1188
1178
  // (overrides all other previous test result calculations)
1189
1179
  if( // (to avoid calculating more if test has already failed :)
1190
1180
  !scenarioResultObjLocal.global.isFailed
1191
1181
  && !isChosenScenarioParamArray
1192
1182
  && chosenScenarioParam.after){
1193
-
1194
1183
 
1195
1184
  let beforeResultFlat=currentExecutedTest.oldResultFlat; // expected result
1196
1185
 
1197
-
1198
1186
  // At this point, we already have applied the method to the input object, in order to compare it with the output object, if necessary.
1199
1187
  let actualInstanceFlat=(actualInstance!=null?getAsFlatStructure(actualInstance):null); // actual instance (after execution)
1200
1188
  let actualArgsFlat=(functionParametersPopulated!=null?getAsFlatStructure(functionParametersPopulated):null); // actual arguments (after execution)
@@ -1246,17 +1234,13 @@ aotest.run=function(testName=null,scenarioName=null){
1246
1234
 
1247
1235
  }
1248
1236
 
1249
-
1250
-
1251
1237
  // DBG
1252
1238
  console.log("³³³³³³ areEquivalentInstances",areEquivalentInstances);
1253
1239
  console.log("³³³³³³ areEquivalentArgs",areEquivalentArgs);
1254
1240
  console.log("³³³³³³ areEquivalentResults",areEquivalentResults);
1255
1241
 
1256
-
1257
1242
  }
1258
1243
 
1259
-
1260
1244
  // We check sub-scenarii here :
1261
1245
  var subScenariiList=null;
1262
1246
  if(!isChosenScenarioParamArray)
@@ -1624,12 +1608,31 @@ aotest.profile=function(rootObject,methodName,visited=[]){
1624
1608
 
1625
1609
  window.sleep = (millis) => new Promise( resolve => setTimeout(resolve, millis) );
1626
1610
 
1611
+ window.PeriodicalExecuter=class PeriodicalExecuter{
1612
+ constructor(callback, refreshRateMillis=1000, instance){
1613
+ this.callback=callback;
1614
+ this.refreshRateMillis=refreshRateMillis;
1615
+ this.instance=instance;
1616
+ const self=this;
1617
+ this.intervalId=setInterval(()=>self.callback.apply(self.instance), this.refreshRateMillis);
1618
+ }
1619
+ stop(){
1620
+ clearInterval(this.intervalId);
1621
+ }
1622
+ restart(){
1623
+ const self=this;
1624
+ this.intervalId=setInterval(()=>self.callback.apply(self.instance), this.refreshRateMillis);
1625
+ }
1626
+ }
1627
1627
 
1628
1628
 
1629
1629
  //================================================================
1630
1630
  //================= Cryptography utility methods =================
1631
1631
  //================================================================
1632
1632
 
1633
+ // Node dependencies :
1634
+ if(typeof(require)!="undefined" && typeof(sjcl)=="undefined" ) sjcl = require("sjcl");
1635
+
1633
1636
 
1634
1637
  // NOT AOTESTABLE !
1635
1638
  //TODO : develop :
@@ -1977,37 +1980,41 @@ window.nothing=function(nullable, insist=false){
1977
1980
  return false;
1978
1981
  }
1979
1982
 
1980
- window.getOrCreateSimpleObjectAttribute=function(parentObject, attributeNameOrAttributesName){
1983
+ window.getOrCreateEmptyAttribute=function(parentObject, attributeNameOrAttributesNames,
1984
+ // BECAUSE CAUTION : if we have defaultValue as parameter, sometimes because of the recursive call, it can refer to a persisting object ! Thus, creating a strange bug.
1985
+ // (SO WE ALWAYS NEED TO START AS A BLANK STATE FOR DEFAULT VALUE !)
1986
+ forceArrayAttributeDefaultValue=false){
1981
1987
  if(nothing(parentObject)) return null;
1982
- if(nothing(attributeNameOrAttributesName)) return parentObject;
1983
- if(isString(attributeNameOrAttributesName) || (isArray(attributeNameOrAttributesName) && attributeNameOrAttributesName.length===1)){
1984
- const attributeName=(isArray(attributeNameOrAttributesName) && attributeNameOrAttributesName.length===1)?
1985
- attributeNameOrAttributesName[0]:attributeNameOrAttributesName;
1988
+ if(nothing(attributeNameOrAttributesNames)) return parentObject;
1989
+ if(isString(attributeNameOrAttributesNames) || (isArray(attributeNameOrAttributesNames) && attributeNameOrAttributesNames.length===1)){
1990
+ const attributeName=(isArray(attributeNameOrAttributesNames) && attributeNameOrAttributesNames.length===1)?
1991
+ attributeNameOrAttributesNames[0]:attributeNameOrAttributesNames;
1986
1992
  if(parentObject[attributeName]==null){
1987
1993
  // NO : parentObject[attributeName]=defaultValue;
1988
1994
  // BECAUSE CAUTION : if we have defaultValue as parameter, sometimes because of the recursive call, it can refer to a persisting object ! Thus, creating a strange bug.
1989
- // (SO WE ALWAYS NEED TO START AS A BLANK STATE FOR DEFAULT VALUE OBJECTS !)
1990
- const DEFAULT_VALUE={};
1995
+ // (SO WE ALWAYS NEED TO START AS A BLANK STATE FOR DEFAULT VALUE !)
1996
+ const DEFAULT_VALUE=(forceArrayAttributeDefaultValue?[]:{});
1991
1997
  parentObject[attributeName]=DEFAULT_VALUE;
1992
1998
  }
1993
1999
  let foundOrCreatedChild=parentObject[attributeName];
1994
2000
  return foundOrCreatedChild;
1995
2001
  }
1996
- if(isArray(attributeNameOrAttributesName)){
2002
+ if(isArray(attributeNameOrAttributesNames)){
1997
2003
 
1998
- const attributeName=attributeNameOrAttributesName[0];
2004
+ const attributeName=attributeNameOrAttributesNames[0];
1999
2005
  if(parentObject[attributeName]==null){
2000
2006
  // NO : parentObject[attributeName]=defaultValue;
2001
2007
  // BECAUSE CAUTION : if we have defaultValue as parameter, sometimes because of the recursive call, it can refer to a persisting object ! Thus, creating a strange bug.
2002
- // (SO WE ALWAYS NEED TO START AS A BLANK STATE FOR DEFAULT VALUE OBJECTS !)
2003
- const DEFAULT_VALUE={};
2008
+ // (SO WE ALWAYS NEED TO START AS A BLANK STATE FOR DEFAULT VALUE !)
2009
+ const DEFAULT_VALUE=(forceArrayAttributeDefaultValue?[]:{});
2004
2010
  parentObject[attributeName]=DEFAULT_VALUE;
2005
2011
  }
2006
- const sliced=attributeNameOrAttributesName.slice(1);
2007
- let foundOrCreatedChild=getOrCreateSimpleObjectAttribute(parentObject[attributeName], sliced);
2012
+ // We remove the just-created child to allow for the other children to be created on the parent object :
2013
+ const slicedAttributesNames=attributeNameOrAttributesNames.slice(1);
2014
+ let foundOrCreatedChild=getOrCreateEmptyAttribute(parentObject, slicedAttributesNames, forceArrayAttributeDefaultValue);
2008
2015
  return foundOrCreatedChild;
2009
2016
  }
2010
- return null; // (case no child found nor could be created.)
2017
+ return null; // (case no child was found or it could be not created.)
2011
2018
  }
2012
2019
 
2013
2020
  window.nonull=function(value,defaultValue){
@@ -2209,6 +2216,14 @@ window.foreach=function(arrayOfValues,doOnEachFunction,
2209
2216
  return null;
2210
2217
  }
2211
2218
 
2219
+ window.findInArray=function(collection, equalsFunction, returnFunction=null){
2220
+ return !!foreach(collection, item=>{
2221
+ if(returnFunction)
2222
+ return returnFunction(item);
2223
+ return item;
2224
+ }, equalsFunction);
2225
+ }
2226
+
2212
2227
  window.getKeyAt=function(associativeOrNormalArray,index){
2213
2228
  return getAt(associativeOrNormalArray,index,true);
2214
2229
  }
@@ -2263,7 +2278,6 @@ window.getNow=function(){
2263
2278
  }
2264
2279
 
2265
2280
 
2266
-
2267
2281
  // UNUSED:
2268
2282
  // CAUTION : this is a costly method...
2269
2283
  window.gotoValueLinearly=function(source, destination, doOnRefreshTreatment,/*OPTIONAL*/onEndMethod,/*OPTIONAL*/refreshingRateMillisParam,/*OPTIONAL*/
@@ -2823,7 +2837,6 @@ window.uncapitalize=function(str){
2823
2837
  window.copy=function(array){
2824
2838
  var result=null;
2825
2839
 
2826
- // if(!isAssociative){
2827
2840
  if(array instanceof Array){
2828
2841
 
2829
2842
  result=new Array();
@@ -2831,7 +2844,6 @@ window.copy=function(array){
2831
2844
  result.push(array[i]);
2832
2845
  } else {
2833
2846
  // Case of regular objects that are like associative arrays :
2834
-
2835
2847
  result=new Object();
2836
2848
  for (key in array){
2837
2849
  if(!array.hasOwnProperty(key))
@@ -2946,10 +2958,15 @@ Math.productInArray=function(arrayOfValues){
2946
2958
  return product;
2947
2959
  };
2948
2960
 
2961
+ Math.average=function(value1,value2){
2962
+ return (value1+value2)*.5;
2963
+ };
2964
+
2965
+
2949
2966
  Math.averageInArray=function(arrayOfValues){
2950
2967
  if(empty(arrayOfValues))
2951
2968
  return null;
2952
- return Math.sumInArray(arrayOfValues) / arrayOfValues.length;
2969
+ return Math.sumInArray(arrayOfValues)/arrayOfValues.length;
2953
2970
  };
2954
2971
 
2955
2972
  Math.maxInArray=function(arrayOfValues, returnKey=false){
@@ -3832,8 +3849,15 @@ window.isFunction=function(functionToCheck){
3832
3849
  window.isPrimitive=function(p){
3833
3850
  return (isNumber(p) || isString(p) || isBoolean(p));
3834
3851
  }
3835
- window.isObject=function(p){
3836
- return !isFunction(p) && !isPrimitive(p);
3852
+ window.isObject=function(o){ // (includes also plain objects)
3853
+ return !isFunction(o) && !isPrimitive(o);
3854
+ }
3855
+ // NO : DOES NOT WORK !!!
3856
+ //window.isPlainObject=function(o){
3857
+ // return Object.prototype.toString.call(o)==="[object Object]";
3858
+ //}
3859
+ window.isClassObject=function(o){
3860
+ return isObject(o) && !isNativeClass(o);
3837
3861
  }
3838
3862
  window.getClassName=function(obj){
3839
3863
  if(!obj) return null;
@@ -4165,8 +4189,12 @@ JSON.parseRecycle=function(str){
4165
4189
 
4166
4190
 
4167
4191
  window.clone=function(obj){
4192
+ // OLD :
4168
4193
  const str=JSON.stringifyDecycle(obj);
4169
4194
  const newObj=JSON.parseRecycle(str);
4195
+ // DOES NOT WORK :
4196
+ //const newObj=deepClone(obj);
4197
+ //const newObj=structuredClone(obj);
4170
4198
  return newObj;
4171
4199
  };
4172
4200
 
@@ -4696,9 +4724,9 @@ window.getAsTreeStructure=function(oldMap, keepClassName=false
4696
4724
  };
4697
4725
 
4698
4726
 
4699
- function restoreGraph(flatData, keepClassName=false, UUID_ATTR_NAME, CLASSNAME_ATTR_NAME, POINTER_TO_ATTR_NAME) {
4727
+ window.restoreGraph=(flatData, keepClassName=false, UUID_ATTR_NAME, CLASSNAME_ATTR_NAME, POINTER_TO_ATTR_NAME)=>{
4700
4728
 
4701
- function revive(id, keepClassName, restoredObjects = {}) {
4729
+ const revive=(id, keepClassName, restoredObjects = {})=>{
4702
4730
 
4703
4731
  if(!id || typeof(flatData[id])==="undefined" || flatData[id]==null) return null;
4704
4732
  const found=restoredObjects[id];
@@ -5044,7 +5072,7 @@ AOTRAUTILS_LIB_IS_LOADED=true;
5044
5072
 
5045
5073
 
5046
5074
 
5047
- /*utils CLIENT library associated with aotra version : «1_29072022-2359 (11/11/2025-18:19:16)»*/
5075
+ /*utils CLIENT library associated with aotra version : «1_29072022-2359 (01/03/2026-01:46:57)»*/
5048
5076
  /*-----------------------------------------------------------------------------*/
5049
5077
  /* ## Utility global methods in a browser (htmljs) client environment.
5050
5078
  *
@@ -5064,8 +5092,6 @@ AOTRAUTILS_LIB_IS_LOADED=true;
5064
5092
  */
5065
5093
 
5066
5094
 
5067
- // Node dependencies :
5068
- if(typeof(require)!="undefined" && typeof(sjcl)=="undefined" ) sjcl = require("sjcl");
5069
5095
 
5070
5096
 
5071
5097
  // COMPATIBILITY browser javascript / nodejs environment :
@@ -6730,6 +6756,91 @@ function htmlColorCodeToDecimalArray(hexColorCodeParam){
6730
6756
  return [ hex2dec(hexColorCode.substring(0, 2)), hex2dec(hexColorCode.substring(2, 4)), hex2dec(hexColorCode.substring(4, 6)) ];
6731
6757
  }
6732
6758
 
6759
+ window.ascii2RGBA=(rgbaWholeString, pixelIndex)=>{
6760
+ const asciiValueRed = rgbaWholeString.charCodeAt(pixelIndex);
6761
+ const r=asciiValueRed;
6762
+ const asciiValueGreen = rgbaWholeString.charCodeAt(pixelIndex+1);
6763
+ const g=asciiValueGreen;
6764
+ const asciiValueBlue = rgbaWholeString.charCodeAt(pixelIndex+2);
6765
+ const b=asciiValueBlue;
6766
+ const asciiValueAlpha = rgbaWholeString.charCodeAt(pixelIndex+3);
6767
+ const a=asciiValueAlpha;
6768
+ return {r:r, g:g, b:b, a:a};
6769
+ }
6770
+
6771
+ window.ascii2RGB=(rgbWholeString, pixelIndex=0)=>{
6772
+ const asciiValueRed = rgbWholeString.charCodeAt(pixelIndex);
6773
+ const r=asciiValueRed;
6774
+ const asciiValueGreen = rgbWholeString.charCodeAt(pixelIndex+1);
6775
+ const g=asciiValueGreen;
6776
+ const asciiValueBlue = rgbWholeString.charCodeAt(pixelIndex+2);
6777
+ const b=asciiValueBlue;
6778
+ return {r:r, g:g, b:b};
6779
+ }
6780
+
6781
+ // UNUSED
6782
+ window.asciiStringToNumber=(str)=>{
6783
+ if(!str || empty(str)){
6784
+ throw new Error("ERROR : String must not be empty. Aborting string conversion to number.");
6785
+ }
6786
+
6787
+ let result=0;
6788
+ let shift=0;
6789
+ for(let i=0;i<str.length;i++){
6790
+ // Get the ASCII (UTF-16 code unit) for each character
6791
+ const c = str.charCodeAt(i);
6792
+ if(255<c){
6793
+ throw new Error("Character «"+c+"» is outside the ASCII range (0-255). Aborting.");
6794
+ }
6795
+ // Combine them into a single number using bitwise shifts
6796
+ // char1 is shifted 16 bits to the left, char2 8 bits, and char3 remains in the lowest 8 bits.
6797
+ result=((c << shift) | result);
6798
+ shift+=8;
6799
+ }
6800
+
6801
+ return result;
6802
+ }
6803
+
6804
+ window.convertBase255NumberRepresentedInUTF16tringToBase10Number=(str)=>{
6805
+ const BASE = 255;
6806
+
6807
+ if(!str || empty(str))
6808
+ return 0;
6809
+
6810
+ let result=0;
6811
+ // We reverse the string digits order :
6812
+ for(let i=str.length-1;0<=i;i--){
6813
+ let charCode=str.charCodeAt(i);
6814
+ // To handle the «modifier letter apostrophe» (UTF8 code 700)) sent as a replacement
6815
+ // for the «single quote» character (ASCII base table code 39) that is invalid in JSON text :
6816
+ if(charCode==700) charCode=39;
6817
+ result+=Math.pow(BASE, str.length-1-i)*charCode;
6818
+ // // DBG
6819
+ // lognow("int:" + str.charCodeAt(i) + ",char:" + str[i]+"|");
6820
+ }
6821
+
6822
+ return result;
6823
+ };
6824
+
6825
+
6826
+ // UNUSED
6827
+ window.numberToAsciiString=(num, originalStringSize)=>{
6828
+ if(!originalStringSize)
6829
+ return "";
6830
+
6831
+ let result="";
6832
+ let shift=(originalStringSize-1)*8;
6833
+ for(let i=0;i<originalStringSize;i++){
6834
+ // Extract characters using bitwise shifts and masking
6835
+ result+=String.fromCharCode((num >> shift) & 0xFF);
6836
+ }
6837
+
6838
+ return result;
6839
+ }
6840
+
6841
+
6842
+
6843
+
6733
6844
 
6734
6845
  /*private*/function componentToHex(c){
6735
6846
  var hex=c.toString(16);
@@ -7987,8 +8098,12 @@ function filterPoints(allPoints ,ctx/*DBG*/){
7987
8098
 
7988
8099
  if(!isUserMediaAvailable()){
7989
8100
  // TRACE
7990
- log("ERROR : User medias is not supported in your browser (or are you running in a non-https context ?). Aborting");
8101
+ log("ERROR : User medias is not supported in your browser, or you are running in a non-https context, or you are running in a nodejs context. Aborting");
7991
8102
  return Promise.resolve();
8103
+ }else{
8104
+ // TODO : FIXME :
8105
+ // Use https://www.npmjs.com/package/node-webcam (based on fswebcam) or https://github.com/yocontra/camera (based on opencv4)
8106
+ // to handle all the «nodejs context running»-like cases
7992
8107
  }
7993
8108
 
7994
8109
  return new Promise((accept,reject)=>{
@@ -8960,8 +9075,7 @@ function loadSoundsLoop(filesPaths,doOnLoaded=null,doOnEnded=null){
8960
9075
  let audios=[];
8961
9076
  let numberLoaded=0;
8962
9077
  foreach(filesPaths,(filePath)=>{
8963
- audios.push( loadSound(filePath,function(selfParam/*UNUSED*/){
8964
-
9078
+ const sound=loadSound2D(filePath,function(selfParam/*UNUSED*/){
8965
9079
  numberLoaded++
8966
9080
  if(doOnLoaded && numberLoaded==filesPaths.length){
8967
9081
 
@@ -8971,10 +9085,9 @@ function loadSoundsLoop(filesPaths,doOnLoaded=null,doOnEnded=null){
8971
9085
  // To prevent starting treatments after the loading has occurred after the loop has been stopped !
8972
9086
  // (rare case when the loading is so long that the user has aborted the loop before all loop files has been loaded !)
8973
9087
  if(!selfSoundsLoop.hasAbortedLooping) doOnLoaded(selfSoundsLoop);
8974
-
8975
9088
  }
8976
-
8977
- }) );
9089
+ });
9090
+ audios.push(sound);
8978
9091
  });
8979
9092
 
8980
9093
  return selfSoundsLoop;
@@ -9190,7 +9303,7 @@ class Sound{
9190
9303
  }
9191
9304
 
9192
9305
 
9193
- function loadSound(filePath,doOnLoaded=null,doOnEnded=null){
9306
+ function loadSound2D(filePath,doOnLoaded=null,doOnEnded=null){
9194
9307
 
9195
9308
  let self=new Sound(filePath,doOnEnded);
9196
9309
 
@@ -11663,19 +11776,19 @@ initClient=function(isNodeContext=true, useSocketIOImplementation=/*DEBUG*/false
11663
11776
  // DBG
11664
11777
  lognow("INFO : Setting up client :...");
11665
11778
 
11666
- let socketToServer=WebsocketImplementation.getStatic(isNodeContext, useSocketIOImplementation).connectToServer(url, port, isSecure, timeout);
11667
- if(!socketToServer){
11779
+ let socketToServerClientInstance=WebsocketImplementation.getStatic(isNodeContext, useSocketIOImplementation).connectToServer(url, port, isSecure, timeout);
11780
+ if(!socketToServerClientInstance){
11668
11781
  // TRACE
11669
- lognow("ERROR : CLIENT : Could not get socketToServer, aborting client connection.");
11782
+ lognow("ERROR : CLIENT : Could not get socketToServerClientInstance, aborting client connection.");
11670
11783
  return null;
11671
11784
  }
11672
11785
 
11673
11786
  // When client is connected, we execute the callback :
11674
11787
  // CAUTION : MUST BE CALLED ONLY ONCE !
11675
- socketToServer.onConnectionToServer(()=>{
11788
+ socketToServerClientInstance.onConnectionToServer(()=>{
11676
11789
  if(doOnServerConnection){
11677
- if(selfParam) doOnServerConnection.apply(selfParam,[socketToServer]);
11678
- else doOnServerConnection(socketToServer);
11790
+ if(selfParam) doOnServerConnection.apply(selfParam,[socketToServerClientInstance]);
11791
+ else doOnServerConnection(socketToServerClientInstance);
11679
11792
  }
11680
11793
 
11681
11794
 
@@ -11696,18 +11809,18 @@ initClient=function(isNodeContext=true, useSocketIOImplementation=/*DEBUG*/false
11696
11809
  // else doOnError();
11697
11810
  // };
11698
11811
  //
11699
- // socketToServer.clientSocket.on("connect_error", errorMethod);
11812
+ // socketToServerClientInstance.clientSocket.on("connect_error", errorMethod);
11700
11813
  //
11701
11814
  //// // DOES NOT SEEM TO WORK :
11702
- //// socketToServer.on("connect_failed", errorMethod);
11815
+ //// socketToServerClientInstance.on("connect_failed", errorMethod);
11703
11816
  //// // DOES NOT SEEM TO WORK :
11704
- //// socketToServer.on("error", errorMethod);
11817
+ //// socketToServerClientInstance.on("error", errorMethod);
11705
11818
  //
11706
11819
  // }
11707
11820
  //
11708
11821
  // // Data messages handling :
11709
11822
  // if(doOnMessage){
11710
- // socketToServer.onIncomingMessage((message)=>{
11823
+ // socketToServerClientInstance.onIncomingMessage((message)=>{
11711
11824
  // if(selfParam) doOnMessage.apply(selfParam,[message]);
11712
11825
  // else doOnMessage(message);
11713
11826
  // });
@@ -11723,7 +11836,7 @@ initClient=function(isNodeContext=true, useSocketIOImplementation=/*DEBUG*/false
11723
11836
  // });
11724
11837
 
11725
11838
 
11726
- aotraClient.client.socketToServer=socketToServer;
11839
+ aotraClient.client.socketToServerClientInstance=socketToServerClientInstance;
11727
11840
 
11728
11841
  return aotraClient;
11729
11842
  };
@@ -11739,20 +11852,18 @@ initClient=function(isNodeContext=true, useSocketIOImplementation=/*DEBUG*/false
11739
11852
 
11740
11853
  class VNCFrame2D{
11741
11854
 
11742
- constructor(url, port=6080, isSecure=true, htmlConfig={parentElement:null,imageMode:"CSSBackground"}, mouseEventsRefreshMillis=100){
11855
+ constructor(url, port=4000, htmlConfig={parentElement:null,imageMode:"CSSBackground"}, mouseEventsRefreshMillis=100){
11743
11856
 
11744
11857
 
11745
11858
  this.url=url;
11746
11859
  this.port=port;
11747
- this.isSecure=isSecure;
11860
+ this.isSecure=(contains(url.toLowerCase(),"wss"));
11748
11861
  this.parentElement=nonull(htmlConfig.parentElement, document.body);
11749
11862
  this.imageMode=htmlConfig.imageMode;
11750
11863
  this.mouseEventsRefreshMillis=mouseEventsRefreshMillis;
11751
11864
 
11752
-
11753
11865
  this.fusrodaClient=null;
11754
11866
 
11755
-
11756
11867
  this.screenAndAudioConfig=null;
11757
11868
  this.image=null;
11758
11869
  self.audioCtx=null;
@@ -11784,7 +11895,6 @@ class VNCFrame2D{
11784
11895
  // lognow("Image finished loading.");
11785
11896
 
11786
11897
  const image=event.target;
11787
- image.isImageLoading=false;
11788
11898
 
11789
11899
  if(!self.canvasSizeHasBeenSet){
11790
11900
  self.canvas.width=image.width;
@@ -11793,25 +11903,25 @@ class VNCFrame2D{
11793
11903
  }
11794
11904
 
11795
11905
  self.canvas.getContext("2d").drawImage(image,0,0);
11906
+ image.isImageLoading=false;
11796
11907
 
11797
- self.fusrodaClient.client.socketToServer.send("screenFrameRequest", {});
11908
+ self.fusrodaClient.client.socketToServerClientInstance.send("screenFrameRequest", {});
11798
11909
 
11799
11910
  });
11800
11911
 
11801
11912
  }
11802
11913
 
11803
-
11804
- self.audioCtx=new AudioContext({sampleRate: self.screenAndAudioConfig.sampleRate});
11914
+ if(self.screenAndAudioConfig.sampleRate)
11915
+ self.audioCtx=new AudioContext({sampleRate: self.screenAndAudioConfig.sampleRate});
11805
11916
 
11806
11917
 
11807
11918
  },// doOnDataReception:
11808
11919
  {
11809
- "image":(imageDataStr)=>{
11810
-
11811
-
11920
+ /*DEPRECATED, NOT HANDLED ANYMORE*/"image64":(imageDataStr)=>{
11812
11921
  const base64String="data:image/png;base64,"+imageDataStr;
11813
11922
  if(self.imageMode=="canvas"){
11814
11923
  if(self.image.complete){
11924
+ if(self.image.isImageLoading) return;
11815
11925
  self.image.src=base64String;
11816
11926
  self.image.isImageLoading=true;
11817
11927
  }
@@ -11819,12 +11929,11 @@ class VNCFrame2D{
11819
11929
  self.parentElement.style["background-image"]="url('"+base64String+"');";
11820
11930
  }
11821
11931
 
11822
-
11823
11932
  },
11824
11933
  "sound":(soundDataStr)=>{
11825
11934
  const soundData=getDecodedArrayFromSoundDataString(soundDataStr);
11826
11935
  playAudioData(soundData,self.audioCtx,self.screenAndAudioConfig.audioBufferSize,1,self.screenAndAudioConfig.sampleRate,()=>{
11827
- self.fusrodaClient.client.socketToServer.send("soundSampleRequest", {});
11936
+ self.fusrodaClient.client.socketToServerClientInstance.send("soundSampleRequest", {});
11828
11937
  });
11829
11938
  }
11830
11939
  }, this.url, this.port, this.isSecure);
@@ -11937,7 +12046,27 @@ class VNCFrame2D{
11937
12046
  }
11938
12047
 
11939
12048
 
11940
-
12049
+ window.base64ToUint8ClampedArray=(base64String)=>{
12050
+ const binaryString=window.atob(base64String);
12051
+
12052
+ const pixelsNumber=(binaryString.length);
12053
+ let bytes=new Uint8ClampedArray(pixelsNumber*4);
12054
+ for (let i=0; i<pixelsNumber; i++) {
12055
+
12056
+ let p=binaryString.charCodeAt(i);
12057
+ let a=(p>>24) & 0xff;
12058
+ let r=(p>>16) & 0xff;
12059
+ let g=(p>>8) & 0xff;
12060
+ let b=(p) & 0xff;
12061
+
12062
+ bytes[i]=r;
12063
+ bytes[i+1]=g;
12064
+ bytes[i+2]=b;
12065
+ bytes[i+3]=a;
12066
+ }
12067
+
12068
+ return bytes;
12069
+ };
11941
12070
 
11942
12071
 
11943
12072
  // ============================================== BROWSER CLIENTS ==============================================
@@ -11945,67 +12074,66 @@ class VNCFrame2D{
11945
12074
 
11946
12075
  // FUSRODA :
11947
12076
 
11948
- createFusrodaClient=function(doOnClientReady, doOnDataReception, urlParam=null, portParam=6080){
12077
+ createFusrodaClient=function(doOnClientReady, doOnDataReception, urlParam=null, portParam=4000){
11949
12078
  // CAUTION : WORKS BETTER WHEN UNSECURE, BUT 'NEEDS CLIENT BROWSER TO ALLOW MIXED (SECURE/UNSECURE) CONTENT !
11950
12079
 
11951
- const isSecure=(contains(urlParam.toLowerCase(),"https") || contains(urlParam.toLowerCase(),"wss"));
11952
12080
 
12081
+ const isSecure=(contains(urlParam.toLowerCase(),"https") || contains(urlParam.toLowerCase(),"wss"));
11953
12082
 
11954
- const fusrodaClient=initClient(false,/*CAUTION : Fusroda Java server requires the SOcket IO websocket implementation !!*/true, /*doOnServerConnection*/(socketToServer)=>{
12083
+ const fusrodaClient=initClient(false,
12084
+ /*CAUTION : Fusroda Java server requires the Socket IO websocket implementation !!*/true,
12085
+ /*doOnServerConnection:*/(socketToServerClientInstance)=>{
11955
12086
 
11956
12087
  // DBG
11957
12088
  lognow("FUSRODA CLIENT : SETTING UP...");
11958
12089
 
11959
-
11960
12090
  //0)
11961
- socketToServer.receive("protocolConfig", (messageConfig)=>{
12091
+ socketToServerClientInstance.receive("protocolConfig", (messageConfig)=>{
11962
12092
 
11963
12093
  // DBG
11964
12094
  lognow("(CLIENT) (DEBUG) Received protocol config from server : messageConfig:",messageConfig);
11965
12095
 
11966
- //2)
11967
- socketToServer.receive("imagePacket", (doOnDataReception && doOnDataReception["image"]?doOnDataReception["image"]:()=>{/*DO NOTHING*/}));
11968
-
11969
- // //DBG
11970
- // lognow("Initial image request.");
11971
-
11972
12096
  // Initial frame request sending :
11973
- socketToServer.send("screenFrameRequest", {});
11974
-
11975
- //2)
11976
- socketToServer.receive("soundPacket", (doOnDataReception && doOnDataReception["sound"]?doOnDataReception["sound"]:()=>{/*DO NOTHING*/}));
12097
+ socketToServerClientInstance.send("screenFrameRequest", {});
11977
12098
  // Initial sound sample request sending :
11978
- socketToServer.send("soundSampleRequest", {});
11979
-
12099
+ socketToServerClientInstance.send("soundSampleRequest", {});
11980
12100
 
11981
12101
  doOnClientReady(messageConfig);
11982
12102
  });
12103
+
12104
+ //2)
12105
+ /*DEPRECATED, NOT HANDLED ANYMORE*/socketToServerClientInstance.receive("image64Packet", (doOnDataReception && doOnDataReception["image64"]?doOnDataReception["image64"]:()=>{/*DO NOTHING*/}));
12106
+ socketToServerClientInstance.receive("imagePacket", (doOnDataReception && doOnDataReception["image"]?doOnDataReception["image"]:()=>{/*DO NOTHING*/}));
12107
+ socketToServerClientInstance.receive("imageDiffPacket", (doOnDataReception && doOnDataReception["imageDiff"]?doOnDataReception["imageDiff"]:()=>{/*DO NOTHING*/}));
12108
+ socketToServerClientInstance.receive("soundPacket", (doOnDataReception && doOnDataReception["sound"]?doOnDataReception["sound"]:()=>{/*DO NOTHING*/}));
12109
+
12110
+ // DBG
12111
+ lognow("(CLIENT) SENDING «hello» TO SERVER...");
11983
12112
 
11984
12113
  //1)
11985
- socketToServer.send("protocol", { type:"hello" });
12114
+ socketToServerClientInstance.send("protocol", { type:"hello" });
11986
12115
 
11987
12116
 
11988
12117
  }, urlParam, portParam, isSecure);
11989
12118
 
11990
12119
  fusrodaClient.sendMouseMoveEvent=(mouseEvent)=>{
11991
- fusrodaClient.client.socketToServer.send("mouseMoveEvent", mouseEvent);
12120
+ fusrodaClient.client.socketToServerClientInstance.send("mouseMoveEvent", mouseEvent);
11992
12121
  };
11993
-
11994
12122
  fusrodaClient.sendMouseClickEvent=(mouseEvent)=>{
11995
- fusrodaClient.client.socketToServer.send("mouseClickEvent", mouseEvent);
12123
+ fusrodaClient.client.socketToServerClientInstance.send("mouseClickEvent", mouseEvent);
11996
12124
  };
11997
12125
  fusrodaClient.sendMouseReleasedEvent=(mouseEvent)=>{
11998
- fusrodaClient.client.socketToServer.send("mouseReleasedEvent", mouseEvent);
12126
+ fusrodaClient.client.socketToServerClientInstance.send("mouseReleasedEvent", mouseEvent);
11999
12127
  };
12000
12128
  fusrodaClient.sendWheelEvent=(wheelEvent)=>{
12001
- fusrodaClient.client.socketToServer.send("wheelEvent", wheelEvent);
12129
+ fusrodaClient.client.socketToServerClientInstance.send("wheelEvent", wheelEvent);
12002
12130
  };
12003
12131
 
12004
12132
  fusrodaClient.sendKeyboardPressedEvent=(keyboardEvent)=>{
12005
- fusrodaClient.client.socketToServer.send("keyboardPressedEvent", keyboardEvent);
12133
+ fusrodaClient.client.socketToServerClientInstance.send("keyboardPressedEvent", keyboardEvent);
12006
12134
  };
12007
12135
  fusrodaClient.sendKeyboardReleasedEvent=(keyboardEvent)=>{
12008
- fusrodaClient.client.socketToServer.send("keyboardReleasedEvent", keyboardEvent);
12136
+ fusrodaClient.client.socketToServerClientInstance.send("keyboardReleasedEvent", keyboardEvent);
12009
12137
  };
12010
12138
 
12011
12139
 
@@ -12189,7 +12317,7 @@ window.createOritaMainClient=function(
12189
12317
 
12190
12318
 
12191
12319
 
12192
- const oritaClient=initClient(false, false, /*doOnServerConnection*/(socketToServer)=>{
12320
+ const oritaClient=initClient(false, false, /*doOnServerConnection*/(socketToServerClientInstance)=>{
12193
12321
 
12194
12322
  // DBG
12195
12323
  lognow("ORITA MAIN CLIENT : SETTING UP");
@@ -12216,7 +12344,7 @@ window.createOritaMainClient=function(
12216
12344
  storeString(ORITA_HASH_STRING_NAME, calculatedHash);
12217
12345
 
12218
12346
  // TODO : FIXME : DUPLICATED CODE :
12219
- socketToServer.send("protocol", "request.register:mainClient:"+calculatedHash);
12347
+ socketToServerClientInstance.send("protocol", "request.register:mainClient:"+calculatedHash);
12220
12348
 
12221
12349
  },()=>{
12222
12350
  // TRACE
@@ -12225,7 +12353,7 @@ window.createOritaMainClient=function(
12225
12353
 
12226
12354
  }else{
12227
12355
  // TODO : FIXME : DUPLICATED CODE :
12228
- socketToServer.send("protocol", "request.register:mainClient:"+storedHashKey);
12356
+ socketToServerClientInstance.send("protocol", "request.register:mainClient:"+storedHashKey);
12229
12357
  }
12230
12358
 
12231
12359
 
@@ -12242,7 +12370,7 @@ window.createOritaMainClient=function(
12242
12370
 
12243
12371
 
12244
12372
 
12245
- oritaClient.client.socketToServer.receive("protocol", (message)=>{
12373
+ oritaClient.client.socketToServerClientInstance.receive("protocol", (message)=>{
12246
12374
 
12247
12375
  // TRACE
12248
12376
  lognow("Mainclient received message from server:",message);
@@ -12260,7 +12388,7 @@ window.createOritaMainClient=function(
12260
12388
 
12261
12389
 
12262
12390
  // Main client starts receiving :
12263
- oritaClient.client.socketToServer.receive("server.send.data", (receivedData)=>{
12391
+ oritaClient.client.socketToServerClientInstance.receive("server.send.data", (receivedData)=>{
12264
12392
 
12265
12393
 
12266
12394
  let microClientId=receivedData.microClientId;
@@ -12343,7 +12471,7 @@ window.createOritaMainClient=function(
12343
12471
 
12344
12472
 
12345
12473
 
12346
- oritaClient.client.socketToServer.receive("communication", (message)=>{
12474
+ oritaClient.client.socketToServerClientInstance.receive("communication", (message)=>{
12347
12475
 
12348
12476
  // DBG
12349
12477
  lognow("MAIN CLIENT RECEIVED COMMUNICATION :",message);
@@ -12581,7 +12709,7 @@ const HALF_STEP_STEPPER_SEQUENCE = [
12581
12709
 
12582
12710
 
12583
12711
 
12584
- createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdParam=null){
12712
+ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdParam=null, videoDataHook=null){
12585
12713
 
12586
12714
 
12587
12715
  let audioBufferSize = ORITA_CONSTANTS.AUDIO_BUFFER_SIZE;
@@ -12593,6 +12721,8 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12593
12721
  const oritaClient={
12594
12722
 
12595
12723
  staticMicroClientId:staticMicroClientId,
12724
+ videoDataHook:videoDataHook,
12725
+ // audioDataHook:audioDataHook,// TODO !
12596
12726
 
12597
12727
  onRegistrationEventListeners:[],
12598
12728
  onCommunicationEventListeners:[],
@@ -12663,10 +12793,8 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12663
12793
  // TODO : FIXME : Utiliser initClient(...) au lieu de directement getStatic(...) avec le paramètre isNode transmis dans l'appel)
12664
12794
  //oritaClient=initClient(isNode,false,doOnServerConnection=null, url, port);
12665
12795
  oritaClient.client={};
12666
- oritaClient.client.socketToServer = WebsocketImplementation.getStatic(isNode).connectToServer(url, port);
12667
- oritaClient.client.socketToServer.onConnectionToServer(() => {
12668
-
12669
-
12796
+ oritaClient.client.socketToServerClientInstance=WebsocketImplementation.getStatic(isNode).connectToServer(url, port);
12797
+ oritaClient.client.socketToServerClientInstance.onConnectionToServer(() => {
12670
12798
 
12671
12799
 
12672
12800
  // TRACE
@@ -12682,17 +12810,13 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12682
12810
  lognow("INFO : (CLIENT) Microclient will now register to server with microClientId «" + microClientId + "».");
12683
12811
 
12684
12812
 
12685
- oritaClient.client.socketToServer.send("protocol", "request.register:microClient:" + microClientId);
12686
-
12687
-
12688
-
12813
+ oritaClient.client.socketToServerClientInstance.send("protocol", "request.register:microClient:" + microClientId);
12689
12814
 
12690
12815
 
12691
12816
 
12692
-
12693
12817
  // ------------------------------ CONNECTION EVENTS REGISTRATION ------------------------------
12694
12818
 
12695
- oritaClient.client.socketToServer.receive("protocol", (message) => {
12819
+ oritaClient.client.socketToServerClientInstance.receive("protocol", (message) => {
12696
12820
 
12697
12821
  // TRACE
12698
12822
  lognow("INFO : MICRO CLIENT : Server sent a message on the protocol channel : message:", stringifyObject(message));
@@ -12741,41 +12865,54 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12741
12865
  // console.log("INFO : MICRO CLIENT : videoData : ",videoData);
12742
12866
 
12743
12867
  if (videoData && videoData.data) {
12744
-
12745
- let videoRawData = videoData.data;
12746
-
12747
-
12748
- if (videoData.format !== "base64" && videoData.messWithAlpha) {
12749
- videoRawData = removeAlpha(videoRawData);
12868
+
12869
+ if(oritaClient.videoDataHook && oritaClient.videoDataHook.doVideoAnalysis){
12870
+ data.videoAnalysis={
12871
+ treatmentName:oritaClient.videoDataHook.doVideoAnalysis.treatmentName,
12872
+ treatmentResult:(oritaClient.videoDataHook.doVideoAnalysis.doTreatment?
12873
+ oritaClient.videoDataHook.doVideoAnalysis.doTreatment(videoData)
12874
+ :null),
12875
+ };
12876
+ }
12877
+
12878
+
12879
+ if(!oritaClient.videoDataHook || (oritaClient.videoDataHook && oritaClient.videoDataHook.doNotSendToServer!=true)){
12880
+
12881
+ let videoRawData = videoData.data;
12882
+
12883
+ if (videoData.format !== "base64" && videoData.messWithAlpha) {
12884
+ videoRawData = removeAlpha(videoRawData);
12885
+ }
12886
+
12887
+ // // TRACE
12888
+ // lognow("CLIENT : getting to send : videoRawData.length:"+videoRawData.length);
12889
+
12890
+ // Actually, only handles "LZW" compression, for now...
12891
+
12892
+ let stringToSend;
12893
+ if (ORITA_CONSTANTS.STRINGIFY_VIDEO_DATA) stringToSend = arrayToString(videoRawData, false, 1);
12894
+ else stringToSend = videoRawData;
12895
+
12896
+ // We set the Side is «left» or «right»
12897
+ if (videoSide) data.videoSide = videoSide;
12898
+
12899
+
12900
+ data.video = {
12901
+ width: videoData.width,
12902
+ height: videoData.height,
12903
+ data: stringToSend,
12904
+ messWithAlpha: videoData.messWithAlpha,
12905
+ format: videoData.format,
12906
+ };
12907
+ data.compression = {
12908
+ precision: 1,
12909
+ //algorithm:"LZW"
12910
+ };
12911
+
12750
12912
  }
12751
-
12752
- // We set the Side is «left» or «right»
12753
- if (videoSide) data.videoSide = videoSide;
12754
-
12755
- // // TRACE
12756
- // lognow("CLIENT : getting to send : videoRawData.length:"+videoRawData.length);
12757
-
12758
- // Actually, only handles "LZW" compression, for now...
12759
-
12760
- let stringToSend;
12761
- if (ORITA_CONSTANTS.STRINGIFY_VIDEO_DATA) stringToSend = arrayToString(videoRawData, false, 1);
12762
- else stringToSend = videoRawData;
12763
-
12764
-
12765
-
12766
- data.video = {
12767
- width: videoData.width,
12768
- height: videoData.height,
12769
- data: stringToSend,
12770
- messWithAlpha: videoData.messWithAlpha,
12771
- format: videoData.format,
12772
- };
12773
- data.compression = {
12774
- precision: 1,
12775
- //algorithm:"LZW"
12776
- };
12777
12913
 
12778
12914
  }
12915
+
12779
12916
 
12780
12917
  } else if (medias === "audio") {
12781
12918
 
@@ -12803,6 +12940,7 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12803
12940
  return;
12804
12941
  }
12805
12942
 
12943
+
12806
12944
 
12807
12945
  // // TRACE
12808
12946
  // lognow("CLIENT : SENDING DATA TO SERVER :");
@@ -12813,12 +12951,11 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12813
12951
  data.microClientId = oritaClient.microClientId;
12814
12952
 
12815
12953
  // Micro client starts sending :
12816
- oritaClient.client.socketToServer.send("microClient.send.data", data);
12954
+ oritaClient.client.socketToServerClientInstance.send("microClient.send.data", data);
12817
12955
 
12818
12956
 
12819
12957
 
12820
- }
12821
- , refreshingRateMillis); // SENDING LOOP
12958
+ }, refreshingRateMillis); // SENDING LOOP
12822
12959
  // *************************
12823
12960
  }
12824
12961
 
@@ -12836,7 +12973,7 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12836
12973
 
12837
12974
  });
12838
12975
 
12839
- oritaClient.client.socketToServer.receive("communication", (message) => {
12976
+ oritaClient.client.socketToServerClientInstance.receive("communication", (message) => {
12840
12977
  // We execute eventual on message events listeners :
12841
12978
  foreach(oritaClient.onCommunicationEventListeners, (e) => {
12842
12979
  if (!e.condition || e.condition(message)) e.execute(message);
@@ -12902,7 +13039,6 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12902
13039
  } else { // Case node micro client :
12903
13040
 
12904
13041
 
12905
-
12906
13042
  // DBG
12907
13043
  console.log("mediaParameters", mediaParameters);
12908
13044
  console.log("medias", medias);
@@ -12992,7 +13128,7 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12992
13128
  // DBG
12993
13129
  lognow("SIGNALING INPUTS");
12994
13130
 
12995
- oritaClient.client.socketToServer.send("communication", {
13131
+ oritaClient.client.socketToServerClientInstance.send("communication", {
12996
13132
  type: "request.microClient.appendInputs",
12997
13133
  inputsGPIO: oritaClient.inputsGPIO,
12998
13134
  microClientId: oritaClient.microClientId,
@@ -13004,7 +13140,7 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
13004
13140
  // DBG
13005
13141
  lognow("SIGNALING OUTPUTS");
13006
13142
 
13007
- oritaClient.client.socketToServer.send("communication", {
13143
+ oritaClient.client.socketToServerClientInstance.send("communication", {
13008
13144
  type: "request.microClient.appendOutputs",
13009
13145
  outputsGPIO: oritaClient.outputsGPIO,
13010
13146
  microClientId: oritaClient.microClientId,
@@ -13055,7 +13191,7 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
13055
13191
  // 1-
13056
13192
  oritaClient.onRegistrationEventListeners.push({
13057
13193
  execute: (microClientId, message) => {
13058
- oritaClient.client.socketToServer.send("communication", {
13194
+ oritaClient.client.socketToServerClientInstance.send("communication", {
13059
13195
  type: "request.microClient.appendInputs",
13060
13196
  inputsGPIO: oritaClient.inputsGPIO,
13061
13197
  microClientId: oritaClient.microClientId,
@@ -13073,7 +13209,7 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
13073
13209
  });
13074
13210
 
13075
13211
 
13076
- oritaClient.client.socketToServer.send("communication", {
13212
+ oritaClient.client.socketToServerClientInstance.send("communication", {
13077
13213
  type: "request.microClient.updateInputsValues",
13078
13214
  inputsGPIO: inputsGPIO,
13079
13215
  microClientId: oritaClient.microClientId,
@@ -13260,7 +13396,7 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
13260
13396
  // 1-
13261
13397
  oritaClient.onRegistrationEventListeners.push({
13262
13398
  execute: (microClientId, message) => {
13263
- oritaClient.client.socketToServer.send("communication", {
13399
+ oritaClient.client.socketToServerClientInstance.send("communication", {
13264
13400
  type: "request.microClient.appendOutputs",
13265
13401
  outputsGPIO: oritaClient.outputsGPIO,
13266
13402
  microClientId: oritaClient.microClientId,
@@ -13354,7 +13490,7 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
13354
13490
  // });
13355
13491
 
13356
13492
  // 4-
13357
- oritaClient.client.socketToServer.send("communication", {
13493
+ oritaClient.client.socketToServerClientInstance.send("communication", {
13358
13494
  type: "response.microClient.outputChanged",
13359
13495
  outputsGPIO: oritaClient.outputsGPIO,
13360
13496
  });
@@ -13390,7 +13526,7 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
13390
13526
  oritaClient.sendCommand = (commandName, commandParameters = null) => {
13391
13527
  const microClientId = oritaClient.microClientId;
13392
13528
  const command = { microClientId: microClientId, type: "microClient.send.command", name: commandName, parameters: commandParameters };
13393
- oritaClient.client.socketToServer.send("communication", command);
13529
+ oritaClient.client.socketToServerClientInstance.send("communication", command);
13394
13530
  return oritaClient;
13395
13531
  };
13396
13532
 
@@ -13401,253 +13537,10 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
13401
13537
 
13402
13538
 
13403
13539
 
13404
- //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
13405
-
13406
-
13407
-
13408
-
13409
- class AORTACClient{
13410
-
13411
- constructor(clientId, serverNodeOrigin, model, view, isNodeContext=true, sslConfig={/*OPTIONAL*/certPath:null,/*OPTIONAL*/keyPath:null}){
13412
- this.clientId=clientId;
13413
- this.serverNodeOrigin=serverNodeOrigin;
13414
- this.isNodeContext=isNodeContext;
13415
- this.sslConfig=sslConfig;
13416
-
13417
- this.model=model; // must implement functions
13418
- this.view=view; // Must implement functions
13419
- this.view.setClient(this);
13420
-
13421
- this.clientInstanceToReferenceServerNode=null;
13422
- this.currentServersNodes={};
13423
-
13424
-
13425
- // TRACE
13426
- lognow(`AORTAC client created with id ${this.clientId}.`);
13427
-
13428
- }
13429
-
13430
- start(){
13431
-
13432
- const self=this;
13433
-
13434
- const splits=splitURL(this.serverNodeOrigin);
13435
- let protocol=nonull(splits.protocol,"ws"), host=nonull(splits.host,"localhost"), port=nonull(splits.port,"30000");
13436
-
13437
-
13438
- // THIS IS ON THE REFERENCE SERER NODE ONLY :
13439
- const clientInstanceToReferenceServerNode=initClient(this.isNodeContext, false, (socketToServer)=>{
13440
-
13441
-
13442
- // First client needs to register to its main reference serer node :
13443
- // TRACE
13444
- lognow(` (client ${self.clientId}) Sending client registering request to server node...`);
13445
-
13446
- const helloClientRequest={
13447
- clientId:self.clientId,
13448
- type:"request.register.client",
13449
- isReferenceNode:true,
13450
- };
13451
- socketToServer.send("protocol", helloClientRequest);
13452
-
13453
- // We place a listener from server on this client instance :
13454
- socketToServer.receive("protocol",(message)=>{
13455
-
13456
-
13457
- // Adding listeners :
13458
- if(message.type==="response.register.client"){
13459
-
13460
- // TRACE
13461
- lognow(` (${self.clientId}) Receving registering response from reference server...`);
13462
-
13463
- // Case reference node:
13464
-
13465
- // Second, once client is registered at its refernce server, then it needs to know to which servers it needs to connect to :
13466
- // TRACE
13467
- lognow("(client "+self.clientId+") Sending client interrogation request to server node...");
13468
-
13469
- const boundaries=self.view.getBoundaries();
13470
- const interrogationRequest={
13471
- clientId:self.clientId,
13472
- type:"request.interrogation.client",
13473
- boundaries:boundaries
13474
- };
13475
- socketToServer.send("protocol", interrogationRequest);
13476
-
13477
-
13478
- }
13479
- });
13480
-
13481
- // We place a listener from server on this client instance :
13482
- socketToServer.receive("protocol",(message)=>{
13483
- if(message.type==="response.unregister.client"){
13484
-
13485
- // TRACE
13486
- lognow(` (${self.clientId}) Receving unregistering response from reference server...`);
13487
-
13488
- // DO NOTHING
13489
-
13490
- }
13491
- });
13492
-
13493
-
13494
- // We place a listener from server on this client instance :
13495
- socketToServer.receive("protocol",(message)=>{
13496
- if(message.type==="response.interrogation.client"){
13497
-
13498
- // TRACE
13499
- lognow("!!!!!! ("+self.clientId+") Receving interrogation response from requested server...",message);
13500
-
13501
- // Now the client needs to know to which other nodes it needs to connect in order to get all its model objects :
13502
- const serversNodesIdsForModelObjectsForClient=message.serversNodesIdsForModelObjectsForClient;
13503
-
13504
- // Now the client needs to know to which other nodes it needs to connect in order to get all its model objects :
13505
-
13506
-
13507
- // We connect (only) to the relevant servers directly :
13508
- foreach(serversNodesIdsForModelObjectsForClient,(nodeInfoAndObjectsIds, serverNodeId)=>{
13509
-
13510
- const splitsServerInfo=splitURL(nodeInfoAndObjectsIds.nodeServerInfo);
13511
- const clientInstanceToAuthorityServerNode=initClient(this.isNodeContext, false, (socketToServer)=>{
13512
-
13513
- // Then we register to them :
13514
- // TRACE
13515
- lognow(` (client ${self.clientId}) Sending client registering request to authority server node...`);
13516
-
13517
- const helloToAuthorityClientRequest={
13518
- clientId:self.clientId,
13519
- type:"request.register.client",
13520
- isReferenceNode:false,
13521
- };
13522
- socketToServer.send("protocol", helloToAuthorityClientRequest);
13523
-
13524
-
13525
- }, splitsServerInfo.protocol+"://"+splitsServerInfo.host, splitsServerInfo.port, false);
13526
- self.currentServersNodes[serverNodeId]={clientInstance:clientInstanceToAuthorityServerNode};
13527
- clientInstanceToAuthorityServerNode.client.start();
13528
-
13529
-
13530
- clientInstanceToAuthorityServerNode.client.socketToServer.receive("protocol", (message)=>{
13531
-
13532
- // Adding listeners :
13533
- if(message.type==="response.register.client"){
13534
- // We place a listener from server on this client instance :
13535
- // Adding listeners :
13536
-
13537
- // Case non-reference authority node servers :
13538
-
13539
- // TRACE
13540
- lognow(` (${self.clientId}) Receving registering response from reference server...`);
13541
-
13542
- // We merge our client's model with the partial model sent from the server it just registered to :
13543
- const partialModelString=message.partialModelString;
13544
- const partialModel=JSON.parseRecycle(partialModelString);
13545
-
13546
- // Merging means that all objects in partialModel not being in model must be added to model :
13547
- self.model.clientMergeWith(partialModel);
13548
-
13549
- // TRACE
13550
- lognow("(client "+self.clientId+") Client model has been merged with a partial model from a server node...",self.model);
13551
-
13552
- }
13553
-
13554
-
13555
- });
13556
-
13557
-
13558
-
13559
- clientInstanceToAuthorityServerNode.client.socketToServer.receive("inputs", (message)=>{
13560
-
13561
- // Adding listeners :
13562
- if(message.type==="response.objectsModifiedOrAdded.client"){
13563
- const modifiedOrAddedObjects=JSON.parseRecycle(message.objectsString);
13564
-
13565
- if(!message.isAddingObjects){
13566
- self.model.clientUpdateObjects(modifiedOrAddedObjects);
13567
- }else{
13568
- self.model.clientAddObjects(modifiedOrAddedObjects);
13569
- }
13570
-
13571
- // TRACE
13572
- lognow(` (client ${self.clientId}) Receving modified or added objects after the inputs...modifiedOrAddedObjects=`,modifiedOrAddedObjects);
13573
-
13574
-
13575
- }
13576
-
13577
-
13578
- });
13579
-
13580
-
13581
- });
13582
-
13583
- }
13584
- });
13585
-
13586
-
13587
- //self.addListeners();
13588
-
13589
-
13590
-
13591
- }, protocol+"://"+host, port, false);
13592
- clientInstanceToReferenceServerNode.client.start();
13593
- this.clientInstanceToReferenceServerNode={clientInstance:clientInstanceToReferenceServerNode};
13594
-
13595
-
13596
- return this;
13597
- }
13598
-
13599
-
13600
- /*public*/sendInputs(inputs, subBoundaries=null){
13601
-
13602
- const self=this;
13603
-
13604
-
13605
- if(subBoundaries){
13606
- // We send client's inputs to ALL its currently connected servers :
13607
- const self=this;
13608
- foreach(this.currentServersNodes,(serversNode,serverNodeId)=>{
13609
- self.sendInputsToServer(serversNode, inputs, subBoundaries);
13610
- });
13611
- }else{
13612
- // If we have no boundaries set for these inputs, then we only send them to one server node at random :
13613
- const serversNode=Math.getRandomInArray(this.currentServersNodes);
13614
- this.sendInputsToServer(serversNode, inputs, subBoundaries);
13615
- }
13616
-
13617
-
13618
- }
13619
-
13620
- /*private*/sendInputsToServer(serversNode, inputs, subBoundaries=null){
13621
- const clientInstance=serversNode.clientInstance;
13622
-
13623
- const inputsMessage={
13624
- clientId:this.clientId,
13625
- type:"request.inputs.client",
13626
- inputs:inputs,
13627
- subBoundaries:subBoundaries,
13628
- };
13629
-
13630
- clientInstance.client.socketToServer.send("inputs", inputsMessage);
13631
- }
13632
-
13633
-
13634
-
13635
-
13636
-
13637
-
13638
- }
13639
-
13640
-
13641
- getAORTACClient=function(clientId=getUUID(), serverNodeOrigin="ws://127.0.0.1:40000", model, view, isNodeContext=true){
13642
- return new AORTACClient(clientId, serverNodeOrigin, model, view, isNodeContext);
13643
- }
13644
-
13645
-
13646
-
13647
13540
 
13648
13541
 
13649
13542
 
13650
- /*utils GEOMETRY library associated with aotra version : «1_29072022-2359 (11/11/2025-18:19:16)»*/
13543
+ /*utils GEOMETRY library associated with aotra version : «1_29072022-2359 (01/03/2026-01:46:57)»*/
13651
13544
  /*-----------------------------------------------------------------------------*/
13652
13545
 
13653
13546
 
@@ -13792,6 +13685,30 @@ Math.rotateAround=function(angleRadians, xParam=0, yParam=0, xCenterParam=0, yCe
13792
13685
  return point;
13793
13686
  };
13794
13687
 
13688
+ Math.rotatePointAround3D=function(angleRadiansOnY, angleRadiansOnZ, pointToRotate, rotationCenterPoint={x:0,y:0,z:0}){
13689
+ const xy=Math.rotateAround(angleRadiansOnY, pointToRotate.x, pointToRotate.y, rotationCenterPoint.x, rotationCenterPoint.y);
13690
+ const xz=Math.rotateAround(angleRadiansOnZ, pointToRotate.x, pointToRotate.z, rotationCenterPoint.x, rotationCenterPoint.z);
13691
+ return {x: xy.x, y:xy.y, z:xz.y};
13692
+ }
13693
+
13694
+ Math.addCoordinates=function(points, functionToApplyAtEachSum=null){
13695
+ if(!points || empty(points)) return null;
13696
+ const resultPoint={};
13697
+ foreach(points, (point)=>{
13698
+ foreach(point, (coordinateComponent,coordinateComponentName)=>{
13699
+ const resultPointCoordinateComponent=resultPoint[coordinateComponentName];
13700
+ if(typeof(resultPointCoordinateComponent)=="undefined" || resultPointCoordinateComponent==null)
13701
+ resultPoint[coordinateComponentName]=0;
13702
+ if(functionToApplyAtEachSum)
13703
+ resultPoint[coordinateComponentName]=functionToApplyAtEachSum(resultPoint[coordinateComponentName]+coordinateComponent);
13704
+ else
13705
+ resultPoint[coordinateComponentName]+=coordinateComponent;
13706
+ });
13707
+ });
13708
+ return resultPoint;
13709
+ }
13710
+
13711
+
13795
13712
  // UNUSED (AND UNUSEFUL) :
13796
13713
  //Math.getGluedPosition=function(referencePosition, relativePosition){
13797
13714
  // const referenceAngleRadians=referencePosition.getAngle2D();
@@ -14026,7 +13943,7 @@ window.calculateLinearlyMovedPoint3DPolar=function(currentPoint, anglesRadians,
14026
13943
  //test();
14027
13944
 
14028
13945
 
14029
- Math.coerceAngle=function(angle,isRadians=false,isOnlyPositive=false){
13946
+ Math.coerceAngle=function(angle, isRadians=false, isOnlyPositive=false){
14030
13947
  const WHOLE_ARC=(isRadians?Math.TAU:360);
14031
13948
  let result=0;
14032
13949
  if(angle <= 0) result=(WHOLE_ARC - Math.abs(angle % WHOLE_ARC)) % WHOLE_ARC;
@@ -14119,73 +14036,8 @@ function verticesToLinePoints(polygon, isLineKeptFunction=null){
14119
14036
  }
14120
14037
  return lines;
14121
14038
  }
14122
- // helper: test line intersections
14123
- // point object: {x:, y:}
14124
- // p0 & p1 form one segment, p2 & p3 form the second segment
14125
- // Get interseting point of 2 line segments (if any)
14126
- // Attribution: http://paulbourke.net/geometry/pointlineplane/
14127
- function lineSegmentsCollide(segment1Point1,segment1Point2,segment2Point1,segment2Point2) {
14128
-
14129
- // ORIGINAL :
14130
- // var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x);
14131
- // var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x);
14132
- // var denominator = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y);
14133
-
14134
-
14135
- let deltaXPoint1=segment1Point1.x-segment2Point1.x;
14136
- let deltaYPoint1=segment1Point1.y-segment2Point1.y;
14137
- let deltaYPoint2=segment2Point2.y-segment2Point1.y;
14138
-
14139
- let deltaXSegment1Point2_1=segment1Point2.x-segment1Point1.x;
14140
- let deltaYSegment1Point2_1=segment1Point2.y-segment1Point1.y;
14141
- let deltaXSegment2Point2_1=segment2Point2.x-segment2Point1.x;
14142
-
14143
- let numerator1 = (deltaXSegment2Point2_1 * deltaYPoint1) - (deltaYPoint2 * deltaXPoint1);
14144
- let numerator2 = (deltaXSegment1Point2_1 * deltaYPoint1) - (deltaYSegment1Point2_1 * deltaXPoint1);
14145
- let denominator = (deltaYPoint2 * deltaXSegment1Point2_1) - (deltaXSegment2Point2_1 * deltaYSegment1Point2_1);
14146
-
14147
- // Test if Coincident
14148
- // If the denominator and numerator for the ua and ub are 0
14149
- // then the two lines are coincident.
14150
- if(numerator1==0 && numerator2==0 && denominator==0) return false;
14151
-
14152
- // Test if Parallel
14153
- // If the denominator for the equations for ua and ub is 0
14154
- // then the two lines are parallel.
14155
- if(denominator == 0) return false;
14156
-
14157
- // test if line segments are colliding
14158
- numerator1 = numerator1/denominator;
14159
- numerator2 = numerator2/denominator;
14160
- return (numerator1>=0 && numerator1<=1 && numerator2>=0 && numerator2<=1);
14161
- }
14162
-
14163
-
14164
14039
 
14165
- // http://alienryderflex.com/polygon/
14166
- // 1) We draw a line parallel to the X axis, at the Y coordinate of the tested point,
14167
- // 2) And we collect all the intersection points (or nodes) with the polygon's sides.
14168
- // (We exclude nodes that have the same coordinates as polygon vertices !)
14169
- // 3) If there is an odd number (or 1) of nodes on each side (on the X axis) of the point, then the point is inside the polygon
14170
-
14171
- function getSegmentEquationParameters(p1,p2){
14172
- const deltaX=p2.x-p1.x;
14173
- const deltaY=p2.y-p1.y;
14174
- if(deltaX==0) return null;
14175
- if(deltaY==0) return {slope:0, yOffset:p2.y};
14176
- const slope=(deltaX/deltaY);
14177
- const yOffset=-(slope*p1.x-p1.y);
14178
- return {slope:slope, yOffset:yOffset};
14179
- }
14180
14040
 
14181
- function getIntersectionPointOnSegmentAtY(p1,p2,yTarget){
14182
- if((yTarget<p1.y && yTarget<p2.y) || (p1.y<yTarget && p2.y<yTarget)) return null;
14183
- const equationParameters=getSegmentEquationParameters(p1,p2);
14184
- if(!equationParameters) return null;
14185
- if(equationParameters.slope==0) return null;
14186
- const xCollision=(yTarget-equationParameters.yOffset)/equationParameters.slope;
14187
- return {x:xCollision, y:yTarget};
14188
- }
14189
14041
 
14190
14042
  Math.isPointInPolygon=function(point,polygon){
14191
14043
 
@@ -14271,6 +14123,137 @@ window.isOriented=function(orientation, width, height){
14271
14123
 
14272
14124
 
14273
14125
 
14126
+ // Lines
14127
+
14128
+ // helper: test line intersections
14129
+ // point object: {x:, y:}
14130
+ // p0 & p1 form one segment, p2 & p3 form the second segment
14131
+ // Get interseting point of 2 line segments (if any)
14132
+ // Attribution: http://paulbourke.net/geometry/pointlineplane/
14133
+ function lineSegmentsCollide(segment1Point1,segment1Point2,segment2Point1,segment2Point2) {
14134
+
14135
+ // ORIGINAL :
14136
+ // var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x);
14137
+ // var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x);
14138
+ // var denominator = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y);
14139
+
14140
+
14141
+ let deltaXPoint1=segment1Point1.x-segment2Point1.x;
14142
+ let deltaYPoint1=segment1Point1.y-segment2Point1.y;
14143
+ let deltaYPoint2=segment2Point2.y-segment2Point1.y;
14144
+
14145
+ let deltaXSegment1Point2_1=segment1Point2.x-segment1Point1.x;
14146
+ let deltaYSegment1Point2_1=segment1Point2.y-segment1Point1.y;
14147
+ let deltaXSegment2Point2_1=segment2Point2.x-segment2Point1.x;
14148
+
14149
+ let numerator1 = (deltaXSegment2Point2_1 * deltaYPoint1) - (deltaYPoint2 * deltaXPoint1);
14150
+ let numerator2 = (deltaXSegment1Point2_1 * deltaYPoint1) - (deltaYSegment1Point2_1 * deltaXPoint1);
14151
+ let denominator = (deltaYPoint2 * deltaXSegment1Point2_1) - (deltaXSegment2Point2_1 * deltaYSegment1Point2_1);
14152
+
14153
+ // Test if Coincident
14154
+ // If the denominator and numerator for the ua and ub are 0
14155
+ // then the two lines are coincident.
14156
+ if(numerator1==0 && numerator2==0 && denominator==0) return false;
14157
+
14158
+ // Test if Parallel
14159
+ // If the denominator for the equations for ua and ub is 0
14160
+ // then the two lines are parallel.
14161
+ if(denominator == 0) return false;
14162
+
14163
+ // test if line segments are colliding
14164
+ numerator1 = numerator1/denominator;
14165
+ numerator2 = numerator2/denominator;
14166
+ return (numerator1>=0 && numerator1<=1 && numerator2>=0 && numerator2<=1);
14167
+ }
14168
+
14169
+
14170
+ window.getIntersectionPointWithYHorizontalLineAndLineEquationParameters=function(slope,yOffset,yLineHeight){
14171
+ if(slope==0) return null;
14172
+ return {x:(yLineHeight-yOffset)/slope,y:yLineHeight};
14173
+ }
14174
+
14175
+ window.getIntersectionPointFrom2LinesEquationParameters=function(slope1,yOffset1,slope2,yOffset2){
14176
+ const slopesDelta=slope1-slope2;
14177
+ if(slopesDelta==0) return null;
14178
+
14179
+ const yOffsetsInverseDelta=yOffset2-yOffset1;
14180
+
14181
+ const intersectionX=yOffsetsInverseDelta/slopesDelta;
14182
+ const intersectionY=(slope1*yOffset2-slope2*yOffset1)/slopesDelta;
14183
+
14184
+ return {x:intersectionX,y:intersectionY};
14185
+ }
14186
+
14187
+ window.getSegmentEquationParameters=function(p1,p2){
14188
+ const deltaX=p2.x-p1.x;
14189
+ const deltaY=p2.y-p1.y;
14190
+ if(deltaX==0) return null;
14191
+ if(deltaY==0) return {slope:0, yOffset:p2.y};
14192
+ const slope=(deltaX/deltaY);
14193
+ const yOffset=-(slope*p1.x-p1.y);
14194
+ return {slope:slope, yOffset:yOffset};
14195
+ }
14196
+
14197
+ // http://alienryderflex.com/polygon/
14198
+ // 1) We draw a line parallel to the X axis, at the Y coordinate of the tested point,
14199
+ // 2) And we collect all the intersection points (or nodes) with the polygon's sides.
14200
+ // (We exclude nodes that have the same coordinates as polygon vertices !)
14201
+ // 3) If there is an odd number (or 1) of nodes on each side (on the X axis) of the point, then the point is inside the polygon
14202
+
14203
+ window.getIntersectionPointOnSegmentAtY=function(p1,p2,yTarget){
14204
+ if((yTarget<p1.y && yTarget<p2.y) || (p1.y<yTarget && p2.y<yTarget)) return null;
14205
+ const equationParameters=getSegmentEquationParameters(p1,p2);
14206
+ if(!equationParameters) return null;
14207
+ if(equationParameters.slope==0) return null;
14208
+ const xCollision=(yTarget-equationParameters.yOffset)/equationParameters.slope;
14209
+ return {x:xCollision, y:yTarget};
14210
+ }
14211
+
14212
+
14213
+
14214
+ // Perspective
14215
+
14216
+
14217
+ // apparentCorner1----apparentCorner2
14218
+ // | |
14219
+ // | |
14220
+ // | |
14221
+ // | |
14222
+ // apparentCorner4----apparentCorner3
14223
+ window.getUntransformed2DCoordinatesInsidePerspectivedRectangle=function(apparentPoint,apparentCorner1,apparentCorner2,apparentCorner3,apparentCorner4){
14224
+
14225
+ const minX=Math.minInArray([apparentCorner1.x,apparentCorner2.x,apparentCorner3.x,apparentCorner4.x]);
14226
+ const minY=Math.minInArray([apparentCorner1.y,apparentCorner2.y,apparentCorner3.y,apparentCorner4.y]);
14227
+ const maxX=Math.maxInArray([apparentCorner1.x,apparentCorner2.x,apparentCorner3.x,apparentCorner4.x]);
14228
+ const maxY=Math.maxInArray([apparentCorner1.y,apparentCorner2.y,apparentCorner3.y,apparentCorner4.y]);
14229
+
14230
+ const apparentSize={w:(maxX-minX),h:(maxY-minY)};
14231
+ if(apparentSize.w==0) return null;
14232
+
14233
+ const heightRatio=(apparentSize.w<apparentSize.h ? 1 : (apparentSize.h/apparentSize.w));
14234
+
14235
+ const leftSideLineEquationParameters=getSegmentEquationParameters(apparentCorner1,apparentCorner4);
14236
+ if(!leftSideLineEquationParameters) return null;
14237
+ const rightSideLineEquationParameters=getSegmentEquationParameters(apparentCorner2,apparentCorner3);
14238
+ if(!rightSideLineEquationParameters) return null;
14239
+
14240
+ const perspectivePoint=getIntersectionPointFrom2LinesEquationParameters(
14241
+ leftSideLineEquationParameters.slope,leftSideLineEquationParameters.yOffset,
14242
+ rightSideLineEquationParameters.slope,rightSideLineEquationParameters.yOffset);
14243
+ if(!perspectivePoint) return null;
14244
+
14245
+ const perspetiveLine=getSegmentEquationParameters(apparentPoint,perspectivePoint);
14246
+ if(!perspetiveLine) return null;
14247
+
14248
+ // (for the X coordinate only)
14249
+ const untransformedPoint=getIntersectionPointWithYHorizontalLineAndLineEquationParameters(perspetiveLine.slope, perspetiveLine.yOffset, minY);
14250
+ const x=untransformedPoint.x;
14251
+ // (for the Y coordinate only)
14252
+ const y=(apparentPoint.y*heightRatio);
14253
+
14254
+ const result={x:x-minX, y:y-minY};
14255
+ return result;
14256
+ }
14274
14257
 
14275
14258
 
14276
14259
 
@@ -14886,10 +14869,10 @@ function rayVsUnitSphereClosestPoint(p, r) {
14886
14869
  // MUST REMAIN AT THE END OF THIS LIBRARY FILE !
14887
14870
 
14888
14871
  AOTRAUTILS_GEOMETRY_LIB_IS_LOADED=true;
14889
- /*utils 3D library associated with aotra version : «1_29072022-2359 (11/11/2025-18:19:16)»*/
14872
+ /*utils 3D library associated with aotra version : «1_29072022-2359 (01/03/2026-01:46:57)»*/
14890
14873
  /*-----------------------------------------------------------------------------*/
14891
14874
 
14892
- /*utils AI library associated with aotra version : «1_29072022-2359 (11/11/2025-18:19:16)»*/
14875
+ /*utils AI library associated with aotra version : «1_29072022-2359 (01/03/2026-01:46:57)»*/
14893
14876
  /*-----------------------------------------------------------------------------*/
14894
14877
 
14895
14878
 
@@ -15035,11 +15018,11 @@ getOpenAIAPIClient=(modelName, apiURL, agentRole, defaultPrompt)=>{
15035
15018
 
15036
15019
 
15037
15020
 
15038
- /*utils CONSOLE library associated with aotra version : «1_29072022-2359 (11/11/2025-18:19:16)»*/
15021
+ /*utils CONSOLE library associated with aotra version : «1_29072022-2359 (01/03/2026-01:46:57)»*/
15039
15022
  /*-----------------------------------------------------------------------------*/
15040
15023
 
15041
15024
 
15042
- /* ## Utility global methods in a javascript, console (nodejs) or vanilla javascript with no browser environment.
15025
+ /* ## Utility methods in a javascript, for AORTAC subsystem (server & client)
15043
15026
  *
15044
15027
  * This set of methods gathers utility generic-purpose methods usable in any JS project.
15045
15028
  * Several authors of snippets published freely on the Internet contributed to this library.
@@ -15062,78 +15045,750 @@ getOpenAIAPIClient=(modelName, apiURL, agentRole, defaultPrompt)=>{
15062
15045
  if(typeof(window)==="undefined") window=global;
15063
15046
 
15064
15047
 
15048
+ // ==================================================================================================================
15065
15049
 
15066
15050
 
15067
- // OLD : socket.io :
15068
- // https://stackoverflow.com/questions/31156884/how-to-use-https-on-node-js-using-express-socket-io
15069
- // https://stackoverflow.com/questions/6599470/node-js-socket-io-with-ssl
15070
- // https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener
15071
- // https://socket.io/docs/v4/client-socket-instance/
15051
+ // AORTAC SERVER
15072
15052
 
15073
- // NEW : ws :
15074
- // https://github.com/websockets/ws#installing
15075
- // https://github.com/websockets/ws/blob/master/doc/ws.md#event-message
15076
- // ON BROWSER SIDE : Native Websockets :
15077
- // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
15078
- // https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications
15079
15053
 
15080
- // We have to import both implementations, regardless of which one is chosen :
15081
- //Socket=require("socket.io");
15082
- //WebSocket=require("ws");
15054
+ //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
15083
15055
 
15056
+ AORTAC_OUTCOMING_SERVERS_CONNECTION_GLOBAL_TIMEOUT=30000;
15084
15057
 
15058
+ // New implementation :
15059
+ AORTAC_SERVER_CELL_PERIODICAL_CONNECTIVITY_CHECK_MILLIS=2000;
15060
+ class AORTACServerCell{
15061
+
15062
+ constructor(quorumNumber=1, selfOrigin, outcomingCellsOrigins, model, controller, sslConfig={/*OPTIONAL*/certPath:null,/*OPTIONAL*/keyPath:null}){
15085
15063
 
15064
+ this.model=model;
15065
+ this.controller=controller;
15066
+
15067
+ this.quorumNumber=quorumNumber;
15068
+ this.selfOrigin=selfOrigin;
15069
+ const splits=splitURL(this.selfOrigin);
15070
+ this.protocol=nonull(splits.protocol,"ws");
15071
+ this.host=nonull(splits.host,"localhost");
15072
+ this.port=nonull(splits.port,"30000");
15073
+ this.sslConfig=sslConfig;
15086
15074
 
15087
- // =================================================================================
15088
- // NODEJS UTILS
15075
+ this.server=null;
15076
+ this.incomingCells={};
15077
+
15078
+ this.outcomingCellsOrigins=outcomingCellsOrigins;
15079
+ this.outcomingCells={};
15080
+ const self=this;
15081
+ foreach(this.outcomingCellsOrigins,outcomingCellOrigin=>{
15082
+ self.outcomingCells[outcomingCellOrigin]={connected:false,client:null};
15083
+ });
15084
+ this.outcomingNetworkProber=null;
15085
+
15086
+ this.onReceiveMessageListeners={
15087
+ "incoming":{"protocol":{}},
15088
+ "outcoming":{"protocol":{}},
15089
+ "both":{"protocol":{}},
15090
+ };
15089
15091
 
15090
- const FILE_ENCODING="utf8";
15092
+ this.cellsOverview={};
15093
+
15094
+ this.startTime=null;
15095
+
15096
+ this.quorumIsReachedSequenceIsInitiated=false;
15097
+ // this.quorumCells=null; // (CAUTION : only the latest ready cell when quorum is reached has this attribute populated)
15091
15098
 
15092
- const ADD_CORS_HEADER=true;
15099
+ this.liveModelObjects={};
15093
15100
 
15101
+ }
15094
15102
 
15103
+ start(){
15095
15104
 
15105
+ this.startTime=getNow();
15106
+ this.server=this.launchServerForIncomingConnections();
15096
15107
 
15108
+ if(empty(this.outcomingCellsOrigins) || (getArraySize(this.outcomingCellsOrigins)==1 && contains(this.outcomingCellsOrigins, this.selfOrigin))){
15109
+ // TRACE
15110
+ lognow("ERROR : Server cell must have an outcoming link to at least one other server cell of the blob. Aborting outcoming network probing.");
15111
+ }else{
15112
+ this.outcomingNetworkProber=new PeriodicalExecuter(this.probeOutcomingNetwork, AORTAC_SERVER_CELL_PERIODICAL_CONNECTIVITY_CHECK_MILLIS, this);
15113
+ }
15097
15114
 
15098
- // Nodejs filesystem utils :
15099
- if(typeof(fs)==="undefined"){
15100
- // TRACE
15101
- console.log("WARN : Could not find the nodejs dependency «fs», aborting persister setup.");
15102
- getPersister=()=>{ return null; };
15103
-
15104
- }else{
15115
+ this.handleCommonListeners();
15116
+
15117
+ return this;
15118
+ }
15105
15119
 
15120
+ /*private*/launchServerForIncomingConnections(){
15121
+ const self=this;
15122
+
15123
+ const server=initNodeServerInfrastructureWrapper(
15124
+ // On each client connection :
15125
+ null,
15126
+ // On client finalization :
15127
+ function(server){
15128
+
15129
+ // On-receive message listeners handling ;
15130
+ foreach(Object.keys(self.onReceiveMessageListeners["incoming"]), channelName=>{
15131
+ server.receive(channelName, (message, clientSocket)=>{
15132
+ self.executeListeners("incoming",channelName, message, clientSocket);
15133
+ });
15134
+ });
15135
+ foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{
15136
+ server.receive(channelName, (message, clientSocket)=>{
15137
+ self.executeListeners("both",channelName, message, clientSocket);
15138
+ });
15139
+ });
15140
+
15141
+
15142
+ // HANDLE HELLO SEQUENCE
15143
+ self.handleHelloSequence();
15144
+
15145
+ }, this.port, this.sslConfig.certPath, this.sslConfig.keyPath);
15146
+ server.serverManager.start();
15147
+ return server;
15148
+ }
15106
15149
 
15107
- getPersister=function(dataDirPath,prefix=""){
15150
+ /*private*/probeOutcomingNetwork(){
15151
+ const numberOfTotalServers=getArraySize(this.outcomingCells);
15152
+ let numberOfConnectedOutcomingServers=0;
15153
+ foreach(this.outcomingCells, outcomingCell=>{
15154
+ numberOfConnectedOutcomingServers++
15155
+ },outcomingCell=>outcomingCell.connected);
15108
15156
 
15109
- let self={
15157
+ // TRACE
15158
+ lognow(`INFO : Number of connected outcoming servers : ${numberOfConnectedOutcomingServers}/${numberOfTotalServers}`);
15110
15159
 
15111
- dataDirPath:dataDirPath,
15112
- prefix:prefix,
15160
+ if(numberOfTotalServers<=numberOfConnectedOutcomingServers){
15161
+ this.outcomingNetworkProber.stop();
15113
15162
 
15114
- //FILE_NAME_PATTERN:"data.clientId.repositoryName.json",
15115
- /*private*/getPath:function(clientId="noclient", repositoryName="norepository"){
15116
- // let path=self.FILE_NAME_PATTERN.replace(new RegExp("@clientId@","g"),clientId);
15117
- let path=`${self.dataDirPath}`
15118
- + (blank(self.prefix)?"":(self.prefix+"."))
15119
- +`${clientId}.${repositoryName}.json`;
15120
- return path;
15121
- },
15163
+ // TRACE
15164
+ lognow(`INFO : All outcoming cells connected. Stopping outcoming network probing.`);
15165
+
15166
+ // INITIATE CELL IS READY SEQUENCE
15167
+ this.initiateCellIsReadySequence();
15168
+
15169
+ return;
15170
+ }
15171
+
15172
+ const self=this;
15173
+
15174
+ // We try to connect to all outcoming cells :
15175
+ foreach(this.outcomingCells, (outcomingCell, outcomingCellOrigin)=>{
15122
15176
 
15123
- readTreeObjectFromFile:function(clientId="noclient", repositoryName="norepository", keepClassName=false){
15177
+ const splits=splitURL(outcomingCellOrigin);
15178
+ const outcomingCellProtocol=nonull(splits.protocol,"ws");
15179
+ const outcomingCellHost=nonull(splits.host,"localhost");
15180
+ const outcomingCellPort=nonull(splits.port,"40000");
15181
+
15182
+ const clientInstance=initClient(true, false, (socketToServerClientInstance)=>{
15124
15183
 
15125
- let path=self.getPath(clientId,repositoryName);
15126
- let resultFlat=null;
15127
-
15128
- try{
15129
- resultFlat=fs.readFileSync(path, FILE_ENCODING);
15130
-
15131
- }catch(error){
15132
- // TRACE
15133
- console.log("ERROR : Could not read file «"+path+"».");
15134
-
15135
- return null;
15136
- }
15184
+ // On-receive message listeners handling ;
15185
+ foreach(Object.keys(self.onReceiveMessageListeners["outcoming"]), channelName=>{
15186
+ socketToServerClientInstance.receive(channelName, (message, clientSocket)=>{
15187
+ self.executeListeners("outcoming",channelName, message, clientSocket);
15188
+ });
15189
+ });
15190
+ foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{
15191
+ socketToServerClientInstance.receive(channelName, (message, clientSocket)=>{
15192
+ self.executeListeners("both",channelName, message, clientSocket);
15193
+ });
15194
+ });
15195
+
15196
+
15197
+ // INITIATE HELLO SEQUENCE
15198
+ self.initiateHelloSequence(socketToServerClientInstance);
15199
+
15200
+ // We are connected:
15201
+ outcomingCell.connected=true;
15202
+ // Only used to identify the outcoming node, DO NOT USE TO SEND/RECEIVE ANYTHINIG !
15203
+ outcomingCell.clientSocket=clientInstance.client.clientSocket;
15204
+ // For this, use the socketToServerClientInstance attribute instead :
15205
+ outcomingCell.socketToServerClientInstance=socketToServerClientInstance;
15206
+
15207
+ }, outcomingCellProtocol+"://"+outcomingCellHost, outcomingCellPort, false);
15208
+
15209
+ clientInstance.client.start();
15210
+
15211
+ clientInstance.client.socketToServerClientInstance.onServerLostListeners.push({execute:(clientSocket)=>{
15212
+ const foundOutcomingNode=findInArray(self.outcomingCells,outcomingCell=>outcomingCell.clientSocket==clientSocket);
15213
+ if(foundOutcomingNode) foundOutcomingNode.connected=false;
15214
+ }});
15215
+
15216
+
15217
+ },outcomingCell=>!outcomingCell.connected);
15218
+
15219
+ }
15220
+
15221
+
15222
+ /*private*/handleCommonListeners(){
15223
+
15224
+ // HANDLE CELL IS READY SEQUENCE
15225
+ this.handleCellIsReadySequence();
15226
+
15227
+ // HANDLE PARTITION SEQUENCE
15228
+ this.handlePartitionSequence();
15229
+
15230
+ }
15231
+
15232
+
15233
+ /*private*/executeListeners(outletName, channelName, message, clientSocket){
15234
+ const onReceiveMessageListeners=this.onReceiveMessageListeners[outletName][channelName];
15235
+ if(!onReceiveMessageListeners) return;
15236
+
15237
+ // OUTCOMING NODES LISTENERS HOOK :
15238
+ // // TRACE
15239
+ // lognow("INFO : SERVER : Received a "+channelName+" message: ", message);
15240
+
15241
+ foreach(onReceiveMessageListeners, (listeners,listenerMessageType)=>{
15242
+ foreach(listeners, (listener)=>{
15243
+ // // TRACE
15244
+ // lognow(`INFO : SERVER : Executing a onReceiveMessageListeners for messageType ${listenerMessageType}.`);
15245
+
15246
+ listener.execute(this, message, this.server, clientSocket);
15247
+
15248
+ },(listener)=>(message.type===listener.listenerMessageType));
15249
+ });
15250
+
15251
+ }
15252
+
15253
+
15254
+ /*private*/sendMessageToBlob(channelName, message,
15255
+ broadcastConfig={isOriginatingCell:false, destinationCellsOrigins:null, includeIncomingConnectionInTransmission:false, isRequest:false}){
15256
+
15257
+ const self=this;
15258
+
15259
+ if(broadcastConfig){
15260
+ if(broadcastConfig.isOriginatingCell){
15261
+ message.originatingCellOrigin=this.selfOrigin;
15262
+ }
15263
+ if(broadcastConfig.isRequest){
15264
+ message.isRequest=true;
15265
+ }
15266
+ if(broadcastConfig.destinationCellsOrigins && !empty(broadcastConfig.destinationCellsOrigins)){
15267
+ message.destinationCellsOrigins=broadcastConfig.destinationCellsOrigins;
15268
+ }
15269
+ }
15270
+
15271
+ if(!message.visitedCells)
15272
+ message.visitedCells=[];
15273
+ else if(contains(message.visitedCells,this.selfOrigin))
15274
+ return;
15275
+ message.visitedCells.push(this.selfOrigin);
15276
+
15277
+ if(broadcastConfig && broadcastConfig.includeIncomingConnectionInTransmission){
15278
+ foreach(this.incomingCells,(incomingCell)=>{
15279
+ // As a server, we send (forward) the message to the currently iterated upon client that is connected to us :
15280
+ incomingCell.server.send(channelName, message, null, incomingCell.clientSocket);
15281
+ },(incomingCell, incomingCellOrigin)=>
15282
+ (incomingCell.connected && !contains(message.visitedCells, incomingCellOrigin))
15283
+ );
15284
+ }
15285
+
15286
+ foreach(this.outcomingCells,(outcomingCell)=>{
15287
+ // As a client, we send (forward) the message to the currently iterated upon server we are connected to :
15288
+ outcomingCell.socketToServerClientInstance.send(channelName, message);
15289
+ },(outcomingCell, outcomingCellOrigin)=>
15290
+ (outcomingCell.connected && !contains(message.visitedCells, outcomingCellOrigin))
15291
+ );
15292
+
15293
+
15294
+ // We only add a result listener for the cell which sent the request message :
15295
+ if(message.isRequest && message.originatingCellOrigin==this.selfOrigin
15296
+ //&& (typeof(message.result)=="undefined" || message.result==null)
15297
+ ){
15298
+
15299
+ const outletName="both";
15300
+
15301
+ // To emulate a promise-like behavior :
15302
+ let blobResponseListeners=this.onReceiveMessageListeners[outletName][channelName][message.type];
15303
+ if(blobResponseListeners && blobResponseListeners["forRequestsIssuedBySelf"]){
15304
+ // TRACE
15305
+ lognow(`INFO : Blob response «forRequestsIssuedBySelf» listener for cell ${this.selfOrigin} on channel ${channelName} for «${message.type}» already exists.`);
15306
+ return blobResponseListeners["forRequestsIssuedBySelf"];
15307
+ }else{
15308
+ blobResponseListeners=getOrCreateEmptyAttribute(this.onReceiveMessageListeners[outletName][channelName],message.type);
15309
+ }
15310
+
15311
+ const blobResponseListener={
15312
+ thenCallback:null,
15313
+ execute:(self, message, server, clientSocket)=>{
15314
+ // When we have received the message :
15315
+ this.thenCallback(message);
15316
+
15317
+ // We need to remove the result listener once it is completed though.
15318
+ delete blobResponseListeners["forRequestsIssuedBySelf"];
15319
+ },
15320
+ then:(thenCallback)=>{
15321
+ this.thenCallback=thenCallback;
15322
+ }
15323
+ };
15324
+ blobResponseListeners["forRequestsIssuedBySelf"]=blobResponseListener;
15325
+
15326
+ return blobResponseListener;
15327
+ }else{
15328
+ return null;
15329
+ }
15330
+
15331
+ }
15332
+
15333
+
15334
+ // ==========================================================
15335
+ // HELLO SEQUENCE
15336
+
15337
+ /*private*/initiateHelloSequence(socketToServerClientInstance){
15338
+
15339
+ // 1- We want to present ourselves to other outcoming cells :
15340
+ const helloRequest={
15341
+ originatingCellOrigin:this.selfOrigin,
15342
+ type:"helloRequest",
15343
+ };
15344
+ socketToServerClientInstance.send("protocol", helloRequest);
15345
+ }
15346
+
15347
+
15348
+ /*private*/handleHelloSequence(){
15349
+
15350
+ // 2- We wait to receive the hello request of the incoming cell connection :
15351
+ getOrCreateEmptyAttribute(
15352
+ this.onReceiveMessageListeners["both"]["protocol"],"helloRequest")["forRequestsIssuedByOthers"]={
15353
+ execute:(selfParam, message, server, clientSocket)=>{
15354
+
15355
+ const cellOriginToCheck=message.originatingCellOrigin;
15356
+
15357
+ // TRACE
15358
+ lognow(`INFO : Incoming node ${cellOriginToCheck} has said hello. Updating its local information...`);
15359
+
15360
+
15361
+ selfParam.incomingCells[cellOriginToCheck]={
15362
+ connected:true,
15363
+ server:server,
15364
+ // DO NOT USE TO SEND/RECEIVE ANYTHINIG !
15365
+ // For this, use the server attribute (+ the clientSocket as argument) instead.
15366
+ clientSocket:clientSocket,
15367
+ };
15368
+
15369
+ },
15370
+ listenerMessageType:"helloRequest"
15371
+ };
15372
+
15373
+ }
15374
+
15375
+
15376
+ // ==========================================================
15377
+ // CELL IS READY SEQUENCE
15378
+
15379
+ /*private*/initiateCellIsReadySequence(){
15380
+
15381
+ // We update ourselve's status :
15382
+ this.cellsOverview[this.selfOrigin]={ready:true,startTime:this.startTime};
15383
+
15384
+ // INITIATE QUORUM IS REACHED SEQUENCE
15385
+ this.initiateQuorumIsReachedSequenceIfNecessary();
15386
+
15387
+ // We ask the others to update their status too :
15388
+ this.sendMessageToBlob("protocol",
15389
+ {type:"cellIsReady",startTime:this.startTime},
15390
+ {isOriginatingCell:true});
15391
+ }
15392
+
15393
+ /*private*/handleCellIsReadySequence(){
15394
+ getOrCreateEmptyAttribute(
15395
+ this.onReceiveMessageListeners["both"]["protocol"],"cellIsReady")["forRequestsIssuedByOthers"]={
15396
+ execute:(selfParam, message, server, clientSocket)=>{
15397
+
15398
+ selfParam.cellsOverview[message.originatingCellOrigin]={ready:true,startTime:message.startTime};
15399
+
15400
+ selfParam.sendMessageToBlob("protocol",message);
15401
+
15402
+ // // TRACE
15403
+ // lognow("INFO : Updated cells overview for this cell ("+selfParam.selfOrigin+") :",selfParam.cellsOverview);
15404
+ // lognow("DEBUG : selfParam.outcomingCells :",Object.keys(selfParam.outcomingCells));
15405
+ // lognow("DEBUG : selfParam.incomingCells :",Object.keys(selfParam.incomingCells));
15406
+
15407
+ // INITIATE QUORUM IS REACHED SEQUENCE
15408
+ selfParam.initiateQuorumIsReachedSequenceIfNecessary();
15409
+
15410
+ },
15411
+ listenerMessageType:"cellIsReady"
15412
+ };
15413
+ }
15414
+
15415
+
15416
+
15417
+ // ==========================================================
15418
+ // QUORUM IS REACHED SEQUENCE
15419
+
15420
+ /*private*/initiateQuorumIsReachedSequenceIfNecessary(){
15421
+
15422
+ if(getArraySize(this.cellsOverview) < this.quorumNumber) return;
15423
+ const lastStartedReadyCellOrigin=this.getLastStartedReadyCell();
15424
+ if(this.selfOrigin!=lastStartedReadyCellOrigin) return;
15425
+ if(this.quorumIsReachedSequenceIsInitiated) return;
15426
+ this.quorumIsReachedSequenceIsInitiated=true;
15427
+
15428
+ const quorumCells=copy(this.cellsOverview);
15429
+
15430
+ // TRACE
15431
+ lognow("INFO : Quorum is reached and this is the latest started cell. quorumCells :",quorumCells);
15432
+
15433
+ this.initializeModel(quorumCells);
15434
+
15435
+
15436
+
15437
+ }
15438
+
15439
+ /*private*/getLastStartedReadyCell(){
15440
+ return foreach(this.cellsOverview,(cell,cellOrigin)=>cellOrigin,null,(c1,c2)=>(c1.startTime==c2.startTime?0:(c1.startTime<c2.startTime?1:-1)));
15441
+ }
15442
+
15443
+ // ==========================================================
15444
+ // MODEL MANAGEMENT
15445
+
15446
+ /*private*/initializeModel(quorumCells){
15447
+
15448
+ const self=this;
15449
+
15450
+ const numberOfPartitions=getArraySize(quorumCells);
15451
+
15452
+ const controller=this.controller;
15453
+ const model=this.model;
15454
+
15455
+
15456
+ // We initialize the whole model :
15457
+ // (controller will handle if a persisted model exists)
15458
+ controller.initializeModelForAORTACNode(model);
15459
+
15460
+ // This must return the asked number of partitions, indexed by partition id :
15461
+ // CAUTION : The partition function MUST return ALL the objects in a partition, WHATEVER THEIR NESTING LEVEL !!!
15462
+ const modelPartitions=model.getPartitions(numberOfPartitions);
15463
+
15464
+ // We exclude this cell from the partition sending, because we already have the model partition at hand.
15465
+ this.removeLinks(modelPartitions[0].objects);
15466
+ this.doOnModelPartitionReception(modelPartitions[0]);
15467
+
15468
+ // We send the models objects to the required cells :
15469
+ let i=1;
15470
+ foreach(quorumCells, (quorumCell,quorumCellOrigin)=>{
15471
+
15472
+ const modelPartition=modelPartitions[i];
15473
+ self.removeLinks(modelPartition.objects);
15474
+
15475
+ const message=JSON.decycle({type:"modelPartition",partition:modelPartition});
15476
+
15477
+ // TRACE
15478
+ lognow(`INFO : Cell ${self.selfOrigin} is sending a partition to cell ${quorumCellOrigin}...`,message);
15479
+
15480
+ self.sendMessageToBlob("protocol", message,
15481
+ {isOriginatingCell:true, destinationCellsOrigins:[quorumCellOrigin], includeIncomingConnectionInTransmission:false, isRequest:false});
15482
+
15483
+ i++;
15484
+ },(quorumCell,quorumCellOrigin)=>quorumCellOrigin!=self.selfOrigin);
15485
+
15486
+
15487
+ // Each quorum member cell is responsible for a model partition
15488
+ // Then the sattelite cells will be handling the duplication
15489
+
15490
+ }
15491
+
15492
+
15493
+ // HANDLE PARTITION SEQUENCE
15494
+
15495
+ /*private*/handlePartitionSequence(){
15496
+
15497
+ // 2- We wait to receive the hello request of the incoming cell connection :
15498
+ getOrCreateEmptyAttribute(
15499
+ this.onReceiveMessageListeners["both"]["protocol"],"modelPartition")["forRequestsIssuedByOthers"]={
15500
+ execute:(selfParam, messageParam, server, clientSocket)=>{
15501
+
15502
+ // If this cell is in the destinations of this message, we pass it along and then we do what it says :
15503
+ selfParam.sendMessageToBlob("protocol",messageParam);
15504
+
15505
+ if(contains(messageParam.destinationCellsOrigins,selfParam.selfOrigin)){
15506
+
15507
+ const message=JSON.recycle(messageParam);
15508
+
15509
+ selfParam.doOnModelPartitionReception(message.partition);
15510
+
15511
+ }
15512
+
15513
+ },
15514
+ listenerMessageType:"modelPartition"
15515
+ };
15516
+
15517
+ }
15518
+
15519
+
15520
+ /*private*/doOnModelPartitionReception(partition){
15521
+
15522
+ // TRACE
15523
+ lognow(`INFO : Incoming partition for cell ${this.selfOrigin}. Updating local model...`,partition);
15524
+ lognow(`>>>>`,stringifyObject(JSON.decycle(partition.objects),1));
15525
+
15526
+
15527
+
15528
+ }
15529
+
15530
+
15531
+ /*private*/removeLinks(linkedObjects, currentObject=null){
15532
+
15533
+ const self=this;
15534
+
15535
+ if(!currentObject){
15536
+ foreach(linkedObjects, obj=>{
15537
+ self.removeLinksOnSingleObject(linkedObjects, obj);
15538
+ });
15539
+ }else{
15540
+ self.removeLinksOnSingleObject(linkedObjects, currentObject);
15541
+ }
15542
+
15543
+ return linkedObjects;
15544
+ }
15545
+
15546
+ /*private*/removeLinksOnSingleObject(linkedObjects, currentObject){
15547
+
15548
+ const self=this;
15549
+ foreach(currentObject, (attr,attrNameOrIndex)=>{
15550
+ // We only remove links to the objects not in the partition
15551
+ if(isClassObject(attr)){
15552
+ let aortacId=attr.aortacId;
15553
+ if(!aortacId){
15554
+ aortacId=getUUID();
15555
+ attr.aortacId=aortacId;
15556
+ }
15557
+ if(!contains(linkedObjects,attr)){
15558
+ currentObject[attrNameOrIndex]=aortacId+"@aortacId";
15559
+ // CAUTION : No need for recursive call here, because the partition function returns ALL the objects in a partition,
15560
+ // WHATEVER THEIR NESTING LEVEL !!!
15561
+ }
15562
+ }else{
15563
+ // However, we need a recursive call for any simple object or array that may reference a class object in another partition :
15564
+ self.removeLinksOnSingleObject(linkedObjects, attr);
15565
+ }
15566
+ },obj=>(isObject(obj) || isArray(obj)));
15567
+
15568
+ }
15569
+
15570
+
15571
+ // ******************************************************************
15572
+
15573
+ /*private*/collectDependencies(inputObjects){
15574
+
15575
+ ////
15576
+ lognow("DEBUG : collectDependencies()...",inputObjects);
15577
+
15578
+
15579
+ }
15580
+
15581
+ /*private*/repercutChangesIfNeeded(inputObjects){
15582
+
15583
+
15584
+ ////
15585
+ lognow("DEBUG : repercutChangesIfNeeded()...",inputObjects);
15586
+
15587
+
15588
+ }
15589
+
15590
+
15591
+ }
15592
+
15593
+
15594
+
15595
+ // ******************************************************************
15596
+
15597
+ // Public static hydration method :
15598
+ window.ao=(incompleteModelObjectToDecorate)=>{
15599
+
15600
+ const localServerCell=window.aortacCServerNodeInstance;
15601
+ if(!localServerCell){
15602
+ // TRACE
15603
+ lognow("ERROR : No AORTACCServerNode singleton instance. Doing nothing on the model object.");
15604
+ return incompleteModelObjectToDecorate;
15605
+ }
15606
+
15607
+ const liveModelObjects=localServerCell.liveModelObjects;
15608
+
15609
+ // First we clone the object, for its attriutes values information :
15610
+ const clonedObject=clone(incompleteModelObjectToDecorate);
15611
+
15612
+ // Then we replace all its methods :
15613
+ foreach(clonedObject, (method, methodName)=>{
15614
+ clonedObject[methodName]=new Proxy(method,
15615
+ {
15616
+ apply: function(methodToEnhance, thisArg, argumentsList) {
15617
+
15618
+ const inputObjects=[thisArg];
15619
+ inputObjects.push(...argumentsList);
15620
+ localServerCell.collectDependencies(inputObjects);
15621
+
15622
+ // // --- Treatment BEFORE function execution ---
15623
+ // console.log(`Calling function "${methodToEnhance.name}" with arguments: ${argumentsList}`);
15624
+
15625
+ // Call the original function (target) with its intended 'this' context (thisArg)
15626
+ // and arguments (argumentsList) using Reflect.apply
15627
+ const result = Reflect.apply(methodToEnhance, thisArg, argumentsList);
15628
+
15629
+ // // --- Treatment AFTER function execution ---
15630
+ // console.log(`Function "${methodToEnhance.name}" returned: ${result}`);
15631
+
15632
+ inputObjects.push(result);
15633
+ localServerCell.repercutChangesIfNeeded(inputObjects);
15634
+
15635
+ return result;
15636
+ },
15637
+ }
15638
+ );
15639
+ },attribute=>isFunction(attribute));
15640
+
15641
+ return clonedObject;
15642
+ };
15643
+
15644
+
15645
+
15646
+ window.getAORTACServerNode=function(quorumNumber=1,selfOrigin="ws://127.0.0.1:40000", outcomingCellsOrigins=[], model, controller){
15647
+ //return new AORTACServerNode("node_"+getUUID("short"), selfOrigin, outcomingCellsOrigins, model, controller);
15648
+ if(window.aortacCServerNodeInstance){
15649
+ // TRACE
15650
+ throw new Error("ERROR : The AORTACCServerNode singleton instance already exists. It cannot be instantiated again in the same process. Aborting.");
15651
+ }
15652
+ window.aortacCServerNodeInstance=new AORTACServerCell(quorumNumber, selfOrigin, outcomingCellsOrigins, model, controller);
15653
+ return window.aortacCServerNodeInstance;
15654
+ }
15655
+
15656
+
15657
+
15658
+
15659
+
15660
+
15661
+
15662
+ // ==================================================================================================================
15663
+
15664
+
15665
+ // AORTAC CLIENT
15666
+
15667
+
15668
+
15669
+ //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
15670
+
15671
+ // New implementation :
15672
+
15673
+ class AORTACClientCell{
15674
+
15675
+ constructor(serverNodeOrigin, model, view, isNodeContext=false){
15676
+
15677
+ }
15678
+
15679
+ }
15680
+
15681
+
15682
+
15683
+
15684
+ window.getAORTACClient=function(serverNodeOrigin="ws://127.0.0.1:40000", model, view, isNodeContext=false){
15685
+ //return new AORTACClient("client_"+getUUID(), serverNodeOrigin, model, view, isNodeContext);
15686
+ return new AORTACClientCell(serverNodeOrigin, model, view, isNodeContext);
15687
+ }
15688
+
15689
+
15690
+
15691
+
15692
+ /* ## Utility global methods in a javascript, console (nodejs) or vanilla javascript with no browser environment.
15693
+ *
15694
+ * This set of methods gathers utility generic-purpose methods usable in any JS project.
15695
+ * Several authors of snippets published freely on the Internet contributed to this library.
15696
+ * Feel free to use/modify-enhance/publish them under the terms of its license.
15697
+ *
15698
+ * # Library name : «aotrautils»
15699
+ * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
15700
+ * # Author name : Jérémie Ratomposon (massively helped by his native country free education system)
15701
+ * # Author email : info@alqemia.com
15702
+ * # Organization name : Alqemia
15703
+ * # Organization email : admin@alqemia.com
15704
+ * # Organization website : https://alqemia.com
15705
+ *
15706
+ *
15707
+ */
15708
+
15709
+
15710
+
15711
+ // COMPATIBILITY browser javascript / nodejs environment :
15712
+ if(typeof(window)==="undefined") window=global;
15713
+
15714
+
15715
+
15716
+
15717
+ // OLD : socket.io :
15718
+ // https://stackoverflow.com/questions/31156884/how-to-use-https-on-node-js-using-express-socket-io
15719
+ // https://stackoverflow.com/questions/6599470/node-js-socket-io-with-ssl
15720
+ // https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener
15721
+ // https://socket.io/docs/v4/client-socket-instance/
15722
+
15723
+ // NEW : ws :
15724
+ // https://github.com/websockets/ws#installing
15725
+ // https://github.com/websockets/ws/blob/master/doc/ws.md#event-message
15726
+ // ON BROWSER SIDE : Native Websockets :
15727
+ // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
15728
+ // https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications
15729
+
15730
+ // We have to import both implementations, regardless of which one is chosen :
15731
+ //Socket=require("socket.io");
15732
+ //WebSocket=require("ws");
15733
+
15734
+
15735
+
15736
+
15737
+ // =================================================================================
15738
+ // NODEJS UTILS
15739
+
15740
+ const FILE_ENCODING="utf8";
15741
+
15742
+ const ADD_CORS_HEADER=true;
15743
+
15744
+
15745
+
15746
+ if(typeof(require)!="undefined" && typeof(fs)!="undefined" )
15747
+ window.fs=require("fs");
15748
+
15749
+
15750
+
15751
+
15752
+ // Nodejs filesystem utils :
15753
+ if(typeof(fs)==="undefined"){
15754
+ // TRACE
15755
+ console.log("WARN : Could not find the nodejs dependency «fs», aborting persister setup.");
15756
+ getPersister=()=>{ return null; };
15757
+
15758
+ }else{
15759
+
15760
+
15761
+ getPersister=function(dataDirPath,prefix=""){
15762
+
15763
+ let self={
15764
+
15765
+ dataDirPath:dataDirPath,
15766
+ prefix:prefix,
15767
+
15768
+ //FILE_NAME_PATTERN:"data.clientId.repositoryName.json",
15769
+ /*private*/getPath:function(clientId="noclient", repositoryName="norepository"){
15770
+ // let path=self.FILE_NAME_PATTERN.replace(new RegExp("@clientId@","g"),clientId);
15771
+ let path=`${self.dataDirPath}`
15772
+ + (blank(self.prefix)?"":(self.prefix+"."))
15773
+ +`${clientId}.${repositoryName}.json`;
15774
+ return path;
15775
+ },
15776
+
15777
+ readTreeObjectFromFile:function(clientId="noclient", repositoryName="norepository", keepClassName=false){
15778
+
15779
+ let path=self.getPath(clientId,repositoryName);
15780
+ let resultFlat=null;
15781
+
15782
+ try{
15783
+
15784
+ resultFlat=fs.readFileSync(path, FILE_ENCODING);
15785
+
15786
+ }catch(error){
15787
+ // TRACE
15788
+ console.log("ERROR : Could not read file «"+path+"».");
15789
+
15790
+ return null;
15791
+ }
15137
15792
 
15138
15793
 
15139
15794
  let resultData={};
@@ -15153,8 +15808,6 @@ if(typeof(fs)==="undefined"){
15153
15808
 
15154
15809
  saveDataToFileForClient:function(clientId,repositoryName,dataFlatForClient,forceKeepUnflatten=false,doOnSuccess=null){
15155
15810
 
15156
-
15157
-
15158
15811
  if(!empty(dataFlatForClient) && !isFlatMap(dataFlatForClient) && !forceKeepUnflatten){
15159
15812
  dataFlatForClient=getAsFlatStructure(dataFlatForClient);
15160
15813
  }
@@ -15170,7 +15823,6 @@ if(typeof(fs)==="undefined"){
15170
15823
 
15171
15824
 
15172
15825
  let path=self.getPath(clientId,repositoryName);
15173
-
15174
15826
  fs.writeFile(path, dataFlatStr, FILE_ENCODING, (error) => {
15175
15827
  if(error){
15176
15828
  // TRACE
@@ -15190,6 +15842,16 @@ if(typeof(fs)==="undefined"){
15190
15842
  }
15191
15843
 
15192
15844
 
15845
+ window.fileExists=(filePath)=>{
15846
+ if(typeof(fs)=="undefined"){
15847
+ // TRACE
15848
+ lognow("ERROR : «fs» node dependency is not available ! Cannot test if file exists.");
15849
+ return null;
15850
+ }
15851
+ return fs.existsSync(filePath);
15852
+ };
15853
+
15854
+
15193
15855
 
15194
15856
  // Nodejs server launching helper functions :
15195
15857
  //Networking management :
@@ -15267,6 +15929,47 @@ getConsoleParam=function(index=0, argsOffset=0){
15267
15929
 
15268
15930
 
15269
15931
 
15932
+ window.getConsoleCLI=(doOnCommands={"makeSandiwch":()=>{}}, promptText="Enter command> ")=>{
15933
+
15934
+ const readline = require("node:readline");
15935
+
15936
+ const cliInterface = readline.createInterface({
15937
+ input: process.stdin,
15938
+ output: process.stdout,
15939
+ prompt: nonoull(promptText,"Enter command> ")
15940
+ });
15941
+
15942
+ cliInterface.prompt();
15943
+
15944
+ cliInterface.on("line", (line) => {
15945
+ const input = line.trim();
15946
+ switch (input) {
15947
+ case "quit":
15948
+ console.log("Bye!");
15949
+ cliInterface.close();
15950
+ break;
15951
+ case "help":
15952
+ console.log("Available commands: quit, help and :", Object.keys(doOnCommands));
15953
+ break;
15954
+ default:
15955
+ if(doOnCommands) doOnCommands[input]();
15956
+ break;
15957
+ }
15958
+
15959
+ // Show the prompt again :
15960
+ cliInterface.prompt();
15961
+
15962
+ }).on("close", () => {
15963
+ process.exit(0);
15964
+ });
15965
+
15966
+ return cliInterface
15967
+ };
15968
+
15969
+
15970
+
15971
+
15972
+
15270
15973
  // NODE ONLY SERVER / CLIENTS :
15271
15974
  WebsocketImplementation={
15272
15975
 
@@ -15409,9 +16112,8 @@ WebsocketImplementation={
15409
16112
 
15410
16113
  // Join room server part protocol :
15411
16114
  nodeServerInstance.receive("protocol",(message, clientSocket)=>{
15412
- if(message.type!=="joinRoom" || !clientSocket) return;
15413
16115
  nodeServerInstance.addToRoom(clientSocket, message.clientRoomTag);
15414
- });
16116
+ },{listenerMessageType:"joinRoom"});
15415
16117
 
15416
16118
  // To make the server aware of the clients connections states :
15417
16119
  nodeServerInstance.serverSocket.on("close", function close() {
@@ -15465,6 +16167,12 @@ WebsocketImplementation={
15465
16167
  rejectUnauthorized:false, // (THIS IS A KNOWN SECURITY BREACH)
15466
16168
  secure: isSecure
15467
16169
  });
16170
+
16171
+ clientSocket.addEventListener("error", error=>{
16172
+ // TRACE
16173
+ lognow("ERROR : (NODEJS) A WebSocket client error occurred while trying to connect to server:", error.message);
16174
+ });
16175
+
15468
16176
  }else{
15469
16177
  // NOW : socket.io :
15470
16178
  //client on server-side:
@@ -15475,6 +16183,12 @@ WebsocketImplementation={
15475
16183
  // NO : clientSocket=new Socket.Client(serverURL + ":" + port,{timeout: timeout, secure: isSecure});
15476
16184
  clientSocket=io(serverURL + ":" + port,{timeout: timeout, secure: isSecure, autoConnect:true});
15477
16185
  // UNUSEFUL (since we have the autoconnect:true option) : clientSocket.connect();
16186
+
16187
+ clientSocket.on("connect_error", error=>{
16188
+ // TRACE
16189
+ lognow("ERROR : (NODEJS) A SocketIO client error occurred while trying to connect to server:", error.message);
16190
+ });
16191
+
15478
16192
  }
15479
16193
  }
15480
16194
 
@@ -15508,10 +16222,22 @@ WebsocketImplementation={
15508
16222
  // - FIRST GO TO THE HTTPS:// SERVER ADDRESS WITH BROWSER
15509
16223
  // - THEN ADD THE SECURITY EXCEPTION IN THE BROWSER !
15510
16224
  clientSocket=new WebSocket(serverURL+":"+port,["ws","wss"]);
16225
+
16226
+ clientSocket.addEventListener("error", error=>{
16227
+ // TRACE
16228
+ lognow("ERROR : (BROWSER) A WebSocket client error occurred while trying to connect to server:", error.message);
16229
+ });
16230
+
15511
16231
  }else if(typeof(io)!=="undefined"){
15512
- // OLD SYNTAX :clientSocket=io.connect(serverURL + ":" + port,{timeout: timeout, secure: isSecure});
15513
- // ALTERNATIVE :
15514
- clientSocket=io(serverURL + ":" + port,{timeout: timeout, secure: isSecure});
16232
+ // OLD SYNTAX :clientSocket=io.connect(serverURL + ":" + port,{timeout: timeout, secure: isSecure});
16233
+ // ALTERNATIVE :
16234
+ clientSocket=io(serverURL + ":" + port,{timeout: timeout, secure: isSecure});
16235
+
16236
+ clientSocket.on("connect_error", error=>{
16237
+ // TRACE
16238
+ lognow("ERROR : (BROWSER) A SocketIO client error occurred while trying to connect to server:", error.message);
16239
+ });
16240
+
15515
16241
  }
15516
16242
 
15517
16243
  // BROWSER CLIENT INSTANCE :
@@ -15817,7 +16543,7 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
15817
16543
  // DOES NOT WORK : USE Java FusrodaServer instead :
15818
16544
  //
15819
16545
  ///*FUSRODA server stands from FSRD SERVER, for Fucking Simple Remote Desktop SERVER*/
15820
- //createFusrodaServer=function(certPathParam=null,keyPathParam=null,portParam=6080){
16546
+ //createFusrodaServer=function(certPathParam=null,keyPathParam=null,portParam=4000){
15821
16547
  //
15822
16548
  // // https://www.npmjs.com/package/screenshot-desktop
15823
16549
  // // https://github.com/octalmage/robotjs
@@ -15854,7 +16580,7 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
15854
16580
  //
15855
16581
  // screenshot().then((img) => {
15856
16582
  //
15857
- // const data={image:img,messageType:"imageData"};
16583
+ // const data={image:img,listenerMessageType:"imageData"};
15858
16584
  // serverParam.send("message", data, "clients");
15859
16585
  //
15860
16586
  // serverParam.isScreenshotStarted=false;
@@ -15876,7 +16602,7 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
15876
16602
  // // /*DO NOTHING*/
15877
16603
  // // };
15878
16604
  // // const server={};
15879
- // // server.start=(port=6080)=>{
16605
+ // // server.start=(port=4000)=>{
15880
16606
  // // server.httpServer=launchNodeHTTPServer(port, doOnConnect, doOnFinalizeServer, sslOptions);
15881
16607
  // // };
15882
16608
  //
@@ -16278,871 +17004,287 @@ appendGetParameters=function(apiURL, namedArgsParam){
16278
17004
 
16279
17005
 
16280
17006
 
16281
- //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
16282
17007
 
16283
- AORTAC_OUTCOMING_SERVERS_CONNECTION_GLOBAL_TIMEOUT=30000;
16284
- REQUESTS_IDS_HISTORY_SIZE=10;
16285
17008
 
16286
- class AORTACNode{
16287
-
16288
- constructor(nodeId, selfOrigin="127.0.0.1:40000",outcomingNodesOrigins=[], model, controller, allowAnonymousNodes=true, sslConfig={/*OPTIONAL*/certPath:null,/*OPTIONAL*/keyPath:null}){
16289
-
16290
- this.nodeId=nodeId;
16291
- this.selfOrigin=selfOrigin;
16292
- this.sslConfig=sslConfig;
16293
- this.outcomingNodesOrigins=outcomingNodesOrigins;
16294
- this.allowAnonymousNodes=allowAnonymousNodes;
16295
-
16296
- this.model=model; // must implement functions
16297
- this.controller=controller; // Must implement functions
16298
-
16299
- this.server=null;
16300
- this.clients={};
16301
- this.incomingServers={};
16302
- this.outcomingServers={};
16303
- this.authorizedNodesIds=[this.nodeId];
16304
-
16305
- this.listeners={"protocol":[],"cluster":[],"neighbors":[],"inputs":[]};
16306
-
16307
- this.executedRequestIdsHistory=[];
16308
-
16309
- this.isConnectedToCluster=false;
16310
-
16311
- this.modelObjectsDirectory={};
16312
- this.objectsIndexesForClients={};
16313
17009
 
16314
- this.addProtocolListeners();
16315
17010
 
16316
- // TRACE
16317
- lognow(`AORTAC node created with id ${this.nodeId}.`);
16318
- }
16319
-
16320
- start(){
16321
17011
 
16322
- const splits=splitURL(this.selfOrigin);
16323
- let port=nonull(splits.port,"40000");
16324
-
16325
- const self=this;
16326
- this.server = initNodeServerInfrastructureWrapper(
16327
- // On each client connection :
16328
- null,
16329
- // On client finalization :
16330
- function(server){
16331
- self.server=server;
16332
-
16333
- // Listeners handling ;
16334
- foreach(Object.keys(self.listeners), channelName=>{
16335
- self.server.receive(channelName, (message, clientSocket)=>{
16336
-
16337
- // INCOMING NODES LISTENERS HOOK :
16338
- // TRACE
16339
- console.log("INFO : SERVER : Client or incoming node has sent a "+channelName+" message: ", message);
16340
- foreach(self.listeners[channelName], listener => {
16341
- listener.execute(self, message, self.server, clientSocket);
16342
- },(listener)=>(message.type===listener.messageType));
16343
-
16344
- });
16345
- });
16346
-
16347
- // OLD : self.connectToOutcomingServers(self, server).then((server)=>{ self.doOnConnectedToCluster(); });
16348
-
16349
-
16350
- }, port, this.sslConfig.certPath, this.sslConfig.keyPath);
16351
-
16352
- this.server.serverManager.start();
16353
-
16354
- // TRACE
16355
- lognow(`AORTAC node started with id ${this.nodeId}.`);
17012
+ /* ## Utility network global methods in a javascript, console (nodejs), or vanilla javascript with no browser environment.
17013
+ *
17014
+ * This set of methods gathers utility generic-purpose methods usable in any JS project.
17015
+ * Several authors of snippets published freely on the Internet contributed to this library.
17016
+ * Feel free to use/modify-enhance/publish them under the terms of its license.
17017
+ *
17018
+ * # Library name : «aotrautils»
17019
+ * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
17020
+ * # Author name : Jérémie Ratomposon (massively helped by his native country free education system)
17021
+ * # Author email : info@alqemia.com
17022
+ * # Organization name : Alqemia
17023
+ * # Organization email : admin@alqemia.com
17024
+ * # Organization website : https://alqemia.com
17025
+ *
17026
+ *
17027
+ */
16356
17028
 
16357
17029
 
16358
- return this;
16359
- }
16360
-
16361
-
16362
- connect(){
16363
-
16364
- const self=this;
16365
- self.connectToOutcomingServers(self).then((server)=>{ self.doOnConnectedToCluster(); });
16366
17030
 
16367
- return this;
16368
- }
16369
-
16370
-
16371
-
16372
-
16373
- /*private*/addProtocolListeners(){
16374
-
16375
- const self=this;
16376
- // ============== OTHER SERVER / CLIENT REGISTRATION PHASE ==============
16377
- // - OTHER SERVER REGISTRATION PHASE :
16378
- this.listeners["protocol"].push({
16379
- messageType:"request.register.server.incoming",
16380
- execute:(self, message, server, clientSocket)=>{
16381
- const incomingServerNodeId=message.nodeId;
16382
-
16383
- // TRACE
16384
- lognow(` (${self.nodeId}) Receiving registering request from node ${incomingServerNodeId}...`);
16385
-
16386
- if(!self.allowAnonymousNodes && self.isInAuthorizedNodes(incomingServerNodeId)){
16387
- // TRACE
16388
- lognow("WARN : Cannot accept incoming server with node id «"+incomingServerNodeId+"» because it's not in the authorized nodes list.");
16389
- return;
16390
- }
16391
- if(!contains(self.authorizedNodesIds,incomingServerNodeId)) self.authorizedNodesIds.push(incomingServerNodeId);
16392
-
16393
- // TRACE
16394
- lognow(` (${self.nodeId}) Adding registering node ${incomingServerNodeId} to incoming servers...`);
16395
-
16396
- self.incomingServers[incomingServerNodeId]={clientSocket:clientSocket};
16397
-
16398
- const welcomeResponse={
16399
- nodeId:self.nodeId, // MUST BE SELF ! ELSE THE REGISTERING CLIENT WON'T KNOW WHICH ID IT HAS BEEN REGISTERED TO !
16400
- type:"response.register.server.incoming",
16401
- authorizedNodesIds:self.authorizedNodesIds
16402
- };
16403
- server.send("protocol", welcomeResponse, null, clientSocket);
16404
-
16405
- // TRACE
16406
- lognow(` (${self.nodeId}) Sent registering response to node ${incomingServerNodeId}.`);
16407
- }
16408
- });
16409
-
16410
- // - CLIENT REGISTRATION PHASE : (for clients to the server, NOT INCOMING NODES !)
16411
- this.handleClients();
16412
-
16413
- }
16414
-
16415
- /*private*/handleClients(){
16416
- const self=this;
16417
-
16418
- // --------- PROTOCOL HANDLING ---------
16419
-
16420
- this.listeners["protocol"].push({
16421
- messageType:"request.register.client",
16422
- execute:(self, message, server, clientSocket)=>{
16423
- const clientId=message.clientId;
16424
-
16425
- self.clients[clientId]={clientSocket:clientSocket};
17031
+ // COMPATIBILITY browser javascript / nodejs environment :
17032
+ if(typeof(window)==="undefined") window=global;
16426
17033
 
16427
- // TRACE
16428
- lognow(` (${self.nodeId}) Receiving non-cluster client registration request from client ${clientId}.`);
16429
-
16430
- const welcomeClientResponse={
16431
- nodeId:self.nodeId, // MUST BE SELF ! ELSE THE REGISTERING CLIENT WON'T KNOW WHICH ID IT HAS BEEN REGISTERED TO !
16432
- type:"response.register.client",
16433
- // If necessary we also send them our model part :
16434
- partialModelString:(message.isReferenceNode?null:JSON.stringifyDecycle(self.model)),
16435
- isReferenceNode:(!!message.isReferenceNode),
16436
- };
16437
- server.send("protocol", welcomeClientResponse, null, clientSocket);
16438
- }
16439
- });
16440
-
16441
- this.listeners["protocol"].push({
16442
- messageType:"request.unregister.client",
16443
- execute:(self, message, server, clientSocket)=>{
16444
- const clientId=message.clientId;
16445
-
16446
- delete self.clients[clientId];
16447
17034
 
16448
- // TRACE
16449
- lognow(` (${self.nodeId}) Receiving non-cluster client unregistration request from client ${clientId}.`);
16450
-
16451
- const unwelcomeClientResponse={
16452
- nodeId:self.nodeId, // MUST BE SELF ! ELSE THE REGISTERING CLIENT WON'T KNOW WHICH ID IT HAS BEEN REGISTERED TO !
16453
- type:"response.unregister.client",
16454
- };
16455
- server.send("protocol", unwelcomeClientResponse, null, clientSocket);
16456
- }
16457
- });
16458
-
16459
-
16460
- this.listeners["protocol"].push({
16461
- messageType:"request.interrogation.client",
16462
- execute:(self, message, server, clientSocket)=>{
16463
- const clientId=message.clientId;
16464
-
16465
- // TRACE
16466
- lognow(` (${self.nodeId}) Receiving non-cluster client interrogation request from client ${clientId}.`);
16467
-
16468
- const clientBoundaries=message.boundaries;
16469
-
16470
- // We ask the whole cluster to find the requested objects :
16471
- const modelSeekObjectsRequest={
16472
- type:"request.model.seekObjects",
16473
- originatingClientId:clientId,
16474
- clientBoundaries:clientBoundaries,
16475
- };
16476
- this.propagateToCluster(modelSeekObjectsRequest);
16477
17035
 
16478
- self.objectsIndexesForClients[clientId]={};
16479
-
16480
- }
16481
- });
16482
-
16483
- // --------- INPUTS HANDLING ---------
16484
-
16485
- this.listeners["inputs"].push({
16486
- messageType:"request.inputs.client",
16487
- execute:(self, message, server, clientSocket)=>{
16488
- const clientId=message.clientId;
16489
-
16490
- // TRACE
16491
- lognow(` (${self.nodeId}) Receiving non-cluster client inputs request from client ${clientId}.`);
16492
-
16493
- const clientInputs=message.inputs;
16494
- const clientSubBoundaries=message.subBoundaries;
17036
+ // ==================================================================================================================
16495
17037
 
16496
-
16497
- // We let the controller interpret these inputs :
16498
- const modifiedObjects=self.controller.interpretInputs(clientInputs, clientSubBoundaries);
16499
-
16500
17038
 
16501
- }
16502
- });
16503
-
17039
+ class ClientReceptionEntryPoint{
17040
+
17041
+ constructor(channelNameParam, entryPointId, clientsRoomsTag, listenerConfig, doOnIncomingMessage){
17042
+ this.channelName=channelNameParam;
17043
+ this.entryPointId=entryPointId;
17044
+ this.clientsRoomsTag=clientsRoomsTag;
17045
+ this.listenerConfig=listenerConfig;
17046
+ this.doOnIncomingMessage=doOnIncomingMessage;
16504
17047
  }
16505
-
16506
-
16507
- /*private*/addPropagationForClientsListeners(){
16508
-
16509
- const self=this;
17048
+
17049
+ // III-
17050
+ execute(eventOrMessage, clientSocket, clientReceptionEntryPoints){
16510
17051
 
16511
- // ------------------------------------- CLIENTS PROTOCOL HANDLING -------------------------------------
16512
- // Adding propagation listener :
16513
- this.setPropagation("cluster","request.model.seekObjects",(self, message, server, clientSocket)=>{
16514
-
16515
- // If we receive this request from cluster, then we try to find the requested objects
16516
- const relayNodeid=message.nodeId;
16517
- const originatingClientId=message.originatingClientId;
16518
- const clientBoundaries=message.clientBoundaries
16519
- const objectsIds=self.model.getObjectsIdsWithinBoundaries(clientBoundaries);
16520
-
16521
- // If server has not the seeked objects, it answers with an empty array.
16522
-
16523
- // TRACE
16524
- lognow(`(${self.nodeId}) Node receiving a seek objects request from relay node ${message.nodeId}...objectsIds=`,objectsIds);
16525
-
17052
+ if(!eventOrMessage){
16526
17053
  // TRACE
16527
- lognow(`(${self.nodeId}) Sending objects ids to relay node ${relayNodeid}...`);
16528
-
16529
- const modelObjectsSeekResponse={
16530
- type:"response.model.seekObject",
16531
- originatingClientId:originatingClientId,
16532
- clientBoundaries:clientBoundaries,
16533
- objectsIds:objectsIds,
16534
- nodeServerInfo:self.selfOrigin
16535
- };
16536
- this.propagateToCluster(modelObjectsSeekResponse, relayNodeid/*destination node*/);
17054
+ lognow("ERROR : No message received.");
17055
+ return;
17056
+ }
16537
17057
 
16538
- });
16539
-
16540
- // Adding propagation listener :
16541
- this.setPropagation("cluster","response.model.seekObject",(self, message, server, clientSocket)=>{
16542
-
16543
- // Here the relay server gathers the information from the other nodes, and sends it back to client ;
16544
-
16545
- const referenceNodeId=self.nodeId;
16546
- const originatingClientId=message.originatingClientId;
16547
- const originatingNodeId=message.nodeId;
16548
- const clientBoundaries=message.clientBoundaries;
16549
- const objectsIds=message.objectsIds;
16550
- const nodeServerInfo=message.nodeServerInfo;
16551
-
17058
+ if(!clientSocket){
16552
17059
  // TRACE
16553
- lognow(`(${self.nodeId}) Node receiving a seek objects request from relay node ${message.nodeId}...objectsIds=`,objectsIds);
16554
-
16555
- // Here the currently answering node fills its information :
16556
- const objectsIndexForClient=self.objectsIndexesForClients[originatingClientId];
16557
- objectsIndexForClient[originatingNodeId]={nodeServerInfo:nodeServerInfo, objectsIds:objectsIds};
16558
-
16559
-
16560
- // We check if all known nodes have answered :
16561
- if(getArraySize(self.authorizedNodesIds)-1/*Because we exclude the reference node*/<=getArraySize(objectsIndexForClient)){
16562
-
16563
- // The reference node also answers to the request to its direct client :
16564
- const objectsIdsFromReferenceNode=self.model.getObjectsIdsWithinBoundaries(clientBoundaries);
16565
- const referenceNodeServerInfo=self.selfOrigin;
16566
- objectsIndexForClient[referenceNodeId]={nodeServerInfo:referenceNodeServerInfo, objectsIds:objectsIdsFromReferenceNode};
16567
-
16568
- const client=self.clients[originatingClientId];
16569
- const clientSocket=client.clientSocket;
16570
-
16571
- // TRACE
16572
- lognow(`(${self.nodeId}) Sending objects ids to the client ${originatingClientId}...objectsIndexForClient=`,objectsIndexForClient);
16573
-
16574
- // We filter all the index entries with an empty objetcs ids array :
16575
- const objectsIndexForClientWithoutEmptyObjects={};
16576
- foreach(objectsIndexForClient,(nodeInfoAndObjectsIds, serverNodeId)=>{
16577
- objectsIndexForClientWithoutEmptyObjects[serverNodeId]=nodeInfoAndObjectsIds;
16578
- },(nodeInfoAndObjectsIds,serverNodeId)=>(!empty(nodeInfoAndObjectsIds.objectsIds)));
16579
-
16580
- // We send the final result information (objects ids) to client :
16581
- const interrogationClientResponse={
16582
- nodeId:self.nodeId,
16583
- type:"response.interrogation.client",
16584
- serversNodesIdsForModelObjectsForClient:objectsIndexForClientWithoutEmptyObjects
16585
- };
16586
- self.server.send("protocol", interrogationClientResponse, null, clientSocket);
16587
- }
16588
-
16589
- });
16590
-
16591
-
16592
- // ------------------------------------- CLIENTS INPUTS HANDLING -------------------------------------
16593
- // Adding propagation listener :
16594
-
16595
-
16596
-
16597
- // DBG
16598
- lognow(">>>>>>>>FOR CLIENTS this.listeners",this.listeners);
16599
-
16600
- }
16601
-
16602
- // /*private*/getServersNodesIdsForModelObjectsForClient(clientObjectsIds){
16603
- // const serversNodesIdsForModelObjectsForClient={};
16604
- // const self=this;
16605
- // // TODO : FIXME : INEFFICIENT !!!
16606
- // foreach(clientObjectsIds,(objectId)=>{
16607
- // foreach(self.modelObjectsDirectory,(objectsInNode, nodeId)=>{
16608
- // if(contains(objectsInNode,objectId))
16609
- // serversNodesIdsForModelObjectsForClient[objectId]=nodeId;
16610
- // });
16611
- // });
16612
- //
16613
- // return serversNodesIdsForModelObjectsForClient;
16614
- // }
17060
+ lognow("ERROR : No clientSocket to client ! Cannot do reception treatment.");
17061
+ return;
17062
+ }
16615
17063
 
16616
- /*private*/connectToOutcomingServers(self){
16617
-
16618
- const server=self.server;
16619
-
16620
- const theoreticNumberOfOutcomingServers=getArraySize(self.outcomingNodesOrigins);
16621
-
16622
- return new Promise((resolve,error)=>{
16623
-
16624
- // In case we timeout :
16625
- setTimeout(()=>{
16626
- if(self.isConnectedToCluster) return;
16627
- self.isConnectedToCluster=true;
16628
- resolve(server);
16629
- },AORTAC_OUTCOMING_SERVERS_CONNECTION_GLOBAL_TIMEOUT);
16630
-
16631
- // We try to connect to all the other outcoming servers :
16632
-
16633
- // Special case : if node has no outcoming serves, (it's the seed node), then
16634
- // it should be considered as connected to cluster nonetheless :
16635
- if(empty(self.outcomingNodesOrigins)){
16636
- self.finalizeClusterConnection(self.nodeId);
16637
- resolve(server);
16638
- return;
16639
- }
16640
-
16641
- // We try to connect to all outcoming nodes :
16642
- foreach(self.outcomingNodesOrigins, outcomingNodeOrigin=>{
16643
-
16644
- // TRACE
16645
- lognow(` (${self.nodeId}) Sending registering response to node ${outcomingNodeOrigin}...`);
16646
-
16647
- const splits=splitURL(outcomingNodeOrigin);
16648
- const outcomingNodeProtocol=nonull(splits.protocol,"ws");
16649
- const outcomingNodeHost=nonull(splits.host,"localhost");
16650
- const outcomingNodePort=nonull(splits.port,"40000");
16651
- //const isSecure=splits.isSecure; // UNUSED
16652
-
16653
- const clientInstance=initClient(true, false, (socketToServer)=>{
16654
-
16655
- // TODO : FIXME : IF CONNECTION TO SERVER FAILS, IT MUST BE ABLE TO TRY AGAIN LATER !
16656
-
16657
- // TRACE
16658
- lognow(` (${self.nodeId}) Sending registering request to outcoming node...`);
16659
-
16660
- const helloRequest={
16661
- nodeId:self.nodeId,
16662
- type:"request.register.server.incoming"
16663
- };
16664
- socketToServer.send("protocol", helloRequest);
16665
-
16666
-
16667
- // We place a listener from outcoming node on this client instance to this outcoming node :
16668
- socketToServer.receive("protocol",(message)=>{
16669
- if(message.type==="response.register.server.incoming"){
16670
-
16671
- // TRACE
16672
- lognow(` (${self.nodeId}) Receving registering response from requested outcoming node...`);
16673
-
16674
- const welcomeNodeId=message.nodeId;
16675
- const duplicatesFreeAuthorizedNodesIds=[...new Set([...self.authorizedNodesIds, ...message.authorizedNodesIds, welcomeNodeId ])];
16676
- self.authorizedNodesIds=duplicatesFreeAuthorizedNodesIds;
16677
-
16678
- self.outcomingServers[welcomeNodeId]={clientInstance:clientInstance};
16679
-
16680
- // Once we have registered to all the outcoming nodes :
16681
- const currentNumberOfOutcomingServers=getArraySize(self.outcomingServers);
16682
- if(!self.isConnectedToCluster && theoreticNumberOfOutcomingServers<=currentNumberOfOutcomingServers){
16683
- //self.finalizeClusterConnection(welcomeNodeId);
16684
- self.finalizeClusterConnection(self.nodeId);
16685
- resolve(server);
16686
- }
16687
- }
16688
- });
16689
-
16690
- // Listeners handling ;
16691
- foreach(Object.keys(self.listeners), channelName=>{
16692
- socketToServer.receive(channelName, (message, clientSocket)=>{
16693
-
16694
- // OUTCOMING NODES LISTENERS HOOK :
16695
- // TRACE
16696
- console.log("INFO : SERVER : Outcoming node has sent a "+channelName+" message: ", message);
16697
- foreach(self.listeners[channelName], listener => {
16698
- listener.execute(self, message, self.server, clientSocket);
16699
- },(listener)=>(message.type===listener.messageType));
16700
-
16701
- });
16702
- });
16703
-
16704
-
16705
- }, outcomingNodeProtocol+"://"+outcomingNodeHost, outcomingNodePort, false);
16706
- clientInstance.client.start();
16707
-
16708
- });
16709
-
16710
- });
16711
-
16712
- }
16713
-
16714
- /*private*/finalizeClusterConnection(welcomeNodeId){
16715
- // TRACE
16716
- lognow(` (${this.nodeId}) Propagating this new node to the whole cluster...`);
17064
+ const dataWrapped=WebsocketImplementation.getMessageDataBothImplementations(eventOrMessage);
16717
17065
 
16718
- // We propagate the new node id to the whole cluster :
16719
- const newNodeRequest={
16720
- nodeId:welcomeNodeId,
16721
- type:"request.node.new"
16722
- };
16723
- this.propagateToCluster(newNodeRequest);
17066
+ // We check if the message is in the right channel (channel information is stored in exchanged wrapped data) :
17067
+ const channelName=this.channelName;
17068
+ if(dataWrapped.channelName && dataWrapped.channelName!==this.channelName){
17069
+ return;
17070
+ }
16724
17071
 
16725
- this.isConnectedToCluster=true;
17072
+ // We check if the message is in the right room (r)oom information is stored in client socket object) :
17073
+ if(!WebsocketImplementation.isInRoom(clientSocket, this.clientsRoomsTag)) return;
16726
17074
 
16727
- this.addPropagationListeners();
16728
- this.addPropagationForClientsListeners();
16729
- }
16730
-
16731
-
16732
17075
 
16733
-
16734
- /*public*/doOnConnectedToCluster(){
16735
-
16736
- // TRACE
16737
- lognow(`(${this.nodeId}) Node is connected to cluster...`);
16738
-
16739
- // The node asks the cluster for its model partition :
17076
+ // We check if the message matches the required message type :
17077
+ if( this.listenerConfig && this.listenerConfig.listenerMessageType && dataWrapped.type
17078
+ && this.listenerConfig.listenerMessageType!==dataWrapped.type){
17079
+ return;
17080
+ }
16740
17081
 
16741
- // At this point, the new node does not know the state of the model in the cluster, only cluster nodes know.
17082
+ // We check if the message matches the condition :
17083
+ const dataLocal=dataWrapped.data;
17084
+ if(this.listenerConfig.condition && !this.istenerConfig.condition(dataLocal, clientSocket)){
17085
+ return;
17086
+ }
16742
17087
 
16743
- // TRACE
16744
- lognow(`(${this.nodeId}) Node is asking to its model partition...`);
17088
+ // We check if we have the same message id:
17089
+ const messageIdLocal=this.listenerConfig.messageId;
17090
+ if(messageIdLocal!==dataLocal.messageId){
17091
+ return;
17092
+ }
17093
+
17094
+ // At this point, message is the right one, so it can be processed :
17095
+ // --------------------------------
16745
17096
 
16746
- // New node asks for its model partition :
17097
+ // We remove one-time usage listeners :
17098
+ // We remove BEFORE executing doOnIncomingMessage(), because in this reception entry point function we can have other listener registration UNDER THE SAME ID !
17099
+ if(this.listenerConfig && this.listenerConfig.destroyListenerAfterReceiving)
17100
+ remove(clientReceptionEntryPoints, this);
16747
17101
 
16748
- // - First we need to know which server has the biggest model :
16749
- const modelSizeRequest={
16750
- type:"request.model.getSize",
16751
- };
16752
- this.propagateToCluster(modelSizeRequest);
17102
+ if(this.doOnIncomingMessage)
17103
+ this.doOnIncomingMessage(dataLocal, clientSocket);
16753
17104
 
16754
17105
  }
16755
17106
 
16756
-
16757
- /*private*/addPropagationListeners(){
16758
-
16759
- // Adding propagation listener for acknowledging new node added to cluster :
16760
- this.setPropagation("cluster","request.node.new", (self, message, server, clientSocket)=>{
16761
- const newNodeId=message.nodeId;
16762
- // TRACE
16763
- lognow(` (${self.nodeId}) Acknowledging new node ${newNodeId} added to cluster...`);
16764
- if(!contains(self.authorizedNodesIds,newNodeId)) self.authorizedNodesIds.push(newNodeId);
16765
- });
16766
-
16767
- // Adding propagation listener if a node receives a model size request :
16768
- this.setPropagation("cluster","request.model.getSize",(self, message, server, clientSocket)=>{
16769
- const modelSize=self.model.getModelSize();
16770
- // TRACE
16771
- lognow(`(${self.nodeId}) Node gives its model size to node ${message.nodeId}, modelSize=${modelSize}...`);
16772
- return modelSize;
16773
- },"response.model.getSize");
16774
-
16775
- // Adding propagation listener :
16776
- this.modelSizes={};
16777
- this.setPropagation("cluster","response.model.getSize",(self, message, server, clientSocket)=>{
16778
-
16779
- const modelSizes=self.modelSizes;
16780
- const currentClusterSize=getArraySize(self.authorizedNodesIds);
16781
-
16782
- const concernedNodeId=message.nodeId;
16783
- const modelSize=message.result;
16784
-
16785
- let currentNumberOfAnswers=getArraySize(modelSizes);
16786
-
16787
- if(currentNumberOfAnswers<=currentClusterSize-1){// -1 because we want to exclude this node also !
16788
- modelSizes[concernedNodeId]=modelSize;
16789
- }
17107
+ }
16790
17108
 
16791
- currentNumberOfAnswers=getArraySize(modelSizes);
16792
- // TRACE
16793
- lognow(`(${self.nodeId}) Node receiving a model size from node ${message.nodeId}...currentClusterSize=${currentClusterSize} ; modelSizes:`,modelSizes);
16794
- if(currentClusterSize-1<=currentNumberOfAnswers){
16795
- const nodeIdWithBiggestModel=Math.maxInArray(modelSizes, true);
16796
- // TRACE
16797
- lognow(`(${self.nodeId}) Node with biggest model is nodeIdWithBiggestModel=${nodeIdWithBiggestModel}...`);
16798
-
16799
- const modelPartitionRequest={
16800
- type:"request.model.partition",
16801
- };
16802
- this.propagateToCluster(modelPartitionRequest, nodeIdWithBiggestModel);
16803
- }
16804
- });
16805
-
16806
- // Adding propagation listener :
16807
- // If a node receives a model partition request :
16808
- this.setPropagation("cluster","request.model.partition",(self, message, server, clientSocket)=>{
16809
- // Each node will give a little portion of its model, according to its size :
16810
- // TRACE
16811
- lognow(`(${self.nodeId}) Node splits its model for node ${message.nodeId}...`);
16812
-
16813
- const ownModel=self.model;
16814
- const splittedModel=ownModel.split();
16815
-
16816
- // Node shares also its model objects directory with the newcomer node :
16817
- const ownModelObjectsIds=ownModel.getAllObjectsIds();
16818
- self.modelObjectsDirectory[self.nodeId]=ownModelObjectsIds;
16819
-
16820
- const modelString=JSON.stringifyDecycle(splittedModel);
16821
- const modelPartitionResponse={
16822
- type:"response.model.partition",
16823
- modelString:modelString,
16824
- modelObjectsDirectory:self.modelObjectsDirectory
16825
- };
16826
- this.propagateToCluster(modelPartitionResponse, message.nodeId);
16827
-
16828
- // This node also advertises that its model has changed to all other nodes (not the newcomer, because it's unnecessary) :
16829
- const updatemodelObjectsDirectoryRequest={
16830
- type:"request.update.modelObjectsDirectory",
16831
- ownModelObjectsIds:ownModelObjectsIds
16832
- };
16833
- this.propagateToCluster(updatemodelObjectsDirectoryRequest, null, [message.nodeId]);
16834
-
16835
- });
16836
17109
 
16837
- // Adding propagation listener :
16838
- this.setPropagation("cluster","response.model.partition",(self, message, server, clientSocket)=>{
16839
- // TRACE
16840
- lognow(`(${self.nodeId}) Node receives a model partition from node ${message.nodeId}...`,message);
16841
-
16842
- const newModel=JSON.parseRecycle(message.modelString);
16843
- self.model.replaceBy(newModel);
16844
-
16845
- // We initialize this node's model objects directory from the one froom the node tha provided it its model partition :
16846
- self.modelObjectsDirectory=message.modelObjectsDirectory;
16847
-
16848
- // Once this node has received its model partition, it updates its model objects directory
16849
- const ownModelObjectsIds=newModel.getAllObjectsIds();
16850
- self.modelObjectsDirectory[self.nodeId]=ownModelObjectsIds;
16851
-
16852
- // TRACE
16853
- lognow(`(${self.nodeId}) Node sends a model objects directory update request...ownModelObjectsIds=${ownModelObjectsIds}`);
16854
-
16855
- // And notifies the other nodes of the change :
16856
- const updatemodelObjectsDirectoryRequest={
16857
- type:"request.update.modelObjectsDirectory",
16858
- ownModelObjectsIds:ownModelObjectsIds
16859
- };
16860
- this.propagateToCluster(updatemodelObjectsDirectoryRequest);
17110
+ //
17111
+ // CLIENT INSTANCE :
17112
+ //
17113
+ class ClientInstance{
16861
17114
 
16862
- });
16863
-
16864
- this.addModelObjectsDirectoryListeners();
17115
+ constructor(clientSocket){
16865
17116
 
17117
+ this.onServerLostListeners=[];
16866
17118
 
16867
- // DBG
16868
- lognow(">>>>>>>>FOR OTHER NODES this.listeners",this.listeners);
17119
+ this.clientSocket=clientSocket;
17120
+ this.clientReceptionEntryPoints=[];
16869
17121
 
16870
17122
  }
16871
17123
 
16872
17124
 
16873
-
16874
- /*private*/addModelObjectsDirectoryListeners(){
16875
-
17125
+ sendChainable(channelNameParam, data, clientsRoomsTag=null){
16876
17126
  const self=this;
16877
17127
 
16878
- // Adding propagation listener :
16879
- // If a node receives a model objects directory update request :
16880
- this.setPropagation("cluster","request.update.modelObjectsDirectory",(self, message, server, clientSocket)=>{
16881
-
16882
- // TRACE
16883
- lognow(`(${self.nodeId}) !!!!!! Node receives a model objects directory update request from node ${message.nodeId}...`,message);
16884
-
16885
- const modelObjectsIds=message.ownModelObjectsIds;
16886
- self.modelObjectsDirectory[message.nodeId]=modelObjectsIds;
16887
-
16888
- });
17128
+ // DBG
17129
+ lognow(">>>>>>sendChainable CLIENT ("+channelNameParam+"):data:",data);
16889
17130
 
16890
-
16891
- // Adding propagation listener :
16892
- // If a node receives a model objects directory update request :
16893
- this.setPropagation("cluster","request.add.modelObjectsDirectory",(self, message, server, clientSocket)=>{
16894
-
16895
- // TRACE
16896
- lognow(`(${self.nodeId}) !!!!!! Node receives a model objects directory add request from node ${message.nodeId}...`,message);
17131
+ // We add a message id :
17132
+ const messageId=getUUID();
17133
+ data.messageId=messageId;
17134
+
17135
+ // 1) We prepare the reception :
17136
+ const resultPromise={
17137
+ // CAUTION : CANNOT STORE ANYTHING IN THE resultPromise, FOR ASYNC CALLS MAKES ITS DATA INCONSISTENT IN TIME !
17138
+ thenWhenReceiveMessageType:(channelNameForResponse, listenerConfig={listenerMessageType:"",condition:()=>true}, doOnIncomingMessageForResponse)=>{
16897
17139
 
16898
- const newObjectsIds=message.newObjectsIds;
16899
- self.modelObjectsDirectory[message.nodeId].push(...newObjectsIds);
17140
+ listenerConfig=nonull(listenerConfig,{listenerMessageType:"",condition:()=>true});
17141
+ const listenerId=nonull(listenerConfig.listenerMessageType,"");
17142
+
17143
+ // I-
17144
+ //
17145
+ self.receive(channelNameForResponse, doOnIncomingMessageForResponse,
17146
+ clientsRoomsTag, listenerId, {messageId:messageId, destroyListenerAfterReceiving:true});
17147
+ //
17148
+
17149
+ return resultPromise;
17150
+ }
17151
+ };
16900
17152
 
16901
- });
17153
+ // 2) We send the data :
17154
+ this.send(channelNameParam, data, clientsRoomsTag);
16902
17155
 
16903
- }
16904
-
16905
-
16906
- /*public*/sendUpdatedObjects(modifiedObjects, modifiedObjectsIds){
16907
-
16908
- if(empty(modifiedObjects)) return modifiedObjects;
16909
17156
 
16910
- // Then we send back the modified objects to all the concerned clients : (which are all clients connected to this node)
16911
- return this.sendObjectsUpdatesToClients(modifiedObjects);
17157
+ return resultPromise;
16912
17158
  }
16913
17159
 
16914
- /*public*/sendNewObjects(newObjects, newObjectsIds){
17160
+ // II-
17161
+ receive(channelNameParam, doOnIncomingMessage, clientsRoomsTag=null, entryPointId=null, listenerConfig={destroyListenerAfterReceiving:false}){
17162
+ const self=this;
16915
17163
 
16916
- if(empty(newObjects)) return newObjects;
16917
-
16918
- // At this point, node controller has already added the objects to its model.
16919
- // We just want to update the objects directories of all the nodes in the cluster:
17164
+ // // DBG
17165
+ // lognow("INFO : (CLIENT) SETTING UP RECEIVE for :",channelNameParam);
16920
17166
 
16921
- // !!! CAUTION : NOTE THAT THE EMITTER NODE IS ALWAYS INCLUDED IN THE THE VISITED NODES !!!
16922
- // So this is why ir needs to update its mode objects directory itself, before sending the add request to the cluster :
16923
- this.modelObjectsDirectory[this.nodeId].push(...newObjectsIds);
17167
+ const clientReceptionEntryPoint=new ClientReceptionEntryPoint(channelNameParam, entryPointId, clientsRoomsTag, listenerConfig, doOnIncomingMessage);
17168
+ if(!contains.filter((l)=>(
17169
+ l.entryPointId && clientReceptionEntryPoint.entryPointId
17170
+ && l.entryPointId===clientReceptionEntryPoint.entryPointId
17171
+ ))(this.clientReceptionEntryPoints)){
17172
+
17173
+ this.clientReceptionEntryPoints.push(clientReceptionEntryPoint);
17174
+ }
16924
17175
 
16925
- // This node also advertises that its model has changed to all other nodes (not the newcomer, because it's unnecessary) :
16926
- const updatemodelObjectsDirectoryRequest={
16927
- type:"request.add.modelObjectsDirectory",
16928
- newObjectsIds:newObjectsIds
16929
- };
16930
- this.propagateToCluster(updatemodelObjectsDirectoryRequest);
16931
-
16932
-
16933
- // But we also want all the concerned clients to update their local models : (which are all clients connected to this node)
16934
- return this.sendObjectsUpdatesToClients(newObjects, true);
17176
+ const clientSocket=this.clientSocket;
17177
+ if(WebsocketImplementation.useSocketIOImplementation){
17178
+ // FOR THE SOCKETIO IMPLEMENTATION :
17179
+ clientSocket.on(channelNameParam, (eventOrMessage)=>{
17180
+ clientReceptionEntryPoint.execute(eventOrMessage, clientSocket, this.clientReceptionEntryPoints);
17181
+ });
17182
+ }
17183
+
17184
+ return this;
16935
17185
  }
17186
+
16936
17187
 
16937
- /*private*/sendObjectsUpdatesToClients(modifiedOrAddedObjects, isAddingObjects=false){
17188
+ send(channelNameParam, data, clientsRoomsTag=null){
16938
17189
 
16939
- const self=this;
16940
- foreach(this.clients, client=>{
16941
- const clientSocket=client.clientSocket;
16942
- const objectsModifiedOrAddedClientResponse={
16943
- nodeId:self.nodeId,
16944
- type:"response.objectsModifiedOrAdded.client",
16945
- objectsString:JSON.stringifyDecycle(modifiedOrAddedObjects),
16946
- isAddingObjects:isAddingObjects,
16947
- };
16948
- self.server.send("inputs", objectsModifiedOrAddedClientResponse, null, clientSocket);
16949
- });
17190
+ // // DBG
17191
+ // lognow("(CLIENT) CLIENT TRIES TO SEND !");
17192
+
17193
+ const clientSocket=this.clientSocket;
17194
+
17195
+ if(!isConnected(clientSocket)) return;
17196
+
17197
+ // Room information is stored in client socket object :
17198
+ if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
16950
17199
 
16951
- return modifiedOrAddedObjects;
17200
+ // Channel information is stored in exchanged data :
17201
+ let dataWrapped={channelName:channelNameParam, data:data};
17202
+
17203
+ dataWrapped=stringifyObject(dataWrapped);
17204
+
17205
+ // TODO : FIXME : Use one single interface !
17206
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
17207
+ else clientSocket.emit(channelNameParam,dataWrapped);
17208
+
17209
+ return this;
16952
17210
  }
16953
17211
 
16954
17212
 
16955
-
16956
-
16957
- /*private*/getNeighborsNodesIds(){
16958
- const neighborsNodesIds=[];
16959
- foreach(this.incomingServers,(incomingServer,nodeId)=>{
16960
- neighborsNodesIds.push(nodeId);
16961
- });
16962
- foreach(this.outcomingServers,(outcomingServer,nodeId)=>{
16963
- neighborsNodesIds.push(nodeId);
16964
- });
16965
- return neighborsNodesIds;
17213
+ join(clientRoomTag){
17214
+ // Join room client part protocol :
17215
+ const message={type:"joinRoom",clientRoomTag:clientRoomTag};
17216
+ this.send("protocol",message);
16966
17217
  }
17218
+
16967
17219
 
16968
- /*private*/getLeastOccupiedNeighborNodeId(){
17220
+ onConnectionToServer(doOnConnection){
16969
17221
  const self=this;
16970
-
16971
- // We send the new objects to the neighbor node with the least amount of objects :
16972
- let leastOccupiedNodeId=null;
16973
- foreach(this.getNeighborsNodesIds(), (nodeId)=>{
16974
- const entry=self.modelObjectsDirectory[nodeId];
16975
- const numberOfObjects=getArraySize(entry);
16976
- if(!leastOccupiedNodeId || numberOfObjects<getArraySize(self.modelObjectsDirectory[leastOccupiedNodeId]))
16977
- leastOccupiedNodeId=nodeId;
16978
- });
16979
17222
 
16980
- if(!leastOccupiedNodeId){
16981
- // TRACE
16982
- lognow("WARN : Cannot find the least occupied node. Aborting.");
16983
- }
16984
- return leastOccupiedNodeId;
16985
- }
16986
-
17223
+ // DBG
17224
+ lognow("DEBUG : CLIENT : setting up onConnectionToServer.");
17225
+
17226
+ const doAllOnConnection=()=>{
16987
17227
 
16988
- // ****************************************************************************************************
16989
- /*private*/setPropagation(channelName, requestType, doOnReception=null, responseRequestType=null, propagateToAllCluster=true){
16990
- const self=this;
16991
- this.setUniqueReceptionPoint(channelName, requestType, (self, message, server, clientSocket)=>{
17228
+ // To avoid triggering this event several times, depending on the implementation :
17229
+ if(self.hasConnectEventFired) return;
17230
+ self.hasConnectEventFired=true;
16992
17231
 
16993
- const requestId=message.requestId;
16994
- if(contains(self.executedRequestIdsHistory, requestId)){
16995
- // TRACE
16996
- lognow(`WARN : Request of type ${message.type} already answered. Aborting.`);
16997
- return;
16998
- }
16999
- if(message.visitedNodeIds){
17000
- if(contains(message.visitedNodeIds, self.nodeId)){
17001
- return;
17002
- }
17003
- message.visitedNodeIds.push(self.nodeId);
17004
- }
17232
+ // DBG
17233
+ lognow("DEBUG : CLIENT : doOnConnection !");
17005
17234
 
17006
- const hasNoExclusionsOrExclusionDoesNotApplyOnThisNode=(!message.excludedNodesIds || !contains(message.excludedNodesIds,self.nodeId));
17007
- let response=null;
17008
- if(!message.destinationNodeId && hasNoExclusionsOrExclusionDoesNotApplyOnThisNode){
17009
- // Case broadcast message :
17010
- if(doOnReception)
17011
- response=doOnReception(self, message, server, clientSocket);
17012
- pushInArrayAsQueue(self.executedRequestIdsHistory, REQUESTS_IDS_HISTORY_SIZE, requestId);
17013
- if(propagateToAllCluster) self.sendToOtherNodes(channelName, message);
17014
- }else{
17015
- if(message.destinationNodeId===self.nodeId && hasNoExclusionsOrExclusionDoesNotApplyOnThisNode){
17016
- if(doOnReception)
17017
- response=doOnReception(self, message, server, clientSocket);
17018
- pushInArrayAsQueue(self.executedRequestIdsHistory, REQUESTS_IDS_HISTORY_SIZE, requestId);
17019
- }else{
17020
- if(propagateToAllCluster) self.sendToOtherNodes(channelName, message);
17021
- }
17235
+ const clientSocket=self.clientSocket;
17236
+ if(!WebsocketImplementation.useSocketIOImplementation){
17237
+ // FOR THE WEBSOCKET IMPLEMENTATION :
17238
+ clientSocket.addEventListener("message", (eventOrMessage)=>{
17239
+ foreach(self.clientReceptionEntryPoints,(clientReceptionEntryPoint)=>{
17240
+ clientReceptionEntryPoint.execute(eventOrMessage, clientSocket, self.clientReceptionEntryPoints);
17241
+ });
17242
+ });
17022
17243
  }
17023
-
17024
- // We send back the answer to the originating node :
17025
- if(response!=null && responseRequestType){
17026
- // TRACE
17027
- lognow(`(${self.nodeId}) Node sending back a response of type ${responseRequestType}...`);
17028
17244
 
17029
- const clusterResponse={
17030
- type:responseRequestType,
17031
- result:response
17032
- };
17033
- const originNodeId=message.nodeId;
17034
- if(propagateToAllCluster) self.propagateToCluster(clusterResponse, originNodeId);
17035
- else self.propagateToSingleNeighbor(clusterResponse, originNodeId);
17245
+ doOnConnection(self, clientSocket);
17246
+ };
17247
+
17248
+
17249
+ if(!WebsocketImplementation.useSocketIOImplementation){
17250
+ // FOR THE WEBSOCKET IMPLEMENTATION :
17251
+ this.clientSocket.addEventListener("open",doAllOnConnection);
17252
+
17253
+ if(!empty(this.onServerLostListeners)){
17254
+ this.clientSocket.addEventListener("close",()=>{
17255
+ foreach(self.onServerLostListeners,l=>{l.execute(self.clientSocket);});
17256
+ });
17036
17257
  }
17037
17258
 
17038
- });
17039
- }
17040
- // ****************************************************************************************************
17041
-
17042
- /*private*/propagateToCluster(request, destinationNodeId=null, excludedNodesIds=null){
17043
- request.requestId=getUUID();
17044
- const originNodeId=this.nodeId;
17045
- request.nodeId=originNodeId;
17046
- if(destinationNodeId) request.destinationNodeId=destinationNodeId;
17047
- if(excludedNodesIds) request.excludedNodesIds=excludedNodesIds;
17048
- request.visitedNodeIds=[originNodeId];
17049
- this.sendToOtherNodes("cluster",request);
17050
- }
17051
-
17052
- /*private*/propagateToNeighbors(request){
17053
- request.requestId=getUUID();
17054
- const originNodeId=this.nodeId;
17055
- request.nodeId=originNodeId;
17056
- this.sendToOtherNodes("neightbors", request);
17057
- }
17058
- /*private*/propagateToSingleNeighbor(request, destinationNodeId){
17059
- request.requestId=getUUID();
17060
- const originNodeId=this.nodeId;
17061
- request.nodeId=originNodeId;
17062
- request.destinationNodeId=destinationNodeId;
17063
- this.sendToOtherSingleNode("neightbors", request, destinationNodeId);
17064
- }
17065
-
17066
- // ****************************************************************************************************
17067
-
17068
- /*private*/sendToOtherNodes(channelName, message){
17069
- let hasBeenSentIncoming=this.sendToIncomingServers(channelName, message);
17070
- let hasBeenSentOutcoming=this.sendToOutcomingServers(channelName, message);
17071
- return (hasBeenSentIncoming || hasBeenSentOutcoming);
17072
- }
17073
- /*private*/sendToIncomingServers(channelName, message){
17074
- const self=this;
17075
- let hasBeenSent=false;
17076
- foreach(this.incomingServers, (client)=>{
17077
- const clientSocket=client.clientSocket;
17078
- self.server.send(channelName, message, null, clientSocket);
17079
- hasBeenSent=true;
17080
- });
17081
- return hasBeenSent;
17082
- }
17083
- /*private*/sendToOutcomingServers(channelName, message){
17084
- let hasBeenSent=false;
17085
- foreach(this.outcomingServers, (client)=>{
17086
- const clientInstanceToOtherServer=client.clientInstance;
17087
- clientInstanceToOtherServer.client.socketToServer.send(channelName, message);
17088
- hasBeenSent=true;
17089
- });
17090
- return hasBeenSent;
17091
- }
17092
- /*private*/sendToOtherSingleNode(channelName, request, destinationNodeId){
17093
- const self=this;
17094
- let hasBeenSent=false;
17095
- foreach(this.incomingServers, (client)=>{
17096
- const clientSocket=client.clientSocket;
17097
- self.server.send(channelName, message, null, clientSocket);
17098
- hasBeenSent=true;
17099
- },(c, nodeId)=>(nodeId===destinationNodeId));
17100
- if(!hasBeenSent){
17101
- foreach(this.outcomingServers, (client)=>{
17102
- const clientInstanceToOtherServer=client.clientInstance;
17103
- clientInstanceToOtherServer.client.socketToServer.send(channelName, message);
17104
- hasBeenSent=true;
17105
- },(c, nodeId)=>(nodeId===destinationNodeId));
17106
- }
17107
- return hasBeenSent;
17108
- }
17259
+ }else{
17260
+ // FOR THE SOCKETIO IMPLEMENTATION :
17261
+ this.clientSocket.on("connect",doAllOnConnection);
17109
17262
 
17110
- /*private*/setUniqueReceptionPoint(channelName, messageType, doOnReception){
17111
- const self=this;
17112
- const listeners=this.listeners[channelName];
17113
- if(foreach(listeners,listener=>{
17114
- if(listener.messageType===messageType) return true;
17115
- })) return;
17116
- listeners.push({
17117
- messageType:messageType,
17118
- execute:(self, message, server, clientSocket)=>{
17119
- doOnReception(self, message, server, clientSocket);
17263
+ if(!empty(this.onServerLostListeners)){
17264
+ this.clientSocket.on("close",()=>{
17265
+ foreach(self.onServerLostListeners,l=>{l.execute(self.clientSocket);});
17266
+ });
17120
17267
  }
17121
- });
17122
- }
17123
-
17124
- // ****************************************************************************************************
17125
17268
 
17126
- /*private*/isInAuthorizedNodes(incomingServerNodeId){
17127
- return contains(this.authorizedNodesIds,incomingServerNodeId);
17128
- }
17129
-
17130
- /*public*/traceNode(){
17131
- // TRACE
17132
- lognow("-------------------------------------------------------------");
17133
- lognow(`(${this.nodeId}) :`,this);
17134
- lognow("-------------------------------------------------------------");
17269
+
17270
+ // Client ping handling :
17271
+ // (SocketIO implementation only, the websocket implementation sends back a hidden pong !)
17272
+ // PING-PONG :
17273
+ this.receive("protocol",(message)=>{
17274
+ if(message.type!=="ping") return;
17275
+ self.send("protocol",{type:"pong"});
17276
+ // // DBG
17277
+ // lognow("DEBUG : CLIENT : Pong sent.");
17278
+ });
17279
+ }
17280
+
17281
+
17282
+
17135
17283
  }
17136
17284
 
17137
-
17138
-
17139
17285
  }
17140
17286
 
17141
17287
 
17142
- getAORTACNode=function(nodeId=getUUID(), selfOrigin="ws://127.0.0.1:40000", outcomingNodesOrigins=[], model, controller){
17143
- return new AORTACNode(nodeId, selfOrigin, outcomingNodesOrigins, model, controller);
17144
- }
17145
-
17146
17288
 
17147
17289
 
17148
17290
 
@@ -17150,18 +17292,32 @@ getAORTACNode=function(nodeId=getUUID(), selfOrigin="ws://127.0.0.1:40000", outc
17150
17292
 
17151
17293
  class ServerReceptionEntryPoint{
17152
17294
 
17153
- constructor(channelNameParam, clientsRoomsTag, doOnIncomingMessage){
17295
+ constructor(channelNameParam, clientsRoomsTag, listenerConfig=null, doOnIncomingMessage){
17154
17296
  this.channelName=channelNameParam;
17155
17297
  this.clientsRoomsTag=clientsRoomsTag;
17298
+ this.listenerConfig=listenerConfig;
17156
17299
  this.doOnIncomingMessage=doOnIncomingMessage;
17157
17300
  }
17158
17301
 
17159
17302
  execute(eventOrMessage, clientSocketParam){
17160
17303
 
17304
+ if(!eventOrMessage){
17305
+ // TRACE
17306
+ lognow("ERROR : No message received.");
17307
+ return;
17308
+ }
17309
+
17310
+ if(!clientSocketParam){
17311
+ // TRACE
17312
+ lognow("ERROR : No clientSocket to client ! Cannot do reception treatment.");
17313
+ return;
17314
+ }
17315
+
17316
+
17161
17317
  // With «ws» library we have no choive than register message events inside a «connection» event !
17162
17318
 
17163
17319
  const dataWrapped=WebsocketImplementation.getMessageDataBothImplementations(eventOrMessage);
17164
-
17320
+
17165
17321
  // Channel information is stored in exchanged data :
17166
17322
  if(dataWrapped.channelName!==this.channelName) return;
17167
17323
 
@@ -17169,14 +17325,21 @@ class ServerReceptionEntryPoint{
17169
17325
  // Room information is stored in client socket object :
17170
17326
  const isClientInRoom=WebsocketImplementation.isInRoom(clientSocketParam, this.clientsRoomsTag);
17171
17327
 
17172
- // DBG
17173
- lognow("(SERVER) isClientInRoom:",isClientInRoom);
17328
+ // // DBG
17329
+ // lognow("(SERVER) isClientInRoom:",isClientInRoom);
17174
17330
 
17175
17331
  if(!isClientInRoom) return;
17332
+
17333
+ // We check if the message matches the required message type :
17334
+ if( this.listenerConfig && this.listenerConfig.listenerMessageType && dataWrapped.type
17335
+ && this.listenerConfig.listenerMessageType!==dataWrapped.type){
17336
+ return;
17337
+ }
17176
17338
 
17177
17339
  if(this.doOnIncomingMessage){
17178
- // DBG
17179
- lognow("(SERVER) this.doOnIncomingMessage:");
17340
+ // // DBG
17341
+ // lognow("(SERVER) this.doOnIncomingMessage:");
17342
+
17180
17343
  this.doOnIncomingMessage(dataWrapped.data, clientSocketParam);
17181
17344
  }
17182
17345
 
@@ -17197,7 +17360,9 @@ class NodeServerInstance{
17197
17360
  this.onClientLostListeners=[];
17198
17361
 
17199
17362
  this.clientPingIntervalMillis=5000;
17363
+
17200
17364
  this.serverSocket=serverSocket;
17365
+
17201
17366
  this.listenableServer=listenableServer;
17202
17367
  this.serverReceptionEntryPoints=[];
17203
17368
 
@@ -17230,13 +17395,12 @@ class NodeServerInstance{
17230
17395
  //}
17231
17396
 
17232
17397
 
17233
- receive(channelNameParam, doOnIncomingMessage, clientsRoomsTag=null){
17234
- const self=this;
17398
+ receive(channelNameParam, doOnIncomingMessage, listenerConfig=null, clientsRoomsTag=null){
17235
17399
 
17236
17400
  // DBG
17237
17401
  lognow("(SERVER) Registering receive for «"+channelNameParam+"»...");
17238
17402
 
17239
- const serverReceptionEntryPoint=new ServerReceptionEntryPoint(channelNameParam, clientsRoomsTag, doOnIncomingMessage);
17403
+ const serverReceptionEntryPoint=new ServerReceptionEntryPoint(channelNameParam, clientsRoomsTag, listenerConfig, doOnIncomingMessage);
17240
17404
  this.serverReceptionEntryPoints.push(serverReceptionEntryPoint);
17241
17405
 
17242
17406
  // SPECIAL FOR THE SOCKETIO IMPLEMENTATION :
@@ -17445,255 +17609,13 @@ class NodeServerInstance{
17445
17609
 
17446
17610
 
17447
17611
 
17448
- /* ## Utility network global methods in a javascript, console (nodejs), or vanilla javascript with no browser environment.
17449
- *
17450
- * This set of methods gathers utility generic-purpose methods usable in any JS project.
17451
- * Several authors of snippets published freely on the Internet contributed to this library.
17452
- * Feel free to use/modify-enhance/publish them under the terms of its license.
17453
- *
17454
- * # Library name : «aotrautils»
17455
- * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
17456
- * # Author name : Jérémie Ratomposon (massively helped by his native country free education system)
17457
- * # Author email : info@alqemia.com
17458
- * # Organization name : Alqemia
17459
- * # Organization email : admin@alqemia.com
17460
- * # Organization website : https://alqemia.com
17461
- *
17462
- *
17463
- */
17464
-
17465
-
17466
-
17467
- // COMPATIBILITY browser javascript / nodejs environment :
17468
- if(typeof(window)==="undefined") window=global;
17469
-
17470
-
17471
-
17472
- // ==================================================================================================================
17473
-
17474
-
17475
- class ClientReceptionEntryPoint{
17476
-
17477
- constructor(channelNameParam, entryPointId, clientsRoomsTag, listenerConfig, doOnIncomingMessage){
17478
- this.channelName=channelNameParam;
17479
- this.entryPointId=entryPointId;
17480
- this.clientsRoomsTag=clientsRoomsTag;
17481
- this.listenerConfig=listenerConfig;
17482
- this.doOnIncomingMessage=doOnIncomingMessage;
17483
- }
17484
-
17485
- // III-
17486
- execute(eventOrMessage, clientSocket, clientReceptionEntryPoints){
17487
-
17488
-
17489
- const dataWrapped=WebsocketImplementation.getMessageDataBothImplementations(eventOrMessage);
17490
-
17491
- // We check if the message is in the right channel (channel information is stored in exchanged wrapped data) :
17492
- const channelName=this.channelName;
17493
- if(dataWrapped.channelName && dataWrapped.channelName!==this.channelName){
17494
- return;
17495
- }
17496
-
17497
- // We check if the message is in the right room (r)oom information is stored in client socket object) :
17498
- if(!WebsocketImplementation.isInRoom(clientSocket, this.clientsRoomsTag)){
17499
- return;
17500
- }
17501
-
17502
- // We check if the message matches the required message type :
17503
- if( this.listenerConfig && this.listenerConfig.messageType && dataWrapped.type
17504
- && this.listenerConfig.messageType!==dataWrapped.type){
17505
- return;
17506
- }
17507
-
17508
- // We check if the message matches the condition :
17509
- const dataLocal=dataWrapped.data;
17510
- if(this.listenerConfig.condition && !this.istenerConfig.condition(dataLocal, clientSocket)){
17511
- return;
17512
- }
17513
-
17514
- // We check if we have the same message id:
17515
- const messageIdLocal=this.listenerConfig.messageId;
17516
- if(messageIdLocal!==dataLocal.messageId){
17517
- return;
17518
- }
17519
-
17520
- // At this point, message is the right one, so it can be processed :
17521
- // --------------------------------
17522
-
17523
- // We remove one-time usage listeners :
17524
- // We remove BEFORE executing doOnIncomingMessage(), because in this reception entry point function we can have other listener registration UNDER THE SAME ID !
17525
- if(this.listenerConfig && this.listenerConfig.destroyListenerAfterReceiving)
17526
- remove(clientReceptionEntryPoints, this);
17527
-
17528
- if(this.doOnIncomingMessage)
17529
- this.doOnIncomingMessage(dataLocal, clientSocket);
17530
-
17531
- }
17532
-
17533
- }
17534
-
17535
-
17536
- //
17537
- // CLIENT INSTANCE :
17538
- //
17539
- class ClientInstance{
17540
-
17541
- constructor(clientSocket){
17542
- this.clientSocket=clientSocket;
17543
- this.clientReceptionEntryPoints=[];
17544
-
17545
- }
17546
-
17547
-
17548
- sendChainable(channelNameParam, data, clientsRoomsTag=null){
17549
- const self=this;
17550
-
17551
- // DBG
17552
- lognow(">>>>>>sendChainable CLIENT ("+channelNameParam+"):data:",data);
17553
-
17554
- // We add a message id :
17555
- const messageId=getUUID();
17556
- data.messageId=messageId;
17557
-
17558
- // 1) We prepare the reception :
17559
- const resultPromise={
17560
- // CAUTION : CANNOT STORE ANYTHING IN THE resultPromise, FOR ASYNC CALLS MAKES ITS DATA INCONSISTENT IN TIME !
17561
- thenWhenReceiveMessageType:(channelNameForResponse, listenerConfig={messageType:"",condition:()=>true}, doOnIncomingMessageForResponse)=>{
17562
-
17563
- listenerConfig=nonull(listenerConfig,{messageType:"",condition:()=>true});
17564
- const listenerId=nonull(listenerConfig.messageType,"");
17565
-
17566
- // I-
17567
- //
17568
- self.receive(channelNameForResponse, doOnIncomingMessageForResponse
17569
- , clientsRoomsTag, listenerId, {messageId:messageId, destroyListenerAfterReceiving:true});
17570
- //
17571
-
17572
- return resultPromise;
17573
- }
17574
- };
17575
-
17576
- // 2) We send the data :
17577
- this.send(channelNameParam, data, clientsRoomsTag);
17578
-
17579
17612
 
17580
- return resultPromise;
17581
- }
17582
-
17583
- // II-
17584
- receive(channelNameParam, doOnIncomingMessage, clientsRoomsTag=null, entryPointId=null, listenerConfig={destroyListenerAfterReceiving:false}){
17585
- const self=this;
17586
-
17587
- // DBG
17588
- lognow("INFO : (CLIENT) SETTING UP RECEIVE for :",channelNameParam);
17589
-
17590
- const clientReceptionEntryPoint=new ClientReceptionEntryPoint(channelNameParam, entryPointId, clientsRoomsTag, listenerConfig, doOnIncomingMessage);
17591
- if(!contains.filter((l)=>(
17592
- l.entryPointId && clientReceptionEntryPoint.entryPointId
17593
- && l.entryPointId===clientReceptionEntryPoint.entryPointId
17594
- ))(this.clientReceptionEntryPoints)){
17595
-
17596
- this.clientReceptionEntryPoints.push(clientReceptionEntryPoint);
17597
- }
17598
17613
 
17599
- const clientSocket=this.clientSocket;
17600
- if(WebsocketImplementation.useSocketIOImplementation){
17601
- // FOR THE SOCKETIO IMPLEMENTATION :
17602
- clientSocket.on(channelNameParam, (eventOrMessage)=>{
17603
- clientReceptionEntryPoint.execute(eventOrMessage, clientSocket, this.clientReceptionEntryPoints);
17604
- });
17605
- }
17606
-
17607
- return this;
17608
- }
17609
17614
 
17610
-
17611
-
17612
-
17613
- send(channelNameParam, data, clientsRoomsTag=null){
17614
-
17615
- // // DBG
17616
- // lognow("(CLIENT) CLIENT TRIES TO SEND !");
17617
-
17618
- const clientSocket=this.clientSocket;
17619
-
17620
- if(!isConnected(clientSocket)) return;
17621
-
17622
- // Room information is stored in client socket object :
17623
- if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
17624
17615
 
17625
- // Channel information is stored in exchanged data :
17626
- let dataWrapped={channelName:channelNameParam, data:data};
17627
-
17628
- dataWrapped=stringifyObject(dataWrapped);
17629
-
17630
- // TODO : FIXME : Use one single interface !
17631
- if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
17632
- else clientSocket.emit(channelNameParam,dataWrapped);
17633
-
17634
- return this;
17635
- }
17636
-
17637
-
17638
- join(clientRoomTag){
17639
- // Join room client part protocol :
17640
- const message={type:"joinRoom",clientRoomTag:clientRoomTag};
17641
- this.send("protocol",message);
17642
- }
17643
-
17644
-
17645
- onConnectionToServer(doOnConnection){
17646
- const self=this;
17647
-
17648
- // DBG
17649
- lognow("DEBUG : CLIENT : setting up onConnectionToServer.");
17650
-
17651
- const doAllOnConnection=()=>{
17652
17616
 
17653
- // To avoid triggering this event several times, depending on the implementation :
17654
- if(self.hasConnectEventFired) return;
17655
- self.hasConnectEventFired=true;
17656
-
17657
- // DBG
17658
- lognow("DEBUG : CLIENT : doOnConnection !");
17659
-
17660
- const clientSocket=self.clientSocket;
17661
- if(!WebsocketImplementation.useSocketIOImplementation){
17662
- // FOR THE WEBSOCKET IMPLEMENTATION :
17663
- clientSocket.addEventListener("message", (eventOrMessage)=>{
17664
- foreach(self.clientReceptionEntryPoints,(clientReceptionEntryPoint)=>{
17665
- clientReceptionEntryPoint.execute(eventOrMessage, clientSocket, self.clientReceptionEntryPoints);
17666
- });
17667
- });
17668
- }
17669
17617
 
17670
- doOnConnection(self, clientSocket);
17671
- };
17672
-
17673
-
17674
- if(!WebsocketImplementation.useSocketIOImplementation){
17675
- // FOR THE WEBSOCKET IMPLEMENTATION :
17676
- this.clientSocket.addEventListener("open",doAllOnConnection);
17677
- }else{
17678
- // FOR THE SOCKETIO IMPLEMENTATION :
17679
- this.clientSocket.on("connect",doAllOnConnection);
17680
17618
 
17681
- // Client ping handling :
17682
- // (SocketIO implementation only, the websocket implementation sends back a hidden pong !)
17683
- // PING-PONG :
17684
- this.receive("protocol",(message)=>{
17685
- if(message.type!=="ping") return;
17686
- self.send("protocol",{type:"pong"});
17687
- // // DBG
17688
- // lognow("DEBUG : CLIENT : Pong sent.");
17689
- });
17690
- }
17691
-
17692
-
17693
-
17694
- }
17695
-
17696
- }
17697
17619
 
17698
17620
 
17699
17621
  /* INCLUDED EXTERNAL LIBRAIRIES