@internetarchive/ia-book-actions 0.1.4-alpha7 → 0.1.5-alpha1

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.
package/demo/index.html CHANGED
@@ -131,11 +131,11 @@
131
131
  iaBookActions.returnUrl = 'https://openlibrary.org';
132
132
  iaBookActions.localCache = localCache;
133
133
 
134
- let loanTotalTime2Min = { loanTotalTime: 20, loanRenewAtLast: 15, pageChangedInLast: 5 };
135
- let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast: 230, pageChangedInLast: 15 };
134
+ let loanTotalTime2Min = { loanTotalTime: 10, loanRenewAtLast: 5, pageChangedInLast: 5 };
135
+ let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast: 290, pageChangedInLast: 15 };
136
136
  let loanTotalTime1Hour = { loanTotalTime: 3600, loanRenewAtLast: 600, pageChangedInLast: 900 };
137
137
 
138
- iaBookActions.loanRenewTimeConfig = loanTotalTime5Min;
138
+ iaBookActions.loanRenewTimeConfig = loanTotalTime2Min;
139
139
 
140
140
  let lendingStatus1 = iaBookActions.lendingStatus;
141
141
  let secondsLeftOnLoan = iaBookActions.loanRenewTimeConfig.loanTotalTime;
@@ -298,16 +298,6 @@
298
298
  })
299
299
  );
300
300
  })
301
-
302
- document.querySelector('.pageChangedEventSec').addEventListener('click', () => {
303
- window.dispatchEvent(
304
- new CustomEvent('BookReader:userAction', {
305
- detail: {},
306
- bubbles: true,
307
- composed: true,
308
- })
309
- );
310
- })
311
301
  </script>
312
302
 
313
303
  <div>
@@ -320,7 +310,6 @@
320
310
  </td>
321
311
  <td>
322
312
  <button class="pageChangedEvent">Page Change Event</button>
323
- <button class="pageChangedEventSec">BR Controls Event</button>
324
313
  </td>
325
314
  </tr>
326
315
  </table>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.1.4-alpha7",
3
+ "version": "0.1.5-alpha1",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -32,7 +32,8 @@ export class CollapsibleActionGroup extends ActionsHandler {
32
32
  dropdownArrow: { type: String },
33
33
  disabled: { type: Boolean },
34
34
  returnUrl: { type: String },
35
- renewNow: { type: Boolean },
35
+ autoRenew: { type: Boolean },
36
+ autoReturn: { type: Boolean },
36
37
  };
37
38
  }
38
39
 
@@ -52,7 +53,8 @@ export class CollapsibleActionGroup extends ActionsHandler {
52
53
  this.loaderIcon = 'https://archive.org/upload/images/tree/loading.gif';
53
54
  this.disabled = false;
54
55
  this.returnUrl = '';
55
- this.renewNow = false;
56
+ this.autoRenew = false;
57
+ this.autoReturn = false;
56
58
  }
57
59
 
