@iankibetsh/shframework 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,36 +1,4 @@
1
1
 
2
- .colored-toast.swal2-icon-success {
3
- background-color: #a5dc86 !important;
4
- }
5
-
6
- .colored-toast.swal2-icon-error {
7
- background-color: #f27474 !important;
8
- }
9
-
10
- .colored-toast.swal2-icon-warning {
11
- background-color: #f8bb86 !important;
12
- }
13
-
14
- .colored-toast.swal2-icon-info {
15
- background-color: #3fc3ee !important;
16
- }
17
-
18
- .colored-toast.swal2-icon-question {
19
- background-color: #87adbd !important;
20
- }
21
-
22
- .colored-toast .swal2-title {
23
- color: white;
24
- }
25
-
26
- .colored-toast .swal2-close {
27
- color: white;
28
- }
29
-
30
- .colored-toast .swal2-html-container {
31
- color: white;
32
- }
33
-
34
2
  .sh-phone{
35
3
  display: flex;
36
4
  width: 100%;
@@ -63,23 +31,36 @@
63
31
  opacity: 0.5;
64
32
  }
65
33
 
66
- .sh-selected-item{
67
- line-height: unset!important;
34
+ .colored-toast.swal2-icon-success {
35
+ background-color: #a5dc86 !important;
68
36
  }
69
- .sh-suggestion-input{
70
- padding: 0.375rem 0.75rem;
37
+
38
+ .colored-toast.swal2-icon-error {
39
+ background-color: #f27474 !important;
71
40
  }
72
- .sh-suggest{
73
- margin-bottom: 1rem;
41
+
42
+ .colored-toast.swal2-icon-warning {
43
+ background-color: #f8bb86 !important;
74
44
  }
75
- .sh-suggest-control::after{
76
- margin-top: auto;
77
- margin-bottom: auto;
78
- margin-right: 0.255em;
45
+
46
+ .colored-toast.swal2-icon-info {
47
+ background-color: #3fc3ee !important;
79
48
  }
80
49
 
81
- .sh-forgot-link, .sh-register-link{
82
- cursor: pointer;
50
+ .colored-toast.swal2-icon-question {
51
+ background-color: #87adbd !important;
52
+ }
53
+
54
+ .colored-toast .swal2-title {
55
+ color: white;
56
+ }
57
+
58
+ .colored-toast .swal2-close {
59
+ color: white;
60
+ }
61
+
62
+ .colored-toast .swal2-html-container {
63
+ color: white;
83
64
  }
84
65
 
85
66
  :root {
@@ -102,3 +83,22 @@
102
83
  width: 100% !important;
103
84
  }
104
85
  }
86
+
87
+ .sh-selected-item{
88
+ line-height: unset!important;
89
+ }
90
+ .sh-suggestion-input{
91
+ padding: 0.375rem 0.75rem;
92
+ }
93
+ .sh-suggest{
94
+ margin-bottom: 1rem;
95
+ }
96
+ .sh-suggest-control::after{
97
+ margin-top: auto;
98
+ margin-bottom: auto;
99
+ margin-right: 0.255em;
100
+ }
101
+
102
+ .sh-forgot-link, .sh-register-link{
103
+ cursor: pointer;
104
+ }
package/dist/library.js CHANGED
@@ -4,10 +4,10 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var Axios = require('axios');
6
6
  var moment = require('moment');
7
+ var Swal = require('sweetalert2');
7
8
  var vue = require('vue');
8
9
  var NProgress = require('nprogress');
9
10
  var Editor = require('@tinymce/tinymce-vue');
10
- var Swal = require('sweetalert2');
11
11
  var pinia = require('pinia');
12
12
  var vueRouter = require('vue-router');
13
13
 
@@ -15,9 +15,9 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
15
15
 
16
16
  var Axios__default = /*#__PURE__*/_interopDefaultLegacy(Axios);
17
17
  var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);
18
+ var Swal__default = /*#__PURE__*/_interopDefaultLegacy(Swal);
18
19
  var NProgress__default = /*#__PURE__*/_interopDefaultLegacy(NProgress);
19
20
  var Editor__default = /*#__PURE__*/_interopDefaultLegacy(Editor);
20
- var Swal__default = /*#__PURE__*/_interopDefaultLegacy(Swal);
21
21
 
22
22
  function setItem (key, value) {
23
23
  let toStore = value;
@@ -28,7 +28,11 @@ function setItem (key, value) {
28
28
  }
29
29
 
30
30
  function getItem (key) {
31
- return localStorage.getItem(key)
31
+ try {
32
+ return JSON.parse(localStorage.getItem(key))
33
+ } catch (err) {
34
+ return localStorage.getItem(key)
35
+ }
32
36
  }
33
37
  function removeItem (key) {
34
38
  return localStorage.removeItem(key)
@@ -39,61 +43,271 @@ var ShStorage = {
39
43
  removeItem
40
44
  };
41
45
 
46
+ function swalSuccess (message) {
47
+ Swal__default["default"].fire('Success!', message, 'success');
48
+ }
49
+ function swalError (message) {
50
+ Swal__default["default"].fire('Error!', message, 'error');
51
+ }
52
+
53
+ function swalHttpError (reason) {
54
+ let error = '';
55
+ if (typeof reason !== 'undefined') {
56
+ if (typeof reason.response !== 'undefined') {
57
+ let reasonString = '';
58
+ if (typeof reason.response.data === 'string') {
59
+ reasonString = reason.response.data;
60
+ } else {
61
+ reasonString = JSON.stringify(reason.response.data);
62
+ }
63
+ error = reason.response.status + ': ' + reason.response.statusText + '<br/>' + reasonString;
64
+ } else {
65
+ if (typeof reason !== 'string') {
66
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
67
+ } else {
68
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
69
+ }
70
+ }
71
+ } else {
72
+ if (typeof reason !== 'string') {
73
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
74
+ } else {
75
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
76
+ }
77
+ }
78
+ Swal__default["default"].fire('Error!', error, 'error');
79
+ }
80
+
81
+ function runSilentRequest (url) {
82
+ return shApis.doPost(url)
83
+ }
84
+
85
+ function setTabCounts (url) {
86
+ shApis.doGet(url).then(res => {
87
+ Object.keys(res.data).forEach(key => {
88
+ const elem = document.getElementById(key);
89
+ if (elem === null) {
90
+ return
91
+ }
92
+ if (typeof elem !== 'undefined') {
93
+ let txt = elem.innerHTML;
94
+ txt = txt.split('<i class="d-none"></i>')[0];
95
+ if (parseInt(res.data[key]) > 0) {
96
+ elem.innerHTML = txt + '<i class="d-none"></i><sup class="rounded-circle p-1 bg-info text-white">' + res.data[key] + '</sup>';
97
+ }
98
+ }
99
+ // document.getElementById(key).innerHTML res.data[key]
100
+ });
101
+ });
102
+ }
103
+ function formatHttpCatchError (reason) {
104
+ console.log(reason);
105
+ let error = '';
106
+ if (typeof reason !== 'undefined') {
107
+ if (typeof reason.response !== 'undefined') {
108
+ alert('here');
109
+ let reasonString = '';
110
+ if (typeof reason.response.data === 'string') {
111
+ reasonString = reason.response.data;
112
+ } else {
113
+ reasonString = JSON.stringify(reason.response.data);
114
+ }
115
+ error = reason.response.status + ': ' + reason.response.statusText + '<br/>' + reasonString;
116
+ } else {
117
+ if (typeof reason !== 'string') {
118
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
119
+ } else {
120
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
121
+ }
122
+ }
123
+ } else {
124
+ if (typeof reason !== 'string') {
125
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
126
+ } else {
127
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
128
+ }
129
+ }
130
+ return error
131
+ }
132
+ function getMenuCount (url) {
133
+ shApis.doGet(url).then(res => {
134
+ console.log(res);
135
+ });
136
+ }
137
+
138
+
139
+ const Toast = Swal__default["default"].mixin({
140
+ toast: true,
141
+ position: 'top-end',
142
+ showConfirmButton: false,
143
+ customClass: {
144
+ popup: 'colored-toast'
145
+ },
146
+ iconColor: 'white',
147
+ timer: 2000,
148
+ timerProgressBar: true,
149
+ didOpen: (toast) => {
150
+ toast.addEventListener('mouseenter', Swal__default["default"].stopTimer);
151
+ toast.addEventListener('mouseleave', Swal__default["default"].resumeTimer);
152
+ }
153
+ });
154
+ function getConfig() {
155
+ const config = ShStorage.getItem('ShConfig');
156
+ console.log(config);
157
+ return config
158
+ }
159
+ function showToast (message, toastType, position) {
160
+ if (!toastType) {
161
+ toastType = 'success';
162
+ }
163
+ if(!position){
164
+ position = window.swalPosition;
165
+ }
166
+ Toast.mixin({
167
+ position: position
168
+ });
169
+ Toast.fire({
170
+ icon: toastType,
171
+ title: message
172
+ });
173
+ }
174
+
175
+ async function runPlainRequest (url, message, title, data) {
176
+ if (typeof title === 'undefined') {
177
+ title = null;
178
+ }
179
+ return Swal__default["default"].fire({
180
+ title: title !== null ? title : 'Are you sure?',
181
+ html: message,
182
+ showCancelButton: true,
183
+ confirmButtonColor: '#32c787',
184
+ cancelButtonText: 'No, cancel',
185
+ confirmButtonText: 'Yes, Proceed!',
186
+ reverseButtons: true,
187
+ showLoaderOnConfirm: true,
188
+ preConfirm: () => {
189
+ return shApis.doPost(url, data).then(function (response) {
190
+ return {
191
+ response: response.data,
192
+ success: true
193
+ }
194
+ })
195
+ .catch(error => {
196
+ return {
197
+ success: false,
198
+ error: error
199
+ }
200
+ })
201
+ },
202
+ allowOutsideClick: () => !Swal__default["default"].isLoading()
203
+ })
204
+ }
205
+
206
+ function formatDate(date, format) {
207
+ if (!format) {
208
+ format = 'lll';
209
+ }
210
+ return moment__default["default"](date).format(format)
211
+ }
212
+
213
+ var shRepo = {
214
+ swalSuccess,
215
+ swalError,
216
+ runPlainRequest,
217
+ getMenuCount,
218
+ setTabCounts,
219
+ getConfig,
220
+ showToast,
221
+ runSilentRequest,
222
+ swalHttpError,
223
+ formatHttpCatchError,
224
+ formatDate
225
+ };
226
+
227
+ startSession();
42
228
  function logoutUser(){
43
- // let logoutUrl = inject()
229
+ const loginUrl = shRepo.getConfig().loginUrl;
44
230
  const logoutApiEndpoint = vue.inject('logoutApiEndpoint','auth/logout');
45
231
  shApis.doPost(logoutApiEndpoint ?? 'auth/logout').then(res=>{
46
232
  ShStorage.removeItem('access_token');
47
233
  ShStorage.removeItem('user');
48
- // const loginUrl = inject('loginUrl','/login')
49
- window.location.href = '/login';
234
+ ShStorage.removeItem('last_activity');
235
+ window.location.href = loginUrl;
50
236
  }).catch(ex=>{
51
- vue.inject('loginUrl','/login');
52
237
  ShStorage.removeItem('access_token');
53
238
  ShStorage.removeItem('user');
54
- window.location.href = '/login';
239
+ window.location.href = loginUrl;
55
240
  });
56
- JSON.parse(ShStorage.getItem('user'));
241
+ ShStorage.getItem('user');
57
242
  }
58
243
  const checkSession = function (isCheking) {
59
- let timeout = vue.inject('sessionTimeout');
60
- if(!timeout){
61
- timeout = 30;
62
- } else {
63
- timeout = parseFloat(timeout);
64
- }
65
- if(window.shLogoutTimeout){
66
- clearTimeout(window.shLogoutTimeout);
67
- }
68
-
69
- if(ShStorage.getItem('access_token')){
70
- const timeOutSession = setTimeout(()=>{
71
- if(ShStorage.getItem('access_token')){
244
+ const timeout = ShStorage.getItem('sessionTimeout');
245
+ const last_activity = ShStorage.getItem('last_activity');
246
+ if (ShStorage.getItem('access_token')) {
247
+ const pastMinutes = moment__default["default"]().diff(last_activity, 'minutes');
248
+ const pastSeconds = moment__default["default"]().diff(last_activity, 'seconds');
249
+ if(pastMinutes >= timeout) {
250
+ const gracePeriod = pastSeconds - (timeout * 60);
251
+ if (gracePeriod >= 30 ) {
72
252
  logoutUser();
73
253
  }
74
- }, timeout * 60 * 1000);
75
- window.shLogoutTimeout = timeOutSession;
254
+ else {
255
+ if (!window.ShConfirmation)
256
+ {
257
+ window.ShConfirmation = shSwalLogout();
258
+ }
259
+ }
260
+ } else {
261
+ console.log(pastSeconds);
262
+ console.log(pastMinutes);
263
+ }
264
+ }else {
265
+ if (window.shInterval) {
266
+ clearInterval(window.shInterval);
267
+ }
76
268
  }
269
+ };
77
270
 
78
- // const sessionStart = ShStorage.getItem('session_start')
79
- // const started = moment(sessionStart)
80
- // if(!sessionStart){
81
- // ShStorage.removeItem('access_token')
82
- // ShStorage.removeItem('user')
83
- // return false
84
- // }
85
- // const pastMinutes = moment().diff(started, 'minutes')
86
- // if(pastMinutes >= timeout) {
87
- // ShStorage.removeItem('user')
88
- // ShStorage.removeItem('access_token')
89
- // return false
90
- // }
91
- // if (isCheking) {
92
- // return true
93
- // }
94
- // const timeNow = moment().toISOString()
95
- // ShStorage.setItem('session_start', timeNow)
96
- // return true
271
+ async function shSwalLogout () {
272
+ return Swal__default["default"].fire({
273
+ title: 'Your session is about to Expire!',
274
+ html: 'You will be logout due to inactivity!',
275
+ showCancelButton: true,
276
+ cancelButtonColor: '#32c787',
277
+ confirmButtonColor: '#000',
278
+ cancelButtonText: 'Stay signed in',
279
+ confirmButtonText: 'Sign out now!',
280
+ timer: 100000,
281
+ allowOutsideClick: false,
282
+ reverseButtons: true,
283
+ showLoaderOnConfirm: true,
284
+ }).then((result) => {
285
+ if (result.isConfirmed) {
286
+ logoutUser();
287
+ } else if (result.isDenied) {
288
+ updateSession();
289
+ }
290
+ })
291
+ }
292
+ function startSession () {
293
+ const timeNow = moment__default["default"]().toISOString();
294
+ const accessToken = ShStorage.getItem('access_token');
295
+ if (accessToken) {
296
+ ShStorage.setItem('last_activity', timeNow);
297
+ const timout = ShStorage.getItem('sessionTimeout');
298
+ const interval = (timout * 60 *1000) / 3;
299
+ window.shInterval = setInterval(()=>{
300
+ checkSession();
301
+ },interval);
302
+ }
303
+ }
304
+ const updateSession = () =>{
305
+ if(!window.shInterval) {
306
+ startSession();
307
+ }
308
+ const timeNow = moment__default["default"]().toISOString();
309
+ ShStorage.setItem('last_activity', timeNow);
310
+ window.ShConfirmation = null;
97
311
  };
98
312
 
99
313
  let apiUrl = undefined.VITE_APP_API_URL;
@@ -109,7 +323,7 @@ const axios = Axios__default["default"].create({
109
323
  baseURL: apiUrl
110
324
  });
111
325
  function doGet (endPoint, data) {
112
- if(!checkSession());
326
+ updateSession();
113
327
  return axios.get(endPoint, {
114
328
  params: data,
115
329
  crossOrigin: true,
@@ -120,7 +334,7 @@ function doGet (endPoint, data) {
120
334
  })
121
335
  }
122
336
  function doPost (endPoint, data) {
123
- if(!checkSession());
337
+ updateSession();
124
338
  return axios.post(endPoint,
125
339
  data,
126
340
  {
@@ -2966,180 +3180,6 @@ function render$2(_ctx, _cache, $props, $setup, $data, $options) {
2966
3180
  script$8.render = render$2;
2967
3181
  script$8.__file = "src/lib/components/list_templates/Pagination.vue";
2968
3182
 
2969
- function swalSuccess (message) {
2970
- Swal__default["default"].fire('Success!', message, 'success');
2971
- }
2972
- function swalError (message) {
2973
- Swal__default["default"].fire('Error!', message, 'error');
2974
- }
2975
-
2976
- function swalHttpError (reason) {
2977
- let error = '';
2978
- if (typeof reason !== 'undefined') {
2979
- if (typeof reason.response !== 'undefined') {
2980
- let reasonString = '';
2981
- if (typeof reason.response.data === 'string') {
2982
- reasonString = reason.response.data;
2983
- } else {
2984
- reasonString = JSON.stringify(reason.response.data);
2985
- }
2986
- error = reason.response.status + ': ' + reason.response.statusText + '<br/>' + reasonString;
2987
- } else {
2988
- if (typeof reason !== 'string') {
2989
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
2990
- } else {
2991
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
2992
- }
2993
- }
2994
- } else {
2995
- if (typeof reason !== 'string') {
2996
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
2997
- } else {
2998
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
2999
- }
3000
- }
3001
- Swal__default["default"].fire('Error!', error, 'error');
3002
- }
3003
-
3004
- function runSilentRequest (url) {
3005
- return shApis.doPost(url)
3006
- }
3007
-
3008
- function setTabCounts (url) {
3009
- shApis.doGet(url).then(res => {
3010
- Object.keys(res.data).forEach(key => {
3011
- const elem = document.getElementById(key);
3012
- if (elem === null) {
3013
- return
3014
- }
3015
- if (typeof elem !== 'undefined') {
3016
- let txt = elem.innerHTML;
3017
- txt = txt.split('<i class="d-none"></i>')[0];
3018
- if (parseInt(res.data[key]) > 0) {
3019
- elem.innerHTML = txt + '<i class="d-none"></i><sup class="rounded-circle p-1 bg-info text-white">' + res.data[key] + '</sup>';
3020
- }
3021
- }
3022
- // document.getElementById(key).innerHTML res.data[key]
3023
- });
3024
- });
3025
- }
3026
- function formatHttpCatchError (reason) {
3027
- console.log(reason);
3028
- let error = '';
3029
- if (typeof reason !== 'undefined') {
3030
- if (typeof reason.response !== 'undefined') {
3031
- alert('here');
3032
- let reasonString = '';
3033
- if (typeof reason.response.data === 'string') {
3034
- reasonString = reason.response.data;
3035
- } else {
3036
- reasonString = JSON.stringify(reason.response.data);
3037
- }
3038
- error = reason.response.status + ': ' + reason.response.statusText + '<br/>' + reasonString;
3039
- } else {
3040
- if (typeof reason !== 'string') {
3041
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
3042
- } else {
3043
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
3044
- }
3045
- }
3046
- } else {
3047
- if (typeof reason !== 'string') {
3048
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
3049
- } else {
3050
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
3051
- }
3052
- }
3053
- return error
3054
- }
3055
- function getMenuCount (url) {
3056
- shApis.doGet(url).then(res => {
3057
- console.log(res);
3058
- });
3059
- }
3060
-
3061
- const Toast = Swal__default["default"].mixin({
3062
- toast: true,
3063
- position: 'top-end',
3064
- showConfirmButton: false,
3065
- customClass: {
3066
- popup: 'colored-toast'
3067
- },
3068
- iconColor: 'white',
3069
- timer: 2000,
3070
- timerProgressBar: true,
3071
- didOpen: (toast) => {
3072
- toast.addEventListener('mouseenter', Swal__default["default"].stopTimer);
3073
- toast.addEventListener('mouseleave', Swal__default["default"].resumeTimer);
3074
- }
3075
- });
3076
- function showToast (message, toastType, position) {
3077
- if (!toastType) {
3078
- toastType = 'success';
3079
- }
3080
- if(!position){
3081
- position = window.swalPosition;
3082
- }
3083
- Toast.mixin({
3084
- position: position
3085
- });
3086
- Toast.fire({
3087
- icon: toastType,
3088
- title: message
3089
- });
3090
- }
3091
-
3092
- async function runPlainRequest (url, message, title, data) {
3093
- if (typeof title === 'undefined') {
3094
- title = null;
3095
- }
3096
- return Swal__default["default"].fire({
3097
- title: title !== null ? title : 'Are you sure?',
3098
- html: message,
3099
- showCancelButton: true,
3100
- confirmButtonColor: '#32c787',
3101
- cancelButtonText: 'No, cancel',
3102
- confirmButtonText: 'Yes, Proceed!',
3103
- reverseButtons: true,
3104
- showLoaderOnConfirm: true,
3105
- preConfirm: () => {
3106
- return shApis.doPost(url, data).then(function (response) {
3107
- return {
3108
- response: response.data,
3109
- success: true
3110
- }
3111
- })
3112
- .catch(error => {
3113
- return {
3114
- success: false,
3115
- error: error
3116
- }
3117
- })
3118
- },
3119
- allowOutsideClick: () => !Swal__default["default"].isLoading()
3120
- })
3121
- }
3122
-
3123
- function formatDate(date, format) {
3124
- if (!format) {
3125
- format = 'lll';
3126
- }
3127
- return moment__default["default"](date).format(format)
3128
- }
3129
-
3130
- var shRepo = {
3131
- swalSuccess,
3132
- swalError,
3133
- runPlainRequest,
3134
- getMenuCount,
3135
- setTabCounts,
3136
- showToast,
3137
- runSilentRequest,
3138
- swalHttpError,
3139
- formatHttpCatchError,
3140
- formatDate
3141
- };
3142
-
3143
3183
  const _hoisted_1$6 = /*#__PURE__*/vue.createElementVNode("span", {
3144
3184
  class: "spinner-border spinner-border-sm me-1",
3145
3185
  role: "status",
@@ -4349,13 +4389,18 @@ const useUserStore = pinia.defineStore('user-store', {
4349
4389
  }),
4350
4390
  actions: {
4351
4391
  setUser (){
4352
- const user = ShStorage.getItem('user') ? JSON.parse(ShStorage.getItem('user')) : null;
4392
+ let user = null;
4393
+ try {
4394
+ user = ShStorage.getItem('user') ? ShStorage.getItem('user') : null;
4395
+ } catch (error) {
4396
+ user= null;
4397
+ }
4353
4398
  if (user) {
4354
4399
  user.isAllowedTo = function (slug) {
4355
4400
  if (this.permissions) {
4356
4401
  let permissions = [];
4357
4402
  if (typeof this.permissions === 'string') {
4358
- permissions = JSON.parse(this.permissions);
4403
+ permissions = this.permissions;
4359
4404
  } else {
4360
4405
  permissions = this.permissions;
4361
4406
  }
@@ -4366,13 +4411,16 @@ const useUserStore = pinia.defineStore('user-store', {
4366
4411
  }
4367
4412
  this.user = user;
4368
4413
  shApis.doGet('auth/user').then(res => {
4369
- const user = res.data.user;
4370
- ShStorage.setItem('user',res.data.user);
4414
+ let user = res.data.user;
4415
+ if (typeof(user) === 'undefined') {
4416
+ user = res.data;
4417
+ }
4418
+ ShStorage.setItem('user',user);
4371
4419
  user.isAllowedTo = function (slug) {
4372
4420
  if (this.permissions) {
4373
4421
  let permissions = [];
4374
4422
  if (typeof this.permissions === 'string') {
4375
- permissions = JSON.parse(this.permissions);
4423
+ permissions = this.permissions;
4376
4424
  } else {
4377
4425
  permissions = this.permissions;
4378
4426
  }
@@ -4392,7 +4440,7 @@ const useUserStore = pinia.defineStore('user-store', {
4392
4440
  });
4393
4441
  if (this.user) {
4394
4442
  if (typeof this.user.permissions === 'string') {
4395
- this.permissions = JSON.parse(this.user.permissions);
4443
+ this.permissions = this.user.permissions;
4396
4444
  } else {
4397
4445
  this.permissions = this.user.permissions;
4398
4446
  }
@@ -4573,7 +4621,7 @@ function viewPermissions(rModule) {
4573
4621
  modulePermissions.value = res.data.permissions;
4574
4622
  module.value = rModule;
4575
4623
  if (rModule.permissions) {
4576
- selectedPermissions.value = JSON.parse(rModule.permissions);
4624
+ selectedPermissions.value = rModule.permissions;
4577
4625
  }
4578
4626
  console.log(module.value);
4579
4627
  }).catch(ex => {
@@ -4829,6 +4877,7 @@ script.__file = "src/lib/components/core/auth/ShAuth.vue";
4829
4877
 
4830
4878
  const ShFrontend = {
4831
4879
  install: (app, options) => {
4880
+ ShStorage.setItem('ShConfig',options);
4832
4881
  if(options.sessionTimeout){
4833
4882
  app.provide('sessionTimeout',options.sessionTimeout);
4834
4883
  ShStorage.setItem('sessionTimeout',options.sessionTimeout);
package/dist/library.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import Axios from 'axios';
2
2
  import moment from 'moment';
3
+ import Swal from 'sweetalert2';
3
4
  import { inject, openBlock, createElementBlock, createElementVNode, createTextVNode, toDisplayString, createCommentVNode, withDirectives, Fragment, renderList, vModelSelect, vModelText, resolveComponent, withModifiers, createVNode, ref, onMounted, unref, normalizeClass, createBlock, resolveDynamicComponent, renderSlot, normalizeProps, guardReactiveProps, withCtx, createStaticVNode, mergeProps, shallowRef, computed, isRef, vModelCheckbox, watch, pushScopeId, popScopeId } from 'vue';
4
5
  import NProgress from 'nprogress';
5
6
  import Editor from '@tinymce/tinymce-vue';
6
- import Swal from 'sweetalert2';
7
7
  import { defineStore, storeToRefs } from 'pinia';
8
8
  import { useRoute, useRouter } from 'vue-router';
9
9
 
@@ -16,7 +16,11 @@ function setItem (key, value) {
16
16
  }
17
17
 
18
18
  function getItem (key) {
19
- return localStorage.getItem(key)
19
+ try {
20
+ return JSON.parse(localStorage.getItem(key))
21
+ } catch (err) {
22
+ return localStorage.getItem(key)
23
+ }
20
24
  }
21
25
  function removeItem (key) {
22
26
  return localStorage.removeItem(key)
@@ -27,61 +31,271 @@ var ShStorage = {
27
31
  removeItem
28
32
  };
29
33
 
34
+ function swalSuccess (message) {
35
+ Swal.fire('Success!', message, 'success');
36
+ }
37
+ function swalError (message) {
38
+ Swal.fire('Error!', message, 'error');
39
+ }
40
+
41
+ function swalHttpError (reason) {
42
+ let error = '';
43
+ if (typeof reason !== 'undefined') {
44
+ if (typeof reason.response !== 'undefined') {
45
+ let reasonString = '';
46
+ if (typeof reason.response.data === 'string') {
47
+ reasonString = reason.response.data;
48
+ } else {
49
+ reasonString = JSON.stringify(reason.response.data);
50
+ }
51
+ error = reason.response.status + ': ' + reason.response.statusText + '<br/>' + reasonString;
52
+ } else {
53
+ if (typeof reason !== 'string') {
54
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
55
+ } else {
56
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
57
+ }
58
+ }
59
+ } else {
60
+ if (typeof reason !== 'string') {
61
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
62
+ } else {
63
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
64
+ }
65
+ }
66
+ Swal.fire('Error!', error, 'error');
67
+ }
68
+
69
+ function runSilentRequest (url) {
70
+ return shApis.doPost(url)
71
+ }
72
+
73
+ function setTabCounts (url) {
74
+ shApis.doGet(url).then(res => {
75
+ Object.keys(res.data).forEach(key => {
76
+ const elem = document.getElementById(key);
77
+ if (elem === null) {
78
+ return
79
+ }
80
+ if (typeof elem !== 'undefined') {
81
+ let txt = elem.innerHTML;
82
+ txt = txt.split('<i class="d-none"></i>')[0];
83
+ if (parseInt(res.data[key]) > 0) {
84
+ elem.innerHTML = txt + '<i class="d-none"></i><sup class="rounded-circle p-1 bg-info text-white">' + res.data[key] + '</sup>';
85
+ }
86
+ }
87
+ // document.getElementById(key).innerHTML res.data[key]
88
+ });
89
+ });
90
+ }
91
+ function formatHttpCatchError (reason) {
92
+ console.log(reason);
93
+ let error = '';
94
+ if (typeof reason !== 'undefined') {
95
+ if (typeof reason.response !== 'undefined') {
96
+ alert('here');
97
+ let reasonString = '';
98
+ if (typeof reason.response.data === 'string') {
99
+ reasonString = reason.response.data;
100
+ } else {
101
+ reasonString = JSON.stringify(reason.response.data);
102
+ }
103
+ error = reason.response.status + ': ' + reason.response.statusText + '<br/>' + reasonString;
104
+ } else {
105
+ if (typeof reason !== 'string') {
106
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
107
+ } else {
108
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
109
+ }
110
+ }
111
+ } else {
112
+ if (typeof reason !== 'string') {
113
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
114
+ } else {
115
+ error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
116
+ }
117
+ }
118
+ return error
119
+ }
120
+ function getMenuCount (url) {
121
+ shApis.doGet(url).then(res => {
122
+ console.log(res);
123
+ });
124
+ }
125
+
126
+
127
+ const Toast = Swal.mixin({
128
+ toast: true,
129
+ position: 'top-end',
130
+ showConfirmButton: false,
131
+ customClass: {
132
+ popup: 'colored-toast'
133
+ },
134
+ iconColor: 'white',
135
+ timer: 2000,
136
+ timerProgressBar: true,
137
+ didOpen: (toast) => {
138
+ toast.addEventListener('mouseenter', Swal.stopTimer);
139
+ toast.addEventListener('mouseleave', Swal.resumeTimer);
140
+ }
141
+ });
142
+ function getConfig() {
143
+ const config = ShStorage.getItem('ShConfig');
144
+ console.log(config);
145
+ return config
146
+ }
147
+ function showToast (message, toastType, position) {
148
+ if (!toastType) {
149
+ toastType = 'success';
150
+ }
151
+ if(!position){
152
+ position = window.swalPosition;
153
+ }
154
+ Toast.mixin({
155
+ position: position
156
+ });
157
+ Toast.fire({
158
+ icon: toastType,
159
+ title: message
160
+ });
161
+ }
162
+
163
+ async function runPlainRequest (url, message, title, data) {
164
+ if (typeof title === 'undefined') {
165
+ title = null;
166
+ }
167
+ return Swal.fire({
168
+ title: title !== null ? title : 'Are you sure?',
169
+ html: message,
170
+ showCancelButton: true,
171
+ confirmButtonColor: '#32c787',
172
+ cancelButtonText: 'No, cancel',
173
+ confirmButtonText: 'Yes, Proceed!',
174
+ reverseButtons: true,
175
+ showLoaderOnConfirm: true,
176
+ preConfirm: () => {
177
+ return shApis.doPost(url, data).then(function (response) {
178
+ return {
179
+ response: response.data,
180
+ success: true
181
+ }
182
+ })
183
+ .catch(error => {
184
+ return {
185
+ success: false,
186
+ error: error
187
+ }
188
+ })
189
+ },
190
+ allowOutsideClick: () => !Swal.isLoading()
191
+ })
192
+ }
193
+
194
+ function formatDate(date, format) {
195
+ if (!format) {
196
+ format = 'lll';
197
+ }
198
+ return moment(date).format(format)
199
+ }
200
+
201
+ var shRepo = {
202
+ swalSuccess,
203
+ swalError,
204
+ runPlainRequest,
205
+ getMenuCount,
206
+ setTabCounts,
207
+ getConfig,
208
+ showToast,
209
+ runSilentRequest,
210
+ swalHttpError,
211
+ formatHttpCatchError,
212
+ formatDate
213
+ };
214
+
215
+ startSession();
30
216
  function logoutUser(){
31
- // let logoutUrl = inject()
217
+ const loginUrl = shRepo.getConfig().loginUrl;
32
218
  const logoutApiEndpoint = inject('logoutApiEndpoint','auth/logout');
33
219
  shApis.doPost(logoutApiEndpoint ?? 'auth/logout').then(res=>{
34
220
  ShStorage.removeItem('access_token');
35
221
  ShStorage.removeItem('user');
36
- // const loginUrl = inject('loginUrl','/login')
37
- window.location.href = '/login';
222
+ ShStorage.removeItem('last_activity');
223
+ window.location.href = loginUrl;
38
224
  }).catch(ex=>{
39
- inject('loginUrl','/login');
40
225
  ShStorage.removeItem('access_token');
41
226
  ShStorage.removeItem('user');
42
- window.location.href = '/login';
227
+ window.location.href = loginUrl;
43
228
  });
44
- JSON.parse(ShStorage.getItem('user'));
229
+ ShStorage.getItem('user');
45
230
  }
46
231
  const checkSession = function (isCheking) {
47
- let timeout = inject('sessionTimeout');
48
- if(!timeout){
49
- timeout = 30;
50
- } else {
51
- timeout = parseFloat(timeout);
52
- }
53
- if(window.shLogoutTimeout){
54
- clearTimeout(window.shLogoutTimeout);
55
- }
56
-
57
- if(ShStorage.getItem('access_token')){
58
- const timeOutSession = setTimeout(()=>{
59
- if(ShStorage.getItem('access_token')){
232
+ const timeout = ShStorage.getItem('sessionTimeout');
233
+ const last_activity = ShStorage.getItem('last_activity');
234
+ if (ShStorage.getItem('access_token')) {
235
+ const pastMinutes = moment().diff(last_activity, 'minutes');
236
+ const pastSeconds = moment().diff(last_activity, 'seconds');
237
+ if(pastMinutes >= timeout) {
238
+ const gracePeriod = pastSeconds - (timeout * 60);
239
+ if (gracePeriod >= 30 ) {
60
240
  logoutUser();
61
241
  }
62
- }, timeout * 60 * 1000);
63
- window.shLogoutTimeout = timeOutSession;
242
+ else {
243
+ if (!window.ShConfirmation)
244
+ {
245
+ window.ShConfirmation = shSwalLogout();
246
+ }
247
+ }
248
+ } else {
249
+ console.log(pastSeconds);
250
+ console.log(pastMinutes);
251
+ }
252
+ }else {
253
+ if (window.shInterval) {
254
+ clearInterval(window.shInterval);
255
+ }
64
256
  }
257
+ };
65
258
 
66
- // const sessionStart = ShStorage.getItem('session_start')
67
- // const started = moment(sessionStart)
68
- // if(!sessionStart){
69
- // ShStorage.removeItem('access_token')
70
- // ShStorage.removeItem('user')
71
- // return false
72
- // }
73
- // const pastMinutes = moment().diff(started, 'minutes')
74
- // if(pastMinutes >= timeout) {
75
- // ShStorage.removeItem('user')
76
- // ShStorage.removeItem('access_token')
77
- // return false
78
- // }
79
- // if (isCheking) {
80
- // return true
81
- // }
82
- // const timeNow = moment().toISOString()
83
- // ShStorage.setItem('session_start', timeNow)
84
- // return true
259
+ async function shSwalLogout () {
260
+ return Swal.fire({
261
+ title: 'Your session is about to Expire!',
262
+ html: 'You will be logout due to inactivity!',
263
+ showCancelButton: true,
264
+ cancelButtonColor: '#32c787',
265
+ confirmButtonColor: '#000',
266
+ cancelButtonText: 'Stay signed in',
267
+ confirmButtonText: 'Sign out now!',
268
+ timer: 100000,
269
+ allowOutsideClick: false,
270
+ reverseButtons: true,
271
+ showLoaderOnConfirm: true,
272
+ }).then((result) => {
273
+ if (result.isConfirmed) {
274
+ logoutUser();
275
+ } else if (result.isDenied) {
276
+ updateSession();
277
+ }
278
+ })
279
+ }
280
+ function startSession () {
281
+ const timeNow = moment().toISOString();
282
+ const accessToken = ShStorage.getItem('access_token');
283
+ if (accessToken) {
284
+ ShStorage.setItem('last_activity', timeNow);
285
+ const timout = ShStorage.getItem('sessionTimeout');
286
+ const interval = (timout * 60 *1000) / 3;
287
+ window.shInterval = setInterval(()=>{
288
+ checkSession();
289
+ },interval);
290
+ }
291
+ }
292
+ const updateSession = () =>{
293
+ if(!window.shInterval) {
294
+ startSession();
295
+ }
296
+ const timeNow = moment().toISOString();
297
+ ShStorage.setItem('last_activity', timeNow);
298
+ window.ShConfirmation = null;
85
299
  };
86
300
 
87
301
  let apiUrl = import.meta.env.VITE_APP_API_URL;
@@ -97,7 +311,7 @@ const axios = Axios.create({
97
311
  baseURL: apiUrl
98
312
  });
99
313
  function doGet (endPoint, data) {
100
- if(!checkSession());
314
+ updateSession();
101
315
  return axios.get(endPoint, {
102
316
  params: data,
103
317
  crossOrigin: true,
@@ -108,7 +322,7 @@ function doGet (endPoint, data) {
108
322
  })
109
323
  }
110
324
  function doPost (endPoint, data) {
111
- if(!checkSession());
325
+ updateSession();
112
326
  return axios.post(endPoint,
113
327
  data,
114
328
  {
@@ -2954,180 +3168,6 @@ function render$2(_ctx, _cache, $props, $setup, $data, $options) {
2954
3168
  script$8.render = render$2;
2955
3169
  script$8.__file = "src/lib/components/list_templates/Pagination.vue";
2956
3170
 
2957
- function swalSuccess (message) {
2958
- Swal.fire('Success!', message, 'success');
2959
- }
2960
- function swalError (message) {
2961
- Swal.fire('Error!', message, 'error');
2962
- }
2963
-
2964
- function swalHttpError (reason) {
2965
- let error = '';
2966
- if (typeof reason !== 'undefined') {
2967
- if (typeof reason.response !== 'undefined') {
2968
- let reasonString = '';
2969
- if (typeof reason.response.data === 'string') {
2970
- reasonString = reason.response.data;
2971
- } else {
2972
- reasonString = JSON.stringify(reason.response.data);
2973
- }
2974
- error = reason.response.status + ': ' + reason.response.statusText + '<br/>' + reasonString;
2975
- } else {
2976
- if (typeof reason !== 'string') {
2977
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
2978
- } else {
2979
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
2980
- }
2981
- }
2982
- } else {
2983
- if (typeof reason !== 'string') {
2984
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
2985
- } else {
2986
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
2987
- }
2988
- }
2989
- Swal.fire('Error!', error, 'error');
2990
- }
2991
-
2992
- function runSilentRequest (url) {
2993
- return shApis.doPost(url)
2994
- }
2995
-
2996
- function setTabCounts (url) {
2997
- shApis.doGet(url).then(res => {
2998
- Object.keys(res.data).forEach(key => {
2999
- const elem = document.getElementById(key);
3000
- if (elem === null) {
3001
- return
3002
- }
3003
- if (typeof elem !== 'undefined') {
3004
- let txt = elem.innerHTML;
3005
- txt = txt.split('<i class="d-none"></i>')[0];
3006
- if (parseInt(res.data[key]) > 0) {
3007
- elem.innerHTML = txt + '<i class="d-none"></i><sup class="rounded-circle p-1 bg-info text-white">' + res.data[key] + '</sup>';
3008
- }
3009
- }
3010
- // document.getElementById(key).innerHTML res.data[key]
3011
- });
3012
- });
3013
- }
3014
- function formatHttpCatchError (reason) {
3015
- console.log(reason);
3016
- let error = '';
3017
- if (typeof reason !== 'undefined') {
3018
- if (typeof reason.response !== 'undefined') {
3019
- alert('here');
3020
- let reasonString = '';
3021
- if (typeof reason.response.data === 'string') {
3022
- reasonString = reason.response.data;
3023
- } else {
3024
- reasonString = JSON.stringify(reason.response.data);
3025
- }
3026
- error = reason.response.status + ': ' + reason.response.statusText + '<br/>' + reasonString;
3027
- } else {
3028
- if (typeof reason !== 'string') {
3029
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
3030
- } else {
3031
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
3032
- }
3033
- }
3034
- } else {
3035
- if (typeof reason !== 'string') {
3036
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
3037
- } else {
3038
- error = 'A Unexpected script error occurred<br/>' + JSON.stringify(reason);
3039
- }
3040
- }
3041
- return error
3042
- }
3043
- function getMenuCount (url) {
3044
- shApis.doGet(url).then(res => {
3045
- console.log(res);
3046
- });
3047
- }
3048
-
3049
- const Toast = Swal.mixin({
3050
- toast: true,
3051
- position: 'top-end',
3052
- showConfirmButton: false,
3053
- customClass: {
3054
- popup: 'colored-toast'
3055
- },
3056
- iconColor: 'white',
3057
- timer: 2000,
3058
- timerProgressBar: true,
3059
- didOpen: (toast) => {
3060
- toast.addEventListener('mouseenter', Swal.stopTimer);
3061
- toast.addEventListener('mouseleave', Swal.resumeTimer);
3062
- }
3063
- });
3064
- function showToast (message, toastType, position) {
3065
- if (!toastType) {
3066
- toastType = 'success';
3067
- }
3068
- if(!position){
3069
- position = window.swalPosition;
3070
- }
3071
- Toast.mixin({
3072
- position: position
3073
- });
3074
- Toast.fire({
3075
- icon: toastType,
3076
- title: message
3077
- });
3078
- }
3079
-
3080
- async function runPlainRequest (url, message, title, data) {
3081
- if (typeof title === 'undefined') {
3082
- title = null;
3083
- }
3084
- return Swal.fire({
3085
- title: title !== null ? title : 'Are you sure?',
3086
- html: message,
3087
- showCancelButton: true,
3088
- confirmButtonColor: '#32c787',
3089
- cancelButtonText: 'No, cancel',
3090
- confirmButtonText: 'Yes, Proceed!',
3091
- reverseButtons: true,
3092
- showLoaderOnConfirm: true,
3093
- preConfirm: () => {
3094
- return shApis.doPost(url, data).then(function (response) {
3095
- return {
3096
- response: response.data,
3097
- success: true
3098
- }
3099
- })
3100
- .catch(error => {
3101
- return {
3102
- success: false,
3103
- error: error
3104
- }
3105
- })
3106
- },
3107
- allowOutsideClick: () => !Swal.isLoading()
3108
- })
3109
- }
3110
-
3111
- function formatDate(date, format) {
3112
- if (!format) {
3113
- format = 'lll';
3114
- }
3115
- return moment(date).format(format)
3116
- }
3117
-
3118
- var shRepo = {
3119
- swalSuccess,
3120
- swalError,
3121
- runPlainRequest,
3122
- getMenuCount,
3123
- setTabCounts,
3124
- showToast,
3125
- runSilentRequest,
3126
- swalHttpError,
3127
- formatHttpCatchError,
3128
- formatDate
3129
- };
3130
-
3131
3171
  const _hoisted_1$6 = /*#__PURE__*/createElementVNode("span", {
3132
3172
  class: "spinner-border spinner-border-sm me-1",
3133
3173
  role: "status",
@@ -4337,13 +4377,18 @@ const useUserStore = defineStore('user-store', {
4337
4377
  }),
4338
4378
  actions: {
4339
4379
  setUser (){
4340
- const user = ShStorage.getItem('user') ? JSON.parse(ShStorage.getItem('user')) : null;
4380
+ let user = null;
4381
+ try {
4382
+ user = ShStorage.getItem('user') ? ShStorage.getItem('user') : null;
4383
+ } catch (error) {
4384
+ user= null;
4385
+ }
4341
4386
  if (user) {
4342
4387
  user.isAllowedTo = function (slug) {
4343
4388
  if (this.permissions) {
4344
4389
  let permissions = [];
4345
4390
  if (typeof this.permissions === 'string') {
4346
- permissions = JSON.parse(this.permissions);
4391
+ permissions = this.permissions;
4347
4392
  } else {
4348
4393
  permissions = this.permissions;
4349
4394
  }
@@ -4354,13 +4399,16 @@ const useUserStore = defineStore('user-store', {
4354
4399
  }
4355
4400
  this.user = user;
4356
4401
  shApis.doGet('auth/user').then(res => {
4357
- const user = res.data.user;
4358
- ShStorage.setItem('user',res.data.user);
4402
+ let user = res.data.user;
4403
+ if (typeof(user) === 'undefined') {
4404
+ user = res.data;
4405
+ }
4406
+ ShStorage.setItem('user',user);
4359
4407
  user.isAllowedTo = function (slug) {
4360
4408
  if (this.permissions) {
4361
4409
  let permissions = [];
4362
4410
  if (typeof this.permissions === 'string') {
4363
- permissions = JSON.parse(this.permissions);
4411
+ permissions = this.permissions;
4364
4412
  } else {
4365
4413
  permissions = this.permissions;
4366
4414
  }
@@ -4380,7 +4428,7 @@ const useUserStore = defineStore('user-store', {
4380
4428
  });
4381
4429
  if (this.user) {
4382
4430
  if (typeof this.user.permissions === 'string') {
4383
- this.permissions = JSON.parse(this.user.permissions);
4431
+ this.permissions = this.user.permissions;
4384
4432
  } else {
4385
4433
  this.permissions = this.user.permissions;
4386
4434
  }
@@ -4561,7 +4609,7 @@ function viewPermissions(rModule) {
4561
4609
  modulePermissions.value = res.data.permissions;
4562
4610
  module.value = rModule;
4563
4611
  if (rModule.permissions) {
4564
- selectedPermissions.value = JSON.parse(rModule.permissions);
4612
+ selectedPermissions.value = rModule.permissions;
4565
4613
  }
4566
4614
  console.log(module.value);
4567
4615
  }).catch(ex => {
@@ -4817,6 +4865,7 @@ script.__file = "src/lib/components/core/auth/ShAuth.vue";
4817
4865
 
4818
4866
  const ShFrontend = {
4819
4867
  install: (app, options) => {
4868
+ ShStorage.setItem('ShConfig',options);
4820
4869
  if(options.sessionTimeout){
4821
4870
  app.provide('sessionTimeout',options.sessionTimeout);
4822
4871
  ShStorage.setItem('sessionTimeout',options.sessionTimeout);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iankibetsh/shframework",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Vue library for handling laravel backend",
5
5
  "main": "dist/library.js",
6
6
  "module": "dist/library.mjs",