{"version":3,"file":"app-e161f048.981ca8458d389c9a476a.bundle.js","mappings":";;;;;;;;;;;;;;;;;AAAA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AAAA;AAIA;AACA;AACA;AAEA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AAEA;;;;;;;;;;;;;;;;;;;;;;AC7BA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAGA;;AAKA;AAAA;AAIA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;AClDA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;;;;;;;;;;;;;;ACbA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;ACjBA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;AC7BA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AAOA;AANA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;ACnEA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAGA;AAGA;AAGA;;AAKA;AAAA;AASA;AAMA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAEA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;;;;;;;;;;;;;;;;;;ACtFA;;AAEA;AACA;AACA;;AAKA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AAGA;AAAA;AAAA;AACA;AACA;AAEA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;AChEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;ACzFA;;AAEA;AACA;AACA;AACA;;AAMA;AACA;;AAKA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AAGA;AAAA;AAAA;;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;AC/IA;;AAEA;AACA;AACA;;AAEA;AACA;;AAIA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;ACnFA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;AC3FA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAGA;AACA;AAKA;AAHA;AAAA;AAIA;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;;ACtDA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAKA;AACA;;AAGA;AACA;AACA;AACA;AACA;AASA;AARA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;AChGA;;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;ACjCA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AAAA;AAUA;AAOA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;;;;;;;;;;;;;;;;;;ACnEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;AAGA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;ACrEA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;AChDA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAIA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AAEA;AACA;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;ACjFA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AAAA;AAOA;AAIA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAEA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAEA;;;;;;;;;;;;;;;;;ACzDA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;AC5CA;;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;ACvCA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;ACzFA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;;;;;;;;;;;;;;;AC/DA;;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;;;;;;;;;;;;;;ACrCA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;;;;;;;;;;;;;;;;;;;AClEA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAGA;AAAA;AAOA;AAIA;AACA;AACA;;AAGA;AACA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;ACrDA;;AAEA;AACA;;AAEA;AACA;;AAKA;AACA;;AAIA;AACA;AACA;AACA;AACA;AAGA;AAFA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;AC3DA;;AAEA;AACA;;AAEA;AACA;;AAMA;AACA;AACA;AACA;AACA;AAIA;AAHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;AC1DA;;AAEA;AACA;;AAEA;AACA;;AAGA;AAAA;AAMA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5BA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAIA;;AAMA;AACA;AAKA;;AAKA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAKA;AAAA;AAkBA;AANA;AAAA;AACA;AACA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAAA;AAAA;AACA;AACA;AAEA;AAEA;AAAA;AAAA;AACA;AACA;AAEA;AAEA;AAAA;AAAA;AACA;AACA;AAEA;AAEA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAAA;AAAA;AACA;AACA;AAEA;AAEA;AAAA;AAAA;AACA;AACA;AAEA;AAEA;AAAA;AAAA;AACA;AACA;AAEA;AAEA;AAAA;AAAA;AACA;AACA;AAEA;AAEA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AAEA;AAEA;AAAA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAEA;;;;;;;;;;;;;;;;AC3RA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;ACxBA;;AAEA;AACA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;AC1CA;;AAEA;AACA;;AAEA;AACA;;AAKA;AACA;;AAKA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;ACxEA;;AAEA;AACA;;AAKA;AACA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AAKA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;AC7BA;;AAEA;AACA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAAA;AAAA;AACA;AAEA;;;;;;;;;;;;;;;;AChDA;;AAEA;AACA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;AClCA;;AAEA;AACA;;AAEA;AACA;AACA;AAIA;;AAKA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;;AAEA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;;AAGA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;;AAGA;AACA;;AAEA;AACA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;AC5GA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AAEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;AC3BA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;ACxBA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;ACvBA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;;;;;;;;;;;;;;;AC7BA;;AAEA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;;;;;;;;;;;;;;;;;;ACvBA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAGA;AAAA;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;;;AC1BA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA","sources":["webpack://latinera/./sources/services/locale.js","webpack://latinera/./sources/services/notification.js","webpack://latinera/./sources/services/notification/close-ui-notifications.js","webpack://latinera/./sources/services/notification/request-system-notifications-permission.js","webpack://latinera/./sources/services/notification/show-system-notification.js","webpack://latinera/./sources/services/notification/show-ui-notification.js","webpack://latinera/./sources/services/offer.js","webpack://latinera/./sources/services/offer/activate-offer-for-free.js","webpack://latinera/./sources/services/offer/get-activated-offers-by-filter.js","webpack://latinera/./sources/services/offer/get-offer-activation-status.js","webpack://latinera/./sources/services/offer/get-offer-additional-discounts.js","webpack://latinera/./sources/services/offer/get-offers-by-filter.js","webpack://latinera/./sources/services/pay.js","webpack://latinera/./sources/services/pay/fetch.js","webpack://latinera/./sources/services/pay/is-alive.js","webpack://latinera/./sources/services/sentence.js","webpack://latinera/./sources/services/sentence/add-sentence-author.js","webpack://latinera/./sources/services/sentence/create-sentence.js","webpack://latinera/./sources/services/sentence/get-sentences-by-filter.js","webpack://latinera/./sources/services/state.js","webpack://latinera/./sources/services/state/load-states-by-activity.js","webpack://latinera/./sources/services/state/purge-states-by-activity.js","webpack://latinera/./sources/services/state/push-state-by-activity.js","webpack://latinera/./sources/services/state/redo-state-by-activity.js","webpack://latinera/./sources/services/state/save-states-by-activity.js","webpack://latinera/./sources/services/state/undo-state-by-activity.js","webpack://latinera/./sources/services/stripe.js","webpack://latinera/./sources/services/stripe/cancel-payment-intent.js","webpack://latinera/./sources/services/stripe/create-payment-intent.js","webpack://latinera/./sources/services/task.js","webpack://latinera/./sources/services/user.js","webpack://latinera/./sources/services/user/change-password.js","webpack://latinera/./sources/services/user/charge-user-credit-for-activity-creation.js","webpack://latinera/./sources/services/user/close-session.js","webpack://latinera/./sources/services/user/conditionally-extend-session.js","webpack://latinera/./sources/services/user/extend-session.js","webpack://latinera/./sources/services/user/get-auth-users-stats.js","webpack://latinera/./sources/services/user/open-session.js","webpack://latinera/./sources/services/user/patch-user-data.js","webpack://latinera/./sources/services/user/request-create.js","webpack://latinera/./sources/services/user/request-reset-password.js","webpack://latinera/./sources/services/user/set-ui-locale-code.js","webpack://latinera/./sources/services/user/update-settings.js","webpack://latinera/./sources/services/validation.js","webpack://latinera/./sources/services/validation/get-validator.js"],"sourcesContent":["// Define the \"locale\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\nimport { I18N } from \"aurelia-i18n\";\n\n// Import parameter modules\nimport { localesData } from \"parameters/locale\";\n\n\n// Export the \"LocaleService\" class\n@inject(I18N)\nexport default class LocaleService {\n\n constructor(i18n) {\n this.i18n = i18n;\n }\n\n getUILocalesData() {\n return localesData.filter(({ uiEnabled }) => uiEnabled);\n }\n\n getLocaleDataByCode(localeCode) {\n if (!localeCode) {\n throw new Error(`Missing required parameter \"localeCode\"`);\n }\n return localesData.find(({ code }) => code === localeCode);\n }\n\n}\n","// Define the \"notification\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\nimport { I18N } from \"aurelia-i18n\";\n\n// Import method modules\nimport { showUINotification } from \"./notification/show-ui-notification\";\nimport { closeUINotifications } from \"./notification/close-ui-notifications\";\nimport {\n requestSystemNotificationsPermission\n} from \"./notification/request-system-notifications-permission\";\nimport {\n showSystemNotification\n} from \"./notification/show-system-notification\";\n\n\n// Export the class NotificationService\n@inject(I18N)\nexport default class NotificationService {\n\n constructor(i18n) {\n this.i18n = i18n;\n }\n\n get areSystemNotificationsSupported() {\n return (\"Notification\" in window);\n }\n\n get systemNotificationsPermission() {\n return this.areSystemNotificationsSupported ?\n Notification.permission : \"unsupported\";\n }\n\n async requestSystemNotificationsPermission() {\n return await requestSystemNotificationsPermission.call(this);\n }\n\n showUINotification(notificationData) {\n showUINotification.call(this, notificationData);\n }\n\n showSystemNotification(notificationData) {\n showSystemNotification.call(this, notificationData);\n }\n\n closeUINotifications(notificationsGroup) {\n closeUINotifications.call(this, notificationsGroup);\n }\n\n}\n","// Define the \"closeUINotifications\" notification service method\n\n// Import library modules\nimport UIkit from \"uikit\";\n\n// Import parameter modules\nimport { ui as uiNotificationParams } from \"parameters/notification\";\n\n\n// closeUINotifications: sync\n// closes all UI notifications from the specified group\nexport function closeUINotifications(group = uiNotificationParams.group) {\n UIkit.notification.closeAll(group);\n}\n","// Define the \"requestSystemNotificationsPermission\" notification service\n// method\n\nexport async function requestSystemNotificationsPermission() {\n let permission;\n if (this.areSystemNotificationsSupported) {\n if (this.systemNotificationsPermission === \"default\") {\n permission = await Notification.requestPermission();\n } else {\n permission = this.systemNotificationsPermission;\n }\n console.info(`User ${permission} the use of system notifications`);\n } else {\n permission = \"unsupported\";\n console.warn(`Host does not support system notifications`);\n }\n return permission;\n}\n","// Define the \"showSystemNotification\" notification service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport { system as defaultParams } from \"parameters/notification\";\n\n\n// showSystemNotification: sync\n// shows a system notification according to the specified message and\n// parameters\n// note: if message is an i18n key it will be translated, otherwise it\n// will stay as it is\nexport function showSystemNotification({\n message, \n params = null\n}) {\n if (typeOf(message) !== \"string\" || !message) {\n throw new Error(\"System notification message should be a non-empty string\");\n } else if (!this.i18n) {\n throw new Error(`Missing required \"i18n\" service`);\n }\n\n if (this.systemNotificationsPermission === \"granted\") {\n const i18nMessage = this.i18n.tr(message); // in case message is an i18nKey\n const finalParams = { ...defaultParams, ...(params || {}) };\n new Notification(i18nMessage, finalParams);\n }\n}\n","// Define the \"showUINotification\" notification service method\n\n// Import library modules\nimport UIkit from \"uikit\";\n\n// Import utility modules\nimport { capitalizeFirst } from \"utilities/string\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport { ui as uiNotificationParams } from \"parameters/notification\";\n\n\n// showUINotification: sync\n// shows a UI notification according to the specified message and\n// parameters\n// note: if message is an i18n key it will be translated, otherwise it\n// will stay as it is\nexport function showUINotification({\n message = \"\",\n params = {},\n status = uiNotificationParams.status,\n timeout = uiNotificationParams.timeout,\n position: pos = uiNotificationParams.position,\n group = uiNotificationParams.group\n}) {\n if (typeOf(message) !== \"string\" || !message) {\n throw new Error(\"Notification message should be a non-empty string\");\n } else if (!this.i18n) {\n throw new Error(`Missing required \"i18n\" service`);\n }\n\n\n const i18nMessage = this.i18n.tr(message, params); // in case message was an i18nKey\n let htmlIcon;\n switch (status) {\n case \"primary\":\n htmlIcon = ``;\n break;\n case \"success\":\n htmlIcon = `<i class=\"fas fa-check\"></i>`;\n break;\n case \"warning\":\n htmlIcon = `<i class=\"fas fa-exclamation\"></i>`;\n break;\n case \"failure\": // converted to \"danger\" as expected by notification\n status = \"danger\";\n htmlIcon = `<i class=\"fas fa-times\"></i>`;\n break;\n case \"danger\":\n htmlIcon = `<i class=\"fas fa-times\"></i>`;\n break;\n default:\n throw new Error(`Unknown notification status \"${status}\"`);\n }\n const htmlMessage = `\n <div class=\"uk-flex uk-flex-top\">\n <div class=\"uk-width-auto\">\n ${htmlIcon}\n </div>\n <div class=\"uk-width-expand uk-text uk-text-strong\n uk-margin-small-left\">\n ${capitalizeFirst(i18nMessage)}\n </div>\n </div>\n `;\n UIkit.notification({ message: htmlMessage, status, timeout, pos, group });\n}\n","// Define the \"offer\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport EntityService from \"services/entity\";\nimport GraphQLService from \"services/graphql\";\nimport IndexService from \"services/index\";\nimport UserService from \"services/user\";\n\n// Import methods modules\nimport { getOffersByFilter } from \"./offer/get-offers-by-filter\";\nimport { \n getActivatedOffersByFilter \n} from \"./offer/get-activated-offers-by-filter\";\nimport {\n getOfferAdditionalDiscounts\n} from \"./offer/get-offer-additional-discounts\";\nimport { \n getOfferActivationStatus \n} from \"./offer/get-offer-activation-status\";\nimport {\n activateOfferForFree\n} from \"./offer/activate-offer-for-free\";\n\n\n// Export the \"OfferService\" class\n@inject(\n EntityService,\n GraphQLService, \n IndexService, \n UserService\n)\nexport default class OfferService {\n\n constructor(\n entityService,\n graphQLService, \n indexService, \n userService\n ) {\n this.entityService = entityService;\n this.graphQLService = graphQLService;\n this.indexService = indexService;\n this.userService = userService;\n }\n\n\n // Core methods\n async getOfferByKey(offerKey) {\n return await this.entityService.getEntityByKey({\n indexDatabaseName: \"global\",\n entityName: \"offer\",\n entityKey: offerKey || \"\"\n });\n }\n\n async getOffersByKeys(offersKeys) {\n return await this.entityService.getEntitiesByKeys({\n indexDatabaseName: \"global\",\n entityName: \"offer\",\n entitiesKeys: offersKeys || []\n });\n }\n\n async getOffersByFilter(filterData) {\n return await getOffersByFilter.call(this, filterData);\n }\n\n async getActivatedOffersByFilter(filterData) {\n return await getActivatedOffersByFilter.call(this, filterData);\n }\n\n async getOfferAdditionalDiscounts(offerKey) {\n return await getOfferAdditionalDiscounts.call(this, offerKey);\n }\n\n async activateOfferForFree({ offerKey = \"\" }) {\n return await activateOfferForFree.call(this, { offerKey });\n }\n\n async getOfferActivationStatus({ offerKey = \"\", paymentId = \"\" }) {\n return await getOfferActivationStatus.call(this, { offerKey, paymentId });\n }\n\n}\n","// Define the \"activateOfferForFree\" service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\nimport {\n unsetFalsyProperties,\n pickProperties\n} from \"utilities/object\";\n\n// Import query modules\nimport { generateActivateOfferForFreeQueryData } from \"queries/offer\";\n\n\n// activateOfferForFree: async\n// returns a promise which resolves either with an object whose fields are\n// the \"offer\" vertex and \"activates\" edge of an offer (by key) that has \n// been activated for free, or with null if the operation failed\nexport async function activateOfferForFree({\n offerKey = \"\"\n}) {\n if (typeOf(offerKey) !== \"string\" || !offerKey) {\n throw new Error(`Parameter \"offerKey\" should be a key string`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Activate offer for free on the database\n const queryData = generateActivateOfferForFreeQueryData({ \n offerKey\n });\n let offerActivationData;\n try {\n const responseData = await this.graphQLService.query({ \n queryData,\n authenticated: true\n });\n ({ activateOfferForFree: offerActivationData = null } = responseData || {});\n } catch(error) {\n console.warn(`Error activating offer \"${offerKey}\" for free ` +\n `on the server`, error);\n return null;\n }\n\n if (offerActivationData) {\n console.info(`Activated offer \"${offerKey}\" for free on the server`);\n\n // Patches user data with updated values (after successful activation)\n const { userData: updatedUserData = null } = offerActivationData || {};\n const userPatchData = unsetFalsyProperties(\n pickProperties(updatedUserData, [ \"isCreditBound\", \"credit\" ])\n );\n this.userService.patchUserData({ userPatchData });\n } else {\n console.warn(`Could not activate offer \"${offerKey}\" for free ` +\n `on the server`);\n }\n\n // Return offers' data\n return offerActivationData;\n}\n","// Define the \"getActivatedOffersByFilter\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { toString } from \"utilities/object\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateActivatedOffersByFilterQueryData } from \"queries/offer\";\n\n\n// getActivatedOffersByFilter: async\n// returns a promise which either resolves with an array of offer data\n// objects activated by the user and matching the specified filter data \n// object, or rejects with an error\nexport async function getActivatedOffersByFilter({\n filterData = null,\n fetchFromIndex = true,\n saveToIndex = true\n}) {\n if (typeOf(filterData) !== \"object\") {\n throw new Error(`Parameter \"filterData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n const filterString = toString(filterData);\n const cacheKey = computeHash(`activated-offers-by-filter-${filterString}`);\n\n // Fetch activated offers from the index\n if (fetchFromIndex) {\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: indexedActivatedOffersData = [] } = cacheData || {};\n const indexedActivatedOffersCount = indexedActivatedOffersData.length;\n if (indexedActivatedOffersCount > 0) {\n console.debug(`Fetched ${indexedActivatedOffersCount} activated offers ` +\n `by filter from index`);\n return indexedActivatedOffersData;\n }\n }\n\n // Fetch activated offers from server\n const queryData = generateActivatedOffersByFilterQueryData({ filterData });\n let activatedOffersData;\n try {\n const responseData = await this.graphQLService.query({ \n queryData,\n authenticated: true\n });\n ({ activatedOffersByFilter: activatedOffersData = [] } = responseData || {});\n } catch(error) {\n console.warn(`Error fetching activated offers by filter from server`, error);\n return [];\n }\n const activatedOffersCount = activatedOffersData.length;\n console.debug(`Fetched ${activatedOffersCount} activated offers ` +\n `by filter from server`);\n if (activatedOffersCount === 0) {\n return [];\n }\n\n // Save activated offers to the index\n if (saveToIndex) {\n try {\n await this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: { \n key: cacheKey,\n value: activatedOffersData\n }\n });\n console.debug(`Saved activated offers by filter to the index`);\n } catch(error) {\n console.warn(`Error saving activated offers by filter to the index`, error);\n }\n }\n\n // Return activated offers' data\n return activatedOffersData;\n}\n","// Define the \"getOfferActivationStatus\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\nimport {\n unsetFalsyProperties,\n pickProperties,\n toString\n} from \"utilities/object\";\n\n// Import parameter modules\nimport {\n recentlyActivatedOffersFilterData,\n maxActivatedOffersDisplayCount\n} from \"parameters/offer\";\n\n// Import query modules\nimport { generateWaitForOfferActivationQueryData } from \"queries/offer\";\n\n\n// getOfferActivationStatus: async\n// returns a promise which resolves either with an object whose fields are\n// the (updated) user vertex and activates edge of a recently activated offer\n// (by key), or with null\nexport async function getOfferActivationStatus({\n offerKey = \"\",\n paymentId = \"\"\n}) {\n if (typeOf(offerKey) !== \"string\" || !offerKey) {\n throw new Error(`Parameter \"offerKey\" should be a key string`);\n } else if (typeOf(paymentId) !== \"string\" || !paymentId) {\n throw new Error(`Parameter \"paymentId\" should be a payment id string`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Fetch offer activation data from the index\n const cacheKey =\n computeHash(`activation-of-offer-${offerKey}-with-payment-${paymentId}`);\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: indexedOfferActivationData = null } = cacheData || {};\n if (indexedOfferActivationData) {\n console.debug(`Fetched activation data for offer \"${offerKey}\" from index`);\n return indexedOfferActivationData;\n }\n\n // Get offer activation data from the server\n const queryData = generateWaitForOfferActivationQueryData({ \n offerKey, \n paymentId \n });\n let offerActivationData;\n try {\n const responseData = await this.graphQLService.query({ \n queryData,\n authenticated: true\n });\n ({ waitForOfferActivation: offerActivationData = null } = responseData || {});\n } catch(error) {\n console.warn(`Error fetching activation data for offer \"${offerKey}\" ` +\n `from server`, error);\n return null;\n }\n console.debug(`Fetched activation data for offer \"${offerKey}\" from server`);\n\n // Skip saving offer activation data to the index if status is not active\n const {\n activatesData = null,\n offerData = null\n } = offerActivationData || {};\n const { status: offerActivationStatus = \"\" } = activatesData || {};\n if (![\"act\"].includes(offerActivationStatus)) {\n return offerActivationData;\n }\n\n // Patches user data with updated values (after successful activation)\n const { userData: updatedUserData = null } = offerActivationData || {};\n const userPatchData = unsetFalsyProperties(\n pickProperties(updatedUserData, [ \"isCreditBound\", \"credit\" ])\n );\n this.userService.patchUserData({ userPatchData });\n\n // Update the array of activated offers on the index (if any)\n const filterString = toString(recentlyActivatedOffersFilterData);\n const activatedOffersByFilterCacheKey =\n computeHash(`activated-offers-by-filter-${filterString}`);\n const activatedOffersByFilterCacheData =\n await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: activatedOffersByFilterCacheKey\n });\n if (activatedOffersByFilterCacheData) {\n const {\n value: indexedActivatedOffersData = []\n } = activatedOffersByFilterCacheData || {};\n const newlyActivatedOfferData = { activatesData, offerData };\n const activatedOffersData = [\n newlyActivatedOfferData,\n ...indexedActivatedOffersData\n ].slice(0, maxActivatedOffersDisplayCount);\n try {\n await this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: activatedOffersByFilterCacheKey,\n value: activatedOffersData\n }\n });\n } catch(error) {\n console.warn(`Error updating the array of activated offers on the index`,\n error);\n }\n console.debug(`Updated the array of activated offers on the index`);\n }\n\n // Save offer activation data to the index if status is active\n try {\n await this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: { \n key: cacheKey,\n value: offerActivationData\n }\n });\n } catch(error) {\n console.warn(`Error saving activation data for offer ${offerKey} ` +\n `to the index`, error);\n }\n console.debug(`Saved activation data for offer ${offerKey} to the index`);\n\n // Return offers' data\n return offerActivationData;\n}\n","// Define the \"getOfferAdditionalDiscounts\" offer service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport {\n generateOfferAdditionalDiscountsQueryData\n} from \"queries/offer\";\n\n// Define internal parameters\nconst additionalDiscountsDataExpiry = \"10 min\";\nconst additionalDiscountsDataExpiryExtension = \"5 min\";\n\n\n// getOfferAdditionalDiscounts: async\n// returns a promise which resolves with an object representing the\n// additional discounts to be applied to the specified offer (by key)\nexport async function getOfferAdditionalDiscounts(offerKey = \"\") {\n if (typeOf(offerKey) !== \"string\" || !offerKey) {\n throw new Error(`Parameter \"offerKey\" should be a non-empty string`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Fetch additinal discounts from the index\n const cacheKey = computeHash(`additional-discounts-by-offer-${offerKey}`);\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey,\n expiryExtension: additionalDiscountsDataExpiryExtension\n });\n const { value: indexedAdditionalDiscountsData = null } = cacheData || {};\n if (indexedAdditionalDiscountsData) {\n console.debug(`Fetched additional discounts for offer \"${offerKey}\" ` +\n `from the index`);\n return indexedAdditionalDiscountsData;\n }\n\n // Fetch offer's additional discounts data from server\n const queryData = generateOfferAdditionalDiscountsQueryData({ offerKey });\n let additionalDiscountsData;\n try {\n const responseData = await this.graphQLService.query({ \n queryData,\n authenticated: true\n });\n const { offerByKey: offerData = null } = responseData || {};\n ({ additionalDiscounts: additionalDiscountsData = [] } = offerData || {});\n } catch(error) {\n console.warn(`Error fetching additional discounts for offer \"${offerKey}\" ` +\n `from server`, error);\n return null;\n }\n console.debug(`Fetched additional discounts for offer \"${offerKey}\" ` +\n `from the server`);\n\n // Save additional discounts to the index\n try {\n await this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: additionalDiscountsData\n },\n expiry: additionalDiscountsDataExpiry\n });\n } catch(error) {\n console.error(error);\n throw new Error(`Error saving additional discounts for offer \"${offerKey}\" ` +\n `to the index`);\n }\n\n // Return offer's additional discounts\n return additionalDiscountsData;\n}\n","// Define the \"getOffersByFilter\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import query modules\nimport { generateOffersByFilterQueryData } from \"queries/offer\";\n\n\n// getOffersByFilter: async\n// returns a promise which either resolves with an array of offer data\n// objects matching the specified filter data object, or rejects with an\n// error\nexport async function getOffersByFilter(filterData) {\n if (typeOf(filterData) !== \"object\") {\n throw new Error(`Parameter \"filterData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Fetch offers from the index\n let offersData, offersCount;\n const filterString = toString(filterData);\n const filterDigest = computeHash(filterString);\n const cacheKey = computeHash(`offers-by-filter-${filterDigest}`);\n const cacheData = await this.indexService.getDocumentByKey({\n databaseName: \"global\",\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n ({ value: offersData = [] } = cacheData || {});\n offersCount = offersData.length;\n if (offersCount > 0) {\n console.debug(`Fetched ${offersCount} offers by filter from index`);\n return offersData;\n }\n\n // Fetch offers from server\n const queryData = generateOffersByFilterQueryData({ filterData });\n try {\n const responseData = await this.graphQLService.query({ \n queryData,\n authenticated: true\n });\n ({ offersByFilter: offersData = [] } = responseData || {});\n offersCount = offersData.length;\n } catch(error) {\n console.warn(`Error fetching offers by filter from server`, error);\n return [];\n }\n console.debug(`Fetched ${offersCount} offers by filter from server`);\n if (offersCount === 0) {\n return [];\n }\n\n // Save offers to the index\n const saveOffersKeyPromise = this.indexService.saveDocument({\n databaseName: \"global\",\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: offersData\n }\n });\n const saveOffersDataPromises = offersData.map(offerData => {\n return this.indexService.saveDocument({\n databaseName: \"global\",\n collectionName: \"offers\",\n documentData: offerData\n });\n });\n try {\n await Promise.all([\n saveOffersKeyPromise, \n ...saveOffersDataPromises\n ]);\n console.debug(`Saved ${offersCount} offers to the index`);\n } catch(error) {\n console.warn(`Error saving ${offersCount} offers to the index`, error);\n }\n\n // Return offers' data\n return offersData;\n}\n","// Define the \"pay\" service\n\n// Import class modules\nimport CircularArray from \"classes/circular-array\";\n\n// Import parameter modules\nimport { responseTimeAverageWindowLength } from \"parameters/pay\";\n\n// Import method modules\nimport { isAlive } from \"services/pay/is-alive\";\nimport { fetch } from \"services/pay/fetch\";\n\n\n// Export the \"PayService\" class\nexport default class PayService {\n\n // Attributes\n _responseTimesMs = new CircularArray(responseTimeAverageWindowLength, 0);\n\n constructor() {\n if (window.fetch) {\n console.debug(`User agent supports the fetch API`);\n } else {\n throw new Error(`User agent does not support the fetch API: ` +\n `please update your browser`);\n }\n }\n\n\n // Getter and setter methods\n get responseTimesMs() {\n return this._responseTimesMs.elements\n .filter(rttMs => rttMs > 0);\n }\n\n get averageResponseTimeMs() {\n if (this.responseTimesMs.length > 0) {\n const responseTimesSumMs = this.responseTimesMs\n .reduce((accRTTsSumMs, rttMs) => accRTTsSumMs + rttMs, 0);\n return Math.round(responseTimesSumMs/this.responseTimesMs.length);\n }\n return 0;\n }\n\n\n // Core methods\n async isAlive() {\n return await isAlive.call(this);\n }\n\n async fetch(fetchParams) {\n return await fetch.call(this, fetchParams);\n }\n\n}\n","// Define the \"fetch\" pay service method\n// notes: directly wraps the window.fetch method\n\n// Import utility modules\nimport { getCurrentTimeStampMs } from \"utilities/time\";\nimport { toString } from \"utilities/object\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport { runMode } from \"parameters/environment\";\nimport {\n baseEndpoint as payBaseEndpoint,\n fetchOptions as defaultOptions\n} from \"parameters/pay\";\n\n// Define internal parameters\nconst leadingSlashesRegExp = /^\\/+/;\n\n\n// fetch: async\n// returns a promise which either resolves with the data returned by the\n// specified path (relative to the pay base endpoint) or rejects with an\n// error\nexport async function fetch({\n method = defaultOptions.method,\n path = \"\", \n body = null,\n headers = {},\n mode = defaultOptions.mode,\n credentials = defaultOptions.credentials,\n cache = defaultOptions.cache,\n redirect = defaultOptions.redirect\n}) {\n if (typeOf(method) !== \"string\" || !method) {\n throw new Error(`Parameter \"method\" should be a non-empty string`);\n } else if (typeOf(path) !== \"string\" || !path) {\n throw new Error(`Parameter \"path\" should be a non-empty string`);\n } else if (![\"object\", \"null\"].includes(typeOf(body))) {\n throw new Error(`Parameter \"body\" should be an object or null`);\n } else if (typeOf(headers) !== \"object\") {\n throw new Error(`Parameter \"headers\" should be an object`);\n } else if (typeOf(mode) !== \"string\" || !mode) {\n throw new Error(`Parameter \"mode\" should be a non-empty string`);\n } else if (typeOf(credentials) !== \"string\" || !credentials) {\n throw new Error(`Parameter \"credentials\" should be a non-empty string`);\n } else if (typeOf(cache) !== \"string\" || !cache) {\n throw new Error(`Parameter \"cache\" should be a non-empty string`);\n } else if (typeOf(redirect) !== \"string\" || !redirect) {\n throw new Error(`Parameter \"redirect\" should be a non-empty string`);\n } else if (!window.navigator.onLine) {\n throw new Error(`No network connection`);\n }\n\n // Verify that browser is online\n const isOnline = window.navigator.onLine;\n if (!isOnline) {\n throw new Error(`Cannot fetch from the \"pay\" service: ` +\n `browser is currently off-line`);\n }\n\n const finalPath = path.replace(leadingSlashesRegExp, \"\");\n const finalUrl = `${payBaseEndpoint}/${finalPath}`;\n const finalOptions = {\n method,\n ...(body ? { body: toString(body, { multiline: false }) } : {}),\n headers: { ...defaultOptions.headers, ...headers },\n mode,\n credentials,\n cache,\n redirect\n };\n\n let response, responseTimeMs;\n try {\n const txTimeStampMs = getCurrentTimeStampMs();\n response = await window.fetch(finalUrl, finalOptions);\n const rxTimeStampMs = getCurrentTimeStampMs();\n responseTimeMs = rxTimeStampMs - txTimeStampMs;\n } catch(error) { // network errors (only) handled here\n throw new Error(`Network error while fetching from the \"pay\" service`);\n }\n this._responseTimesMs.element = responseTimeMs;\n if ([ \"dev\", \"test\" ].includes(runMode)) {\n console.debug(`pay current/average response time ` +\n `${responseTimeMs}ms/${this.averageResponseTimeMs}ms ` +\n `(${this.responseTimesMs.length} samples)`);\n }\n if (!response.ok) { // non 2xx responses handled here\n if ([ \"dev\", \"test\" ].includes(runMode)) {\n console.warn(\"error response\", response);\n }\n throw new Error(`Error while fetching from the \"pay\" service`);\n }\n\n // Return parsed response data\n return response.json(); // 2xx responses handled here\n}\n","// Define the \"isAlive\" pay service method\n\n// Import parameter modules\nimport { alivePath as payAlivePath } from \"parameters/pay\";\nimport { runMode } from \"parameters/environment\";\n\n\n// isAlive: async\n// returns a promise which resolves with the current liveness status of\n// the pay service by sending a request to its /alive end-point\nexport async function isAlive() {\n let responseData;\n try {\n responseData = await this.fetch({\n path: payAlivePath\n });\n } catch(error) {\n return false;\n }\n const {\n alive: isAlive = false,\n on: serviceAliveOnMs\n } = responseData || {};\n if ([ \"dev\", \"test\" ].includes(runMode)) {\n const serviceAliveDate = new Date(serviceAliveOnMs);\n const serviceAliveDateString = serviceAliveDate.toDateString();\n if (isAlive) {\n console.debug(`pay service tested alive on ${serviceAliveDateString}`);\n } else {\n console.warn(`pay service tested dead on ${serviceAliveDateString}`);\n }\n }\n return isAlive;\n}\n","// Define the \"sentence\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport ElementService from \"services/element\";\nimport EntityService from \"services/entity\";\nimport GraphQLService from \"services/graphql\";\nimport IndexService from \"services/index\";\nimport UserService from \"services/user\";\n\n// Import method modules\nimport { getSentencesByFilter } from \"./sentence/get-sentences-by-filter\";\nimport { addSentenceAuthor } from \"./sentence/add-sentence-author\";\nimport { createSentence } from \"./sentence/create-sentence\";\n\n\n// Export the \"SentenceService\" class\n@inject(\n ElementService,\n EntityService,\n GraphQLService,\n IndexService,\n UserService\n)\nexport default class SentenceService {\n\n constructor(\n elementService,\n entityService,\n graphQLService,\n indexService,\n userService\n ) {\n this.elementService = elementService;\n this.entityService = entityService;\n this.graphQLService = graphQLService;\n this.indexService = indexService;\n this.userService = userService;\n }\n\n\n // Core methods\n async getSentenceByKey(sentenceKey) {\n return await this.entityService.getEntityByKey({\n entityName: \"sentence\",\n entityKey: sentenceKey || \"\"\n });\n }\n\n async getSentencesByFilter(filterData) {\n return await getSentencesByFilter.call(this, filterData);\n }\n\n async createSentence({ sentenceData, authorKey = \"\" }) {\n return await createSentence.call(this, { sentenceData, authorKey });\n }\n\n async saveSentence({ sentenceData, authorKey = \"\" }) {\n return await this.createSentence({ sentenceData, authorKey });\n }\n\n async addSentenceAuthor({ sentenceKey, authorKey }) {\n return await addSentenceAuthor.call(this, { sentenceKey, authorKey });\n }\n\n}\n","// Define the \"addSentenceAuthor\" service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { deduplicate } from \"utilities/array\";\n\n// Import query modules\nimport { generateAddSentenceAuthorMutationData } from \"queries/sentence\";\n\n\n// addSentenceAuthor: async\n// returns a promise which either resolves with the updated sentence data\n// object after adding the specified author to it on the server/index, or\n// rejects with an error\nexport async function addSentenceAuthor({\n sentenceKey = \"\",\n authorKey = \"\"\n}) {\n // Create sentence on server\n const queryData = generateAddSentenceAuthorMutationData({\n sentenceKey,\n authorKey\n });\n let updatedSentenceData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ addSentenceAuthor: updatedSentenceData } = responseData);\n } catch(error) {\n throw new Error(`Error adding author \"${authorKey}\" ` +\n `to sentence \"${sentenceKey}\" on server`);\n }\n console.debug(`Added author \"${authorKey}\" ` +\n `to sentence \"${sentenceKey}\" on server`);\n\n // Update the index\n const cacheKey = computeHash(`authors-by-sentence-${sentenceKey}`);\n const cacheData = await this.indexService.getDocumentByKey({\n collectionName: \"keyvalues\",\n documentKey: cacheKey\n });\n const { value: authorsKeys = [] } = cacheData;\n const newAuthorsKeys = deduplicate([ ...authorsKeys, authorKey ]);\n try {\n await Promise.all([\n this.indexService.saveDocument({\n collectionName: \"sentences\",\n documentData: updatedSentenceData\n }),\n this.indexService.saveDocument({\n collectionName: \"keyvalues\",\n documentData: {\n key: cacheKey,\n value: newAuthorsKeys\n }\n })\n ]);\n } catch(error) {\n console.error(error);\n throw new Error(`Error adding author \"${authorKey}\" ` +\n `to sentence \"${sentenceKey}\" on index`);\n }\n console.debug(`Added author \"${authorKey}\" ` +\n `to sentence \"${sentenceKey}\" on index`);\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Return created sentence's data\n return updatedSentenceData;\n}\n","// Define the \"createSentence\" service method\n\n// Import utility modules\nimport { computeCreateSentenceData } from \"utilities/sentence\";\n\n// Import query modules\nimport { generateCreateSentenceMutationData } from \"queries/sentence\";\n\n\n// createSentence: async\n// returns a promise which either resolves with the sentence data object\n// created on the server/index, or rejects with an error\nexport async function createSentence({\n sentenceData,\n authorKey\n}) {\n // Create sentence on server\n const createSentenceData = computeCreateSentenceData({\n sentenceData,\n authorKey\n });\n const queryData = generateCreateSentenceMutationData({ createSentenceData });\n let createdSentenceData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ createSentence: createdSentenceData } = responseData);\n } catch(error) {\n throw new Error(`Error creating sentence on server`);\n }\n const { key: createdSentenceKey } = createdSentenceData;\n console.debug(`Created sentence \"${createdSentenceKey}\" on server`);\n\n // Update the index\n try {\n await this.indexService.saveDocument({\n collectionName: \"sentences\",\n documentData: createdSentenceData\n });\n } catch(error) {\n throw new Error(`Error creating sentence \"${createdSentenceKey}\" on index`);\n }\n console.debug(`Created sentence \"${createdSentenceKey}\" on index`);\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession();\n\n // Return created sentence's data\n return createdSentenceData;\n}\n","// Define the \"getSentencesByFilter\" service method\n\n// Import utility modules\nimport { splitTokens } from \"utilities/string\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport { sentenceEndingMarkElementTexts } from \"parameters/element\";\nimport {\n sentences as defaultSentenceParams\n} from \"parameters/default\";\n\n// Import query modules\nimport { generateSentencesByFilterQueryData } from \"queries/sentence\";\n\n\n// getSentencesByFilter: async\n// returns a promise which either resolves with an array of sentence data\n// objects matching the specified filter data object, or rejects with an\n// error\nexport async function getSentencesByFilter(filterData) {\n if (typeOf(filterData) !== \"object\") {\n throw new Error(`Parameter \"filterData\" should be an object`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Validate textHint against minimum characters' count\n const { textHint = \"\" } = filterData || {};\n if (textHint.length < defaultSentenceParams.minCharsSearchCount) {\n console.debug(`Returned empty sentences' array: filter's textHint below ` +\n `minimum characters' count threshold)\"`);\n return [];\n }\n\n // Validate elements' texts againt minimum elements' count\n const elementTexts = splitTokens(textHint)\n .filter(eText => !sentenceEndingMarkElementTexts.includes(eText));\n if (elementTexts.length < defaultSentenceParams.minElementsSearchCount) {\n console.debug(`Returned empty sentences' array (filter's textHint below ` +\n `elements' count threshold)\"`);\n return [];\n }\n\n // Fetch sentences from server\n const queryData = generateSentencesByFilterQueryData({ filterData });\n let sentencesData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ sentencesByFilter: sentencesData = [] } = responseData);\n } catch(error) {\n throw new Error(`Error fetching sentences by filter ` +\n toString(filterData));\n }\n const sentencesCount = sentencesData.length;\n console.debug(`Fetched ${sentencesCount} sentences by filter from server`);\n if (sentencesCount === 0) {\n return [];\n }\n\n // Save sentences to the index\n try {\n await this.indexService.saveDocuments({\n collectionName: \"sentences\",\n documentsData: sentencesData\n });\n console.debug(`Saved ${sentencesCount} sentences to the index`);\n } catch(error) {\n console.warn(`Error saving ${sentencesCount} sentences ` +\n `to the index`, error);\n }\n\n // Return sentences' data\n return sentencesData;\n}\n","// Define the \"state\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport IndexService from \"services/index\";\nimport UserService from \"services/user\";\n\n// Import method modules\nimport { loadStatesByActivity } from \"./state/load-states-by-activity\";\nimport { saveStatesByActivity } from \"./state/save-states-by-activity\";\nimport { pushStateByActivity } from \"./state/push-state-by-activity\";\nimport { undoStateByActivity } from \"./state/undo-state-by-activity\";\nimport { redoStateByActivity } from \"./state/redo-state-by-activity\";\nimport { purgeStatesByActivity } from \"./state/purge-states-by-activity\";\n\n\n// Export the \"StateService\" class\n@inject(\n IndexService,\n UserService\n)\nexport default class StateService {\n\n constructor(\n indexService,\n userService\n ) {\n this.indexService = indexService;\n this.userService = userService;\n }\n\n async loadStatesByActivity({ activityKey = \"\"}) {\n return await loadStatesByActivity.call(this, { activityKey });\n }\n\n async saveStatesByActivity({ activityKey = \"\", statesExtData = null }) {\n return await saveStatesByActivity.call(this, { activityKey, statesExtData });\n }\n\n async pushStateByActivity({ activityKey = \"\", stateData = null }) {\n return await pushStateByActivity.call(this, { activityKey, stateData });\n }\n\n async undoStateByActivity({ activityKey = \"\" }) {\n return await undoStateByActivity.call(this, { activityKey });\n }\n\n async redoStateByActivity({ activityKey = \"\" }) {\n return await redoStateByActivity.call(this, { activityKey });\n }\n\n async purgeStatesByActivity({ activityKey = \"\" }) {\n await purgeStatesByActivity.call(this, { activityKey });\n }\n\n}\n","// Define the \"loadStatesByActivity\" state service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Define internal parameters\nconst defaultStatesExtData = {\n statesData: [],\n currentStateIndex: -1\n};\n\n\n// loadStatesByActivity: async \n// returns a promise which resolves with the states data associated with\n// the specified activty key (either loaded from the index or created anew\n// with default values\nexport async function loadStatesByActivity({ activityKey = \"\"}) {\n if (typeOf(activityKey) !== \"string\" || !activityKey) {\n throw new Error(`Parameter \"activityKey\" should be a key string`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n }\n\n const indexKey = computeHash(`states-by-activity-${activityKey}`);\n let indexedStatesExtData;\n try {\n const indexedData = await this.indexService.getDocumentByKey({\n collectionName: \"states\",\n documentKey: indexKey\n });\n ({ value: indexedStatesExtData = null } = indexedData || {});\n console.debug(`Loaded states data for activity \"activityKey\" ` +\n `from the index`);\n } catch(error) {\n console.warn(`Error loading states data for activity \"activityKey\" ` +\n `from the index`, error);\n indexedStatesExtData = null;\n }\n const finalStatesExtData = {\n ...defaultStatesExtData,\n ...(indexedStatesExtData || {}),\n }\n return finalStatesExtData;\n}\n","// Define the \"purgeStatesByActivity\" state service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n\n// purgeStatesByActivity: async\n// returns a promise which resolves with the number of states data objects \n// associated with the specified activity key that have been purged from \n// the index\nexport async function purgeStatesByActivity({ \n activityKey = \"\" \n}) {\n if (typeOf(activityKey) !== \"string\" || !activityKey) {\n throw new Error(`Parameter \"activityKey\" should be a key string`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Purge states data\n const statesKey = computeHash(`states-by-activity-${activityKey}`);\n try {\n await this.indexService.deleteDocumentByKey({\n collectionName: \"states\",\n documentKey: statesKey\n });\n } catch(error) {\n console.warn(`Error purging states for activity ` +\n `\"${activityKey}\"`, error);\n return 0;\n }\n console.debug(`Purged states for activity \"${activityKey}\"`);\n return 1;\n}\n","// Define the \"pushStateByActivity\" state service method\n\n// Import utility modules\nimport { computeRebasedStateData } from \"utilities/state\";\nimport { getCurrentTimeStampMs } from \"utilities/time\";\nimport { clone, typeOf } from \"utilities/etc\";\n\n// Define internal parameters\nconst saveActionsNames = [\"saveAnalysisActivity\"];\n\n\n// pushStateByActivity: async\n// returns a promise which resolves with the new overall states object\n// obtained by pushing the specified state object at the current index \n// of the states array associated with the specified activity key.\n// If the pushed state action name is \"saveAnalysisActivity\", all other \n// states with the same action name will be purged from the states array\nexport async function pushStateByActivity({\n activityKey = \"\",\n stateData = null\n}) {\n if (typeOf(activityKey) !== \"string\" || !activityKey) {\n throw new Error(`Parameter \"activityKey\" should be a key string`);\n } else if (typeOf(stateData) !== \"object\") {\n throw new Error(`Parameter \"stateData\" should be an object`);\n } else if (!this.loadStatesByActivity) {\n throw new Error(`Missing required \"loadStatesByActivity\" method`);\n } else if (!this.saveStatesByActivity) {\n throw new Error(`Missing required \"saveStatesByActivity\" method`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Load states data from the index\n const statesExtData = await this.loadStatesByActivity({ activityKey });\n const { \n currentStateIndex = -1,\n statesData = []\n } = statesExtData;\n const statesDataCount = statesData.length;\n\n // Update states data\n const pushedStateData = {\n timeStampMs: getCurrentTimeStampMs(),\n ...stateData\n }\n const { metaData: analysisActivityMetaData = null } = pushedStateData || {};\n const { action: actionData = null } = analysisActivityMetaData || {};\n const { name: actionName = \"\" } = actionData || {};\n let previousStatesData = clone(statesData);\n if (currentStateIndex < statesDataCount-1) {\n previousStatesData = previousStatesData.slice(0, currentStateIndex+1);\n }\n const previousStatesCount = previousStatesData.length;\n let newStatesData;\n if (saveActionsNames.includes(actionName)) { // save action\n // note: using filter instead of slice here to preserve deep data!\n const rebasedStatesData = previousStatesData\n .filter((_, stateIndex) => stateIndex !== previousStatesCount-1)\n .map(stateData => computeRebasedStateData({\n stateData,\n savedStateData: pushedStateData\n }));\n newStatesData = [ ...rebasedStatesData, pushedStateData ];\n } else { // not a save action\n newStatesData = [ ...previousStatesData, pushedStateData ];\n }\n const newStateIndex = newStatesData.length - 1;\n\n const newStatesExtData = {\n statesData: newStatesData,\n currentStateIndex: newStateIndex\n };\n\n // Save states data to the index\n const savedStatesExtData = await this.saveStatesByActivity({ \n activityKey, \n statesExtData: newStatesExtData \n });\n if (!savedStatesExtData) {\n console.warn(`Error pushing state for activity \"${activityKey}\" ` +\n `to the index`);\n return statesExtData;\n }\n console.debug(`Pushed state for activity \"${activityKey}\" to the index`);\n return newStatesExtData;\n}\n","// Define the \"redoStateByActivity\" state service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n\n// redoStateByActivity: async\n// returns a promise which resolves with the new overall states object\n// obtained by redoing the state object at the current index of the \n// states array associated with the specified activity key\nexport async function redoStateByActivity({\n activityKey = \"\"\n}) {\n if (typeOf(activityKey) !== \"string\" || !activityKey) {\n throw new Error(`Parameter \"activityKey\" should be a key string`);\n } else if (!this.loadStatesByActivity) {\n throw new Error(`Missing required \"loadStatesByActivity\" method`);\n } else if (!this.saveStatesByActivity) {\n throw new Error(`Missing required \"saveStatesByActivity\" method`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Load states data from the index\n const statesExtData = await this.loadStatesByActivity({ activityKey });\n const { \n currentStateIndex = -1,\n statesData = []\n } = statesExtData;\n\n // Update states data\n const statesCount = statesData.length;\n if (currentStateIndex === statesCount-1) {\n console.warn(`Cannot redo past the last action`);\n return statesExtData;\n }\n const newStatesExtData = {\n statesData,\n currentStateIndex: currentStateIndex + 1\n };\n\n // Get next action name\n const nextStateData = statesData.at(currentStateIndex+1) || null;\n const { metaData: nextAnalysisActivityMetaData = null } = nextStateData || {};\n const { action: nextActionData = null } = nextAnalysisActivityMetaData || {};\n const { name: nextActionName = \"\" } = nextActionData || {};\n\n // Save states data to the index\n const savedStatesExtData = await this.saveStatesByActivity({ \n activityKey, \n statesExtData: newStatesExtData \n });\n if (!savedStatesExtData) {\n console.warn(`Error redoing \"${nextActionName}\" action ` +\n `for activity \"${activityKey}\" `);\n return statesExtData;\n }\n console.debug(`Redone \"${nextActionName}\" action ` +\n `for activity \"${activityKey}\"`);\n return newStatesExtData;\n}\n","// Define the \"saveStatesByActivity\" state service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n\n// saveStatesByActivity: async\n// returns a promise which resolves either with the states data that have\n// just been saved to the index, or with null if an error occurred\nexport async function saveStatesByActivity({ \n activityKey = \"\",\n statesExtData = null\n}) {\n if (typeOf(activityKey) !== \"string\" || !activityKey) {\n throw new Error(`Parameter \"activityKey\" should be a key string`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n }\n\n const indexKey = computeHash(`states-by-activity-${activityKey}`);\n try {\n await this.indexService.saveDocument({\n collectionName: \"states\",\n documentData: {\n key: indexKey,\n value: statesExtData\n }\n });\n } catch(error) {\n console.warn(`Error saving states data for activity \"${activityKey}\" ` +\n `to the index`, error);\n return null;\n }\n console.debug(`Saved states data for activity \"${activityKey}\" ` +\n `to the index`);\n return statesExtData;\n}\n","// Define the \"undoStateByActivity\" state service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n\n// undoStateByActivity: async\n// returns a promise which resolves with the new overall states object\n// obtained by undoing the state object at the current index of the \n// states array associated with the specified activity key\nexport async function undoStateByActivity({\n activityKey = \"\"\n}) {\n if (typeOf(activityKey) !== \"string\" || !activityKey) {\n throw new Error(`Parameter \"activityKey\" should be a key string`);\n } else if (!this.loadStatesByActivity) {\n throw new Error(`Missing required \"loadStatesByActivity\" method`);\n } else if (!this.saveStatesByActivity) {\n throw new Error(`Missing required \"saveStatesByActivity\" method`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Load states data from the index\n const statesExtData = await this.loadStatesByActivity({ activityKey });\n const { \n currentStateIndex = -1,\n statesData = []\n } = statesExtData;\n\n // Update states data\n if (currentStateIndex <= 0) {\n console.warn(`Cannot undo past the first action`);\n return statesExtData;\n }\n const newStatesExtData = {\n statesData,\n currentStateIndex: currentStateIndex - 1\n };\n\n // Get current action name\n const currentStateData = statesData.at(currentStateIndex) || null;\n const { \n metaData: currentAnalysisActivityMetaData = null \n } = currentStateData || {};\n const { \n action: currentActionData = null \n } = currentAnalysisActivityMetaData || {};\n const { name: currentActionName = \"\" } = currentActionData || {};\n\n // Save states data to the index\n const savedStatesExtData = await this.saveStatesByActivity({ \n activityKey, \n statesExtData: newStatesExtData \n });\n if (!savedStatesExtData) {\n console.warn(`Error undoing \"${currentActionName}\" action ` +\n `for activity \"${activityKey}\" `);\n return statesExtData;\n }\n console.debug(`Undone \"${currentActionName}\" action ` +\n `for activity \"${activityKey}\"`);\n return newStatesExtData;\n}\n","// Define the \"stripe\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import method modules\nimport { createPaymentIntent } from \"./stripe/create-payment-intent\";\nimport { cancelPaymentIntent } from \"./stripe/cancel-payment-intent\";\n\n// Import service modules\nimport PayService from \"services/pay\";\nimport UserService from \"services/user\";\n\n\n// Export the \"StripeService\" class\n@inject(\n PayService,\n UserService\n)\nexport default class StripeService {\n\n constructor(\n payService,\n userService\n ) {\n this.payService = payService;\n this.userService = userService;\n }\n\n\n // Core methods\n async createPaymentIntent({\n offerKey = \"\",\n intentDescription = \"\",\n intentStatementDescriptor = \"\"\n }) {\n return await createPaymentIntent.call(this, { \n offerKey,\n intentDescription,\n intentStatementDescriptor\n });\n }\n\n async cancelPaymentIntent({\n paymentIntentId = \"\",\n cancellationReason = \"\"\n }) {\n return await cancelPaymentIntent.call(this, { \n paymentIntentId,\n cancellationReason\n });\n }\n\n}\n","// Define the \"cancelPaymentIntent\" stripe service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport { \n basePath, \n paymentIntentsPath \n} from \"parameters/stripe\";\n\n// Define internal parameters\nconst allowedCancellationReasons =\n [ \"abandoned\", \"duplicate\", \"fraudolent\", \"requested_by_customer\" ];\n\n\n// cancelPaymentIntent: async \n// returns a promise which resolves after the specified payment intent\n// (by id) has been canceled with the specified reason. The return object \n// contains an \"intentData\" field\nexport async function cancelPaymentIntent({\n paymentIntentId = \"\",\n cancellationReason = \"abandoned\"\n}) {\n if (typeOf(paymentIntentId) !== \"string\" || !paymentIntentId) {\n throw new Error(`Parameter \"paymentIntentId\" should be a non-empty string`);\n } else if (\n typeOf(cancellationReason) !== \"string\" ||\n !allowedCancellationReasons.includes(cancellationReason)\n ) {\n throw new Error(`Parameter \"cancellationReason\" should be a valid ` +\n `cancellation reasong string`);\n } else if (!this.payService) {\n throw new Error(`Missing required \"pay\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n } else if (!this.userService.isSessionOpen) {\n throw new Error(`Error creating payment intent: user session is not open`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Get session token\n const { tokenString = \"\" } = this.userService || {};\n if (!tokenString) {\n throw new Error(`Error creating payment intent: session token is empty`);\n }\n\n // Perform http request\n return await this.payService.fetch({\n method: \"post\",\n path: `${basePath}/${paymentIntentsPath}/cancel`,\n body: { paymentIntentId, cancellationReason },\n headers: {\n \"Content-Type\": `application/json`,\n \"Authentication\": `Bearer ${tokenString}`\n }\n });\n}\n","// Define the \"createPaymentIntent\" stripe service method\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport { \n basePath, \n paymentIntentsPath \n} from \"parameters/stripe\";\n\n\n// createPaymentIntent: async \n// returns a promise which resolves after a payment intent for the\n// specified offer (by key) has been created. The return object contains\n// \"clientSecret\", \"intentData\" and \"receiptData\" fields\nexport async function createPaymentIntent({\n offerKey = \"\",\n intentDescription = \"\",\n intentStatementDescriptor = \"\"\n}) {\n if (typeOf(offerKey) !== \"string\" || !offerKey) {\n throw new Error(`Parameter \"offerKey\" should be a non-empty string`);\n } else if (typeOf(intentDescription) !== \"string\") {\n throw new Error(`Parameter \"intentDescription\" should be a string`);\n } else if (typeOf(intentStatementDescriptor) !== \"string\") {\n throw new Error(`Parameter \"intentStatementDescriptor\" should be a string`);\n } else if (!this.payService) {\n throw new Error(`Missing required \"pay\" service`);\n } else if (!this.userService) {\n throw new Error(`Missing required \"user\" service`);\n } else if (!this.userService.isSessionOpen) {\n throw new Error(`Error creating payment intent: user session is not open`);\n }\n\n // Conditionally extend user session\n this.userService.conditionallyExtendSession(); // do not await\n\n // Get session token\n const { tokenString = \"\" } = this.userService || {};\n if (!tokenString) {\n throw new Error(`Error creating payment intent: session token is empty`);\n }\n\n // Perform http request\n return await this.payService.fetch({\n method: \"post\",\n path: `${basePath}/${paymentIntentsPath}`,\n body: { \n offerKey, \n ...(intentDescription ? { intentDescription } : {}),\n ...(intentStatementDescriptor ? { intentStatementDescriptor } : {})\n },\n headers: {\n \"Content-Type\": `application/json`,\n \"Authentication\": `Bearer ${tokenString}`\n }\n });\n}\n","// Define the \"task\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import service modules\nimport EntityService from \"services/entity\";\n\n\n// Export the \"TaskService\" class\n@inject(\n EntityService\n)\nexport default class TaskService {\n\n constructor(\n entityService\n ) {\n this.entityService = entityService;\n }\n\n async getTaskByKey(taskKey) {\n return await this.entityService.getEntityByKey({\n entityName: \"task\",\n entityKey: taskKey || \"\"\n });\n }\n\n}\n","// Define the \"user\" service\n\n// Import library modules\nimport { Aurelia, inject, computedFrom } from \"aurelia-framework\";\nimport { Router } from \"aurelia-router\";\nimport { I18N } from \"aurelia-i18n\";\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\nimport {\n computeDistanceTimeString,\n getCurrentTimeStampMs\n} from \"utilities/time\";\nimport {\n getSessionStorageData,\n getLocalStorageData,\n patchLocalStorageData\n} from \"utilities/storage\";\n\n// Import parameter modules\nimport {\n inLocaleCode as defaultInLocaleCode,\n uiLocaleCode as defaultUILocaleCode,\n outLocaleCode as defaultOutLocaleCode\n} from \"parameters/environment\";\nimport {\n initialLocalStorageData,\n initialSessionStorageData\n} from \"parameters/state\";\n\n// Import service modules\nimport NotificationService from \"services/notification\";\nimport GraphQLService from \"services/graphql\";\nimport EventService from \"services/event\";\nimport IndexService from \"services/index\";\n\n// Import method modules\nimport { getAuthUsersStats } from \"./user/get-auth-users-stats\";\nimport { requestCreate } from \"./user/request-create\";\nimport { openSession } from \"./user/open-session\";\nimport { extendSession } from \"./user/extend-session\";\nimport { closeSession } from \"./user/close-session\";\nimport { updateSettings } from \"./user/update-settings\";\nimport { changePassword } from \"./user/change-password\";\nimport { requestResetPassword } from \"./user/request-reset-password\";\nimport { setUILocaleCode } from \"./user/set-ui-locale-code\";\nimport { conditionallyExtendSession } from \"./user/conditionally-extend-session\";\nimport { patchUserData } from \"./user/patch-user-data\";\nimport { \n chargeUserCreditForActivityCreation\n} from \"./user/charge-user-credit-for-activity-creation\";\n\n\n// Export the \"UserService\" class\n@inject(\n Aurelia,\n EventService,\n GraphQLService,\n IndexService,\n I18N,\n NotificationService,\n Router\n)\nexport default class UserService {\n\n // Local attributes\n tokenEtcData = initialSessionStorageData.token || {};\n configData = initialLocalStorageData.config || {};\n userData = initialLocalStorageData.user || {};\n extendingUserSession = false;\n\n constructor(\n aurelia,\n eventService,\n graphQLService,\n indexService,\n i18n,\n notificationService,\n router\n ) {\n this.aurelia = aurelia;\n this.eventService = eventService;\n this.graphQLService = graphQLService;\n this.indexService = indexService;\n this.i18n = i18n;\n this.notificationService = notificationService;\n this.router = router;\n\n const sessionStorageData =\n getSessionStorageData() || initialSessionStorageData;\n const localStorageData =\n getLocalStorageData() || initialLocalStorageData;\n this.tokenEtcData = sessionStorageData.token;\n this.configData = localStorageData.config;\n this.userData = localStorageData.user;\n }\n\n\n // Getter and setter methods\n // tokenString: sync getter\n // returns the token string representing the user's session\n @computedFrom(\"tokenEtcData.string\")\n get tokenString() {\n return this.tokenEtcData.string || \"\";\n }\n\n // tokenData: sync getter\n // returns the token data representing the user's session\n @computedFrom(\"tokenEtcData.data\")\n get tokenData() {\n return this.tokenEtcData.data || null;\n }\n\n // isSessionOpen: sync getter\n // returns a boolean indicating whether the user's session is currently\n // open\n get isSessionOpen() {\n const currentTimeStampMs = getCurrentTimeStampMs();\n const {\n exp: sessionExpiresOnMs = currentTimeStampMs\n } = this.tokenData || {};\n return currentTimeStampMs < sessionExpiresOnMs;\n }\n\n // sessionExpiresOnMs: sync getter\n // returns the datetime in which the user's session expires\n @computedFrom(\"tokenData.exp\")\n get sessionExpiresOnMs() {\n const {\n exp: sessionExpiresOnMs = getCurrentTimeStampMs()\n } = this.tokenData || {};\n return sessionExpiresOnMs;\n }\n\n // sessionExpiresInMs: sync getter\n // returns the amount of milliseconds to the expiry of the user's session\n // note: returns zero when the session is not open\n get sessionExpiresInMs() {\n return Math.max(0, this.sessionExpiresOnMs - getCurrentTimeStampMs());\n }\n\n // sessionExpiresInString: sync getter\n // returns a human-readable string representation of the user's session\n // expiry time\n get sessionExpiresInString() {\n const {\n exp: sessionExpiresOnMs = getCurrentTimeStampMs()\n } = this.tokenData || {};\n return computeDistanceTimeString({\n timeStampMs: sessionExpiresOnMs,\n addSuffix: false\n });\n }\n\n @computedFrom(\"userData.key\")\n get userKey() {\n const { key: userKey } = this.userData || {};\n return userKey;\n }\n\n @computedFrom(\"userData.profile\")\n get profileData() {\n const { profile: profileData } = this.userData || {};\n return profileData;\n }\n\n @computedFrom(\"userData.settings\")\n get settingsData() {\n const { settings: settingsData = null } = this.userData || {};\n return settingsData;\n }\n\n @computedFrom(\"userData.isCreditBound\")\n get isUserCreditBound() {\n const { isCreditBound: isUserCreditBound = true } = this.userData || {};\n return isUserCreditBound;\n }\n @computedFrom(\"isUserCreditBound\")\n get isUserCreditLocked() {\n return !this.isUserCreditBound;\n }\n\n @computedFrom(\"userData.credit\")\n get userCredit() {\n const { credit: userCredit = 0 } = this.userData || {};\n return userCredit;\n }\n\n @computedFrom(\"settingsData.inLocaleCode\")\n get inLocaleCode() {\n const { inLocaleCode = defaultInLocaleCode } = this.settingsData || {};\n return inLocaleCode;\n }\n\n @computedFrom(\"settingsData.uiLocaleCode\")\n get uiLocaleCode() {\n const { uiLocaleCode = defaultUILocaleCode } = this.settingsData || {};\n return uiLocaleCode;\n }\n\n @computedFrom(\"settingsData.outLocaleCode\")\n get outLocaleCode() {\n const { outLocaleCode = defaultOutLocaleCode } = this.settingsData || {};\n return outLocaleCode;\n }\n\n @computedFrom(\"configData.acceptPrivacyPolicy\")\n get acceptPrivacyPolicy() {\n const { acceptPrivacyPolicy = false } = this.configData || {};\n return acceptPrivacyPolicy;\n }\n set acceptPrivacyPolicy(acceptPrivacyPolicy) {\n if (typeOf(acceptPrivacyPolicy) !== \"boolean\") {\n throw new Error(`Parameter \"acceptPrivacyPolicy\" should be a boolean`);\n }\n this.configData = { ...(this.configData || {}), acceptPrivacyPolicy };\n patchLocalStorageData({ data: { config: this.configData || {} } });\n }\n\n @computedFrom(\"userData.invitingUser\")\n get invitingUserData() {\n const { invitingUser: invitingUserData = null } = this.userData || {};\n return invitingUserData;\n }\n\n @computedFrom(\"invitingUserData.profile\")\n get invitingUserProfileData() {\n const { \n profile: invitingUserProfileData = null \n } = this.invitingUserData || {};\n return invitingUserProfileData;\n }\n\n\n // Core methods\n async getAuthUsersStats() {\n return await getAuthUsersStats.call(this);\n }\n\n async requestCreate(createUserData) {\n return await requestCreate.call(this, createUserData);\n }\n\n async setUILocaleCode(uiLocaleCode) {\n await setUILocaleCode.call(this, uiLocaleCode);\n }\n\n async updateSettings(updateUserSettingsData) {\n return await updateSettings.call(this, updateUserSettingsData);\n }\n\n async changePassword(changeUserPasswordData) {\n return await changePassword.call(this, changeUserPasswordData);\n }\n\n async requestResetPassword(resetUserPasswordData) {\n await requestResetPassword.call(this, resetUserPasswordData);\n }\n\n async openSession(openUserSessionData) {\n await openSession.call(this, openUserSessionData);\n }\n\n async extendSession() {\n await extendSession.call(this);\n }\n\n async conditionallyExtendSession(params = {}) {\n await conditionallyExtendSession.call(this, params);\n }\n\n patchUserData({ userPatchData = null }) {\n return patchUserData.call(this, { userPatchData });\n }\n\n async closeSession() {\n await closeSession.call(this);\n }\n\n chargeUserCreditForActivityCreation({ activityCostCredits } = {}) {\n chargeUserCreditForActivityCreation.call(this, { activityCostCredits });\n }\n\n}\n","// Define the \"changePassword\" user service method\n\n// Import query modules\nimport { generateChangeUserPasswordMutationData } from \"queries/user\";\n\n\n// changePassword: async\n// returns a promise which either resolves with the key identifying the\n// user whose password has been changed, or rejects with an error\nexport async function changePassword(changeUserPasswordData) {\n const queryData =\n generateChangeUserPasswordMutationData({ changeUserPasswordData });\n let userKey;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ changeUserPassword: { key: userKey } } = responseData);\n } catch(error) {\n console.warn(\"Error while changing the user's password\");\n throw error;\n }\n console.debug(`Changed the password for user \"${userKey}\"`);\n\n // Return user's key\n return userKey;\n}\n","// Define the \"chargeUserCreditForActivityCreation\" user service method\n\n// Import utility modules\nimport { patchLocalStorageData } from \"utilities/storage\";\n\n// Import parameter modules\nimport {\n activityCostCredits as defaultActivityCostsCredits\n} from \"parameters/activity\";\n\n\n// chargeUserCreditForActivityCreation: sync\n// charges the user credit for creating a new activity, and stores the updated \n// credit amount on the local storage\nexport function chargeUserCreditForActivityCreation({\n activityCostCredits = defaultActivityCostsCredits\n} = {}) {\n if (!this.isSessionOpen) {\n return;\n }\n\n const {\n isCreditBound: isUserCreditBound = true,\n credit: currentUserCredit = 0.0\n } = this.userData;\n if (currentUserCredit > 0) {\n const updatedUserCredit = isUserCreditBound ?\n Math.max(0, currentUserCredit-activityCostCredits) :\n currentUserCredit;\n this.userData.credit = updatedUserCredit;\n patchLocalStorageData({ data: { user: this.userData } });\n if (updatedUserCredit < currentUserCredit) {\n console.debug(`Updated user credit to ${updatedUserCredit}`);\n this.eventService.publish({\n eventName: \"userDataChanged\", \n eventData: {\n oldValue: currentUserCredit,\n newValue: updatedUserCredit\n }\n });\n }\n }\n}\n","// Define the \"closeSession\" user service method\n\n// Import library modules\nimport { PLATFORM } from \"aurelia-pal\";\n\n// Import utility modules\nimport { \n setSessionStorageData,\n setLocalStorageData\n} from \"utilities/storage\";\n\n// Import parameter modules\nimport { \n initialSessionStorageData,\n initialLocalStorageData\n} from \"parameters/state\";\n\n// Import query modules\nimport { generateCloseUserSessionMutationData } from \"queries/user\";\n\n\n// closeSession: async\n// returns a promise which either resolves after the user session has\n// been closed, or rejects with an error\nexport async function closeSession() {\n if (!this.aurelia) {\n throw new Error(`Missing required \"aurelia\" service`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.notificationService) {\n throw new Error(`Missing required \"notificaiton\" service`);\n } else if (!this.router) {\n throw new Error(`Missing required \"router\" service`);\n }\n\n // Close session on server\n const queryData = generateCloseUserSessionMutationData();\n try {\n await this.graphQLService.query({ queryData });\n } catch(error) {\n console.warn(\"Error closing the user's session\", error);\n return;\n }\n this.graphQLService.tokenString = \"\";\n this.tokenEtcData = initialSessionStorageData.token;\n const { key: userKey = \"\" } = this.userData || {};\n console.info(`Closed session for user \"${userKey}\"`);\n\n // Update local/session storage\n setSessionStorageData({ \n data: initialSessionStorageData // reset session data\n });\n setLocalStorageData({ \n data: {\n config: this.configData, // keep current user config\n user: initialLocalStorageData.user // reset user data\n }\n });\n\n // Close all UI notifications\n this.notificationService.closeUINotifications();\n\n // Navigate to base route and deactivate current (private) router\n this.router.navigate(\"/logged-out\", { replace: true, trigger: false });\n this.router.deactivate();\n\n // Set navigation root to \"public\"\n await this.aurelia.setRoot(PLATFORM.moduleName(\"public\"));\n console.debug(`Set root to \"public\"`);\n\n // Disable back button functionality by pushing an empty state in the history\n history.pushState(null, document.title, location.href);\n}\n","// Define the \"conditionallyExtendSession\" user service method\n\n// Import parameter modules\nimport {\n autoExtensionThresholdMs as defaultAutoExtensionThresholdMs\n} from \"parameters/session\";\n\n\n// conditionallyExtendSession: async\n// returns a promise which resolves either after the user's session has\n// been automatically extended (whenever session's expiry is within the\n// specified auto-extension threshold), or immediately\nexport async function conditionallyExtendSession({\n autoExtensionThresholdMs = defaultAutoExtensionThresholdMs\n} = {}) {\n if (\n this.isSessionOpen &&\n !this.extendingUserSession &&\n this.sessionExpiresInMs < autoExtensionThresholdMs\n ) {\n const { userKey } = this;\n try {\n await this.extendSession();\n } catch(error) {\n console.warn(`Error while automatically extending session ` +\n `for user \"${userKey}\"`);\n return;\n }\n }\n}\n","// Define the \"extendSession\" user service method\n\n// Import utility modules\nimport { computeClientTokenData, decodeToken } from \"utilities/token\";\nimport { patchSessionStorageData } from \"utilities/storage\";\n\n// Import query modules\nimport { generateExtendUserSessionMutationData } from \"queries/user\";\n\n\n// extendSession: async\n// returns a promise which either resolves after the session has been\n// extended, or rejects with an error\nexport async function extendSession() {\n if (this.extendingUserSession) {\n console.debug(\"Session is already being extended\");\n return;\n }\n\n // Extend session on server\n const queryData = generateExtendUserSessionMutationData();\n let extendedTokenString;\n this.extendingUserSession = true;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ extendUserSession: extendedTokenString } = responseData);\n } catch(error) {\n this.extendingUserSession = false;\n console.warn(\"Error extending the user's session\");\n throw error;\n }\n this.extendingUserSession = false;\n\n // Update token data and session storage\n const extendedServerTokenData = decodeToken(extendedTokenString);\n const extendedClientTokenData =\n computeClientTokenData(extendedServerTokenData, \"extend\");\n this.graphQLService.tokenString = extendedTokenString;\n this.tokenEtcData = {\n string: extendedTokenString,\n data: extendedClientTokenData\n };\n patchSessionStorageData({ \n data: { token: this.tokenEtcData } \n });\n const { key: userKey } = this.userData || {};\n console.info(`Extended session for user \"${userKey}\": ` +\n `it will expire in ${this.sessionExpiresInString}`);\n}\n","// Define the \"getAuthUsersStats\" user service method\n\n// Import library modules\nimport { PLATFORM } from \"aurelia-pal\";\n\n// Import query modules\nimport { generateGetAuthUsersStatsQueryData } from \"queries/user\";\n\n\n// getAuthUsersStats: async\n// returns a promise which either resolves with an object containing the \n// auth users' stats data, or reject with an error\nexport async function getAuthUsersStats() {\n if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n }\n\n // Open session on server\n const queryData = generateGetAuthUsersStatsQueryData();\n let authUsersStatsData;\n try {\n const responseData = await this.graphQLService.query({\n authenticated: false,\n queryData\n });\n ({ getAuthUsersStats: authUsersStatsData } = responseData);\n } catch(error) {\n console.warn(\"Error getting auth users' stats\");\n throw error;\n }\n console.info(`Got auth users' stats`);\n\n // Return auth users' stats object\n return authUsersStatsData;\n}\n","// Define the \"openSession\" user service method\n\n// Import library modules\nimport { PLATFORM } from \"aurelia-pal\";\n\n// Import utility modules\nimport { typeOf } from \"utilities/etc\";\nimport {\n computeClientTokenData,\n decodeToken\n} from \"utilities/token\";\nimport {\n patchLocalStorageData,\n setSessionStorageData\n} from \"utilities/storage\";\n\n// Import query modules\nimport { generateOpenUserSessionMutationData } from \"queries/user\";\n\n\n// openSession: async\n// returns a promise which either resolves after the session has been\n// opened, or rejects with an error\nexport async function openSession(openUserSessionData) {\n if (typeOf(openUserSessionData) !== \"object\") {\n throw new Error(`Parameter \"openUserSessionData\" should be an object`);\n } else if (!this.aurelia) {\n throw new Error(`Missing required \"aurelia\" service`);\n } else if (!this.indexService) {\n throw new Error(`Missing required \"index\" service`);\n } else if (!this.graphQLService) {\n throw new Error(`Missing required \"graphQL\" service`);\n } else if (!this.notificationService) {\n throw new Error(`Missing required \"notificaiton\" service`);\n } else if (!this.router) {\n throw new Error(`Missing required \"router\" service`);\n }\n\n if (this.isSessionOpen) {\n console.warn(\"Session is currently open\");\n return;\n }\n\n // Open session on server\n const queryData = generateOpenUserSessionMutationData({ openUserSessionData });\n let userData, openedTokenString;\n try {\n const responseData = await this.graphQLService.query({\n authenticated: false,\n queryData\n });\n ({ openUserSession: {\n userData,\n sessionToken: openedTokenString\n } } = responseData);\n } catch(error) {\n console.warn(\"Error opening the user's session\");\n throw error;\n }\n this.userData = userData;\n const openedServerTokenData = decodeToken(openedTokenString);\n const openedClientTokenData =\n computeClientTokenData(openedServerTokenData, \"open\");\n this.graphQLService.tokenString = openedTokenString;\n this.tokenEtcData = {\n string: openedTokenString,\n data: openedClientTokenData // corrected for client-side time\n };\n const { key: userKey } = this.userData || {};\n console.info(`Opened session for user \"${userKey}\": ` +\n `it will expire in ${this.sessionExpiresInString}`);\n\n // Update local/session storage\n setSessionStorageData({ \n data: { token: this.tokenEtcData } \n });\n patchLocalStorageData({ \n data: { user: this.userData } \n });\n\n // Create user database on index\n this.indexService.createUserDatabase();\n\n // Prune expired documents from index\n await Promise.all([\n this.indexService.pruneExpiredDocuments({\n databaseName: \"global\",\n collectionName: \"keyvalues\"\n }),\n this.indexService.pruneExpiredDocuments({\n databaseName: \"global\",\n collectionName: \"offers\"\n })\n ]);\n\n // Close all UI notifications\n this.notificationService.closeUINotifications();\n\n // Navigate to base route and deactivate current (public) router\n this.router.navigate(\"/\", { replace: true, trigger: false });\n this.router.deactivate();\n\n // Set navigation root to \"private\"\n await this.aurelia.setRoot(PLATFORM.moduleName(\"private\"));\n console.debug(`Set root to \"private\"`);\n\n // Disable back button functionality by pushing an empty state in the history\n history.pushState(null, document.title, location.href);\n}\n","// Define the \"patchUserData\" user service method\n\n// Import utility modules\nimport { patchLocalStorageData } from \"utilities/storage\";\n\n\n// patchUserData: sync\n// patch the user data with the specified userPatchData object, stores \n// the updated user data object on the local storage and returns it\nexport function patchUserData({\n userPatchData = null\n} = {}) {\n if (!userPatchData) {\n console.debug(`Returning un-patched user data`);\n return this.userData;\n }\n const currentUserData = { ...this.userData };\n this.userData = { ...this.userData, ...userPatchData };\n patchLocalStorageData({ data: { user: this.userData } });\n console.debug(`Patched user data`);\n this.eventService.publish({\n eventName: \"userDataChanged\", \n eventData: {\n oldValue: currentUserData,\n newValue: this.userData\n }\n });\n}\n","// Define the \"requestCreate\" user service method\n\n// Import query modules\nimport { generateRequestCreateUserMutationData } from \"queries/user\";\n\n\n// requestCreate: async\n// returns a promise which either resolves with the created user's key,\n// or rejects with an error\nexport async function requestCreate(createUserData) {\n const queryData = generateRequestCreateUserMutationData({ createUserData });\n let userKey;\n try {\n const responseData = await this.graphQLService.query({\n authenticated: false,\n queryData\n });\n ({ requestCreateUser: { userKey } } = responseData);\n } catch(error) {\n console.warn(\"Error requesting to create a new user's account\");\n throw error;\n }\n console.info(\"Requested to create a new user's account\");\n return userKey;\n}\n","// Define the \"requestResetPassword\" user service method\n\n// Import query modules\nimport { generateRequestResetUserPasswordMutationData } from \"queries/user\";\n\n\n// requestResetPassword: async\n// returns a promise which either resolves with the key identifying the \n// user whose password reset request has been processed, or rejects with\n// an error\nexport async function requestResetPassword(resetUserPasswordData) {\n const queryData =\n generateRequestResetUserPasswordMutationData({ resetUserPasswordData });\n try {\n await this.graphQLService.query({\n authenticated: false,\n queryData\n });\n } catch(error) {\n console.warn(\"Error requesting to reset user's password\");\n throw error;\n }\n console.debug(`Requested to reset the user's password`);\n}\n","// Define the \"setUILocaleCode\" user service method\n\n// Import utility modules\nimport { patchLocalStorageData } from \"utilities/storage\";\n\n\n// setUILocaleCode\n// returns a promise which either resolves after the UI locale code field\n// in the user settings has been updated server-side (and the settings\n// applied clien-side), or rejects with an error\nexport async function setUILocaleCode(uiLocaleCode) {\n this.userData = this.userData || {};\n this.userData.settings = this.userData.settings || {};\n if (this.isSessionOpen) {\n let updatedSettingsData;\n try {\n updatedSettingsData = await this.updateSettings({ uiLocaleCode });\n } catch(error) {\n console.warn(`Error while updating UI locale code to \"${uiLocaleCode}\"`,\n error);\n return;\n }\n this.userData.settings = updatedSettingsData;\n } else {\n this.userData.settings.uiLocaleCode = uiLocaleCode;\n }\n patchLocalStorageData({ data: { user: this.userData } });\n await this.i18n.setLocale(uiLocaleCode);\n console.debug(`Set UI locale code to \"${uiLocaleCode}\"`);\n}\n","// Define the \"updateSettings\" user service method\n\n// Import query modules\nimport { generateUpdateUserSettingsMutationData } from \"queries/user\";\n\n\n// updateSettings: async\n// returns a promise which either resolves with the updated user settings\n// data object, or rejects with an error\nexport async function updateSettings(updateUserSettingsData) {\n const queryData =\n generateUpdateUserSettingsMutationData({ updateUserSettingsData });\n let updatedSettingsData;\n try {\n const responseData = await this.graphQLService.query({ queryData });\n ({ updateUserSettings: { settings: updatedSettingsData } } = responseData);\n } catch(error) {\n throw new Error(\"Error updating user's settings\");\n }\n console.debug(`Updated user's settings`);\n\n // Return updated settings' data\n return updatedSettingsData;\n}\n","// Define the \"validation\" service\n\n// Import library modules\nimport { inject } from \"aurelia-framework\";\n\n// Import method modules\nimport { getValidator } from \"services/validation/get-validator\";\n\n// Import service modules\nimport GraphQLService from \"./graphql\";\nimport IndexService from \"./index\";\n\n\n// Export the \"ValidationService\" class\n@inject(GraphQLService, IndexService)\nexport default class ValidationService {\n\n constructor(graphQLService, indexService) {\n this.graphQLService = graphQLService;\n this.indexService = indexService;\n }\n\n async getValidator(entityName, schemaName) {\n return await getValidator.call(this, entityName, schemaName);\n }\n\n}\n","// Define the \"getValidator\" validator service method\n\n// Import utility modules\nimport { cyrb53 as computeHash } from \"utilities/hash\";\nimport { typeOf } from \"utilities/etc\";\n\n// Import parameter modules\nimport { globalDatabaseData } from \"parameters/index\";\n\n// Import class modules\nimport AjvValidator from \"classes/ajv-validator\";\n\n// Import query modules\nimport { generateSchemaQueryData } from \"queries/schema\";\n\n// Define internal parameters\nconst { name: globalDatabaseName } = globalDatabaseData;\n\n\n// getValidator: async\n// returns a promise which either resolves with an instance of the\n// validator for the specified entity/schema, or rejects with an error\nexport async function getValidator(entityName, schemaName) {\n if (typeOf(entityName) !== \"string\") {\n throw new Error(`Argument \"entityName\" should be a non-empty string`);\n } else if (typeOf(schemaName) !== \"string\") {\n throw new Error(`Argument \"schemaName\" should be a non-empty string`);\n }\n\n // Get validation schema from index\n const cacheKey = computeHash(`schemas-${entityName}-${schemaName}`);\n const cacheData = await this.indexService.getDocumentByKey({\n databaseName: globalDatabaseName,\n collectionName: \"schemas\",\n documentKey: cacheKey\n });\n let { value: schemaData } = cacheData || {};\n if (schemaData) {\n console.debug(`Fetched validation schema for ${entityName}/` +\n `${schemaName} from index`);\n return new AjvValidator(schemaData);\n }\n\n // Get validation schema from server\n const queryData = generateSchemaQueryData({ entityName, schemaName });\n try {\n const responseData = await this.graphQLService.query({\n authenticated: false,\n queryData\n });\n ({ schema: schemaData } = responseData);\n } catch(error) {\n console.warn(`Error fetching schema for \"${entityName}/` +\n `${schemaName}\" from server`, error);\n return null;\n }\n console.debug(`Fetched validation schema for ${entityName}/` +\n `${schemaName} from server`);\n\n // Update the index\n await this.indexService.saveDocument({\n databaseName: globalDatabaseName,\n collectionName: \"schemas\",\n documentData: {\n key: cacheKey,\n value: schemaData\n }\n });\n\n // Return validator instance\n return new AjvValidator(schemaData);\n}\n"],"names":[],"sourceRoot":""}