58
60
  updated(changed) {
@@ -63,21 +65,33 @@ export class CollapsibleActionGroup extends ActionsHandler {
63
65
  this.resetActions();
64
66
  }
65
67
 
66
- if (changed.has('renewNow') && this.renewNow === true) {
67
- this.dispatchLoanRenewEvent();
68
+ if (changed.has('autoRenew') && this.autoRenew === true) {
69
+ this.dispatchLoanEvent('autoRenew');
70
+ }
71
+
72
+ if (changed.has('autoReturn') && this.autoReturn === true) {
73
+ this.dispatchLoanEvent('autoReturn');
68
74
  }
69
75
  }
70
76
 
71
- // dispatch this event to loan renew handler
72
- dispatchLoanRenewEvent() {
73
- const { category, action } = {
74
- category: analyticsCategories.browse,
75
- action: analyticsActions.browseLoanRenew,
76
- };
77
+ /**
78
+ * dispatch event when book is auto renewed / returned
79
+ * listen these events in action-handler.js
80
+ * @see ActionsHandler
81
+ *
82
+ * @param {string} event - autoRenew|autoReturn
83
+ * @memberof CollapsibleActionGroup
84
+ */
85
+ dispatchLoanEvent(event) {
86
+ const category = analyticsCategories.browse;
87
+ const action =
88
+ event === 'autoRenew'
89
+ ? analyticsActions.browseAutoRenew
90
+ : analyticsActions.browseAutoReturn;
77
91
 
78
92
  // listen in action-handler.js to execute ajax call on petabox.
79
93
  this.dispatchEvent(
80
- new CustomEvent('loanRenew', {
94
+ new CustomEvent(event, {
81
95
  detail: {
82
96
  event: { category, action },
83
97
  },
@@ -41,7 +41,7 @@ export default class TimerCountdown extends LitElement {
41
41
  * delay seconds in setInterval function
42
42
  * @type {number}
43
43
  */
44
- this.timerExecutionSeconds = 0;
44
+ this.timerExecutionSeconds = 60;
45
45
  }
46
46
 
47
47
  disconnectedCallback() {
@@ -61,8 +61,6 @@ export default class TimerCountdown extends LitElement {
61
61
  timerCountdown() {
62
62
  this.disconnectedCallback();
63
63
 
64
- // - let just execute setInterval in every 1 minute
65
- this.timerExecutionSeconds = 60;
66
64
  let secondsLeft = this.secondsLeftOnLoan;
67
65
 
68
66
  // store current time
@@ -13,7 +13,8 @@ export const analyticsCategories = {
13
13
  export const analyticsActions = {
14
14
  browse: 'Borrow-1Hour',
15
15
  browseAgain: 'Borrow-Again',
16
- browseLoanRenew: 'Borrow-Loan-Renew',
16
+ browseAutoRenew: 'AutoRenewBook',
17
+ browseAutoReturn: 'AutoReturnBook',
17
18
  borrow: 'Borrow-14Days',
18
19
  waitlistJoin: 'JoinWaitlist',
19
20
  waitlistLeave: 'LeaveWaitlist',
@@ -2,6 +2,7 @@ import { LitElement } from 'lit';
2
2
 
3
3
  import { URLHelper } from '../../config/url-helper.js';
4
4
  import { sentryLogs } from '../../config/sentry-events.js';
5
+ import { analyticsCategories } from '../../config/analytics-event-and-category.js';
5
6
 
6
7
  import ActionsHandlerService from './actions-handler-service.js';
7
8
  import * as Cookies from '../doc-cookies.js';
@@ -27,31 +28,50 @@ export default class ActionsHandler extends LitElement {
27
28
  this.bindEvents();
28
29
  }
29
30
 
30
- sendEvent(eventCategory, eventAction) {
31
- window?.archive_analytics?.send_event(
31
+ /**
32
+ * reponsible to sent events to GA
33
+ *
34
+ * @param {string} eventCategory
35
+ * @param {string} eventAction
36
+ * @memberof ActionsHandler
37
+ */
38
+ sendEvent(eventCategory, eventAction, label, extraParams) {
39
+ window?.archive_analytics?.send_event_no_sampling(
32
40
  eventCategory,
33
41
  eventAction,
34
- `identifier=${this.identifier}`
42
+ label || `identifier=${this.identifier}`,
43
+ extraParams
35
44
  );
36
45
  }
37
46
 
47
+ get getLoanCountStorageKey() {
48
+ return `br-browse-${this.identifier}`;
49
+ }
50
+
38
51
  bindEvents() {
39
52
  this.addEventListener('browseBook', ({ detail }) => {
40
53
  this.handleBrowseIt();
41
- this.setConsecutiveLoanCounts();
54
+ this.setConsecutiveLoanCounts('browseBook');
42
55
  const { category, action } = detail.event;
43
56
  this.sendEvent(category, action);
44
57
  });
45
58
 
46
59
  this.addEventListener('browseBookAgain', ({ detail }) => {
47
60
  this.handleBrowseIt();
48
- this.setConsecutiveLoanCounts('browseAgain');
61
+ this.setConsecutiveLoanCounts('browseBookAgain');
62
+ const { category, action } = detail.event;
63
+ this.sendEvent(category, action);
64
+ });
65
+
66
+ this.addEventListener('autoRenew', ({ detail }) => {
67
+ this.handleLoanRenewNow();
68
+ this.setConsecutiveLoanCounts('autoRenew');
49
69
  const { category, action } = detail.event;
50
70
  this.sendEvent(category, action);
51
71
  });
52
72
 
53
- this.addEventListener('loanRenew', ({ detail }) => {
54
- this.handleLoanRenew();
73
+ this.addEventListener('autoReturn', ({ detail }) => {
74
+ this.setConsecutiveLoanCounts('autoReturn');
55
75
  const { category, action } = detail.event;
56
76
  this.sendEvent(category, action);
57
77
  });
@@ -107,6 +127,100 @@ export default class ActionsHandler extends LitElement {
107
127
  });
108
128
  }
109
129
 
130
+ /**
131
+ * store consecutive one hour loan counts in cookies for GA
132
+ *
133
+ * There are two possible way of consecutive loan
134
+ * - book is browsed again just after getting expired
135
+ * - book loan is auto renewed for another hour
136
+ *
137
+ * @param {string} action - user action on book
138
+ * @return {void}
139
+ * @memberof ActionsHandler
140
+ */
141
+ async setConsecutiveLoanCounts(action = '') {
142
+ try {
143
+ await this.getConsecutiveLoanRecord(action);
144
+
145
+ const date = new Date();
146
+ date.setHours(date.getHours() + 2); // 2 hours
147
+
148
+ // set new value
149
+ Cookies.setItem(
150
+ this.getLoanCountStorageKey,
151
+ JSON.stringify(this.lendingEventCounts),
152
+ date,
153
+ '/'
154
+ );
155
+ } catch (error) {
156
+ console.log(error);
157
+ window?.Sentry?.captureException(
158
+ `${sentryLogs.setConsecutiveLoanCounts} - Error: ${error}`
159
+ );
160
+ this.sendEvent('Cookies-Error-Actions', error);
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Get consecutive loan count records from cookies for GA
166
+ *
167
+ * @param {string} action
168
+ * @memberof ActionsHandler
169
+ */
170
+ async getConsecutiveLoanRecord(action) {
171
+ const gaStats = { browse: 0, renew: 0, expire: 0 };
172
+
173
+ this.lendingEventCounts = JSON.parse(
174
+ Cookies.getItem(this.getLoanCountStorageKey)
175
+ );
176
+
177
+ let browse = this.lendingEventCounts?.browse ?? 0;
178
+ let renew = this.lendingEventCounts?.renew ?? 0;
179
+ let expire = this.lendingEventCounts?.expire ?? 0;
180
+
181
+ if (this.lendingEventCounts !== null) {
182
+ switch (action) {
183
+ case 'browseBook' || 'browseBookAgain':
184
+ browse = browse ? Number(browse) + 1 : 1;
185
+ gaStats.browse = browse;
186
+ break;
187
+
188
+ case 'autoRenew':
189
+ renew = renew ? Number(renew) + 1 : 1;
190
+ gaStats.renew = renew;
191
+ this.sendEvent(
192
+ analyticsCategories.browse,
193
+ `BookAutoRenewedFor${renew}`
194
+ );
195
+ break;
196
+
197
+ case 'autoReturn':
198
+ expire = expire ? Number(expire) + 1 : 1;
199
+ gaStats.expire = expire;
200
+
201
+ renew = 0; // reset renew count when book is expired
202
+ break;
203
+ default:
204
+ break;
205
+ }
206
+ }
207
+
208
+ // store these counts in cookies
209
+ this.lendingEventCounts = { browse, renew, expire };
210
+
211
+ // send google analytics events
212
+ this.sendEvent(
213
+ analyticsCategories.browse,
214
+ `browse${gaStats.browse}-autoRenew${gaStats.renew}-autoExpire${gaStats.expire}`,
215
+ '',
216
+ {
217
+ browse: gaStats.browse,
218
+ renew: gaStats.renew,
219
+ expire: gaStats.expire,
220
+ }
221
+ );
222
+ }
223
+
110
224
  handleBrowseIt() {
111
225
  const action = 'browse_book';
112
226
  this.dispatchToggleActionGroup();
@@ -124,7 +238,7 @@ export default class ActionsHandler extends LitElement {
124
238
  });
125
239
  }
126
240
 
127
- handleLoanRenew() {
241
+ handleLoanRenewNow() {
128
242
  const action = 'renew_loan';
129
243
 
130
244
  ActionsHandlerService({
@@ -271,31 +385,6 @@ export default class ActionsHandler extends LitElement {
271
385
  }, this.waitUntillBorrowComplete * 1000);
272
386
  }
273
387
 
274
- // save consecutive loan count for borrow
275
- async setConsecutiveLoanCounts(action = '') {
276
- try {
277
- let newCount = 1;
278
- const storageKey = `loan-count-${this.identifier}`;
279
- const existingCount = Cookies.getItem(storageKey);
280
-
281
- // increase browse-count by 1 when you consecutive reading a book.
282
- if (action === 'browseAgain' && existingCount !== undefined) {
283
- newCount = existingCount ? Number(existingCount) + 1 : 1;
284
- }
285
-
286
- const date = new Date();
287
- date.setHours(date.getHours() + 2); // 2 hours
288
-
289
- // set new value
290
- Cookies.setItem(storageKey, newCount, date, '/');
291
- } catch (error) {
292
- window?.Sentry?.captureException(
293
- `${sentryLogs.setConsecutiveLoanCounts} - Error: ${error}`
294
- );
295
- this.sendEvent('Cookies-Error-Actions', error);
296
- }
297
- }
298
-
299
388
  /**
300
389
  * set browse time in indexedDB
301
390
  */
@@ -62,7 +62,7 @@ export function setItem(sKey, sValue, vEnd, sPath, sDomain, bSecure) {
62
62
 
63
63
  /**
64
64
  * BROKEN Remove specific key's value from cookie
65
- * @fixme hasItem isn't even implemented! This will always error.
65
+ *
66
66
  * @param {string} sKey cookie name
67
67
  * @param {string} [sPath] path of current item
68
68
  * @param {string} [sDomain]
@@ -81,3 +81,25 @@ export function removeItem(sKey, sPath, sDomain) {
81
81
 
82
82
  return true;
83
83
  }
84
+
85
+ /**
86
+ * determine if a specific cookie is exist
87
+ *
88
+ * @param {string} sKey cookie name
89
+ *
90
+ * @returns {boolean}
91
+ */
92
+ export function hasItem(sKey) {
93
+ const reCNameAllowed = /^(?:expires|max\-age|path|domain|secure|samesite|httponly)$/i;
94
+
95
+ // eslint-disable-next-line
96
+ if (!sKey || reCNameAllowed.test(sKey)) {
97
+ return false;
98
+ }
99
+
100
+ return new RegExp(
101
+ '(?:^|;\\s*)' +
102
+ encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, '\\$&') +
103
+ '\\s*\\='
104
+ ).test(document.cookie);
105
+ }
@@ -77,7 +77,7 @@ export class LoanTokenPoller {
77
77
  }
78
78
 
79
79
  sendEvent(eventCategory, eventAction) {
80
- window?.archive_analytics?.send_event(
80
+ window?.archive_analytics?.send_event_no_sampling(
81
81
  eventCategory,
82
82
  eventAction,
83
83
  `identifier=${this.identifier}`
@@ -11,7 +11,6 @@ import './components/book-title-bar.js';
11
11
  import './components/text-group.js';
12
12
  import './components/info-icon.js';
13
13
  import './components/timer-countdown.js';
14
- import './core/config/ia-lending-intervals.js';
15
14
 
16
15
  import { GetLendingActions } from './core/services/get-lending-actions.js';
17
16
  import { mobileContainerWidth } from './core/config/constants.js';
@@ -19,6 +18,9 @@ import { sentryLogs } from './core/config/sentry-events.js';
19
18
  import { LoanTokenPoller } from './core/services/loan-token-poller.js';
20
19
  import { LoanRenewHelper } from './core/services/loan-renew-helper.js';
21
20
 
21
+ import './core/config/ia-lending-intervals.js';
22
+ import * as Cookies from './core/services/doc-cookies.js';
23
+
22
24
  export const events = {
23
25
  browseExpired: 'IABookReader:BrowsingHasExpired',
24
26
  };
@@ -236,16 +238,10 @@ export default class IABookActions extends LitElement {
236
238
  /**
237
239
  * tokenPoller determines if user has loan token for this book
238
240
  * - if book is going to renew, need to wait until renew is completed
239
- * - otherwise execute immediately
240
241
  */
241
- setTimeout(
242
- () => {
243
- if (!hasExpired) this.startLoanTokenPoller();
244
- },
245
-
246
- // if book is renew while reading, let's wait to execute create-token api
247
- this.loanRenewResult.renewNow ? this.tokenDelay * 1000 : 100
248
- );
242
+ setTimeout(() => {
243
+ if (!hasExpired) this.startLoanTokenPoller();
244
+ }, 100);
249
245
  }
250
246
 
251
247
  /**
@@ -260,7 +256,6 @@ export default class IABookActions extends LitElement {
260
256
  * dispatched this event from bookreader page changed
261
257
  */
262
258
  window.addEventListener('BookReader:userAction', () => {
263
- console.log('user action');
264
259
  this.suppressToast = true;
265
260
  this.closeToastManager();
266
261
 
@@ -293,7 +288,6 @@ export default class IABookActions extends LitElement {
293
288
  * @see TimerCountdown::timerCountdown
294
289
  */
295
290
  this.addEventListener('IABookActions:loanRenew', async event => {
296
- console.log('event::loanRenew');
297
291
  await this.autoLoanRenewChecker(false);
298
292
 
299
293
  // show warning message with remaining time to auto returned it.
@@ -326,7 +320,7 @@ export default class IABookActions extends LitElement {
326
320
  * close/hide toast message
327
321
  */
328
322
  async closeToastManager() {
329
- let toastTemplate = this.shadowRoot.querySelector('toast-template');
323
+ const toastTemplate = this.shadowRoot.querySelector('toast-template');
330
324
  if (toastTemplate) {
331
325
  toastTemplate?.remove();
332
326
  }
@@ -411,6 +405,9 @@ export default class IABookActions extends LitElement {
411
405
  await this.localCache.delete(`${this.identifier}-loanTime`);
412
406
  await this.localCache.delete(`${this.identifier}-pageChangedTime`);
413
407
 
408
+ // remove loan-renew-count-<identifier> cookie
409
+ Cookies.removeItem(`loan-renew-count-${this.identifier}`);
410
+
414
411
  // show message after browsed book is expired.
415
412
  this.loanRenewResult.renewNow = false;
416
413
  this.loanRenewResult.texts =
@@ -493,7 +490,8 @@ export default class IABookActions extends LitElement {
493
490
  .loanTotalTime=${this.loanRenewTimeConfig.loanTotalTime}
494
491
  ?hasAdminAccess=${this.hasAdminAccess}
495
492
  ?disabled=${this.disableActionGroup}
496
- ?renewNow=${this.loanRenewResult.renewNow}
493
+ ?autoRenew=${this.loanRenewResult.renewNow}
494
+ ?autoReturn=${this.lendingStatus.browsingExpired}
497
495
  @loanAutoRenewed=${this.handleLoanAutoRenewed}
498
496
  @lendingActionError=${this.handleLendingActionError}
499
497
  @toggleActionGroup=${this.handleToggleActionGroup}
@@ -591,7 +589,8 @@ export default class IABookActions extends LitElement {
591
589
  const errorMsg = event?.detail?.data?.error;
592
590
 
593
591
  // template not show create_token errors
594
- if (errorMsg && action !== 'create_token') this.showErrorModal(errorMsg, action);
592
+ if (errorMsg && action !== 'create_token')
593
+ this.showErrorModal(errorMsg, action);
595
594
 
596
595
  // if error related to loan token
597
596
  // - clear tokenPoller interval