aotrautils-srv 0.0.1841 → 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-02:00:37)»*/
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,20 +4152,26 @@ 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.`);
4155
+ lognow(`ERROR : CAUTION : «${className}» class does not seem to be registered in the global object. Cannot instantiate it.`);
4134
4156
 
4157
+ // DEBUG
4135
4158
  // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4136
4159
  newObj=eval("new "+className+"();");
4137
4160
 
4138
4161
  }
4139
4162
  }else{
4140
- // TRACE
4141
- 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.`);
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.`);
4142
4165
 
4166
+ // DEBUG
4143
4167
  // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4144
4168
  newObj=eval("new "+className+"();");
4145
4169
 
4146
4170
  }
4171
+
4172
+ // WORKAROUND
4173
+ newObj[CLASSNAME_ATTRIBUTE_NAME]=className;
4174
+
4147
4175
  return newObj;
4148
4176
  }
4149
4177
  }
@@ -4154,21 +4182,21 @@ window.instanciate=function(className=null){
4154
4182
  // newObj=Reflect.construct(classInWindow);
4155
4183
  // }catch(e1){
4156
4184
  //// // TRACE
4157
- //// 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 !)");
4158
4186
  // // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4159
4187
  // newObj=eval("new "+className+"();");
4160
4188
  // }
4161
4189
  // }catch(e2){
4162
4190
  //// // TRACE
4163
- //// 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 !)");
4164
4192
  // try{
4165
4193
  //// // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4166
4194
  //// newObj=eval("new window."+className+"();");
4167
4195
  // newObj=Reflect.construct(classInWindow);
4168
4196
  // }catch(e3){
4169
4197
  //// // TRACE
4170
- //// console.log("ERROR : Could not instanciate class «"+className+"» with eval and «window.». (Maybe class is undefined or default constructor doesn't exist !)");
4171
- //// 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 !)");
4172
4200
  // // TODO : FIXME : This is the most performance-costing fallback :
4173
4201
  // try{
4174
4202
  // if(classInWindow){
@@ -4180,7 +4208,7 @@ window.instanciate=function(className=null){
4180
4208
  // }
4181
4209
  // }catch(e4){
4182
4210
  //// // TRACE
4183
- //// console.log("ERROR : Could not instanciate class «"+className+"» with prototype affectation.");
4211
+ //// console.log("ERROR : Could not instantiate class «"+className+"» with prototype affectation.");
4184
4212
  //// console.log("ERROR : Returning plain object since all instantiation methods have failed for class «"+className+"».");
4185
4213
  // }
4186
4214
  // }
@@ -4188,17 +4216,17 @@ window.instanciate=function(className=null){
4188
4216
 
4189
4217
  // NEW :
4190
4218
  newObj=new classInWindow();
4219
+
4220
+ // WORKAROUND
4221
+ newObj[CLASSNAME_ATTRIBUTE_NAME]=className;
4222
+
4191
4223
  return newObj;
4192
4224
  };
4193
4225
 
4194
4226
 
4195
4227
 
4196
-
4197
-
4198
-
4199
-
4200
4228
  JSON.stringifyDecycle=function(obj){
4201
- let decycled=JSON.decycle(obj,null,"$className");
4229
+ let decycled=JSON.decycle(obj,null,CLASSNAME_ATTRIBUTE_NAME);
4202
4230
  // CANNOT USE stringifyObject(...) function because we are in a common, lower-level library !
4203
4231
  return stringifyObject(decycled);
4204
4232
  }
@@ -4206,6 +4234,8 @@ JSON.stringifyDecycle=function(obj){
4206
4234
 
4207
4235
  JSON.parseRecycle=function(str){
4208
4236
 
4237
+ const attributeClassName=CLASSNAME_ATTRIBUTE_NAME;
4238
+
4209
4239
  const parsedDecycled=parseJSON(str);
4210
4240
  let restoreClass=function(objParam){
4211
4241
 
@@ -4215,11 +4245,11 @@ JSON.parseRecycle=function(str){
4215
4245
  if(isArray(objParam)){
4216
4246
  newObj=[];
4217
4247
  }else{
4218
- let className=objParam["$className"];
4248
+ let className=objParam[attributeClassName];
4219
4249
  if(!className || isNativeClassName(className)){
4220
4250
  newObj= {};
4221
4251
  }else{
4222
- newObj=instanciate(className);
4252
+ newObj=instantiate(className);
4223
4253
  }
4224
4254
  }
4225
4255
 
@@ -4249,8 +4279,11 @@ JSON.parseRecycle=function(str){
4249
4279
  ,(itemOrAttr)=>{ return !isFunction(itemOrAttr); }
4250
4280
  );
4251
4281
 
4252
- if(destination["$className"]){
4253
- 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];
4254
4287
  }
4255
4288
 
4256
4289
  };
@@ -4268,6 +4301,7 @@ JSON.parseRecycle=function(str){
4268
4301
 
4269
4302
 
4270
4303
  window.clone=function(obj){
4304
+ if(obj==null) return null;
4271
4305
  // OLD :
4272
4306
  const str=JSON.stringifyDecycle(obj);
4273
4307
  const newObj=JSON.parseRecycle(str);
@@ -4740,7 +4774,7 @@ window.isFlatMap=function(obj){
4740
4774
 
4741
4775
  window.getAsFlatStructure=function(rawObject
4742
4776
  ,UUID_ATTR_NAME=DEFAULT_UUID_ATTR_NAME/* Yet Another UUID */
4743
- ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another Classname */
4777
+ ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another ClassName */
4744
4778
  ,POINTER_TO_ATTR_NAME=DEFAULT_POINTER_TO_ATTR_NAME/* Yet Another PointerTo */
4745
4779
  ){
4746
4780
  // Flattening :
@@ -4791,7 +4825,7 @@ function flattenGraph(root, UUID_ATTR_NAME, CLASSNAME_ATTR_NAME, POINTER_TO_ATTR
4791
4825
 
4792
4826
  window.getAsTreeStructure=function(oldMap, keepClassName=false
4793
4827
  ,UUID_ATTR_NAME=DEFAULT_UUID_ATTR_NAME/* Yet Another UUID */
4794
- ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another Classname */
4828
+ ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another ClassName */
4795
4829
  ,POINTER_TO_ATTR_NAME=DEFAULT_POINTER_TO_ATTR_NAME/* Yet Another PointerTo */
4796
4830
  ){
4797
4831
 
@@ -4813,7 +4847,7 @@ window.restoreGraph=(flatData, keepClassName=false, UUID_ATTR_NAME, CLASSNAME_AT
4813
4847
 
4814
4848
  const objData = flatData[id];
4815
4849
  const className = objData[CLASSNAME_ATTR_NAME];
4816
- const newInstance = instanciate(className);
4850
+ const newInstance = instantiate(className);
4817
4851
 
4818
4852
  restoredObjects[id]=newInstance; // Assign ID early to handle circular references
4819
4853
 
@@ -5178,7 +5212,7 @@ window.getMonoThreadedTimeout=function(actuator=null){
5178
5212
 
5179
5213
  // ======================== Routine ========================
5180
5214
 
5181
- window.MonoThreadedRoutine=class {
5215
+ window.MonoThreadedRoutine=class MonoThreadedRoutine{
5182
5216
  constructor(actuator,refreshMillis=null,startDependsOnParentOnly=false){
5183
5217
 
5184
5218
  this.actuator=actuator;
@@ -5188,15 +5222,21 @@ window.MonoThreadedRoutine=class {
5188
5222
  this.time=getNow();
5189
5223
  this.refreshMillis=refreshMillis;
5190
5224
 
5191
- this.durationTimeFactorHolder={durationTimeFactor:1};
5225
+ this.managedTimeFactor=1;
5192
5226
  // Three-states : started, paused, stopped.
5193
5227
 
5194
5228
  }
5195
5229
 
5196
- setDurationTimeFactorHolder(durationTimeFactorHolder){
5197
- this.durationTimeFactorHolder=durationTimeFactorHolder;
5230
+ registerToTimeFactorManager(timeFactorManager){
5231
+ timeFactorManager.registerTimeFactorManagee(this);
5198
5232
  return this;
5199
5233
  }
5234
+
5235
+ changeManagedTimeFactor(newManagedTimeFactor){
5236
+ this.managedTimeFactor=newManagedTimeFactor;
5237
+ return this;
5238
+ }
5239
+
5200
5240
  isStarted(){
5201
5241
  return this.started || this.startDependsOnParentOnly;
5202
5242
  }
@@ -5233,7 +5273,7 @@ window.MonoThreadedRoutine=class {
5233
5273
  if(!this.isStarted() || this.paused) return;
5234
5274
  // Looping index with a delay :
5235
5275
 
5236
- const delayHasPassed=hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor());
5276
+ const delayHasPassed=hasDelayPassed(this.time, this.refreshMillis*this.managedTimeFactor);
5237
5277
  if(!this.refreshMillis || delayHasPassed){
5238
5278
  if(this.refreshMillis && delayHasPassed){
5239
5279
  this.time=getNow();
@@ -5243,7 +5283,8 @@ window.MonoThreadedRoutine=class {
5243
5283
  this.stop(args);
5244
5284
  return;
5245
5285
  }
5246
- if(this.actuator.doOnEachStep) this.actuator.doOnEachStep(args);
5286
+ if(this.actuator.doOnEachStep)
5287
+ this.actuator.doOnEachStep(args);
5247
5288
  }
5248
5289
 
5249
5290
  }
@@ -5272,19 +5313,27 @@ window.MonoThreadedGoToGoal=class {
5272
5313
  this.time=getNow();
5273
5314
  this.refreshMillis=refreshMillis;
5274
5315
 
5275
- this.durationTimeFactorHolder={durationTimeFactor:1};
5316
+ this.managedTimeFactor=1;
5317
+
5276
5318
 
5277
5319
  // Three-states : started, paused, stopped.
5278
5320
  }
5279
- setDurationTimeFactorHolder(durationTimeFactorHolder){
5280
- this.durationTimeFactorHolder=durationTimeFactorHolder;
5321
+
5322
+ registerToTimeFactorManager(timeFactorManager){
5323
+ timeFactorManager.registerTimeFactorManagee(this);
5324
+ return this;
5325
+ }
5326
+
5327
+ changeManagedTimeFactor(newManagedTimeFactor){
5328
+ this.managedTimeFactor=newManagedTimeFactor;
5281
5329
 
5282
5330
  // We recalculate total time-dependent values :
5283
- this.totalTimeMillis=this.totalTimeMillis*this.durationTimeFactorHolder.getDurationTimeFactor();
5331
+ this.totalTimeMillis=this.totalTimeMillis*this.managedTimeFactor;
5284
5332
  this.stepValue=((this.refreshMillis*(this.goalValue-this.actualValue))/this.totalTimeMillis);
5285
5333
 
5286
5334
  return this;
5287
5335
  }
5336
+
5288
5337
  isStarted(){
5289
5338
  return this.started || this.startDependsOnParentOnly;
5290
5339
  }
@@ -5296,7 +5345,7 @@ window.MonoThreadedGoToGoal=class {
5296
5345
  setNewGoal(goalValue, refreshMillisParam=null, totalTimeMillis=null){
5297
5346
  if(Math.round(this.value)===Math.round(goalValue)) return;
5298
5347
  if(refreshMillisParam) this.refreshMillis=refreshMillisParam;
5299
- if(totalTimeMillis) this.totalTimeMillis=totalTimeMillis*this.durationTimeFactorHolder.getDurationTimeFactor();
5348
+ if(totalTimeMillis) this.totalTimeMillis=totalTimeMillis*this.managedTimeFactor;
5300
5349
 
5301
5350
  this.goalValue=goalValue;
5302
5351
  this.stepValue=((this.refreshMillis*(this.goalValue-this.value))/this.totalTimeMillis);
@@ -5383,7 +5432,7 @@ window.MonoThreadedGoToGoal=class {
5383
5432
  }
5384
5433
 
5385
5434
  hasDelayPassed(){
5386
- return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor()));
5435
+ return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.managedTimeFactor));
5387
5436
  }
5388
5437
 
5389
5438
 
@@ -5409,7 +5458,7 @@ AOTRAUTILS_LIB_IS_LOADED=true;
5409
5458
 
5410
5459
 
5411
5460
 
5412
- /*utils AI library associated with aotra version : «1_29072022-2359 (09/04/2026-02:00:37)»*/
5461
+ /*utils AI library associated with aotra version : «1_29072022-2359 (02/06/2026-01:06:15)»*/
5413
5462
  /*-----------------------------------------------------------------------------*/
5414
5463
 
5415
5464
 
@@ -5555,11 +5604,383 @@ getOpenAIAPIClient=(modelName, apiURL, agentRole, defaultPrompt)=>{
5555
5604
 
5556
5605
 
5557
5606
 
5558
- /*utils CONSOLE library associated with aotra version : «1_29072022-2359 (09/04/2026-02:00:37)»*/
5607
+ /*utils CONSOLE library associated with aotra version : «1_29072022-2359 (02/06/2026-01:06:15)»*/
5559
5608
  /*-----------------------------------------------------------------------------*/
5560
5609
 
5561
5610
 
5562
- /* ## 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)
5563
5984
  *
5564
5985
  * This set of methods gathers utility generic-purpose methods usable in any JS project.
5565
5986
  * Several authors of snippets published freely on the Internet contributed to this library.
@@ -5592,8 +6013,8 @@ if(typeof(window)==="undefined") window=global;
5592
6013
 
5593
6014
  AORTAC_OUTCOMING_SERVERS_CONNECTION_GLOBAL_TIMEOUT=30000;
5594
6015
 
5595
- // New implementation :
5596
6016
  AORTAC_SERVER_CELL_PERIODICAL_CONNECTIVITY_CHECK_MILLIS=2000;
6017
+
5597
6018
  class AORTACServerCell{
5598
6019
 
5599
6020
  constructor(quorumNumber=1, selfOrigin, outcomingCellsOrigins, model, controller, sslConfig={/*OPTIONAL*/certPath:null,/*OPTIONAL*/keyPath:null}){
@@ -5606,14 +6027,14 @@ class AORTACServerCell{
5606
6027
  const infos=splitURL(this.selfOrigin);
5607
6028
 
5608
6029
  //DBG
5609
- lognow("infos:::::::::::::",infos);
6030
+ lognow("(SERVER) infos:::::::::::::",infos);
5610
6031
 
5611
6032
  this.protocol=nonull(infos.protocol,"ws");
5612
6033
  this.host=nonull(infos.host,"localhost");
5613
6034
  this.port=nonull(infos.port,"30000");
5614
6035
  this.sslConfig=sslConfig;
5615
6036
 
5616
- this.server=null;
6037
+ this.serverWrapper=null;
5617
6038
  this.incomingCells={};
5618
6039
 
5619
6040
  this.outcomingCellsOrigins=outcomingCellsOrigins;
@@ -5631,20 +6052,26 @@ class AORTACServerCell{
5631
6052
  };
5632
6053
 
5633
6054
  this.cellsOverview={};
6055
+ this.numberOfPartitions=null;
6056
+ this.partitionId=null;
6057
+
6058
+ this.isServerCellQuorumLastCell=false;
5634
6059
 
5635
6060
  this.startTime=null;
5636
6061
 
5637
6062
  this.quorumIsReachedSequenceIsInitiated=false;
5638
6063
  // this.quorumCells=null; // (CAUTION : only the latest ready cell when quorum is reached has this attribute populated)
5639
6064
 
5640
- this.liveModelObjects={};
6065
+ this.lobuleZone=new ServerCellLobule();
6066
+
6067
+ this.clients={};
5641
6068
 
5642
6069
  }
5643
6070
 
5644
6071
  start(){
5645
6072
 
5646
6073
  this.startTime=getNow();
5647
- this.server=this.launchServerForIncomingConnections();
6074
+ this.serverWrapper=this.launchServerForIncomingConnections();
5648
6075
 
5649
6076
  if(empty(this.outcomingCellsOrigins) || (getArraySize(this.outcomingCellsOrigins)==1 && contains(this.outcomingCellsOrigins, this.selfOrigin))){
5650
6077
  // TRACE
@@ -5661,37 +6088,72 @@ class AORTACServerCell{
5661
6088
  return this;
5662
6089
  }
5663
6090
 
6091
+
6092
+ // ===================================================================================================
6093
+ // SERVER CELL CORE
6094
+ // ===================================================================================================
6095
+
5664
6096
  /*private*/launchServerForIncomingConnections(){
5665
6097
  const self=this;
5666
6098
 
5667
- const server=initNodeServerInfrastructureWrapper(
6099
+ const serverWrapper=initNodeServerInfrastructureWrapper(
5668
6100
  // On each client connection :
5669
6101
  null,
5670
6102
  // On client finalization :
5671
6103
  function(server){
5672
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
+
5673
6111
  // On-receive message listeners handling ;
5674
- foreach(Object.keys(self.onReceiveMessageListeners["incoming"]), channelName=>{
5675
- server.receive(channelName, (message, clientSocket)=>{
5676
- self.executeListeners("incoming",channelName, message, clientSocket);
5677
- });
5678
- });
5679
- foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{
6112
+ foreach(channelsNames, channelName=>{
5680
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
+
5681
6123
  self.executeListeners("both",channelName, message, clientSocket);
5682
6124
  });
5683
6125
  });
5684
6126
 
5685
6127
 
5686
- // HANDLE HELLO SEQUENCE
5687
- self.handleHelloSequence();
6128
+ // HANDLE SERVER CELL HELLO SEQUENCE
6129
+ self.handleServerCellHelloSequence();
6130
+
6131
+
6132
+ // ******************************************
6133
+ // CLIENT
6134
+
6135
+ // HANDLE CLIENT HELLO SEQUENCE
6136
+ self.handleClientHelloSequence();
5688
6137
 
5689
- }, this.port, this.sslConfig.certPath, this.sslConfig.keyPath);
5690
- server.serverManager.start();
5691
- return server;
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;
5692
6151
  }
5693
6152
 
5694
6153
  /*private*/probeOutcomingNetwork(){
6154
+
6155
+ const self=this;
6156
+
5695
6157
  const numberOfTotalServers=getArraySize(this.outcomingCells);
5696
6158
  let numberOfConnectedOutcomingServers=0;
5697
6159
  foreach(this.outcomingCells, outcomingCell=>{
@@ -5713,7 +6175,6 @@ class AORTACServerCell{
5713
6175
  return;
5714
6176
  }
5715
6177
 
5716
- const self=this;
5717
6178
 
5718
6179
  // We try to connect to all outcoming cells :
5719
6180
  foreach(this.outcomingCells, (outcomingCell, outcomingCellOrigin)=>{
@@ -5725,20 +6186,29 @@ class AORTACServerCell{
5725
6186
 
5726
6187
  const clientInstance=initClient(true, false, (socketToServerClientInstance)=>{
5727
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
+
5728
6195
  // On-receive message listeners handling ;
5729
- foreach(Object.keys(self.onReceiveMessageListeners["outcoming"]), channelName=>{
6196
+ foreach(channelsNames, channelName=>{
5730
6197
  socketToServerClientInstance.receive(channelName, (message, clientSocket)=>{
6198
+
6199
+ // DBG
6200
+ lognow("DEBUG : Server cell receives message from outcoming...");
6201
+
5731
6202
  self.executeListeners("outcoming",channelName, message, clientSocket);
5732
- });
5733
- });
5734
- foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{
5735
- socketToServerClientInstance.receive(channelName, (message, clientSocket)=>{
6203
+
6204
+ // DBG
6205
+ lognow("DEBUG : Server cell receives message from both (out)...");
6206
+
5736
6207
  self.executeListeners("both",channelName, message, clientSocket);
5737
6208
  });
5738
6209
  });
5739
-
5740
6210
 
5741
- // INITIATE HELLO SEQUENCE
6211
+ // INITIATE SERVER CELL HELLO SEQUENCE
5742
6212
  self.initiateHelloSequence(socketToServerClientInstance);
5743
6213
 
5744
6214
  // We are connected:
@@ -5762,47 +6232,68 @@ class AORTACServerCell{
5762
6232
 
5763
6233
  }
5764
6234
 
5765
-
5766
6235
  /*private*/handleCommonListeners(){
5767
-
5768
6236
  // HANDLE CELL IS READY SEQUENCE
5769
6237
  this.handleCellIsReadySequence();
5770
-
5771
6238
  // HANDLE PARTITION SEQUENCE
5772
6239
  this.handlePartitionSequence();
5773
6240
 
5774
- }
6241
+
6242
+ // ******************************************
6243
+ // CLIENT
6244
+ // HANDLE CLIENT MODEL INITIAL POPULATION SEQUENCE
6245
+ this.handleGetServersForZoneRequest();
6246
+
6247
+ // HANDLE RECONNECTED CLIENT HELLO SEQUENCE
6248
+ this.handleReconnectedClientHelloSequence();
5775
6249
 
6250
+ }
6251
+
5776
6252
 
5777
6253
  /*private*/executeListeners(outletName, channelName, message, clientSocket){
5778
6254
  const onReceiveMessageListeners=this.onReceiveMessageListeners[outletName][channelName];
5779
6255
  if(!onReceiveMessageListeners) return;
5780
6256
 
5781
- // OUTCOMING NODES LISTENERS HOOK :
5782
- // // TRACE
5783
- // lognow("INFO : SERVER : Received a "+channelName+" message: ", message);
5784
-
5785
- foreach(onReceiveMessageListeners, (listeners,listenerMessageType)=>{
5786
- foreach(listeners, (listener)=>{
5787
- // // TRACE
5788
- // lognow(`INFO : SERVER : Executing a onReceiveMessageListeners for messageType ${listenerMessageType}.`);
5789
-
5790
- listener.execute(this, message, this.server, clientSocket);
5791
-
5792
- },(listener)=>(message.type===listener.listenerMessageType));
5793
- });
5794
-
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");
5795
6280
  }
5796
6281
 
5797
6282
 
5798
6283
  /*private*/sendMessageToBlob(channelName, message,
5799
- 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){
5800
6292
 
5801
- const self=this;
5802
-
5803
6293
  if(broadcastConfig){
5804
6294
  if(broadcastConfig.isOriginatingCell){
5805
6295
  message.originatingCellOrigin=this.selfOrigin;
6296
+ message.originatingPartitionId=this.partitionId;
5806
6297
  }
5807
6298
  if(broadcastConfig.isRequest){
5808
6299
  message.isRequest=true;
@@ -5815,10 +6306,36 @@ class AORTACServerCell{
5815
6306
  if(!message.visitedCells)
5816
6307
  message.visitedCells=[];
5817
6308
  else if(contains(message.visitedCells,this.selfOrigin))
5818
- return;
6309
+ return null;
5819
6310
  message.visitedCells.push(this.selfOrigin);
5820
6311
 
5821
- 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){
5822
6339
  foreach(this.incomingCells,(incomingCell)=>{
5823
6340
  // As a server, we send (forward) the message to the currently iterated upon client that is connected to us :
5824
6341
  incomingCell.server.send(channelName, message, null, incomingCell.clientSocket);
@@ -5826,7 +6343,6 @@ class AORTACServerCell{
5826
6343
  (incomingCell.connected && !contains(message.visitedCells, incomingCellOrigin))
5827
6344
  );
5828
6345
  }
5829
-
5830
6346
  foreach(this.outcomingCells,(outcomingCell)=>{
5831
6347
  // As a client, we send (forward) the message to the currently iterated upon server we are connected to :
5832
6348
  outcomingCell.socketToServerClientInstance.send(channelName, message);
@@ -5834,49 +6350,84 @@ class AORTACServerCell{
5834
6350
  (outcomingCell.connected && !contains(message.visitedCells, outcomingCellOrigin))
5835
6351
  );
5836
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
+ }
5837
6369
 
5838
- // We only add a result listener for the cell which sent the request message :
5839
- if(message.isRequest && message.originatingCellOrigin==this.selfOrigin
5840
- //&& (typeof(message.result)=="undefined" || message.result==null)
5841
- ){
5842
-
5843
- const outletName="both";
5844
-
5845
- // To emulate a promise-like behavior :
5846
- let blobResponseListeners=this.onReceiveMessageListeners[outletName][channelName][message.type];
5847
- if(blobResponseListeners && blobResponseListeners["forRequestsIssuedBySelf"]){
5848
- // TRACE
5849
- lognow(`INFO : Blob response «forRequestsIssuedBySelf» listener for cell ${this.selfOrigin} on channel ${channelName} for «${message.type}» already exists.`);
5850
- return blobResponseListeners["forRequestsIssuedBySelf"];
5851
- }else{
5852
- blobResponseListeners=getOrCreateEmptyAttribute(this.onReceiveMessageListeners[outletName][channelName],message.type);
5853
- }
5854
-
5855
- const blobResponseListener={
5856
- thenCallback:null,
5857
- execute:(self, message, server, clientSocket)=>{
5858
- // When we have received the message :
5859
- 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){
5860
6390
 
5861
- // We need to remove the result listener once it is completed though.
5862
- delete blobResponseListeners["forRequestsIssuedBySelf"];
5863
- },
5864
- then:(thenCallback)=>{
5865
- 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
+ }
5866
6399
  }
5867
- };
5868
- blobResponseListeners["forRequestsIssuedBySelf"]=blobResponseListener;
5869
-
5870
- return blobResponseListener;
5871
- }else{
5872
- return null;
5873
- }
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
+ };
6417
+
5874
6418
 
6419
+
6420
+ return blobResponseListener;
5875
6421
  }
6422
+
6423
+
5876
6424
 
6425
+ // ===================================================================================================
6426
+ // SERVER CELLS
6427
+ // ===================================================================================================
5877
6428
 
5878
6429
  // ==========================================================
5879
- // HELLO SEQUENCE
6430
+ // SERVER CELL HELLO SEQUENCE
5880
6431
 
5881
6432
  /*private*/initiateHelloSequence(socketToServerClientInstance){
5882
6433
 
@@ -5889,28 +6440,30 @@ class AORTACServerCell{
5889
6440
  }
5890
6441
 
5891
6442
 
5892
- /*private*/handleHelloSequence(){
6443
+ /*private*/handleServerCellHelloSequence(){
5893
6444
 
5894
6445
  // 2- We wait to receive the hello request of the incoming cell connection :
5895
- getOrCreateEmptyAttribute(
5896
- this.onReceiveMessageListeners["both"]["protocol"],"helloRequest")["forRequestsIssuedByOthers"]={
5897
- execute:(selfParam, message, server, clientSocket)=>{
6446
+ this.onReceiveMessageListeners["both"]["protocol"]["helloRequest"]={
6447
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
5898
6448
 
5899
- const cellOriginToCheck=message.originatingCellOrigin;
6449
+ const cellOriginToCheck=messageParam.originatingCellOrigin;
5900
6450
 
5901
6451
  // TRACE
5902
- 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...`);
5903
6453
 
