aotrautils 0.0.44 → 0.0.45

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,5 +1,6 @@
1
1
 
2
- /*utils CLIENT library associated with aotra version : «1.0.0.000 (11/01/2022-00:18:33)»*/
2
+
3
+ /*utils CLIENT library associated with aotra version : «1.0.0.000 (11/07/2022-00:48:13)»*/
3
4
  /*-----------------------------------------------------------------------------*/
4
5
  /* ## Utility global methods in a browser (htmljs) client environment.
5
6
  *
@@ -35,6 +36,9 @@ if(typeof(window)==="undefined") window=global;
35
36
  const APOSTROPHE="’";
36
37
  PERFORM_TESTS_ON_LIBRARY=false;
37
38
 
39
+ const isUserMediaAvailable=!!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia && navigator.mediaDevices.enumerateDevices);
40
+
41
+
38
42
  // Dependances-optionalizing mechanisms :
39
43
  if(typeof aotest === "undefined"){
40
44
  aotest=function(testcase,testedFunction){ return testedFunction; };
@@ -1960,11 +1964,11 @@ if(typeof(window)!=="undefined") window.URL=window.URL || window.webkitURL;
1960
1964
  //navigator.getUserMedia=navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
1961
1965
  //window.AudioContext=window.AudioContext || window.webkitAudioContext;
1962
1966
 
1963
- function hasGetUserMedia(){
1964
- // OLD : (DEPRECATED)
1965
- // return !!/*<-=to force a boolean result*/(navigator.getUserMedia);
1966
- return !!/*<-=to force a boolean result*/(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
1967
- }
1967
+ //function hasGetUserMedia(){
1968
+ // // OLD : (DEPRECATED)
1969
+ //// return !!/*<-=to force a boolean result*/(navigator.getUserMedia);
1970
+ // return !!/*<-=to force a boolean result*/(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
1971
+ //}
1968
1972
 
1969
1973
 
1970
1974
 
