@internetarchive/ia-book-actions 0.1.4 → 0.1.5-alpha2

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
@@ -128,14 +128,14 @@
128
128
  iaBookActions.modalConfig = modalConfig;
129
129
  iaBookActions.lendingBarPostInit = lendingBarPostInit;
130
130
  iaBookActions.tokenDelay = 2; // 2 seconds
131
- iaBookActions.returnUrl = 'https://openlibrary.org';
131
+ iaBookActions.returnUrl = '';
132
132
  iaBookActions.localCache = localCache;
133
133
 
134
- let loanTotalTime2Min = { loanTotalTime: 20, loanRenewAtLast: 15, pageChangedInLast: 15 };
135
- let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast: 180, 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 = loanTotalTime1Hour;
138
+ iaBookActions.loanRenewTimeConfig = loanTotalTime2Min;
139
139
 
140
140
  let lendingStatus1 = iaBookActions.lendingStatus;
141
141
  let secondsLeftOnLoan = iaBookActions.loanRenewTimeConfig.loanTotalTime;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.1.4",
3
+ "version": "0.1.5-alpha2",
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
  },
@@ -31,43 +31,51 @@ export default class TimerCountdown extends LitElement {
31
31
  */
32
32
  this.loanRenewAtLast = 0;
33
33
 
34
+ /**
35
+ * store time when timer is started
36
+ * @type {string}
37
+ */
38
+ this.timeWhenTimerStart = '';
39
+
34
40
  /**
35
41
  * delay seconds in setInterval function
36
42
  * @type {number}
37
43
  */
38
- this.timerExecutionSeconds = 0;
44
+ this.timerExecutionSeconds = 60;
39
45
  }
40
46
 
41
47
  disconnectedCallback() {
42
48
  window?.IALendingIntervals?.clearTimerCountdown();
43
49
  }
44
50
 
51
+ firstUpdated() {
52
+ this.timerCountdown();
53
+ }
54
+
45
55
  updated(changed) {
46
56
  if (changed.has('secondsLeftOnLoan') && this.secondsLeftOnLoan > 0) {
47
- this.disconnectedCallback();
48
-
49
57
  this.timerCountdown();
50
58
  }
51
59
  }
52
60
 