5904
6454
 
5905
6455
  selfParam.incomingCells[cellOriginToCheck]={
5906
6456
  connected:true,
5907
- server:server,
6457
+ server:serverWrapper.server,
5908
6458
  // DO NOT USE TO SEND/RECEIVE ANYTHINIG !
5909
6459
  // For this, use the server attribute (+ the clientSocket as argument) instead.
5910
6460
  clientSocket:clientSocket,
5911
6461
  };
5912
6462
 
5913
- },
6463
+
6464
+ // NO SERVER CELL HELLO CONFIRMATION SENT BACK TO THE OTHER SERVER CELL (to save time)
6465
+
6466
+ },
5914
6467
  listenerMessageType:"helloRequest"
5915
6468
  };
5916
6469
 
@@ -5935,18 +6488,14 @@ class AORTACServerCell{
5935
6488
  }
5936
6489
 
5937
6490
  /*private*/handleCellIsReadySequence(){
5938
- getOrCreateEmptyAttribute(
5939
- this.onReceiveMessageListeners["both"]["protocol"],"cellIsReady")["forRequestsIssuedByOthers"]={
5940
- execute:(selfParam, message, server, clientSocket)=>{
5941
6491
 
5942
- selfParam.cellsOverview[message.originatingCellOrigin]={ready:true,startTime:message.startTime};
5943
-
5944
- 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};
5945
6497
 
5946
- // // TRACE
5947
- // lognow("INFO : Updated cells overview for this cell ("+selfParam.selfOrigin+") :",selfParam.cellsOverview);
5948
- // lognow("DEBUG : selfParam.outcomingCells :",Object.keys(selfParam.outcomingCells));
5949
- // lognow("DEBUG : selfParam.incomingCells :",Object.keys(selfParam.incomingCells));
6498
+ selfParam.sendMessageToBlob("protocol",messageParam);
5950
6499
 
5951
6500
  // INITIATE QUORUM IS REACHED SEQUENCE
5952
6501
  selfParam.initiateQuorumIsReachedSequenceIfNecessary();
@@ -5972,11 +6521,11 @@ class AORTACServerCell{
5972
6521
  const quorumCells=copy(this.cellsOverview);
5973
6522
 
5974
6523
  // TRACE
5975
- lognow("INFO : Quorum is reached and this is the latest started cell. quorumCells :",quorumCells);
5976
-
5977
- this.initializeModel(quorumCells);
5978
-
5979
-
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);
5980
6529
 
5981
6530
  }
5982
6531
 
@@ -5987,249 +6536,694 @@ class AORTACServerCell{
5987
6536
  // ==========================================================
5988
6537
  // MODEL MANAGEMENT
5989
6538
 
5990
- /*private*/initializeModel(quorumCells){
5991
-
5992
- const self=this;
5993
-
5994
- 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});
5995
6771
 
5996
- const controller=this.controller;
5997
- const model=this.model;
5998
-
5999
-
6000
- // We initialize the whole model :
6001
- // (controller will handle if a persisted model exists)
6002
- controller.initializeModelForAORTACNode(model);
6003
-
6004
- // This must return the asked number of partitions, indexed by partition id :
6005
- // CAUTION : The partition function MUST return ALL the objects in a partition, WHATEVER THEIR NESTING LEVEL !!!
6006
- 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);
6007
6778
 
