{"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":""}