@@ -3082,6 +3086,7 @@ function filterPoints(allPoints ,ctx/*DBG*/){
3082
3086
 
3083
3087
 
3084
3088
 
3089
+
3085
3090
  //Deprecated : use getMediaHandler directly
3086
3091
  /*public*/getWebcamHandler=function(
3087
3092
  // videoTagId
@@ -3101,6 +3106,12 @@ function filterPoints(allPoints ,ctx/*DBG*/){
3101
3106
  /*OPTIONAL*/doFinallyWhenMultipleDevices=null
3102
3107
  ){
3103
3108
 
3109
+ if(!isUserMediaAvailable){
3110
+ // TRACE
3111
+ log("ERROR : User medias is not supported in your browser (or are you running in a non-https context ?). Aborting");
3112
+ return null;
3113
+ }
3114
+
3104
3115
  const cameras=[];
3105
3116
  const microphones=[];
3106
3117
  navigator.mediaDevices.enumerateDevices().then(function(devices){
@@ -3110,14 +3121,10 @@ function filterPoints(allPoints ,ctx/*DBG*/){
3110
3121
  else if(d.kind==="audioinput") microphones.push(d);
3111
3122
  });
3112
3123
 
3113
-
3114
- // DBG
3115
- //alert(JSON.stringify(cameras));
3116
-
3117
3124
  // CAUTION : On today this method only handles media handlers with a single track :
3118
3125
 
3119
3126
  let constraints={video:(videoConfigParam!=null), audio:(audioConfigParam!=null)}; // Default constraints
3120
- if(videoConfigParam){
3127
+ if(!empty(cameras) && videoConfigParam){
3121
3128
  let webcamIndex=videoConfigParam.webcamIndex;
3122
3129
  if(webcamIndex!=null){
3123
3130
  const deviceIndex=Math.min(cameras.length-1, webcamIndex);
@@ -3127,7 +3134,7 @@ function filterPoints(allPoints ,ctx/*DBG*/){
3127
3134
  }
3128
3135
  };
3129
3136
  }
3130
- }else if(audioConfigParam){
3137
+ }else if(!empty(microphones) && audioConfigParam){
3131
3138
  let microphoneIndex=audioConfigParam.microphoneIndex;
3132
3139
  if(microphoneIndex!=null){
3133
3140
  const deviceIndex=Math.min(microphones.length-1, microphoneIndex);
@@ -3195,9 +3202,10 @@ function filterPoints(allPoints ,ctx/*DBG*/){
3195
3202
  return null;
3196
3203
  }
3197
3204
 
3198
- if (!hasGetUserMedia()){
3205
+
3206
+ if(!isUserMediaAvailable){
3199
3207
  // TRACE
3200
- log("ERROR : Method getUserMedia() is not supported in your browser, your browser doesn't have a getUserMedia() usable method.");
3208
+ log("ERROR : User medias is not supported in your browser (or are you running in a non-https context ?). Aborting");
3201
3209
  return null;
3202
3210
  }
3203
3211
 
@@ -3747,34 +3755,51 @@ getColorFingersPointer=(mediaHandler,colorsPattern="rgb")=>{
3747
3755
  //Sound handling :
3748
3756
 
3749
3757
 
3750
- function playAudioData(audioData,/*OPTIONAL*/audioCtxParam,/*OPTIONAL*/audioBufferSizeParam,/*OPTIONAL*/numberOfAudioChannelsParam){
3758
+ function playAudioData(audioData,audioCtxParam=null,audioBufferSizeParam=null,numberOfAudioChannelsParam=null,sampleRateParam=null,
3759
+ //sourceParam=null, bufferParam=null,
3760
+ doOnEndPlayingSample=null,
3761
+ playbackRateParam=2
3762
+ ){
3751
3763
 
3752
3764
  const DEFAULT_AUDIO_BUFFER_SIZE=4096;
3753
- let audioBufferSize=audioBufferSizeParam?audioBufferSizeParam:DEFAULT_AUDIO_BUFFER_SIZE;
3754
- let numberOfAudioChannelsOUT=numberOfAudioChannelsParam?numberOfAudioChannelsParam:1;//default is «mono»
3755
3765
 
3756
3766
  // To read the audio data :
3757
3767
  // simply perform a reading of mediaHandler.audioData
3758
3768
  // To play the sound :
3759
3769
 
3760
3770
  let audioCtx=nonull(audioCtxParam,new AudioContext());
3761
-
3762
- let sampleRate=audioCtx.sampleRate;
3763
-
3764
- let source=audioCtx.createBufferSource();
3765
-
3766
- let buffer=audioCtx.createBuffer(numberOfAudioChannelsOUT, audioBufferSize, sampleRate); // Only one channel for «mono»
3767
- let data=buffer.getChannelData(0);
3768
- for(let i=0; i<audioBufferSize; i++){
3769
- data[i]=audioData[i];
3770
- }
3771
- source.buffer=buffer;
3772
- source.connect(audioCtx.destination);
3771
+ let audioBufferSize=nonull(audioBufferSizeParam,DEFAULT_AUDIO_BUFFER_SIZE);
3772
+ let numberOfAudioChannels=nonull(numberOfAudioChannelsParam,1);//default is «mono»
3773
+ let sampleRate=nonull(sampleRateParam,audioCtx.sampleRate);
3774
+
3775
+ // We have to recreate them each time ! :
3776
+ let source=audioCtx.createBufferSource();
3777
+ let buffer=audioCtx.createBuffer(numberOfAudioChannels, audioBufferSize, sampleRate); // Only one channel for «mono»
3778
+
3779
+ let data=buffer.getChannelData(0);
3780
+ for(let i=0; i<audioBufferSize; i++){
3781
+ data[i]=audioData[i];
3782
+ }
3783
+ if(!source.buffer){
3784
+ source.playbackRate.value=playbackRateParam;
3785
+ source.buffer=buffer;
3786
+ source.connect(audioCtx.destination);
3787
+ // TODO : FIXME : DOES NOT CURRENTLY WORK :
3788
+ // BECAUSE OF INTERACTION-FIRST RESTRICTIONS WILL MAKE IT NOT WORK !
3789
+ // OLD : source.start(0);
3790
+ source.start();
3791
+
3792
+ // // DBG
3793
+ // lognow("audioSource STARTED : ",source);
3794
+ }
3773
3795
 
3774
- // TODO : FIXME : DOES NOT CURRENTLY WORK :
3775
- // BECAUSE OF INTERACTION-FIRST RESTRICTIONS WILL MAKE IT NOT WORK !
3776
- source.start(0);
3777
-
3796
+
3797
+ if(doOnEndPlayingSample){
3798
+ const playingDurationMillis=(audioBufferSize/sampleRate);
3799
+ // DBG
3800
+ // setTimeout(doOnEndPlayingSample,playingDurationMillis*1000); (because Hertz)
3801
+ doOnEndPlayingSample();
3802
+ }
3778
3803
 
3779
3804
  // OLD :
3780
3805
  // // Fill the buffer with data from array;
@@ -3934,6 +3959,7 @@ function loadSound(filePath,doOnLoaded=null,doOnEnded=null){
3934
3959
  isReady:false,
3935
3960
  speed:1,
3936
3961
  volume:1,
3962
+ isPlaying:false,
3937
3963
  mainLoopSound:null,
3938
3964
  setVolume:function(newVolume=null){
3939
3965
  if(newVolume) self.volume=newVolume;
@@ -3947,12 +3973,15 @@ function loadSound(filePath,doOnLoaded=null,doOnEnded=null){
3947
3973
  },
3948
3974
  play:function(){
3949
3975
  if(!self.nativeAudioElement.paused) return self;
3950
- self.nativeAudioElement.play();
3976
+ self.nativeAudioElement.play().then(()=>{
3977
+ self.isPlaying=true;
3978
+ });
3951
3979
  return self;
3952
3980
  },
3953
3981
  pause:function(){
3954
- if(self.nativeAudioElement.paused) return self;
3982
+ if(self.nativeAudioElement.paused || !self.isPlaying) return self;
3955
3983
  self.nativeAudioElement.pause();
3984
+ self.isPlaying=false;
3956
3985
  return self;
3957
3986
  },
3958
3987
  resumeLoop:function(){
@@ -3962,7 +3991,7 @@ function loadSound(filePath,doOnLoaded=null,doOnEnded=null){
3962
3991
  return self;
3963
3992
  },
3964
3993
  pauseLoop:function(){
3965
- if(!self.isLoop || !self.mainLoopSound) return self;
3994
+ if(!self.isLoop || !self.mainLoopSound || !self.isPlaying) return self;
3966
3995
  self.mainLoopSound.pause();
3967
3996
  self.pause();
3968
3997
  return self;
@@ -4170,7 +4199,7 @@ function getCenteredZoneCoords(xParam,yParam,width,height,center={x:"left",y:"to
4170
4199
  }
4171
4200
 
4172
4201
 
4173
- function drawImageAndCenter(ctx,image,xParam,yParam,center={x:"left",y:"top"},
4202
+ function drawImageAndCenter(ctx,image,xParam=0,yParam=0,zooms={zx:1,zy:1},center={x:"left",y:"top"},
4174
4203
  scaleFactorW=1,scaleFactorH=1,
4175
4204
  sizeW=null,sizeH=null,
4176
4205
  opacity=1,
@@ -4193,6 +4222,8 @@ function drawImageAndCenter(ctx,image,xParam,yParam,center={x:"left",y:"top"},
4193
4222
  ctx.globalAlpha=opacity;
4194
4223
  }
4195
4224
 
4225
+ // TODO : FIXME : use zooms parameter !
4226
+
4196
4227
  if(opacity!==0){
4197
4228
 
4198
4229
  if(alphaAngleRadians==null){
@@ -4353,8 +4384,12 @@ function getSpriteMonoThreaded(imagesPool,/*OPTIONAL*/refreshMillis=null,
4353
4384
  // drawingWidth, drawingHeight);
4354
4385
 
4355
4386
 
4356
- drawImageAndCenter(ctx,nonull(img, self.imagesPool),
4357
- xParam+self.xOffset, yParam+self.yOffset,
4387
+ const xImage=xParam+self.xOffset;
4388
+ const yImage=yParam+self.yOffset;
4389
+
4390
+ drawImageAndCenter(ctx, nonull(img,self.imagesPool),
4391
+ xImage, yImage,
4392
+ {zx:1,zy:1},
4358
4393
  self.center,
4359
4394
  (self.scaleFactorW*self.zoom),
4360
4395
  (self.scaleFactorH*self.zoom),
@@ -4387,9 +4422,11 @@ function getSpriteMonoThreaded(imagesPool,/*OPTIONAL*/refreshMillis=null,
4387
4422
  }
4388
4423
  }
4389
4424
 
4425
+ // DOES NOT WORK :
4426
+ // return ctx.getImageData(clipX,clipY,drawingWidth,drawingHeight);
4427
+ // }else{
4428
+ // return ctx.getImageData(0,0,drawingWidth,drawingHeight);
4390
4429
  }
4391
-
4392
-
4393
4430
  },
4394
4431
 
4395
4432
  };
@@ -4451,20 +4488,22 @@ function getMonoThreadedTimeout(doOnStop=null, delayMillis=null){
4451
4488
 
4452
4489
 
4453
4490
 
4454
- function getMonoThreadedRoutine(doOnEachStepCallback,terminateFunction=null,doOnStop=null,refreshMillis=null,startDependsOnParentOnly=false){
4491
+ function getMonoThreadedRoutine(doOnEachStepCallback,terminateFunction=null,doOnStop=null,refreshMillis=null,startDependsOnParentOnly=false,doOnSuspend=null,doOnResume=null){
4455
4492
 
4456
- // Three-states : started, suspended, stopped.
4493
+ // Three-states : started, paused, stopped.
4457
4494
 
4458
4495
  let self={
4459
4496
 
4460
4497
  startDependsOnParentOnly:startDependsOnParentOnly,
4461
4498
  started:false,
4462
- suspended:false,
4499
+ paused:false,
4463
4500
  time:getNow(),
4464
4501
  refreshMillis:refreshMillis,
4465
4502
  doOnEachStepCallback:doOnEachStepCallback,
4466
4503
  terminateFunction:terminateFunction,
4467
4504
  doOnStop:doOnStop,
4505
+ doOnSuspend:doOnSuspend,
4506
+ doOnResume:doOnResume,
4468
4507
  durationTimeFactorHolder:{durationTimeFactor:1},
4469
4508
  setDurationTimeFactorHolder:function(durationTimeFactorHolder){
4470
4509
  self.durationTimeFactorHolder=durationTimeFactorHolder;
@@ -4473,44 +4512,50 @@ function getMonoThreadedRoutine(doOnEachStepCallback,terminateFunction=null,doOn
4473
4512
  isStarted:function(){
4474
4513
  return self.started || self.startDependsOnParentOnly;
4475
4514
  },
4476
- start:function(callingObject=null){
4515
+ start:function(callingObject=null,args=null){
4477
4516
 
4478
4517
  // if(self.isStarted()) return;
4479
- if(self.terminateFunction && self.terminateFunction(callingObject)){
4518
+ if(self.terminateFunction && self.terminateFunction(callingObject,args)){
4480
4519
 
4481
4520
  // CAUTION : Even if the routine is «pre-terminated» before even starting
4482
4521
  // (ie. its stop conditions are fullfilled even before it has started)
4483
4522
  // we all the same have to trigger its stop treatments :
4484
- if(self.doOnStop) self.doOnStop(callingObject);
4523
+ if(self.doOnStop) self.doOnStop(callingObject,args);
4485
4524
 
4486
4525
  return;
4487
4526
  }
4488
4527
 
4489
4528
  self.started=true;
4490
4529
 
4491
- self.suspended=false;
4530
+ self.paused=false;
4492
4531
 
4493
4532
  return self;
4494
4533
  },
4495
- stop:function(callingObject=null){
4534
+ stop:function(callingObject=null,args=null){
4496
4535
 
4497
4536
  if(!self.isStarted()) return;
4498
4537
  self.started=false;
4499
4538
 
4500
- if(self.doOnStop) self.doOnStop(callingObject);
4539
+ if(self.doOnStop) self.doOnStop(callingObject,args);
4540
+
4541
+ },
4542
+ pause:function(callingObject=null,args=null){
4543
+ self.paused=true;
4501
4544
 
4545
+ if(self.doOnSuspend) self.doOnSuspend(callingObject,args);
4502
4546
  },
4503
- suspend:function(){
4504
- self.suspended=true;
4547
+ resume:function(callingObject=null,args=null){
4548
+ self.paused=false;
4549
+
4550
+ if(self.doOnResume) self.doOnResume(callingObject,args);
4505
4551
  },
4506
- resume:function(){
4507
- self.suspended=false;
4552
+ isPaused:function(){
4553
+ return self.paused;
4508
4554
  },
4509
-
4510
4555
  doStep:function(callingObject=null,args=null){
4511
4556
 
4512
4557
 
4513
- if(!self.isStarted() || self.suspended) return;
4558
+ if(!self.isStarted() || self.paused) return;
4514
4559
 
4515
4560
  // Looping index with a delay :
4516
4561
 
@@ -4521,8 +4566,8 @@ function getMonoThreadedRoutine(doOnEachStepCallback,terminateFunction=null,doOn
4521
4566
  }
4522
4567
 
4523
4568
  // We perform the step :
4524
- if(self.terminateFunction && self.terminateFunction(callingObject)){
4525
- self.stop(callingObject);
4569
+ if(self.terminateFunction && self.terminateFunction(callingObject,args)){
4570
+ self.stop(callingObject,args);
4526
4571
  return;
4527
4572
  }
4528
4573
 
@@ -4545,7 +4590,7 @@ function getMonoThreadedRoutine(doOnEachStepCallback,terminateFunction=null,doOn
4545
4590
  function getMonoThreadedGoToGoal(actualValue,goalValue,refreshMillis=null,totalTimeMillis=1000,mode="linear",doOnEachStepCallback=null,doOnStop=null){
4546
4591
 
4547
4592
  // const STEP_PRECISION=4;
4548
- // Three-states : started, suspended, stopped.
4593
+ // Three-states : started, paused, stopped.
4549
4594
 
4550
4595
  let self={
4551
4596
 
@@ -4555,7 +4600,7 @@ function getMonoThreadedGoToGoal(actualValue,goalValue,refreshMillis=null,totalT
4555
4600
  value:actualValue,
4556
4601
  goalValue:goalValue,
4557
4602
  started:false,
4558
- suspended:false,
4603
+ paused:false,
4559
4604
  time:getNow(),
4560
4605
  refreshMillis:refreshMillis,
4561
4606
  doOnEachStepCallback:doOnEachStepCallback,
@@ -4591,39 +4636,39 @@ function getMonoThreadedGoToGoal(actualValue,goalValue,refreshMillis=null,totalT
4591
4636
 
4592
4637
  return self;
4593
4638
  },
4594
- start:function(callingObject){
4639
+ start:function(callingObject=null,args=null){
4595
4640
 
4596
4641
  if( self.value===self.goalValue
4597
4642
  // || self.isStarted();
4598
- || self.terminateFunction(callingObject)){
4643
+ || self.terminateFunction(callingObject,args)){
4599
4644
 
4600
4645
  // CAUTION : Even if the routine is «pre-terminated» before even starting
4601
4646
  // (ie. its stop conditions are fullfilled even before it has started)
4602
4647
  // we all the same have to trigger its stop treatments :
4603
- if(self.doOnStop) self.doOnStop(callingObject);
4648
+ if(self.doOnStop) self.doOnStop(callingObject,args);
4604
4649
 
4605
4650
  return;
4606
4651
  }
4607
4652
 
4608
4653
  self.started=true;
4609
4654
 
4610
- self.suspended=false;
4655
+ self.paused=false;
4611
4656
 
4612
4657
  return self;
4613
4658
  },
4614
- stop:function(callingObject=null){
4659
+ stop:function(callingObject=null,args=null){
4615
4660
 
4616
4661
  if(!self.isStarted()) return;
4617
4662
  self.started=false;
4618
4663
 
4619
- if(self.doOnStop) self.doOnStop(callingObject);
4664
+ if(self.doOnStop) self.doOnStop(callingObject,args);
4620
4665
 
4621
4666
  },
4622
- suspend:function(){
4623
- self.suspended=true;
4667
+ pause:function(){
4668
+ self.paused=true;
4624
4669
  },
4625
4670
  resume:function(){
4626
- self.suspended=false;
4671
+ self.paused=false;
4627
4672
  },
4628
4673
  terminateFunction:function(){
4629
4674
 
@@ -4643,7 +4688,7 @@ function getMonoThreadedGoToGoal(actualValue,goalValue,refreshMillis=null,totalT
4643
4688
 
4644
4689
  doStep:function(callingObject=null,args=null){
4645
4690
 
4646
- if(!self.isStarted() || self.suspended) return self.value;
4691
+ if(!self.isStarted() || self.paused) return self.value;
4647
4692
 
4648
4693
  // Looping index with a delay :
4649
4694
 
@@ -5951,15 +5996,26 @@ function normalizeDisplayQuotesForJSONParsing(str){
5951
5996
 
5952
5997
  //A polyvalent popup :
5953
5998
  //CAUTION : Non-blocking of execution ! (uses callbacks)
5954
- function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=null,config={displayMode:"popup",backgroundColor1:"#B3DCED",font:"helvetica"}){
5999
+ function promptWindow(label,type=null,defaultValue=null,
6000
+ doOnOK=null,doOnCancel=null,
6001
+ config={displayMode:"popup",backgroundColor1:"#B3DCED",font:"helvetica",width:"80%",height:"80%"}){
5955
6002
 
6003
+ const LABEL_AND_DEFAULT_VALUE_SEPARATOR=":";
5956
6004
  const MAX_Z_INDEX_OVERLAY_LOCAL=9999;// (Repeated because must be stand-alone constant...)
5957
- const WIDTH="90%";
5958
- const HEIGHT="90%";
6005
+ const promptWindowWidth=(config && config.width?config.width:"80%");
6006
+ const promptWindowHeight=(config && config.height?config.height:"80%");
6007
+ // DOES NOT WORK : const promptWindowWidth="auto", promptWindowHeight="auto";
6008
+
6009
+
5959
6010
  // const POSITION_X="7.5%";
5960
6011
  // const POSITION_Y="5%";
5961
6012
  const SPECIAL_SEPARATOR="@@@";
5962
6013
 
6014
+
6015
+ let resultPromiseOK=null;
6016
+ let resultPromiseCancel=null;
6017
+
6018
+
5963
6019
  let displayMode=config && config.displayMode ? config.displayMode:"popup";
5964
6020
  let backgroundColor1=config && config.backgroundColor1 ? config.backgroundColor1:"#B3DCED";
5965
6021
  let backgroundColor2=config && config.backgroundColor2 ? config.backgroundColor2:"#29B8E5";
@@ -5975,12 +6031,11 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
5975
6031
  };
5976
6032
 
5977
6033
  //Each field rendering :
5978
- /* private */let getRenderedInputElement=function(inputType, defaultValueParam=null,/* OPTIONAL */labelParam=null){
6034
+ /* private */const getRenderedInputElement=function(inputType, defaultValueParam=null,/* OPTIONAL */labelParam=null){
5979
6035
 
5980
- const LABEL_AND_DEFAULT_VALUE_SEPARATOR=":";
5981
6036
  const DISABLED_OPACITY=0.6;
5982
6037
 
5983
- var inputElement=null;
6038
+ let inputElement=null;
5984
6039
  if(contains(inputType, "password")){
5985
6040
  inputElement=document.createElement("input");
5986
6041
  if(defaultValueParam) inputElement.value=defaultValueParam;
@@ -6000,7 +6055,7 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6000
6055
 
6001
6056
  } else if(contains(inputType, "uniqueChoice") || contains(inputType, "multipleChoice")){ // If we have a group of inputs for (a) value(s) outcome :
6002
6057
 
6003
- var choicesLabelsStrs=defaultValueParam;
6058
+ let choicesLabelsStrs=defaultValueParam;
6004
6059
  if(typeof(defaultValueParam) === "string"){
6005
6060
  choicesLabelsStrs=parseJSON(defaultValueParam);
6006
6061
  }
@@ -6008,97 +6063,99 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6008
6063
  inputElement=document.createElement("p");
6009
6064
  inputElement.style.display="flex";
6010
6065
 
6011
-
6012
6066
 
6013
6067
  // Represents a list with a unique or a multiple outcome value(s) :
6014
- foreach(choicesLabelsStrs,(choiceLabelStr,i)=>{
6015
-
6016
- let radioButtonContainer=document.createElement("div");
6017
- radioButtonContainer.style.width="200px";
6018
- // radioButtonContainer.style.height="400px";
6019
- radioButtonContainer.style["padding-right"]="10px";
6020
- radioButtonContainer.style["text-align"]="center";
6021
- radioButtonContainer.style["font-size"]="10px";
6022
- radioButtonContainer.style["line-height"]="10px";
6023
-
6024
-
6025
- let radio=document.createElement("input");
6068
+
6069
+ resultPromiseOK=new Promise((resolve, reject)=>{
6026
6070
 
6027
- if(contains(inputType, "multipleChoice")){ // multipleChoice :
6028
- radio.type="checkbox";
6029
- radio.id="checkboxMultipleChoice_" + i;
6030
- } else { // uniqueChoice :
6031
-
6032
-
6033
- if(contains(inputType, "icons")){
6034
-
6035
- radio.type="image";
6036
- if(typeof(choiceLabelStr) === "string"){
6037
- radio.src=choiceLabelStr.trim();
6038
- }else{
6071
+ foreach(choicesLabelsStrs,(choiceLabelStr,i)=>{
6072
+
6073
+ // Radio image card :
6074
+ const radioButtonContainer=document.createElement("div");
6075
+ radioButtonContainer.style.width="200px";
6076
+ // radioButtonContainer.style.height="400px";
6077
+ radioButtonContainer.style["padding-right"]="10px";
6078
+ radioButtonContainer.style["text-align"]="center";
6079
+ radioButtonContainer.style["font-size"]="10px";
6080
+ radioButtonContainer.style["line-height"]="10px";
6081
+ inputElement.appendChild(radioButtonContainer);
6082
+
6083
+
6084
+
6085
+ let radio=document.createElement("input");
6086
+ if(contains(inputType, "multipleChoice")){ // multipleChoice :
6087
+ radio.type="checkbox";
6088
+ radio.id="checkboxMultipleChoice_" + i;
6039
6089
 
6040
- if(choiceLabelStr.width)
6041
- radioButtonContainer.style.width=choiceLabelStr.width;
6042
- if(choiceLabelStr.height)
6043
- radioButtonContainer.style.height=choiceLabelStr.height;
6090
+ } else { // uniqueChoice :
6044
6091
 
6092
+ if(contains(inputType, "icons")){
6093
+
6094
+ radio.type="image";
6095
+ if(typeof(choiceLabelStr) === "string"){
6096
+ radio.src=choiceLabelStr.trim();
6097
+ }else{
6098
+
6099
+ if(choiceLabelStr.width) radioButtonContainer.style.width=choiceLabelStr.width;
6100
+ if(choiceLabelStr.height) radioButtonContainer.style.height=choiceLabelStr.height;
6101
+
6102
+ radio.src=choiceLabelStr.src.trim();
6103
+
6104
+ if(choiceLabelStr.inactive){
6105
+ radio.disabled=true;
6106
+ radio.style.opacity=DISABLED_OPACITY;
6107
+ }
6108
+ }
6109
+
6110
+ // Icon substitutes itself to the OK button :
6111
+
6112
+ radio.onclick=function(event){
6113
+ let promptedValue=event.target.indexValue;
6114
+
6115
+ if(!validationFunction(promptedValue)){
6116
+ // TRACE
6117
+ alert(validationFunction.errorMessage);
6118
+ return;
6119
+ }
6120
+
6121
+ if(doOnOK){
6122
+ const doOnOKResult=doOnOK(promptedValue);
6123
+ resolve({value:promptedValue,result:doOnOKResult});
6124
+ }
6125
+ closeWindow();
6126
+
6127
+ };
6128
+
6129
+
6130
+ }else{
6131
+ radio.type="radio";
6132
+ }
6045
6133
 
6046
- radio.src=choiceLabelStr.src.trim();
6134
+ radio.id="radioButtonUniqueChoice_" + i;
6135
+ radio.name="radioButtonsUniqueChoice";
6047
6136
 
6048
- if(choiceLabelStr.inactive){
6049
- radio.disabled=true;
6050
- radio.style.opacity=DISABLED_OPACITY;
6051
- }
6052
6137
  }
6053
6138
 
6054
- // Icon substitutes itself to the OK button :
6055
- radio.onclick=function(event){
6056
- var promptedValue=event.target.indexValue;
6057
-
6058
- if(!validationFunction(promptedValue)){
6059
- // TRACE
6060
- alert(validationFunction.errorMessage);
6061
- return;
6062
- }
6063
-
6064
- if(doOnOK){
6065
- doOnOK(promptedValue);
6066
- }
6067
- closeWindow();
6068
- };
6069
-
6070
-
6071
-
6072
-
6073
- }else{
6074
6139
 
6075
- radio.type="radio";
6076
- }
6077
-
6078
- radio.id="radioButtonUniqueChoice_" + i;
6079
- radio.name="radioButtonsUniqueChoice";
6080
-
6081
-
6082
- }
6083
-
6084
-
6085
- radio.indexValue=i;
6086
- radioButtonContainer.appendChild(radio);
6087
-
6088
- var choiceLabel=document.createElement("label");
6089
- if(typeof(choiceLabelStr) === "string"){
6090
- choiceLabel.innerHTML=choiceLabelStr.trim();
6091
- }else{
6092
- choiceLabel.innerHTML=choiceLabelStr.html.trim();
6093
- }
6094
- choiceLabel.htmlFor=radio.id;
6095
- radioButtonContainer.appendChild(choiceLabel);
6096
-
6097
-
6098
- inputElement.appendChild(radioButtonContainer);
6140
+ radio.indexValue=i;
6141
+ radioButtonContainer.appendChild(radio);
6142
+
6143
+ let choiceLabel=document.createElement("label");
6144
+ if(typeof(choiceLabelStr)==="string"){
6145
+ choiceLabel.innerHTML=choiceLabelStr.trim();
6146
+ }else{
6147
+ choiceLabel.innerHTML=choiceLabelStr.html.trim();
6148
+ }
6149
+ choiceLabel.htmlFor=radio.id;
6150
+ radioButtonContainer.appendChild(choiceLabel);
6099
6151
 
6100
-
6152
+
6153
+
6154
+ });
6155
+
6101
6156
  });
6157
+
6158
+
6102
6159
 
6103
6160
  // We redefine the function used to retrieve entered value :
6104
6161
  if(contains(inputType, "icons")){
@@ -6108,11 +6165,11 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6108
6165
  }else{
6109
6166
 
6110
6167
  inputElement.getResultValue=function(){
6111
- var result="";
6168
+ let result="";
6112
6169
 
6113
6170
  // OLD :
6114
6171
  // jQuery(inputElement).find("input:checked").each(function(){
6115
- // var radioSlct=jQuery(this);
6172
+ // let radioSlct=jQuery(this);
6116
6173
  // result+=(nothing(result) ? "" : ",") + radioSlct.get(0).indexValue;
6117
6174
  // });
6118
6175
 
@@ -6127,9 +6184,7 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6127
6184
  }
6128
6185
 
6129
6186
 
6130
-
6131
-
6132
- } else { // DEFAULT TYPE IS TEXT AREA :
6187
+ } else if(contains(inputType, "textarea")){
6133
6188
 
6134
6189
  inputElement=document.createElement("textarea");
6135
6190
  if(defaultValueParam)
@@ -6138,14 +6193,16 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6138
6193
  inputElement.rows="20";
6139
6194
 
6140
6195
  }
6196
+ // DEFAULT TYPE IS "yesno"
6141
6197
 
6198
+
6142
6199
  inputElement.style.padding="4px";
6143
6200
  inputElement.style.margin="0";
6144
6201
 
6145
6202
  // The default function used to retrieve entered value :
6146
6203
  if(!inputElement.getResultValue){
6147
6204
  inputElement.getResultValue=function(){
6148
- var result=inputElement.value;
6205
+ let result=inputElement.value;
6149
6206
  return result;
6150
6207
  };
6151
6208
  }
@@ -6180,10 +6237,12 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6180
6237
  if(contains(displayMode,":")){
6181
6238
  let s=displayMode.split(":");
6182
6239
  divModal.style.opacity=s[1];
6240
+ }else{
6241
+ divModal.style.opacity="0.85";// Default modal transparency value
6183
6242
  }
6184
6243
 
6185
6244
  divModal.appendChild(divWindow);
6186
- // parent.appendChild(divModal);
6245
+ // parent.appendChild(divModal);
6187
6246
 
6188
6247
  appendAsFirstChildOf(divModal, parent);
6189
6248
  }else{
@@ -6198,12 +6257,12 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6198
6257
 
6199
6258
  divWindow.className="promptWindowClass";
6200
6259
 
6201
- var style="";
6202
- style+="position:sticky;";
6203
- //style+="left:" + POSITION_X + ";top:" + POSITION_Y + ";z-index:" + (MAX_Z_INDEX_OVERLAY_LOCAL) + ";";
6204
- style+="margin:auto;";
6260
+ let style="";
6261
+ style+="position:absolute;";
6262
+ style+="left:50%;top:50%;";
6205
6263
  style+="z-index:" + (MAX_Z_INDEX_OVERLAY_LOCAL) + ";";
6206
- style+="width:"+WIDTH+";height:"+HEIGHT+"; ";
6264
+ style+="z-index:" + (MAX_Z_INDEX_OVERLAY_LOCAL) + ";";
6265
+ style+="width:"+promptWindowWidth+";height:"+promptWindowHeight+"; ";
6207
6266
  style+="border:#888888 8px dotted;padding:10px;opacity:1;";
6208
6267
  style+="font-family:"+font+";";
6209
6268
  style+="color:"+textColor+";";
@@ -6219,10 +6278,12 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6219
6278
 
6220
6279
  divWindow.style=style;
6221
6280
 
6222
-
6281
+ divWindow.style["margin-left"]=(-divWindow.offsetWidth/2)+"px";
6282
+ divWindow.style["margin-top"]= (-divWindow.offsetHeight/2)+"px";
6283
+
6223
6284
 
6224
6285
  // The label for the title of the window :
6225
- var labelElement=document.createElement("div");
6286
+ let labelElement=document.createElement("div");
6226
6287
  labelElement.innerHTML=label;
6227
6288
  labelElement.style="padding-top:0;padding-bottom:10px;";
6228
6289
  // Positioning :
@@ -6232,21 +6293,21 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6232
6293
  // The container containing all the elements :
6233
6294
  let divContainer=document.createElement("div");
6234
6295
  divContainer.id="divPromptContainer";
6235
- divContainer.style="overflow-y:auto;width:100%;height:80%;";
6296
+ divContainer.style="overflow-y:auto;width:100%;max-height:83%;";
6236
6297
  divWindow.appendChild(divContainer);
6237
6298
 
6238
6299
 
6239
6300
 
6240
6301
 
6241
6302
  // Eventual validation :
6242
- var validationFunction=function(value){
6303
+ let validationFunction=function(value){
6243
6304
  return true;/* DO NOTHING */
6244
6305
  };
6245
6306
  validationFunction.errorMessage="";
6246
6307
  if(!nothing(type) && contains(type, ":")){
6247
- var typeParams=type.split(":");
6308
+ let typeParams=type.split(":");
6248
6309
  if(typeParams.length > 1){
6249
- var validationType=typeParams[1];
6310
+ let validationType=typeParams[1];
6250
6311
 
6251
6312
  if(validationType === "email"){
6252
6313
  validationFunction=function(value){
@@ -6259,19 +6320,20 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6259
6320
 
6260
6321
 
6261
6322
  // We define the global function used to retrieve entered value :
6262
- var getGlobalResultValue=null;
6263
- var inputElements=new Array();
6323
+ let getGlobalResultValue=null;
6324
+ const inputElements=[];
6264
6325
 
6265
6326
  // Case the window is actually a form of several fields :
6266
6327
  if(!nothing(type) && containsIgnoreCase(type, "form{")){
6267
- var namesWithTypesAndDefaultValuesStr=type.replace("form{", "{");
6328
+
6329
+ let namesWithTypesAndDefaultValuesStr=type.replace("form{", "{");
6268
6330
  try {
6269
6331
 
6270
- var defaultValues=new Array();
6332
+ let defaultValues=new Array();
6271
6333
  if(!nothing(defaultValue)) defaultValues=defaultValue.split(SPECIAL_SEPARATOR);
6272
6334
 
6273
- var namesWithTypesAndDefaultValues=parseJSON(namesWithTypesAndDefaultValuesStr);
6274
- var j=0;
6335
+ let namesWithTypesAndDefaultValues=parseJSON(namesWithTypesAndDefaultValuesStr);
6336
+ let j=0;
6275
6337
  foreach(namesWithTypesAndDefaultValues,(inputItemDescriptionString,key)=>{
6276
6338
 
6277
6339
  let typeInputItem=inputItemDescriptionString;
@@ -6293,9 +6355,9 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6293
6355
 
6294
6356
  // We redefine the global function used to retrieve entered value :
6295
6357
  getGlobalResultValue=function(){
6296
- var result="";
6358
+ let result="";
6297
6359
 
6298
- for(var k=0; k<inputElements.length; k++){
6360
+ for(let k=0; k<inputElements.length; k++){
6299
6361
  result+=(nothing(result) ? "" : SPECIAL_SEPARATOR) + inputElements[k].getResultValue();
6300
6362
  }
6301
6363
 
@@ -6303,22 +6365,22 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6303
6365
  };
6304
6366
 
6305
6367
  } catch (e1){
6306
- var errorDiv=document.createElement("div");
6368
+ let errorDiv=document.createElement("div");
6307
6369
  errorDiv.innerHTML="Error parsing JSON «" + namesWithTypesAndDefaultValuesStr + "» : ERROR «:" + e1 + "»";
6308
6370
  inputElements.push(errorDiv);
6309
6371
  }
6310
6372
 
6311
6373
 
6312
- // UNUSED, AND DOES NOT WORK :
6313
- // }else if(type==="yesno"){ // Case yes/no
6314
- //
6315
- // // We redefine the global function used to retrieve entered value :
6316
- // getGlobalResultValue=function(){
6317
- // return null;
6318
- // };
6319
- }else { // Case of a single field :
6374
+ } else if(type==="yesno"){ // Case yes/no
6375
+
6376
+ // We redefine the global function used to retrieve entered value :
6377
+ getGlobalResultValue=function(){
6378
+ return null;
6379
+ };
6380
+
6381
+ } else { // Case of a single field :
6320
6382
 
6321
- var singleInputElement=getRenderedInputElement(type, defaultValue);
6383
+ let singleInputElement=getRenderedInputElement(type, defaultValue);
6322
6384
  inputElements.push(singleInputElement);
6323
6385
 
6324
6386
  // Focus on password field :
@@ -6335,50 +6397,65 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6335
6397
  // TODO : FIXME : Focus on text field is not working.... :,-(
6336
6398
  // inputElements[inputElements.length - 1].focus();
6337
6399
 
6338
- var buttonCancel=document.createElement("button");
6400
+ // This button is displayed in all cases :
6401
+ let buttonCancel=document.createElement("button");
6339
6402
  buttonCancel.innerHTML="Cancel";
6340
- buttonCancel.style="display:block;float:right;margin-top:8px;";
6341
- buttonCancel.onclick=function(){
6342
- var promptedValue=getGlobalResultValue();
6343
- if(doOnCancel){
6344
- doOnCancel(promptedValue);
6345
- }
6346
- closeWindow();
6347
- };
6348
-
6349
- // TODO : FISME : There should be a way to hide this OK button,
6350
- // when we are in a "radio-image" unique selection case,
6351
- // since the doOnOK() is triggered when we click on the "radio-image" icon,
6352
- // thus rendering this OK button unuseful !! :
6353
-
6354
- var buttonOK=document.createElement("button");
6355
- buttonOK.innerHTML="OK";
6356
- buttonOK.style="display:block;margin-top:8px;margin-bottom:10px;";
6357
- buttonOK.onclick=function(){
6358
- var promptedValue=getGlobalResultValue();
6359
-
6360
- if(promptedValue!=null && !validationFunction(promptedValue)){
6361
- // TRACE
6362
- alert(validationFunction.errorMessage);
6363
- return;
6364
- }
6403
+ buttonCancel.style="display:block;float:right;";
6404
+ resultPromiseCancel=new Promise((resolve, reject)=>{
6405
+ buttonCancel.onclick=function(){
6406
+ let promptedValue=getGlobalResultValue();
6407
+ if(doOnCancel){
6408
+ const doOnCancelResult=doOnCancel(promptedValue);
6409
+ resolve({value:promptedValue,result:doOnCancelResult});
6410
+ }
6411
+ closeWindow();
6412
+ };
6413
+ });
6414
+ divWindow.appendChild(buttonCancel);
6415
+
6365
6416
 
6366
- if(doOnOK){
6367
- doOnOK(promptedValue);
6368
- }
6369
- closeWindow();
6370
- };
6371
6417
 
6372
- for(var i=0; i<inputElements.length; i++){
6373
-
6418
+ // We hide this OK button, when we are in a "radio-image" unique selection case,
6419
+ // since the doOnOK() is triggered when we click on the "radio-image" icon,
6420
+ // thus rendering this OK button unuseful ! :
6421
+ if(nothing(type) || !contains(type, "uniqueChoice")){
6422
+
6423
+ let buttonOK=document.createElement("button");
6424
+ buttonOK.innerHTML="OK";
6425
+ buttonOK.style="display:block;margin-bottom:10px;";
6426
+
6427
+ resultPromiseOK=new Promise((resolve, reject)=>{
6428
+
6429
+ buttonOK.onclick=function(){
6430
+ let promptedValue=getGlobalResultValue();
6431
+
6432
+ if(promptedValue!=null && !validationFunction(promptedValue)){
6433
+ // TRACE
6434
+ alert(validationFunction.errorMessage);
6435
+ return;
6436
+ }
6437
+
6438
+ if(doOnOK){
6439
+ const doOnOKResult=doOnOK(promptedValue);
6440
+ resolve({value:promptedValue,result:doOnOKResult});
6441
+ }
6442
+ closeWindow();
6443
+ };
6444
+
6445
+ });
6446
+ divWindow.appendChild(buttonOK);
6447
+ }
6448
+
6449
+ // Final integration of the generated input elements :
6450
+ for(let i=0; i<inputElements.length; i++){
6374
6451
  if(0<i){
6375
- var br=document.createElement("br");
6452
+ const br=document.createElement("br");
6376
6453
  divContainer.appendChild(br);
6377
6454
  }
6378
6455
 
6379
6456
  // The label :
6380
6457
  if(inputElements[i].label){
6381
- var divLabel=document.createElement("div");
6458
+ const divLabel=document.createElement("div");
6382
6459
  divLabel.innerHTML=inputElements[i].label;
6383
6460
  divContainer.appendChild(divLabel);
6384
6461
  }
@@ -6386,14 +6463,16 @@ function promptWindow(label,type=null,defaultValue=null,doOnOK=null,doOnCancel=n
6386
6463
  divContainer.appendChild(inputElements[i]);
6387
6464
  }
6388
6465
 
6389
- // Positioning :
6390
- divWindow.appendChild(buttonCancel);
6391
- divWindow.appendChild(buttonOK);
6392
-
6393
6466
 
6467
+ return {
6468
+ toPromise:(issue="OK")=>(issue=="OK"?resultPromiseOK:resultPromiseCancel),
6469
+ toPromises:()=>{ return {OK:resultPromiseOK,cancel:resultPromiseCancel}; }
6470
+ };
6394
6471
  }
6395
6472
 
6396
6473
 
6474
+
6475
+
6397
6476
  getInputCoords=function(event){
6398
6477
  return (((event.clientX && event.clientY) || !event.touches) ? event : event.touches.item(0) );
6399
6478
  }
@@ -6689,66 +6768,419 @@ function getPointsFromSVGDString(str){
6689
6768
 
6690
6769
 
6691
6770
 
6692
- /*utils GEOMETRY library associated with aotra version : «1.0.0.000 (11/01/2022-00:18:33)»*/
6693
- /*-----------------------------------------------------------------------------*/
6694
-
6695
-
6696
- /* ## Utility global methods in a javascript, at least console (nodejs) server, or vanilla javascript with no browser environment.
6697
- *
6698
- * This set of methods gathers utility generic-purpose methods usable in any JS project.
6699
- * Several authors of snippets published freely on the Internet contributed to this library.
6700
- * Feel free to use/modify-enhance/publish them under the terms of its license.
6701
- *
6702
- * # Library name : «aotrautils»
6703
- * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
6704
- * # Author name : Jérémie Ratomposon (massively helped by its programming egos legions)
6705
- * # Author email : info@alqemia.com
6706
- * # Organization name : Alqemia
6707
- * # Organization email : admin@alqemia.com
6708
- * # Organization website : https://alqemia.com
6709
- *
6710
- *
6711
- */
6712
-
6713
-
6714
- // COMPATIBILITY browser javascript / nodejs environment :
6715
-
6716
- if(typeof(window)==="undefined") window=global;
6717
-
6718
-
6719
6771
 
6720
- //=========================================================================
6721
- // GLOBAL CONSTANTS :
6722
6772
 
6723
6773
 
6724
- Math.TAU=6.28318530718; // == Math.PI*2
6725
- Math.PSI=1.57079632679; // == Math.PI/2
6726
- Math.TAN_RATIOS_DEGREES={
6727
- 22.5:0.4142135624,
6728
- 45:1,
6729
- 67.5:2.4142135624,
6730
- 112.5:-2.4142135624,
6731
- 135:-1,
6732
- 157.5:-0.4142135624,
6733
- 202.5:0.4142135624,
6734
- 225:1,
6735
- 247.5:2.4142135624,
6736
- 292.5:-2.4142135624,
6737
- 315:-1,
6738
- 337.5:-0.4142135624
6739
- };
6740
- Math.LN_2_INVERTED=1.44269504089; // == 1/ln(2)
6741
6774
 
6742
- Math.INVERSE_OF_SQUARE_OF_TWO=0.707106781; // == 1/sqrt(2)
6743
- Math.RATIO_TO_DEGREES=57.295779513; // == 180/PI
6744
- Math.RATIO_TO_RADIANS=0.017453293; // == PI/180
6745
6775
 
6776
+ // -Client :
6746
6777
 
6778
+ // TODO : Use everywhere it's applicable !
6779
+ initClient=function(isNodeContext=true, useSocketIOImplementation=false, doOnServerConnection=null, url, /*OPTIONAL*/port=25000, isSecure=true, /*OPTIONAL*/timeout=10000, /*OPTIONAL*/selfParam=null){
6780
+ let aotraClient={};
6781
+
6782
+ aotraClient.client={};
6783
+ aotraClient.client.start=function(){
6784
+
6785
+
6786
+ let socketToServer=WebsocketImplementation.getStatic(isNodeContext,useSocketIOImplementation).connectToServer(url, port, isSecure, timeout);
6787
+ if(!socketToServer){
6788
+ // TRACE
6789
+ lognow("ERROR : CLIENT : Could not get socketToServer, aborting client connection.");
6790
+ return null;
6791
+ }
6792
+
6793
+ // When client is connected, we execute the callback :
6794
+ // CAUTION : MUST BE CALLED ONLY ONCE !
6795
+ socketToServer.onConnectionToServer(()=>{
6796
+ if(doOnServerConnection){
6797
+ if(selfParam) doOnServerConnection.apply(selfParam,[socketToServer]);
6798
+ else doOnServerConnection(socketToServer);
6799
+ }
6800
+ });
6747
6801
 
6748
- //=========================================================================
6802
+ // // Errors handling :
6803
+ // if(doOnError && useSocketIOImplementation /*TODO : FIXME: FOR NOW, ONLY WORKS FOR SOCKET.IO IMPLEMENTATION !'*/){
6804
+ //
6805
+ //// // DBG
6806
+ //// lognow(">>> serverSocket.connected:"+serverSocket.connected);
6807
+ //// lognow(">>> serverSocket:",serverSocket);
6808
+ //
6809
+ //
6810
+ // const errorMethod=function(){
6811
+ // // TRACE
6812
+ // lognow("ERROR : CLIENT : Client encountered an error while trying to connect to server.");
6813
+ //
6814
+ // if(selfParam) doOnError.apply(selfParam);
6815
+ // else doOnError();
6816
+ // };
6817
+ //
6818
+ // socketToServer.clientSocket.on("connect_error", errorMethod);
6819
+ //
6820
+ //// // DOES NOT SEEM TO WORK :
6821
+ //// socketToServer.on("connect_failed", errorMethod);
6822
+ //// // DOES NOT SEEM TO WORK :
6823
+ //// socketToServer.on("error", errorMethod);
6824
+ //
6825
+ // }
6826
+ //
6827
+ // // Data messages handling :
6828
+ // if(doOnMessage){
6829
+ // socketToServer.onIncomingMessage((message)=>{
6830
+ // if(selfParam) doOnMessage.apply(selfParam,[message]);
6831
+ // else doOnMessage(message);
6832
+ // });
6833
+ // }
6834
+
6835
+ // DBG
6836
+ lognow("aotraClient.clientSocket:",aotraClient.clientSocket);
6837
+
6838
+ // DBG
6839
+ // aotraClient.clientSocket.addEventListener("close",()=>{
6840
+ // // TRACE
6841
+ // lognow("WARN : CONNECTION CLOSED !");
6842
+ // });
6749
6843
 
6844
+
6845
+ aotraClient.client.socketToServer=socketToServer;
6846
+
6847
+ return aotraClient;
6848
+ };
6849
+
6850
+
6851
+ return aotraClient;
6852
+ }
6750
6853
 
6751
- // ==================== COMMONS Geometry ====================
6854
+
6855
+
6856
+
6857
+ // ========================= FUSRODA CLIENT : =========================
6858
+
6859
+ class VNCFrame2D{
6860
+
6861
+ constructor(url, port=6080, isSecure=true, htmlConfig={parentElement:null,imageMode:"CSSBackground"}, mouseEventsRefreshMillis=100){
6862
+
6863
+
6864
+ this.url=url;
6865
+ this.port=port;
6866
+ this.isSecure=isSecure;
6867
+ this.parentElement=nonull(htmlConfig.parentElement, document.body);
6868
+ this.imageMode=htmlConfig.imageMode;
6869
+ this.mouseEventsRefreshMillis=mouseEventsRefreshMillis;
6870
+
6871
+
6872
+ this.fusrodaClient=null;
6873
+
6874
+
6875
+ this.screenAndAudioConfig=null;
6876
+ this.image=null;
6877
+ self.audioCtx=null;
6878
+
6879
+ if(this.imageMode=="canvas"){
6880
+ this.canvas=document.createElement("canvas");
6881
+ this.parentElement.appendChild(this.canvas);
6882
+ }
6883
+
6884
+ this.lastPointerMoveTime=null;
6885
+
6886
+ }
6887
+
6888
+
6889
+ start(){
6890
+
6891
+ const self=this;
6892
+ this.fusrodaClient=createFusrodaClient(
6893
+ // doOnClientReady:
6894
+ (messageConfig)=>{
6895
+
6896
+ self.screenAndAudioConfig=messageConfig;
6897
+
6898
+ if(self.imageMode=="canvas"){
6899
+ self.image=new Image();
6900
+ self.image.addEventListener("load",(event)=>{
6901
+
6902
+
6903
+
6904
+ const image=event.target;
6905
+ image.isImageLoading=false;
6906
+
6907
+ if(!self.canvasSizeHasBeenSet){
6908
+ self.canvas.width=image.width;
6909
+ self.canvas.height=image.height;
6910
+ self.canvasSizeHasBeenSet=true;
6911
+ }
6912
+
6913
+ self.canvas.getContext("2d").drawImage(image,0,0);
6914
+
6915
+
6916
+ self.fusrodaClient.client.socketToServer.send("screenFrameRequest", {});
6917
+
6918
+ });
6919
+
6920
+ }
6921
+
6922
+
6923
+ self.audioCtx=new AudioContext({sampleRate: self.screenAndAudioConfig.sampleRate});
6924
+
6925
+
6926
+ },// doOnDataReception:
6927
+ {
6928
+ "image":(imageDataStr)=>{
6929
+
6930
+
6931
+ const base64String="data:image/png;base64,"+imageDataStr;
6932
+ if(self.imageMode=="canvas"){
6933
+ if(self.image.complete){
6934
+ self.image.src=base64String;
6935
+ self.image.isImageLoading=true;
6936
+ }
6937
+ }else{
6938
+ self.parentElement.style["background-image"]="url('"+base64String+"');";
6939
+ }
6940
+
6941
+
6942
+ },
6943
+ "sound":(soundDataStr)=>{
6944
+ const soundData=getDecodedArrayFromSoundDataString(soundDataStr);
6945
+ playAudioData(soundData,self.audioCtx,self.screenAndAudioConfig.audioBufferSize,1,self.screenAndAudioConfig.sampleRate,()=>{
6946
+ // doOnEndedPlaying
6947
+
6948
+ self.fusrodaClient.client.socketToServer.send("soundSampleRequest", {});
6949
+
6950
+ });
6951
+ }
6952
+ }, this.url, this.port, this.isSecure);
6953
+
6954
+
6955
+ // TODO : FIXME : DUPLICATED CODE !
6956
+ this.parentElement.addEventListener("pointermove",(event)=>{
6957
+
6958
+ const config=self.screenAndAudioConfig;
6959
+ if(!config || !self.canvas.width) return;
6960
+
6961
+
6962
+ if(!hasDelayPassed(self.lastPointerMoveTime,self.mouseEventsRefreshMillis)) return;
6963
+ self.lastPointerMoveTime=getNow();
6964
+
6965
+
6966
+ const width=config.width;
6967
+ const height=config.height;
6968
+
6969
+ const x=Math.floor((event.clientX/self.canvas.width)*width);
6970
+ const y=Math.floor((event.clientY/self.canvas.height)*height);
6971
+
6972
+
6973
+ self.fusrodaClient.sendMouseMoveEvent({x:x,y:y});
6974
+
6975
+ // DBG
6976
+ lognow("sendMouseMoveEvent:"+x+";"+y);
6977
+
6978
+ });
6979
+ // TODO : FIXME : DUPLICATED CODE !
6980
+ this.parentElement.addEventListener("mousedown",(event)=>{
6981
+ const mouseButton=event.button;
6982
+ self.fusrodaClient.sendMouseClickEvent({mouseButton:mouseButton});
6983
+
6984
+ // DBG
6985
+ lognow("mousedown:",event);
6986
+ });
6987
+ // TODO : FIXME : DUPLICATED CODE !
6988
+ this.parentElement.addEventListener("mouseup",(event)=>{
6989
+ const mouseButton=event.button;
6990
+ self.fusrodaClient.sendMouseReleasedEvent({mouseButton:mouseButton});
6991
+ });
6992
+ // TODO : FIXME : DUPLICATED CODE !
6993
+ const WHEEL_EVENT_FACTOR=.01;
6994
+ this.parentElement.addEventListener("wheel",(event)=>{
6995
+ const wheelAmount=event.deltaY*WHEEL_EVENT_FACTOR;
6996
+ self.fusrodaClient.sendWheelEvent({wheelAmount:wheelAmount});
6997
+
6998
+ // DBG
6999
+ lognow("wheel:",event);
7000
+ });
7001
+ // TODO : FIXME : DUPLICATED CODE !
7002
+ // To remove the local contextual menu :
7003
+ this.parentElement.addEventListener("contextmenu",(event)=>{event.preventDefault();});
7004
+
7005
+ // TODO : FIXME : DUPLICATED CODE !
7006
+ this.parentElement.addEventListener("keydown",(event)=>{
7007
+ let keyChar=event.key;
7008
+ const keyCode=event.keyCode;
7009
+
7010
+ // To avoid server-side «A JSONObject text must end with '}'» errors :
7011
+ if(keyChar==="{") keyChar="left_brace";
7012
+ if(keyChar==="}") keyChar="right_brace";
7013
+ self.fusrodaClient.sendKeyboardPressedEvent({keyChar:keyChar,keyCode:keyCode});
7014
+ });
7015
+ // TODO : FIXME : DUPLICATED CODE !
7016
+ this.parentElement.addEventListener("keyup",(event)=>{
7017
+ let keyChar=event.key;
7018
+ const keyCode=event.keyCode;
7019
+ // DBG
7020
+ lognow("!!! keydown KEYUP event:[event.key:"+event.key+"][keyCode:"+keyCode+"][keyChar:"+keyChar+"]",event);
7021
+
7022
+ // To avoid server-side «A JSONObject text must end with '}'» errors :
7023
+ if(keyChar==="{") keyChar="left_brace";
7024
+ if(keyChar==="}") keyChar="right_brace";
7025
+ self.fusrodaClient.sendKeyboardReleasedEvent({keyChar:keyChar,keyCode:keyCode});
7026
+ });
7027
+
7028
+
7029
+
7030
+ this.fusrodaClient.client.start();
7031
+
7032
+
7033
+ return this;
7034
+ }
7035
+
7036
+ }
7037
+
7038
+
7039
+
7040
+ /*private*/getDecodedArrayFromSoundDataString=function(soundDataStr){
7041
+ const decodedIntsArray=[];
7042
+
7043
+ const decodedString=atob(soundDataStr);
7044
+
7045
+ //// DBG
7046
+ //const decodedString=soundDataStr;
7047
+
7048
+ for(let i=0;i<decodedString.length;i++){
7049
+ const dataInt=decodedString.charCodeAt(i)-127;
7050
+
7051
+ // CAUTION : audio needs to be in [-1.0; 1.0]
7052
+ decodedIntsArray.push(dataInt/127);
7053
+ }
7054
+
7055
+ return decodedIntsArray;
7056
+ }
7057
+
7058
+
7059
+
7060
+ createFusrodaClient=function(doOnClientReady, doOnDataReception, urlParam=null, portParam=6080){
7061
+ // CAUTION : WORKS BETTER WHEN UNSECURE, BUT 'NEEDS CLIENT BROWSER TO ALLOW MIXED (SECURE/UNSECURE) CONTENT !
7062
+
7063
+ const isSecure=(contains(urlParam.toLowerCase(),"https") || contains(urlParam.toLowerCase(),"wss"));
7064
+
7065
+
7066
+ const fusrodaClient=initClient(false,true, (browserClientInstance)=>{
7067
+
7068
+ // DBG
7069
+ lognow("SETTING UP");
7070
+
7071
+ //0)
7072
+ fusrodaClient.client.socketToServer.send("protocol", { type:"hello" });
7073
+ //1)
7074
+ browserClientInstance.receive("protocolConfig", (messageConfig)=>{
7075
+ //2)
7076
+ fusrodaClient.client.socketToServer.send("screenFrameRequest", {});
7077
+ browserClientInstance.receive("imagePacket", (doOnDataReception && doOnDataReception["image"]?doOnDataReception["image"]:()=>{/*DO NOTHING*/}));
7078
+
7079
+ //2)
7080
+ fusrodaClient.client.socketToServer.send("soundSampleRequest", {});
7081
+ browserClientInstance.receive("soundPacket", (doOnDataReception && doOnDataReception["sound"]?doOnDataReception["sound"]:()=>{/*DO NOTHING*/}));
7082
+
7083
+
7084
+ doOnClientReady(messageConfig);
7085
+ });
7086
+
7087
+
7088
+ }, urlParam, portParam, isSecure);
7089
+
7090
+ fusrodaClient.sendMouseMoveEvent=(mouseEvent)=>{
7091
+ fusrodaClient.client.socketToServer.send("mouseMoveEvent", mouseEvent);
7092
+ };
7093
+
7094
+ fusrodaClient.sendMouseClickEvent=(mouseEvent)=>{
7095
+ fusrodaClient.client.socketToServer.send("mouseClickEvent", mouseEvent);
7096
+ };
7097
+ fusrodaClient.sendMouseReleasedEvent=(mouseEvent)=>{
7098
+ fusrodaClient.client.socketToServer.send("mouseReleasedEvent", mouseEvent);
7099
+ };
7100
+ fusrodaClient.sendWheelEvent=(wheelEvent)=>{
7101
+ fusrodaClient.client.socketToServer.send("wheelEvent", wheelEvent);
7102
+ };
7103
+
7104
+ fusrodaClient.sendKeyboardPressedEvent=(keyboardEvent)=>{
7105
+ fusrodaClient.client.socketToServer.send("keyboardPressedEvent", keyboardEvent);
7106
+ };
7107
+ fusrodaClient.sendKeyboardReleasedEvent=(keyboardEvent)=>{
7108
+ fusrodaClient.client.socketToServer.send("keyboardReleasedEvent", keyboardEvent);
7109
+ };
7110
+
7111
+
7112
+
7113
+ // // DBG
7114
+ // fusrodaClient.clientSocket.addEventListener("close",()=>{
7115
+ // lognow("CONNECTION CLOSED !");
7116
+ // });
7117
+
7118
+ return fusrodaClient;
7119
+ }
7120
+
7121
+
7122
+
7123
+
7124
+ /*utils GEOMETRY library associated with aotra version : «1.0.0.000 (11/07/2022-00:48:13)»*/
7125
+ /*-----------------------------------------------------------------------------*/
7126
+
7127
+
7128
+ /* ## Utility global methods in a javascript, at least console (nodejs) server, or vanilla javascript with no browser environment.
7129
+ *
7130
+ * This set of methods gathers utility generic-purpose methods usable in any JS project.
7131
+ * Several authors of snippets published freely on the Internet contributed to this library.
7132
+ * Feel free to use/modify-enhance/publish them under the terms of its license.
7133
+ *
7134
+ * # Library name : «aotrautils»
7135
+ * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
7136
+ * # Author name : Jérémie Ratomposon (massively helped by its programming egos legions)
7137
+ * # Author email : info@alqemia.com
7138
+ * # Organization name : Alqemia
7139
+ * # Organization email : admin@alqemia.com
7140
+ * # Organization website : https://alqemia.com
7141
+ *
7142
+ *
7143
+ */
7144
+
7145
+
7146
+ // COMPATIBILITY browser javascript / nodejs environment :
7147
+
7148
+ if(typeof(window)==="undefined") window=global;
7149
+
7150
+
7151
+
7152
+ //=========================================================================
7153
+ // GLOBAL CONSTANTS :
7154
+
7155
+
7156
+ Math.TAU=6.28318530718; // == Math.PI*2
7157
+ Math.PSI=1.57079632679; // == Math.PI/2
7158
+ Math.TAN_RATIOS_DEGREES={
7159
+ 22.5:0.4142135624,
7160
+ 45:1,
7161
+ 67.5:2.4142135624,
7162
+ 112.5:-2.4142135624,
7163
+ 135:-1,
7164
+ 157.5:-0.4142135624,
7165
+ 202.5:0.4142135624,
7166
+ 225:1,
7167
+ 247.5:2.4142135624,
7168
+ 292.5:-2.4142135624,
7169
+ 315:-1,
7170
+ 337.5:-0.4142135624
7171
+ };
7172
+ Math.LN_2_INVERTED=1.44269504089; // == 1/ln(2)
7173
+
7174
+ Math.INVERSE_OF_SQUARE_OF_TWO=0.707106781; // == 1/sqrt(2)
7175
+ Math.RATIO_TO_DEGREES=57.295779513; // == 180/PI
7176
+ Math.RATIO_TO_RADIANS=0.017453293; // == PI/180
7177
+
7178
+
7179
+
7180
+ //=========================================================================
7181
+
7182
+
7183
+ // ==================== COMMONS Geometry ====================
6752
7184
 
6753
7185
 
6754
7186
 
@@ -6893,6 +7325,14 @@ Math.polarPositionRadiansToCartesianPosition3D=(distance,gamma,alpha,xOffset=0,y
6893
7325
  return {x:x,y:y,z:z};
6894
7326
  }
6895
7327
 
7328
+ // DOES NOT SEEM TO WORK :
7329
+ //Math.getArcCoordinatesOnSphereFromCartesianVector=function(vector3D,sphereRadius=1){
7330
+ // const zAxisAngle=(vector3D.x==0?Math.PI*.5:Math.atan(vector3D.y/vector3D.x));
7331
+ // const yAxisAngle=(vector3D.z==0?0:Math.atan(vector3D.x/vector3D.z));
7332
+ // const y=yAxisAngle*sphereRadius;
7333
+ // const x=zAxisAngle*sphereRadius;
7334
+ // return {x:x,y:y};
7335
+ //}
6896
7336
 
6897
7337
 
6898
7338
  Math.expApprox=function(x){
@@ -7005,9 +7445,9 @@ window.calculateAngleRadians2D=function(p1, p2, approximate=false){
7005
7445
  //}
7006
7446
 
7007
7447
  window.calculateAngleRadians3D=function(p1, p2, approximate=false){
7008
- let a=calculateAngleRadians2D({x:p1.x, y:p1.z}, {x:p2.x, y:p2.z}, approximate);
7009
- let b=calculateAngleRadians2D({x:p1.x, y:p1.y}, {x:p2.x, y:p2.y}, approximate);
7010
- let g=calculateAngleRadians2D({x:p1.z, y:p1.y}, {x:p2.z, y:p2.y}, approximate);
7448
+ let a=calculateAngleRadians2D({x:p1.x, y:p1.y}, {x:p2.x, y:p2.y}, approximate); // alpha => angle [x,y]
7449
+ let b=calculateAngleRadians2D({x:p1.z, y:p1.y}, {x:p2.z, y:p2.y}, approximate); // beta => angle [z,y]
7450
+ let g=calculateAngleRadians2D({x:p1.x, y:p1.z}, {x:p2.x, y:p2.z}, approximate); // gamma => angle [x,z]
7011
7451
  return {a:a, b:b, g:g};
7012
7452
  }
7013
7453
 
@@ -7017,15 +7457,21 @@ window.calculateLinearlyMovedPoint2DPolar=function(currentPoint, angleRadians, l
7017
7457
  return {x:x, y:y};
7018
7458
  }
7019
7459
 
7460
+
7461
+ // NEW : WE ACTUALL ONLY NEED 2 ANGLES ! BETA angle in plan [Z,Y] AND GAMMA angle in plan [X,Z] !
7020
7462
  window.calculateLinearlyMovedPoint3DPolar=function(currentPoint, anglesRadians, linearMotion){
7463
+
7464
+ // IN THIS NORM (MOBILE NORM:)
7465
+ // alpha => angle [x,y]
7466
+ // beta => angle [z,y]
7467
+ // gamma => angle [x,z]
7468
+
7469
+ let translatedBeta= calculateLinearlyMovedPoint2DPolar({x:currentPoint.z, y:currentPoint.y}, anglesRadians.b, linearMotion);
7470
+ let translatedGamma=calculateLinearlyMovedPoint2DPolar({x:currentPoint.x, y:currentPoint.z}, anglesRadians.g, linearMotion);
7021
7471
 
7022
- let translatedAlpha=calculateLinearlyMovedPoint2DPolar({x:currentPoint.x, y:currentPoint.z}, anglesRadians.a, linearMotion);
7023
- let translatedBeta= calculateLinearlyMovedPoint2DPolar({x:currentPoint.x, y:currentPoint.y}, anglesRadians.b, linearMotion);
7024
- let translatedGamma=calculateLinearlyMovedPoint2DPolar({x:currentPoint.z, y:currentPoint.y}, anglesRadians.g, linearMotion);
7025
-
7026
- let x=translatedAlpha.x+translatedBeta.x;
7027
- let y= translatedBeta.y+translatedGamma.y;
7028
- let z=translatedAlpha.y +translatedGamma.x;
7472
+ let x=translatedGamma.x;
7473
+ let y=translatedBeta.y;
7474
+ let z=translatedBeta.x;
7029
7475
 
7030
7476
  return {x:x, y:y, z:z};
7031
7477
  }
@@ -7493,12 +7939,6 @@ Math.getQuadrant=function(centerPoint,point){
7493
7939
 
7494
7940
 
7495
7941
 
7496
-
7497
-
7498
-
7499
-
7500
-
7501
-
7502
7942
  Math.compareDistances=function(p1, p2, distance, comparison){
7503
7943
  let distanceSquare=Math.pow(distance, 2);
7504
7944
  let diffSquare=Math.getSquaredDistance(p1, p2);
@@ -7516,7 +7956,7 @@ Math.compareDistances=function(p1, p2, distance, comparison){
7516
7956
  };
7517
7957
 
7518
7958
  Math.getSquaredDistance=function(p1, p2){
7519
- return Math.pow(Math.abs(p1.x - p2.x), 2) + Math.pow(Math.abs(p1.y - p2.y), 2);
7959
+ return Math.pow(Math.abs(p2.x-p1.x), 2) + Math.pow(Math.abs(p2.y-p1.y), 2) + ((p1.z!=null && p2.z!=null)?Math.pow(Math.abs(p2.z-p1.z), 2):0);
7520
7960
  };
7521
7961
 
7522
7962
  Math.getDistance=function(p1, p2){
@@ -7886,7 +8326,8 @@ function rayVsUnitSphereClosestPoint(p, r) {
7886
8326
 
7887
8327
  // MUST REMAIN AT THE END OF THIS LIBRARY FILE !
7888
8328
 
7889
- AOTRAUTILS_GEOMETRY_LIB_IS_LOADED=true;/*utils SERVER library associated with aotra version : «1.0.0.000 (11/01/2022-00:18:33)»*/
8329
+ AOTRAUTILS_GEOMETRY_LIB_IS_LOADED=true;
8330
+ /*utils SERVER library associated with aotra version : «1.0.0.000 (11/07/2022-00:48:13)»*/
7890
8331
  /*-----------------------------------------------------------------------------*/
7891
8332
 
7892
8333
 
@@ -8084,23 +8525,31 @@ getServerParams=function(portParam,isSecureParam,certPathParam,keyPathParam){
8084
8525
  // NODE ONLY SERVER / CLIENTS :
8085
8526
  WebsocketImplementation={
8086
8527
 
8087
- isNode:true,
8528
+ isNodeContext:true,
8529
+ useSocketIOImplementation:false,
8530
+ useFlatStrings:false,
8088
8531
 
8089
8532
  // COMMON METHODS
8090
8533
  /*private static*/isInRoom(clientSocket, clientsRoomsTag){
8091
8534
  return !clientsRoomsTag || empty(clientsRoomsTag) || contains(clientsRoomsTag, clientSocket.clientRoomTag);
8092
8535
  },
8093
8536
 
8094
- // NODE SERVER
8095
8537
 
8096
- getStatic:(isNode=true)=>{
8538
+ //
8539
+ // NODE SERVER
8540
+ //
8541
+ getStatic:(isNodeContext=true, useSocketIOImplementation=false)=>{
8097
8542
 
8098
- WebsocketImplementation.isNode=isNode;
8543
+ WebsocketImplementation.isNodeContext=isNodeContext;
8099
8544
 
8545
+ // cf. https://socket.io/docs/v4/client-socket-instance/
8546
+ WebsocketImplementation.useSocketIOImplementation=useSocketIOImplementation;
8547
+
8100
8548
  // OLD : socket.io :
8101
8549
  // https://stackoverflow.com/questions/31156884/how-to-use-https-on-node-js-using-express-socket-io
8102
8550
  // https://stackoverflow.com/questions/6599470/node-js-socket-io-with-ssl
8103
8551
  // https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener
8552
+
8104
8553
  // if(typeof(socketIO)==="undefined"){
8105
8554
  // // TRACE
8106
8555
  // console.log("«socket.io» SERVER library not called yet, calling it now.");
@@ -8139,30 +8588,38 @@ WebsocketImplementation={
8139
8588
 
8140
8589
  getServer:(listenableServer)=>{
8141
8590
 
8142
- if(!WebsocketImplementation.isNode){
8591
+ if(!WebsocketImplementation.isNodeContext){
8143
8592
  // TRACE
8144
- throw new Error("ERROR : SERVER : Server launch is not supported in a non-nodejs context.");
8593
+ throw new Error("ERROR : SERVER : Server launch is not supported in a non-nodejs context for any implementation.");
8145
8594
  }
8146
8595
 
8147
- // OLD : socket.io :
8148
- // // Loading socket.io
8149
- // let serverSocket=socketIO.listen(listenableServer);
8150
-
8151
- const serverSocket=new WebSocket.Server({ "server":listenableServer });
8596
+ // TODO : FIXME : Use one single interface !
8597
+ // NODE SERVER MODE ONLY :
8598
+ let serverSocket;
8599
+ if(!WebsocketImplementation.useSocketIOImplementation){
8600
+ serverSocket=new WebSocket.Server({ "server":listenableServer });
8601
+ }else{
8602
+ // NOW : socket.io :
8603
+ // Loading socket.io
8604
+ serverSocket=socketIO.listen(listenableServer);
8605
+
8606
+ // Setting up the disctonnect event :
8607
+ serverSocket.on("endConnection",()=>{
8608
+ serverSocket.disconnect();
8609
+ });
8610
+
8611
+ }
8152
8612
 
8153
-
8613
+ // NODE SERVER INSTANCE :
8154
8614
  const nodeServerInstance={
8155
8615
 
8156
- clientTimeoutMillis:20000,
8157
-
8158
- // clientsSockets:[],
8616
+ onClientLostListeners:[],
8159
8617
 
8618
+ clientTimeoutMillis:20000,
8619
+ // clientsSockets:[],
8160
8620
  serverSocket:serverSocket,
8161
-
8162
8621
  receptionEntryPoints:[],
8163
8622
 
8164
-
8165
-
8166
8623
  receive:(channelName, doOnIncomingMessage, clientsRoomsTag=null)=>{
8167
8624
 
8168
8625
  const receptionEntryPoint={
@@ -8172,41 +8629,40 @@ WebsocketImplementation={
8172
8629
 
8173
8630
 
8174
8631
  // With «ws» library we have no choive than register message events inside a «connection» event !
8175
- // nodeServerInstance.onConnectionToClient((serverParam, clientSocketParam)=>{
8632
+ // nodeServerInstance.onConnectionToClient((serverParam, clientSocketParam)=>{
8633
+
8634
+ // TODO : Find a way to remove this !
8635
+ const doOnMessage=(message)=>{
8636
+
8637
+ // dataWrapped=JSON.parse(dataWrapped);
8638
+ // dataWrapped=getAt(dataWrapped,0);// We get the root element
8639
+ const dataWrapped=(WebsocketImplementation.useFlatStrings?JSON.parse(message):message);
8176
8640
 
8177
- // TODO : Find a way to remove this !
8641
+
8642
+ // Channel information is stored in exchanged data :
8643
+ if(dataWrapped.channelName!==receptionEntryPoint.channelName) return;
8178
8644
 
8179
- clientSocketParam.on("message", (dataWrapped)=>{
8180
-
8181
- dataWrapped=JSON.parse(dataWrapped);
8182
-
8183
-
8184
- // Channel information is stored in exchanged data :
8185
- if(dataWrapped.channelName!==receptionEntryPoint.channelName) return;
8645
+
8646
+ const clientSocket=clientSocketParam;
8186
8647
 
8648
+ // TODO : FIXME : Use one single interface !
8649
+ // Room information is stored in client socket object :
8650
+ if(!WebsocketImplementation.isInRoom(clientSocket, receptionEntryPoint.clientsRoomsTag)) return;
8651
+
8652
+ doOnIncomingMessage(dataWrapped.data, clientSocket);
8187
8653
 
8188
- // nodeServerInstance.serverSocket.clients.forEach((clientSocket)=>{
8189
-
8190
- const clientSocket=clientSocketParam;
8654
+ };
8191
8655
 
8192
- // Room information is stored in client socket object :
8193
- if(!WebsocketImplementation.isInRoom(clientSocket, receptionEntryPoint.clientsRoomsTag)) return;
8194
8656
 
8195
-
8196
-
8197
- doOnIncomingMessage(dataWrapped.data, clientSocket);
8198
-
8199
- });
8200
-
8201
-
8202
- // });
8657
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocketParam.addEventListener("message", doOnMessage);
8658
+ else clientSocketParam.on(channelName, doOnMessage);
8659
+
8203
8660
 
8204
8661
 
8205
8662
 
8206
8663
  },
8207
8664
  };
8208
8665
 
8209
-
8210
8666
  // DBG
8211
8667
  console.log("ADD RECEPTION ENTRY POINT channelName:«"+channelName+"»! nodeServerInstance.receptionEntryPoints.length:",nodeServerInstance.receptionEntryPoints.length);
8212
8668
 
@@ -8219,11 +8675,22 @@ WebsocketImplementation={
8219
8675
 
8220
8676
  send:(channelName, data, clientsRoomsTag=null, clientSocketParam=null)=>{
8221
8677
 
8678
+
8222
8679
  if(!clientSocketParam){
8223
8680
 
8224
- nodeServerInstance.serverSocket.clients.forEach((clientSocket)=>{
8681
+ // TODO : FIXME : Use one single interface !
8682
+ let serverClients;
8683
+ if(!WebsocketImplementation.useSocketIOImplementation) serverClients=nodeServerInstance.serverSocket.clients;
8684
+ else serverClients=nodeServerInstance.serverSocket.sockets.clients();
8685
+
8686
+ serverClients.forEach((clientSocket)=>{
8687
+
8688
+
8689
+ // TODO : FIXME : Use one single interface !
8690
+ if(!WebsocketImplementation.useSocketIOImplementation) if(clientSocket.readyState!==WebSocket.OPEN) return;
8691
+ else if(clientSocket.connected) return;
8692
+
8225
8693
 
8226
- if(clientSocket.readyState!==WebSocket.OPEN) return;
8227
8694
 
8228
8695
  // Room information is stored in client socket object :
8229
8696
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
@@ -8234,14 +8701,19 @@ WebsocketImplementation={
8234
8701
 
8235
8702
  dataWrapped=JSON.stringify(dataWrapped);
8236
8703
 
8237
- clientSocket.send(dataWrapped);
8238
-
8704
+ // TODO : FIXME : Use one single interface !
8705
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
8706
+ else clientSocket.emit(channelName,dataWrapped);
8707
+
8239
8708
  });
8240
8709
 
8241
8710
  }else{
8242
8711
 
8712
+ // TODO : FIXME : Use one single interface !
8243
8713
  let clientSocket=clientSocketParam;
8244
- if(clientSocket.readyState!==WebSocket.OPEN) return;
8714
+ if(!WebsocketImplementation.useSocketIOImplementation) if(clientSocket.readyState!==WebSocket.OPEN) return;
8715
+ else if(clientSocket.connected) return;
8716
+
8245
8717
 
8246
8718
  // Room information is stored in client socket object :
8247
8719
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
@@ -8249,10 +8721,12 @@ WebsocketImplementation={
8249
8721
  // Channel information is stored in exchanged data :
8250
8722
  let dataWrapped={channelName:channelName, data:data};
8251
8723
 
8252
-
8253
8724
  dataWrapped=JSON.stringify(dataWrapped);
8254
8725
 
8255
- clientSocket.send(dataWrapped);
8726
+ // TODO : FIXME : Use one single interface !
8727
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
8728
+ else clientSocket.emit(channelName,dataWrapped);
8729
+
8256
8730
 
8257
8731
  }
8258
8732
 
@@ -8293,23 +8767,45 @@ WebsocketImplementation={
8293
8767
 
8294
8768
 
8295
8769
 
8296
-
8297
-
8298
8770
  // To make the server aware of the clients connections states :
8299
8771
  clientSocket.stateCheckInterval=setInterval(()=>{
8772
+
8773
+
8300
8774
  if (clientSocket.isAlive===false){
8301
8775
  clearInterval(clientSocket.stateCheckInterval);
8302
8776
 
8303
- return clientSocket.terminate();
8777
+ // TODO : FIXME : DUPLICATED CODE :
8778
+ // TODO : FIXME : Use one single interface !
8779
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.terminate();
8780
+ else clientSocket.emit("endConnection");
8781
+
8782
+ // TODO : FIXME : DUPLICATED CODE :
8783
+ if(!empty(nodeServerInstance.onClientLostListeners))
8784
+ foreach(nodeServerInstance.onClientLostListeners,l=>{l.execute(clientSocket);});
8785
+
8786
+ return;
8304
8787
  }
8788
+
8305
8789
  clientSocket.isAlive=false;
8306
8790
  try{
8307
8791
  clientSocket.ping();
8308
8792
  }catch(error){
8309
8793
  lognow("ERROR : A problem occurred when tried to ping client socket : ",error);
8310
8794
  // We effectively end the client connection to this server :
8311
- clientSocket.terminate();
8795
+
8796
+ // TODO : FIXME : DUPLICATED CODE :
8797
+ // TODO : FIXME : Use one single interface !
8798
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.terminate();
8799
+ else clientSocket.emit("endConnection");
8800
+
8801
+ // TODO : FIXME : DUPLICATED CODE :
8802
+ if(!empty(nodeServerInstance.onClientLostListeners))
8803
+ foreach(nodeServerInstance.onClientLostListeners,l=>{l.execute(clientSocket);});
8804
+
8805
+ return;
8312
8806
  }
8807
+
8808
+
8313
8809
  }, nodeServerInstance.clientTimeoutMillis);
8314
8810
  clientSocket.on("pong",()=>{
8315
8811
  clientSocket.isAlive=true;
@@ -8354,7 +8850,12 @@ WebsocketImplementation={
8354
8850
 
8355
8851
  // To make the server aware of the clients connections states :
8356
8852
  nodeServerInstance.serverSocket.on("close", function close() {
8357
- nodeServerInstance.serverSocket.clients.forEach((clientSocket)=>{
8853
+
8854
+ // TODO : FIXME : Use one single interface !
8855
+ if(!WebsocketImplementation.useSocketIOImplementation) serverClients=nodeServerInstance.serverSocket.clients;
8856
+ else serverClients=nodeServerInstance.serverSocket.sockets.clients();
8857
+
8858
+ serverClients.forEach((clientSocket)=>{
8358
8859
  clearInterval(clientSocket.stateCheckInterval);
8359
8860
  });
8360
8861
  });
@@ -8365,39 +8866,26 @@ WebsocketImplementation={
8365
8866
  },
8366
8867
 
8367
8868
 
8368
- // CLIENT
8369
- connectToServer:(serverURL, port, timeout)=>{
8370
- if(WebsocketImplementation.isNode)
8371
- return WebsocketImplementation.connectToServerFromNode(serverURL, port, timeout);
8372
- // else
8373
- return WebsocketImplementation.connectToServerFromBrowser(serverURL, port, timeout);
8374
- },
8375
-
8376
-
8869
+ // NODE / BROWSER CLIENT CONNECTS TO SERVER MAIN ENTRYPOINT:
8870
+ connectToServer:(serverURL, port, isSecure=false, timeout)=>{
8377
8871
 
8872
+ // TRACE
8873
+ lognow("INFO : Using websocket implementation : "+(WebsocketImplementation.isNodeContext?"node (server-side)":"browser (client-side)"));
8874
+
8875
+ if(WebsocketImplementation.isNodeContext)
8876
+ return WebsocketImplementation.connectToServerFromNode(serverURL, port, isSecure, timeout);
8877
+ else
8878
+ return WebsocketImplementation.connectToServerFromBrowser(serverURL, port, isSecure, timeout);
8879
+ },
8378
8880
 
8379
8881
 
8882
+
8883
+ //
8380
8884
  // NODE CLIENT
8381
- /*private*/connectToServerFromNode:(serverURL, port, timeout)=>{
8885
+ //
8886
+ /*private*/connectToServerFromNode:(serverURL, port, isSecure, timeout)=>{
8887
+
8382
8888
 
8383
- // OLD : socket.io :
8384
- //client on server-side:
8385
- //if(typeof(socketIO)!=="undefined"){
8386
- // // Node client :
8387
- // connectToServer=function (server, port, timeout=10000){
8388
- // return socketIO.connect(server + ":" + port,{timeout: timeout});;
8389
- // }
8390
- //}else
8391
- // if(typeof(io)!=="undefined"){
8392
- // // Browser client :
8393
- // connectToServer=function (server, port, timeout=10000){
8394
- // return io.connect(server + ":" + port,{timeout: timeout});
8395
- //// ALTERNATIVE : return io(server + ":" + port,{timeout: timeout});
8396
- // };
8397
- // }else{
8398
- // // TRACE
8399
- // console.log("ERROR : Could not initialize socket !");
8400
- // }
8401
8889
 
8402
8890
  // NEW : ws :
8403
8891
  if(typeof(WebSocket)==="undefined"){
@@ -8405,32 +8893,46 @@ WebsocketImplementation={
8405
8893
  lognow("ERROR : CLIENT : Could not find websocket client lib, aborting client connection.");
8406
8894
  return null;
8407
8895
  }
8408
-
8409
- const clientSocket=new WebSocket(serverURL+":"+port,/*WORKAROUND:*/{ rejectUnauthorized:false });
8410
-
8411
8896
 
8897
+ // NODE CLIENT MODE ONLY :
8898
+ let clientSocket;
8899
+ if(!WebsocketImplementation.useSocketIOImplementation){
8900
+ clientSocket=new WebSocket(serverURL+":"+port,/*WORKAROUND:*/{ rejectUnauthorized:false, secure: isSecure });
8901
+ }else{
8902
+ // NOW : socket.io :
8903
+ //client on server-side:
8904
+ if(WebsocketImplementation.isNodeContext && typeof(socketIO)!=="undefined"){
8905
+ // // Node client :
8906
+ // connectToServer=function (serverURL, port, isSecure, timeout=10000){
8907
+ // return socketIO.connect(serverURL + ":" + port,{timeout: timeout});
8908
+ // }
8909
+ clientSocket=socketIO.connect(serverURL + ":" + port,{timeout: timeout, secure: isSecure});
8910
+ }
8911
+ }
8912
+
8913
+ // NODE CLIENT INSTANCE :
8412
8914
  const nodeClientInstance={
8413
8915
  clientSocket:clientSocket,
8414
8916
 
8415
8917
  receptionEntryPoints:[],
8416
-
8417
8918
 
8418
8919
  receive:(channelName, doOnIncomingMessage, clientsRoomsTag=null)=>{
8419
8920
 
8420
-
8421
8921
  const receptionEntryPoint={
8422
8922
  channelName:channelName,
8423
8923
  clientsRoomsTag:clientsRoomsTag,
8424
8924
  execute:(clientSocketParam)=>{
8425
8925
 
8426
-
8427
- nodeClientInstance.clientSocket.on("message", (dataWrapped)=>{
8926
+ const doOnMessage=(message)=>{
8428
8927
 
8429
- dataWrapped=JSON.parse(dataWrapped);
8928
+ // dataWrapped=JSON.parse(dataWrapped);
8929
+ // dataWrapped=getAt(dataWrapped,0);// We get the root element
8930
+
8931
+ const dataWrapped=(WebsocketImplementation.useFlatStrings?JSON.parse(message):message);
8430
8932
 
8431
8933
 
8432
8934
  // Channel information is stored in exchanged data :
8433
- if(dataWrapped.channelName!==channelName) return;
8935
+ if(dataWrapped.channelName && dataWrapped.channelName!==channelName) return;
8434
8936
 
8435
8937
  let clientSocket=nodeClientInstance.clientSocket;
8436
8938
 
@@ -8439,8 +8941,11 @@ WebsocketImplementation={
8439
8941
 
8440
8942
  doOnIncomingMessage(dataWrapped.data, clientSocket);
8441
8943
 
8442
-
8443
- });
8944
+ };
8945
+
8946
+ if(!WebsocketImplementation.useSocketIOImplementation) nodeClientInstance.clientSocket.addEventListener("message", doOnMessage);
8947
+ else nodeClientInstance.clientSocket.on(channelName, doOnMessage);
8948
+
8444
8949
 
8445
8950
  }
8446
8951
  };
@@ -8448,7 +8953,6 @@ WebsocketImplementation={
8448
8953
 
8449
8954
  nodeClientInstance.receptionEntryPoints.push(receptionEntryPoint);
8450
8955
 
8451
-
8452
8956
  return nodeClientInstance;
8453
8957
  },
8454
8958
 
@@ -8461,8 +8965,10 @@ WebsocketImplementation={
8461
8965
  // // DBG
8462
8966
  // console.log("(NODE CLIENT) TRYING TO SEND : clientSocket.readyState:",clientSocket.readyState);
8463
8967
 
8464
-
8465
- if(clientSocket.readyState!==WebSocket.OPEN) return;
8968
+ // TODO : FIXME : Use one single interface !
8969
+ if(!WebsocketImplementation.useSocketIOImplementation) if(clientSocket.readyState!==WebSocket.OPEN) return;
8970
+ else if(clientSocket.connected) return;
8971
+
8466
8972
 
8467
8973
  // Room information is stored in client socket object :
8468
8974
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
@@ -8482,8 +8988,9 @@ WebsocketImplementation={
8482
8988
  // console.log("(NODE CLIENT) SENDING DATA ! channelName:«"+channelName+"» ; clientsRoomsTag:«"+clientsRoomsTag+"»");
8483
8989
  // console.log("(NODE CLIENT) SENDING DATA ! dataWrapped:",dataWrapped);
8484
8990
 
8485
-
8486
- clientSocket.send(dataWrapped);
8991
+ // TODO : FIXME : Use one single interface !
8992
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
8993
+ else clientSocket.emit(channelName,dataWrapped);
8487
8994
 
8488
8995
  return nodeClientInstance;
8489
8996
  },
@@ -8492,14 +8999,19 @@ WebsocketImplementation={
8492
8999
  join:(clientRoomTag)=>{
8493
9000
  // Join room client part protocol :
8494
9001
  const message={type:"joinRoom",clientRoomTag:clientRoomTag};
9002
+
8495
9003
  nodeClientInstance.send("protocol",message);
8496
9004
 
8497
9005
  },
8498
9006
 
8499
9007
 
8500
9008
  onConnectionToServer:(doOnConnection)=>{
8501
-
8502
- nodeClientInstance.clientSocket.on("open", ()=>{
9009
+
9010
+ const doAllOnConnection=()=>{
9011
+
9012
+ // To avoid triggering this event several times, depending on the implementation :
9013
+ if(nodeClientInstance.hasConnectEventFired) return;
9014
+ nodeClientInstance.hasConnectEventFired=true;
8503
9015
 
8504
9016
  let clientSocket=nodeClientInstance.clientSocket;
8505
9017
 
@@ -8510,7 +9022,11 @@ WebsocketImplementation={
8510
9022
  receptionEntryPoint.execute(clientSocket);
8511
9023
  });
8512
9024
 
8513
- });
9025
+ };
9026
+
9027
+ if(!WebsocketImplementation.useSocketIOImplementation) nodeClientInstance.clientSocket.addEventListener("open",doAllOnConnection);
9028
+ else nodeClientInstance.clientSocket.on("connect",doAllOnConnection);
9029
+
8514
9030
  },
8515
9031
 
8516
9032
 
@@ -8524,7 +9040,7 @@ WebsocketImplementation={
8524
9040
 
8525
9041
  // BROWSER CLIENT
8526
9042
 
8527
- /*private*/connectToServerFromBrowser:(serverURL, port, timeout)=>{
9043
+ /*private*/connectToServerFromBrowser:(serverURL, port, isSecure, timeout)=>{
8528
9044
 
8529
9045
  // NEW : ws :
8530
9046
  if(typeof(WebSocket)==="undefined"){
@@ -8532,23 +9048,49 @@ WebsocketImplementation={
8532
9048
  lognow("ERROR : CLIENT : Could not find websocket client lib, aborting client connection.");
8533
9049
  return null;
8534
9050
  }
8535
-
8536
- const clientSocket=new WebSocket(serverURL+":"+port,["wss"]);
9051
+
9052
+ // TODO : FIXME : Use one single interface !
9053
+ // BROWSER CLIENT MODE ONLY :
9054
+ let clientSocket;
9055
+ if(!WebsocketImplementation.useSocketIOImplementation){
9056
+ clientSocket=new WebSocket(serverURL+":"+port,["ws","wss"]);
9057
+ }else if(typeof(io)!=="undefined"){
9058
+ // NOW : socket.io :
9059
+ // // Browser client :
9060
+ // connectToServer=function (server, port, isSecure, timeout=10000){
9061
+ // return io.connect(serverURL + ":" + port,{timeout: timeout});
9062
+ //// ALTERNATIVE : return io(serverURL + ":" + port,{timeout: timeout});
9063
+ // };
9064
+ // }else{
9065
+ // // TRACE
9066
+ // console.log("ERROR : Could not initialize socket !");
9067
+ clientSocket=io.connect(serverURL + ":" + port,{timeout: timeout, secure: isSecure});
9068
+ }
8537
9069
 
8538
9070
 
9071
+ // BROWSER CLIENT INSTANCE :
8539
9072
  const browserInstance={
8540
9073
  clientSocket:clientSocket,
8541
-
8542
9074
 
8543
9075
  receive:(channelName, doOnIncomingMessage, clientsRoomsTag=null)=>{
9076
+
9077
+ // DBG
9078
+ lognow("!!! SETTING UP RECEIVE for :",channelName);
8544
9079
 
8545
- browserInstance.clientSocket.addEventListener("message", (event)=>{
9080
+ const doOnMessage=(message)=>{
8546
9081
 
8547
- let dataWrapped=JSON.parse(event.data);
8548
-
8549
-
9082
+ // // DBG
9083
+ // lognow("!!! RECEIVED : message",message);
9084
+
9085
+ // OLD : let dataWrapped=JSON.parse(event.data);
9086
+ // const dataWrapped=JSON.parse(message);
9087
+ // dataWrapped=getAt(dataWrapped,0);// We get the root element
9088
+
9089
+ const dataWrapped=(WebsocketImplementation.useFlatStrings?JSON.parse(message):message);
9090
+
9091
+
8550
9092
  // Channel information is stored in exchanged data :
8551
- if(dataWrapped.channelName!==channelName) return;
9093
+ if(dataWrapped.channelName && dataWrapped.channelName!==channelName) return;
8552
9094
 
8553
9095
  let clientSocket=browserInstance.clientSocket;
8554
9096
 
@@ -8557,9 +9099,14 @@ WebsocketImplementation={
8557
9099
 
8558
9100
  doOnIncomingMessage(dataWrapped.data, clientSocket);
8559
9101
 
8560
- });
9102
+ };
9103
+
9104
+
9105
+ if(!WebsocketImplementation.useSocketIOImplementation) browserInstance.clientSocket.addEventListener("message", doOnMessage);
9106
+ else browserInstance.clientSocket.on(channelName, doOnMessage);
9107
+
8561
9108
 
8562
- return browserInstance;
9109
+ return browserInstance;
8563
9110
  },
8564
9111
 
8565
9112
 
@@ -8571,8 +9118,10 @@ WebsocketImplementation={
8571
9118
  // // DBG
8572
9119
  // console.log("(BROWSER) TRYING TO SEND : clientSocket.readyState:",clientSocket.readyState);
8573
9120
 
8574
-
8575
- if(clientSocket.readyState!==WebSocket.OPEN) return;
9121
+ // TODO : FIXME : Use one single interface !
9122
+ if(!WebsocketImplementation.useSocketIOImplementation) if(clientSocket.readyState!==WebSocket.OPEN) return;
9123
+ else if(clientSocket.connected) return;
9124
+
8576
9125
 
8577
9126
  // Room information is stored in client socket object :
8578
9127
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
@@ -8587,7 +9136,11 @@ WebsocketImplementation={
8587
9136
 
8588
9137
  dataWrapped=JSON.stringify(dataWrapped);
8589
9138
 
8590
- clientSocket.send(dataWrapped);
9139
+
9140
+ // TODO : FIXME : Use one single interface !
9141
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
9142
+ else clientSocket.emit(channelName,dataWrapped);
9143
+
8591
9144
 
8592
9145
  return browserInstance;
8593
9146
  },
@@ -8601,7 +9154,15 @@ WebsocketImplementation={
8601
9154
 
8602
9155
 
8603
9156
  onConnectionToServer:(doOnConnection)=>{
8604
- browserInstance.clientSocket.addEventListener("open",doOnConnection);
9157
+
9158
+ // To avoid triggering this event several times, depending on the implementation :
9159
+ if(browserInstance.hasConnectEventFired) return;
9160
+ browserInstance.hasConnectEventFired=true;
9161
+
9162
+ if(!WebsocketImplementation.useSocketIOImplementation) browserInstance.clientSocket.addEventListener("open",doOnConnection);
9163
+ else browserInstance.clientSocket.on("connect",doOnConnection);
9164
+
9165
+
8605
9166
  },
8606
9167
 
8607
9168
 
@@ -8732,7 +9293,7 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
8732
9293
 
8733
9294
 
8734
9295
  // TRACE
8735
- console.log("INFO : SERVER : Generic Nodejs server launched and listening on port " + port + "!");
9296
+ console.log("INFO : SERVER : Generic Nodejs server launched and listening on port:" + port + "!");
8736
9297
 
8737
9298
 
8738
9299
  return server;
@@ -8743,7 +9304,7 @@ initNodeServer=function(doOnClientConnection=null, doOnFinalizeServer=null, /*OP
8743
9304
 
8744
9305
  // TRACE
8745
9306
  console.log("Server launched.");
8746
- console.log("Usage : node <server.js> [port] [ssl certificate path | unsecure ] [ssl key path]");
9307
+ console.log("Usage : node <server.js> conf {port:[port], sslCertPath:[ssl certificate path | unsecure ], sslKeyPath:[ssl key path], serverConfig:[JSON server configuration]}");
8747
9308
  console.log("Or (to generate password hash) : node <server.js> hash <clientId@repositoryName> <clearText>");
8748
9309
  // EXAMPLE : node orita-srv.js hash orita.global@saltkey 1234567890
8749
9310
  console.log("Server launched.");
@@ -8753,30 +9314,46 @@ initNodeServer=function(doOnClientConnection=null, doOnFinalizeServer=null, /*OP
8753
9314
  let argCLPort;
8754
9315
  let argCLCertPath;
8755
9316
  let argCLKeyPath;
9317
+ let serverConfig={};
8756
9318
  let isForceUnsecure;
8757
9319
 
8758
9320
  let isHashAsked=false;
8759
9321
  let clearTextParam=null;
8760
9322
  let persisterId=null;
9323
+
9324
+
8761
9325
  process.argv.forEach(function (val, i){
8762
9326
  if(!val) return;
8763
9327
  // 0 corresponds to «node / nodejs»
8764
9328
  if(i<=1) return; // 1 corresponds to « <server.js> »
8765
9329
  else if(i==2){
8766
- if(val!=="hash") argCLPort=val;
8767
- else isHashAsked=true;
9330
+ if(val==="hash") isHashAsked=true;
8768
9331
  }else if(i==3){
8769
- if(!isHashAsked) argCLCertPath=val;
8770
- else persisterId=val;
9332
+ if(!isHashAsked){
9333
+ try{
9334
+ const jsonConf=JSON.parse(val);
9335
+ argCLPort=jsonConf.port;
9336
+ argCLCertPath=jsonConf.sslCertPath;
9337
+ argCLKeyPath=jsonConf.sslKeyPath;
9338
+ try{
9339
+ serverConfig=nonull({},JSON.parse(jsonConf.serverConfig));
9340
+ }catch(err1){
9341
+ lognow("ERROR : Cannot parse JSON string «"+jsonConf.serverConfig+"».",err1);
9342
+ }
9343
+ }catch(err2){
9344
+ lognow("ERROR : Cannot parse JSON string «"+val+"».",err2);
9345
+ isHashAsked=true;
9346
+ }
9347
+ } else persisterId=val;
8771
9348
  }else if(i==4){
8772
- if(!isHashAsked) argCLKeyPath=val;
8773
- else clearTextParam=val;
9349
+ if(isHashAsked) clearTextParam=val;
8774
9350
  }
8775
9351
  });
8776
-
8777
9352
  isForceUnsecure=(argCLCertPath==="unsecure");
8778
-
8779
- let aotraNodeServer={};
9353
+
9354
+
9355
+
9356
+ const aotraNodeServer={config:serverConfig};
8780
9357
  aotraNodeServer.serverManager={ start:()=>{/*DEFAULT START FUNCTION, WILL BE OVERRIDEN LATER*/}};
8781
9358
 
8782
9359
  if(isHashAsked){
@@ -8820,8 +9397,6 @@ initNodeServer=function(doOnClientConnection=null, doOnFinalizeServer=null, /*OP
8820
9397
  }
8821
9398
 
8822
9399
 
8823
-
8824
-
8825
9400
  const DEFAULT_PORT=nonull(argCLPort,25000);
8826
9401
  const DEFAULT_CERT_PATH=nonull(argCLCertPath,"cert.pem");
8827
9402
  const DEFAULT_KEY_PATH=nonull(argCLKeyPath,"key.key");
@@ -8838,9 +9413,9 @@ initNodeServer=function(doOnClientConnection=null, doOnFinalizeServer=null, /*OP
8838
9413
 
8839
9414
 
8840
9415
  // UNUSEFUL :
8841
- // aotraNodeServer.serverManager.microClientsSockets=[];
9416
+ //aotraNodeServer.serverManager.microClientsSockets=[];
8842
9417
  // UNUSEFUL :
8843
- // aotraNodeServer.serverManager.mainClientsSockets=[];
9418
+ //aotraNodeServer.serverManager.mainClientsSockets=[];
8844
9419
  aotraNodeServer.serverManager.start=function(){
8845
9420
 
8846
9421
  // Eventual encryption options :
@@ -8871,147 +9446,181 @@ initNodeServer=function(doOnClientConnection=null, doOnFinalizeServer=null, /*OP
8871
9446
  return aotraNodeServer;
8872
9447
  }
8873
9448
 
9449
+ // ========================= FUSRODA SERVER : =========================
8874
9450
 
8875
- // -Client :
9451
+ //
9452
+ // DOES NOT WORK : USE Java FusrodaServer instead :
9453
+ //
9454
+ ///*FUSRODA server stands from FSRD SERVER, for Fucking Simple Remote Desktop SERVER*/
9455
+ //createFusrodaServer=function(certPathParam=null,keyPathParam=null,portParam=6080){
9456
+ //
9457
+ // // https://www.npmjs.com/package/screenshot-desktop
9458
+ // // https://github.com/octalmage/robotjs
9459
+ // // npm install --save screenshot-desktop
9460
+ // //
9461
+ // // sudo apt-get install libxtst-dev libx11-dev
9462
+ // // npm install --save robotjs
9463
+ //
9464
+ // // http://getrobot.net/docs/usage.html
9465
+ // //
9466
+ // // apt-get install build-essential python libxt-dev libxtst-dev libxinerama-dev -y
9467
+ //
9468
+ // const screenshot=require("screenshot-desktop");
9469
+ //
9470
+ // const REFRESH_SCREENSHOTS_MILLIS=500;
9471
+ //
9472
+ //
9473
+ // const server=initNodeServer(
9474
+ // // On client connection :
9475
+ //// (serverParam, clientSocketParam)=>{},
9476
+ // null,
9477
+ // // On server finalization :
9478
+ // (serverParam)=>{
9479
+ //
9480
+ // serverParam.receive("protocol_fusroda", (message, clientSocket)=> {
9481
+ // serverParam.addToRoom(clientSocket,"clients");
9482
+ // });
9483
+ //
9484
+ //
9485
+ // serverParam.sendScreenshotsRoutine=setInterval(()=>{
9486
+ // if(serverParam.isScreenshotStarted) return;
9487
+ //
9488
+ // serverParam.isScreenshotStarted=true;
9489
+ //
9490
+ // screenshot().then((img) => {
9491
+ //
9492
+ // const data={image:img,messageType:"imageData"};
9493
+ // serverParam.send("message", data, "clients");
9494
+ //
9495
+ // serverParam.isScreenshotStarted=false;
9496
+ //
9497
+ // }).catch((error) => {
9498
+ // // TRACE
9499
+ // lognow("ERROR : Error during screenshot :",error);
9500
+ // });
9501
+ //
9502
+ // },REFRESH_SCREENSHOTS_MILLIS);
9503
+ //
9504
+ //
9505
+ // },portParam,certPathParam,keyPathParam);
9506
+ //
9507
+ //
9508
+ //// const doOnConnect=(serverParam, clientSocketParam)=>{
9509
+ //// };
9510
+ //// const doOnFinalizeServer=(serverParam)=>{
9511
+ //// /*DO NOTHING*/
9512
+ //// };
9513
+ //// const server={};
9514
+ //// server.start=(port=6080)=>{
9515
+ //// server.httpServer=launchNodeHTTPServer(port, doOnConnect, doOnFinalizeServer, sslOptions);
9516
+ //// };
9517
+ //
9518
+ // return server;
9519
+ //}
8876
9520
 
8877
- // TODO : Use everywhere it's applicable !
8878
- initClient=function(isNode=true, doOnServerConnection=null, doOnError=null, url, /*OPTIONAL*/port=25000, /*OPTIONAL*/timeout=10000, /*OPTIONAL*/selfParam=null){
8879
- let aotraClient={};
8880
9521
 
8881
-
8882
- aotraClient.client={};
8883
- aotraClient.client.start=function(){
8884
-
9522
+ // ========================= UTILITY SERVERSIDE METHODS : =========================
9523
+
9524
+ class ListManager{
9525
+ constructor(config){
9526
+ this.config=config;
9527
+ this.maxItemsNumber=nonull(this.config.max,999);
9528
+ this.simultaneousItemsNumber=nonull(this.config.simultaneous,1);
9529
+ this.sessionDurationSeconds=nonull(this.config.duration,null);
9530
+ this.mode=nonull(this.config.mode,"startAtConnexion");
8885
9531
 
8886
- let socketToServer=WebsocketImplementation.getStatic(isNode).connectToServer(url, port, timeout);
8887
- if(!socketToServer){
9532
+ this.itemsInfos={};
9533
+ this.time=null;
9534
+ this.started=false;
9535
+ }
9536
+
9537
+ addItem(id,item){
9538
+ if(id==null){
8888
9539
  // TRACE
8889
- lognow("ERROR : CLIENT : Could not get socketToServer, aborting client connection.");
8890
- return null;
9540
+ lognow("ERROR : Cannot add item with no id.");
9541
+ return;
8891
9542
  }
8892
-
8893
- // When client is connected, we execute the callback :
8894
- // CAUTION : MUST BE CALLED ONLY ONCE !
8895
- socketToServer.onConnectionToServer(()=>{
8896
- if(doOnServerConnection){
8897
- if(selfParam) doOnServerConnection.apply(selfParam,[socketToServer]);
8898
- else doOnServerConnection(socketToServer);
8899
- }
8900
- });
8901
-
8902
- // Errors handling :
8903
- if(doOnError){
8904
-
8905
- // // DBG
8906
- // lognow(">>> serverSocket.connected:"+serverSocket.connected);
8907
- // lognow(">>> serverSocket:",serverSocket);
8908
-
8909
-
8910
- const errorMethod=function(){
8911
- // TRACE
8912
- lognow("ERROR : CLIENT : Client encountered an error while trying to connect to server.");
8913
-
8914
- if(selfParam) doOnError.apply(selfParam);
8915
- else doOnError();
8916
- };
8917
-
8918
- // // DOES NOT SEEM TO WORK :
8919
- // socketToServer.on("connect_failed", errorMethod);
8920
- // // DOES NOT SEEM TO WORK :
8921
- // socketToServer.on("error", errorMethod);
8922
-
9543
+ if(!item){
9544
+ // TRACE
9545
+ lognow("ERROR : Cannot add null item.");
9546
+ return;
8923
9547
  }
8924
-
8925
-
8926
- aotraClient.client.socketToServer=socketToServer;
8927
-
8928
- return aotraClient;
8929
- };
8930
-
9548
+ const numberOfItemsCurrently=countKeys(this.itemsInfos);
9549
+ if(this.maxItemsNumber<=numberOfItemsCurrently){
9550
+ // TRACE
9551
+ lognow("ERROR : Cannot add item with id «"+id+"», list already full.");
9552
+ return;
9553
+ }
9554
+ if(numberOfItemsCurrently==0 && this.mode==="startAtConnexion"){
9555
+ this.startSession();
9556
+ }
9557
+ this.itemsInfos[id]={
9558
+ item:item,
9559
+ time:getNow(),
9560
+ };
9561
+ }
8931
9562
 
8932
- return aotraClient;
8933
- }
8934
-
8935
-
8936
-
8937
-
8938
- // ===================================================================================================
8939
-
8940
- /*FUSRODA server stands from FSRD SERVER, for Fucking Simple Remote Desktop SERVER*/
8941
- createFusrodaServer=function(certPathParam=null,keyPathParam=null,portParam=6001){
9563
+ startSession(){
9564
+ this.time==getNow();
9565
+ this.started=true;
9566
+ }
8942
9567
 
8943
- // https://www.npmjs.com/package/screenshot-desktop
8944
- // https://github.com/octalmage/robotjs
8945
- // npm install --save screenshot-desktop robotjs
9568
+ stopSession(){
9569
+ this.started=false;
9570
+ }
8946
9571
 
8947
- const screenshot=require("screenshot-desktop");
8948
-
8949
- const REFRESH_SCREENSHOTS_MILLIS=500;
8950
-
9572
+ isSessionActive(){
9573
+ if(!this.sessionDurationSeconds) return true;
9574
+ if(!this.started) return false;
9575
+ return !hasDelayPassed(this.time, this.sessionDurationSeconds*1000);
9576
+ }
8951
9577
 
8952
- const server=initNodeServer(
8953
- // On client connection :
8954
- // (serverParam, clientSocketParam)=>{},
8955
- null,
8956
- // On server finalization :
8957
- (serverParam)=>{
8958
-
8959
- serverParam.receive("protocol_fusroda", (message, clientSocket)=> {
8960
- serverParam.addToRoom(clientSocket,"clients");
8961
- });
8962
-
8963
-
8964
- serverParam.sendScreenshotsRoutine=setInterval(()=>{
8965
- if(serverParam.isScreenshotStarted) return;
8966
-
8967
- serverParam.isScreenshotStarted=true;
8968
-
8969
- screenshot().then((img) => {
9578
+ isClientActive(clientId){
9579
+ if(!clientsListManager.isSessionActive()) return false;
9580
+ const clientPosition=this.getItemPosition(clientId);
9581
+ const result=(clientPosition<=this.maxItemsNumber);
9582
+ return result;
9583
+ }
8970
9584
 
8971
- const data={image:img};
8972
- serverParam.send("data_fusroda", data, "clients");
8973
-
8974
- serverParam.isScreenshotStarted=false;
8975
-
8976
- }).catch((error) => {
8977
- // TRACE
8978
- lognow("ERROR : Error during screenshot :",error);
8979
- });
8980
-
8981
- },REFRESH_SCREENSHOTS_MILLIS);
8982
-
8983
-
8984
- },portParam,certPathParam,keyPathParam);
8985
-
8986
-
8987
- // const doOnConnect=(serverParam, clientSocketParam)=>{
8988
- // };
8989
- // const doOnFinalizeServer=(serverParam)=>{
8990
- // /*DO NOTHING*/
8991
- // };
8992
- // const server={};
8993
- // server.start=(port=6001)=>{
8994
- // server.httpServer=launchNodeHTTPServer(port, doOnConnect, doOnFinalizeServer, sslOptions);
8995
- // };
8996
-
8997
- return server;
8998
- }
8999
-
9585
+ removeItem(id){
9586
+ if(id==null){
9587
+ // TRACE
9588
+ lognow("ERROR : Cannot remove item, no id specified.");
9589
+ return;
9590
+ }
9591
+ if(!this.itemsInfos[id]){
9592
+ // TRACE
9593
+ lognow("ERROR : Cannot remove item, item not found for id «"+id+"».");
9594
+ return;
9595
+ }
9596
+ delete this.itemsInfos[id];
9597
+ }
9000
9598
 
9599
+ // Goes from 1 to <length>:
9600
+ getItemPosition(id){
9601
+ if(id==null){
9602
+ // TRACE
9603
+ lognow("ERROR : Cannot calculate item position, no id specified.");
9604
+ return;
9605
+ }
9606
+ if(!this.itemsInfos[id]){
9607
+ // TRACE
9608
+ lognow("ERROR : Cannot calculate item position, item not found for id «"+id+"».");
9609
+ return;
9610
+ }
9611
+ let result=0;
9612
+ foreach(this.itemsInfos,(itemInfo,key)=>{
9613
+ result++;
9614
+ if(id==key) return "break";
9615
+ },null,(itemInfo1,itemInfo2)=>item1.time<item2.time);
9616
+ return result;
9617
+ }
9001
9618
 
9002
- createFusrodaClient=function(doOnDataReception, urlParam=null,portParam=6001){
9003
9619
 
9004
- const isNode=false;
9005
- const client=WebsocketImplementation.getStatic(isNode).connectToServer(urlParam, portParam);
9006
- client.onConnectionToServer((nodeClientInstance)=>{
9007
-
9008
- nodeClientInstance.send("protocol_fusroda");
9620
+ }
9009
9621
 
9010
- nodeClientInstance.receive("data_fusroda", doOnDataReception, "clients");
9011
-
9012
- });
9013
-
9014
- return client;
9622
+ function getListManager(config){
9623
+ return new ListManager(config);
9015
9624
  }
9016
9625
 
9017
9626
 
@@ -9020,7 +9629,8 @@ createFusrodaClient=function(doOnDataReception, urlParam=null,portParam=6001){
9020
9629
 
9021
9630
 
9022
9631
 
9023
- /*utils COMMONS library associated with aotra version : «1.0.0.000 (11/01/2022-00:18:33)»*/
9632
+
9633
+ /*utils COMMONS library associated with aotra version : «1.0.0.000 (11/07/2022-00:48:13)»*/
9024
9634
  /*-----------------------------------------------------------------------------*/
9025
9635
 
9026
9636
 
@@ -10884,9 +11494,9 @@ window.LZWString={
10884
11494
 
10885
11495
  readBits : function(numBits, data){
10886
11496
  var res=0;
10887
- var maxpower=Math.pow(2,numBits);
11497
+ var maxPower=Math.pow(2,numBits);
10888
11498
  var power=1;
10889
- while (power!=maxpower){
11499
+ while (power!=maxPower){
10890
11500
  res |= this.readBit(data) * power;
10891
11501
  power <<= 1;
10892
11502
  }
@@ -11230,6 +11840,31 @@ window.copy=function(array){
11230
11840
  return result;
11231
11841
  }
11232
11842
 
11843
+
11844
+
11845
+ window.removeByIndex=function(array, elementIndex){
11846
+ if(array instanceof Array){
11847
+ if(empty(array)) return false;
11848
+ if(elementIndex<0) return false;
11849
+ array.splice(elementIndex, 1);
11850
+ return true;
11851
+ }
11852
+ let hasDeleted=false;
11853
+ // Case of regular objects that are like associative arrays :
11854
+ let i=0;
11855
+ for (key in array){
11856
+ if(!array.hasOwnProperty(key)) continue;
11857
+ if(i===elementIndex){
11858
+ delete array[key];
11859
+ hasDeleted=true;
11860
+ break;
11861
+ }
11862
+ i++;
11863
+ }
11864
+ return hasDeleted;
11865
+ }
11866
+
11867
+
11233
11868
  window.remove=function(array, element){
11234
11869
  if(array instanceof Array){
11235
11870
  if(empty(array)) return false;
@@ -11335,15 +11970,41 @@ Math.minInArray=function(arrayOfValues){
11335
11970
  return min;
11336
11971
  };
11337
11972
 
11973
+
11974
+
11338
11975
  // TODO : FIXME : Create a namespace MathUtils. and ArraysUtils. and sort out the corresponding static methods.
11339
- Math.getRandomInArray=function(arrayOfValues){
11340
- if(empty(arrayOfValues))
11341
- return null;
11342
- var size=getArraySize(arrayOfValues);
11976
+ Math.getRandomsInArray=function(arrayOfValues, numberOfItemsToGet=1){
11977
+
11978
+ if(numberOfItemsToGet<0 || empty(arrayOfValues)) return [];
11979
+ const size=getArraySize(arrayOfValues);
11980
+ // We skip a costly random calculation if we have only one element in array :
11981
+ if(size===1) return [valueAt(arrayOfValues,0)];
11982
+
11983
+ const indexesPool=[];
11984
+ for(let i=0;i<size;i++) indexesPool.push(i);
11985
+
11986
+ const results=[];
11987
+ for(let i=0;i<numberOfItemsToGet;i++){
11988
+ if(empty(indexesPool)) break;
11989
+ const chosenIndex=Math.getRandomInArray(indexesPool,true);
11990
+ const item=valueAt(arrayOfValues,chosenIndex);
11991
+ results.push(item);
11992
+ }
11993
+
11994
+ return results;
11995
+ }
11996
+
11997
+ // TODO : FIXME : Create a namespace MathUtils. and ArraysUtils. and sort out the corresponding static methods.
11998
+ Math.getRandomInArray=function(arrayOfValues,removeFromArray=false){
11999
+ if(empty(arrayOfValues)) return null;
12000
+ const size=getArraySize(arrayOfValues);
11343
12001
  // We skip a costly random calculation if we have only one element in array :
11344
12002
  if(size===1) return valueAt(arrayOfValues,0);
11345
- var chosenIndex=Math.getRandomInt(size-1,0);
11346
- return valueAt(arrayOfValues,chosenIndex);
12003
+
12004
+ const chosenIndex=Math.getRandomInt(size-1,0);
12005
+ const result=valueAt(arrayOfValues, chosenIndex);
12006
+ if(removeFromArray) removeByIndex(arrayOfValues, chosenIndex);
12007
+ return result;
11347
12008
  }
11348
12009
 
11349
12010
  // This function is for arrays, like Array or Objects used as associative arrays you want to test as holding elements :
@@ -11956,6 +12617,52 @@ Math.demiCurve=function(min, max, value, strategy="triangle", precision=2){
11956
12617
  return Math.roundTo(cursorFactor,precision);
11957
12618
  }
11958
12619
 
12620
+ // Not the same as curve, where min and max are the minimum and maximum values taken by returned value :
12621
+ Math.curveTriggered=function(minTriggerFactor, maxTriggerFactor, valueFactorParam, strategy="linear"){
12622
+ const valueFactor=Math.coerceInRange(valueFactorParam,0,1);
12623
+ let result=1;
12624
+ if(valueFactor<minTriggerFactor){
12625
+ // Ascending slope :
12626
+ if(minTriggerFactor==0) return 1;
12627
+ result=valueFactor/minTriggerFactor;
12628
+ }else{
12629
+ if(maxTriggerFactor==0) return 1;
12630
+ if(maxTriggerFactor<valueFactor){
12631
+ // Descending slope :
12632
+ result=1-((valueFactor-maxTriggerFactor)/(1-maxTriggerFactor));
12633
+ }else{
12634
+ // Plateau :
12635
+ return 1;
12636
+ }
12637
+ }
12638
+ return result;
12639
+ }
12640
+
12641
+ Math.slopeTriggered=function(minTriggerFactor, maxTriggerFactor, valueFactorParam, direction="ascending", strategy="linear"){
12642
+
12643
+ const valueFactor=Math.coerceInRange(valueFactorParam,0,1);
12644
+
12645
+ if(valueFactor<minTriggerFactor){
12646
+ // Beginning plateau :
12647
+ if(direction=="ascending") return 0;
12648
+ else return 1;
12649
+ }else if(maxTriggerFactor<valueFactor){
12650
+ // Ending plateau :
12651
+ if(direction=="ascending") return 1;
12652
+ else return 0;
12653
+ }
12654
+
12655
+ // Intermediate slope :
12656
+ const total=(maxTriggerFactor-minTriggerFactor);
12657
+ if(total==0) return ((direction=="ascending")?1:0);
12658
+
12659
+ const factor=((valueFactor-minTriggerFactor)/total);
12660
+
12661
+ return ((direction=="ascending")?factor:(1-factor));
12662
+ }
12663
+
12664
+
12665
+
11959
12666
  Math.curve=function(min, max, value, strategy="triangle",
11960
12667
  // It is actually where to place the «summit» of the curve, before returning to 0,
11961
12668
  // the factor value where the values starts decreasing, after having increased all along, and then goes back to 0: