aotrautils 0.0.53 → 0.0.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- /*utils CLIENT library associated with aotra version : «1.0.0.000 (11/07/2022-23:49:31)»*/
3
+ /*utils CLIENT library associated with aotra version : «1.0.0.000 (12/07/2022-23:38:27)»*/
4
4
  /*-----------------------------------------------------------------------------*/
5
5
  /* ## Utility global methods in a browser (htmljs) client environment.
6
6
  *
@@ -55,571 +55,298 @@ if(typeof monitorProgression === "undefined"){
55
55
 
56
56
 
57
57
 
58
+
59
+
60
+
58
61
  // NOT AOTESTABLE !
59
- //TODO : develop :
60
- //getHashedString=monitorProgression(100000,
61
- window.getHashedString=function(str,/*OPTIONAL*/isHeavyTreatment){
62
-
63
- //CAUTION : YOU MUST *NOT* CHANGE THIS VALUE, UNLESS THE HASH HEAVY TREATMENT VALUE IN YOUR AOTRA SERVER-SIDE CODEIS THE SAME !
64
- const HEAVY_TREATMENT_LOOP_COUNT=100000;
65
-
66
- if(isHeavyTreatment){
67
62
 
68
- // If needed, we make sure the hashing will take a long time, so that
69
- // if the private salt key is leaked, it will be fairly difficult to crack
70
- // password write protection and content encryption :
71
-
72
- let result=str;
73
- for(var i=0;i<HEAVY_TREATMENT_LOOP_COUNT;i++){
74
- result=getHashedString(result,false);
75
- }
63
+ //Works, but doesn't compile under PhoneGap...:
64
+ //function getURLParameter(nameParam){
65
+ //var name=encodeURIComponent(nameParam);
66
+ //return decodeURIComponent(
67
+ //(
68
+ //new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)' )
69
+ //.exec(window.location.search)
70
+ //|| [,""]
71
+ //)[1]
72
+ //.replace(/\+/g, '%20')
73
+ //)||null;
74
+ //}
76
75
 
77
-
78
- return result;
79
-
80
- }else{
81
-
82
-
83
- // SHA-256 hash (sucks less) :
84
- // DEPENDENCY : SJCL JS libray (Stanford Javascript Crypto Library)
85
- if(typeof(sjcl)!="undefined"){
86
- let result=sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(str));
87
-
88
- // TODO : develop :
89
- // hash.progressionMonitor.progress(1);
90
-
91
-
92
- return result;
93
- }
94
-
95
- // if not found : MD5 hash (sucks) :
96
- //TRACE
97
- log("WARN : Could not find SHA hashing, using MD5. This sucks, and this very likely will prevent your aotra client to be able to write data to aotra server.");
98
-
99
- return hex_md5(str);
100
- }
76
+ //...so we replace it with another function that is supposed to do the same
77
+ //thing :
78
+ function getURLParameter(nameParam){
79
+ var name=nameParam;
80
+ if (name=new RegExp("[?&]" + encodeURIComponent(name) + "=([^&]*)").exec(location.search))
81
+ return decodeURIComponent(name[1]);
82
+ return null;
101
83
  }
102
- //);
103
84
 
104
- function encrypt(strToEncrypt,key,/*OPTIONAL*/mode){
105
- if(!mode || typeof(sjcl)=="undefined") mode="xor";
106
- mode=mode.trim();
107
85
 
108
- if(contains(mode,"aes") && contains(mode,"xor")){
109
86
 
110
- // XOR :
111
- var xorStr=btoa(encodeXORNoPattern(strToEncrypt,key));
112
-
113
- var aesStr=
114
- // AES
115
- sjcl.encrypt(key,xorStr);
116
87
 
117
- return aesStr;
118
- }
88
+ // A little method to help i18ning aotra user interface :
89
+ const I18N_REPOSITORY_VARIABLE_NAME="I18N_KEYS";
90
+ function i18n(languagesAndStrings, langParam=null){
119
91
 
120
- // DEPENDENCY : SJCL JS libray (Stanford Javascript Crypto Library)
121
- if(mode==="aes")
122
- return sjcl.encrypt(key,strToEncrypt);
123
- return btoa(encodeXORNoPattern(strToEncrypt,key));
124
-
125
- }
126
-
127
- function decrypt(strToDecrypt,key,/*OPTIONAL*/mode){
128
- if(!mode || typeof(sjcl)=="undefined") mode="xor";
129
- mode=mode.trim();
92
+ // ******************* CONFIG *******************
93
+ const PREFERED_LANGUAGE="fr"; // My Quebecer wife (love on her for ever and beyond) forced me to set this...(I'm French.)
94
+
95
+ const NO_TRANSLATION_LABEL="NO TRANSLATION FOUND";
96
+
97
+ const ADD_SYSTEMATICALLY_TO_REPOSITORY=true;
98
+ const MAX_CHARACTERS_FOR_TRANSLATION_KEY=64; // Really if you have more than 64 characters in commons, then it really should be the same string !
130
99
 
131
- if(contains(mode,"aes") && contains(mode,"xor")){
132
-
133
- // AES
134
- var aesStr=sjcl.decrypt(key,strToDecrypt);
135
-
136
- var xorStr=
137
- // XOR :
138
- encodeXORNoPattern(atob(aesStr),key);
100
+ // ***************** END CONFIG *****************
139
101
 
140
- return xorStr;
141
-
102
+
103
+ if(!languagesAndStrings){
104
+ // TRACE
105
+ console.log("ERROR : No parameter for i18n provided!",languagesAndStrings);
106
+ return NO_TRANSLATION_LABEL;
142
107
  }
143
108
 
144
- // DEPENDENCY : SJCL JS libray (Stanford Javascript Crypto Library)
145
- if(mode==="aes")
146
- return sjcl.decrypt(key,strToDecrypt);
147
- return encodeXORNoPattern(atob(strToDecrypt),key);
109
+ let lang=null;
110
+ if(langParam){
111
+ lang=langParam;
112
+ }else{
113
+ // Parameters priority :
114
+ if(window){
115
+ if(window.getURLParameter){
116
+ lang=nonull(window.getURLParameter("language"), window.getURLParameter("lang"));
117
+ }
118
+ if(!lang && (window.language || window.lang)){
119
+ lang=nonull(window.language, window.lang);
120
+ }
121
+ }else if(global){ // (NodeJS compatibility)
122
+ if(global.getURLParameter){
123
+ lang=nonull(global.getURLParameter("language"), global.getURLParameter("lang"));
124
+ }
125
+ if(!lang && (global.language || global.lang)){
126
+ lang=nonull(global.language, global.lang);
127
+ }
128
+ }
129
+ lang=nonull(lang, PREFERED_LANGUAGE);
130
+ }
148
131
 
132
+ if(contains(["capitalize","append"],lang)){
133
+ // TRACE
134
+ console.log("WARN : Language parameter has for value a reserved attribute keyword, using default language «"+PREFERED_LANGUAGE+"»");
135
+ lang=PREFERED_LANGUAGE;
136
+ }
149
137
 
150
- }
151
-
152
- function encodeXORNoPattern(strToEncode,key){
153
- let result="";
154
- let keyChunk=key;
155
- let keyLength=keyChunk.length;
156
- let previousChars="";
157
- let cnt=0;
158
- for(let i=0;i<strToEncode.length;i++){
159
- let char=strToEncode.charAt(i);
160
- let keyByte=keyChunk.charAt(cnt);
138
+
139
+ let result=null;
140
+
141
+
142
+ if(!isString(languagesAndStrings)){ // Case direct i18n translation, with no translation keys :
161
143
 
162
- result+=String.fromCharCode(char.charCodeAt(0)^keyByte.charCodeAt(0));
144
+ result=languagesAndStrings[lang];
145
+
146
+ if (!result){
147
+ // TRACE
148
+ console.log("ERROR : No label found for language «"+lang+"»!",languagesAndStrings);
149
+ }
150
+
151
+ if (!result){
152
+ for(key in languagesAndStrings){
153
+ if (!languagesAndStrings.hasOwnProperty(key))
154
+ continue;
155
+ // We take the first that we find, in the other languages:
156
+ result=languagesAndStrings[key];
157
+ break;
158
+ }
159
+ }
160
+
161
+ if (!result) return NO_TRANSLATION_LABEL;
163
162
 
164
- if(cnt%keyLength===(keyLength-1)){
165
- cnt=0;
166
- // NO : TOO LONG TREATMENT !: We hash with all the previous blocks too :
167
- // We hash with a character of the previous chunk (they add up at each new chunk):
168
- keyChunk=getHashedString(previousChars+keyChunk);
169
163
 
170
- previousChars+=keyChunk.charAt(keyChunk.length-1);
171
- }else{
172
- cnt++;
164
+ // We add this «rogue» usage of i18n to the whole repository at all useful ends :
165
+ if(ADD_SYSTEMATICALLY_TO_REPOSITORY){
166
+ // let declarable={};
167
+ let maxLength=Math.min(result.length, MAX_CHARACTERS_FOR_TRANSLATION_KEY);
168
+ let key=toConvention("camel", result.substring(0,maxLength).toLowerCase() );
169
+ // let calculatedKey=radicalKey+"_"+getUUID("short");
170
+ // declarable[calculatedKey]=languagesAndStrings;
171
+ i18n.declareSingleEntry(key, languagesAndStrings);
173
172
  }
173
+
174
+ // Eventual post-treatments :
175
+ if(languagesAndStrings.capitalize) result=capitalize(result);
176
+ if(languagesAndStrings.append) result+=languagesAndStrings.append;
177
+
178
+ return result;
174
179
  }
175
- return result;
176
- }
180
+ // else // Case we use translation keys : (here, languagesAndStrings parameter holds just the wished translation key)
177
181
 
178
- function generateRandomString(length,/*NULLABLE*/mode){
179
182
 
180
- // This list must be very conservative, because we want to be able to pass it through GET URLs !
181
- // OLD (not enough conservative ?) : const ALLOWED_CHARS="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0912346789_-£¢¤¬²³¼½¾";
182
- const ALLOWED_CHARS="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0912346789!-:;";
183
+ let repository=(window?window:global)[I18N_REPOSITORY_VARIABLE_NAME];
184
+ if(!repository){
185
+ // TRACE
186
+ console.log("ERROR : No repository variable found for repository «"+I18N_REPOSITORY_VARIABLE_NAME+"»!");
187
+ return NO_TRANSLATION_LABEL;
188
+ }
183
189
 
190
+ // OLD (ie. splitted format :)
191
+ // let repositoryForLang=repository[lang];
192
+ // if (!repositoryForLang){
193
+ // // TRACE
194
+ // console.log("ERROR : No translation found for language «"+lang+"»!",languagesAndStrings);
195
+ // return NO_TRANSLATION_LABEL;
196
+ // }
197
+ // result=repositoryForLang[languagesAndStrings]; // Here, languagesAndStrings is just a bundle key.
198
+ // if (!result) return NO_TRANSLATION_LABEL+" (for key «"+languagesAndStrings+"»)";
184
199
 
185
- if(typeof(length)==="string"){
186
- if(contains(length,"->")){
187
- length=Math.getRandomInRange(length);
188
- }else{
189
- length=64;
190
- }
200
+ let languagesAndStringsForTranslationKey=repository[languagesAndStrings];
201
+ if (!languagesAndStringsForTranslationKey){
202
+ // SILENT ERROR :
203
+ // // TRACE
204
+ // console.log("ERROR : No translation found for key «"+languagesAndStrings+"» for language «"+lang+"»!");
205
+ // NO, OLD : return NO_TRANSLATION_LABEL;
206
+ return languagesAndStrings;
191
207
  }
208
+ result=i18n(languagesAndStringsForTranslationKey, lang); // Since we use the same format !
192
209
 
193
- var result="";
210
+ return result;
211
+ };
194
212
 
195
- if(mode==="simple"){
196
-
197
- for(var i=0;i<length;i++){
198
- var value=Math.getRandomInArray(ALLOWED_CHARS);
199
- result+=value;
200
- }
201
-
202
- return result;
203
- }else if(mode==="human"){
204
-
205
- const markovGraph={
206
- // For simplicity sakes, we ignore the consonants and vowels doubling possibilities:
207
- consonants:{// They always accept vowels at their following
208
- // Indicate ^ to specify wich of the other group letter to exclude as direct follower of the given letter :
209
- "b":["r","l"],
210
- "c":["h","t","k","l","r"],
211
- "d":["r"],
212
- "f":["r","l"],
213
- "g":["r","l"],
214
- "h":[],
215
- "j":["r"],
216
- "k":["r","l","t","h"],
217
- "l":["t"],
218
- "m":["b","p"],
219
- "n":["t","d"],
220
- "p":["h","r","s","t","l"],
221
- "q":["r","l"],
222
- "r":["t","c","d","k","q","h"],
223
- "s":["t","c","d","k","q","h"],
224
- "t":["r","h"],
225
- "v":["r","l"],
226
- "w":["r"],
227
- "x":["p"],
228
- "z":["r","h"],
229
- },
230
- vowels:{// They always accept consonants at their following
231
- "a":["i","u","y"],
232
- "e":["i","u","y"],
233
- "i":["e","o"],
234
- "o":["i","u"],
235
- "u":["i"],
236
- "y":[],
237
- },
238
- };
239
-
240
- const LETTERS_TYPES=Object.keys(markovGraph);
241
-
242
- const CURR_LETTER_MODE=Math.getRandomInArray(LETTERS_TYPES);
243
- const POSSIBLE_LETTERS=Object.keys(markovGraph[CURR_LETTER_MODE]);
244
- const CURR_LETTER=Math.getRandomInArray(POSSIBLE_LETTERS);
245
-
246
- var automataState={
247
- currentLetterMode:CURR_LETTER_MODE,
248
- possibleLetters:POSSIBLE_LETTERS,
249
- currentLetter:CURR_LETTER,
250
-
251
- calculateLetter:function(){
252
-
253
- this.currentLetter=Math.getRandomInArray(this.possibleLetters);
254
-
255
- var sameLetters=Object.keys( markovGraph[this.currentLetterMode] );
256
- if(!contains(sameLetters,this.currentLetter)){
257
- // If we picked from the same group : we don't change of group to pick
258
- // If we picked from the other group : we do change of group to pick
259
-
260
- var otherLetterMode=foreach(LETTERS_TYPES,(item)=>{
261
- if(item!==this.currentLetterMode) return item;
262
- });
263
-
264
- this.currentLetterMode=otherLetterMode;
265
- }
266
-
267
- },
268
-
269
- updatePossibleLettersForLetter:function(){
270
-
271
- var nextPossibleLetters=[];
272
- var nextNotPossibleLetters=[];
273
-
274
- // We store those of the other group marked «^»
275
- foreach(markovGraph[this.currentLetterMode][this.currentLetter],(letter)=>{
276
- if(letter.charAt(0)==="^"){
277
- nextNotPossibleLetters.push(letter.charAt(1));
278
- }else{
279
- nextPossibleLetters.push(letter);
280
- }
281
- });
282
-
283
- var otherLetterMode=foreach(LETTERS_TYPES,(item)=>{
284
- if(item!==this.currentLetterMode) return item;
285
- });
286
- var otherLetters=Object.keys(markovGraph[otherLetterMode]);
287
-
288
- // We exclude those marked «^»
289
- foreach( otherLetters ,(letter)=>{
290
- if(!contains(nextNotPossibleLetters,letter)){
291
- nextPossibleLetters.push(letter);
292
- }
293
- });
294
-
295
- this.possibleLetters=nextPossibleLetters;
296
- }
297
- };
298
-
299
- var i=0;
300
- do{
301
213
 
302
- result+=automataState.currentLetter;
303
-
304
- automataState.updatePossibleLettersForLetter();
305
- automataState.calculateLetter();
306
-
307
- i++;
308
- }while(i<length);
309
-
310
- return result;
214
+ i18n.declareSingleEntry=function(key, languagesAndStringsEntry){
215
+ let newRepository;
216
+ let foundRepository=(window?window:global)[I18N_REPOSITORY_VARIABLE_NAME];
217
+ if(!foundRepository){
218
+ // TRACE
219
+ console.log("ERROR : No repository variable found for repository «"+I18N_REPOSITORY_VARIABLE_NAME+"»!");
220
+ return;
311
221
  }
312
222
 
313
- for(var i=0;i<length;i++){
314
- var value=Math.getRandomInt(255,0);
315
- result+=String.fromCharCode(value);
223
+ let foundEntryForKey=foundRepository[key];
224
+ if(!foundEntryForKey){
225
+ foundRepository[key]=languagesAndStringsEntry;
226
+ // }else{
227
+ // // TRACE
228
+ // console.log("WARN : Duplicate entry for translation key «"+key+"», keeping only the first declared :",foundEntryForKey);
316
229
  }
317
230
 
318
- if(mode==="base64"){
319
- return btoa(result);
231
+ };
232
+
233
+
234
+ //USAGE :
235
+ //Example :
236
+ //i18n.declare({
237
+ // "commencer":{"fr":"Commencer","en":"Start"}
238
+ //});
239
+ i18n.declare=function(repositoryInfos){
240
+ let newRepository;
241
+ let foundRepository=(window?window:global)[I18N_REPOSITORY_VARIABLE_NAME];
242
+ if(foundRepository){
243
+ // // TRACE
244
+ // console.log("WARN : A repository has already been declared, merging.",foundRepository);
245
+ newRepository=merge(foundRepository, repositoryInfos);
246
+ }else{
247
+ newRepository=repositoryInfos;
320
248
  }
249
+ (window?window:global)[I18N_REPOSITORY_VARIABLE_NAME]=newRepository;
250
+ };
251
+
252
+ // USAGE :
253
+ // Example :
254
+ //i18n.declareSplitted({
255
+ // "fr":{
256
+ // "commencer":"Commencer",
257
+ // },
258
+ // "en":{
259
+ // "commencer":"Start",
260
+ // },
261
+ //});
262
+ i18n.declareSplitted=function(repositoryInfos){
263
+ let nonSplittedFormat={};
321
264
 
322
- return result;
323
- }
265
+ foreach(repositoryInfos,(allKeys, lang)=>{
266
+ foreach(allKeys,(text, key)=>{
267
+
268
+ let foundKeyInfos=nonSplittedFormat[key];
269
+ if(!foundKeyInfos){
270
+ nonSplittedFormat[key]={};
271
+ foundKeyInfos=nonSplittedFormat[key];
272
+ }
273
+
274
+ let foundTextForLanguage=foundKeyInfos[lang];
275
+ if(foundTextForLanguage){
276
+ // TRACE
277
+ console.log("WARN : Duplicate entry for translation key «"+key+"» for language «"+lang+"», keeping only the first declared :",foundTextForLanguage);
278
+ }else{
279
+ foundKeyInfos[lang]=text;
280
+ }
281
+
282
+ });
283
+ });
324
284
 
285
+ // Then we use the standard declaration method :
286
+ i18n.declare(nonSplittedFormat);
287
+ };
325
288
 
289
+ // For generating languages files helping purposes :
290
+ i18n.displayAll=function(displaySingleEntriesCalls=false){
291
+ let foundRepository=(window?window:global)[I18N_REPOSITORY_VARIABLE_NAME];
292
+ if(!foundRepository){
293
+ // TRACE
294
+ console.log("ERROR : No repository found under the variable name «"+I18N_REPOSITORY_VARIABLE_NAME+"»");
295
+ }else{
296
+ console.log("TRACE : Variable under the name of «"+I18N_REPOSITORY_VARIABLE_NAME+"»:", foundRepository);
297
+ console.log("\n\n");
326
298
 
327
- // NOT AOTESTABLE !
328
- function getUniqueIdWithDate(){
329
- return getHashedString(getNow() + "");
299
+ let message="";
300
+ foreach(foundRepository, (entry, entryKey)=>{
301
+ let normalizedEntry={};
302
+ if(entry.capitalize) normalizedEntry.capitalize=entry.capitalize;
303
+ if(entry.append) normalizedEntry.append=entry.append;
304
+ foreach(entry,(e,k)=>{
305
+ normalizedEntry[k]=e;
306
+ }
307
+ ,(i,key)=>{ return (!contains(["append","capitalize"],key)); }
308
+ ,(i1,i2)=>{ return (i1.key==i2.key?0:(i1.key=="fr"?-1:1));/*(Sorry, French always comes first !)*/}
309
+ );
310
+ let entryStr=stringifyObject(normalizedEntry);
311
+ message+="\""+entryKey+"\":"+entryStr+",\n";
312
+ if(displaySingleEntriesCalls) message+="// i18n("+entryStr+");\n";
313
+ });
314
+
315
+ console.log("\n"+message);
316
+
317
+ }
330
318
  }
331
319
 
332
320
 
333
321
 
334
- // NOT AOTESTABLE !
335
322
 
336
- //Works, but doesn't compile under PhoneGap...:
337
- //function getURLParameter(nameParam){
338
- //var name=encodeURIComponent(nameParam);
339
- //return decodeURIComponent(
340
- //(
341
- //new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)' )
342
- //.exec(window.location.search)
343
- //|| [,""]
344
- //)[1]
345
- //.replace(/\+/g, '%20')
346
- //)||null;
347
- //}
348
323
 
349
- //...so we replace it with another function that is supposed to do the same
350
- //thing :
351
- function getURLParameter(nameParam){
352
- var name=nameParam;
353
- if (name=new RegExp("[?&]" + encodeURIComponent(name) + "=([^&]*)").exec(location.search))
354
- return decodeURIComponent(name[1]);
355
- return null;
356
- }
324
+ // -------------------------------------------------------------------------------------------
357
325
 
326
+ // Mouse management :
358
327
 
328
+ // Code from (www.adomas.org/javascript-mouse-wheel)
329
+ function initMouseWheel(handle,/* OPTIONAL */filterFunction){
359
330
 
331
+ var wheel=function(event){
360
332
 
361
- // A little method to help i18ning aotra user interface :
362
- const I18N_REPOSITORY_VARIABLE_NAME="I18N_KEYS";
363
- function i18n(languagesAndStrings, langParam=null){
364
-
365
- // ******************* CONFIG *******************
366
- const PREFERED_LANGUAGE="fr"; // My Quebecer wife (love on her for ever and beyond) forced me to set this...(I'm French.)
367
-
368
- const NO_TRANSLATION_LABEL="NO TRANSLATION FOUND";
369
-
370
- const ADD_SYSTEMATICALLY_TO_REPOSITORY=true;
371
- const MAX_CHARACTERS_FOR_TRANSLATION_KEY=64; // Really if you have more than 64 characters in commons, then it really should be the same string !
333
+ if (filterFunction && !filterFunction(event.target))
334
+ return;
372
335
 
373
- // ***************** END CONFIG *****************
374
-
375
-
376
- if(!languagesAndStrings){
377
- // TRACE
378
- console.log("ERROR : No parameter for i18n provided!",languagesAndStrings);
379
- return NO_TRANSLATION_LABEL;
380
- }
381
-
382
- let lang=null;
383
- if(langParam){
384
- lang=langParam;
385
- }else{
386
- // Parameters priority :
387
- if(window){
388
- if(window.getURLParameter){
389
- lang=nonull(window.getURLParameter("language"), window.getURLParameter("lang"));
390
- }
391
- if(!lang && (window.language || window.lang)){
392
- lang=nonull(window.language, window.lang);
393
- }
394
- }else if(global){ // (NodeJS compatibility)
395
- if(global.getURLParameter){
396
- lang=nonull(global.getURLParameter("language"), global.getURLParameter("lang"));
397
- }
398
- if(!lang && (global.language || global.lang)){
399
- lang=nonull(global.language, global.lang);
400
- }
401
- }
402
- lang=nonull(lang, PREFERED_LANGUAGE);
403
- }
404
-
405
- if(contains(["capitalize","append"],lang)){
406
- // TRACE
407
- console.log("WARN : Language parameter has for value a reserved attribute keyword, using default language «"+PREFERED_LANGUAGE+"»");
408
- lang=PREFERED_LANGUAGE;
409
- }
410
-
411
-
412
- let result=null;
413
-
414
-
415
- if(!isString(languagesAndStrings)){ // Case direct i18n translation, with no translation keys :
416
-
417
- result=languagesAndStrings[lang];
418
-
419
- if (!result){
420
- // TRACE
421
- console.log("ERROR : No label found for language «"+lang+"»!",languagesAndStrings);
422
- }
423
-
424
- if (!result){
425
- for(key in languagesAndStrings){
426
- if (!languagesAndStrings.hasOwnProperty(key))
427
- continue;
428
- // We take the first that we find, in the other languages:
429
- result=languagesAndStrings[key];
430
- break;
431
- }
432
- }
433
-
434
- if (!result) return NO_TRANSLATION_LABEL;
435
-
436
-
437
- // We add this «rogue» usage of i18n to the whole repository at all useful ends :
438
- if(ADD_SYSTEMATICALLY_TO_REPOSITORY){
439
- // let declarable={};
440
- let maxLength=Math.min(result.length, MAX_CHARACTERS_FOR_TRANSLATION_KEY);
441
- let key=toConvention("camel", result.substring(0,maxLength).toLowerCase() );
442
- // let calculatedKey=radicalKey+"_"+getUUID("short");
443
- // declarable[calculatedKey]=languagesAndStrings;
444
- i18n.declareSingleEntry(key, languagesAndStrings);
445
- }
446
-
447
- // Eventual post-treatments :
448
- if(languagesAndStrings.capitalize) result=capitalize(result);
449
- if(languagesAndStrings.append) result+=languagesAndStrings.append;
450
-
451
- return result;
452
- }
453
- // else // Case we use translation keys : (here, languagesAndStrings parameter holds just the wished translation key)
454
-
455
-
456
- let repository=(window?window:global)[I18N_REPOSITORY_VARIABLE_NAME];
457
- if(!repository){
458
- // TRACE
459
- console.log("ERROR : No repository variable found for repository «"+I18N_REPOSITORY_VARIABLE_NAME+"»!");
460
- return NO_TRANSLATION_LABEL;
461
- }
462
-
463
- // OLD (ie. splitted format :)
464
- // let repositoryForLang=repository[lang];
465
- // if (!repositoryForLang){
466
- // // TRACE
467
- // console.log("ERROR : No translation found for language «"+lang+"»!",languagesAndStrings);
468
- // return NO_TRANSLATION_LABEL;
469
- // }
470
- // result=repositoryForLang[languagesAndStrings]; // Here, languagesAndStrings is just a bundle key.
471
- // if (!result) return NO_TRANSLATION_LABEL+" (for key «"+languagesAndStrings+"»)";
472
-
473
- let languagesAndStringsForTranslationKey=repository[languagesAndStrings];
474
- if (!languagesAndStringsForTranslationKey){
475
- // SILENT ERROR :
476
- // // TRACE
477
- // console.log("ERROR : No translation found for key «"+languagesAndStrings+"» for language «"+lang+"»!");
478
- // NO, OLD : return NO_TRANSLATION_LABEL;
479
- return languagesAndStrings;
480
- }
481
- result=i18n(languagesAndStringsForTranslationKey, lang); // Since we use the same format !
482
-
483
- return result;
484
- };
485
-
486
-
487
- i18n.declareSingleEntry=function(key, languagesAndStringsEntry){
488
- let newRepository;
489
- let foundRepository=(window?window:global)[I18N_REPOSITORY_VARIABLE_NAME];
490
- if(!foundRepository){
491
- // TRACE
492
- console.log("ERROR : No repository variable found for repository «"+I18N_REPOSITORY_VARIABLE_NAME+"»!");
493
- return;
494
- }
495
-
496
- let foundEntryForKey=foundRepository[key];
497
- if(!foundEntryForKey){
498
- foundRepository[key]=languagesAndStringsEntry;
499
- // }else{
500
- // // TRACE
501
- // console.log("WARN : Duplicate entry for translation key «"+key+"», keeping only the first declared :",foundEntryForKey);
502
- }
503
-
504
- };
505
-
506
-
507
- //USAGE :
508
- //Example :
509
- //i18n.declare({
510
- // "commencer":{"fr":"Commencer","en":"Start"}
511
- //});
512
- i18n.declare=function(repositoryInfos){
513
- let newRepository;
514
- let foundRepository=(window?window:global)[I18N_REPOSITORY_VARIABLE_NAME];
515
- if(foundRepository){
516
- // // TRACE
517
- // console.log("WARN : A repository has already been declared, merging.",foundRepository);
518
- newRepository=merge(foundRepository, repositoryInfos);
519
- }else{
520
- newRepository=repositoryInfos;
521
- }
522
- (window?window:global)[I18N_REPOSITORY_VARIABLE_NAME]=newRepository;
523
- };
524
-
525
- // USAGE :
526
- // Example :
527
- //i18n.declareSplitted({
528
- // "fr":{
529
- // "commencer":"Commencer",
530
- // },
531
- // "en":{
532
- // "commencer":"Start",
533
- // },
534
- //});
535
- i18n.declareSplitted=function(repositoryInfos){
536
- let nonSplittedFormat={};
537
-
538
- foreach(repositoryInfos,(allKeys, lang)=>{
539
- foreach(allKeys,(text, key)=>{
540
-
541
- let foundKeyInfos=nonSplittedFormat[key];
542
- if(!foundKeyInfos){
543
- nonSplittedFormat[key]={};
544
- foundKeyInfos=nonSplittedFormat[key];
545
- }
546
-
547
- let foundTextForLanguage=foundKeyInfos[lang];
548
- if(foundTextForLanguage){
549
- // TRACE
550
- console.log("WARN : Duplicate entry for translation key «"+key+"» for language «"+lang+"», keeping only the first declared :",foundTextForLanguage);
551
- }else{
552
- foundKeyInfos[lang]=text;
553
- }
554
-
555
- });
556
- });
557
-
558
- // Then we use the standard declaration method :
559
- i18n.declare(nonSplittedFormat);
560
- };
561
-
562
- // For generating languages files helping purposes :
563
- i18n.displayAll=function(displaySingleEntriesCalls=false){
564
- let foundRepository=(window?window:global)[I18N_REPOSITORY_VARIABLE_NAME];
565
- if(!foundRepository){
566
- // TRACE
567
- console.log("ERROR : No repository found under the variable name «"+I18N_REPOSITORY_VARIABLE_NAME+"»");
568
- }else{
569
- console.log("TRACE : Variable under the name of «"+I18N_REPOSITORY_VARIABLE_NAME+"»:", foundRepository);
570
- console.log("\n\n");
571
-
572
- let message="";
573
- foreach(foundRepository, (entry, entryKey)=>{
574
- let normalizedEntry={};
575
- if(entry.capitalize) normalizedEntry.capitalize=entry.capitalize;
576
- if(entry.append) normalizedEntry.append=entry.append;
577
- foreach(entry,(e,k)=>{
578
- normalizedEntry[k]=e;
579
- }
580
- ,(i,key)=>{ return (!contains(["append","capitalize"],key)); }
581
- ,(i1,i2)=>{ return (i1.key==i2.key?0:(i1.key=="fr"?-1:1));/*(Sorry, French always comes first !)*/}
582
- );
583
- let entryStr=stringifyObject(normalizedEntry);
584
- message+="\""+entryKey+"\":"+entryStr+",\n";
585
- if(displaySingleEntriesCalls) message+="// i18n("+entryStr+");\n";
586
- });
587
-
588
- console.log("\n"+message);
589
-
590
- }
591
- }
592
-
593
-
594
-
595
-
596
-
597
- // -------------------------------------------------------------------------------------------
598
-
599
- // Mouse management :
600
-
601
- // Code from (www.adomas.org/javascript-mouse-wheel)
602
- function initMouseWheel(handle,/* OPTIONAL */filterFunction){
603
-
604
- var wheel=function(event){
605
-
606
- if (filterFunction && !filterFunction(event.target))
607
- return;
608
-
609
- var delta=0;
610
- if (!event)
611
- event=window.event;
612
- if (event.wheelDelta){
613
- delta=event.wheelDelta / 120;
614
- } else if (event.detail){
615
- delta=-event.detail / 3;
616
- }
617
- if (delta)
618
- handle(delta);
619
- if (event.preventDefault)
620
- event.preventDefault();
621
- event.returnValue=false;
622
- };
336
+ var delta=0;
337
+ if (!event)
338
+ event=window.event;
339
+ if (event.wheelDelta){
340
+ delta=event.wheelDelta / 120;
341
+ } else if (event.detail){
342
+ delta=-event.detail / 3;
343
+ }
344
+ if (delta)
345
+ handle(delta);
346
+ if (event.preventDefault)
347
+ event.preventDefault();
348
+ event.returnValue=false;
349
+ };
623
350
 
624
351
  /* Initialization code. */
625
352
  if (window.addEventListener){
@@ -6782,6 +6509,9 @@ initClient=function(isNodeContext=true, useSocketIOImplementation=false, doOnSer
6782
6509
  aotraClient.client={};
6783
6510
  aotraClient.client.start=function(){
6784
6511
 
6512
+ // DBG
6513
+ lognow("INFO : Setting up Websocket implementation server :...");
6514
+
6785
6515
 
6786
6516
  let socketToServer=WebsocketImplementation.getStatic(isNodeContext,useSocketIOImplementation).connectToServer(url, port, isSecure, timeout);
6787
6517
  if(!socketToServer){
@@ -7121,7 +6851,7 @@ createFusrodaClient=function(doOnClientReady, doOnDataReception, urlParam=null,
7121
6851
 
7122
6852
 
7123
6853
 
7124
- /*utils GEOMETRY library associated with aotra version : «1.0.0.000 (11/07/2022-23:49:31)»*/
6854
+ /*utils GEOMETRY library associated with aotra version : «1.0.0.000 (12/07/2022-23:38:27)»*/
7125
6855
  /*-----------------------------------------------------------------------------*/
7126
6856
 
7127
6857
 
@@ -7955,368 +7685,1670 @@ Math.compareDistances=function(p1, p2, distance, comparison){
7955
7685
  return false;
7956
7686
  };
7957
7687
 
7958
- Math.getSquaredDistance=function(p1, p2){
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);
7960
- };
7688
+ Math.getSquaredDistance=function(p1, p2){
7689
+ 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);
7690
+ };
7691
+
7692
+ Math.getDistance=function(p1, p2){
7693
+ return Math.sqrt(Math.getSquaredDistance(p1,p2));
7694
+ };
7695
+
7696
+
7697
+
7698
+
7699
+ Math.getBarycenter=function(coordsArray){
7700
+
7701
+ let coordsX=[];
7702
+ let coordsY=[];
7703
+ foreach(coordsArray,(coords)=>{
7704
+ coordsX.push(coords.x);
7705
+ coordsY.push(coords.y);
7706
+ });
7707
+
7708
+ // Barycenters calculation :
7709
+ let bx=Math.floor(Math.averageInArray(coordsX));
7710
+ let by=Math.floor(Math.averageInArray(coordsY));
7711
+
7712
+
7713
+ return {x:bx,y:by};
7714
+ }
7715
+
7716
+
7717
+ Math.getBarycentersFromZonesWithColors=function(zonesWithColors, withColors=true){
7718
+ let barycenters=[];
7719
+
7720
+ foreach(zonesWithColors,(singleContigousZonePixelsCoords)=>{
7721
+ let barycenter=Math.getBarycenter(singleContigousZonePixelsCoords);
7722
+ barycenters.push(barycenter);
7723
+
7724
+ let zoneLength=singleContigousZonePixelsCoords.length;
7725
+
7726
+ if(withColors){
7727
+ // barycenter color :
7728
+ let avgR=0;
7729
+ let avgG=0;
7730
+ let avgB=0;
7731
+ foreach(singleContigousZonePixelsCoords,(coords)=>{
7732
+ let color=coords.color;
7733
+ avgR+=color.r;
7734
+ avgG+=color.g;
7735
+ avgB+=color.b;
7736
+ });
7737
+ barycenter.color={r:avgR/zoneLength, g:avgG/zoneLength, b:avgB/zoneLength};
7738
+ }else{
7739
+ barycenter.color={r:128, g:128, b:128};
7740
+ }
7741
+
7742
+ });
7743
+
7744
+ return barycenters;
7745
+ }
7746
+
7747
+
7748
+ Math.getBidimensional8Direction=(currentPosition,destination,invertYAxis=false)=>{
7749
+
7750
+ let x=currentPosition.x;
7751
+ let y=currentPosition.y;
7752
+
7753
+ let deltaX=(destination.x-x);
7754
+ let deltaY=(invertYAxis?-1:1)*(destination.y-y);
7755
+
7756
+ if(deltaX===0 && deltaY===0) return null;
7757
+
7758
+ let directionXAxis=(deltaX<0?"W":"E");
7759
+ let directionYAxis=(deltaY<0?"S":"N");
7760
+ if(deltaX===0) return directionYAxis;
7761
+ if(deltaY===0) return directionXAxis;
7762
+
7763
+ let tanRatio=(deltaY/deltaX);
7764
+
7765
+ if(0<deltaX && 0<deltaY){ // 0 -> PI/2 quarter :
7766
+ if(tanRatio<=Math.TAN_RATIOS_DEGREES[22.5]) return "E";
7767
+ if(Math.TAN_RATIOS_DEGREES[22.5]<=tanRatio && tanRatio<Math.TAN_RATIOS_DEGREES[67.5]) return "NE";
7768
+ if(Math.TAN_RATIOS_DEGREES[67.5]<=tanRatio) return "N";
7769
+ }else if(deltaX<0 && 0<deltaY){ // PI/2 -> PI quarter :
7770
+ if(tanRatio<=Math.TAN_RATIOS_DEGREES[112.5]) return "N";
7771
+ if(Math.TAN_RATIOS_DEGREES[112.5]<=tanRatio && tanRatio<Math.TAN_RATIOS_DEGREES[157.5]) return "NW";
7772
+ if(Math.TAN_RATIOS_DEGREES[157.5]<=tanRatio) return "W";
7773
+ }else if(deltaX<0 && deltaY<0){ // PI -> (3/2)*PI quarter :
7774
+ if(tanRatio<=Math.TAN_RATIOS_DEGREES[202.5]) return "W";
7775
+ if(Math.TAN_RATIOS_DEGREES[202.5]<=tanRatio && tanRatio<Math.TAN_RATIOS_DEGREES[247.5]) return "SW";
7776
+ if(Math.TAN_RATIOS_DEGREES[247.5]<=tanRatio) return "S";
7777
+ }else{ // (3/2)*PI -> 2*PI quarter :
7778
+ if(tanRatio<=Math.TAN_RATIOS_DEGREES[292.5]) return "S";
7779
+ if(Math.TAN_RATIOS_DEGREES[292.5]<=tanRatio && tanRatio<Math.TAN_RATIOS_DEGREES[337.5]) return "SE";
7780
+ if(Math.TAN_RATIOS_DEGREES[337.5]<=tanRatio) return "E";
7781
+ }
7782
+ return null;
7783
+ };
7784
+
7785
+
7786
+
7787
+ /*public static*/window.getRandomPositionInZone=function(positionZone, xOffset=0, yOffset=0, zOffset=0, avoidOverlap=null, size=null, randomizeAngles=false){
7788
+
7789
+ let w=nonull(positionZone.w,0);
7790
+ let h=nonull(positionZone.h,0);
7791
+ let d=nonull(positionZone.d,0);
7792
+
7793
+ let demiW=w*.5;
7794
+ let demiH=h*.5;
7795
+ let demiD=d*.5;
7796
+
7797
+ let xOrZero=nonull(positionZone.x,0);
7798
+ let yOrZero=nonull(positionZone.y,0);
7799
+ let zOrZero=nonull(positionZone.z,0);
7800
+
7801
+ // If respectively width or height is 0, then there is no need to use a randomization !
7802
+ let x= ( w<=0 ? xOrZero : (xOrZero + Math.getRandomInt(Math.floor(demiW), -Math.ceil(demiW)))) + xOffset;
7803
+ let y= ( h<=0 ? yOrZero : (yOrZero + Math.getRandomInt(Math.floor(demiH), -Math.ceil(demiH)))) + yOffset;
7804
+ let z= ( d<=0 ? zOrZero : (zOrZero + Math.getRandomInt(Math.floor(demiD), -Math.ceil(demiD)))) + zOffset;
7805
+
7806
+ if(avoidOverlap && size){
7807
+
7808
+ let itemW=nonull(size.w,0);
7809
+ let itemH=nonull(size.h,0);
7810
+ let itemD=nonull(size.d,0);
7811
+
7812
+ if(xOrZero/*includes case==0*/ && 0<itemW && contains(avoidOverlap,"x")) x=Math.floor(x/itemW)*itemW;
7813
+ if(yOrZero/*includes case==0*/ && 0<itemH && contains(avoidOverlap,"y")) y=Math.floor(y/itemH)*itemH;
7814
+ if(zOrZero/*includes case==0*/ && 0<itemD && contains(avoidOverlap,"z")) z=Math.floor(z/itemD)*itemD;
7815
+
7816
+ }
7817
+
7818
+ let b=positionZone.b;
7819
+ let g=positionZone.g;
7820
+ let a=positionZone.a;
7821
+ if(randomizeAngles){
7822
+ b=Math.random()*Math.PI*2;
7823
+ g=Math.random()*Math.PI*2;
7824
+ a=Math.random()*Math.PI*2;
7825
+ }
7826
+
7827
+ return {x:x, y:y, z:z, b:b, g:g, a:a, parallax:positionZone.parallax};
7828
+ }
7829
+
7830
+
7831
+ // CAUTION : ONLY HANDLES «center-positionned» ZONES !
7832
+ /*public static*/window.isZoneCollidingWithZone=function(zone1, zone2){
7833
+
7834
+ let demiWidth1=zone1.w*.5;
7835
+ let demiWidth2=zone2.w*.5;
7836
+
7837
+ let xMin1=zone1.x-demiWidth1;
7838
+ let xMax1=zone1.x+demiWidth1;
7839
+ let xMin2=zone2.x-demiWidth2;
7840
+ let xMax2=zone2.x+demiWidth2;
7841
+
7842
+ //OLD : let widthsOverlap=((xMin2<xMin1 && xMin1<xMax2) || (xMin2<xMax1 && xMax1<xMax2)
7843
+ // || (xMin1<xMin2 && xMin2<xMax1) || (xMin1<xMax2 && xMax2<xMax1));
7844
+
7845
+ let widthsOverlap=(xMin1<xMax2 && xMin2<xMax1);
7846
+
7847
+
7848
+ let demiHeight1=zone1.h*.5;
7849
+ let demiHeight2=zone2.h*.5;
7850
+
7851
+ let yMin1=zone1.y-demiHeight1;
7852
+ let yMax1=zone1.y+demiHeight1;
7853
+ let yMin2=zone2.y-demiHeight2;
7854
+ let yMax2=zone2.y+demiHeight2;
7855
+
7856
+ //OLD : let heightsOverlap=((yMin1<yMin2 && yMin2<yMax1) || (yMin1<yMax2 && yMax2<yMax1)
7857
+ // || (yMin2<yMin1 && yMin1<yMax2) || (yMin2<yMax1 && yMax1<yMax2));
7858
+
7859
+ let heightsOverlap=(yMin1<yMax2 && yMin2<yMax1);
7860
+
7861
+ return widthsOverlap && heightsOverlap;
7862
+ }
7863
+
7864
+
7865
+
7866
+ // CAUTION : ONLY HANDLES «center-positionned» ZONES !
7867
+ /*public static*/window.getIntersectionZone=function(zone1, zone2){
7868
+ if(!isZoneCollidingWithZone(zone1, zone2)) return null;
7869
+
7870
+ let edges=[];
7871
+
7872
+
7873
+ // Horizontal axis :
7874
+ let hasXAxisWholeZone1=false;
7875
+ let hasXAxisWholeZone2=false;
7876
+
7877
+ let demiWidth1=zone1.w*.5;
7878
+ let demiWidth2=zone2.w*.5;
7879
+ let xMin1=zone1.x-demiWidth1;
7880
+ let xMax1=zone1.x+demiWidth1;
7881
+ let xMin2=zone2.x-demiWidth2;
7882
+ let xMax2=zone2.x+demiWidth2;
7883
+
7884
+ let deltaXMins=xMin2-xMin1;
7885
+ let deltaXMaxs=xMax2-xMax1;
7886
+
7887
+ let deltaXMinsSign=(deltaXMins<0?-1:1);
7888
+ let deltaXMaxsSign=(deltaXMaxs<0?-1:1);
7889
+ let intersectionXMin=0;
7890
+ let intersectionXMax=0;
7891
+ if(deltaXMinsSign==deltaXMaxsSign){ // case «parallelogram»
7892
+ if(deltaXMinsSign<0){ // case /=/ parallelogram
7893
+ intersectionXMin=xMin1;
7894
+ intersectionXMax=xMax2;
7895
+ // We calculate edges :
7896
+ edges.push("left");
7897
+ }else{ // case \=\ parallelogram
7898
+ intersectionXMin=xMin2;
7899
+ intersectionXMax=xMax1;
7900
+ // We calculate edges :
7901
+ edges.push("right");
7902
+ }
7903
+ }else{ // case «trapeze»
7904
+ if(deltaXMinsSign<0){ // case /=\ trapeze
7905
+ intersectionXMin=xMin1; // Basically zone1 x !
7906
+ intersectionXMax=xMax1;
7907
+ // We calculate edges :
7908
+ hasXAxisWholeZone1=true;
7909
+ }else{ // case \=/ trapeze
7910
+ intersectionXMin=xMin2; // Basically zone2 x !
7911
+ intersectionXMax=xMax2;
7912
+ // We calculate edges :
7913
+ hasXAxisWholeZone2=true;
7914
+ }
7915
+ }
7916
+
7917
+ let w=intersectionXMax-intersectionXMin;
7918
+ let x=(intersectionXMax+intersectionXMin)*.5; // (The average)
7919
+
7920
+
7921
+ // Vertical axis :
7922
+
7923
+ let hasYAxisWholeZone1=false;
7924
+ let hasYAxisWholeZone2=false;
7925
+
7926
+ let demiHeight1=zone1.h*.5;
7927
+ let demiHeight2=zone2.h*.5;
7928
+
7929
+ let yMin1=zone1.y-demiHeight1;
7930
+ let yMax1=zone1.y+demiHeight1;
7931
+ let yMin2=zone2.y-demiHeight2;
7932
+ let yMax2=zone2.y+demiHeight2;
7933
+
7934
+
7935
+ let deltaYMins=yMin2-yMin1;
7936
+ let deltaYMaxs=yMax2-yMax1;
7937
+
7938
+ let deltaYMinsSign=(deltaYMins<0?-1:1);
7939
+ let deltaYMaxsSign=(deltaYMaxs<0?-1:1);
7940
+ let intersectionYMin=0;
7941
+ let intersectionYMax=0;
7942
+ if(deltaYMinsSign==deltaYMaxsSign){ // case «parallelogram»
7943
+ if(deltaYMinsSign<0){ // case /=/ parallelogram
7944
+ intersectionYMin=yMin1;
7945
+ intersectionYMax=yMax2;
7946
+ // We calculate edges :
7947
+ edges.push("bottom"); // Here the Y axis is NOT inverted !
7948
+ }else{ // case \=\ parallelogram
7949
+ intersectionYMin=yMin2;
7950
+ intersectionYMax=yMax1;
7951
+ // We calculate edges :
7952
+ edges.push("top"); // Here the Y axis is NOT inverted !
7953
+ }
7954
+ }else{ // case «trapeze»
7955
+ if(deltaYMinsSign<0){ // case /=\ trapeze
7956
+ intersectionYMin=yMin1; // Basically zone2 y !
7957
+ intersectionYMax=yMax1;
7958
+ // We calculate edges :
7959
+ hasYAxisWholeZone1=true;
7960
+ }else{ // case \=/ trapeze
7961
+ intersectionYMin=yMin2; // Basically zone2 y !
7962
+ intersectionYMax=yMax2;
7963
+ // We calculate edges :
7964
+ hasYAxisWholeZone2=true;
7965
+ }
7966
+ }
7967
+
7968
+ let h=intersectionYMax-intersectionYMin;
7969
+ let y=(intersectionYMax+intersectionYMin)*.5; // (The average)
7970
+
7971
+ // We calculate edges :
7972
+ if(hasXAxisWholeZone1 && hasYAxisWholeZone1) edges.push("wholeZone1");
7973
+ if(hasXAxisWholeZone2 && hasYAxisWholeZone2) edges.push("wholeZone2");
7974
+
7975
+ return {x:x,y:y,w:w,h:h,edges:edges};
7976
+ }
7977
+
7978
+ /*public static*/window.areZonesIdentical=function(zone1, zone2){
7979
+ return (zone1==zone2 || (zone1.x==zone2.x || zone1.y==zone2.y || zone1.w==zone2.w || zone1.h==zone2.h));
7980
+ }
7981
+
7982
+
7983
+
7984
+ // 3D :
7985
+
7986
+
7987
+ /*
7988
+ // (needs the jsgl library file !)
7989
+ // UNUSED
7990
+ function screenToSpherePt(mouseX, mouseY) {
7991
+ var p = { x: camera_mat.e12, y: camera_mat.e13, z: camera_mat.e14 + 1 };
7992
+ // camera dir
7993
+ var r = { x: camera_mat.e0, y: camera_mat.e1, z: camera_mat.e2 };
7994
+ var up = { x: camera_mat.e4, y: camera_mat.e5, z: camera_mat.e6 };
7995
+ var right = { x: camera_mat.e8, y: camera_mat.e9, z: camera_mat.e10 };
7996
+ var tan_half = Math.tan(horizontal_fov_radians / 2);
7997
+ r = jsgl.vectorAdd(r, jsgl.vectorScale(right, mouseX * tan_half));
7998
+ r = jsgl.vectorAdd(r, jsgl.vectorScale(up, mouseY * tan_half));
7999
+ r = jsgl.vectorNormalize(r);
8000
+
8001
+ return rayVsUnitSphereClosestPoint(p, r);
8002
+ }
8003
+ */
8004
+
8005
+ /*
8006
+ // (needs the jsgl library file !)
8007
+ // UNUSED
8008
+ // Return the first exterior hit or closest point between the unit
8009
+ // sphere and the ray starting at p and going in the r direction.
8010
+ function rayVsUnitSphereClosestPoint(p, r) {
8011
+ var p_len2 = jsgl.dotProduct(p, p);
8012
+ if (p_len2 < 1) {
8013
+ // Ray is inside sphere, no exterior hit.
8014
+ return null;
8015
+ }
8016
+
8017
+ var along_ray = -jsgl.dotProduct(p, r);
8018
+ if (along_ray < 0) {
8019
+ // Behind ray start-point.
8020
+ return null;
8021
+ }
8022
+
8023
+ var perp = jsgl.vectorAdd(p, jsgl.vectorScale(r, along_ray));
8024
+ var perp_len2 = jsgl.dotProduct(perp, perp);
8025
+ if (perp_len2 >= 0.999999) {
8026
+ // Return the closest point.
8027
+ return jsgl.vectorNormalize(perp);
8028
+ }
8029
+
8030
+ // Compute intersection.
8031
+ var e = Math.sqrt(1 - jsgl.dotProduct(perp, perp));
8032
+ var hit = jsgl.vectorAdd(p, jsgl.vectorScale(r, (along_ray - e)));
8033
+ return jsgl.vectorNormalize(hit);
8034
+ }
8035
+ */
8036
+
8037
+
8038
+
8039
+
8040
+
8041
+
8042
+
8043
+
8044
+
8045
+
8046
+
8047
+
8048
+
8049
+
8050
+
8051
+
8052
+
8053
+
8054
+
8055
+
8056
+
8057
+ // MUST REMAIN AT THE END OF THIS LIBRARY FILE !
8058
+
8059
+ AOTRAUTILS_GEOMETRY_LIB_IS_LOADED=true;
8060
+ /*utils SERVER library associated with aotra version : «1.0.0.000 (12/07/2022-23:38:27)»*/
8061
+ /*-----------------------------------------------------------------------------*/
8062
+
8063
+
8064
+ /* ## Utility global methods in a javascript, console (nodejs) server, or vanilla javascript with no browser environment.
8065
+ *
8066
+ * This set of methods gathers utility generic-purpose methods usable in any JS project.
8067
+ * Several authors of snippets published freely on the Internet contributed to this library.
8068
+ * Feel free to use/modify-enhance/publish them under the terms of its license.
8069
+ *
8070
+ * # Library name : «aotrautils»
8071
+ * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
8072
+ * # Author name : Jérémie Ratomposon (massively helped by his native country free education system)
8073
+ * # Author email : info@alqemia.com
8074
+ * # Organization name : Alqemia
8075
+ * # Organization email : admin@alqemia.com
8076
+ * # Organization website : https://alqemia.com
8077
+ *
8078
+ *
8079
+ */
8080
+
8081
+
8082
+
8083
+ // COMPATIBILITY browser javascript / nodejs environment :
8084
+
8085
+ if(typeof(window)==="undefined") window=global;
8086
+
8087
+
8088
+
8089
+
8090
+ // =================================================================================
8091
+ // NODEJS UTILS
8092
+
8093
+ const FILE_ENCODING="utf8";
8094
+
8095
+
8096
+ // Nodejs filesystem utils :
8097
+ if(typeof(fs)==="undefined"){
8098
+ // TRACE
8099
+ console.log("WARN : Could not find the nodejs dependency «fs», aborting persister setup.");
8100
+ window.getPersister=()=>{ return null; };
8101
+
8102
+ }else{
8103
+
8104
+
8105
+ window.getPersister=function(dataDirPath,suffix=""){
8106
+
8107
+ let self={
8108
+
8109
+ dataDirPath:dataDirPath,
8110
+ suffix:suffix,
8111
+
8112
+ //FILE_NAME_PATTERN:"data.clientId.repositoryName.json",
8113
+ /*private*/getPath:function(clientId,repositoryName){
8114
+ // let path=self.FILE_NAME_PATTERN.replace(new RegExp("@clientId@","g"),clientId);
8115
+ let path=`${self.dataDirPath}`
8116
+ + (blank(self.suffix)?"":(self.suffix+"."))
8117
+ +`${clientId}.${repositoryName}.json`;
8118
+ return path;
8119
+ },
8120
+
8121
+ readTreeObjectFromFile:function(clientId, repositoryName, CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME){
8122
+
8123
+ let path=self.getPath(clientId,repositoryName);
8124
+
8125
+ let resultFlat=null;
8126
+
8127
+ try{
8128
+ resultFlat=fs.readFileSync(path, FILE_ENCODING);
8129
+
8130
+ }catch(error){
8131
+ // TRACE
8132
+ console.log("ERROR : Could not read file «"+path+"».");
8133
+
8134
+ return null;
8135
+ }
8136
+
8137
+
8138
+ let resultData={};
8139
+ if(!empty(resultFlat)) resultData=JSON.parse(resultFlat);
8140
+
8141
+
8142
+ if(!empty(resultData) && isFlatMap(resultData)){
8143
+
8144
+ resultData=getAsTreeStructure(resultData,true
8145
+ // We have to keep the type information, here too ! (in the sub-objects)
8146
+ ,false);
8147
+
8148
+ }
8149
+
8150
+ return resultData;
8151
+ },
8152
+
8153
+
8154
+ saveDataToFileForClient:function(clientId,repositoryName,dataFlatForClient,forceKeepUnflatten=false,doOnSuccess=null){
8155
+
8156
+
8157
+
8158
+ if(!empty(dataFlatForClient) && !isFlatMap(dataFlatForClient) && !forceKeepUnflatten){
8159
+ dataFlatForClient=getAsFlatStructure(dataFlatForClient,true);
8160
+ }
8161
+
8162
+ // reserved characters : -/\^$*+?.()|[]{}
8163
+ // CANNOT USE stringifyObject(...) function because we are in a common, lower-level library !
8164
+ let dataFlatStr=JSON.stringify(dataFlatForClient)
8165
+ // We «aerate» the produced JSON :
8166
+ .replace(/":[\w]*\{/gim,"\":{\n").replace(/,"/gim,",\n\"")
8167
+ // ...except for inline, escaped JSON string representations :
8168
+ .replace(/\\\":[\w]*\{\n/gim,"\\\":{");
8169
+ // NO : .replace(/}/gim,"}\n");
8170
+
8171
+
8172
+ let path=self.getPath(clientId,repositoryName);
8173
+
8174
+ fs.writeFile(path, dataFlatStr, FILE_ENCODING, (error) => {
8175
+ if(error){
8176
+ // TRACE
8177
+ console.log("ERROR : Could not write file «"+path+"»:",error);
8178
+ throw error;
8179
+ }
8180
+ if(doOnSuccess) doOnSuccess(dataFlatForClient);
8181
+ });
8182
+ }
8183
+
8184
+ };
8185
+
8186
+
8187
+ return self;
8188
+ };
8189
+
8190
+ }
8191
+
8192
+
8193
+
8194
+ // Nodejs server launching helper functions :
8195
+ //Networking management :
8196
+ //- WEBSOCKETS AND NODEJS :
8197
+
8198
+ // -Server :
8199
+
8200
+ // UNUSED : (BUT KEEP CODE!!)
8201
+ //getServerParams=function(portParam,isSecureParam,certPathParam,keyPathParam){
8202
+ //
8203
+ // // Node dependencies :
8204
+ // // https=require("https");
8205
+ // // fs=require("fs");
8206
+ //
8207
+ // if(!https){
8208
+ // // TRACE
8209
+ // console.log("WARN : Could not find the nodejs dependency «https», aborting SSL setup.");
8210
+ // return null;
8211
+ // }
8212
+ // if(!fs){
8213
+ // // TRACE
8214
+ // console.log("WARN : Could not find the nodejs dependency «fs», aborting SSL setup.");
8215
+ // return null;
8216
+ // }
8217
+ //
8218
+ // var result={};
8219
+ //
8220
+ // // We read the command-line arguments if needed :
8221
+ // var argCLPort;
8222
+ // var argCLCertPath;
8223
+ // var argCLKeyPath;
8224
+ // process.argv.forEach(function (val, i){
8225
+ // if(i<=1) return;
8226
+ // else if(i==2) argCLPort=val;
8227
+ // else if(i==3) argCLCertPath=val;
8228
+ // else if(i==4) argCLKeyPath=val;
8229
+ // });
8230
+ //
8231
+ // // Console, command-line arguments OVERRIDE parameters values :
8232
+ //
8233
+ // result.port=nonull(argCLPort,portParam);
8234
+ // result.certPath=null;
8235
+ // result.keyPath=null;
8236
+ // result.isSecure=isSecureParam;
8237
+ //
8238
+ // if(isSecureParam){
8239
+ // result.certPath=nonull(argCLCertPath,certPathParam);
8240
+ // result.keyPath=nonull(argCLKeyPath,keyPathParam);
8241
+ // }
8242
+ //
8243
+ // // Eventual encryption options :
8244
+ // result.sslOptions=null;
8245
+ // if(isSecureParam){
8246
+ // result.sslOptions={
8247
+ // cert: fs.readFileSync(certPath),
8248
+ // key: fs.readFileSync(keyPath),
8249
+ // };
8250
+ // }
8251
+ //
8252
+ // return result;
8253
+ //}
8254
+
8255
+
8256
+ // NODE ONLY SERVER / CLIENTS :
8257
+ WebsocketImplementation={
8258
+
8259
+ isNodeContext:true,
8260
+ useSocketIOImplementation:false,
8261
+ useFlatStrings:false,
8262
+
8263
+ // COMMON METHODS
8264
+ /*private static*/isInRoom(clientSocket, clientsRoomsTag){
8265
+ return !clientsRoomsTag || empty(clientsRoomsTag) || contains(clientsRoomsTag, clientSocket.clientRoomTag);
8266
+ },
8267
+
8268
+
8269
+ //
8270
+ // NODE SERVER
8271
+ //
8272
+ getStatic:(isNodeContext=true, useSocketIOImplementation=false)=>{
8273
+
8274
+ WebsocketImplementation.isNodeContext=isNodeContext;
8275
+
8276
+ // cf. https://socket.io/docs/v4/client-socket-instance/
8277
+ WebsocketImplementation.useSocketIOImplementation=useSocketIOImplementation;
8278
+
8279
+ // OLD : socket.io :
8280
+ // https://stackoverflow.com/questions/31156884/how-to-use-https-on-node-js-using-express-socket-io
8281
+ // https://stackoverflow.com/questions/6599470/node-js-socket-io-with-ssl
8282
+ // https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener
8283
+
8284
+ // if(typeof(socketIO)==="undefined"){
8285
+ // // TRACE
8286
+ // console.log("«socket.io» SERVER library not called yet, calling it now.");
8287
+ // socketIO=require("socket.io");
8288
+ // return socketIO;
8289
+ // }
8290
+ // if(typeof(socketIO)==="undefined"){
8291
+ // // TRACE
8292
+ // console.log("ERROR : «socket.io» SERVER library not found. Cannot launch nodejs server. Aborting.");
8293
+ // return null;
8294
+ // }
8295
+
8296
+ // *********************************************************************************
8297
+
8298
+ // NEW : ws :
8299
+ // https://github.com/websockets/ws#installing
8300
+ // https://github.com/websockets/ws/blob/master/doc/ws.md#event-message
8301
+ // ON BROWSER SIDE : Native Websockets :
8302
+ // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
8303
+ // https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications
8304
+ if(typeof(WebSocket)==="undefined"){
8305
+ WebSocket=require("ws");
8306
+ // TRACE
8307
+ console.log("«ws» SERVER library not called yet, calling it now.");
8308
+ return WebsocketImplementation;
8309
+ }
8310
+ if(typeof(WebSocket)==="undefined"){
8311
+ // TRACE
8312
+ console.log("ERROR : «ws» SERVER library not found. Cannot launch nodejs server. Aborting.");
8313
+ return null;
8314
+ }
8315
+
8316
+ return WebsocketImplementation;
8317
+ },
8318
+
8319
+
8320
+ getServer:(listenableServer)=>{
8321
+
8322
+ if(!WebsocketImplementation.isNodeContext){
8323
+ // TRACE
8324
+ throw new Error("ERROR : SERVER : Server launch is not supported in a non-nodejs context for any implementation.");
8325
+ }
8326
+
8327
+ // TODO : FIXME : Use one single interface !
8328
+ // NODE SERVER MODE ONLY :
8329
+ let serverSocket;
8330
+ if(!WebsocketImplementation.useSocketIOImplementation){
8331
+ serverSocket=new WebSocket.Server({ "server":listenableServer });
8332
+ }else{
8333
+ // NOW : socket.io :
8334
+ // Loading socket.io
8335
+ serverSocket=socketIO.listen(listenableServer);
8336
+
8337
+ // Setting up the disctonnect event :
8338
+ serverSocket.on("endConnection",()=>{
8339
+ serverSocket.disconnect();
8340
+ });
8341
+
8342
+ }
8343
+
8344
+ // NODE SERVER INSTANCE :
8345
+ const nodeServerInstance={
8346
+
8347
+ onClientLostListeners:[],
8348
+
8349
+ clientTimeoutMillis:20000,
8350
+ // clientsSockets:[],
8351
+ serverSocket:serverSocket,
8352
+ receptionEntryPoints:[],
8353
+
8354
+ receive:(channelName, doOnIncomingMessage, clientsRoomsTag=null)=>{
8355
+
8356
+ const receptionEntryPoint={
8357
+ channelName:channelName,
8358
+ clientsRoomsTag:clientsRoomsTag,
8359
+ execute:(clientSocketParam)=>{
8360
+
8361
+
8362
+ // With «ws» library we have no choive than register message events inside a «connection» event !
8363
+ // nodeServerInstance.onConnectionToClient((serverParam, clientSocketParam)=>{
8364
+
8365
+ // TODO : Find a way to remove this !
8366
+ const doOnMessage=(message)=>{
8367
+
8368
+ // dataWrapped=JSON.parse(dataWrapped);
8369
+ // dataWrapped=getAt(dataWrapped,0);// We get the root element
8370
+ const dataWrapped=(WebsocketImplementation.useFlatStrings?JSON.parse(message):message);
8371
+
8372
+
8373
+ // Channel information is stored in exchanged data :
8374
+ if(dataWrapped.channelName!==receptionEntryPoint.channelName) return;
8375
+
8376
+
8377
+ const clientSocket=clientSocketParam;
8378
+
8379
+ // TODO : FIXME : Use one single interface !
8380
+ // Room information is stored in client socket object :
8381
+ if(!WebsocketImplementation.isInRoom(clientSocket, receptionEntryPoint.clientsRoomsTag)) return;
8382
+
8383
+ doOnIncomingMessage(dataWrapped.data, clientSocket);
8384
+
8385
+ };
8386
+
8387
+
8388
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocketParam.addEventListener("message", doOnMessage);
8389
+ else clientSocketParam.on(channelName, doOnMessage);
8390
+
8391
+
8392
+
8393
+
8394
+ },
8395
+ };
8396
+
8397
+
8398
+ nodeServerInstance.receptionEntryPoints.push(receptionEntryPoint);
8399
+
8400
+
8401
+ // DBG
8402
+ console.log("ADD RECEPTION ENTRY POINT channelName:«"+channelName+"»! nodeServerInstance.receptionEntryPoints.length:",nodeServerInstance.receptionEntryPoints.length);
8403
+
8404
+
8405
+ return nodeServerInstance;
8406
+ },
8407
+
8408
+
8409
+ send:(channelName, data, clientsRoomsTag=null, clientSocketParam=null)=>{
8410
+
8411
+
8412
+ if(!clientSocketParam){
8413
+
8414
+ // TODO : FIXME : Use one single interface !
8415
+ let serverClients;
8416
+ if(!WebsocketImplementation.useSocketIOImplementation) serverClients=nodeServerInstance.serverSocket.clients;
8417
+ else serverClients=nodeServerInstance.serverSocket.sockets.clients();
8418
+
8419
+ serverClients.forEach((clientSocket)=>{
8420
+
8421
+
8422
+ // TODO : FIXME : Use one single interface !
8423
+ if(!WebsocketImplementation.useSocketIOImplementation) if(clientSocket.readyState!==WebSocket.OPEN) return;
8424
+ else if(clientSocket.connected) return;
8425
+
8426
+
8427
+
8428
+ // Room information is stored in client socket object :
8429
+ if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8430
+
8431
+ // Channel information is stored in exchanged data :
8432
+ let dataWrapped={channelName:channelName, data:data};
8433
+
8434
+
8435
+ dataWrapped=JSON.stringify(dataWrapped);
8436
+
8437
+ // TODO : FIXME : Use one single interface !
8438
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
8439
+ else clientSocket.emit(channelName,dataWrapped);
8440
+
8441
+ });
8442
+
8443
+ }else{
8444
+
8445
+ // TODO : FIXME : Use one single interface !
8446
+ let clientSocket=clientSocketParam;
8447
+ if(!WebsocketImplementation.useSocketIOImplementation) if(clientSocket.readyState!==WebSocket.OPEN) return;
8448
+ else if(clientSocket.connected) return;
7961
8449
 
7962
- Math.getDistance=function(p1, p2){
7963
- return Math.sqrt(Math.getSquaredDistance(p1,p2));
7964
- };
7965
8450
 
8451
+ // Room information is stored in client socket object :
8452
+ if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
7966
8453
 
8454
+ // Channel information is stored in exchanged data :
8455
+ let dataWrapped={channelName:channelName, data:data};
8456
+
8457
+ dataWrapped=JSON.stringify(dataWrapped);
8458
+
8459
+ // TODO : FIXME : Use one single interface !
8460
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
8461
+ else clientSocket.emit(channelName,dataWrapped);
8462
+
8463
+
8464
+ }
8465
+
8466
+
8467
+ return nodeServerInstance;
8468
+ },
8469
+
8470
+
8471
+ onConnectionToClient:(doOnConnection)=>{
8472
+ // «connection» is the only event fired by the serverSocket :
8473
+ nodeServerInstance.serverSocket.on("connection", (clientSocket)=>{
8474
+
8475
+ // DBG
8476
+ console.log("SERVER : ON CONNECTION !");
8477
+
8478
+
8479
+ // if(contains(nodeServerInstance.clientsSockets, clientSocket)) return;
8480
+ // nodeServerInstance.clientsSockets.push(clientSocket);
7967
8481
 
7968
8482
 
7969
- Math.getBarycenter=function(coordsArray){
7970
-
7971
- let coordsX=[];
7972
- let coordsY=[];
7973
- foreach(coordsArray,(coords)=>{
7974
- coordsX.push(coords.x);
7975
- coordsY.push(coords.y);
7976
- });
8483
+ // DBG
8484
+ console.log("nodeServerInstance.receptionEntryPoints.length:",nodeServerInstance.receptionEntryPoints.length);
7977
8485
 
7978
- // Barycenters calculation :
7979
- let bx=Math.floor(Math.averageInArray(coordsX));
7980
- let by=Math.floor(Math.averageInArray(coordsY));
7981
8486
 
7982
-
7983
- return {x:bx,y:by};
7984
- }
8487
+
8488
+ // We execute the events registration listeners entry points:
8489
+ foreach(nodeServerInstance.receptionEntryPoints,(receptionEntryPoint)=>{
8490
+
8491
+ // DBG
8492
+ console.log("ENTRY POINT !");
8493
+
8494
+ receptionEntryPoint.execute(clientSocket);
8495
+ });
7985
8496
 
7986
8497
 
7987
- Math.getBarycentersFromZonesWithColors=function(zonesWithColors, withColors=true){
7988
- let barycenters=[];
7989
-
7990
- foreach(zonesWithColors,(singleContigousZonePixelsCoords)=>{
7991
- let barycenter=Math.getBarycenter(singleContigousZonePixelsCoords);
7992
- barycenters.push(barycenter);
7993
-
7994
- let zoneLength=singleContigousZonePixelsCoords.length;
7995
-
7996
- if(withColors){
7997
- // barycenter color :
7998
- let avgR=0;
7999
- let avgG=0;
8000
- let avgB=0;
8001
- foreach(singleContigousZonePixelsCoords,(coords)=>{
8002
- let color=coords.color;
8003
- avgR+=color.r;
8004
- avgG+=color.g;
8005
- avgB+=color.b;
8498
+ doOnConnection(nodeServerInstance, clientSocket);
8499
+
8500
+
8501
+
8502
+
8503
+ // To make the server aware of the clients connections states :
8504
+ clientSocket.stateCheckInterval=setInterval(()=>{
8505
+
8506
+
8507
+ if (clientSocket.isAlive===false){
8508
+ clearInterval(clientSocket.stateCheckInterval);
8509
+
8510
+ // TODO : FIXME : DUPLICATED CODE :
8511
+ // TODO : FIXME : Use one single interface !
8512
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.terminate();
8513
+ else clientSocket.emit("endConnection");
8514
+
8515
+ // TODO : FIXME : DUPLICATED CODE :
8516
+ if(!empty(nodeServerInstance.onClientLostListeners))
8517
+ foreach(nodeServerInstance.onClientLostListeners,l=>{l.execute(clientSocket);});
8518
+
8519
+ return;
8520
+ }
8521
+
8522
+ clientSocket.isAlive=false;
8523
+ try{
8524
+ clientSocket.ping();
8525
+ }catch(error){
8526
+ lognow("ERROR : A problem occurred when tried to ping client socket : ",error);
8527
+ // We effectively end the client connection to this server :
8528
+
8529
+ // TODO : FIXME : DUPLICATED CODE :
8530
+ // TODO : FIXME : Use one single interface !
8531
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.terminate();
8532
+ else clientSocket.emit("endConnection");
8533
+
8534
+ // TODO : FIXME : DUPLICATED CODE :
8535
+ if(!empty(nodeServerInstance.onClientLostListeners))
8536
+ foreach(nodeServerInstance.onClientLostListeners,l=>{l.execute(clientSocket);});
8537
+
8538
+ return;
8539
+ }
8540
+
8541
+
8542
+ }, nodeServerInstance.clientTimeoutMillis);
8543
+ clientSocket.on("pong",()=>{
8544
+ clientSocket.isAlive=true;
8545
+ });
8546
+
8547
+
8548
+
8549
+ });
8550
+
8551
+
8552
+ return nodeServerInstance;
8553
+ },
8554
+
8555
+ onFinalize:(doOnFinalizeServer)=>{
8556
+
8557
+ doOnFinalizeServer(nodeServerInstance);
8558
+
8559
+ // TRACE
8560
+ console.log("INFO : SERVER : Node server setup complete.");
8561
+
8562
+ return nodeServerInstance;
8563
+ },
8564
+
8565
+ addToRoom:(clientSocket,clientRoomTag)=>{
8566
+ clientSocket.clientRoomTag=clientRoomTag;
8567
+ },
8568
+
8569
+
8570
+
8571
+ };
8572
+
8573
+ // Join room server part protocol :
8574
+ nodeServerInstance.receive("protocol",(message, clientSocket)=>{
8575
+
8576
+ if(message.type!=="joinRoom" || !clientSocket) return;
8577
+
8578
+ nodeServerInstance.addToRoom(clientSocket, message.clientRoomTag);
8579
+
8006
8580
  });
8007
- barycenter.color={r:avgR/zoneLength, g:avgG/zoneLength, b:avgB/zoneLength};
8008
- }else{
8009
- barycenter.color={r:128, g:128, b:128};
8010
- }
8581
+
8582
+
8583
+
8584
+ // To make the server aware of the clients connections states :
8585
+ nodeServerInstance.serverSocket.on("close", function close() {
8586
+
8587
+ // TODO : FIXME : Use one single interface !
8588
+ if(!WebsocketImplementation.useSocketIOImplementation) serverClients=nodeServerInstance.serverSocket.clients;
8589
+ else serverClients=nodeServerInstance.serverSocket.sockets.clients();
8590
+
8591
+ serverClients.forEach((clientSocket)=>{
8592
+ clearInterval(clientSocket.stateCheckInterval);
8593
+ });
8594
+ });
8595
+
8596
+
8597
+
8598
+ return nodeServerInstance;
8599
+ },
8011
8600
 
8012
- });
8601
+
8602
+ // NODE / BROWSER CLIENT CONNECTS TO SERVER MAIN ENTRYPOINT:
8603
+ connectToServer:(serverURL, port, isSecure=false, timeout)=>{
8013
8604
 
8014
- return barycenters;
8015
- }
8605
+ // TRACE
8606
+ lognow("INFO : Using websocket implementation : "+(WebsocketImplementation.isNodeContext?"node (server-side)":"browser (client-side)"));
8607
+
8608
+ if(WebsocketImplementation.isNodeContext)
8609
+ return WebsocketImplementation.connectToServerFromNode(serverURL, port, isSecure, timeout);
8610
+ else
8611
+ return WebsocketImplementation.connectToServerFromBrowser(serverURL, port, isSecure, timeout);
8612
+ },
8613
+
8614
+
8615
+
8616
+ //
8617
+ // NODE CLIENT
8618
+ //
8619
+ /*private*/connectToServerFromNode:(serverURL, port, isSecure, timeout)=>{
8620
+
8621
+
8622
+
8623
+ // NEW : ws :
8624
+ if(typeof(WebSocket)==="undefined"){
8625
+ // TRACE
8626
+ lognow("ERROR : CLIENT : Could not find websocket client lib, aborting client connection.");
8627
+ return null;
8628
+ }
8629
+
8630
+ // NODE CLIENT MODE ONLY :
8631
+ let clientSocket;
8632
+ if(!WebsocketImplementation.useSocketIOImplementation){
8633
+ clientSocket=new WebSocket(serverURL+":"+port,/*WORKAROUND:*/{ rejectUnauthorized:false, secure: isSecure });
8634
+ }else{
8635
+ // NOW : socket.io :
8636
+ //client on server-side:
8637
+ if(WebsocketImplementation.isNodeContext && typeof(socketIO)!=="undefined"){
8638
+ // // Node client :
8639
+ // connectToServer=function (serverURL, port, isSecure, timeout=10000){
8640
+ // return socketIO.connect(serverURL + ":" + port,{timeout: timeout});
8641
+ // }
8642
+ clientSocket=socketIO.connect(serverURL + ":" + port,{timeout: timeout, secure: isSecure});
8643
+ }
8644
+ }
8016
8645
 
8646
+ // NODE CLIENT INSTANCE :
8647
+ const nodeClientInstance={
8648
+ clientSocket:clientSocket,
8649
+
8650
+ receptionEntryPoints:[],
8651
+
8652
+ receive:(channelName, doOnIncomingMessage, clientsRoomsTag=null)=>{
8653
+
8654
+ const receptionEntryPoint={
8655
+ channelName:channelName,
8656
+ clientsRoomsTag:clientsRoomsTag,
8657
+ execute:(clientSocketParam)=>{
8658
+
8659
+ const doOnMessage=(message)=>{
8660
+
8661
+ // dataWrapped=JSON.parse(dataWrapped);
8662
+ // dataWrapped=getAt(dataWrapped,0);// We get the root element
8663
+
8664
+ const dataWrapped=(WebsocketImplementation.useFlatStrings?JSON.parse(message):message);
8665
+
8666
+
8667
+ // Channel information is stored in exchanged data :
8668
+ if(dataWrapped.channelName && dataWrapped.channelName!==channelName) return;
8669
+
8670
+ let clientSocket=nodeClientInstance.clientSocket;
8671
+
8672
+ // Room information is stored in client socket object :
8673
+ if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8674
+
8675
+ doOnIncomingMessage(dataWrapped.data, clientSocket);
8676
+
8677
+ };
8678
+
8679
+ if(!WebsocketImplementation.useSocketIOImplementation) nodeClientInstance.clientSocket.addEventListener("message", doOnMessage);
8680
+ else nodeClientInstance.clientSocket.on(channelName, doOnMessage);
8017
8681
 
8018
- Math.getBidimensional8Direction=(currentPosition,destination,invertYAxis=false)=>{
8682
+
8683
+ }
8684
+ };
8685
+
8686
+
8687
+ nodeClientInstance.receptionEntryPoints.push(receptionEntryPoint);
8688
+
8689
+ return nodeClientInstance;
8690
+ },
8691
+
8692
+
8693
+ send:(channelName, data, clientsRoomsTag=null)=>{
8694
+
8695
+ let clientSocket=nodeClientInstance.clientSocket;
8696
+
8697
+
8698
+ // // DBG
8699
+ // console.log("(NODE CLIENT) TRYING TO SEND : clientSocket.readyState:",clientSocket.readyState);
8019
8700
 
8020
- let x=currentPosition.x;
8021
- let y=currentPosition.y;
8022
-
8023
- let deltaX=(destination.x-x);
8024
- let deltaY=(invertYAxis?-1:1)*(destination.y-y);
8701
+ // TODO : FIXME : Use one single interface !
8702
+ if(!WebsocketImplementation.useSocketIOImplementation) if(clientSocket.readyState!==WebSocket.OPEN) return;
8703
+ else if(clientSocket.connected) return;
8025
8704
 
8026
- if(deltaX===0 && deltaY===0) return null;
8027
8705
 
8028
- let directionXAxis=(deltaX<0?"W":"E");
8029
- let directionYAxis=(deltaY<0?"S":"N");
8030
- if(deltaX===0) return directionYAxis;
8031
- if(deltaY===0) return directionXAxis;
8032
-
8033
- let tanRatio=(deltaY/deltaX);
8034
-
8035
- if(0<deltaX && 0<deltaY){ // 0 -> PI/2 quarter :
8036
- if(tanRatio<=Math.TAN_RATIOS_DEGREES[22.5]) return "E";
8037
- if(Math.TAN_RATIOS_DEGREES[22.5]<=tanRatio && tanRatio<Math.TAN_RATIOS_DEGREES[67.5]) return "NE";
8038
- if(Math.TAN_RATIOS_DEGREES[67.5]<=tanRatio) return "N";
8039
- }else if(deltaX<0 && 0<deltaY){ // PI/2 -> PI quarter :
8040
- if(tanRatio<=Math.TAN_RATIOS_DEGREES[112.5]) return "N";
8041
- if(Math.TAN_RATIOS_DEGREES[112.5]<=tanRatio && tanRatio<Math.TAN_RATIOS_DEGREES[157.5]) return "NW";
8042
- if(Math.TAN_RATIOS_DEGREES[157.5]<=tanRatio) return "W";
8043
- }else if(deltaX<0 && deltaY<0){ // PI -> (3/2)*PI quarter :
8044
- if(tanRatio<=Math.TAN_RATIOS_DEGREES[202.5]) return "W";
8045
- if(Math.TAN_RATIOS_DEGREES[202.5]<=tanRatio && tanRatio<Math.TAN_RATIOS_DEGREES[247.5]) return "SW";
8046
- if(Math.TAN_RATIOS_DEGREES[247.5]<=tanRatio) return "S";
8047
- }else{ // (3/2)*PI -> 2*PI quarter :
8048
- if(tanRatio<=Math.TAN_RATIOS_DEGREES[292.5]) return "S";
8049
- if(Math.TAN_RATIOS_DEGREES[292.5]<=tanRatio && tanRatio<Math.TAN_RATIOS_DEGREES[337.5]) return "SE";
8050
- if(Math.TAN_RATIOS_DEGREES[337.5]<=tanRatio) return "E";
8051
- }
8052
- return null;
8053
- };
8706
+ // Room information is stored in client socket object :
8707
+ if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8708
+
8709
+ // Channel information is stored in exchanged data :
8710
+ let dataWrapped={channelName:channelName, data:data};
8054
8711
 
8055
8712
 
8713
+ // // DBG
8714
+ // console.log("(NODE CLIENT) SENDING DATA ! dataWrapped:",dataWrapped);
8056
8715
 
8057
- /*public static*/window.getRandomPositionInZone=function(positionZone, xOffset=0, yOffset=0, zOffset=0, avoidOverlap=null, size=null, randomizeAngles=false){
8058
-
8059
- let w=nonull(positionZone.w,0);
8060
- let h=nonull(positionZone.h,0);
8061
- let d=nonull(positionZone.d,0);
8062
8716
 
8063
- let demiW=w*.5;
8064
- let demiH=h*.5;
8065
- let demiD=d*.5;
8717
+ dataWrapped=JSON.stringify(dataWrapped);
8066
8718
 
8067
- let xOrZero=nonull(positionZone.x,0);
8068
- let yOrZero=nonull(positionZone.y,0);
8069
- let zOrZero=nonull(positionZone.z,0);
8070
8719
 
8071
- // If respectively width or height is 0, then there is no need to use a randomization !
8072
- let x= ( w<=0 ? xOrZero : (xOrZero + Math.getRandomInt(Math.floor(demiW), -Math.ceil(demiW)))) + xOffset;
8073
- let y= ( h<=0 ? yOrZero : (yOrZero + Math.getRandomInt(Math.floor(demiH), -Math.ceil(demiH)))) + yOffset;
8074
- let z= ( d<=0 ? zOrZero : (zOrZero + Math.getRandomInt(Math.floor(demiD), -Math.ceil(demiD)))) + zOffset;
8720
+ // // DBG
8721
+ // console.log("(NODE CLIENT) SENDING DATA ! channelName:«"+channelName+"» ; clientsRoomsTag:«"+clientsRoomsTag+"»");
8722
+ // console.log("(NODE CLIENT) SENDING DATA ! dataWrapped:",dataWrapped);
8075
8723
 
8076
- if(avoidOverlap && size){
8077
-
8078
- let itemW=nonull(size.w,0);
8079
- let itemH=nonull(size.h,0);
8080
- let itemD=nonull(size.d,0);
8081
-
8082
- if(xOrZero/*includes case==0*/ && 0<itemW && contains(avoidOverlap,"x")) x=Math.floor(x/itemW)*itemW;
8083
- if(yOrZero/*includes case==0*/ && 0<itemH && contains(avoidOverlap,"y")) y=Math.floor(y/itemH)*itemH;
8084
- if(zOrZero/*includes case==0*/ && 0<itemD && contains(avoidOverlap,"z")) z=Math.floor(z/itemD)*itemD;
8085
-
8086
- }
8087
-
8088
- let b=positionZone.b;
8089
- let g=positionZone.g;
8090
- let a=positionZone.a;
8091
- if(randomizeAngles){
8092
- b=Math.random()*Math.PI*2;
8093
- g=Math.random()*Math.PI*2;
8094
- a=Math.random()*Math.PI*2;
8095
- }
8724
+ // TODO : FIXME : Use one single interface !
8725
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
8726
+ else clientSocket.emit(channelName,dataWrapped);
8727
+
8728
+ return nodeClientInstance;
8729
+ },
8730
+
8731
+
8732
+ join:(clientRoomTag)=>{
8733
+ // Join room client part protocol :
8734
+ const message={type:"joinRoom",clientRoomTag:clientRoomTag};
8735
+
8736
+ nodeClientInstance.send("protocol",message);
8737
+
8738
+ },
8739
+
8740
+
8741
+ onConnectionToServer:(doOnConnection)=>{
8742
+
8743
+ const doAllOnConnection=()=>{
8744
+
8745
+ // To avoid triggering this event several times, depending on the implementation :
8746
+ if(nodeClientInstance.hasConnectEventFired) return;
8747
+ nodeClientInstance.hasConnectEventFired=true;
8748
+
8749
+ let clientSocket=nodeClientInstance.clientSocket;
8750
+
8751
+ doOnConnection(nodeClientInstance, clientSocket);
8752
+
8753
+ // We execute the listeners entry points:
8754
+ foreach(nodeClientInstance.receptionEntryPoints,(receptionEntryPoint)=>{
8755
+ receptionEntryPoint.execute(clientSocket);
8756
+ });
8757
+
8758
+ };
8096
8759
 
8097
- return {x:x, y:y, z:z, b:b, g:g, a:a, parallax:positionZone.parallax};
8098
- }
8760
+ if(!WebsocketImplementation.useSocketIOImplementation) nodeClientInstance.clientSocket.addEventListener("open",doAllOnConnection);
8761
+ else nodeClientInstance.clientSocket.on("connect",doAllOnConnection);
8762
+
8763
+ },
8764
+
8765
+
8766
+ };
8767
+
8768
+
8769
+ return nodeClientInstance;
8770
+ },
8771
+
8099
8772
 
8773
+
8774
+ // BROWSER CLIENT
8775
+
8776
+ /*private*/connectToServerFromBrowser:(serverURL, port, isSecure, timeout)=>{
8777
+
8778
+ // NEW : ws :
8779
+ if(typeof(WebSocket)==="undefined"){
8780
+ // TRACE
8781
+ lognow("ERROR : CLIENT : Could not find websocket client lib, aborting client connection.");
8782
+ return null;
8783
+ }
8784
+
8785
+ // TODO : FIXME : Use one single interface !
8786
+ // BROWSER CLIENT MODE ONLY :
8787
+ let clientSocket;
8788
+ if(!WebsocketImplementation.useSocketIOImplementation){
8789
+ clientSocket=new WebSocket(serverURL+":"+port,["ws","wss"]);
8790
+ }else if(typeof(io)!=="undefined"){
8791
+ // NOW : socket.io :
8792
+ // // Browser client :
8793
+ // connectToServer=function (server, port, isSecure, timeout=10000){
8794
+ // return io.connect(serverURL + ":" + port,{timeout: timeout});
8795
+ //// ALTERNATIVE : return io(serverURL + ":" + port,{timeout: timeout});
8796
+ // };
8797
+ // }else{
8798
+ // // TRACE
8799
+ // console.log("ERROR : Could not initialize socket !");
8800
+ clientSocket=io.connect(serverURL + ":" + port,{timeout: timeout, secure: isSecure});
8801
+ }
8802
+
8803
+
8804
+ // BROWSER CLIENT INSTANCE :
8805
+ const browserInstance={
8806
+ clientSocket:clientSocket,
8807
+
8808
+ receive:(channelName, doOnIncomingMessage, clientsRoomsTag=null)=>{
8100
8809
 
8101
- // CAUTION : ONLY HANDLES «center-positionned» ZONES !
8102
- /*public static*/window.isZoneCollidingWithZone=function(zone1, zone2){
8810
+ // DBG
8811
+ lognow("!!! SETTING UP RECEIVE for :",channelName);
8812
+
8813
+ const doOnMessage=(message)=>{
8814
+
8815
+ // // DBG
8816
+ // lognow("!!! RECEIVED : message",message);
8817
+
8818
+ // OLD : let dataWrapped=JSON.parse(event.data);
8819
+ // const dataWrapped=JSON.parse(message);
8820
+ // dataWrapped=getAt(dataWrapped,0);// We get the root element
8103
8821
 
8104
- let demiWidth1=zone1.w*.5;
8105
- let demiWidth2=zone2.w*.5;
8822
+ const dataWrapped=(WebsocketImplementation.useFlatStrings?JSON.parse(message):message);
8823
+
8824
+
8825
+ // Channel information is stored in exchanged data :
8826
+ if(dataWrapped.channelName && dataWrapped.channelName!==channelName) return;
8827
+
8828
+ let clientSocket=browserInstance.clientSocket;
8829
+
8830
+ // Room information is stored in client socket object :
8831
+ if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8832
+
8833
+ doOnIncomingMessage(dataWrapped.data, clientSocket);
8834
+
8835
+ };
8836
+
8837
+
8838
+ if(!WebsocketImplementation.useSocketIOImplementation) browserInstance.clientSocket.addEventListener("message", doOnMessage);
8839
+ else browserInstance.clientSocket.on(channelName, doOnMessage);
8106
8840
 
8107
- let xMin1=zone1.x-demiWidth1;
8108
- let xMax1=zone1.x+demiWidth1;
8109
- let xMin2=zone2.x-demiWidth2;
8110
- let xMax2=zone2.x+demiWidth2;
8841
+
8842
+ return browserInstance;
8843
+ },
8844
+
8845
+
8846
+ send:(channelName, data, clientsRoomsTag=null)=>{
8847
+
8848
+ let clientSocket=browserInstance.clientSocket;
8849
+
8850
+
8851
+ // // DBG
8852
+ // console.log("(BROWSER) TRYING TO SEND : clientSocket.readyState:",clientSocket.readyState);
8111
8853
 
8112
- //OLD : let widthsOverlap=((xMin2<xMin1 && xMin1<xMax2) || (xMin2<xMax1 && xMax1<xMax2)
8113
- // || (xMin1<xMin2 && xMin2<xMax1) || (xMin1<xMax2 && xMax2<xMax1));
8854
+ // TODO : FIXME : Use one single interface !
8855
+ if(!WebsocketImplementation.useSocketIOImplementation) if(clientSocket.readyState!==WebSocket.OPEN) return;
8856
+ else if(clientSocket.connected) return;
8114
8857
 
8115
- let widthsOverlap=(xMin1<xMax2 && xMin2<xMax1);
8858
+
8859
+ // Room information is stored in client socket object :
8860
+ if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
8116
8861
 
8862
+ // Channel information is stored in exchanged data :
8863
+ let dataWrapped={channelName:channelName, data:data};
8864
+
8865
+
8866
+ // // DBG
8867
+ // console.log("(BROWSER) SENDING... : dataWrapped :",dataWrapped);
8117
8868
 
8118
- let demiHeight1=zone1.h*.5;
8119
- let demiHeight2=zone2.h*.5;
8869
+
8870
+ dataWrapped=JSON.stringify(dataWrapped);
8120
8871
 
8121
- let yMin1=zone1.y-demiHeight1;
8122
- let yMax1=zone1.y+demiHeight1;
8123
- let yMin2=zone2.y-demiHeight2;
8124
- let yMax2=zone2.y+demiHeight2;
8872
+
8873
+ // TODO : FIXME : Use one single interface !
8874
+ if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
8875
+ else clientSocket.emit(channelName,dataWrapped);
8125
8876
 
8126
- //OLD : let heightsOverlap=((yMin1<yMin2 && yMin2<yMax1) || (yMin1<yMax2 && yMax2<yMax1)
8127
- // || (yMin2<yMin1 && yMin1<yMax2) || (yMin2<yMax1 && yMax1<yMax2));
8877
+
8878
+ return browserInstance;
8879
+ },
8880
+
8881
+
8882
+ join:(clientRoomTag)=>{
8883
+ // Join room client part protocol :
8884
+ const message={type:"joinRoom",clientRoomTag:clientRoomTag};
8885
+ browserInstance.send("protocol",message);
8886
+ },
8887
+
8888
+
8889
+ onConnectionToServer:(doOnConnection)=>{
8890
+
8891
+ // To avoid triggering this event several times, depending on the implementation :
8892
+ if(browserInstance.hasConnectEventFired) return;
8893
+ browserInstance.hasConnectEventFired=true;
8894
+
8895
+ if(!WebsocketImplementation.useSocketIOImplementation) browserInstance.clientSocket.addEventListener("open",doOnConnection);
8896
+ else browserInstance.clientSocket.on("connect",doOnConnection);
8897
+
8898
+
8899
+ },
8900
+
8901
+
8902
+ };
8903
+
8904
+
8905
+ return browserInstance;
8906
+ },
8128
8907
 
8129
- let heightsOverlap=(yMin1<yMax2 && yMin2<yMax1);
8130
-
8131
- return widthsOverlap && heightsOverlap;
8132
- }
8908
+ };
8133
8909
 
8134
8910
 
8135
8911
 
8136
- // CAUTION : ONLY HANDLES «center-positionned» ZONES !
8137
- /*public static*/window.getIntersectionZone=function(zone1, zone2){
8138
- if(!isZoneCollidingWithZone(zone1, zone2)) return null;
8912
+ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /*OPTIONAL*/sslOptions=null){
8139
8913
 
8140
- let edges=[];
8914
+ const EXCLUDED_FILENAMES_PARTS=[".saltkey."];
8141
8915
 
8142
8916
 
8143
- // Horizontal axis :
8144
- let hasXAxisWholeZone1=false;
8145
- let hasXAxisWholeZone2=false;
8917
+ if(typeof(https)==="undefined"){
8918
+ // TRACE
8919
+ console.log("«https» SERVER library not called yet, calling it now.");
8920
+ https=require("https");
8921
+ }
8922
+ if(typeof(http)==="undefined"){
8923
+ // TRACE
8924
+ console.log("«http» SERVER library not called yet, calling it now.");
8925
+ http=require("http");
8926
+ }
8146
8927
 
8147
- let demiWidth1=zone1.w*.5;
8148
- let demiWidth2=zone2.w*.5;
8149
- let xMin1=zone1.x-demiWidth1;
8150
- let xMax1=zone1.x+demiWidth1;
8151
- let xMin2=zone2.x-demiWidth2;
8152
- let xMax2=zone2.x+demiWidth2;
8153
-
8154
- let deltaXMins=xMin2-xMin1;
8155
- let deltaXMaxs=xMax2-xMax1;
8156
8928
 
8157
- let deltaXMinsSign=(deltaXMins<0?-1:1);
8158
- let deltaXMaxsSign=(deltaXMaxs<0?-1:1);
8159
- let intersectionXMin=0;
8160
- let intersectionXMax=0;
8161
- if(deltaXMinsSign==deltaXMaxsSign){ // case «parallelogram»
8162
- if(deltaXMinsSign<0){ // case /=/ parallelogram
8163
- intersectionXMin=xMin1;
8164
- intersectionXMax=xMax2;
8165
- // We calculate edges :
8166
- edges.push("left");
8167
- }else{ // case \=\ parallelogram
8168
- intersectionXMin=xMin2;
8169
- intersectionXMax=xMax1;
8170
- // We calculate edges :
8171
- edges.push("right");
8172
- }
8173
- }else{ // case «trapeze»
8174
- if(deltaXMinsSign<0){ // case /=\ trapeze
8175
- intersectionXMin=xMin1; // Basically zone1 x !
8176
- intersectionXMax=xMax1;
8177
- // We calculate edges :
8178
- hasXAxisWholeZone1=true;
8179
- }else{ // case \=/ trapeze
8180
- intersectionXMin=xMin2; // Basically zone2 x !
8181
- intersectionXMax=xMax2;
8182
- // We calculate edges :
8183
- hasXAxisWholeZone2=true;
8184
- }
8185
- }
8186
-
8187
- let w=intersectionXMax-intersectionXMin;
8188
- let x=(intersectionXMax+intersectionXMin)*.5; // (The average)
8189
-
8190
-
8191
- // Vertical axis :
8192
-
8193
- let hasYAxisWholeZone1=false;
8194
- let hasYAxisWholeZone2=false;
8195
-
8196
- let demiHeight1=zone1.h*.5;
8197
- let demiHeight2=zone2.h*.5;
8929
+ const handler=function(request, response){
8930
+
8931
+ const url=request.url;
8198
8932
 
8199
- let yMin1=zone1.y-demiHeight1;
8200
- let yMax1=zone1.y+demiHeight1;
8201
- let yMin2=zone2.y-demiHeight2;
8202
- let yMax2=zone2.y+demiHeight2;
8933
+ let isURLInExclusionZone=!!foreach(EXCLUDED_FILENAMES_PARTS,(excludedStr)=>{
8934
+ if(contains(url,excludedStr)){
8935
+ return true;
8936
+ }
8937
+ });
8938
+
8939
+ if(isURLInExclusionZone){
8940
+ // TRACE
8941
+ console.log("ERROR 403 forbidden access error :");
8942
+ console.log(error);
8943
+
8944
+ response.writeHead(403);
8945
+ response.end("Sorry, cannot access resource : error: "+error.code+" ..\n");
8946
+ response.end();
8947
+ return;
8948
+ }
8949
+
8950
+
8951
+ const urlFile="." + url;
8952
+
8953
+ let filePath=urlFile.indexOf("?")!==-1?urlFile.split("?")[0]:urlFile;
8954
+ if(filePath == "./") filePath="./index.html";
8955
+
8956
+ let extname=path.extname(filePath);
8957
+ let contentType="text/html";
8958
+
8959
+
8960
+ fs.readFile(filePath, function(error, fileContent){
8961
+ if(error){
8962
+ if(error.code == "ENOENT"){
8963
+ // TRACE
8964
+ console.log("ERROR 404 file not found :"+filePath);
8965
+
8966
+ fs.readFile("./404.html", function(error, fileContent){
8967
+ response.writeHead(200, { "Content-Type": contentType });
8968
+ response.end(fileContent, "utf-8");
8969
+ });
8970
+
8971
+ }else {
8972
+
8973
+ // TRACE
8974
+ console.log("ERROR 500 server error :");
8975
+ console.log(error);
8976
+
8977
+ response.writeHead(500);
8978
+ response.end("Sorry, check with the site admin for error: "+error.code+" ..\n");
8979
+ response.end();
8980
+
8981
+ }
8982
+ }else {
8983
+
8984
+ // TRACE
8985
+ console.log("INFO 200 OK :"+filePath);
8986
+
8987
+ response.writeHead(200, { "Content-Type": contentType });
8988
+ response.end(fileContent, "utf-8");
8989
+
8990
+ // res.writeHead(200);
8991
+ // res.end("hello world\n");
8992
+ // res.sendFile(__dirname + "/public/index.html");
8993
+
8994
+ }
8995
+ });
8996
+
8997
+ };
8203
8998
 
8204
-
8205
- let deltaYMins=yMin2-yMin1;
8206
- let deltaYMaxs=yMax2-yMax1;
8207
8999
 
8208
- let deltaYMinsSign=(deltaYMins<0?-1:1);
8209
- let deltaYMaxsSign=(deltaYMaxs<0?-1:1);
8210
- let intersectionYMin=0;
8211
- let intersectionYMax=0;
8212
- if(deltaYMinsSign==deltaYMaxsSign){ // case «parallelogram»
8213
- if(deltaYMinsSign<0){ // case /=/ parallelogram
8214
- intersectionYMin=yMin1;
8215
- intersectionYMax=yMax2;
8216
- // We calculate edges :
8217
- edges.push("bottom"); // Here the Y axis is NOT inverted !
8218
- }else{ // case \=\ parallelogram
8219
- intersectionYMin=yMin2;
8220
- intersectionYMax=yMax1;
8221
- // We calculate edges :
8222
- edges.push("top"); // Here the Y axis is NOT inverted !
8223
- }
8224
- }else{ // case «trapeze»
8225
- if(deltaYMinsSign<0){ // case /=\ trapeze
8226
- intersectionYMin=yMin1; // Basically zone2 y !
8227
- intersectionYMax=yMax1;
8228
- // We calculate edges :
8229
- hasYAxisWholeZone1=true;
8230
- }else{ // case \=/ trapeze
8231
- intersectionYMin=yMin2; // Basically zone2 y !
8232
- intersectionYMax=yMax2;
8233
- // We calculate edges :
8234
- hasYAxisWholeZone2=true;
8235
- }
9000
+ let listenableServer;
9001
+ if(sslOptions){
9002
+ let httpsServer=https.createServer(sslOptions, handler).listen(port);
9003
+ // TRACE
9004
+ console.log("INFO : SERVER : HTTPS Server launched and listening on port " + port + "!");
9005
+ listenableServer=httpsServer;
9006
+ }else{
9007
+ let httpServer=http.createServer(handler).listen(port);
9008
+ // TRACE
9009
+ console.log("INFO : SERVER : HTTP Server launched and listening on port " + port + "!");
9010
+ listenableServer=httpServer;
8236
9011
  }
8237
9012
 
8238
- let h=intersectionYMax-intersectionYMin;
8239
- let y=(intersectionYMax+intersectionYMin)*.5; // (The average)
8240
9013
 
8241
- // We calculate edges :
8242
- if(hasXAxisWholeZone1 && hasYAxisWholeZone1) edges.push("wholeZone1");
8243
- if(hasXAxisWholeZone2 && hasYAxisWholeZone2) edges.push("wholeZone2");
9014
+ const server=WebsocketImplementation.getStatic(true).getServer(listenableServer);
8244
9015
 
8245
- return {x:x,y:y,w:w,h:h,edges:edges};
8246
- }
9016
+ // When a client connects, we execute the callback :
9017
+ // CAUTION : MUST BE CALLED ONLY ONCE !
9018
+ server.onConnectionToClient((serverParam, clientSocketParam)=>{
9019
+ if(doOnConnect) doOnConnect(serverParam, clientSocketParam);
9020
+ });
8247
9021
 
8248
- /*public static*/window.areZonesIdentical=function(zone1, zone2){
8249
- return (zone1==zone2 || (zone1.x==zone2.x || zone1.y==zone2.y || zone1.w==zone2.w || zone1.h==zone2.h));
8250
- }
8251
9022
 
9023
+ server.onFinalize((serverParam)=>{
9024
+ if(doOnFinalizeServer) doOnFinalizeServer(serverParam);
9025
+ });
9026
+
8252
9027
 
9028
+ // TRACE
9029
+ console.log("INFO : SERVER : Generic Nodejs server launched and listening on port:" + port + "!");
8253
9030
 
8254
- // 3D :
8255
9031
 
9032
+ return server;
9033
+ }
8256
9034
 
8257
- /*
8258
- // (needs the jsgl library file !)
8259
- // UNUSED
8260
- function screenToSpherePt(mouseX, mouseY) {
8261
- var p = { x: camera_mat.e12, y: camera_mat.e13, z: camera_mat.e14 + 1 };
8262
- // camera dir
8263
- var r = { x: camera_mat.e0, y: camera_mat.e1, z: camera_mat.e2 };
8264
- var up = { x: camera_mat.e4, y: camera_mat.e5, z: camera_mat.e6 };
8265
- var right = { x: camera_mat.e8, y: camera_mat.e9, z: camera_mat.e10 };
8266
- var tan_half = Math.tan(horizontal_fov_radians / 2);
8267
- r = jsgl.vectorAdd(r, jsgl.vectorScale(right, mouseX * tan_half));
8268
- r = jsgl.vectorAdd(r, jsgl.vectorScale(up, mouseY * tan_half));
8269
- r = jsgl.vectorNormalize(r);
8270
9035
 
8271
- return rayVsUnitSphereClosestPoint(p, r);
8272
- }
8273
- */
9036
+ initNodeServer=function(doOnClientConnection=null, doOnFinalizeServer=null, /*OPTIONAL*/portParam, /*OPTIONAL*/certPathParam, /*OPTIONAL*/keyPathParam){
8274
9037
 
8275
- /*
8276
- // (needs the jsgl library file !)
8277
- // UNUSED
8278
- // Return the first exterior hit or closest point between the unit
8279
- // sphere and the ray starting at p and going in the r direction.
8280
- function rayVsUnitSphereClosestPoint(p, r) {
8281
- var p_len2 = jsgl.dotProduct(p, p);
8282
- if (p_len2 < 1) {
8283
- // Ray is inside sphere, no exterior hit.
8284
- return null;
8285
- }
9038
+ // TRACE
9039
+ console.log("Server launched.");
9040
+ console.log("Usage : node <server.js> conf {port:[port], sslCertPath:[ssl certificate path | unsecure ], sslKeyPath:[ssl key path], serverConfig:[JSON server configuration]}");
9041
+ console.log("Or (to generate password hash) : node <server.js> hash <clientId@repositoryName> <clearTextSecretString>");
9042
+ // EXAMPLE : node orita-srv.js hash orita.global@saltkey 1234567890
9043
+ console.log("Server launched.");
9044
+
9045
+ // We read the command-line arguments if needed :
9046
+
9047
+ let argCLPort;
9048
+ let argCLCertPath;
9049
+ let argCLKeyPath;
9050
+ let serverConfig={};
9051
+ let isForceUnsecure;
9052
+
9053
+ let isHashAsked=false;
9054
+ let clearTextParam=null;
9055
+ let persisterId=null;
9056
+
9057
+
9058
+ process.argv.forEach(function (val, i){
9059
+ if(!val) return;
9060
+ // 0 corresponds to «node / nodejs»
9061
+ if(i<=1) return; // 1 corresponds to « <server.js> »
9062
+ else if(i==2){
9063
+ if(val==="hash") isHashAsked=true;
9064
+ }else if(i==3){
9065
+ if(!isHashAsked){
9066
+ try{
9067
+ const jsonConf=JSON.parse(val);
9068
+ argCLPort=jsonConf.port;
9069
+ argCLCertPath=jsonConf.sslCertPath;
9070
+ argCLKeyPath=jsonConf.sslKeyPath;
9071
+ serverConfig=nonull({},jsonConf.serverConfig);
9072
+ }catch(err1){
9073
+ lognow("ERROR : Cannot parse argument JSON string «"+val+"».",err1);
9074
+ }
9075
+ } else persisterId=val;
9076
+ }else if(i==4){
9077
+ if(isHashAsked) clearTextParam=val;
9078
+ }
9079
+ });
9080
+ isForceUnsecure=(argCLCertPath==="unsecure");
8286
9081
 
8287
- var along_ray = -jsgl.dotProduct(p, r);
8288
- if (along_ray < 0) {
8289
- // Behind ray start-point.
8290
- return null;
8291
- }
9082
+
9083
+ const aotraNodeServer={config:serverConfig};
9084
+ aotraNodeServer.serverManager={ start:()=>{/*DEFAULT START FUNCTION, WILL BE OVERRIDEN LATER*/}};
8292
9085
 
8293
- var perp = jsgl.vectorAdd(p, jsgl.vectorScale(r, along_ray));
8294
- var perp_len2 = jsgl.dotProduct(perp, perp);
8295
- if (perp_len2 >= 0.999999) {
8296
- // Return the closest point.
8297
- return jsgl.vectorNormalize(perp);
9086
+ if(isHashAsked){
9087
+ // We isntanciate a temporary persister just to read the saltkey file:
9088
+ const persister=getPersister("./");
9089
+ let persisterIdSplits=persisterId.split("@");
9090
+ if(empty(persisterIdSplits) || persisterIdSplits.length!=2){
9091
+ // TRACE
9092
+ console.log("ERROR : No persister repository IDs provided correctly. Cannot read saltkey. Aborting hash generation.");
9093
+ return aotraNodeServer;
9094
+ }
9095
+ const persisterClientId=persisterIdSplits[0];
9096
+ const persisterRepositoryName=persisterIdSplits[1];
9097
+ let globalSaltkeyObject=persister.readTreeObjectFromFile(persisterClientId, persisterRepositoryName);
9098
+ if(!globalSaltkeyObject || !globalSaltkeyObject.saltkey){
9099
+ // TRACE
9100
+ console.log("WARN : No saltkey found. Generating one now.");
9101
+ globalSaltkeyObject={saltkey:getUUID(), hashes:[]};
9102
+ persister.saveDataToFileForClient(persisterClientId,persisterRepositoryName,globalSaltkeyObject,false,()=>{
9103
+ // TRACE
9104
+ console.log("INFO : Saltkey generated and saved successfully.");
9105
+ });
9106
+ }
9107
+ const globalSaltkey=globalSaltkeyObject.saltkey;
9108
+
9109
+ let firstHash=getHashedString(clearTextParam);
9110
+
9111
+ let generatedHash=getHashedString( firstHash + globalSaltkey, true);// (we use the heavy treatment thing.)
9112
+ globalSaltkeyObject.hashes.push(generatedHash);
9113
+
9114
+ // We update the repository :
9115
+ persister.saveDataToFileForClient(persisterClientId,persisterRepositoryName,globalSaltkeyObject,false,()=>{
9116
+ // TRACE
9117
+ console.log("INFO : Hash added to repository and saved successfully.");
9118
+ });
9119
+
9120
+ // OUTPUT
9121
+ console.log("Here is your key : share it with your clients but DO NOT LEAK IT !\n********************\n"+clearTextParam+"\n********************\n");
9122
+
9123
+ return aotraNodeServer;
8298
9124
  }
8299
9125
 
8300
- // Compute intersection.
8301
- var e = Math.sqrt(1 - jsgl.dotProduct(perp, perp));
8302
- var hit = jsgl.vectorAdd(p, jsgl.vectorScale(r, (along_ray - e)));
8303
- return jsgl.vectorNormalize(hit);
8304
- }
8305
- */
9126
+
9127
+ const DEFAULT_PORT=nonull(argCLPort,25000);
9128
+ const DEFAULT_CERT_PATH=nonull(argCLCertPath,"cert.pem");
9129
+ const DEFAULT_KEY_PATH=nonull(argCLKeyPath,"key.key");
8306
9130
 
9131
+
9132
+ let port=portParam ? portParam : DEFAULT_PORT;
9133
+ let certPath=null;
9134
+ let keyPath=null;
8307
9135
 
9136
+ if(!isForceUnsecure){
9137
+ certPath=certPathParam?certPathParam:DEFAULT_CERT_PATH;
9138
+ keyPath=keyPathParam?keyPathParam:DEFAULT_KEY_PATH;
9139
+ }
8308
9140
 
9141
+
9142
+ // UNUSEFUL :
9143
+ //aotraNodeServer.serverManager.microClientsSockets=[];
9144
+ // UNUSEFUL :
9145
+ //aotraNodeServer.serverManager.mainClientsSockets=[];
9146
+ aotraNodeServer.serverManager.start=function(){
8309
9147
 
9148
+ // Eventual encryption options :
9149
+ let sslOptions=null;
9150
+ if(!isForceUnsecure){
9151
+ if(!fs){
9152
+ // TRACE
9153
+ lognow("ERROR : «fs» node subsystem not present, cannot access files. Aborting SSL configuration of server.");
9154
+ }else{
9155
+ try{
9156
+ sslOptions={
9157
+ cert: fs.readFileSync(certPath),
9158
+ key: fs.readFileSync(keyPath),
9159
+ };
9160
+ }catch(exception){
9161
+ // TRACE
9162
+ lognow("ERROR : Could not open SSL files certPath:«"+certPath+"» or keyPath:«"+keyPath+"». Aborting SSL configuration of server.");
9163
+ }
9164
+ }
9165
+ }
9166
+
9167
+ aotraNodeServer.server=launchNodeHTTPServer(port, doOnClientConnection, doOnFinalizeServer, sslOptions);
9168
+
8310
9169
 
9170
+ return aotraNodeServer;
9171
+ };
8311
9172
 
9173
+ return aotraNodeServer;
9174
+ }
8312
9175
 
9176
+ // ========================= FUSRODA SERVER : =========================
8313
9177
 
9178
+ //
9179
+ // DOES NOT WORK : USE Java FusrodaServer instead :
9180
+ //
9181
+ ///*FUSRODA server stands from FSRD SERVER, for Fucking Simple Remote Desktop SERVER*/
9182
+ //createFusrodaServer=function(certPathParam=null,keyPathParam=null,portParam=6080){
9183
+ //
9184
+ // // https://www.npmjs.com/package/screenshot-desktop
9185
+ // // https://github.com/octalmage/robotjs
9186
+ // // npm install --save screenshot-desktop
9187
+ // //
9188
+ // // sudo apt-get install libxtst-dev libx11-dev
9189
+ // // npm install --save robotjs
9190
+ //
9191
+ // // http://getrobot.net/docs/usage.html
9192
+ // //
9193
+ // // apt-get install build-essential python libxt-dev libxtst-dev libxinerama-dev -y
9194
+ //
9195
+ // const screenshot=require("screenshot-desktop");
9196
+ //
9197
+ // const REFRESH_SCREENSHOTS_MILLIS=500;
9198
+ //
9199
+ //
9200
+ // const server=initNodeServer(
9201
+ // // On client connection :
9202
+ //// (serverParam, clientSocketParam)=>{},
9203
+ // null,
9204
+ // // On server finalization :
9205
+ // (serverParam)=>{
9206
+ //
9207
+ // serverParam.receive("protocol_fusroda", (message, clientSocket)=> {
9208
+ // serverParam.addToRoom(clientSocket,"clients");
9209
+ // });
9210
+ //
9211
+ //
9212
+ // serverParam.sendScreenshotsRoutine=setInterval(()=>{
9213
+ // if(serverParam.isScreenshotStarted) return;
9214
+ //
9215
+ // serverParam.isScreenshotStarted=true;
9216
+ //
9217
+ // screenshot().then((img) => {
9218
+ //
9219
+ // const data={image:img,messageType:"imageData"};
9220
+ // serverParam.send("message", data, "clients");
9221
+ //
9222
+ // serverParam.isScreenshotStarted=false;
9223
+ //
9224
+ // }).catch((error) => {
9225
+ // // TRACE
9226
+ // lognow("ERROR : Error during screenshot :",error);
9227
+ // });
9228
+ //
9229
+ // },REFRESH_SCREENSHOTS_MILLIS);
9230
+ //
9231
+ //
9232
+ // },portParam,certPathParam,keyPathParam);
9233
+ //
9234
+ //
9235
+ //// const doOnConnect=(serverParam, clientSocketParam)=>{
9236
+ //// };
9237
+ //// const doOnFinalizeServer=(serverParam)=>{
9238
+ //// /*DO NOTHING*/
9239
+ //// };
9240
+ //// const server={};
9241
+ //// server.start=(port=6080)=>{
9242
+ //// server.httpServer=launchNodeHTTPServer(port, doOnConnect, doOnFinalizeServer, sslOptions);
9243
+ //// };
9244
+ //
9245
+ // return server;
9246
+ //}
8314
9247
 
8315
9248
 
9249
+ // ========================= UTILITY SERVERSIDE METHODS : =========================
8316
9250
 
9251
+ class ListManager{
9252
+ constructor(config){
9253
+ this.config=config;
9254
+ this.maxItemsNumber=nonull(this.config.max,999);
9255
+ this.simultaneousItemsNumber=nonull(this.config.simultaneous,1);
9256
+ this.sessionDurationSeconds=nonull(this.config.duration,null);
9257
+ this.mode=nonull(this.config.mode,"startAtConnexion");
9258
+
9259
+ this.itemsInfos={};
9260
+ this.time=null;
9261
+ this.started=false;
9262
+ }
9263
+
9264
+ addItem(id,item){
9265
+ if(id==null){
9266
+ // TRACE
9267
+ lognow("ERROR : Cannot add item with no id.");
9268
+ return;
9269
+ }
9270
+ if(!item){
9271
+ // TRACE
9272
+ lognow("ERROR : Cannot add null item.");
9273
+ return;
9274
+ }
9275
+ const numberOfItemsCurrently=countKeys(this.itemsInfos);
9276
+ if(this.maxItemsNumber<=numberOfItemsCurrently){
9277
+ // TRACE
9278
+ lognow("ERROR : Cannot add item with id «"+id+"», list already full.");
9279
+ return;
9280
+ }
9281
+ if(numberOfItemsCurrently==0 && this.mode==="startAtConnexion"){
9282
+ this.startSession();
9283
+ }
9284
+ this.itemsInfos[id]={
9285
+ item:item,
9286
+ time:getNow(),
9287
+ };
9288
+ }
9289
+
9290
+ startSession(){
9291
+ this.time==getNow();
9292
+ this.started=true;
9293
+ }
9294
+
9295
+ stopSession(){
9296
+ this.started=false;
9297
+ }
9298
+
9299
+ isSessionActive(){
9300
+ if(!this.sessionDurationSeconds) return true;
9301
+ if(!this.started) return false;
9302
+ return !hasDelayPassed(this.time, this.sessionDurationSeconds*1000);
9303
+ }
9304
+
9305
+ isClientActive(clientId){
9306
+ if(!clientsListManager.isSessionActive()) return false;
9307
+ const clientPosition=this.getItemPosition(clientId);
9308
+ const result=(clientPosition<=this.maxItemsNumber);
9309
+ return result;
9310
+ }
9311
+
9312
+ removeItem(id){
9313
+ if(id==null){
9314
+ // TRACE
9315
+ lognow("ERROR : Cannot remove item, no id specified.");
9316
+ return;
9317
+ }
9318
+ if(!this.itemsInfos[id]){
9319
+ // TRACE
9320
+ lognow("ERROR : Cannot remove item, item not found for id «"+id+"».");
9321
+ return;
9322
+ }
9323
+ delete this.itemsInfos[id];
9324
+ }
8317
9325
 
9326
+ // Goes from 1 to <length>:
9327
+ getItemPosition(id){
9328
+ if(id==null){
9329
+ // TRACE
9330
+ lognow("ERROR : Cannot calculate item position, no id specified.");
9331
+ return;
9332
+ }
9333
+ if(!this.itemsInfos[id]){
9334
+ // TRACE
9335
+ lognow("ERROR : Cannot calculate item position, item not found for id «"+id+"».");
9336
+ return;
9337
+ }
9338
+ let result=0;
9339
+ foreach(this.itemsInfos,(itemInfo,key)=>{
9340
+ result++;
9341
+ if(id==key) return "break";
9342
+ },null,(itemInfo1,itemInfo2)=>item1.time<item2.time);
9343
+ return result;
9344
+ }
8318
9345
 
9346
+
9347
+ }
8319
9348
 
9349
+ function getListManager(config){
9350
+ return new ListManager(config);
9351
+ }
8320
9352
 
8321
9353
 
8322
9354
 
@@ -8324,10 +9356,8 @@ function rayVsUnitSphereClosestPoint(p, r) {
8324
9356
 
8325
9357
 
8326
9358
 
8327
- // MUST REMAIN AT THE END OF THIS LIBRARY FILE !
8328
9359
 
8329
- AOTRAUTILS_GEOMETRY_LIB_IS_LOADED=true;
8330
- /*utils COMMONS library associated with aotra version : «1.0.0.000 (11/07/2022-23:49:31)»*/
9360
+ /*utils COMMONS library associated with aotra version : «1.0.0.000 (12/07/2022-23:38:27)»*/
8331
9361
  /*-----------------------------------------------------------------------------*/
8332
9362
 
8333
9363
 
@@ -9668,105 +10698,398 @@ aotest.run=function(testName=null,scenarioName=null){
9668
10698
 
9669
10699
  var buildHRReport=function(resultObj){
9670
10700
 
9671
- let str="";
9672
-
9673
- let resultsFailed=getCumulatedInt(resultObj,"childrenFailed");
9674
- let resultsRunned=getCumulatedInt(resultObj,"childrenRunned");
9675
-
10701
+ let str="";
10702
+
10703
+ let resultsFailed=getCumulatedInt(resultObj,"childrenFailed");
10704
+ let resultsRunned=getCumulatedInt(resultObj,"childrenRunned");
10705
+
10706
+
10707
+ // TRACE
10708
+ str+="@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n";
10709
+ str+=" AOTEST REPORT \n";
10710
+ str+="REPORT: "+resultsFailed + "/" + resultsRunned + " scenarii failed.\n";
10711
+ str+="REPORT: "+(resultsRunned - resultsFailed) + "/" + resultsRunned + " scenarii succeeded.\n";
10712
+ str+="REPORT: "+resultsRunned + " scenarii runned.\n";
10713
+ str+="@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n";
10714
+
10715
+ if(0<resultsRunned){
10716
+ if(!resultObj.global.isFailed){
10717
+ // TRACE
10718
+ str+="LOG: RESULT :>>>>>> All tests SUCCEEDED ! :D\n";
10719
+ } else {
10720
+ // TRACE
10721
+ str+="LOG: RESULT :>>>>>> Some tests FAILED... :(\n";
10722
+ }
10723
+ } else {
10724
+ // TRACE
10725
+ str+="LOG: RESULT :>>>>>> No tests runned... :|\n";
10726
+ }
10727
+
10728
+ return str;
10729
+ };
10730
+
10731
+
10732
+ var getCumulatedInt=function(resultObj, attrName){
10733
+
10734
+ let result=0;
10735
+
10736
+ result+=resultObj.global[attrName];
10737
+
10738
+ foreach(resultObj.children,(child)=>{
10739
+ result+=getCumulatedInt(child, attrName);
10740
+ });
10741
+
10742
+ return result;
10743
+ };
10744
+
10745
+
10746
+
10747
+
10748
+ // TRACE
10749
+ console.log( buildHRReport(allTestsResultObj));
10750
+
10751
+
10752
+ // DBG
10753
+ console.log("TOTAL RESULT :",allTestsResultObj);
10754
+
10755
+
10756
+ aotest.isRunning=false;
10757
+
10758
+ return allTestsResultObj;
10759
+ };
10760
+
10761
+
10762
+
10763
+ aotest.profile=function(rootObject,methodName,visited=[]){
10764
+
10765
+
10766
+ //if(!rootObject || !isObject(rootObject) || contains(visited,rootObject)) return;
10767
+ if( !rootObject || isPrimitive(rootObject) || isFunction(rootObject)
10768
+ || contains(visited,rootObject)) return;
10769
+
10770
+ const THRESHOLD_MILLIS=10;
10771
+
10772
+ visited.push(rootObject);
10773
+
10774
+ let method=rootObject[methodName];
10775
+ if(method && isFunction(method)){
10776
+ rootObject[methodName]=function(){
10777
+
10778
+ let time=getNow();
10779
+
10780
+ method.apply(rootObject, arguments);
10781
+
10782
+ let deltaMillis=(getNow()-time);
10783
+
10784
+ // TRACE
10785
+ if(THRESHOLD_MILLIS<=deltaMillis) lognow("Duration ["+deltaMillis+"ms] of «"+methodName+"» on object:"+getClassName(rootObject),rootObject);
10786
+ };
10787
+ }
10788
+
10789
+ foreach(rootObject,(attr,attrName)=>{
10790
+
10791
+ // DBG
10792
+ if(attrName==="currentContainer") lognow("currentContainer:",attr);
10793
+
10794
+ aotest.profile(attr,methodName,visited);
10795
+
10796
+ });
10797
+
10798
+
10799
+ };
10800
+
10801
+
10802
+
10803
+
10804
+
10805
+ //================================================================
10806
+ //================= Cryptography utility methods =================
10807
+ //================================================================
10808
+
10809
+
10810
+ // NOT AOTESTABLE !
10811
+ //TODO : develop :
10812
+ //getHashedString=monitorProgression(100000,
10813
+ window.getHashedString=function(str,/*OPTIONAL*/isHeavyTreatment){
10814
+
10815
+ //CAUTION : YOU MUST *NOT* CHANGE THIS VALUE, UNLESS THE HASH HEAVY TREATMENT VALUE IN YOUR AOTRA SERVER-SIDE CODEIS THE SAME !
10816
+ const HEAVY_TREATMENT_LOOP_COUNT=100000;
10817
+
10818
+ if(isHeavyTreatment){
10819
+
10820
+ // If needed, we make sure the hashing will take a long time, so that
10821
+ // if the private salt key is leaked, it will be fairly difficult to crack
10822
+ // password write protection and content encryption :
10823
+
10824
+ let result=str;
10825
+ for(var i=0;i<HEAVY_TREATMENT_LOOP_COUNT;i++){
10826
+ result=getHashedString(result,false);
10827
+ }
10828
+
10829
+
10830
+ return result;
10831
+
10832
+ }else{
10833
+
10834
+
10835
+ // SHA-256 hash (sucks less) :
10836
+ // DEPENDENCY : SJCL JS libray (Stanford Javascript Crypto Library)
10837
+ if(typeof(sjcl)!="undefined"){
10838
+ let result=sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(str));
10839
+
10840
+ // TODO : develop :
10841
+ // hash.progressionMonitor.progress(1);
10842
+
10843
+
10844
+ return result;
10845
+ }
10846
+
10847
+ // if not found : MD5 hash (sucks) :
10848
+ //TRACE
10849
+ log("WARN : Could not find SHA hashing, using MD5. This sucks, and this very likely will prevent your aotra client to be able to write data to aotra server.");
10850
+
10851
+ return hex_md5(str);
10852
+ }
10853
+ }
10854
+ //);
10855
+
10856
+ function encrypt(strToEncrypt,key,/*OPTIONAL*/mode){
10857
+ if(!mode || typeof(sjcl)=="undefined") mode="xor";
10858
+ mode=mode.trim();
10859
+
10860
+ if(contains(mode,"aes") && contains(mode,"xor")){
10861
+
10862
+ // XOR :
10863
+ var xorStr=btoa(encodeXORNoPattern(strToEncrypt,key));
10864
+
10865
+ var aesStr=
10866
+ // AES
10867
+ sjcl.encrypt(key,xorStr);
10868
+
10869
+ return aesStr;
10870
+ }
10871
+
10872
+ // DEPENDENCY : SJCL JS libray (Stanford Javascript Crypto Library)
10873
+ if(mode==="aes")
10874
+ return sjcl.encrypt(key,strToEncrypt);
10875
+ return btoa(encodeXORNoPattern(strToEncrypt,key));
10876
+
10877
+ }
10878
+
10879
+ function decrypt(strToDecrypt,key,/*OPTIONAL*/mode){
10880
+ if(!mode || typeof(sjcl)=="undefined") mode="xor";
10881
+ mode=mode.trim();
10882
+
10883
+ if(contains(mode,"aes") && contains(mode,"xor")){
9676
10884
 
9677
- // TRACE
9678
- str+="@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n";
9679
- str+=" AOTEST REPORT \n";
9680
- str+="REPORT: "+resultsFailed + "/" + resultsRunned + " scenarii failed.\n";
9681
- str+="REPORT: "+(resultsRunned - resultsFailed) + "/" + resultsRunned + " scenarii succeeded.\n";
9682
- str+="REPORT: "+resultsRunned + " scenarii runned.\n";
9683
- str+="@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n";
10885
+ // AES
10886
+ var aesStr=sjcl.decrypt(key,strToDecrypt);
9684
10887
 
9685
- if(0<resultsRunned){
9686
- if(!resultObj.global.isFailed){
9687
- // TRACE
9688
- str+="LOG: RESULT :>>>>>> All tests SUCCEEDED ! :D\n";
9689
- } else {
9690
- // TRACE
9691
- str+="LOG: RESULT :>>>>>> Some tests FAILED... :(\n";
9692
- }
9693
- } else {
9694
- // TRACE
9695
- str+="LOG: RESULT :>>>>>> No tests runned... :|\n";
9696
- }
10888
+ var xorStr=
10889
+ // XOR :
10890
+ encodeXORNoPattern(atob(aesStr),key);
10891
+
10892
+ return xorStr;
9697
10893
 
9698
- return str;
9699
- };
10894
+ }
9700
10895
 
10896
+ // DEPENDENCY : SJCL JS libray (Stanford Javascript Crypto Library)
10897
+ if(mode==="aes")
10898
+ return sjcl.decrypt(key,strToDecrypt);
10899
+ return encodeXORNoPattern(atob(strToDecrypt),key);
10900
+
10901
+
10902
+ }
9701
10903
 
9702
- var getCumulatedInt=function(resultObj, attrName){
9703
-
9704
- let result=0;
10904
+ function encodeXORNoPattern(strToEncode,key){
10905
+ let result="";
10906
+ let keyChunk=key;
10907
+ let keyLength=keyChunk.length;
10908
+ let previousChars="";
10909
+ let cnt=0;
10910
+ for(let i=0;i<strToEncode.length;i++){
10911
+ let char=strToEncode.charAt(i);
10912
+ let keyByte=keyChunk.charAt(cnt);
9705
10913
 
9706
- result+=resultObj.global[attrName];
10914
+ result+=String.fromCharCode(char.charCodeAt(0)^keyByte.charCodeAt(0));
9707
10915
 
9708
- foreach(resultObj.children,(child)=>{
9709
- result+=getCumulatedInt(child, attrName);
9710
- });
10916
+ if(cnt%keyLength===(keyLength-1)){
10917
+ cnt=0;
10918
+ // NO : TOO LONG TREATMENT !: We hash with all the previous blocks too :
10919
+ // We hash with a character of the previous chunk (they add up at each new chunk):
10920
+ keyChunk=getHashedString(previousChars+keyChunk);
9711
10921
 
9712
- return result;
9713
- };
10922
+ previousChars+=keyChunk.charAt(keyChunk.length-1);
10923
+ }else{
10924
+ cnt++;
10925
+ }
10926
+ }
10927
+ return result;
10928
+ }
9714
10929
 
10930
+ function generateRandomString(length,/*NULLABLE*/mode){
9715
10931
 
10932
+ // This list must be very conservative, because we want to be able to pass it through GET URLs !
10933
+ // OLD (not enough conservative ?) : const ALLOWED_CHARS="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0912346789_-£¢¤¬²³¼½¾";
10934
+ const ALLOWED_CHARS="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0912346789!-:;";
9716
10935
 
9717
10936
 
9718
- // TRACE
9719
- console.log( buildHRReport(allTestsResultObj));
9720
-
10937
+ if(typeof(length)==="string"){
10938
+ if(contains(length,"->")){
10939
+ length=Math.getRandomInRange(length);
10940
+ }else{
10941
+ length=64;
10942
+ }
10943
+ }
9721
10944
 
9722
- // DBG
9723
- console.log("TOTAL RESULT :",allTestsResultObj);
10945
+ var result="";
9724
10946
 
9725
-
9726
- aotest.isRunning=false;
9727
-
9728
- return allTestsResultObj;
9729
- };
10947
+ if(mode==="simple"){
10948
+
10949
+ for(var i=0;i<length;i++){
10950
+ var value=Math.getRandomInArray(ALLOWED_CHARS);
10951
+ result+=value;
10952
+ }
10953
+
10954
+ return result;
10955
+ }else if(mode==="human"){
10956
+
10957
+ const markovGraph={
10958
+ // For simplicity sakes, we ignore the consonants and vowels doubling possibilities:
10959
+ consonants:{// They always accept vowels at their following
10960
+ // Indicate ^ to specify wich of the other group letter to exclude as direct follower of the given letter :
10961
+ "b":["r","l"],
10962
+ "c":["h","t","k","l","r"],
10963
+ "d":["r"],
10964
+ "f":["r","l"],
10965
+ "g":["r","l"],
10966
+ "h":[],
10967
+ "j":["r"],
10968
+ "k":["r","l","t","h"],
10969
+ "l":["t"],
10970
+ "m":["b","p"],
10971
+ "n":["t","d"],
10972
+ "p":["h","r","s","t","l"],
10973
+ "q":["r","l"],
10974
+ "r":["t","c","d","k","q","h"],
10975
+ "s":["t","c","d","k","q","h"],
10976
+ "t":["r","h"],
10977
+ "v":["r","l"],
10978
+ "w":["r"],
10979
+ "x":["p"],
10980
+ "z":["r","h"],
10981
+ },
10982
+ vowels:{// They always accept consonants at their following
10983
+ "a":["i","u","y"],
10984
+ "e":["i","u","y"],
10985
+ "i":["e","o"],
10986
+ "o":["i","u"],
10987
+ "u":["i"],
10988
+ "y":[],
10989
+ },
10990
+ };
10991
+
10992
+ const LETTERS_TYPES=Object.keys(markovGraph);
9730
10993
 
10994
+ const CURR_LETTER_MODE=Math.getRandomInArray(LETTERS_TYPES);
10995
+ const POSSIBLE_LETTERS=Object.keys(markovGraph[CURR_LETTER_MODE]);
10996
+ const CURR_LETTER=Math.getRandomInArray(POSSIBLE_LETTERS);
10997
+
10998
+ var automataState={
10999
+ currentLetterMode:CURR_LETTER_MODE,
11000
+ possibleLetters:POSSIBLE_LETTERS,
11001
+ currentLetter:CURR_LETTER,
11002
+
11003
+ calculateLetter:function(){
11004
+
11005
+ this.currentLetter=Math.getRandomInArray(this.possibleLetters);
11006
+
11007
+ var sameLetters=Object.keys( markovGraph[this.currentLetterMode] );
11008
+ if(!contains(sameLetters,this.currentLetter)){
11009
+ // If we picked from the same group : we don't change of group to pick
11010
+ // If we picked from the other group : we do change of group to pick
11011
+
11012
+ var otherLetterMode=foreach(LETTERS_TYPES,(item)=>{
11013
+ if(item!==this.currentLetterMode) return item;
11014
+ });
11015
+
11016
+ this.currentLetterMode=otherLetterMode;
11017
+ }
9731
11018
 
11019
+ },
11020
+
11021
+ updatePossibleLettersForLetter:function(){
11022
+
11023
+ var nextPossibleLetters=[];
11024
+ var nextNotPossibleLetters=[];
9732
11025
 
9733
- aotest.profile=function(rootObject,methodName,visited=[]){
9734
-
9735
-
9736
- //if(!rootObject || !isObject(rootObject) || contains(visited,rootObject)) return;
9737
- if( !rootObject || isPrimitive(rootObject) || isFunction(rootObject)
9738
- || contains(visited,rootObject)) return;
11026
+ // We store those of the other group marked «^»
11027
+ foreach(markovGraph[this.currentLetterMode][this.currentLetter],(letter)=>{
11028
+ if(letter.charAt(0)==="^"){
11029
+ nextNotPossibleLetters.push(letter.charAt(1));
11030
+ }else{
11031
+ nextPossibleLetters.push(letter);
11032
+ }
11033
+ });
11034
+
11035
+ var otherLetterMode=foreach(LETTERS_TYPES,(item)=>{
11036
+ if(item!==this.currentLetterMode) return item;
11037
+ });
11038
+ var otherLetters=Object.keys(markovGraph[otherLetterMode]);
11039
+
11040
+ // We exclude those marked «^»
11041
+ foreach( otherLetters ,(letter)=>{
11042
+ if(!contains(nextNotPossibleLetters,letter)){
11043
+ nextPossibleLetters.push(letter);
11044
+ }
11045
+ });
9739
11046
 
9740
- const THRESHOLD_MILLIS=10;
11047
+ this.possibleLetters=nextPossibleLetters;
11048
+ }
11049
+ };
11050
+
11051
+ var i=0;
11052
+ do{
9741
11053
 
9742
- visited.push(rootObject);
9743
-
9744
- let method=rootObject[methodName];
9745
- if(method && isFunction(method)){
9746
- rootObject[methodName]=function(){
9747
-
9748
- let time=getNow();
9749
-
9750
- method.apply(rootObject, arguments);
11054
+ result+=automataState.currentLetter;
9751
11055
 
9752
- let deltaMillis=(getNow()-time);
11056
+ automataState.updatePossibleLettersForLetter();
11057
+ automataState.calculateLetter();
9753
11058
 
9754
- // TRACE
9755
- if(THRESHOLD_MILLIS<=deltaMillis) lognow("Duration ["+deltaMillis+"ms] of «"+methodName+"» on object:"+getClassName(rootObject),rootObject);
9756
- };
11059
+ i++;
11060
+ }while(i<length);
11061
+
11062
+ return result;
9757
11063
  }
9758
11064
 
9759
- foreach(rootObject,(attr,attrName)=>{
9760
-
9761
- // DBG
9762
- if(attrName==="currentContainer") lognow("currentContainer:",attr);
9763
-
9764
- aotest.profile(attr,methodName,visited);
9765
-
9766
- });
11065
+ for(var i=0;i<length;i++){
11066
+ var value=Math.getRandomInt(255,0);
11067
+ result+=String.fromCharCode(value);
11068
+ }
9767
11069
 
11070
+ if(mode==="base64"){
11071
+ return btoa(result);
11072
+ }
9768
11073
 
9769
- };
11074
+ return result;
11075
+ }
11076
+
11077
+
11078
+
11079
+ // NOT AOTESTABLE !
11080
+ function getUniqueIdWithDate(){
11081
+ return getHashedString(getNow() + "");
11082
+ }
11083
+
11084
+
11085
+
11086
+
11087
+
11088
+
11089
+ //================================================================
11090
+ //================= Miscelanneous utility methods =================
11091
+ //================================================================
11092
+
9770
11093
 
9771
11094
 
9772
11095