6008
- // We exclude this cell from the partition sending, because we already have the model partition at hand.
6009
- this.removeLinks(modelPartitions[0].objects);
6010
- this.doOnModelPartitionReception(modelPartitions[0]);
6011
-
6012
- // We send the models objects to the required cells :
6013
- let i=1;
6014
- foreach(quorumCells, (quorumCell,quorumCellOrigin)=>{
6779
+ selfParam2.lobuleZone.clear(clientIdLocal);
6780
+
6781
+ });
6782
+
6015
6783
 
6016
- const modelPartition=modelPartitions[i];
6017
- self.removeLinks(modelPartition.objects);
6018
-
6019
- const message=JSON.decycle({type:"modelPartition",partition:modelPartition});
6020
-
6021
- // TRACE
6022
- lognow(`INFO : Cell ${self.selfOrigin} is sending a partition to cell ${quorumCellOrigin}...`,message);
6023
-
6024
- self.sendMessageToBlob("protocol", message,
6025
- {isOriginatingCell:true, destinationCellsOrigins:[quorumCellOrigin], includeIncomingConnectionInTransmission:false, isRequest:false});
6784
+ },
6785
+ listenerMessageType:"clientHello"
6786
+ };
6026
6787
 
6027
- i++;
6028
- },(quorumCell,quorumCellOrigin)=>quorumCellOrigin!=self.selfOrigin);
6029
-
6030
-
6031
- // Each quorum member cell is responsible for a model partition
6032
- // Then the sattelite cells will be handling the duplication
6033
-
6034
6788
  }
