aotrautils-srv 0.0.1840 → 0.0.1846

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.
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- /*utils COMMONS library associated with aotra version : «1_29072022-2359 (09/04/2026-01:44:45)»*/
3
+ /*utils COMMONS library associated with aotra version : «1_29072022-2359 (02/06/2026-01:06:15)»*/
4
4
  /*-----------------------------------------------------------------------------*/
5
5
 
6
6
 
@@ -35,7 +35,7 @@ PERFORM_TESTS_ON_LIBRARY=false;
35
35
 
36
36
  const ROOT_UUID="00000000-0000-0000-0000-000000000000";
37
37
  const DEFAULT_UUID_ATTR_NAME="JSONid";/* Yet Another UUID */
38
- const DEFAULT_CLASSNAME_ATTR_NAME="JSONtype";/* Yet Another Classname */
38
+ const DEFAULT_CLASSNAME_ATTR_NAME="JSONtype";/* Yet Another ClassName */
39
39
  const DEFAULT_POINTER_TO_ATTR_NAME="JSONref";/* Yet Another PointerTo */
40
40
 
41
41
  //=========================================================================
@@ -1760,7 +1760,7 @@ function encodeXORNoPattern(strToEncode,key){
1760
1760
 
1761
1761
  // CAUTION : This method is declared this way because it's also used in a nodejs context!
1762
1762
  // TODO : FIXME : DO THIS FOR ALL OF THESE COMMON METHODS AND FUNCTIONS !
1763
- generateRandomString=function(length,/*NULLABLE*/mode=null){
1763
+ generateRandomString=function(length,/*NULLABLE*/mode=null, separator="~>"){
1764
1764
 
1765
1765
  // This list must be very conservative, because we want to be able to pass it through GET URLs !
1766
1766
  // OLD (not enough conservative ?) : const ALLOWED_CHARS="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0912346789_-£¢¤¬²³¼½¾";
@@ -1768,7 +1768,7 @@ generateRandomString=function(length,/*NULLABLE*/mode=null){
1768
1768
 
1769
1769
 
1770
1770
  if(typeof(length)==="string"){
1771
- if(contains(length,"->")){
1771
+ if(contains(length,"~>")){
1772
1772
  length=Math.getRandomInRange(length);
1773
1773
  }else{
1774
1774
  length=64;
@@ -2049,7 +2049,7 @@ window.nothing=function(nullable, insist=false){
2049
2049
  }
2050
2050
 
2051
2051
  window.getOrCreateEmptyAttribute=function(parentObject, attributeNameOrAttributesNames,
2052
- // 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.
2052
+ // BECAUSE CAUTION : We don't allow to specify a default initialization value as parameter, because if we have defaultValue as parameter, sometimes because of the recursive call, it can refer to a persisting object ! Thus, creating a strange bug.
2053
2053
  // (SO WE ALWAYS NEED TO START AS A BLANK STATE FOR DEFAULT VALUE !)
2054
2054
  forceArrayAttributeDefaultValue=false){
2055
2055
  if(nothing(parentObject)) return null;
@@ -2085,6 +2085,7 @@ window.getOrCreateEmptyAttribute=function(parentObject, attributeNameOrAttribute
2085
2085
  return null; // (case no child was found or it could be not created.)
2086
2086
  }
2087
2087
 
2088
+ // ( Equivalent to syntax: value??defaultValue )
2088
2089
  window.nonull=function(value,defaultValue){
2089
2090
  if(value===false) return value;
2090
2091
  if(nothing(value)) return defaultValue;
@@ -2153,7 +2154,7 @@ window.pushInArrayAsQueue=function(array, maxSize, item){
2153
2154
  /*KNOWN LIMITATIONS : You will have an incomplete browsing loop if there are the string values "break" or "continue" in your array or associative array !*/
2154
2155
  // (Alternatively, you can use this instead : for (const key of Object.keys(obj)) { const item=obj[key]; ... })
2155
2156
  // CAUTION : Only use «return foreach» syntax in client code loops with SINGLE-LEVEL nested loops ONLY !
2156
- window.foreach=function(arrayOfValues,doOnEachFunction,
2157
+ window.foreach=function(arrayOfValues, doOnEachFunction,
2157
2158
  /*OPTIONAL*/filterFunction=null,
2158
2159
  // CAUTION ! Javascript sort compare function result is the same as in Java :
2159
2160
  // 0: if(a==b)
@@ -3693,7 +3694,7 @@ Math.coerceInRange=window.aotest({
3693
3694
  }, !PERFORM_TESTS_ON_LIBRARY);
3694
3695
 
3695
3696
 
3696
- Math.getMinMax=function(strMinMaxRange, separator="->"){
3697
+ Math.getMinMax=function(strMinMaxRange, separator="¬>"){
3697
3698
  if(strMinMaxRange && isString(strMinMaxRange) && contains(strMinMaxRange,separator)){
3698
3699
  let splits=strMinMaxRange.split(separator);
3699
3700
  let min=parseInt(splits[0]);
@@ -3704,7 +3705,7 @@ Math.getMinMax=function(strMinMaxRange, separator="->"){
3704
3705
  }
3705
3706
 
3706
3707
 
3707
- Math.isInMinMax=function(minMaxRange,value,mode="][",separator="->"){
3708
+ Math.isInMinMax=function(minMaxRange,value,mode="][",separator="¬>"){
3708
3709
  let minMax=null;
3709
3710
 
3710
3711
  if(minMaxRange){
@@ -3727,7 +3728,7 @@ Math.isInMinMax=function(minMaxRange,value,mode="][",separator="->"){
3727
3728
  }
3728
3729
 
3729
3730
 
3730
- Math.getRandomInRange=function(str, separator="->"){
3731
+ Math.getRandomInRange=function(str, separator="~>"){
3731
3732
  if(str && isString(str) && contains(str,separator)){
3732
3733
  let splits=Math.getMinMax(str,separator);
3733
3734
  let minValue=splits.min;
@@ -3912,7 +3913,10 @@ window.isBoolean=function(b){
3912
3913
  return typeof b === "boolean";
3913
3914
  }
3914
3915
  window.isFunction=function(functionToCheck){
3915
- return (functionToCheck && {}.toString.call(functionToCheck) === "[object Function]");
3916
+ return (functionToCheck && (isAsyncFunction(functionToCheck) || {}.toString.call(functionToCheck) === "[object Function]"));
3917
+ }
3918
+ window.isAsyncFunction=function(functionToCheck){
3919
+ return (functionToCheck && functionToCheck.constructor && functionToCheck.constructor.name==="AsyncFunction");
3916
3920
  }
3917
3921
  window.isPrimitive=function(p){
3918
3922
  return (isNumber(p) || isString(p) || isBoolean(p));
@@ -3920,6 +3924,8 @@ window.isPrimitive=function(p){
3920
3924
  window.isObject=function(o){ // (includes also plain objects)
3921
3925
  return !isFunction(o) && !isPrimitive(o);
3922
3926
  }
3927
+
3928
+
3923
3929
  // NO : DOES NOT WORK !!!
3924
3930
  //window.isPlainObject=function(o){
3925
3931
  // return Object.prototype.toString.call(o)==="[object Object]";
@@ -3928,9 +3934,19 @@ window.isClassObject=function(o){
3928
3934
  return isObject(o) && !isNativeClass(o);
3929
3935
  }
3930
3936
  window.getClassName=function(obj){
3931
- if(!obj) return null;
3937
+ if((typeof obj === "undefined") || obj==null) return null;
3932
3938
  if(!obj.constructor) return null;
3933
- return obj.constructor.name;
3939
+ //if(!empty(Object.getPrototypeOf(obj).constructor.name))
3940
+ // return Object.getPrototypeOf(obj).constructor.name;
3941
+ if(!empty(obj.constructor.name)){
3942
+ return obj.constructor.name;
3943
+ }else{
3944
+ //TRACE
3945
+ lognow("WARN : Object is an instance of an anonymous class ! Cannot infer its class name ! Please fix its class definition ! obj:", obj);
3946
+ }
3947
+ // WORKAROUND
3948
+ if(!empty(obj[CLASSNAME_ATTRIBUTE_NAME])) return obj[CLASSNAME_ATTRIBUTE_NAME];
3949
+ return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1];
3934
3950
  }
3935
3951
  window.isNativeClass=function(obj){
3936
3952
  let className=getClassName(obj);
@@ -4097,8 +4113,12 @@ getUUID=function(mode=null){
4097
4113
  }
4098
4114
 
4099
4115
 
4116
+
4117
+ window.CLASSNAME_ATTRIBUTE_NAME="__className";
4118
+
4119
+
4100
4120
  // JSON
4101
- window.instanciate=function(className=null){
4121
+ window.instantiate=function(className=null){
4102
4122
  let newObj={};
4103
4123
 
4104
4124
  if(!className || !isString(className)) return newObj;
@@ -4116,7 +4136,9 @@ window.instanciate=function(className=null){
4116
4136
  // If class name is a simple array, a simple «[]» is enough :
4117
4137
  if(className==="Array") return [];
4118
4138
 
4139
+
4119
4140
 
4141
+
4120
4142
  let classInWindow=globalThis[className];
4121
4143
  if(typeof(classInWindow)=="undefined"){
4122
4144
  // TRACE
@@ -4130,14 +4152,27 @@ window.instanciate=function(className=null){
4130
4152
  classInWindow=global[className];
4131
4153
  if(!classInWindow){
4132
4154
  // TRACE
4133
- lognow(`ERROR : CAUTION : «${className}» class does not seem to be registered in the global object. Cannot instanciate it.`);
4134
- return newObj;
4155
+ lognow(`ERROR : CAUTION : «${className}» class does not seem to be registered in the global object. Cannot instantiate it.`);
4156
+
4157
+ // DEBUG
4158
+ // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4159
+ newObj=eval("new "+className+"();");
4160
+
4135
4161
  }
4136
4162
  }else{
4137
- // TRACE
4138
- lognow(`ERROR : CAUTION : «${className}» class does not seem to be accessible in the globalThis nor window object, and global object does not exist. Cannot instanciate it.`);
4139
- return newObj;
4163
+ // TRACEfireNewPuff()
4164
+ lognow(`ERROR : CAUTION : «${className}» class does not seem to be accessible in the globalThis nor window object, and global object does not exist. Cannot instantiate it.`);
4165
+
4166
+ // DEBUG
4167
+ // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4168
+ newObj=eval("new "+className+"();");
4169
+
4140
4170
  }
4171
+
4172
+ // WORKAROUND
4173
+ newObj[CLASSNAME_ATTRIBUTE_NAME]=className;
4174
+
4175
+ return newObj;
4141
4176
  }
4142
4177
  }
4143
4178
 
@@ -4147,21 +4182,21 @@ window.instanciate=function(className=null){
4147
4182
  // newObj=Reflect.construct(classInWindow);
4148
4183
  // }catch(e1){
4149
4184
  //// // TRACE
4150
- //// console.log("WARN : Could not instanciate class «"+className+"». (Maybe class is undefined or default constructor doesn't exist !)");
4185
+ //// console.log("WARN : Could not instantiate class «"+className+"». (Maybe class is undefined or default constructor doesn't exist !)");
4151
4186
  // // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4152
4187
  // newObj=eval("new "+className+"();");
4153
4188
  // }
4154
4189
  // }catch(e2){
4155
4190
  //// // TRACE
4156
- //// console.log("ERROR : Could not instanciate class «"+className+"» with eval. (Maybe class is undefined or default constructor doesn't exist !)");
4191
+ //// console.log("ERROR : Could not instantiate class «"+className+"» with eval. (Maybe class is undefined or default constructor doesn't exist !)");
4157
4192
  // try{
4158
4193
  //// // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4159
4194
  //// newObj=eval("new window."+className+"();");
4160
4195
  // newObj=Reflect.construct(classInWindow);
4161
4196
  // }catch(e3){
4162
4197
  //// // TRACE
4163
- //// console.log("ERROR : Could not instanciate class «"+className+"» with eval and «window.». (Maybe class is undefined or default constructor doesn't exist !)");
4164
- //// console.log("ERROR : Could not instanciate class «"+className+"» with Reflect and «window.». (Maybe class is undefined or default constructor doesn't exist !)");
4198
+ //// console.log("ERROR : Could not instantiate class «"+className+"» with eval and «window.». (Maybe class is undefined or default constructor doesn't exist !)");
4199
+ //// console.log("ERROR : Could not instantiate class «"+className+"» with Reflect and «window.». (Maybe class is undefined or default constructor doesn't exist !)");
4165
4200
  // // TODO : FIXME : This is the most performance-costing fallback :
4166
4201
  // try{
4167
4202
  // if(classInWindow){
@@ -4173,7 +4208,7 @@ window.instanciate=function(className=null){
4173
4208
  // }
4174
4209
  // }catch(e4){
4175
4210
  //// // TRACE
4176
- //// console.log("ERROR : Could not instanciate class «"+className+"» with prototype affectation.");
4211
+ //// console.log("ERROR : Could not instantiate class «"+className+"» with prototype affectation.");
4177
4212
  //// console.log("ERROR : Returning plain object since all instantiation methods have failed for class «"+className+"».");
4178
4213
  // }
4179
4214
  // }
@@ -4181,17 +4216,17 @@ window.instanciate=function(className=null){
4181
4216
 
4182
4217
  // NEW :
4183
4218
  newObj=new classInWindow();
4219
+
4220
+ // WORKAROUND
4221
+ newObj[CLASSNAME_ATTRIBUTE_NAME]=className;
4222
+
4184
4223
  return newObj;
4185
4224
  };
4186
4225
 
4187
4226
 
4188
4227
 
4189
-
4190
-
4191
-
4192
-
4193
4228
  JSON.stringifyDecycle=function(obj){
4194
- let decycled=JSON.decycle(obj,null,"$className");
4229
+ let decycled=JSON.decycle(obj,null,CLASSNAME_ATTRIBUTE_NAME);
4195
4230
  // CANNOT USE stringifyObject(...) function because we are in a common, lower-level library !
4196
4231
  return stringifyObject(decycled);
4197
4232
  }
@@ -4199,6 +4234,8 @@ JSON.stringifyDecycle=function(obj){
4199
4234
 
4200
4235
  JSON.parseRecycle=function(str){
4201
4236
 
4237
+ const attributeClassName=CLASSNAME_ATTRIBUTE_NAME;
4238
+
4202
4239
  const parsedDecycled=parseJSON(str);
4203
4240
  let restoreClass=function(objParam){
4204
4241
 
@@ -4208,11 +4245,11 @@ JSON.parseRecycle=function(str){
4208
4245
  if(isArray(objParam)){
4209
4246
  newObj=[];
4210
4247
  }else{
4211
- let className=objParam["$className"];
4248
+ let className=objParam[attributeClassName];
4212
4249
  if(!className || isNativeClassName(className)){
4213
4250
  newObj= {};
4214
4251
  }else{
4215
- newObj=instanciate(className);
4252
+ newObj=instantiate(className);
4216
4253
  }
4217
4254
  }
4218
4255
 
@@ -4242,8 +4279,11 @@ JSON.parseRecycle=function(str){
4242
4279
  ,(itemOrAttr)=>{ return !isFunction(itemOrAttr); }
4243
4280
  );
4244
4281
 
4245
- if(destination["$className"]){
4246
- delete destination["$className"];
4282
+ // However we have to keep the class information attribute for further house !
4283
+ if(destination[attributeClassName]
4284
+ //...except if the object is a simple object :
4285
+ && destination[attributeClassName]=="Object"){
4286
+ delete destination[attributeClassName];
4247
4287
  }
4248
4288
 
4249
4289
  };
@@ -4261,6 +4301,7 @@ JSON.parseRecycle=function(str){
4261
4301
 
4262
4302
 
4263
4303
  window.clone=function(obj){
4304
+ if(obj==null) return null;
4264
4305
  // OLD :
4265
4306
  const str=JSON.stringifyDecycle(obj);
4266
4307
  const newObj=JSON.parseRecycle(str);
@@ -4733,7 +4774,7 @@ window.isFlatMap=function(obj){
4733
4774
 
4734
4775
  window.getAsFlatStructure=function(rawObject
4735
4776
  ,UUID_ATTR_NAME=DEFAULT_UUID_ATTR_NAME/* Yet Another UUID */
4736
- ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another Classname */
4777
+ ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another ClassName */
4737
4778
  ,POINTER_TO_ATTR_NAME=DEFAULT_POINTER_TO_ATTR_NAME/* Yet Another PointerTo */
4738
4779
  ){
4739
4780
  // Flattening :
@@ -4784,7 +4825,7 @@ function flattenGraph(root, UUID_ATTR_NAME, CLASSNAME_ATTR_NAME, POINTER_TO_ATTR
4784
4825
 
4785
4826
  window.getAsTreeStructure=function(oldMap, keepClassName=false
4786
4827
  ,UUID_ATTR_NAME=DEFAULT_UUID_ATTR_NAME/* Yet Another UUID */
4787
- ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another Classname */
4828
+ ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another ClassName */
4788
4829
  ,POINTER_TO_ATTR_NAME=DEFAULT_POINTER_TO_ATTR_NAME/* Yet Another PointerTo */
4789
4830
  ){
4790
4831
 
@@ -4806,7 +4847,7 @@ window.restoreGraph=(flatData, keepClassName=false, UUID_ATTR_NAME, CLASSNAME_AT
4806
4847
 
4807
4848
  const objData = flatData[id];
4808
4849
  const className = objData[CLASSNAME_ATTR_NAME];
4809
- const newInstance = instanciate(className);
4850
+ const newInstance = instantiate(className);
4810
4851
 
4811
4852
  restoredObjects[id]=newInstance; // Assign ID early to handle circular references
4812
4853
 
@@ -5171,7 +5212,7 @@ window.getMonoThreadedTimeout=function(actuator=null){
5171
5212
 
5172
5213
  // ======================== Routine ========================
5173
5214
 
5174
- window.MonoThreadedRoutine=class {
5215
+ window.MonoThreadedRoutine=class MonoThreadedRoutine{
5175
5216
  constructor(actuator,refreshMillis=null,startDependsOnParentOnly=false){
5176
5217
 
5177
5218
  this.actuator=actuator;
@@ -5181,15 +5222,21 @@ window.MonoThreadedRoutine=class {
5181
5222
  this.time=getNow();
5182
5223
  this.refreshMillis=refreshMillis;
5183
5224
 
5184
- this.durationTimeFactorHolder={durationTimeFactor:1};
5225
+ this.managedTimeFactor=1;
5185
5226
  // Three-states : started, paused, stopped.
5186
5227
 
5187
5228
  }
5188
5229
 
5189
- setDurationTimeFactorHolder(durationTimeFactorHolder){
5190
- this.durationTimeFactorHolder=durationTimeFactorHolder;
5230
+ registerToTimeFactorManager(timeFactorManager){
5231
+ timeFactorManager.registerTimeFactorManagee(this);
5191
5232
  return this;
5192
5233
  }
5234
+
5235
+ changeManagedTimeFactor(newManagedTimeFactor){
5236
+ this.managedTimeFactor=newManagedTimeFactor;
5237
+ return this;
5238
+ }
5239
+
5193
5240
  isStarted(){
5194
5241
  return this.started || this.startDependsOnParentOnly;
5195
5242
  }
@@ -5226,7 +5273,7 @@ window.MonoThreadedRoutine=class {
5226
5273
  if(!this.isStarted() || this.paused) return;
5227
5274
  // Looping index with a delay :
5228
5275
 
5229
- const delayHasPassed=hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor());
5276
+ const delayHasPassed=hasDelayPassed(this.time, this.refreshMillis*this.managedTimeFactor);
5230
5277
  if(!this.refreshMillis || delayHasPassed){
5231
5278
  if(this.refreshMillis && delayHasPassed){
5232
5279
  this.time=getNow();
@@ -5236,7 +5283,8 @@ window.MonoThreadedRoutine=class {
5236
5283
  this.stop(args);
5237
5284
  return;
5238
5285
  }
5239
- if(this.actuator.doOnEachStep) this.actuator.doOnEachStep(args);
5286
+ if(this.actuator.doOnEachStep)
5287
+ this.actuator.doOnEachStep(args);
5240
5288
  }
5241
5289
 
5242
5290
  }
@@ -5265,19 +5313,27 @@ window.MonoThreadedGoToGoal=class {
5265
5313
  this.time=getNow();
5266
5314
  this.refreshMillis=refreshMillis;
5267
5315
 
5268
- this.durationTimeFactorHolder={durationTimeFactor:1};
5316
+ this.managedTimeFactor=1;
5317
+
5269
5318
 
5270
5319
  // Three-states : started, paused, stopped.
5271
5320
  }
5272
- setDurationTimeFactorHolder(durationTimeFactorHolder){
5273
- this.durationTimeFactorHolder=durationTimeFactorHolder;
5321
+
5322
+ registerToTimeFactorManager(timeFactorManager){
5323
+ timeFactorManager.registerTimeFactorManagee(this);
5324
+ return this;
5325
+ }
5326
+
5327
+ changeManagedTimeFactor(newManagedTimeFactor){
5328
+ this.managedTimeFactor=newManagedTimeFactor;
5274
5329
 
5275
5330
  // We recalculate total time-dependent values :
5276
- this.totalTimeMillis=this.totalTimeMillis*this.durationTimeFactorHolder.getDurationTimeFactor();
5331
+ this.totalTimeMillis=this.totalTimeMillis*this.managedTimeFactor;
5277
5332
  this.stepValue=((this.refreshMillis*(this.goalValue-this.actualValue))/this.totalTimeMillis);
5278
5333
 
5279
5334
  return this;
5280
5335
  }
5336
+
5281
5337
  isStarted(){
5282
5338
  return this.started || this.startDependsOnParentOnly;
5283
5339
  }
@@ -5289,7 +5345,7 @@ window.MonoThreadedGoToGoal=class {
5289
5345
  setNewGoal(goalValue, refreshMillisParam=null, totalTimeMillis=null){
5290
5346
  if(Math.round(this.value)===Math.round(goalValue)) return;
5291
5347
  if(refreshMillisParam) this.refreshMillis=refreshMillisParam;
5292
- if(totalTimeMillis) this.totalTimeMillis=totalTimeMillis*this.durationTimeFactorHolder.getDurationTimeFactor();
5348
+ if(totalTimeMillis) this.totalTimeMillis=totalTimeMillis*this.managedTimeFactor;
5293
5349
 
5294
5350
  this.goalValue=goalValue;
5295
5351
  this.stepValue=((this.refreshMillis*(this.goalValue-this.value))/this.totalTimeMillis);
@@ -5376,7 +5432,7 @@ window.MonoThreadedGoToGoal=class {
5376
5432
  }
5377
5433
 
5378
5434
  hasDelayPassed(){
5379
- return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor()));
5435
+ return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.managedTimeFactor));
5380
5436
  }
5381
5437
 
5382
5438
 
@@ -5402,7 +5458,7 @@ AOTRAUTILS_LIB_IS_LOADED=true;
5402
5458
 
5403
5459
 
5404
5460
 
5405
- /*utils AI library associated with aotra version : «1_29072022-2359 (09/04/2026-01:44:45)»*/
5461
+ /*utils AI library associated with aotra version : «1_29072022-2359 (02/06/2026-01:06:15)»*/
5406
5462
  /*-----------------------------------------------------------------------------*/
5407
5463
 
5408
5464
 
@@ -5548,11 +5604,383 @@ getOpenAIAPIClient=(modelName, apiURL, agentRole, defaultPrompt)=>{
5548
5604
 
5549
5605
 
5550
5606
 
5551
- /*utils CONSOLE library associated with aotra version : «1_29072022-2359 (09/04/2026-01:44:45)»*/
5607
+ /*utils CONSOLE library associated with aotra version : «1_29072022-2359 (02/06/2026-01:06:15)»*/
5552
5608
  /*-----------------------------------------------------------------------------*/
5553
5609
 
5554
5610
 
5555
- /* ## Utility methods in a javascript, for AORTAC subsystem (server & client)
5611
+ /* ## Utility methods in a javascript, for AORTAC subsystem (client)
5612
+ *
5613
+ * This set of methods gathers utility generic-purpose methods usable in any JS project.
5614
+ * Several authors of snippets published freely on the Internet contributed to this library.
5615
+ * Feel free to use/modify-enhance/publish them under the terms of its license.
5616
+ *
5617
+ * # Library name : «aotrautils»
5618
+ * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
5619
+ * # Author name : Jérémie Ratomposon (massively helped by his native country free education system)
5620
+ * # Author email : info@alqemia.com
5621
+ * # Organization name : Alqemia
5622
+ * # Organization email : admin@alqemia.com
5623
+ * # Organization website : https://alqemia.com
5624
+ *
5625
+ *
5626
+ */
5627
+
5628
+
5629
+
5630
+ // COMPATIBILITY browser javascript / nodejs environment :
5631
+ if(typeof(window)==="undefined") window=global;
5632
+
5633
+
5634
+
5635
+
5636
+ // ==================================================================================================================
5637
+
5638
+
5639
+ // AORTAC CLIENT
5640
+
5641
+
5642
+
5643
+ //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
5644
+
5645
+ const AORTAC_CLIENT_FORCE_SSL_USAGE=true;
5646
+
5647
+ // New implementation :
5648
+
5649
+ class AORTACClientCell{
5650
+
5651
+ constructor(serverCellOrigin, model, view, isNodeContext=false){
5652
+
5653
+ this.clientId=getUUID();
5654
+
5655
+ this.model=model;
5656
+ this.view=view;
5657
+ this.isNodeContext=isNodeContext;
5658
+
5659
+ const infos=splitURL(serverCellOrigin);
5660
+
5661
+ this.protocol=nonull(infos.protocol,"ws");
5662
+ this.host=nonull(infos.host,"localhost");
5663
+ this.port=nonull(infos.port,"30000");
5664
+ this.usesSSL=AORTAC_CLIENT_FORCE_SSL_USAGE;
5665
+
5666
+ this.startTime=null;
5667
+
5668
+ this.initialClientInstance=null;
5669
+ this.clientInstances={};
5670
+
5671
+
5672
+
5673
+ this.ready=false;
5674
+
5675
+ this.lobuleZone=new ClientLobule();
5676
+
5677
+
5678
+ }
5679
+
5680
+
5681
+ start(manifestationZone){
5682
+
5683
+ const self=this;
5684
+
5685
+
5686
+ this.manifestationZone=manifestationZone;
5687
+ this.startTime=getNow();
5688
+
5689
+
5690
+
5691
+ return new Promise((resolve,reject)=>{
5692
+
5693
+
5694
+ // 1) Initial connection
5695
+ self.initialClientInstance=initClient(self.isNodeContext, false, (socketToServerClientInstance, initialClientInstanceLocal)=>{
5696
+
5697
+ // DBG
5698
+ lognow("DEBUG : Starting client...");
5699
+
5700
+ initialClientInstanceLocal.socketToServerClientInstance=socketToServerClientInstance;
5701
+
5702
+ // INITIATE CLIENT HELLO SEQUENCE
5703
+ self.initiateClientHelloSequence();
5704
+
5705
+ // HANDLE CLIENT HELLO SEQUENCE
5706
+ self.handleClientHelloSequence(resolve);
5707
+
5708
+
5709
+ }, self.protocol+"://"+self.host, self.port, self.usesSSL);
5710
+ self.initialClientInstance.client.start();
5711
+
5712
+ });
5713
+ }
5714
+
5715
+
5716
+ // ===================================================================================================
5717
+ // SEQUENCES
5718
+ // ===================================================================================================
5719
+
5720
+
5721
+ // ==========================================================
5722
+ // CLIENT HELLO SEQUENCE
5723
+
5724
+ // 1) We send a client hello request
5725
+ // 2) Server cell receives it and asks the blob for all servers which this client must reconnect to
5726
+ // (also server sends its objects corresponding to zone, if it has some of them)
5727
+ // 3) We receive the confirmation and reconnect to all the indicated servers
5728
+ // 4) Each concerned server cell receives it and registers the client and sends the hello confirmation
5729
+ // (and also the objects in the zone it had stored when trying to know if it's a concerned server or not)
5730
+ // 5) We have all the objects, we can initialize the client model.
5731
+
5732
+
5733
+ /*private*/initiateClientHelloSequence(){
5734
+ // 1- We want to present ourselves to server cell :
5735
+ const clientHelloRequest={
5736
+ clientId:this.clientId,
5737
+ type:"clientHello",
5738
+ manifestationZone:this.manifestationZone
5739
+ };
5740
+ this.initialClientInstance.socketToServerClientInstance.send("protocol", clientHelloRequest);
5741
+ }
5742
+
5743
+ /*private*/handleClientHelloSequence(resolve){
5744
+
5745
+ const self=this;
5746
+ this.initialClientInstance.socketToServerClientInstance.receive("protocol", (messageParam, clientSocket)=>{
5747
+
5748
+ const message=JSON.recycle(messageParam);
5749
+
5750
+ const servers=message.servers;
5751
+ // CAUTION : THE QLC ALWAYS SENDS AT LEAST THE ROOTCONTAINER (during the «client hello» phase) !!!
5752
+ const objects=message.objects;
5753
+
5754
+ lognow("DEBUG : Client received its servers :",servers);
5755
+ lognow("DEBUG : Client received some objects :",objects);
5756
+
5757
+
5758
+ self.lobuleZone.resetServersNumbers();
5759
+ self.lobuleZone.append(servers, "serversBag");
5760
+ // CAUTION : THE QLC ALWAYS SENDS AT LEAST ITS ROOTCONTAINER (during the «client hello» phase) !!!
5761
+ self.lobuleZone.append(objects);
5762
+
5763
+ const lobuleObjects=self.lobuleZone.getObjects();
5764
+ if(!servers || empty(servers)){
5765
+ // TRACE
5766
+ lognow("ERROR : Servers blob sent no servers to reconnect to. Aborting.");
5767
+ // (We only return the structural object, ie. the root container :)
5768
+ resolve(lobuleObjects);
5769
+ return;
5770
+ }
5771
+
5772
+ if(empty(servers)){
5773
+ // TRACE
5774
+ lognow("ERROR : No servers to connect to. Aborting (not initiaing the reconnection sequence).");
5775
+ // (We only return the structural object, ie. the root container :)
5776
+ resolve(lobuleObjects);
5777
+ return;
5778
+ }
5779
+
5780
+ // 2) Re-connections :
5781
+ foreach(servers, serverOrigin=>{
5782
+
5783
+ const infos=splitURL(serverOrigin);
5784
+
5785
+ const protocol=nonull(infos.protocol,"ws");
5786
+ const host=nonull(infos.host,"localhost");
5787
+ const port=nonull(infos.port,"30000");
5788
+ const usesSSL=AORTAC_CLIENT_FORCE_SSL_USAGE;
5789
+
5790
+ const reconnectedClientInstance=initClient(self.isNodeContext, false, (socketToServerClientInstance, reconnectedClientInstanceLocal)=>{
5791
+
5792
+ // DBG
5793
+ lognow("DEBUG : Starting reconnected client...");
5794
+
5795
+ reconnectedClientInstanceLocal.socketToServerClientInstance=socketToServerClientInstance;
5796
+
5797
+ // INITIATE CLIENT RECONNECTED HELLO SEQUENCE
5798
+ self.initiateReconnectedClientHelloSequence(reconnectedClientInstance);
5799
+
5800
+ // HANDLE CLIENT RECONNECTED HELLO SEQUENCE
5801
+ self.handleReconnectedClientHelloSequence(reconnectedClientInstance, resolve)
5802
+
5803
+
5804
+ }, protocol+"://"+host, port, usesSSL);
5805
+
5806
+ self.clientInstances[serverOrigin]=reconnectedClientInstance;
5807
+ reconnectedClientInstance.client.start();
5808
+ });
5809
+
5810
+
5811
+ },{listenerMessageType:"clientHello.response"});
5812
+
5813
+ }
5814
+
5815
+ // ----
5816
+ // RECONNECTED CLIENT HELLO SEQUENCE
5817
+
5818
+
5819
+ /*private*/initiateReconnectedClientHelloSequence(clientInstance){
5820
+
5821
+ // We want to present ourselves to server cell :
5822
+ const reconnectedClientHelloRequest={
5823
+ clientId:this.clientId,
5824
+ type:"reconnectedClientHello"
5825
+ };
5826
+ clientInstance.client.socketToServerClientInstance.send("protocol", reconnectedClientHelloRequest);
5827
+
5828
+ }
5829
+
5830
+ /*private*/handleReconnectedClientHelloSequence(clientInstance, resolve){
5831
+
5832
+
5833
+ const self=this;
5834
+
5835
+ clientInstance.client.socketToServerClientInstance.receive("protocol", (messageParam, clientSocket)=>{
5836
+
5837
+ const message=JSON.recycle(messageParam);
5838
+
5839
+ const objects=message.objects;
5840
+
5841
+ if(!objects || empty(objects)){
5842
+ // TRACE
5843
+ lognow("ERROR : This server cell sent no objects. Continuing.");
5844
+
5845
+ // DBG
5846
+ lognow("DEBUG : !!!!!!!!!!!!! message:",message);
5847
+
5848
+ }
5849
+
5850
+ lognow("DEBUG : Client received some objects :",objects);
5851
+
5852
+ // We accumulate the received objects in the lobule zone :
5853
+ self.lobuleZone.append(objects, "objectsBag");
5854
+
5855
+ self.lobuleZone.incrementContactedServersNumber();
5856
+
5857
+
5858
+ // DBG
5859
+ lognow("DEBUG : !!!!!!!!!!!!! self.lobuleZone.getObjects():",self.lobuleZone.getObjects());
5860
+
5861
+
5862
+ if(self.lobuleZone.hasReceivedFromAllServers()){
5863
+
5864
+ const lobuleObjects=self.lobuleZone.getObjects();
5865
+ if(!lobuleObjects || empty(lobuleObjects)){
5866
+ // TRACE
5867
+ lognow("ERROR : No objects at all accumulated from server cells blob. Aborting.");
5868
+ resolve(null);
5869
+ return;
5870
+ }
5871
+
5872
+ // const allClientModelObjectsByClassName=self.sortByClassName(lobuleObjects);
5873
+ const allClientModelObjects=lobuleObjects;
5874
+
5875
+ //DBG
5876
+ // lognow("DEBUG : allClientModelObjectsByClassName : ",allClientModelObjectsByClassName);
5877
+ lognow("DEBUG : allClientModelObjects : ",allClientModelObjects);
5878
+
5879
+ if(empty(allClientModelObjects)){
5880
+ // TRACE
5881
+ lognow("ERROR : Servers blob sent no game level. Aborting.");
5882
+ resolve(allClientModelObjects);
5883
+ return;
5884
+ }
5885
+
5886
+ // DBG
5887
+ lognow("DEBUG : Client received its model objects:",message.objects);
5888
+ // lognow("DEBUG : Client received its model objects (after sorting):",allClientModelObjectsByClassName);
5889
+
5890
+ // DBG
5891
+ lognow("DEBUG : Client has received all its objects from the servers cells blob: ");
5892
+
5893
+ resolve(allClientModelObjects);
5894
+ }
5895
+
5896
+
5897
+ },{listenerMessageType:"reconnectedClientHello.response"});
5898
+
5899
+ }
5900
+
5901
+
5902
+
5903
+ // ================================================================================
5904
+
5905
+
5906
+
5907
+
5908
+ }
5909
+
5910
+
5911
+
5912
+
5913
+ class ClientLobule{
5914
+
5915
+ constructor(){
5916
+ this.serversBag=null;
5917
+ this.objectsBag=null;
5918
+ this.numberOfContactedServers=0;
5919
+ this.totalNumberOfServersToContact=0;
5920
+ this.clear();
5921
+ }
5922
+ append(objs, attributeName="objectsBag"){
5923
+ const self=this;
5924
+ if(!this[attributeName])
5925
+ this[attributeName]=[];
5926
+ foreach(objs,obj=>{
5927
+ self[attributeName].push(obj);
5928
+ },(obj)=>(!contains(this[attributeName], obj)));
5929
+ if(attributeName=="serversBag")
5930
+ this.totalNumberOfServersToContact+=objs.length;
5931
+ }
5932
+ getObjects(filterFunction=null){
5933
+ if(!filterFunction) return this.objectsBag;
5934
+ if(!this.objectsBag) return null;
5935
+ const results=[];
5936
+ foreach(this.objectsBag, (obj)=>{
5937
+ results.push(obj);
5938
+ },filterFunction);
5939
+ return results;
5940
+ }
5941
+ getServers(){
5942
+ if(!this.serversBag) return null;
5943
+ return this.serversBag;
5944
+ }
5945
+ clear(attributeName=null){
5946
+ if(!attributeName){
5947
+ this.serversBag=[];
5948
+ this.objectsBag=[];
5949
+ }else{
5950
+ this[attributeName]=[];
5951
+ }
5952
+ this.resetServersNumbers();
5953
+ return this;
5954
+ }
5955
+ resetServersNumbers(){
5956
+ this.numberOfContactedServers=0;
5957
+ this.totalNumberOfServersToContact=0;
5958
+ }
5959
+ incrementContactedServersNumber(increment=1){
5960
+ this.numberOfContactedServers+=increment;
5961
+ }
5962
+ hasReceivedFromAllServers(){
5963
+ return (this.totalNumberOfServersToContact<=this.numberOfContactedServers);
5964
+ }
5965
+ }
5966
+
5967
+
5968
+
5969
+
5970
+
5971
+
5972
+ window.getAORTACClient=function(serverCellOrigin="ws://127.0.0.1:40000", model, view, isNodeContext=false){
5973
+ if(nothing(serverCellOrigin)){
5974
+ // TRACE
5975
+ lognow("ERROR : No known server node, cannot connect to servers cells blob. Aborting AORTAC client setup.");
5976
+ return null;
5977
+ }
5978
+ return new AORTACClientCell(serverCellOrigin, model, view, isNodeContext);
5979
+ }
5980
+
5981
+
5982
+
5983
+ /* ## Utility methods in a javascript, for AORTAC subsystem (server)
5556
5984
  *
5557
5985
  * This set of methods gathers utility generic-purpose methods usable in any JS project.
5558
5986
  * Several authors of snippets published freely on the Internet contributed to this library.
@@ -5585,8 +6013,8 @@ if(typeof(window)==="undefined") window=global;
5585
6013
 
5586
6014
  AORTAC_OUTCOMING_SERVERS_CONNECTION_GLOBAL_TIMEOUT=30000;
5587
6015
 
5588
- // New implementation :
5589
6016
  AORTAC_SERVER_CELL_PERIODICAL_CONNECTIVITY_CHECK_MILLIS=2000;
6017
+
5590
6018
  class AORTACServerCell{
5591
6019
 
5592
6020
  constructor(quorumNumber=1, selfOrigin, outcomingCellsOrigins, model, controller, sslConfig={/*OPTIONAL*/certPath:null,/*OPTIONAL*/keyPath:null}){
@@ -5599,14 +6027,14 @@ class AORTACServerCell{
5599
6027
  const infos=splitURL(this.selfOrigin);
5600
6028
 
5601
6029
  //DBG
5602
- lognow("infos:::::::::::::",infos);
6030
+ lognow("(SERVER) infos:::::::::::::",infos);
5603
6031
 
5604
6032
  this.protocol=nonull(infos.protocol,"ws");
5605
6033
  this.host=nonull(infos.host,"localhost");
5606
6034
  this.port=nonull(infos.port,"30000");
5607
6035
  this.sslConfig=sslConfig;
5608
6036
 
5609
- this.server=null;
6037
+ this.serverWrapper=null;
5610
6038
  this.incomingCells={};
5611
6039
 
5612
6040
  this.outcomingCellsOrigins=outcomingCellsOrigins;
@@ -5624,20 +6052,26 @@ class AORTACServerCell{
5624
6052
  };
5625
6053
 
5626
6054
  this.cellsOverview={};
6055
+ this.numberOfPartitions=null;
6056
+ this.partitionId=null;
6057
+
6058
+ this.isServerCellQuorumLastCell=false;
5627
6059
 
5628
6060
  this.startTime=null;
5629
6061
 
5630
6062
  this.quorumIsReachedSequenceIsInitiated=false;
5631
6063
  // this.quorumCells=null; // (CAUTION : only the latest ready cell when quorum is reached has this attribute populated)
5632
6064
 
5633
- this.liveModelObjects={};
6065
+ this.lobuleZone=new ServerCellLobule();
6066
+
6067
+ this.clients={};
5634
6068
 
5635
6069
  }
5636
6070
 
5637
6071
  start(){
5638
6072
 
5639
6073
  this.startTime=getNow();
5640
- this.server=this.launchServerForIncomingConnections();
6074
+ this.serverWrapper=this.launchServerForIncomingConnections();
5641
6075
 
5642
6076
  if(empty(this.outcomingCellsOrigins) || (getArraySize(this.outcomingCellsOrigins)==1 && contains(this.outcomingCellsOrigins, this.selfOrigin))){
5643
6077
  // TRACE
@@ -5654,37 +6088,72 @@ class AORTACServerCell{
5654
6088
  return this;
5655
6089
  }
5656
6090
 
6091
+
6092
+ // ===================================================================================================
6093
+ // SERVER CELL CORE
6094
+ // ===================================================================================================
6095
+
5657
6096
  /*private*/launchServerForIncomingConnections(){
5658
6097
  const self=this;
5659
6098
 
5660
- const server=initNodeServerInfrastructureWrapper(
6099
+ const serverWrapper=initNodeServerInfrastructureWrapper(
5661
6100
  // On each client connection :
5662
6101
  null,
5663
6102
  // On client finalization :
5664
6103
  function(server){
5665
6104
 
6105
+
6106
+ const channelsNames=[];
6107
+ foreach(Object.keys(self.onReceiveMessageListeners["incoming"]), channelName=>{channelsNames.push(channelName);}, channelName=>!contains(channelsNames,channelName));
6108
+ foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{channelsNames.push(channelName);}, channelName=>!contains(channelsNames,channelName));
6109
+
6110
+
5666
6111
  // On-receive message listeners handling ;
5667
- foreach(Object.keys(self.onReceiveMessageListeners["incoming"]), channelName=>{
5668
- server.receive(channelName, (message, clientSocket)=>{
5669
- self.executeListeners("incoming",channelName, message, clientSocket);
5670
- });
5671
- });
5672
- foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{
6112
+ foreach(channelsNames, channelName=>{
5673
6113
  server.receive(channelName, (message, clientSocket)=>{
6114
+
6115
+ // DBG
6116
+ lognow("DEBUG : Server cell receives message from incoming...");
6117
+
6118
+ self.executeListeners("incoming", channelName, message, clientSocket);
6119
+
6120
+ // DBG
6121
+ lognow("DEBUG : Server cell receives message from both (in)...");
6122
+
5674
6123
  self.executeListeners("both",channelName, message, clientSocket);
5675
6124
  });
5676
6125
  });
5677
6126
 
5678
6127
 
5679
- // HANDLE HELLO SEQUENCE
5680
- self.handleHelloSequence();
6128
+ // HANDLE SERVER CELL HELLO SEQUENCE
6129
+ self.handleServerCellHelloSequence();
6130
+
6131
+
6132
+ // ******************************************
6133
+ // CLIENT
5681
6134
 
5682
- }, this.port, this.sslConfig.certPath, this.sslConfig.keyPath);
5683
- server.serverManager.start();
5684
- return server;
6135
+ // HANDLE CLIENT HELLO SEQUENCE
6136
+ self.handleClientHelloSequence();
6137
+
6138
+
6139
+ },
6140
+ // On client connection lost :
6141
+ (clientId)=>{
6142
+ lognow(`INFO : Connection to client id «${clientId}» was lost.`);
6143
+ // TRACE
6144
+ lognow(`INFO : Removing client information for client id «${clientId}».`);
6145
+ self.lobuleZone.remove(clientId);
6146
+ },
6147
+ this.port, this.sslConfig.certPath, this.sslConfig.keyPath);
6148
+
6149
+ serverWrapper.serverManager.start();
6150
+ return serverWrapper;
5685
6151
  }
5686
6152
 
5687
6153
  /*private*/probeOutcomingNetwork(){
6154
+
6155
+ const self=this;
6156
+
5688
6157
  const numberOfTotalServers=getArraySize(this.outcomingCells);
5689
6158
  let numberOfConnectedOutcomingServers=0;
5690
6159
  foreach(this.outcomingCells, outcomingCell=>{
@@ -5706,7 +6175,6 @@ class AORTACServerCell{
5706
6175
  return;
5707
6176
  }
5708
6177
 
5709
- const self=this;
5710
6178
 
5711
6179
  // We try to connect to all outcoming cells :
5712
6180
  foreach(this.outcomingCells, (outcomingCell, outcomingCellOrigin)=>{
@@ -5718,20 +6186,29 @@ class AORTACServerCell{
5718
6186
 
5719
6187
  const clientInstance=initClient(true, false, (socketToServerClientInstance)=>{
5720
6188
 
6189
+
6190
+ const channelsNames=[];
6191
+ foreach(Object.keys(self.onReceiveMessageListeners["outcoming"]), channelName=>{channelsNames.push(channelName);}, channelName=>!contains(channelsNames,channelName));
6192
+ foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{channelsNames.push(channelName);}, channelName=>!contains(channelsNames,channelName));
6193
+
6194
+
5721
6195
  // On-receive message listeners handling ;
5722
- foreach(Object.keys(self.onReceiveMessageListeners["outcoming"]), channelName=>{
6196
+ foreach(channelsNames, channelName=>{
5723
6197
  socketToServerClientInstance.receive(channelName, (message, clientSocket)=>{
6198
+
6199
+ // DBG
6200
+ lognow("DEBUG : Server cell receives message from outcoming...");
6201
+
5724
6202
  self.executeListeners("outcoming",channelName, message, clientSocket);
5725
- });
5726
- });
5727
- foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{
5728
- socketToServerClientInstance.receive(channelName, (message, clientSocket)=>{
6203
+
6204
+ // DBG
6205
+ lognow("DEBUG : Server cell receives message from both (out)...");
6206
+
5729
6207
  self.executeListeners("both",channelName, message, clientSocket);
5730
6208
  });
5731
6209
  });
5732
-
5733
6210
 
5734
- // INITIATE HELLO SEQUENCE
6211
+ // INITIATE SERVER CELL HELLO SEQUENCE
5735
6212
  self.initiateHelloSequence(socketToServerClientInstance);
5736
6213
 
5737
6214
  // We are connected:
@@ -5755,47 +6232,68 @@ class AORTACServerCell{
5755
6232
 
5756
6233
  }
5757
6234
 
5758
-
5759
6235
  /*private*/handleCommonListeners(){
5760
-
5761
6236
  // HANDLE CELL IS READY SEQUENCE
5762
6237
  this.handleCellIsReadySequence();
5763
-
5764
6238
  // HANDLE PARTITION SEQUENCE
5765
6239
  this.handlePartitionSequence();
5766
6240
 
5767
- }
6241
+
6242
+ // ******************************************
6243
+ // CLIENT
6244
+ // HANDLE CLIENT MODEL INITIAL POPULATION SEQUENCE
6245
+ this.handleGetServersForZoneRequest();
6246
+
6247
+ // HANDLE RECONNECTED CLIENT HELLO SEQUENCE
6248
+ this.handleReconnectedClientHelloSequence();
5768
6249
 
6250
+ }
6251
+
5769
6252
 
5770
6253
  /*private*/executeListeners(outletName, channelName, message, clientSocket){
5771
6254
  const onReceiveMessageListeners=this.onReceiveMessageListeners[outletName][channelName];
5772
6255
  if(!onReceiveMessageListeners) return;
5773
6256
 
5774
- // OUTCOMING NODES LISTENERS HOOK :
5775
- // // TRACE
5776
- // lognow("INFO : SERVER : Received a "+channelName+" message: ", message);
5777
-
5778
- foreach(onReceiveMessageListeners, (listeners,listenerMessageType)=>{
5779
- foreach(listeners, (listener)=>{
5780
- // // TRACE
5781
- // lognow(`INFO : SERVER : Executing a onReceiveMessageListeners for messageType ${listenerMessageType}.`);
5782
-
5783
- listener.execute(this, message, this.server, clientSocket);
5784
-
5785
- },(listener)=>(message.type===listener.listenerMessageType));
5786
- });
5787
-
6257
+ const self=this;
6258
+ foreach(onReceiveMessageListeners, (listener, listenerMessageType)=>{
6259
+ listener.execute(self, message, self.serverWrapper, clientSocket);
6260
+ },(listener)=>(message.type===listener.listenerMessageType));
6261
+
6262
+ }
6263
+
6264
+
6265
+ /*private*/replyToBlobRequest(channelName, originalMessage, message){
6266
+ if(!message.originatingCellOrigin)
6267
+ message.originatingCellOrigin=originalMessage.originatingCellOrigin;
6268
+ if(!message.originatingPartitionId)
6269
+ message.originatingPartitionId=originalMessage.originatingPartitionId;
6270
+ if(!message.clientId)
6271
+ message.clientId=originalMessage.clientId;
6272
+ return this.sendMessageToBlob(channelName, message,
6273
+ {
6274
+ isOriginatingCell:false,
6275
+ destinationCellsOrigins:[originalMessage.originatingCellOrigin],
6276
+ excludeIncomingServersCellsAndClientsInTransmission:true,
6277
+ isRequest:false
6278
+ },
6279
+ originalMessage.type+".response");
5788
6280
  }
5789
6281
 
5790
6282
 
5791
6283
  /*private*/sendMessageToBlob(channelName, message,
5792
- broadcastConfig={isOriginatingCell:false, destinationCellsOrigins:null, includeIncomingConnectionInTransmission:false, isRequest:false}){
6284
+ broadcastConfig={
6285
+ isOriginatingCell:false,
6286
+ destinationCellsOrigins:null,
6287
+ excludeIncomingServersCellsAndClientsInTransmission:true,
6288
+ isRequest:false,
6289
+ contactAllQuorumBlob:false
6290
+ },
6291
+ overridingMessageType=null){
5793
6292
 
5794
- const self=this;
5795
-
5796
6293
  if(broadcastConfig){
5797
6294
  if(broadcastConfig.isOriginatingCell){
5798
6295
  message.originatingCellOrigin=this.selfOrigin;
6296
+ message.originatingPartitionId=this.partitionId;
5799
6297
  }
5800
6298
  if(broadcastConfig.isRequest){
5801
6299
  message.isRequest=true;
@@ -5808,10 +6306,36 @@ class AORTACServerCell{
5808
6306
  if(!message.visitedCells)
5809
6307
  message.visitedCells=[];
5810
6308
  else if(contains(message.visitedCells,this.selfOrigin))
5811
- return;
6309
+ return null;
5812
6310
  message.visitedCells.push(this.selfOrigin);
5813
6311
 
5814
- if(broadcastConfig && broadcastConfig.includeIncomingConnectionInTransmission){
6312
+
6313
+ // AUTOMATIC LISTENER SETUP :
6314
+ const messageTypeForResponse=nonull(overridingMessageType, message.type+".response");
6315
+ let blobResponseListener=null;
6316
+ // We only add a result listener for the one cell which sent the request message (we skip the listener adding for the others in this case) :
6317
+ if(message.isRequest && message.originatingCellOrigin==this.selfOrigin
6318
+ //&& (typeof(message.result)=="undefined" || message.result==null)
6319
+ ){
6320
+ // To emulate a promise-like behavior :
6321
+ blobResponseListener=this.getBlobResponseListenerForRequest(message, channelName, broadcastConfig, messageTypeForResponse);
6322
+
6323
+ const outletName=nonull(blobResponseListener.outletName,"both");
6324
+ if(!this.onReceiveMessageListeners[outletName][channelName][blobResponseListener.listenerMessageType]){
6325
+ this.onReceiveMessageListeners[outletName][channelName][blobResponseListener.listenerMessageType]=blobResponseListener;
6326
+ // TRACE
6327
+ lognow("INFO : Added automatic listener for message type «"+blobResponseListener.listenerMessageType+"».");
6328
+ }else{
6329
+ // TRACE
6330
+ lognow("INFO : Listener for message type «"+blobResponseListener.listenerMessageType+"» already existed. Done nothing.");
6331
+ }
6332
+ }else{
6333
+ // TRACE
6334
+ lognow(`INFO : No listener to setup, because broadcast is not a request, or cell is not the originating cell !`);
6335
+ }
6336
+
6337
+ // SENDING
6338
+ if(broadcastConfig && !broadcastConfig.excludeIncomingServersCellsAndClientsInTransmission){
5815
6339
  foreach(this.incomingCells,(incomingCell)=>{
5816
6340
  // As a server, we send (forward) the message to the currently iterated upon client that is connected to us :
5817
6341
  incomingCell.server.send(channelName, message, null, incomingCell.clientSocket);
@@ -5819,7 +6343,6 @@ class AORTACServerCell{
5819
6343
  (incomingCell.connected && !contains(message.visitedCells, incomingCellOrigin))
5820
6344
  );
5821
6345
  }
5822
-
5823
6346
  foreach(this.outcomingCells,(outcomingCell)=>{
5824
6347
  // As a client, we send (forward) the message to the currently iterated upon server we are connected to :
5825
6348
  outcomingCell.socketToServerClientInstance.send(channelName, message);
@@ -5827,49 +6350,84 @@ class AORTACServerCell{
5827
6350
  (outcomingCell.connected && !contains(message.visitedCells, outcomingCellOrigin))
5828
6351
  );
5829
6352
 
6353
+ return blobResponseListener;
6354
+ }
6355
+
6356
+
6357
+ /*private*/getBlobResponseListenerForRequest(message, channelName, broadcastConfig, messageTypeForResponse){
6358
+
6359
+ const outletName="both"; // «incoming» & «outcoming»
6360
+ // We want to monitor when the server cell receives the RESPONSE of the message it sent as request !!!
6361
+
6362
+ // First we check if the listener for request already exists :
6363
+ let blobResponseListener=this.onReceiveMessageListeners[outletName][channelName][messageTypeForResponse];
6364
+ if(blobResponseListener){
6365
+ // TRACE
6366
+ lognow(`INFO : Blob response listener for cell ${this.selfOrigin} on channel ${channelName} for message type «${messageTypeForResponse}» already exists.`);
6367
+ return blobResponseListener;
6368
+ }
5830
6369
 
5831
- // We only add a result listener for the cell which sent the request message :
5832
- if(message.isRequest && message.originatingCellOrigin==this.selfOrigin
5833
- //&& (typeof(message.result)=="undefined" || message.result==null)
5834
- ){
5835
-
5836
- const outletName="both";
5837
-
5838
- // To emulate a promise-like behavior :
5839
- let blobResponseListeners=this.onReceiveMessageListeners[outletName][channelName][message.type];
5840
- if(blobResponseListeners && blobResponseListeners["forRequestsIssuedBySelf"]){
5841
- // TRACE
5842
- lognow(`INFO : Blob response «forRequestsIssuedBySelf» listener for cell ${this.selfOrigin} on channel ${channelName} for «${message.type}» already exists.`);
5843
- return blobResponseListeners["forRequestsIssuedBySelf"];
5844
- }else{
5845
- blobResponseListeners=getOrCreateEmptyAttribute(this.onReceiveMessageListeners[outletName][channelName],message.type);
5846
- }
5847
-
5848
- const blobResponseListener={
5849
- thenCallback:null,
5850
- execute:(self, message, server, clientSocket)=>{
5851
- // When we have received the message :
5852
- this.thenCallback(message);
6370
+
6371
+ blobResponseListener={
6372
+ broadcastConfig:broadcastConfig,
6373
+ thenCallback:null,
6374
+ allBlobHasBeenContactedCallback:null,
6375
+ hasAllBlobBeenContacted:false,
6376
+ // We use the partition ids to know which server cells have been contacted :
6377
+ partitionIdsThatCouldBeContacted:[],
6378
+ // This message will be the RESPONSE to the REQUEST (ti will be of type «<messageTypeForResponse>», meaning the request message type + «.response» !!)
6379
+ outletName:outletName,
6380
+ listenerMessageType:messageTypeForResponse,
6381
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
6382
+
6383
+ // This is what the requesting cell does every time any cell that is not the requesting cell sends back a response to its request to blob :
6384
+
6385
+ // When we have received the message :
6386
+ if(blobResponseListener.thenCallback) blobResponseListener.thenCallback(selfParam, messageParam);
6387
+
6388
+ // We try to determine if the whole blob has been contacted or not :
6389
+ if(blobResponseListener.broadcastConfig.contactAllQuorumBlob && !blobResponseListener.hasAllBlobBeenContacted){
5853
6390
 
5854
- // We need to remove the result listener once it is completed though.
5855
- delete blobResponseListeners["forRequestsIssuedBySelf"];
5856
- },
5857
- then:(thenCallback)=>{
5858
- this.thenCallback=thenCallback;
6391
+ if(!contains(blobResponseListener.partitionIdsThatCouldBeContacted, messageParam.originatingPartitionId))
6392
+ blobResponseListener.partitionIdsThatCouldBeContacted.push(messageParam.originatingPartitionId);
6393
+ // We need to exclude the current serevr cell itself :
6394
+ if(selfParam.numberOfPartitions-1<=blobResponseListener.partitionIdsThatCouldBeContacted.length){
6395
+ if(blobResponseListener.allBlobHasBeenContactedCallback)
6396
+ blobResponseListener.allBlobHasBeenContactedCallback(selfParam, messageParam);
6397
+ blobResponseListener.hasAllBlobBeenContacted=true;
6398
+ }
5859
6399
  }
5860
- };
5861
- blobResponseListeners["forRequestsIssuedBySelf"]=blobResponseListener;
5862
-
5863
- return blobResponseListener;
5864
- }else{
5865
- return null;
5866
- }
6400
+
6401
+ // We need to remove the result listener once it has been completed though.
6402
+ if(!blobResponseListener.broadcastConfig.contactAllQuorumBlob || blobResponseListener.hasAllBlobBeenContacted){
6403
+ //UNUSEFUL : delete blobResponseListeners[listenerTypeForRequest];
6404
+ delete selfParam.onReceiveMessageListeners[outletName][channelName][messageTypeForResponse];
6405
+ }
6406
+
6407
+ },
6408
+ thenOnAnyResponseFromBlobRequestingCellOnly:(thenCallback)=>{
6409
+ blobResponseListener.thenCallback=thenCallback;
6410
+ return blobResponseListener;
6411
+ },
6412
+ doOnceAllBlobHasBeenContacted:(allBlobHasBeenContactedCallback)=>{
6413
+ blobResponseListener.allBlobHasBeenContactedCallback=allBlobHasBeenContactedCallback;
6414
+ return blobResponseListener;
6415
+ },
6416
+ };
5867
6417
 
6418
+
6419
+
6420
+ return blobResponseListener;
5868
6421
  }
6422
+
6423
+
5869
6424
 
6425
+ // ===================================================================================================
6426
+ // SERVER CELLS
6427
+ // ===================================================================================================
5870
6428
 
5871
6429
  // ==========================================================
5872
- // HELLO SEQUENCE
6430
+ // SERVER CELL HELLO SEQUENCE
5873
6431
 
5874
6432
  /*private*/initiateHelloSequence(socketToServerClientInstance){
5875
6433
 
@@ -5882,28 +6440,30 @@ class AORTACServerCell{
5882
6440
  }
5883
6441
 
5884
6442
 
5885
- /*private*/handleHelloSequence(){
6443
+ /*private*/handleServerCellHelloSequence(){
5886
6444
 
5887
6445
  // 2- We wait to receive the hello request of the incoming cell connection :
5888
- getOrCreateEmptyAttribute(
5889
- this.onReceiveMessageListeners["both"]["protocol"],"helloRequest")["forRequestsIssuedByOthers"]={
5890
- execute:(selfParam, message, server, clientSocket)=>{
6446
+ this.onReceiveMessageListeners["both"]["protocol"]["helloRequest"]={
6447
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
5891
6448
 
5892
- const cellOriginToCheck=message.originatingCellOrigin;
6449
+ const cellOriginToCheck=messageParam.originatingCellOrigin;
5893
6450
 
5894
6451
  // TRACE
5895
- lognow(`INFO : Incoming node ${cellOriginToCheck} has said hello. Updating its local information...`);
6452
+ lognow(`INFO : (handleServerCellHelloSequence()) Incoming node ${cellOriginToCheck} has said hello. Updating its local information...`);
5896
6453
 
5897
6454
 
5898
6455
  selfParam.incomingCells[cellOriginToCheck]={
5899
6456
  connected:true,
5900
- server:server,
6457
+ server:serverWrapper.server,
5901
6458
  // DO NOT USE TO SEND/RECEIVE ANYTHINIG !
5902
6459
  // For this, use the server attribute (+ the clientSocket as argument) instead.
5903
6460
  clientSocket:clientSocket,
5904
6461
  };
5905
6462
 
5906
- },
6463
+
6464
+ // NO SERVER CELL HELLO CONFIRMATION SENT BACK TO THE OTHER SERVER CELL (to save time)
6465
+
6466
+ },
5907
6467
  listenerMessageType:"helloRequest"
5908
6468
  };
5909
6469
 
@@ -5928,18 +6488,14 @@ class AORTACServerCell{
5928
6488
  }
5929
6489
 
5930
6490
  /*private*/handleCellIsReadySequence(){
5931
- getOrCreateEmptyAttribute(
5932
- this.onReceiveMessageListeners["both"]["protocol"],"cellIsReady")["forRequestsIssuedByOthers"]={
5933
- execute:(selfParam, message, server, clientSocket)=>{
5934
6491
 
5935
- selfParam.cellsOverview[message.originatingCellOrigin]={ready:true,startTime:message.startTime};
5936
-
5937
- selfParam.sendMessageToBlob("protocol",message);
6492
+ this.onReceiveMessageListeners["both"]["protocol"]["cellIsReady"]={
6493
+
6494
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
6495
+
6496
+ selfParam.cellsOverview[messageParam.originatingCellOrigin]={ready:true,startTime:messageParam.startTime};
5938
6497
 
5939
- // // TRACE
5940
- // lognow("INFO : Updated cells overview for this cell ("+selfParam.selfOrigin+") :",selfParam.cellsOverview);
5941
- // lognow("DEBUG : selfParam.outcomingCells :",Object.keys(selfParam.outcomingCells));
5942
- // lognow("DEBUG : selfParam.incomingCells :",Object.keys(selfParam.incomingCells));
6498
+ selfParam.sendMessageToBlob("protocol",messageParam);
5943
6499
 
5944
6500
  // INITIATE QUORUM IS REACHED SEQUENCE
5945
6501
  selfParam.initiateQuorumIsReachedSequenceIfNecessary();
@@ -5965,11 +6521,11 @@ class AORTACServerCell{
5965
6521
  const quorumCells=copy(this.cellsOverview);
5966
6522
 
5967
6523
  // TRACE
5968
- lognow("INFO : Quorum is reached and this is the latest started cell. quorumCells :",quorumCells);
5969
-
5970
- this.initializeModel(quorumCells);
5971
-
5972
-
6524
+ lognow("INFO : Quorum is reached and this is the latest started cell (QLC). quorumCells:",quorumCells);
6525
+
6526
+ this.isServerCellQuorumLastCell=true;
6527
+
6528
+ this.launchModelPartitionBlobSequence(quorumCells);
5973
6529
 
5974
6530
  }
5975
6531
 
@@ -5980,249 +6536,694 @@ class AORTACServerCell{
5980
6536
  // ==========================================================
5981
6537
  // MODEL MANAGEMENT
5982
6538
 
5983
- /*private*/initializeModel(quorumCells){
5984
-
5985
- const self=this;
5986
-
5987
- const numberOfPartitions=getArraySize(quorumCells);
6539
+ /*private*/async launchModelPartitionBlobSequence(quorumCells){
6540
+
6541
+ const self=this;
6542
+
6543
+ this.numberOfPartitions=getArraySize(quorumCells);
6544
+
6545
+ const controller=this.controller;
6546
+ // const model=this.model;
6547
+
6548
+ const partitionsInstantiationZones=controller.getPartitionsZones(this.numberOfPartitions);
6549
+
6550
+ const firstPartitionZone=getAt(partitionsInstantiationZones,0);
6551
+
6552
+ // We populate and initialize the server cell model, using the first partition zone :
6553
+ const model=await this.doOnModelZonePartitionReception(firstPartitionZone, this.selfOrigin);
6554
+
6555
+ // DBG
6556
+ lognow("DEBUG : MODEL WAS JUST CREATED.");
6557
+
6558
+ // We send the models partition zones objects to the required cells :
6559
+ const qlcRootContainer=self.partiallyPopulatedRootContainer();
6560
+
6561
+ let i=1;
6562
+ foreach(quorumCells, (quorumCell, quorumCellOrigin)=>{
6563
+
6564
+ const modelPartitionZone=getAt(partitionsInstantiationZones,i);
6565
+
6566
+ const message={qlcOrigin: self.selfOrigin, qlcRootContainer:qlcRootContainer, type:"modelPartitionZone", partitionZone:modelPartitionZone};
6567
+
6568
+ // TRACE
6569
+ lognow(`INFO : Server cell ${self.selfOrigin} is sending a partition zone to server cell ${quorumCellOrigin}...`, message);
6570
+
6571
+ self.sendMessageToBlob("protocol", message,
6572
+ {isOriginatingCell:true, destinationCellsOrigins:[quorumCellOrigin],
6573
+ excludeIncomingServersCellsAndClientsInTransmission:true,
6574
+ isRequest:false});
6575
+
6576
+ i++;
6577
+ },(quorumCell,quorumCellOrigin)=>quorumCellOrigin!=self.selfOrigin);
6578
+
6579
+
6580
+ // Each quorum member cell is responsible for a model partition
6581
+ // Then the sattelite cells will be handling the duplication
6582
+
6583
+ }
6584
+
6585
+
6586
+ // HANDLE PARTITION SEQUENCE
6587
+
6588
+ /*private*/handlePartitionSequence(){
6589
+
6590
+ // CAUTION : For *non-QLC* server cells only :
6591
+
6592
+ // 2- We wait to receive the hello request of the incoming cell connection :
6593
+ this.onReceiveMessageListeners["both"]["protocol"]["modelPartitionZone"]={
6594
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
6595
+
6596
+ // If this cell is in the destinations of this message, we pass it along and then we do what it says :
6597
+ selfParam.sendMessageToBlob("protocol", messageParam);
6598
+
6599
+ if(contains(messageParam.destinationCellsOrigins, selfParam.selfOrigin)){
6600
+
6601
+ // We populate and initialize the server cell model :
6602
+ selfParam.doOnModelZonePartitionReception(messageParam.partitionZone, messageParam.qlcOrigin, messageParam.qlcRootContainer).then(model=>{
6603
+
6604
+ });
6605
+ }
6606
+
6607
+ },
6608
+ listenerMessageType:"modelPartitionZone"
6609
+ };
6610
+
6611
+ }
6612
+
6613
+
6614
+ /*private*/doOnModelZonePartitionReception(partitionZone, qlcOrigin, qlcRootContainer=null){
6615
+
6616
+ return new Promise((resolve,reject)=>{
6617
+
6618
+ // TRACE
6619
+ lognow(`INFO : Applying partition zone for cell ${this.selfOrigin}. Updating local server model...`);
6620
+ lognow(`DEBUG : partitionZone:`, partitionZone);
6621
+
6622
+ this.partitionId=partitionZone.id;
6623
+
6624
+ // TRACE
6625
+ lognow("INFO : Populating model for this partition zone...");
6626
+
6627
+ if(this.isServerCellQuorumLastCell){ // Case QLC :
6628
+
6629
+ // DBG
6630
+ lognow("DEBUG : (this cell is the last quorum server cell (QLC)) ");
6631
+
6632
+ // We initialize the portion of the model :
6633
+ // (controller will handle if a persisted model already exists)
6634
+ // (in this particular case, qlcOrigin and this.selfOrigin is exactly the same !)
6635
+ this.controller.populateModelInZone(this.model, partitionZone, getHashedString(qlcOrigin), getHashedString(this.selfOrigin) ).then((model)=>{
6636
+
6637
+ // DBG
6638
+ lognow("DEBUG : model is populated in QLC server cell.");
6639
+
6640
+ resolve(model);
6641
+ });
6642
+
6643
+ }else{ // Case NON-QLC :
6644
+
6645
+
6646
+ // BASICALLY IT'S THE EXACT SAME TREATMENT AS FOR A BLOB CLIENT !
6647
+
6648
+ // DBG
6649
+ lognow("DEBUG : (this cell is *NOT* last quorum server cell (NON-QLC)) ");
6650
+
6651
+
6652
+ // We initialize the portion of the model :
6653
+ // (controller will handle if a persisted model already exists)
6654
+ this.controller.populateModelInZone(this.model, partitionZone, getHashedString(qlcOrigin), getHashedString(this.selfOrigin), qlcRootContainer ).then((model)=>{
6655
+
6656
+ // DBG
6657
+ lognow("DEBUG : model is populated in NON-QLC server cell.");
6658
+
6659
+ resolve(model);
6660
+ });
6661
+
6662
+ }
6663
+
6664
+
6665
+ });
6666
+
6667
+ }
6668
+
6669
+
6670
+ // ===================================================================================================
6671
+ // CLIENT
6672
+ // ===================================================================================================
6673
+
6674
+ // ==========================================================
6675
+ // HANDLE CLIENT HELLO SEQUENCE
6676
+
6677
+ handleClientHelloSequence(){
6678
+
6679
+ // We wait to receive the hello request of the incoming client connection :
6680
+ this.onReceiveMessageListeners["incoming"]["protocol"]["clientHello"]={
6681
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
6682
+
6683
+ const clientId=messageParam.clientId;
6684
+ const manifestationZone=messageParam.manifestationZone;
6685
+
6686
+ // TRACE
6687
+ lognow(`INFO : (handleClientHelloSequence()) Incoming client ${clientId} has said hello. Starting its reconnection sequence...`);
6688
+
6689
+ // We send the servers on which the client must reconnect to :
6690
+
6691
+ // Init lobule :
6692
+ selfParam.lobuleZone.clear(clientId);
6693
+ selfParam.lobuleZone.setClientConnectionInfo(clientId, serverWrapper, clientSocket);
6694
+
6695
+ // Caution : client must ignore this server cell if it has no objects for it !
6696
+ let allObjectsCorrespondingToZoneInServerCell=[];
6697
+ try{
6698
+ selfParam.model.checkAllMethodsCustomModelClassPrerequisites();
6699
+ allObjectsCorrespondingToZoneInServerCell=selfParam.model.getAllPartitionnableObjectsInZone(manifestationZone);
6700
+ }catch(e){
6701
+ //TRACE
6702
+ lognow(e);
6703
+ }
6704
+
6705
+ if(!empty(allObjectsCorrespondingToZoneInServerCell)){
6706
+ selfParam.lobuleZone.append(clientId, [selfParam.selfOrigin], "serversBag");
6707
+ selfParam.lobuleZone.append(clientId, allObjectsCorrespondingToZoneInServerCell);
6708
+ }else{
6709
+ // TRACE
6710
+ lognow("INFO : Server cell has no relevant objects to send to client");
6711
+ }
6712
+
6713
+ // The QLC in all cases, sends its structural objects to client :
6714
+ if(selfParam.isServerCellQuorumLastCell){
6715
+ // Case this server cell is the QLC :
6716
+ const rootContainer=selfParam.partiallyPopulatedRootContainer(allObjectsCorrespondingToZoneInServerCell);
6717
+ // Then we send the structure and, if any, the concerned objects in client manifestation zone of this server cell :
6718
+ selfParam.lobuleZone.append(clientId, [rootContainer]);
6719
+ }
6720
+
6721
+ // At this point , in the QLC server cell's lobule zone for this client we have :
6722
+ // (- maybe its concerned zone objects.)
6723
+ // - its root container
6724
+
6725
+
6726
+ // We ask the blob
6727
+ selfParam.sendMessageToBlob("protocol", {type:"getServersForZone", clientId:clientId, manifestationZone:manifestationZone},
6728
+ {isOriginatingCell:true,
6729
+ excludeIncomingServersCellsAndClientsInTransmission:true,
6730
+ isRequest:true,
6731
+ contactAllQuorumBlob:true})
6732
+ .thenOnAnyResponseFromBlobRequestingCellOnly((selfParam2, messageParamLocal)=>{
6733
+ // Triggered every time this server cell receives a response to its request from blob :
6734
+
6735
+ const clientIdLocal=messageParamLocal.clientId;
6736
+ const answeringServerOrigin=messageParamLocal.answeringServerOrigin;
6737
+ const objectsCount=messageParamLocal.objectsCount;
6738
+
6739
+ // DBG
6740
+ lognow("DEBUG : This server cell has received a response from another server cell : answeringServerOrigin:",answeringServerOrigin);
6741
+
6742
+ // When another server cells answers to this server cell :
6743
+
6744
+ if(!objectsCount || objectsCount<=0){
6745
+ // TRACE
6746
+ lognow("INFO : No objects corresponding to this zone to send to collector server cell. This server cell must be ignored.");
6747
+ }else{
6748
+ // We accumulate the server origin in the lobule :
6749
+ selfParam2.lobuleZone.append(clientIdLocal, [answeringServerOrigin], "serversBag");
6750
+ }
6751
+
6752
+
6753
+ }).doOnceAllBlobHasBeenContacted((selfParam2, messageParamLocal)=>{
6754
+
6755
+ // Triggered once the requesting server cell has received the response from the last possible other server cell, according to the partition ids !
6756
+
6757
+ const clientIdLocal=messageParamLocal.clientId;
6758
+
6759
+ // At this point we should have all the needed servers in the lobule :
6760
+ const servers=selfParam2.lobuleZone.getServers(clientIdLocal);
6761
+ // CAUTION : THE QLC ALWAYS SENDS AT LEAST THE ROOTCONTAINER (during the «client hello» phase) !!!
6762
+ const objects=selfParam2.lobuleZone.getObjects(clientIdLocal);
6763
+
6764
+ // ???
6765
+ // if(!empty(objects)){
6766
+ // selfParam2.removeLinks(objects);
6767
+ // }
6768
+
6769
+ // CAUTION : THE QLC ALWAYS SENDS AT LEAST THE ROOTCONTAINER (during the «client hello» phase) !!!
6770
+ const messageWrapped=JSON.decycle({type:"clientHello.response", servers:servers, objects:objects});
5988
6771
 
5989
- const controller=this.controller;
5990
- const model=this.model;
5991
-
5992
-
5993
- // We initialize the whole model :
5994
- // (controller will handle if a persisted model exists)
5995
- controller.initializeModelForAORTACNode(model);
5996
-
5997
- // This must return the asked number of partitions, indexed by partition id :
5998
- // CAUTION : The partition function MUST return ALL the objects in a partition, WHATEVER THEIR NESTING LEVEL !!!
5999
- const modelPartitions=model.getPartitions(numberOfPartitions);
6772
+ // DBG
6773
+ lognow("DEBUG : All blob has been contacted : messageWrapped:",messageWrapped);
6774
+
6775
+ // We send back all the collected servers to the requesting client :
6776
+ const clientConnectionInfo=selfParam2.lobuleZone.getClientConnectionInfo(clientIdLocal);
6777
+ clientConnectionInfo.serverWrapper.server.send("protocol", messageWrapped, null, clientConnectionInfo.clientSocket);
6000
6778
 
6001
- // We exclude this cell from the partition sending, because we already have the model partition at hand.
6002
- this.removeLinks(modelPartitions[0].objects);
6003
- this.doOnModelPartitionReception(modelPartitions[0]);
6004
-
6005
- // We send the models objects to the required cells :
6006
- let i=1;
6007
- foreach(quorumCells, (quorumCell,quorumCellOrigin)=>{
6779
+ selfParam2.lobuleZone.clear(clientIdLocal);
6780
+
6781
+ });
6782
+
6008
6783
 
6009
- const modelPartition=modelPartitions[i];
6010
- self.removeLinks(modelPartition.objects);
6011
-
6012
- const message=JSON.decycle({type:"modelPartition",partition:modelPartition});
6013
-
6014
- // TRACE
6015
- lognow(`INFO : Cell ${self.selfOrigin} is sending a partition to cell ${quorumCellOrigin}...`,message);
6016
-
6017
- self.sendMessageToBlob("protocol", message,
6018
- {isOriginatingCell:true, destinationCellsOrigins:[quorumCellOrigin], includeIncomingConnectionInTransmission:false, isRequest:false});
6784
+ },
6785
+ listenerMessageType:"clientHello"
6786
+ };
6019
6787
 
6020
- i++;
6021
- },(quorumCell,quorumCellOrigin)=>quorumCellOrigin!=self.selfOrigin);
6022
-
6023
-
6024
- // Each quorum member cell is responsible for a model partition
6025
- // Then the sattelite cells will be handling the duplication
6026
-
6027
6788
  }
6028
-
6029
6789
 
6030
- // HANDLE PARTITION SEQUENCE
6031
6790
 
6032
- /*private*/handlePartitionSequence(){
6791
+ // This is what other cells do when they receive a request for «getServersForZone» from a cell in the blob
6792
+ /*private*/handleGetServersForZoneRequest(){
6033
6793
 
6034
- // 2- We wait to receive the hello request of the incoming cell connection :
6035
- getOrCreateEmptyAttribute(
6036
- this.onReceiveMessageListeners["both"]["protocol"],"modelPartition")["forRequestsIssuedByOthers"]={
6037
- execute:(selfParam, messageParam, server, clientSocket)=>{
6794
+ this.onReceiveMessageListeners["both"]["protocol"]["getServersForZone"]={
6795
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
6796
+
6797
+ // DBG
6798
+ lognow("DEBUG : handleGetServersForZoneRequest() : messageParam", messageParam);
6799
+
6800
+ const clientId=messageParam.clientId;
6801
+ const manifestationZone=messageParam.manifestationZone;
6802
+
6803
+ // When we receive a request from a server cell :
6804
+ let allObjectsCorrespondingToZoneInServerCell=[];
6805
+ try{
6806
+ selfParam.model.checkAllMethodsCustomModelClassPrerequisites();
6807
+ allObjectsCorrespondingToZoneInServerCell=selfParam.model.getAllPartitionnableObjectsInZone(manifestationZone);
6808
+ }catch(e){
6809
+ // TRACE
6810
+ lognow(e);
6811
+ }
6812
+
6813
+ // We store the objects for this client, because we know it will very soon ask them :
6814
+ // Init lobule :
6815
+ selfParam.lobuleZone.clear(clientId);
6816
+ selfParam.lobuleZone.setClientConnectionInfo(clientId, serverWrapper, clientSocket);
6817
+
6818
+ selfParam.lobuleZone.append(clientId, allObjectsCorrespondingToZoneInServerCell);
6819
+
6038
6820
 
6039
- // If this cell is in the destinations of this message, we pass it along and then we do what it says :
6040
- selfParam.sendMessageToBlob("protocol",messageParam);
6821
+ const message={type:"getServersForZone.response", objectsCount:allObjectsCorrespondingToZoneInServerCell.length, answeringServerOrigin:selfParam.selfOrigin};
6041
6822
 
6042
- if(contains(messageParam.destinationCellsOrigins,selfParam.selfOrigin)){
6043
-
6044
- const message=JSON.recycle(messageParam);
6045
-
6046
- selfParam.doOnModelPartitionReception(message.partition);
6047
-
6048
- }
6823
+ // TRACE
6824
+ lognow(`INFO : Server cell ${selfParam.selfOrigin} is sending its origin to server cell ${messageParam.originatingCellOrigin}...: message:`, message);
6049
6825
 
6050
- },
6051
- listenerMessageType:"modelPartition"
6826
+ selfParam.replyToBlobRequest("protocol", messageParam, message);
6827
+ },listenerMessageType:"getServersForZone"
6052
6828
  };
6053
6829
 
6054
6830
  }
6055
6831
 
6056
6832
 
6057
- /*private*/doOnModelPartitionReception(partition){
6058
-
6059
- // TRACE
6060
- lognow(`INFO : Incoming partition for cell ${this.selfOrigin}. Updating local model...`,partition);
6061
- lognow(`>>>>`,stringifyObject(JSON.decycle(partition.objects),1));
6833
+ // ----
6834
+ // HANDLE RECONNECTED CLIENT HELLO SEQUENCE
6835
+ handleReconnectedClientHelloSequence(){
6062
6836
 
6837
+ // Here we only want to collect the servers on which to reconnect to later :
6063
6838
 
6839
+ // 2- We wait to receive the hello request of the incoming client connection :
6840
+ this.onReceiveMessageListeners["incoming"]["protocol"]["reconnectedClientHello"]={
6841
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
6842
+
6843
+ const clientId=messageParam.clientId;
6844
+
6845
+ // TRACE
6846
+ lognow(`INFO : Incoming reconnected client ${clientId} has said hello. Updating its local information...`);
6847
+
6848
+ selfParam.clients[clientId]={
6849
+ connected:true,
6850
+ server:serverWrapper,
6851
+ // DO NOT USE TO SEND/RECEIVE ANYTHINIG !
6852
+ // For this, use the server attribute (+ the clientSocket as argument) instead.
6853
+ clientSocket:clientSocket,
6854
+ };
6855
+
6856
+ const objects=selfParam.lobuleZone.getObjects(clientId);
6857
+
6858
+ // RECONNECTED CLIENT HELLO CONFIRMATION
6859
+
6860
+
6861
+ const messageResponse={type:"reconnectedClientHello.response", objects:objects};
6862
+ const messageResponseWrapped=JSON.decycle(messageResponse);
6863
+
6864
+ // DBG
6865
+ lognow("DEBUG : Sending «reconnectedClientHello.response»...");
6866
+
6867
+ serverWrapper.server.send("protocol", messageResponseWrapped, null, clientSocket);
6868
+
6869
+ selfParam.lobuleZone.clear(clientId);
6870
+
6871
+ },
6872
+ listenerMessageType:"reconnectedClientHello"
6873
+ };
6874
+
6064
6875
 
6065
6876
  }
6066
6877
 
6067
6878
 
6068
- /*private*/removeLinks(linkedObjects, currentObject=null){
6069
-
6070
- const self=this;
6071
-
6072
- if(!currentObject){
6073
- foreach(linkedObjects, obj=>{
6074
- self.removeLinksOnSingleObject(linkedObjects, obj);
6075
- });
6076
- }else{
6077
- self.removeLinksOnSingleObject(linkedObjects, currentObject);
6078
- }
6079
-
6080
- return linkedObjects;
6081
- }
6879
+ // ==========================================================
6082
6880
 
6083
- /*private*/removeLinksOnSingleObject(linkedObjects, currentObject){
6881
+
6882
+ // ???
6883
+ // /*private*/removeLinks(linkedObjects, currentObject=null){
6884
+ // const self=this;
6885
+ // if(!currentObject){
6886
+ // foreach(linkedObjects, obj=>{
6887
+ // self.removeLinksOnSingleObject(linkedObjects, obj);
6888
+ // });
6889
+ // }else{
6890
+ // self.removeLinksOnSingleObject(linkedObjects, currentObject);
6891
+ // }
6892
+ // return linkedObjects;
6893
+ // }
6894
+ //
6895
+ // ???
6896
+ // /*private*/removeLinksOnSingleObject(linkedObjects, currentObject){
6897
+ // const self=this;
6898
+ // foreach(currentObject, (attr,attrNameOrIndex)=>{
6899
+ // // We only remove links to the objects not in the partition
6900
+ // if(isClassObject(attr)){
6901
+ // let aortacId=attr.aortacId;
6902
+ // if(!aortacId){
6903
+ // aortacId=getUUID();
6904
+ // attr.aortacId=aortacId;
6905
+ // }
6906
+ // if(!contains(linkedObjects,attr)){
6907
+ // currentObject[attrNameOrIndex]=aortacId+"@aortacId";
6908
+ // // CAUTION : No need for recursive call here, because the partition function returns ALL the objects in a partition,
6909
+ // // WHATEVER THEIR NESTING LEVEL !!!
6910
+ // }
6911
+ // }else{
6912
+ // // However, we need a recursive call for any simple object or array that may reference a class object in another partition :
6913
+ // self.removeLinksOnSingleObject(linkedObjects, attr);
6914
+ // }
6915
+ // },obj=>(isObject(obj) || isArray(obj)));
6916
+ // }
6084
6917
 
6085
- const self=this;
6086
- foreach(currentObject, (attr,attrNameOrIndex)=>{
6087
- // We only remove links to the objects not in the partition
6088
- if(isClassObject(attr)){
6089
- let aortacId=attr.aortacId;
6090
- if(!aortacId){
6091
- aortacId=getUUID();
6092
- attr.aortacId=aortacId;
6093
- }
6094
- if(!contains(linkedObjects,attr)){
6095
- currentObject[attrNameOrIndex]=aortacId+"@aortacId";
6096
- // CAUTION : No need for recursive call here, because the partition function returns ALL the objects in a partition,
6097
- // WHATEVER THEIR NESTING LEVEL !!!
6098
- }
6099
- }else{
6100
- // However, we need a recursive call for any simple object or array that may reference a class object in another partition :
6101
- self.removeLinksOnSingleObject(linkedObjects, attr);
6102
- }
6103
- },obj=>(isObject(obj) || isArray(obj)));
6104
6918
 
6105
- }
6106
6919
 
6107
6920
 
6108
- // ******************************************************************
6109
-
6110
- /*private*/collectDependencies(inputObjects){
6921
+ // **********************************************************************
6922
+
6923
+ /*private*/partiallyPopulatedRootContainer(allObjectsCorrespondingToZoneInServerCell=[]){
6111
6924
 
6112
- ////
6113
- lognow("DEBUG : collectDependencies()...",inputObjects);
6925
+ const rootContainerLocal=this.model.getRootContainer();
6114
6926
 
6927
+ const rootContainer=this.model.populateRootContainerWithObjectsSubset(rootContainerLocal, allObjectsCorrespondingToZoneInServerCell, true);
6928
+ rootContainer.isRootContainer=true;
6115
6929
 
6930
+ return rootContainer;
6116
6931
  }
6117
-
6118
- /*private*/repercutChangesIfNeeded(inputObjects){
6119
-
6120
-
6121
- ////
6122
- lognow("DEBUG : repercutChangesIfNeeded()...",inputObjects);
6123
6932
 
6124
-
6125
- }
6126
-
6127
-
6128
6933
  }
6129
6934
 
6130
6935
 
6131
6936
 
6132
- // ******************************************************************
6133
6937
 
6134
- // Public static hydration method :
6135
- window.ao=(incompleteModelObjectToDecorate)=>{
6136
-
6137
- const localServerCell=window.aortacCServerNodeInstance;
6138
- if(!localServerCell){
6139
- // TRACE
6140
- lognow("ERROR : No AORTACCServerNode singleton instance. Doing nothing on the model object.");
6141
- return incompleteModelObjectToDecorate;
6142
- }
6143
-
6144
- const liveModelObjects=localServerCell.liveModelObjects;
6145
-
6146
- // First we clone the object, for its attriutes values information :
6147
- const clonedObject=clone(incompleteModelObjectToDecorate);
6148
-
6149
- // Then we replace all its methods :
6150
- foreach(clonedObject, (method, methodName)=>{
6151
- clonedObject[methodName]=new Proxy(method,
6152
- {
6153
- apply: function(methodToEnhance, thisArg, argumentsList) {
6154
6938
 
6155
- const inputObjects=[thisArg];
6156
- inputObjects.push(...argumentsList);
6157
- localServerCell.collectDependencies(inputObjects);
6158
-
6159
- // // --- Treatment BEFORE function execution ---
6160
- // console.log(`Calling function "${methodToEnhance.name}" with arguments: ${argumentsList}`);
6161
-
6162
- // Call the original function (target) with its intended 'this' context (thisArg)
6163
- // and arguments (argumentsList) using Reflect.apply
6164
- const result = Reflect.apply(methodToEnhance, thisArg, argumentsList);
6165
-
6166
- // // --- Treatment AFTER function execution ---
6167
- // console.log(`Function "${methodToEnhance.name}" returned: ${result}`);
6168
-
6169
- inputObjects.push(result);
6170
- localServerCell.repercutChangesIfNeeded(inputObjects);
6171
6939
 
6172
- return result;
6173
- },
6174
- }
6175
- );
6176
- },attribute=>isFunction(attribute));
6940
+
6941
+
6942
+
6943
+ class ServerCellLobule{
6177
6944
 
6178
- return clonedObject;
6179
- };
6945
+ constructor(){
6946
+ this.serversBag=null;
6947
+ this.objectsBag=null;
6948
+ this.clear();
6949
+ this.clientsConnectionsInfos={};
6950
+ }
6951
+ append(clientId, objs, attributeName="objectsBag"){
6952
+ const self=this;
6953
+ if(!this[attributeName][clientId])
6954
+ this[attributeName][clientId]=[];
6955
+ foreach(objs,obj=>{
6956
+ self[attributeName][clientId].push(obj);
6957
+ },(obj)=>(!contains(this[attributeName][clientId], obj)));
6958
+ }
6959
+ getObjects(clientId, filterFunction=null){
6960
+ if(!filterFunction) return this.objectsBag[clientId];
6961
+ if(!this.objectsBag[clientId]) return null;
6962
+ const results=[];
6963
+ foreach(this.objectsBag[clientId], (obj)=>{
6964
+ results.push(obj);
6965
+ },filterFunction);
6966
+ return results;
6967
+ }
6968
+ getServers(clientId){
6969
+ if(!this.serversBag[clientId]) return null;
6970
+ return this.serversBag[clientId];
6971
+ }
6972
+ clear(clientId=null, attributeName=null){
6973
+ if(!attributeName){
6974
+ if(!clientId){
6975
+ this.serversBag={};
6976
+ this.objectsBag={};
6977
+ }else{
6978
+ this.serversBag[clientId]=[];
6979
+ this.objectsBag[clientId]=[];
6980
+ delete this.clientsConnectionsInfos[clientId];
6981
+ }
6982
+ }else{
6983
+ if(!clientId){
6984
+ this[attributeName]={};
6985
+ }else{
6986
+ this[attributeName][clientId]=[];
6987
+ delete this.clientsConnectionsInfos[clientId];
6988
+ }
6989
+ }
6990
+
6991
+ //DBG
6992
+ if(clientId) lognow("DEBUG : CLEAR() FOR clientId:",clientId);
6993
+
6994
+ return this;
6995
+ }
6996
+ remove(clientId){
6997
+ delete this.serversBag[clientId];
6998
+ delete this.objectsBag[clientId];
6999
+ delete this.clientsConnectionsInfos[clientId];
7000
+ }
7001
+ getClientConnectionInfo(clientId){
7002
+ return this.clientsConnectionsInfos[clientId];
7003
+ }
7004
+ setClientConnectionInfo(clientId, serverWrapper, clientSocket){
7005
+ this.clientsConnectionsInfos[clientId]={serverWrapper:serverWrapper, clientSocket:clientSocket};
7006
+ }
7007
+ }
6180
7008
 
6181
7009
 
6182
7010
 
6183
- window.getAORTACServerNode=function(quorumNumber=1, selfOrigin="ws://127.0.0.1:40000", outcomingCellsOrigins=[], model, controller){
7011
+ window.getAORTACServerCell=function(quorumNumber=1, selfOrigin="ws://127.0.0.1:40000", outcomingCellsOrigins=[], model, controller){
6184
7012
  //return new AORTACServerNode("node_"+getUUID("short"), selfOrigin, outcomingCellsOrigins, model, controller);
6185
- if(window.aortacCServerNodeInstance){
7013
+ if(window.aortacCServerCellInstance){
6186
7014
  // TRACE
6187
- throw new Error("ERROR : The AORTACCServerNode singleton instance already exists. It cannot be instantiated again in the same process. Aborting.");
7015
+ throw new Error("ERROR : The aortacCServerCellInstance singleton instance already exists. It cannot be instantiated again in the same process. Aborting.");
6188
7016
  }
6189
- window.aortacCServerNodeInstance=new AORTACServerCell(quorumNumber, selfOrigin, outcomingCellsOrigins, model, controller);
6190
- return window.aortacCServerNodeInstance;
7017
+ window.aortacCServerCellInstance=new AORTACServerCell(quorumNumber, selfOrigin, outcomingCellsOrigins, model, controller);
7018
+ return window.aortacCServerCellInstance;
6191
7019
  }
6192
7020
 
6193
7021
 
6194
7022
 
6195
7023
 
7024
+ // ===============================================================================================================================
7025
+ // ===============================================================================================================================
7026
+ // UTILITY METHODS
6196
7027
 
6197
7028
 
7029
+ // USAGE EXAMPLE :
6198
7030
 
6199
- // ==================================================================================================================
6200
7031
 
7032
+ // 4 tests only :
7033
+ //class PricingService {
7034
+ // constructor(currency) {
7035
+ // this.currency = currency;
7036
+ // this.callCount = 0;
7037
+ // }
7038
+ //
7039
+ // getPrice(eventId) {
7040
+ // this.callCount++;
7041
+ // return { eventId, amount: 89.99, currency: this.currency };
7042
+ // }
7043
+ //
7044
+ // async updatePrice(eventId, amount) {
7045
+ // await new Promise(r => setTimeout(r, 10)); // simulate async
7046
+ // return { eventId, amount, saved: true };
7047
+ // }
7048
+ //}
7049
+ //
7050
+ //proxyClass(PricingService);
7051
+ //const proxiedClassInstance=new PricingService("CAD");
7052
+ //proxiedClassInstance.currency; // [GET] PricingService.currency{ value: "CAD" }
7053
+ //proxiedClassInstance.currency="USD"; // [SET] PricingService.currency{ value: "USD" }
7054
+ //proxiedClassInstance.getPrice("evt-42"); // [CALL] PricingService.getPrice(){ args: ["evt-42"], result:{...} }
7055
+ //proxiedClassInstance.callCount; // [GET] PricingService.callCount{ value: 1 }
7056
+ //await proxiedClassInstance.updatePrice("evt-42", 120); // [CALL] PricingService.updatePrice(){ args: [...], result:{...} }
7057
+ //restoreNonProxiedClass(PricingService); // undo — new PricingService() works normally again
6201
7058
 
6202
- // AORTAC CLIENT
6203
7059
 
7060
+ window.proxyClass=function(classToProxy,
7061
+ doOnAccess=(className, propertyName, parameter)=>{lognow(`Accessing ${className}.${propertyName}:`,parameter);}){
6204
7062
 
7063
+ const className=classToProxy.name;
6205
7064
 
6206
- //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
7065
+ // Build the Proxy handler
7066
+ const proxyHandler={
6207
7067
 
6208
- // New implementation :
7068
+ // Intercepts: obj.property AND obj.method()
7069
+ get(concernedObject, property, thisArg){
7070
+ const value=Reflect.get(concernedObject, property, thisArg);
6209
7071
 
6210
- class AORTACClientCell{
7072
+ // We skip symbols (ex. Symbol.toPrimitive) — not meaningful to intercept
7073
+ if(typeof(property)!=="string") return value;
7074
+
7075
+ if(typeof(value)==="function"){
7076
+
7077
+ // Return a wrapper so we can intercept args + return value at call time
7078
+ return function(...args){
7079
+ const result=value.apply(concernedObject, args); // `concernedObject` keeps correct `this`
7080
+
7081
+ if(result instanceof Promise){
7082
+ // Async method — intercept after the promise settles
7083
+ return result
7084
+ .then(v=>{ doOnAccess(className, property, { args, result: v }); return v; })
7085
+ .catch(e=>{ doOnAccess(className, property, { args, error: e }); throw e; });
7086
+ }
7087
+
7088
+ // Sync method — intercept immediately
7089
+ doOnAccess(className, property, { args, result }/*(SYNTAX : Equivalent to { args:args, result:result })*/);
7090
+ return result;
7091
+ };
7092
+ }
7093
+
7094
+ // Plain attribute read
7095
+ doOnAccess(className, property, { value });
7096
+ return value;
7097
+ },
7098
+
7099
+ // Intercepts: obj.property=value
7100
+ set(concernedObject, property, valueToSet, thisArg){
7101
+ if(typeof(property)==="string")
7102
+ doOnAccess(className, property, { valueToSet });
7103
+ return Reflect.set(concernedObject, property, valueToSet, thisArg);
7104
+ },
7105
+ };
7106
+
7107
+ // Patch the constructor
7108
+ // Save the original so we can restore later
7109
+ const originalConstructor=classToProxy.prototype.constructor;
7110
+ classToProxy.prototype.originalConstructor=originalConstructor;
7111
+
7112
+ // Override the constructor: it runs first, then we wrap `this` in a Proxy
7113
+ classToProxy.prototype.constructor=function(...args){
7114
+ classToProxy.prototype.originalConstructor.apply(this, args); // run original setup
7115
+ return new Proxy(this, proxyHandler); // return proxy instead of raw `this`
7116
+ };
7117
+
7118
+ // Keep the name and prototype intact so instanceof still works
7119
+ Object.defineProperty(classToProxy.prototype.constructor, "name", { value: className });
7120
+ classToProxy.prototype.constructor.prototype=classToProxy.prototype;
7121
+
7122
+ return classToProxy;
7123
+ };
7124
+
7125
+ // Return a restore function
7126
+ window.restoreNonProxiedClass=function(classToProxy){
7127
+ if(!classToProxy.prototype.originalConstructor){
7128
+ // TRACE
7129
+ lognow("WARN : Class has not been proxied. We do nothing. Concerned class:",classToProxy);
7130
+ return;
7131
+ }
7132
+ classToProxy.prototype.constructor=classToProxy.prototype.originalConstructor;
7133
+ };
7134
+
7135
+
7136
+
7137
+
7138
+ // ******************************************************************
7139
+
7140
+ window.getCurrentLevelConfigForAORTACServerCell=()=>{
7141
+ const allPrototypesConfig=PROTOTYPES_CONFIG.allPrototypes;
7142
+ const levelConfig=allPrototypesConfig.GameLevel;
7143
+
7144
+ const currentLevelPrototypeName=getCurrentLevelPrototypeNameForAORTACServerCell();
7145
+ if(nothing(currentLevelPrototypeName)){
7146
+ // TRACE
7147
+ lognow("WARN : No currentLevelPrototypeName found. Aborting.");
7148
+ return null;
7149
+ }
7150
+ const referenceCurrentLevelConfig=levelConfig[currentLevelPrototypeName];
7151
+ return referenceCurrentLevelConfig;
7152
+ };
7153
+
7154
+ window.getCurrentLevelPrototypeNameForAORTACServerCell=()=>{
6211
7155
 
6212
- constructor(serverNodeOrigin, model, view, isNodeContext=false){
7156
+ const aortacConfig=GAME_CONFIG.aortac;
7157
+ if(nothing(aortacConfig)){
7158
+ // TRACE
7159
+ lognow("WARN : No AORTAC config, Aborting..");
7160
+ return null;
7161
+ }
6213
7162
 
7163
+ const allPrototypesConfig=PROTOTYPES_CONFIG.allPrototypes;
7164
+ const levelConfig=allPrototypesConfig.GameLevel;
7165
+
7166
+ let currentLevelPrototypeName=aortacConfig.startingLevelPrototypeName;
7167
+ if(nothing(currentLevelPrototypeName)){
7168
+ currentLevelPrototypeName=getKeyAt(levelConfig,0);
7169
+ // TRACE
7170
+ lognow("WARN : No startingLevelPrototypeName specified in AORTAC config, using first level name as default «"+currentLevelPrototypeName+"».");
6214
7171
  }
6215
7172
 
6216
- }
7173
+ return currentLevelPrototypeName;
7174
+ };
6217
7175
 
6218
7176
 
6219
7177
 
6220
7178
 
6221
- window.getAORTACClient=function(serverNodeOrigin="ws://127.0.0.1:40000", model, view, isNodeContext=false){
6222
- //return new AORTACClient("client_"+getUUID(), serverNodeOrigin, model, view, isNodeContext);
6223
- return new AORTACClientCell(serverNodeOrigin, model, view, isNodeContext);
6224
- }
6225
7179
 
7180
+ //
7181
+ //// Public static hydration method :
7182
+ //window.ao=(incompleteModelObjectToDecorate)=>{
7183
+ //
7184
+ // const localServerCell=window.aortacCServerCellInstance;
7185
+ // if(!localServerCell){
7186
+ // // TRACE
7187
+ // lognow("ERROR : No AORTACCServerNode singleton instance. Doing nothing on the model object.");
7188
+ // return incompleteModelObjectToDecorate;
7189
+ // }
7190
+ //
7191
+ // const liveModelObjects=localServerCell.liveModelObjects;
7192
+ //
7193
+ // // First we clone the object, for its attriutes values information :
7194
+ // const clonedObject=clone(incompleteModelObjectToDecorate);
7195
+ //
7196
+ // // Then we replace all its methods :
7197
+ // foreach(clonedObject, (method, methodName)=>{
7198
+ // clonedObject[methodName]=new Proxy(method,
7199
+ // {
7200
+ // apply: function(methodToEnhance, thisArg, argumentsList){
7201
+ //
7202
+ // const inputObjects=[thisArg];
7203
+ // inputObjects.push(...argumentsList);
7204
+ // localServerCell.collectDependencies(inputObjects);
7205
+ //
7206
+ //// // --- Treatment BEFORE function execution ---
7207
+ //// console.log(`Calling function "${methodToEnhance.name}" with arguments: ${argumentsList}`);
7208
+ //
7209
+ // // Call the original function(target) with its intended "this" context (thisArg)
7210
+ // // and arguments (argumentsList) using Reflect.apply
7211
+ // const result=Reflect.apply(methodToEnhance, thisArg, argumentsList);
7212
+ //
7213
+ //// // --- Treatment AFTER function execution ---
7214
+ //// console.log(`Function "${methodToEnhance.name}" returned: ${result}`);
7215
+ //
7216
+ // inputObjects.push(result);
7217
+ // localServerCell.repercutChangesIfNeeded(inputObjects);
7218
+ //
7219
+ // return result;
7220
+ // },
7221
+ // }
7222
+ // );
7223
+ // },attribute=>isFunction(attribute));
7224
+ //
7225
+ // return clonedObject;
7226
+ //};
6226
7227
 
6227
7228
 
6228
7229
 
@@ -6938,7 +7939,7 @@ WebsocketImplementation={
6938
7939
  },
6939
7940
 
6940
7941
 
6941
- /*private*/getMessageDataBothImplementations:(eventOrMessageParam)=>{
7942
+ /*public*//*static*/getMessageDataBothImplementations:(eventOrMessageParam)=>{
6942
7943
 
6943
7944
  const eventOrMessage=(!WebsocketImplementation.useSocketIOImplementation?eventOrMessageParam.data:eventOrMessageParam);
6944
7945
 
@@ -7135,12 +8136,11 @@ WebsocketImplementation={
7135
8136
 
7136
8137
 
7137
8138
 
7138
- launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /*OPTIONAL*/sslOptions=null, httpHandlerParam=null, addCORSHeader=ADD_CORS_HEADER){
8139
+ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, doOnConnectionLost=null, /*OPTIONAL*/sslOptions=null, httpHandlerParam=null, addCORSHeader=ADD_CORS_HEADER){
7139
8140
 
7140
8141
  const EXCLUDED_FILENAMES_PARTS=[".keyHash.",".pem"];
7141
8142
 
7142
8143
 
7143
-
7144
8144
  if(typeof(https)==="undefined"){
7145
8145
  // TRACE
7146
8146
  console.log("«https» SERVER library not called yet, calling it now.");
@@ -7232,8 +8232,6 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
7232
8232
 
7233
8233
  const handler=nonull(httpHandlerParam, DEFAULT_HANDLER);
7234
8234
 
7235
-
7236
-
7237
8235
  let listenableServer;
7238
8236
  if(sslOptions){
7239
8237
  let httpsServer=https.createServer(sslOptions, handler).listen(port);
@@ -7254,7 +8252,7 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
7254
8252
  // CAUTION : MUST BE CALLED ONLY ONCE !
7255
8253
  server.onConnectionToClient((serverParam, clientSocketParam)=>{
7256
8254
  if(doOnConnect) doOnConnect(serverParam, clientSocketParam);
7257
- });
8255
+ }, doOnConnectionLost);
7258
8256
 
7259
8257
 
7260
8258
  server.onFinalize((serverParam)=>{
@@ -7269,15 +8267,13 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
7269
8267
  // TRACE
7270
8268
  console.log("INFO : SERVER : Generic Nodejs server launched and listening on port:" + port + "!");
7271
8269
 
7272
-
7273
-
7274
8270
 
7275
8271
 
7276
8272
  return server;
7277
8273
  }
7278
8274
 
7279
8275
 
7280
- initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFinalizeServer=null,
8276
+ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFinalizeServer=null, doOnConnectionLost=null,
7281
8277
  /*OPTIONAL*/portParam,
7282
8278
  /*OPTIONAL*/certPathParam,
7283
8279
  /*OPTIONAL*/keyPathParam){
@@ -7330,13 +8326,11 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
7330
8326
 
7331
8327
 
7332
8328
 
7333
-
7334
-
7335
8329
  const aotraNodeServer={config:serverConfig};
7336
8330
  aotraNodeServer.serverManager={ start:()=>{/*DEFAULT START FUNCTION, WILL BE OVERRIDEN LATER*/}};
7337
8331
 
7338
8332
  if(isHashAsked){
7339
- // We instanciate a temporary persister just to read the key hash file:
8333
+ // We instantiate a temporary persister just to read the key hash file:
7340
8334
  const persister=getPersister("./");
7341
8335
  let persisterIdSplits=persisterId.split("@");
7342
8336
  if(empty(persisterIdSplits) || persisterIdSplits.length!=2){
@@ -7419,9 +8413,9 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
7419
8413
  }
7420
8414
  }
7421
8415
 
7422
- aotraNodeServer.server=launchNodeHTTPServer(port, doOnClientConnection, doOnFinalizeServer, sslOptions);
8416
+ aotraNodeServer.server=launchNodeHTTPServer(port, doOnClientConnection, doOnFinalizeServer, doOnConnectionLost, sslOptions);
8417
+
7423
8418
 
7424
-
7425
8419
  return aotraNodeServer;
7426
8420
  };
7427
8421
 
@@ -7484,7 +8478,10 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
7484
8478
  // },REFRESH_SCREENSHOTS_MILLIS);
7485
8479
  //
7486
8480
  //
7487
- // },portParam,certPathParam,keyPathParam);
8481
+ // },
8482
+ // // On client connection lost :
8483
+ // (clientId)=>{lognow(`INFO : Connection to client id «${clientId}» was lost.`);},
8484
+ // portParam,certPathParam,keyPathParam);
7488
8485
  //
7489
8486
  //
7490
8487
  // // const doOnConnect=(serverParam, clientSocketParam)=>{
@@ -7952,8 +8949,8 @@ initClient=function(isNodeContext=true, useSocketIOImplementation=/*DEBUG*/false
7952
8949
  // CAUTION : MUST BE CALLED ONLY ONCE !
7953
8950
  socketToServerClientInstance.onConnectionToServer(()=>{
7954
8951
  if(doOnServerConnection){
7955
- if(selfParam) doOnServerConnection.apply(selfParam,[socketToServerClientInstance]);
7956
- else doOnServerConnection(socketToServerClientInstance);
8952
+ if(selfParam) doOnServerConnection.apply(selfParam,[socketToServerClientInstance, aotraClient]);
8953
+ else doOnServerConnection(socketToServerClientInstance, aotraClient);
7957
8954
  }
7958
8955
 
7959
8956
 
@@ -8050,8 +9047,9 @@ class ClientReceptionEntryPoint{
8050
9047
 
8051
9048
 
8052
9049
  // We check if the message matches the required message type :
8053
- if( this.listenerConfig && this.listenerConfig.listenerMessageType && dataWrapped.type
8054
- && this.listenerConfig.listenerMessageType!==dataWrapped.type){
9050
+ if( this.listenerConfig && this.listenerConfig.listenerMessageType
9051
+ && dataWrapped.data
9052
+ && dataWrapped.data.type && this.listenerConfig.listenerMessageType!==dataWrapped.data.type){
8055
9053
  return;
8056
9054
  }
8057
9055
 
@@ -8075,8 +9073,12 @@ class ClientReceptionEntryPoint{
8075
9073
  if(this.listenerConfig && this.listenerConfig.destroyListenerAfterReceiving)
8076
9074
  remove(clientReceptionEntryPoints, this);
8077
9075
 
8078
- if(this.doOnIncomingMessage)
8079
- this.doOnIncomingMessage(dataLocal, clientSocket);
9076
+ if(this.doOnIncomingMessage){
9077
+
9078
+ const dataRestored=restoreClassesInformation(dataLocal);
9079
+
9080
+ this.doOnIncomingMessage(dataRestored, clientSocket);
9081
+ }
8080
9082
 
8081
9083
  }
8082
9084
 
@@ -8119,7 +9121,9 @@ class ClientInstance{
8119
9121
  // I-
8120
9122
  //
8121
9123
  self.receive(channelNameForResponse, doOnIncomingMessageForResponse,
8122
- clientsRoomsTag, listenerId, {messageId:messageId, destroyListenerAfterReceiving:true});
9124
+ {messageId:messageId, destroyListenerAfterReceiving:true},
9125
+ clientsRoomsTag, listenerId,
9126
+ );
8123
9127
  //
8124
9128
 
8125
9129
  return resultPromise;
@@ -8134,7 +9138,7 @@ class ClientInstance{
8134
9138
  }
8135
9139
 
8136
9140
  // II-
8137
- receive(channelNameParam, doOnIncomingMessage, clientsRoomsTag=null, entryPointId=null, listenerConfig={destroyListenerAfterReceiving:false}){
9141
+ receive(channelNameParam, doOnIncomingMessage, listenerConfig={destroyListenerAfterReceiving:false, listenerMessageType:null}, clientsRoomsTag=null, entryPointId=null){
8138
9142
  const self=this;
8139
9143
 
8140
9144
  // // DBG
@@ -8174,8 +9178,11 @@ class ClientInstance{
8174
9178
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8175
9179
 
8176
9180
  // Channel information is stored in exchanged data :
8177
- let dataWrapped={channelName:channelNameParam, data:data};
8178
-
9181
+ let dataWrapped=saveClassesInformation({channelName:channelNameParam, data:data});
9182
+
9183
+ // CAUTION : WE CAN ONLY SEND STRINGS !!!
9184
+ // (or else we'll have TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of Object)
9185
+ // (Don't worry, the underlying sub-system will turn it back into Objects without you need to do anything!)
8179
9186
  dataWrapped=stringifyObject(dataWrapped);
8180
9187
 
8181
9188
  // TODO : FIXME : Use one single interface !
@@ -8307,8 +9314,9 @@ class ServerReceptionEntryPoint{
8307
9314
  if(!isClientInRoom) return;
8308
9315
 
8309
9316
  // We check if the message matches the required message type :
8310
- if( this.listenerConfig && this.listenerConfig.listenerMessageType && dataWrapped.type
8311
- && this.listenerConfig.listenerMessageType!==dataWrapped.type){
9317
+ if( this.listenerConfig && this.listenerConfig.listenerMessageType
9318
+ && dataWrapped.data
9319
+ && dataWrapped.data.type && this.listenerConfig.listenerMessageType!==dataWrapped.data.type){
8312
9320
  return;
8313
9321
  }
8314
9322
 
@@ -8316,7 +9324,11 @@ class ServerReceptionEntryPoint{
8316
9324
  // // DBG
8317
9325
  // lognow("(SERVER) this.doOnIncomingMessage:");
8318
9326
 
8319
- this.doOnIncomingMessage(dataWrapped.data, clientSocketParam);
9327
+ const dataLocal=dataWrapped.data;
9328
+
9329
+ const dataRestored=restoreClassesInformation(dataLocal);
9330
+
9331
+ this.doOnIncomingMessage(dataRestored, clientSocketParam);
8320
9332
  }
8321
9333
 
8322
9334
  }
@@ -8364,13 +9376,10 @@ class NodeServerInstance{
8364
9376
  });
8365
9377
  }
8366
9378
 
8367
-
8368
-
8369
9379
  // TODO : DEVELOP...
8370
9380
  //sendChainable(channelNameParam, data, clientsRoomsTag=null){
8371
9381
  //}
8372
9382
 
8373
-
8374
9383
  receive(channelNameParam, doOnIncomingMessage, listenerConfig=null, clientsRoomsTag=null){
8375
9384
 
8376
9385
  // DBG
@@ -8418,14 +9427,16 @@ class NodeServerInstance{
8418
9427
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8419
9428
 
8420
9429
  // Channel information is stored in exchanged data :
8421
- let dataWrapped={channelName:channelName, data:data};
8422
-
8423
-
9430
+ let dataWrapped=saveClassesInformation({channelName:channelName, data:data});
9431
+
9432
+ // CAUTION : WE CAN ONLY SEND STRINGS !!!
9433
+ // (or else we'll have TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of Object)
9434
+ // (Don't worry, the underlying sub-system will turn it back into Objects without you need to do anything!)
8424
9435
  dataWrapped=stringifyObject(dataWrapped);
8425
9436
 
8426
9437
  // TODO : FIXME : Use one single interface !
8427
9438
  if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
8428
- else clientSocket.emit(channelName,dataWrapped);
9439
+ else clientSocket.emit(channelName, dataWrapped);
8429
9440
 
8430
9441
  });
8431
9442
 
@@ -8445,12 +9456,12 @@ class NodeServerInstance{
8445
9456
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8446
9457
 
8447
9458
  // Channel information is stored in exchanged data :
8448
- let dataWrapped={channelName:channelName, data:data};
8449
- dataWrapped=stringifyObject(dataWrapped);
9459
+ let dataWrapped=saveClassesInformation({channelName:channelName, data:data});
8450
9460
 
8451
-
8452
- // DBG
8453
- lognow("(SERVER) WebsocketImplementation.useSocketIOImplementation:"+WebsocketImplementation.useSocketIOImplementation);
9461
+ // CAUTION : WE CAN ONLY SEND STRINGS !!!
9462
+ // (or else we'll have TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of Object)
9463
+ // (Don't worry, the underlying sub-system will turn it back into Objects without you need to do anything!)
9464
+ dataWrapped=stringifyObject(dataWrapped);
8454
9465
 
8455
9466
 
8456
9467
  // TODO : FIXME : Use one single interface !
@@ -8463,8 +9474,8 @@ class NodeServerInstance{
8463
9474
  return this;
8464
9475
  }
8465
9476
 
8466
-
8467
- onConnectionToClient(doOnConnection){
9477
+
9478
+ onConnectionToClient(doOnConnection, doOnConnectionLost=null){
8468
9479
  const self=this;
8469
9480
 
8470
9481
 
@@ -8473,7 +9484,6 @@ class NodeServerInstance{
8473
9484
 
8474
9485
  // DBG
8475
9486
  console.log("SERVER : ON CONNECTION !");
8476
-
8477
9487
 
8478
9488
  const clientId="autogeneratedid_"+getUUID();
8479
9489
 
@@ -8524,6 +9534,9 @@ class NodeServerInstance{
8524
9534
 
8525
9535
 
8526
9536
  clearInterval(clientSocket.stateCheckInterval);
9537
+
9538
+ if(doOnConnectionLost) doOnConnectionLost(clientSocket.clientId);
9539
+
8527
9540
  return;
8528
9541
  }
8529
9542
 
@@ -8582,7 +9595,71 @@ class NodeServerInstance{
8582
9595
 
8583
9596
 
8584
9597
 
9598
+ window.saveClassesInformation=(obj, visitedObjects=[])=>{
9599
+
9600
+ const attributeClassName=CLASSNAME_ATTRIBUTE_NAME;
9601
+
9602
+ if(contains(visitedObjects, obj)) return obj;
9603
+ visitedObjects.push(obj);
9604
+
9605
+ const className=getClassName(obj);
9606
+ if(className!="Object"){
9607
+ // We do the save :
9608
+ obj[attributeClassName]=className;
9609
+
9610
+ // DBG
9611
+ lognow("0 saving class name : :",obj);
9612
+
9613
+ }
9614
+
9615
+ foreach(obj, (attr, attrName)=>{
9616
+ const className=getClassName(attr);
9617
+
9618
+ // DBG
9619
+ if(attrName=="position") lognow("1 attr:",attr);
9620
+
9621
+ if(className!="Object"){
9622
+ saveClassesInformation(attr, visitedObjects);
9623
+ }
9624
+
9625
+ // DBG
9626
+ if(attrName=="position") lognow("2 attr:",attr);
9627
+
9628
+ },(attr, attrName)=>
9629
+ attr &&
9630
+ // DEBUG ONLY
9631
+ (attrName=="position" || (
9632
+ isObject(attr) && !attr[attributeClassName]
9633
+ ))
9634
+ );
9635
+
9636
+ return obj;
9637
+ };
9638
+
9639
+
9640
+ window.restoreClassesInformation=(obj, visitedObjects=[])=>{
9641
+
9642
+ const attributeClassName=CLASSNAME_ATTRIBUTE_NAME;
9643
+
9644
+ if(contains(visitedObjects, obj)) return obj;
9645
+ visitedObjects.push(obj);
9646
+
9647
+ const className=obj[attributeClassName];
9648
+ if(className){
9649
+ // We do the restore :
9650
+ const blankInstance=instantiate(className);
9651
+ obj=Object.assign(blankInstance, obj);
9652
+ }
9653
+
9654
+ foreach(obj, (attr, attrName)=>{
9655
+ const className=attr[attributeClassName];
9656
+ if(className!="Object"){
9657
+ obj[attrName]=restoreClassesInformation(attr, visitedObjects);
9658
+ }
9659
+ },attr=>attr && ((isObject(attr) && attr[attributeClassName]) || isArray(attr)) );
8585
9660
 
9661
+ return obj;
9662
+ };
8586
9663
 
8587
9664
 
8588
9665