53
61
  timerCountdown() {
54
- // - let just execute setInterval in every 1 minute
55
- this.timerExecutionSeconds = 60;
62
+ this.disconnectedCallback();
63
+
64
+ let secondsLeft = this.secondsLeftOnLoan;
65
+
66
+ // store current time
67
+ this.timeWhenTimerStart = new Date();
56
68
 
57
69
  /**
58
70
  * set interval in window object
59
71
  * @see ia-lending-intervals.js
60
72
  */
61
- window.IALendingIntervals.timerCountdown = setInterval(() => {
62
- this.secondsLeftOnLoan -= this.timerExecutionSeconds;
63
- const secondsLeft = Math.round(this.secondsLeftOnLoan);
73
+ window.IALendingIntervals.timerCountdown = setInterval(async () => {
74
+ secondsLeft -= this.timerExecutionSeconds;
75
+ secondsLeft = Math.round(secondsLeft); // round number
64
76
 
65
- console.log(
66
- 'timeLeftInSec',
67
- secondsLeft,
68
- ' ||| timeLeftInMin',
69
- Math.ceil(secondsLeft / 60)
70
- );
77
+ // re-sync timer is gone off
78
+ await this.reSyncTimerIfGoneOff(secondsLeft);
71
79
 
72
80
  /**
73
81
  * execute from last 10th minute to 0th minute
@@ -76,26 +84,74 @@ export default class TimerCountdown extends LitElement {
76
84
  * @see IABookActions::bindLoanRenewEvents
77
85
  */
78
86
  if (secondsLeft <= this.loanRenewAtLast) {
79
- this.dispatchEvent(
80
- new CustomEvent('IABookActions:loanRenew', {
81
- detail: {
82
- hasPageChanged: false,
83
- secondsLeft,
84
- },
85
- bubbles: true,
86
- composed: true,
87
- })
88
- );
87
+ this.dispatchLoanRenewEvent(secondsLeft);
89
88
  }
90
89
 
91
- // clear interval
92
- if (secondsLeft <= 60) {
90
+ // clear interval in secondsLeft if less
91
+ if (secondsLeft <= this.timerExecutionSeconds) {
93
92
  this.disconnectedCallback();
94
93
  window?.Sentry?.captureMessage(sentryLogs.clearOneHourTimer);
95
94
  }
96
95
  }, this.timerExecutionSeconds * 1000);
97
96
  }
98
97
 
98
+ /**
99
+ * dispatch loan renew attempt event
100
+ *
101
+ * @param {number} secondsLeft
102
+ * @memberof TimerCountdown
103
+ */
104
+ dispatchLoanRenewEvent(secondsLeft) {
105
+ this.dispatchEvent(
106
+ new CustomEvent('IABookActions:loanRenew', {
107
+ detail: {
108
+ hasPageChanged: false,
109
+ secondsLeft,
110
+ },
111
+ bubbles: true,
112
+ composed: true,
113
+ })
114
+ );
115
+ }
116
+
117
+ /**
118
+ * helper function to determine if timer is not in sync properly
119
+ *
120
+ * @param {number} timerSecondsLeft - actual seconds left get from setInterval
121
+ *
122
+ * @memberof TimerCountdown
123
+ */
124
+ async reSyncTimerIfGoneOff(timerSecondsLeft) {
125
+ // debug info
126
+ console.log(
127
+ new Date().toLocaleTimeString(),
128
+ 'timeLeftInSec',
129
+ timerSecondsLeft,
130
+ ' ||| timeLeftInMin',
131
+ Math.ceil(timerSecondsLeft / 60)
132
+ );
133
+
134
+ const currentTime = new Date();
135
+
136
+ // current time - loan time
137
+ const diffInSeconds =
138
+ currentTime.getTime() / 1000 - this.timeWhenTimerStart.getTime() / 1000;
139
+
140
+ const secondsShouldLeft = this.secondsLeftOnLoan - diffInSeconds;
141
+
142
+ // convert in minutes
143
+ const whatIsleft = Math.round(timerSecondsLeft);
144
+ const whatShouldLeft = Math.round(secondsShouldLeft);
145
+
146
+ if (whatIsleft !== whatShouldLeft) {
147
+ // debug info
148
+ console.log(
149
+ `timer is gone off, let's re-sync, whatIsleft -> ${whatIsleft}, whatShouldLeft -> ${whatShouldLeft}`
150
+ );
151
+ this.secondsLeftOnLoan = secondsShouldLeft; // the seconds should left in timer again
152
+ }
153
+ }
154
+
99
155
  /**
100
156
  * get remaining time with timeunit
101
157
  *
@@ -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',
@@ -1,17 +1,16 @@
1
1
  import { LitElement } from 'lit';
2
2
 
3
3
  import { URLHelper } from '../../config/url-helper.js';
4
- import { sentryLogs } from '../../config/sentry-events.js';
5
4
 
6
5
  import ActionsHandlerService from './actions-handler-service.js';
6
+ import LoanAnanlytics from '../loan-analytics.js';
7
7
  import * as Cookies from '../doc-cookies.js';
8
8
 
9
9
  /**
10
10
  * These are callback functions calling from actions-config.js file.
11
11
  *
12
- * ActionsHandlerService is a function being used to execute
13
- * APIs based of the request made by user. It consist some parameters are as follows:-
14
- * 1. identifier: book name.
12
+ * ActionsHandlerService is a function being used to execute APIs based of the request made by user.
13
+ *
15
14
  */
16
15
 
17
16
  export default class ActionsHandler extends LitElement {
@@ -24,86 +23,148 @@ export default class ActionsHandler extends LitElement {
24
23
  */
25
24
  this.waitUntillBorrowComplete = 6; // in seconds
26
25
 
27
- this.bindEvents();
28
- }
26
+ /**
27
+ * bind events for lending bar
28
+ * @type {object}
29
+ */
30
+ this.loanAnanlytics = new LoanAnanlytics();
29
31
 
30
- sendEvent(eventCategory, eventAction) {
31
- window?.archive_analytics?.send_event(
32
- eventCategory,
33
- eventAction,
34
- `identifier=${this.identifier}`
35
- );
32
+ /**
33
+ * bind events for lending bar
34
+ * @type {Function}
35
+ */
36
+ this.bindEvents();
36
37
  }
37
38
 
38
39
  bindEvents() {
39
40
  this.addEventListener('browseBook', ({ detail }) => {
40
41
  this.handleBrowseIt();
41
- this.setConsecutiveLoanCounts();
42
+ this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'browse');
42
43
  const { category, action } = detail.event;
43
- this.sendEvent(category, action);
44
+ this.loanAnanlytics?.sendEvent(
45
+ category,
46
+ action,
47
+ `identifier=${this.identifier}`
48
+ );
44
49
  });
45
50
 
46
51
  this.addEventListener('browseBookAgain', ({ detail }) => {
47
52
  this.handleBrowseIt();
48
- this.setConsecutiveLoanCounts('browseAgain');
53
+ this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'browseagain');
49
54
  const { category, action } = detail.event;
50
- this.sendEvent(category, action);
55
+ this.loanAnanlytics?.sendEvent(
56
+ category,
57
+ action,
58
+ `identifier=${this.identifier}`
59
+ );
51
60
  });
52
61
 
53
- this.addEventListener('loanRenew', ({ detail }) => {
54
- this.handleLoanRenew();
62
+ this.addEventListener('autoRenew', ({ detail }) => {
63
+ this.handleLoanRenewNow();
64
+ this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'autorenew');
55
65
  const { category, action } = detail.event;
56
- this.sendEvent(category, action);
66
+ this.loanAnanlytics?.sendEvent(
67
+ category,
68
+ action,
69
+ `identifier=${this.identifier}`
70
+ );
71
+ });
72
+
73
+ this.addEventListener('autoReturn', ({ detail }) => {
74
+ this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'autoreturn');
75
+ const { category, action } = detail.event;
76
+ this.loanAnanlytics?.sendEvent(
77
+ category,
78
+ action,
79
+ `identifier=${this.identifier}`
80
+ );
57
81
  });
58
82
 
59
83
  this.addEventListener('returnNow', ({ detail }) => {
84
+ this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'return');
60
85
  this.handleReturnIt();
61
86
  const { category, action } = detail.event;
62
- this.sendEvent(category, action);
87
+ this.loanAnanlytics?.sendEvent(
88
+ category,
89
+ action,
90
+ `identifier=${this.identifier}`
91
+ );
63
92
  });
64
93
 
65
94
  this.addEventListener('borrowBook', ({ detail }) => {
66
95
  this.handleBorrowIt();
67
96
  const { category, action } = detail.event;
68
- this.sendEvent(category, action);
97
+ this.loanAnanlytics?.sendEvent(
98
+ category,
99
+ action,
100
+ `identifier=${this.identifier}`
101
+ );
69
102
  });
70
103
 
71
104
  this.addEventListener('loginAndBorrow', ({ detail }) => {
72
105
  this.handleLoginOk();
73
106
  const { category, action } = detail.event;
74
- this.sendEvent(category, action);
107
+ this.loanAnanlytics?.sendEvent(
108
+ category,
109
+ action,
110
+ `identifier=${this.identifier}`
111
+ );
75
112
  });
76
113
 
77
114
  this.addEventListener('leaveWaitlist', ({ detail }) => {
78
115
  this.handleRemoveFromWaitingList();
79
116
  const { category, action } = detail.event;
80
- this.sendEvent(category, action);
117
+ this.loanAnanlytics?.sendEvent(
118
+ category,
119
+ action,
120
+ `identifier=${this.identifier}`
121
+ );
81
122
  });
82
123
 
83
124
  this.addEventListener('joinWaitlist', ({ detail }) => {
84
125
  this.handleReserveIt();
85
126
  const { category, action } = detail.event;
86
- this.sendEvent(category, action);
127
+ this.loanAnanlytics?.sendEvent(
128
+ category,
129
+ action,
130
+ `identifier=${this.identifier}`
131
+ );
87
132
  });
88
133
 
89
134
  this.addEventListener('purchaseBook', ({ detail }) => {
90
135
  const { category, action } = detail.event;
91
- this.sendEvent(category, action);
136
+ this.loanAnanlytics?.sendEvent(
137
+ category,
138
+ action,
139
+ `identifier=${this.identifier}`
140
+ );
92
141
  });
93
142
 
94
143
  this.addEventListener('adminAccess', ({ detail }) => {
95
144
  const { category, action } = detail.event;
96
- this.sendEvent(category, action);
145
+ this.loanAnanlytics?.sendEvent(
146
+ category,
147
+ action,
148
+ `identifier=${this.identifier}`
149
+ );
97
150
  });
98
151
 
99
152
  this.addEventListener('exitAdminAccess', ({ detail }) => {
100
153
  const { category, action } = detail.event;
101
- this.sendEvent(category, action);
154
+ this.loanAnanlytics?.sendEvent(
155
+ category,
156
+ action,
157
+ `identifier=${this.identifier}`
158
+ );
102
159
  });
103
160
 
104
161
  this.addEventListener('bookTitleBar', ({ detail }) => {
105
162
  const { category, action } = detail.event;
106
- this.sendEvent(category, action);
163
+ this.loanAnanlytics?.sendEvent(
164
+ category,
165
+ action,
166
+ `identifier=${this.identifier}`
167
+ );
107
168
  });
108
169
  }
109
170
 
@@ -124,7 +185,7 @@ export default class ActionsHandler extends LitElement {
124
185
  });