6035
-
6036
6789
 
6037
- // HANDLE PARTITION SEQUENCE
6038
6790
 
6039
- /*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(){
6040
6793
 
6041
- // 2- We wait to receive the hello request of the incoming cell connection :
6042
- getOrCreateEmptyAttribute(
6043
- this.onReceiveMessageListeners["both"]["protocol"],"modelPartition")["forRequestsIssuedByOthers"]={
6044
- 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
+
6045
6820
 
6046
- // If this cell is in the destinations of this message, we pass it along and then we do what it says :
6047
- selfParam.sendMessageToBlob("protocol",messageParam);
6821
+ const message={type:"getServersForZone.response", objectsCount:allObjectsCorrespondingToZoneInServerCell.length, answeringServerOrigin:selfParam.selfOrigin};
6048
6822
 
6049
- if(contains(messageParam.destinationCellsOrigins,selfParam.selfOrigin)){
6050
-
6051
- const message=JSON.recycle(messageParam);
6052
-
6053
- selfParam.doOnModelPartitionReception(message.partition);
6054
-
6055
- }
6823
+ // TRACE
6824
+ lognow(`INFO : Server cell ${selfParam.selfOrigin} is sending its origin to server cell ${messageParam.originatingCellOrigin}...: message:`, message);
6056
6825
 
6057
- },
6058
- listenerMessageType:"modelPartition"
6826
+ selfParam.replyToBlobRequest("protocol", messageParam, message);
6827
+ },listenerMessageType:"getServersForZone"
6059
6828
  };
6060
6829
 
6061
6830
  }
6062
6831
 
6063
6832
 
6064
- /*private*/doOnModelPartitionReception(partition){
6065
-
6066
- // TRACE
6067
- lognow(`INFO : Incoming partition for cell ${this.selfOrigin}. Updating local model...`,partition);
6068
- lognow(`>>>>`,stringifyObject(JSON.decycle(partition.objects),1));
6833
+ // ----
6834
+ // HANDLE RECONNECTED CLIENT HELLO SEQUENCE
6835
+ handleReconnectedClientHelloSequence(){
6069
6836
 
6837
+ // Here we only want to collect the servers on which to reconnect to later :
6070
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
+
6071
6875
 
6072
6876
  }
6073
6877
 
6074
6878
 
6075
- /*private*/removeLinks(linkedObjects, currentObject=null){
6076
-
6077
- const self=this;
6078
-
6079
- if(!currentObject){
6080
- foreach(linkedObjects, obj=>{
6081
- self.removeLinksOnSingleObject(linkedObjects, obj);
6082
- });
6083
- }else{
6084
- self.removeLinksOnSingleObject(linkedObjects, currentObject);
6085
- }
6086
-
6087
- return linkedObjects;
6088
- }
6879
+ // ==========================================================
6089
6880
 
6090
- /*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
+ // }
6091
6917
 
6092
- const self=this;
6093
- foreach(currentObject, (attr,attrNameOrIndex)=>{
6094
- // We only remove links to the objects not in the partition
6095
- if(isClassObject(attr)){
6096
- let aortacId=attr.aortacId;
6097
- if(!aortacId){
6098
- aortacId=getUUID();
6099
- attr.aortacId=aortacId;
6100
- }
6101
- if(!contains(linkedObjects,attr)){
6102
- currentObject[attrNameOrIndex]=aortacId+"@aortacId";
6103
- // CAUTION : No need for recursive call here, because the partition function returns ALL the objects in a partition,
6104
- // WHATEVER THEIR NESTING LEVEL !!!
6105
- }
6106
- }else{
6107
- // However, we need a recursive call for any simple object or array that may reference a class object in another partition :
6108
- self.removeLinksOnSingleObject(linkedObjects, attr);
6109
- }
6110
- },obj=>(isObject(obj) || isArray(obj)));
6111
6918
 
6112
- }
6113
6919
 
6114
6920
 
6115
- // ******************************************************************
6116
-
6117
- /*private*/collectDependencies(inputObjects){
6921
+ // **********************************************************************
6922
+
6923
+ /*private*/partiallyPopulatedRootContainer(allObjectsCorrespondingToZoneInServerCell=[]){
6118
6924
 
6119
- ////
6120
- lognow("DEBUG : collectDependencies()...",inputObjects);
6925
+ const rootContainerLocal=this.model.getRootContainer();
6121
6926
 
6927
+ const rootContainer=this.model.populateRootContainerWithObjectsSubset(rootContainerLocal, allObjectsCorrespondingToZoneInServerCell, true);
6928
+ rootContainer.isRootContainer=true;
6122
6929
 
6930
+ return rootContainer;
6123
6931
  }
6124
-
6125
- /*private*/repercutChangesIfNeeded(inputObjects){
6126
-
6127
-
6128
- ////
6129
- lognow("DEBUG : repercutChangesIfNeeded()...",inputObjects);
6130
6932
 
6131
-
6132
- }
6133
-
6134
-
6135
6933
  }
6136
6934
 
6137
6935
 
6138
6936
 
6139
- // ******************************************************************
6140
6937
 
6141
- // Public static hydration method :
6142
- window.ao=(incompleteModelObjectToDecorate)=>{
6143
-
6144
- const localServerCell=window.aortacCServerNodeInstance;
6145
- if(!localServerCell){
6146
- // TRACE
6147
- lognow("ERROR : No AORTACCServerNode singleton instance. Doing nothing on the model object.");
6148
- return incompleteModelObjectToDecorate;
6149
- }
6150
-
6151
- const liveModelObjects=localServerCell.liveModelObjects;
6152
-
6153
- // First we clone the object, for its attriutes values information :
6154
- const clonedObject=clone(incompleteModelObjectToDecorate);
6155
-
6156
- // Then we replace all its methods :
6157
- foreach(clonedObject, (method, methodName)=>{
6158
- clonedObject[methodName]=new Proxy(method,
6159
- {
6160
- apply: function(methodToEnhance, thisArg, argumentsList) {
6161
6938
 
6162
- const inputObjects=[thisArg];
6163
- inputObjects.push(...argumentsList);
6164
- localServerCell.collectDependencies(inputObjects);
6165
-
6166
- // // --- Treatment BEFORE function execution ---
6167
- // console.log(`Calling function "${methodToEnhance.name}" with arguments: ${argumentsList}`);
6168
-
6169
- // Call the original function (target) with its intended 'this' context (thisArg)
6170
- // and arguments (argumentsList) using Reflect.apply
6171
- const result = Reflect.apply(methodToEnhance, thisArg, argumentsList);
6172
-
6173
- // // --- Treatment AFTER function execution ---
6174
- // console.log(`Function "${methodToEnhance.name}" returned: ${result}`);
6175
-
6176
- inputObjects.push(result);
6177
- localServerCell.repercutChangesIfNeeded(inputObjects);
6178
6939
 
6179
- return result;
6180
- },
6181
- }
6182
- );
6183
- },attribute=>isFunction(attribute));
6940
+
6941
+
6942
+
6943
+ class ServerCellLobule{
6184
6944
 
6185
- return clonedObject;
6186
- };
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
+ }
6187
7008
 
6188
7009
 
6189
7010
 
6190
- 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){
6191
7012
  //return new AORTACServerNode("node_"+getUUID("short"), selfOrigin, outcomingCellsOrigins, model, controller);
6192
- if(window.aortacCServerNodeInstance){
7013
+ if(window.aortacCServerCellInstance){
6193
7014
  // TRACE
6194
- 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.");
6195
7016
  }
6196
- window.aortacCServerNodeInstance=new AORTACServerCell(quorumNumber, selfOrigin, outcomingCellsOrigins, model, controller);
6197
- return window.aortacCServerNodeInstance;
7017
+ window.aortacCServerCellInstance=new AORTACServerCell(quorumNumber, selfOrigin, outcomingCellsOrigins, model, controller);
7018
+ return window.aortacCServerCellInstance;
6198
7019
  }
6199
7020
 
6200
7021
 
6201
7022
 
6202
7023
 
7024
+ // ===============================================================================================================================
7025
+ // ===============================================================================================================================
7026
+ // UTILITY METHODS
6203
7027
 
6204
7028
 
7029
+ // USAGE EXAMPLE :
6205
7030
 
6206
- // ==================================================================================================================
6207
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
6208
7058
 
6209
- // AORTAC CLIENT
6210
7059
 
7060
+ window.proxyClass=function(classToProxy,
7061
+ doOnAccess=(className, propertyName, parameter)=>{lognow(`Accessing ${className}.${propertyName}:`,parameter);}){
6211
7062
 
7063
+ const className=classToProxy.name;
6212
7064
 
6213
- //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
7065
+ // Build the Proxy handler
7066
+ const proxyHandler={
6214
7067
 
6215
- // New implementation :
7068
+ // Intercepts: obj.property AND obj.method()
7069
+ get(concernedObject, property, thisArg){
7070
+ const value=Reflect.get(concernedObject, property, thisArg);
6216
7071
 
6217
- 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=()=>{
6218
7155
 
6219
- 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
+ }
6220
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+"».");
6221
7171
  }
6222
7172
 
6223
- }
7173
+ return currentLevelPrototypeName;
7174
+ };
6224
7175
 
6225
7176
 
6226
7177
 
6227
7178
 
6228
- window.getAORTACClient=function(serverNodeOrigin="ws://127.0.0.1:40000", model, view, isNodeContext=false){
6229
- //return new AORTACClient("client_"+getUUID(), serverNodeOrigin, model, view, isNodeContext);
6230
- return new AORTACClientCell(serverNodeOrigin, model, view, isNodeContext);
6231
- }
6232
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
+ //};
6233
7227
 
6234
7228
 
6235
7229
 
@@ -6945,7 +7939,7 @@ WebsocketImplementation={
6945
7939
  },
6946
7940
 
6947
7941
 
6948
- /*private*/getMessageDataBothImplementations:(eventOrMessageParam)=>{
7942
+ /*public*//*static*/getMessageDataBothImplementations:(eventOrMessageParam)=>{
6949
7943
 
6950
7944
  const eventOrMessage=(!WebsocketImplementation.useSocketIOImplementation?eventOrMessageParam.data:eventOrMessageParam);
6951
7945
 
@@ -7142,12 +8136,11 @@ WebsocketImplementation={
7142
8136
 
7143
8137
 
7144
8138
 
7145
- 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){
7146
8140
 
7147
8141
  const EXCLUDED_FILENAMES_PARTS=[".keyHash.",".pem"];
7148
8142
 
7149
8143
 
7150
-
7151
8144
  if(typeof(https)==="undefined"){
7152
8145
  // TRACE
7153
8146
  console.log("«https» SERVER library not called yet, calling it now.");
@@ -7239,8 +8232,6 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
7239
8232
 
7240
8233
  const handler=nonull(httpHandlerParam, DEFAULT_HANDLER);
7241
8234
 
7242
-
7243
-
7244
8235
  let listenableServer;
7245
8236
  if(sslOptions){
7246
8237
  let httpsServer=https.createServer(sslOptions, handler).listen(port);
@@ -7261,7 +8252,7 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
7261
8252
  // CAUTION : MUST BE CALLED ONLY ONCE !
7262
8253
  server.onConnectionToClient((serverParam, clientSocketParam)=>{
7263
8254
  if(doOnConnect) doOnConnect(serverParam, clientSocketParam);
7264
- });
8255
+ }, doOnConnectionLost);
7265
8256
 
7266
8257
 
7267
8258
  server.onFinalize((serverParam)=>{
@@ -7276,15 +8267,13 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
7276
8267
  // TRACE
7277
8268
  console.log("INFO : SERVER : Generic Nodejs server launched and listening on port:" + port + "!");
7278
8269
 
7279
-
7280
-
7281
8270
 
7282
8271
 
7283
8272
  return server;
7284
8273
  }
7285
8274
 
7286
8275
 
7287
- initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFinalizeServer=null,
8276
+ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFinalizeServer=null, doOnConnectionLost=null,
7288
8277
  /*OPTIONAL*/portParam,
7289
8278
  /*OPTIONAL*/certPathParam,
7290
8279
  /*OPTIONAL*/keyPathParam){
@@ -7337,13 +8326,11 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
7337
8326
 
7338
8327
 
7339
8328
 
7340
-
7341
-
7342
8329
  const aotraNodeServer={config:serverConfig};
7343
8330
  aotraNodeServer.serverManager={ start:()=>{/*DEFAULT START FUNCTION, WILL BE OVERRIDEN LATER*/}};
7344
8331
 
7345
8332
  if(isHashAsked){
7346
- // 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:
7347
8334
  const persister=getPersister("./");
7348
8335
  let persisterIdSplits=persisterId.split("@");
7349
8336
  if(empty(persisterIdSplits) || persisterIdSplits.length!=2){
@@ -7426,9 +8413,9 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
7426
8413
  }
7427
8414
  }
7428
8415
 
7429
- aotraNodeServer.server=launchNodeHTTPServer(port, doOnClientConnection, doOnFinalizeServer, sslOptions);
8416
+ aotraNodeServer.server=launchNodeHTTPServer(port, doOnClientConnection, doOnFinalizeServer, doOnConnectionLost, sslOptions);
8417
+
7430
8418
 
7431
-
7432
8419
  return aotraNodeServer;
7433
8420
  };
7434
8421
 
@@ -7491,7 +8478,10 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
7491
8478
  // },REFRESH_SCREENSHOTS_MILLIS);
7492
8479
  //
7493
8480
  //
7494
- // },portParam,certPathParam,keyPathParam);
8481
+ // },
8482
+ // // On client connection lost :
8483
+ // (clientId)=>{lognow(`INFO : Connection to client id «${clientId}» was lost.`);},
8484
+ // portParam,certPathParam,keyPathParam);
7495
8485
  //
7496
8486
  //
7497
8487
  // // const doOnConnect=(serverParam, clientSocketParam)=>{
@@ -7959,8 +8949,8 @@ initClient=function(isNodeContext=true, useSocketIOImplementation=/*DEBUG*/false
7959
8949
  // CAUTION : MUST BE CALLED ONLY ONCE !
7960
8950
  socketToServerClientInstance.onConnectionToServer(()=>{
7961
8951
  if(doOnServerConnection){
7962
- if(selfParam) doOnServerConnection.apply(selfParam,[socketToServerClientInstance]);
7963
- else doOnServerConnection(socketToServerClientInstance);
8952
+ if(selfParam) doOnServerConnection.apply(selfParam,[socketToServerClientInstance, aotraClient]);
8953
+ else doOnServerConnection(socketToServerClientInstance, aotraClient);
7964
8954
  }
7965
8955
 
7966
8956
 
@@ -8057,8 +9047,9 @@ class ClientReceptionEntryPoint{
8057
9047
 
8058
9048
 
8059
9049
  // We check if the message matches the required message type :
8060
- if( this.listenerConfig && this.listenerConfig.listenerMessageType && dataWrapped.type
8061
- && 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){
8062
9053
  return;
8063
9054
  }
8064
9055
 
@@ -8082,8 +9073,12 @@ class ClientReceptionEntryPoint{
8082
9073
  if(this.listenerConfig && this.listenerConfig.destroyListenerAfterReceiving)
8083
9074
  remove(clientReceptionEntryPoints, this);
8084
9075
 
8085
- if(this.doOnIncomingMessage)
8086
- this.doOnIncomingMessage(dataLocal, clientSocket);
9076
+ if(this.doOnIncomingMessage){
9077
+
9078
+ const dataRestored=restoreClassesInformation(dataLocal);
9079
+
9080
+ this.doOnIncomingMessage(dataRestored, clientSocket);
9081
+ }
8087
9082
 
8088
9083
  }
8089
9084
 
@@ -8126,7 +9121,9 @@ class ClientInstance{
8126
9121
  // I-
8127
9122
  //
8128
9123
  self.receive(channelNameForResponse, doOnIncomingMessageForResponse,
8129
- clientsRoomsTag, listenerId, {messageId:messageId, destroyListenerAfterReceiving:true});
9124
+ {messageId:messageId, destroyListenerAfterReceiving:true},
9125
+ clientsRoomsTag, listenerId,
9126
+ );
8130
9127
  //
8131
9128
 
8132
9129
  return resultPromise;
@@ -8141,7 +9138,7 @@ class ClientInstance{
8141
9138
  }
8142
9139
 
8143
9140
  // II-
8144
- receive(channelNameParam, doOnIncomingMessage, clientsRoomsTag=null, entryPointId=null, listenerConfig={destroyListenerAfterReceiving:false}){
9141
+ receive(channelNameParam, doOnIncomingMessage, listenerConfig={destroyListenerAfterReceiving:false, listenerMessageType:null}, clientsRoomsTag=null, entryPointId=null){
8145
9142
  const self=this;
8146
9143
 
8147
9144
  // // DBG
@@ -8181,8 +9178,11 @@ class ClientInstance{
8181
9178
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8182
9179
 
8183
9180
  // Channel information is stored in exchanged data :
8184
- let dataWrapped={channelName:channelNameParam, data:data};
8185
-
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!)
8186
9186
  dataWrapped=stringifyObject(dataWrapped);
8187
9187
 
8188
9188
  // TODO : FIXME : Use one single interface !
@@ -8314,8 +9314,9 @@ class ServerReceptionEntryPoint{
8314
9314
  if(!isClientInRoom) return;
8315
9315
 
8316
9316
  // We check if the message matches the required message type :
8317
- if( this.listenerConfig && this.listenerConfig.listenerMessageType && dataWrapped.type
8318
- && 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){
8319
9320
  return;
8320
9321
  }
8321
9322
 
@@ -8323,7 +9324,11 @@ class ServerReceptionEntryPoint{
8323
9324
  // // DBG
8324
9325
  // lognow("(SERVER) this.doOnIncomingMessage:");
8325
9326
 
8326
- this.doOnIncomingMessage(dataWrapped.data, clientSocketParam);
9327
+ const dataLocal=dataWrapped.data;
9328
+
9329
+ const dataRestored=restoreClassesInformation(dataLocal);
9330
+
9331
+ this.doOnIncomingMessage(dataRestored, clientSocketParam);
8327
9332
  }
8328
9333
 
8329
9334
  }
@@ -8371,13 +9376,10 @@ class NodeServerInstance{
8371
9376
  });
8372
9377
  }
8373
9378
 
8374
-
8375
-
8376
9379
  // TODO : DEVELOP...
8377
9380
  //sendChainable(channelNameParam, data, clientsRoomsTag=null){
8378
9381
  //}
8379
9382
 
8380
-
8381
9383
  receive(channelNameParam, doOnIncomingMessage, listenerConfig=null, clientsRoomsTag=null){
8382
9384
 
8383
9385
  // DBG
@@ -8425,14 +9427,16 @@ class NodeServerInstance{
8425
9427
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8426
9428
 
8427
9429
  // Channel information is stored in exchanged data :
8428
- let dataWrapped={channelName:channelName, data:data};
8429
-
8430
-
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!)
8431
9435
  dataWrapped=stringifyObject(dataWrapped);
8432
9436
 
8433
9437
  // TODO : FIXME : Use one single interface !
8434
9438
  if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
8435
- else clientSocket.emit(channelName,dataWrapped);
9439
+ else clientSocket.emit(channelName, dataWrapped);
8436
9440
 
8437
9441
  });
8438
9442
 
@@ -8452,12 +9456,12 @@ class NodeServerInstance{
8452
9456
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8453
9457
 
8454
9458
  // Channel information is stored in exchanged data :
8455
- let dataWrapped={channelName:channelName, data:data};
8456
- dataWrapped=stringifyObject(dataWrapped);
9459
+ let dataWrapped=saveClassesInformation({channelName:channelName, data:data});
8457
9460
 
8458
-
8459
- // DBG
8460
- 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);
8461
9465
 
8462
9466
 
8463
9467
  // TODO : FIXME : Use one single interface !
@@ -8470,8 +9474,8 @@ class NodeServerInstance{
8470
9474
  return this;
8471
9475
  }
8472
9476
 
8473
-
8474
- onConnectionToClient(doOnConnection){
9477
+
9478
+ onConnectionToClient(doOnConnection, doOnConnectionLost=null){
8475
9479
  const self=this;
8476
9480
 
8477
9481
 
@@ -8480,7 +9484,6 @@ class NodeServerInstance{
8480
9484
 
8481
9485
  // DBG
8482
9486
  console.log("SERVER : ON CONNECTION !");
8483
-
8484
9487
 
8485
9488
  const clientId="autogeneratedid_"+getUUID();
8486
9489
 
@@ -8531,6 +9534,9 @@ class NodeServerInstance{
8531
9534
 
8532
9535
 
8533
9536
  clearInterval(clientSocket.stateCheckInterval);
9537
+
9538
+ if(doOnConnectionLost) doOnConnectionLost(clientSocket.clientId);
9539
+
8534
9540
  return;
8535
9541
  }
8536
9542
 
@@ -8589,7 +9595,71 @@ class NodeServerInstance{
8589
9595
 
8590
9596
 
8591
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)) );
8592
9660
 
9661
+ return obj;
9662
+ };
8593
9663
 
8594
9664
 
8595
9665