125
186
  }
126
187
 
127
- handleLoanRenew() {
188
+ handleLoanRenewNow() {
128
189
  const action = 'renew_loan';
129
190
 
130
191
  ActionsHandlerService({
@@ -220,7 +281,7 @@ export default class ActionsHandler extends LitElement {
220
281
  */
221
282
  dispatchActionError(action, data = {}) {
222
283
  // send LendingServiceError to GA
223
- this.sendEvent('LendingServiceError', action);
284
+ this.loanAnanlytics?.sendEvent('LendingServiceError', action);
224
285
 
225
286
  this.dispatchEvent(
226
287
  new CustomEvent('lendingActionError', {
@@ -271,31 +332,6 @@ export default class ActionsHandler extends LitElement {
271
332
  }, this.waitUntillBorrowComplete * 1000);
272
333
  }
273
334
 
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
335
  /**
300
336
  * set browse time in indexedDB
301
337
  */
@@ -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
+ }
@@ -0,0 +1,189 @@
1
+ /* eslint-disable class-methods-use-this */
2
+
3
+ import { analyticsCategories } from '../config/analytics-event-and-category.js';
4
+ import * as Cookies from './doc-cookies.js';
5
+
6
+ /**
7
+ * This class is used to send different GA events for loan system.
8
+ */
9
+ export default class LoanAnanlytics {
10
+ constructor() {
11
+ this.identifier = undefined;
12
+
13
+ /**
14
+ * contains the counts we send to GA
15
+ * @type {object}
16
+ * - @param {number} - browse count
17
+ * - @param {number} - renew count
18
+ * - @param {number} - expire count
19
+ */
20
+ this.gaStats = {};
21
+ }
22
+
23
+ /**
24
+ * store loan stats count in cookies to remember the count we need to sent GA
25
+ *
26
+ * @param {string} identifier - book id
27
+ * @param {string} action - user action on book
28
+ * @return {void}
29
+ * @memberof ActionsHandler
30
+ */
31
+ async storeLoanStatsCount(idetifier, action = '') {
32
+ this.identifier = idetifier;
33
+
34
+ try {
35
+ await this.getLoanStatsCount(action);
36
+
37
+ const date = new Date();
38
+ date.setHours(date.getHours() + 2); // 2 hours
39
+
40
+ // set new value
41
+ Cookies.setItem(
42
+ this.getLoanCountStorageKey,
43
+ JSON.stringify(this.lendingEventCounts),
44
+ date,
45
+ '/'
46
+ );
47
+ } catch (error) {
48
+ console.log(error);
49
+ this.sendEvent(
50
+ 'Cookies-Error-Actions',
51
+ error,
52
+ `identifier=${this.identifier}`
53
+ );
54
+ }
55
+ }
56
+
57
+ /**
58
+ * get loan stats count from cookies for GA
59
+ *
60
+ * @param {string} action 'browse'|'autorenew'|etc...
61
+ * @memberof ActionsHandler
62
+ */
63
+ async getLoanStatsCount(action) {
64
+ this.lendingEventCounts = JSON.parse(
65
+ Cookies.getItem(this.getLoanCountStorageKey)
66
+ );
67
+
68
+ this.gaStats = this.lendingEventCounts ?? {
69
+ browse: 0,
70
+ renew: 0,
71
+ expire: 0,
72
+ };
73
+
74
+ let { browse, renew, expire } = this.lendingEventCounts ?? 0;
75
+
76
+ switch (action) {
77
+ case 'browse' || 'browseagain':
78
+ browse = browse ? Number(browse) + 1 : 1;
79
+ this.gaStats.browse = browse;
80
+
81
+ // reset renew and expire count when browse
82
+ renew = 0;
83
+ expire = 0;
84
+ break;
85
+
86
+ case 'autorenew':
87
+ renew = renew ? Number(renew) + 1 : 1;
88
+ this.gaStats.renew = renew;
89
+ break;
90
+
91
+ case 'return' || 'autoreturn':
92
+ expire = expire ? Number(expire) + 1 : 1;
93
+ this.gaStats.expire = expire;
94
+
95
+ // reset renew and expire count when browse
96
+ renew = 0;
97
+ expire = 0;
98
+ break;
99
+ default:
100
+ break;
101
+ }
102
+
103
+ // store these counts in cookies
104
+ this.lendingEventCounts = { browse, renew, expire };
105
+
106
+ // obtain GA category, event
107
+ // const { category, event } = await this.getGACategoryAction(action);
108
+ const category = analyticsCategories.browse;
109
+ const event = `browse${this.paddedNumber(
110
+ this.gaStats?.browse
111
+ )}-autorenew${this.paddedNumber(this.gaStats?.renew)}:${action}`;
112
+
113
+ // send google analytics events
114
+ this.sendEvent(category, event, `identifier=${this.identifier}`, {
115
+ browse: this.gaStats?.browse,
116
+ renew: this.gaStats?.renew,
117
+ expire: this.gaStats?.expire,
118
+ });
119
+ }
120
+
121
+ /**
122
+ *
123
+ *
124
+ * @param {string} action - like 'browse|autorenew|etc...'
125
+ * @return {object} - contains GA category and event
126
+ * - @param {string}
127
+ * - @param {string}
128
+ *
129
+ * @memberof LoanAnanlytics
130
+ */
131
+ getGACategoryAction(action) {
132
+ const GACategory = analyticsCategories.browse;
133
+ const GAAction = `browse${this.paddedNumber(
134
+ this.gaStats?.browse
135
+ )}-autorenew${this.paddedNumber(this.gaStats?.renew)}:${action}`;
136
+
137
+ return { GACategory, GAAction };
138
+ }
139
+
140
+ /**
141
+ * get zero padded number
142
+ *
143
+ * @param {number} number
144
+ * @return {string} browse001|autorenew001
145
+ * @memberof LoanAnanlytics
146
+ */
147
+ paddedNumber(number) {
148
+ return number.toString().padStart(3, '0');
149
+ }
150
+
151
+ /**
152
+ * get storage key for loan stats count
153
+ *
154
+ * @readonly
155
+ * @memberof LoanAnanlytics
156
+ */
157
+ get getLoanCountStorageKey() {
158
+ return `br-browse-${this.identifier}`;
159
+ }
160
+
161
+ /**
162
+ * reponsible to sent events to GA
163
+ *
164
+ * @param {*} eventCategory
165
+ * @param {*} eventAction
166
+ * @param {*} label
167
+ * @param {*} extraParams
168
+ * @memberof LoanAnanlytics
169
+ */
170
+ sendEvent(eventCategory, eventAction, label, extraParams) {
171
+ console.debug(
172
+ 'eventCategory:- ',
173
+ eventCategory,
174
+ '\neventAction:- ',
175
+ eventAction,
176
+ '\nlabel:- ',
177
+ label,
178
+ '\nextraParams:- ',
179
+ extraParams
180
+ );
181
+
182
+ window?.archive_analytics?.send_event_no_sampling(
183
+ eventCategory,
184
+ eventAction,
185
+ label || `identifier=${this.identifier}`,
186
+ extraParams
187
+ );
188
+ }
189
+ }
@@ -1,4 +1,5 @@
1
1
  import ActionsHandlerService from './actions-handler/actions-handler-service.js';
2
+ import LoanAnanlytics from './loan-analytics.js';
2
3
  import { sentryLogs } from '../config/sentry-events.js';
3
4
 
4
5
  import * as Cookies from './doc-cookies.js';
@@ -17,6 +18,13 @@ export class LoanTokenPoller {
17
18
  this.pollerDelay = pollerDelay; // value in seconds
18
19
 
19
20
  this.loanTokenInterval = undefined;
21
+
22
+ /**
23
+ * loan analytics instance
24
+ * @see loan-analytics.js
25
+ */
26
+ this.loanAnalytics = new LoanAnanlytics();
27
+
20
28
  this.bookAccessed();
21
29
  }
22
30
 
@@ -39,7 +47,6 @@ export class LoanTokenPoller {
39
47
  window?.Sentry?.captureException(
40
48
  `${sentryLogs.enableBookAcces} - CookieError: ${error}`
41
49
  );
42
- this.sendEvent('Cookies-Error-Token', error);
43
50
  }
44
51
  }
45
52
 
@@ -65,7 +72,11 @@ export class LoanTokenPoller {
65
72
  this.borrowType === 'browsed' ? 'BrowseCounts-' : 'Counts-'
66
73
  }${consecutiveLoanCounts}`;
67
74
 
68
- this.sendEvent(category, action);
75
+ this.loanAnalytics?.sendEvent(
76
+ category,
77
+ action,
78
+ `identifier=${this.identifier}`
79
+ );
69
80
  } else {
70
81
  window?.Sentry?.captureMessage(
71
82
  `${sentryLogs.bookAccessed} - not borrowed`
@@ -76,14 +87,6 @@ export class LoanTokenPoller {
76
87
  }
77
88
  }
78
89
 
79
- sendEvent(eventCategory, eventAction) {
80
- window?.archive_analytics?.send_event(
81
- eventCategory,
82
- eventAction,
83
- `identifier=${this.identifier}`
84
- );
85
- }
86
-
87
90
  async handleLoanTokenPoller(isInitial = false) {
88
91
  const action = 'create_token';
89
92
  ActionsHandlerService({
@@ -98,7 +101,11 @@ export class LoanTokenPoller {
98
101
  );
99
102
 
100
103
  // send LendingServiceError to GA
101
- this.sendEvent('LendingServiceLoanError', action);
104
+ this.loanAnalytics?.sendEvent(
105
+ 'LendingServiceLoanError',
106
+ action,
107
+ `identifier=${this.identifier}`
108
+ );
102
109
  },
103
110
  success: () => {
104
111
  if (isInitial) this.successCallback();
@@ -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
- 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,12 +256,12 @@ export default class IABookActions extends LitElement {
260
256
  * dispatched this event from bookreader page changed
261
257
  */
262
258
  window.addEventListener('BookReader:userAction', () => {
259
+ this.suppressToast = true;
260
+ this.closeToastManager();
261
+
263
262
  if (this.borrowType === 'browsed') {
264
- this.suppressToast = false;
265
263
  this.autoLoanRenewChecker(true);
266
264
  }
267
-
268
- return nothing;
269
265
  });
270
266
 
271
267
  /**
@@ -320,6 +316,16 @@ export default class IABookActions extends LitElement {
320
316
  this.loanRenewResult = this.loanRenewHelper.result;
321
317
  }
322
318
 
319
+ /**
320
+ * close/hide toast message
321
+ */
322
+ async closeToastManager() {
323
+ const toastTemplate = this.shadowRoot.querySelector('toast-template');
324
+ if (toastTemplate) {
325
+ toastTemplate?.remove();
326
+ }
327
+ }
328
+
323
329
  /**
324
330
  * Show toast messages on some specific loan renew features. e.g.
325
331
  * - show message when book is auto renewed
@@ -329,14 +335,11 @@ export default class IABookActions extends LitElement {
329
335
  async showToastMessage() {
330
336
  if (this.suppressToast) return;
331
337
 
332
- const iaBookActions = document.querySelector('ia-book-actions')?.shadowRoot;
333
- if (!iaBookActions) return;
334
-
335
- let toastTemplate = iaBookActions.querySelector('toast-template');
338
+ let toastTemplate = this.shadowRoot.querySelector('toast-template');
336
339
  if (!toastTemplate) {
337
340
  toastTemplate = document.createElement('toast-template');
338
341
  }
339
- await iaBookActions.appendChild(toastTemplate);
342
+ await this.shadowRoot.appendChild(toastTemplate);
340
343
 
341
344
  // if secondsLeft < 60, consider it 1 minute
342
345
  let { secondsLeft } = this.loanRenewResult;
@@ -402,6 +405,9 @@ export default class IABookActions extends LitElement {
402
405
  await this.localCache.delete(`${this.identifier}-loanTime`);
403
406
  await this.localCache.delete(`${this.identifier}-pageChangedTime`);
404
407
 
408
+ // remove loan-renew-count-<identifier> cookie
409
+ Cookies.removeItem(`loan-renew-count-${this.identifier}`);
410
+
405
411
  // show message after browsed book is expired.
406
412
  this.loanRenewResult.renewNow = false;
407
413
  this.loanRenewResult.texts =
@@ -484,7 +490,8 @@ export default class IABookActions extends LitElement {
484
490
  .loanTotalTime=${this.loanRenewTimeConfig.loanTotalTime}
485
491
  ?hasAdminAccess=${this.hasAdminAccess}
486
492
  ?disabled=${this.disableActionGroup}
487
- ?renewNow=${this.loanRenewResult.renewNow}
493
+ ?autoRenew=${this.loanRenewResult.renewNow}
494
+ ?autoReturn=${this.lendingStatus.browsingExpired}
488
495
  @loanAutoRenewed=${this.handleLoanAutoRenewed}
489
496
  @lendingActionError=${this.handleLendingActionError}
490
497
  @toggleActionGroup=${this.handleToggleActionGroup}
@@ -573,8 +580,7 @@ export default class IABookActions extends LitElement {
573
580
  * @param {string} event.detail.data.error - error message
574
581
  */
575
582
  handleLendingActionError(event) {
576
- // toggle activity loader
577
- this.handleToggleActionGroup();
583
+ this.disableActionGroup = false;
578
584
 
579
585
  // clear all intervals for lending system when error occured
580
586
  window?.IALendingIntervals?.clearAll();
@@ -582,7 +588,9 @@ export default class IABookActions extends LitElement {
582
588
  const action = event?.detail?.action;
583
589
  const errorMsg = event?.detail?.data?.error;
584
590
 
585
- if (errorMsg) this.showErrorModal(errorMsg, action);
591
+ // template not show create_token errors
592
+ if (errorMsg && action !== 'create_token')
593
+ this.showErrorModal(errorMsg, action);
586
594
 
587
595
  // if error related to loan token
588
596
  // - clear tokenPoller interval
@@ -616,8 +624,6 @@ export default class IABookActions extends LitElement {
616
624
 
617
625
  /* show error message if something went wrong */
618
626
  async showErrorModal(errorMsg, action) {
619
- this.disableActionGroup = false;
620
-
621
627
  // check if this.modal passed as prop
622
628
  if (!this.modal) {
623
629
  this.modal = document.querySelector('modal-manager');
@@ -1,146 +0,0 @@
1
- <!doctype html>
2
- <html lang="en-GB">
3
-
4
- <head>
5
- <meta charset="utf-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
7
- <style>
8
- html {
9
- font-size: 10px;
10
- /* This is to match petabox's base font size */
11
- }
12
- body {
13
- margin: 0;
14
- text-align: center;
15
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
16
- }
17
- #demo {
18
- height: 50px;
19
- }
20
- ia-book-actions {
21
- --white: #fff;
22
- --black: #000;
23
- --primaryBGColor: var(--black);
24
- --secondaryBGColor: #222;
25
- --tertiaryBGColor: #333;
26
- --primaryTextColor: var(--white);
27
- --primaryCTAFill: #194880;
28
- --primaryCTABorder: #c5d1df;
29
- --secondaryCTAFill: #333;
30
- --secondaryCTABorder: #999;
31
- --secondaryCTAFillRGB: 51, 51, 51;
32
- --primaryErrorCTAFill: #e51c26;
33
- --primaryErrorCTABorder: #f8c6c8;
34
- --primaryCTAFillRGB: 25, 72, 128;
35
- --primaryErrorCTAFillRGB: 229, 28, 38;
36
- --primaryDisableCTAFill: #767676;
37
- }
38
-
39
- fieldset {
40
- width: fit-content;
41
- margin-top: 50px;
42
- }
43
- fieldset legend {
44
- text-align: left;
45
- font-size: 12px;
46
- }
47
- td {
48
- color: white;
49
- font-size: 14px;
50
- border-radius: 5%;
51
- display: table-cell;
52
- text-align: initial;
53
- margin: 5px 0;
54
- width: max-content;
55
- display: block;
56
- }
57
- label {
58
- padding: 10px;
59
- display: block;
60
- cursor: pointer;
61
- margin: 2px;
62
- background: #4e545a;
63
- border-radius: 10px;
64
- }
65
- input[type="radio"], input[type="checkbox"] {
66
- margin-top: 0;
67
- vertical-align: middle;
68
- }
69
-
70
- .initial td {
71
- float: left;
72
- margin: 2px;
73
- }
74
-
75
- body.modal-manager-open {
76
- overflow: hidden;
77
- }
78
-
79
- modal-manager[mode='closed'] {
80
- display: none;
81
- }
82
- </style>
83
- </head>
84
-
85
- <body>
86
- <ia-bookreader class="focus-on-child-only" signedin="" fullscreen="">
87
- <div slot="header">
88
- <ia-book-actions></ia-book-actions>
89
- </div>
90
- <div slot="main">
91
- <div id="BookReader"></div>
92
- </div>
93
- </ia-bookreader>
94
-
95
-
96
- <script type="module">
97
- /* eslint-disable */
98
- import '@internetarchive/bookreader';
99
- import '@internetarchive/bookreader/BookReader/jquery-1.10.1.js';
100
- import '@internetarchive/bookreader/BookReader/BookReader.js';
101
- import '../src/ia-book-actions.js';
102
- import {
103
- defaultLendingStatus,
104
- canBrowseAndBorrow,
105
- } from '../src/assets/data/lending-get-status-service.js';
106
- import { ModalConfig } from '@internetarchive/modal-manager';
107
-
108
- const userid = '@neeraj-archive';
109
- const identifier = 'naturalhistoryof00unse_4111';
110
- let bwbPurchaseUrl = 'https://www.betterworldbooks.com/product/detail/cambridge-ancient-hist-v04-0521044863';
111
-
112
- let iaBookActions = document.querySelector('ia-book-actions');
113
-
114
- // for debugging
115
- window.iaBookActions = iaBookActions;
116
-
117
- const modalConfig = new ModalConfig();
118
- modalConfig.headerColor = '#d9534f';
119
-
120
- let lendingBarPostInit = function lendingBarPostInit() {
121
- setTimeout(function() {
122
- console.log('executed', 'br.init()');
123
- }, 100);
124
- }
125
-
126
- // set defaultLendingStatus for unavailable (without borrowables) book
127
- iaBookActions.userid = userid;
128
- iaBookActions.identifier = identifier;
129
- iaBookActions.bookTitle = 'Contemporary Black biography. Volume 39 : profiles from the interContemporary Black biography. Volume 39';
130
- iaBookActions.lendingStatus = canBrowseAndBorrow;
131
- iaBookActions.bwbPurchaseUrl = '';
132
- iaBookActions.modalConfig = modalConfig;
133
- iaBookActions.lendingBarPostInit = lendingBarPostInit;
134
- iaBookActions.tokenDelay = 2000; // 2 seconds
135
- iaBookActions.returnUrl = 'https://openlibrary.org';
136
-
137
- let lendingStatus1 = iaBookActions.lendingStatus;
138
-
139
- const manifest = await fetch('https://ia801302.us.archive.org/BookReader/BookReaderJSIA.php?id=goody&itemPath=/29/items/goody&server=ia801302.us.archive.org&format=jsonp&subPrefix=goody&requestUri=/details/goody/mode/1up%3Fview%3Dtheater%26ui%3Dembed%26wrapper%3Dfalse');
140
-
141
- console.log('manifest ****', manifest).then(res => res.json());
142
- </script>
143
-
144
- </div>
145
- </body>
146
- </html>