@internetarchive/ia-book-actions 0.1.5 → 0.1.6-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 +4 -4
- package/package.json +1 -1
- package/src/components/collapsible-action-group.js +34 -15
- package/src/components/timer-countdown.js +1 -3
- package/src/core/config/analytics-event-and-category.js +2 -1
- package/src/core/config/ia-lending-intervals.js +12 -0
- package/src/core/config/sentry-events.js +2 -0
- package/src/core/services/actions-config.js +1 -0
- package/src/core/services/actions-handler/actions-handler.js +58 -66
- package/src/core/services/doc-cookies.js +23 -1
- package/src/core/services/loan-analytics.js +163 -0
- package/src/core/services/loan-token-poller.js +13 -36
- package/src/ia-book-actions.js +37 -17
- package/test/components/collapsible-action-group.test.js +0 -2
- package/test/components/timer-countdown.test.js +14 -14
- package/test/core/services/get-lending-actions.test.js +1 -1
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 = '
|
|
131
|
+
iaBookActions.returnUrl = '';
|
|
132
132
|
iaBookActions.localCache = localCache;
|
|
133
133
|
|
|
134
|
-
let loanTotalTime2Min = { loanTotalTime:
|
|
135
|
-
let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast:
|
|
134
|
+
let loanTotalTime2Min = { loanTotalTime: 10, loanRenewAtLast: 5, pageChangedInLast: 5 };
|
|
135
|
+
let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast: 180, pageChangedInLast: 15 };
|
|
136
136
|
let loanTotalTime1Hour = { loanTotalTime: 3600, loanRenewAtLast: 600, pageChangedInLast: 900 };
|
|
137
137
|
|
|
138
|
-
iaBookActions.loanRenewTimeConfig =
|
|
138
|
+
iaBookActions.loanRenewTimeConfig = loanTotalTime2Min;
|
|
139
139
|
|
|
140
140
|
let lendingStatus1 = iaBookActions.lendingStatus;
|
|
141
141
|
let secondsLeftOnLoan = iaBookActions.loanRenewTimeConfig.loanTotalTime;
|
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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('
|
|
67
|
-
this.
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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(
|
|
94
|
+
new CustomEvent(event, {
|
|
81
95
|
detail: {
|
|
82
96
|
event: { category, action },
|
|
83
97
|
},
|
|
@@ -190,7 +204,11 @@ export class CollapsibleActionGroup extends ActionsHandler {
|
|
|
190
204
|
href="${action.url}"
|
|
191
205
|
target=${action.target}
|
|
192
206
|
@click=${() => {
|
|
193
|
-
this.clickHandler(
|
|
207
|
+
this.clickHandler(
|
|
208
|
+
action.id,
|
|
209
|
+
action.analyticsEvent,
|
|
210
|
+
action?.borrowType
|
|
211
|
+
);
|
|
194
212
|
}}
|
|
195
213
|
>
|
|
196
214
|
${action.id === 'purchaseBook' ? purchaseIcon : ''} ${action.text}
|
|
@@ -211,7 +229,7 @@ export class CollapsibleActionGroup extends ActionsHandler {
|
|
|
211
229
|
return html`<button
|
|
212
230
|
class="ia-button ${action.className} ${initialButton ? 'initial' : ''}"
|
|
213
231
|
@click=${() => {
|
|
214
|
-
this.clickHandler(action.id, analyticsEvent);
|
|
232
|
+
this.clickHandler(action.id, analyticsEvent, action?.borrowType);
|
|
215
233
|
}}
|
|
216
234
|
>
|
|
217
235
|
${action.text}
|
|
@@ -224,10 +242,10 @@ export class CollapsibleActionGroup extends ActionsHandler {
|
|
|
224
242
|
* @param { object } gaEvent - contains analytics event action and category
|
|
225
243
|
* @param { string } gaEvent.category
|
|
226
244
|
* @param { string } gaEvent.action
|
|
227
|
-
*
|
|
245
|
+
* @param { string } borrowType browse|borrow
|
|
228
246
|
* @fires CollapsibleActionGroup#{eventName} - (will be browseBook, borrowBook etc...)
|
|
229
247
|
*/
|
|
230
|
-
clickHandler(eventName, gaEvent) {
|
|
248
|
+
clickHandler(eventName, gaEvent, borrowType = '') {
|
|
231
249
|
this.dropdownState = 'close';
|
|
232
250
|
this.dropdownArrow = dropdownClosed;
|
|
233
251
|
|
|
@@ -237,6 +255,7 @@ export class CollapsibleActionGroup extends ActionsHandler {
|
|
|
237
255
|
new CustomEvent(eventName, {
|
|
238
256
|
detail: {
|
|
239
257
|
event: { category, action },
|
|
258
|
+
borrowType,
|
|
240
259
|
},
|
|
241
260
|
})
|
|
242
261
|
);
|
|
@@ -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 =
|
|
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
|
-
|
|
16
|
+
browseAutoRenew: 'AutoRenewBook',
|
|
17
|
+
browseAutoReturn: 'AutoReturnBook',
|
|
17
18
|
borrow: 'Borrow-14Days',
|
|
18
19
|
waitlistJoin: 'JoinWaitlist',
|
|
19
20
|
waitlistLeave: 'LeaveWaitlist',
|
|
@@ -11,6 +11,18 @@
|
|
|
11
11
|
* @type {Object}
|
|
12
12
|
*/
|
|
13
13
|
window.IALendingIntervals = {
|
|
14
|
+
/**
|
|
15
|
+
* store loan token poller interval
|
|
16
|
+
* @see LoanTokenPoller::bookAccessed
|
|
17
|
+
*/
|
|
18
|
+
tokenPoller: 0,
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* store timer-countdown interval
|
|
22
|
+
* @see TimerCountdown::timerCountdown
|
|
23
|
+
*/
|
|
24
|
+
timerCountdown: 0,
|
|
25
|
+
|
|
14
26
|
/**
|
|
15
27
|
* clear interval for create_token api
|
|
16
28
|
*/
|
|
@@ -5,6 +5,8 @@ export const sentryLogs = {
|
|
|
5
5
|
disconnectedCallback: 'IABookActions:disconnectedCallback',
|
|
6
6
|
bookHasRenewed:
|
|
7
7
|
'IABookActions:handleLoanAutoRenewed - book has renewed for next one hour',
|
|
8
|
+
bookRenewFailed:
|
|
9
|
+
'IABookActions:handleLoanRenewNow - failed to renew',
|
|
8
10
|
browseHasExpired:
|
|
9
11
|
'IABookActions:browseHasExpired - one-hour loan has been expired',
|
|
10
12
|
bookWasExpired:
|
|
@@ -4,14 +4,14 @@ import { URLHelper } from '../../config/url-helper.js';
|
|
|
4
4
|
import { sentryLogs } from '../../config/sentry-events.js';
|
|
5
5
|
|
|
6
6
|
import ActionsHandlerService from './actions-handler-service.js';
|
|
7
|
+
import LoanAnanlytics from '../loan-analytics.js';
|
|
7
8
|
import * as Cookies from '../doc-cookies.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* These are callback functions calling from actions-config.js file.
|
|
11
12
|
*
|
|
12
|
-
* ActionsHandlerService is a function being used to execute
|
|
13
|
-
*
|
|
14
|
-
* 1. identifier: book name.
|
|
13
|
+
* ActionsHandlerService is a function being used to execute APIs based of the request made by user.
|
|
14
|
+
*
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
export default class ActionsHandler extends LitElement {
|
|
@@ -24,86 +24,97 @@ export default class ActionsHandler extends LitElement {
|
|
|
24
24
|
*/
|
|
25
25
|
this.waitUntillBorrowComplete = 6; // in seconds
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* bind events for lending bar
|
|
29
|
+
* @type {object}
|
|
30
|
+
*/
|
|
31
|
+
this.loanAnanlytics = new LoanAnanlytics();
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
);
|
|
33
|
+
/**
|
|
34
|
+
* bind events for lending bar
|
|
35
|
+
* @type {Function}
|
|
36
|
+
*/
|
|
37
|
+
this.bindEvents();
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
bindEvents() {
|
|
39
|
-
this.addEventListener('browseBook', (
|
|
41
|
+
this.addEventListener('browseBook', () => {
|
|
40
42
|
this.handleBrowseIt();
|
|
41
|
-
this.
|
|
42
|
-
const { category, action } = detail.event;
|
|
43
|
-
this.sendEvent(category, action);
|
|
43
|
+
this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'browse');
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
this.addEventListener('browseBookAgain', (
|
|
46
|
+
this.addEventListener('browseBookAgain', () => {
|
|
47
47
|
this.handleBrowseIt();
|
|
48
|
-
this.
|
|
49
|
-
const { category, action } = detail.event;
|
|
50
|
-
this.sendEvent(category, action);
|
|
48
|
+
this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'browseagain');
|
|
51
49
|
});
|
|
52
50
|
|
|
53
|
-
this.addEventListener('
|
|
54
|
-
this.
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
this.addEventListener('autoRenew', () => {
|
|
52
|
+
this.handleLoanRenewNow();
|
|
53
|
+
this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'autorenew');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
this.addEventListener('autoReturn', () => {
|
|
57
|
+
this.deleteLoanCookies();
|
|
58
|
+
this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'autoreturn');
|
|
57
59
|
});
|
|
58
60
|
|
|
59
61
|
this.addEventListener('returnNow', ({ detail }) => {
|
|
62
|
+
// use loan stats count when 1-hour borrow is active
|
|
63
|
+
if (detail?.borrowType === 'browse') {
|
|
64
|
+
this.loanAnanlytics?.storeLoanStatsCount(this.identifier, 'return');
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
this.handleReturnIt();
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
|
|
69
|
+
// send these events if 14-day borrow return
|
|
70
|
+
if (detail?.borrowType === 'borrow') {
|
|
71
|
+
const { category, action } = detail.event;
|
|
72
|
+
this.loanAnanlytics?.sendEvent(category, action, this.identifier);
|
|
73
|
+
}
|
|
63
74
|
});
|
|
64
75
|
|
|
65
76
|
this.addEventListener('borrowBook', ({ detail }) => {
|
|
66
77
|
this.handleBorrowIt();
|
|
67
78
|
const { category, action } = detail.event;
|
|
68
|
-
this.sendEvent(category, action);
|
|
79
|
+
this.loanAnanlytics?.sendEvent(category, action, this.identifier);
|
|
69
80
|
});
|
|
70
81
|
|
|
71
82
|
this.addEventListener('loginAndBorrow', ({ detail }) => {
|
|
72
83
|
this.handleLoginOk();
|
|
73
84
|
const { category, action } = detail.event;
|
|
74
|
-
this.sendEvent(category, action);
|
|
85
|
+
this.loanAnanlytics?.sendEvent(category, action, this.identifier);
|
|
75
86
|
});
|
|
76
87
|
|
|
77
88
|
this.addEventListener('leaveWaitlist', ({ detail }) => {
|
|
78
89
|
this.handleRemoveFromWaitingList();
|
|
79
90
|
const { category, action } = detail.event;
|
|
80
|
-
this.sendEvent(category, action);
|
|
91
|
+
this.loanAnanlytics?.sendEvent(category, action, this.identifier);
|
|
81
92
|
});
|
|
82
93
|
|
|
83
94
|
this.addEventListener('joinWaitlist', ({ detail }) => {
|
|
84
95
|
this.handleReserveIt();
|
|
85
96
|
const { category, action } = detail.event;
|
|
86
|
-
this.sendEvent(category, action);
|
|
97
|
+
this.loanAnanlytics?.sendEvent(category, action, this.identifier);
|
|
87
98
|
});
|
|
88
99
|
|
|
89
100
|
this.addEventListener('purchaseBook', ({ detail }) => {
|
|
90
101
|
const { category, action } = detail.event;
|
|
91
|
-
this.sendEvent(category, action);
|
|
102
|
+
this.loanAnanlytics?.sendEvent(category, action, this.identifier);
|
|
92
103
|
});
|
|
93
104
|
|
|
94
105
|
this.addEventListener('adminAccess', ({ detail }) => {
|
|
95
106
|
const { category, action } = detail.event;
|
|
96
|
-
this.sendEvent(category, action);
|
|
107
|
+
this.loanAnanlytics?.sendEvent(category, action, this.identifier);
|
|
97
108
|
});
|
|
98
109
|
|
|
99
110
|
this.addEventListener('exitAdminAccess', ({ detail }) => {
|
|
100
111
|
const { category, action } = detail.event;
|
|
101
|
-
this.sendEvent(category, action);
|
|
112
|
+
this.loanAnanlytics?.sendEvent(category, action, this.identifier);
|
|
102
113
|
});
|
|
103
114
|
|
|
104
115
|
this.addEventListener('bookTitleBar', ({ detail }) => {
|
|
105
116
|
const { category, action } = detail.event;
|
|
106
|
-
this.sendEvent(category, action);
|
|
117
|
+
this.loanAnanlytics?.sendEvent(category, action, this.identifier);
|
|
107
118
|
});
|
|
108
119
|
}
|
|
109
120
|
|
|
@@ -124,19 +135,25 @@ export default class ActionsHandler extends LitElement {
|
|
|
124
135
|
});
|
|
125
136
|
}
|
|
126
137
|
|
|
127
|
-
|
|
138
|
+
handleLoanRenewNow() {
|
|
128
139
|
const action = 'renew_loan';
|
|
129
140
|
|
|
130
141
|
ActionsHandlerService({
|
|
131
142
|
action,
|
|
132
143
|
identifier: this.identifier,
|
|
133
144
|
success: data => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
if (data?.success === true && data?.loan?.renewal === true) {
|
|
146
|
+
this.setBrowseTimeSession();
|
|
147
|
+
this.dispatchEvent(
|
|
148
|
+
new CustomEvent('loanAutoRenewed', {
|
|
149
|
+
detail: { action, data },
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
} else {
|
|
153
|
+
window?.Sentry?.captureMessage(
|
|
154
|
+
`${sentryLogs.bookRenewFailed} - Error: ${JSON.stringify(data)}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
140
157
|
},
|
|
141
158
|
error: data => {
|
|
142
159
|
this.dispatchActionError(action, data);
|
|
@@ -220,7 +237,7 @@ export default class ActionsHandler extends LitElement {
|
|
|
220
237
|
*/
|
|
221
238
|
dispatchActionError(action, data = {}) {
|
|
222
239
|
// send LendingServiceError to GA
|
|
223
|
-
this.sendEvent('LendingServiceError', action);
|
|
240
|
+
this.loanAnanlytics?.sendEvent('LendingServiceError', action);
|
|
224
241
|
|
|
225
242
|
this.dispatchEvent(
|
|
226
243
|
new CustomEvent('lendingActionError', {
|
|
@@ -271,31 +288,6 @@ export default class ActionsHandler extends LitElement {
|
|
|
271
288
|
}, this.waitUntillBorrowComplete * 1000);
|
|
272
289
|
}
|
|
273
290
|
|
|
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
291
|
/**
|
|
300
292
|
* set browse time in indexedDB
|
|
301
293
|
*/
|
|
@@ -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
|
-
*
|
|
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,163 @@
|
|
|
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
|
+
await Cookies.setItem(
|
|
42
|
+
this.getLoanCountStorageKey,
|
|
43
|
+
JSON.stringify(this.lendingEventCounts),
|
|
44
|
+
date,
|
|
45
|
+
'/'
|
|
46
|
+
);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.log(error);
|
|
49
|
+
this.sendEvent('Cookies-Error-Actions', error, this.identifier);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* get loan stats count from cookies for GA
|
|
55
|
+
*
|
|
56
|
+
* @param {string} action browse|autorenew|etc...
|
|
57
|
+
* @memberof ActionsHandler
|
|
58
|
+
*/
|
|
59
|
+
async getLoanStatsCount(action) {
|
|
60
|
+
this.lendingEventCounts = JSON.parse(
|
|
61
|
+
await Cookies.getItem(this.getLoanCountStorageKey)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
this.gaStats = this.lendingEventCounts ?? {
|
|
65
|
+
browse: 0,
|
|
66
|
+
renew: 0,
|
|
67
|
+
expire: 0,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let browse = this.lendingEventCounts?.browse ?? 0;
|
|
71
|
+
let renew = this.lendingEventCounts?.renew ?? 0;
|
|
72
|
+
let expire = this.lendingEventCounts?.expire ?? 0;
|
|
73
|
+
|
|
74
|
+
switch (action) {
|
|
75
|
+
case 'browse' || 'browseagain':
|
|
76
|
+
browse = browse ? Number(browse) + 1 : 1;
|
|
77
|
+
this.gaStats.browse = browse;
|
|
78
|
+
|
|
79
|
+
// reset renew and expire count
|
|
80
|
+
renew = 0;
|
|
81
|
+
expire = 0;
|
|
82
|
+
break;
|
|
83
|
+
|
|
84
|
+
case 'autorenew':
|
|
85
|
+
renew = renew ? Number(renew) + 1 : 1;
|
|
86
|
+
this.gaStats.renew = renew;
|
|
87
|
+
break;
|
|
88
|
+
|
|
89
|
+
case 'return' || 'autoreturn':
|
|
90
|
+
expire = expire ? Number(expire) + 1 : 1;
|
|
91
|
+
this.gaStats.expire = expire;
|
|
92
|
+
|
|
93
|
+
// reset renew and expire count
|
|
94
|
+
renew = 0;
|
|
95
|
+
expire = 0;
|
|
96
|
+
break;
|
|
97
|
+
default:
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// store these counts in cookies
|
|
102
|
+
this.lendingEventCounts = { browse, renew, expire };
|
|
103
|
+
|
|
104
|
+
// obtain GA category, event
|
|
105
|
+
const category = analyticsCategories.browse;
|
|
106
|
+
const event = `browse${this.paddedNumber(
|
|
107
|
+
this.gaStats?.browse
|
|
108
|
+
)}-autorenew${this.paddedNumber(this.gaStats?.renew)}:${action}`;
|
|
109
|
+
|
|
110
|
+
// send google analytics events
|
|
111
|
+
this.sendEvent(category, event, this.identifier);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* get zero padded number
|
|
116
|
+
*
|
|
117
|
+
* @param {number} number
|
|
118
|
+
* @return {string} 001|010
|
|
119
|
+
* @memberof LoanAnanlytics
|
|
120
|
+
*/
|
|
121
|
+
paddedNumber(number) {
|
|
122
|
+
if (number) return number.toString().padStart(3, '0');
|
|
123
|
+
|
|
124
|
+
return '000';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* get storage key for loan stats count
|
|
129
|
+
*
|
|
130
|
+
* @readonly
|
|
131
|
+
* @memberof LoanAnanlytics
|
|
132
|
+
*/
|
|
133
|
+
get getLoanCountStorageKey() {
|
|
134
|
+
return `br-browse-${this.identifier}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* reponsible to sent events to GA
|
|
139
|
+
*
|
|
140
|
+
* @param {*} eventCategory
|
|
141
|
+
* @param {*} eventAction
|
|
142
|
+
* @param {*} label
|
|
143
|
+
* @param {*} extraParams
|
|
144
|
+
* @memberof LoanAnanlytics
|
|
145
|
+
*/
|
|
146
|
+
sendEvent(eventCategory, eventAction, label, extraParams) {
|
|
147
|
+
console.log(
|
|
148
|
+
'eventCategory:-',
|
|
149
|
+
eventCategory,
|
|
150
|
+
'\t||\teventAction:-',
|
|
151
|
+
eventAction,
|
|
152
|
+
'\t||\tlabel:-',
|
|
153
|
+
label
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
window?.archive_analytics?.send_event_no_sampling(
|
|
157
|
+
eventCategory,
|
|
158
|
+
eventAction,
|
|
159
|
+
label || this.identifier,
|
|
160
|
+
extraParams
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
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
|
-
import * as Cookies from './doc-cookies.js';
|
|
5
|
-
|
|
6
5
|
/**
|
|
7
6
|
* This class is used to create loan token for borrowed books
|
|
8
7
|
*
|
|
@@ -17,6 +16,13 @@ export class LoanTokenPoller {
|
|
|
17
16
|
this.pollerDelay = pollerDelay; // value in seconds
|
|
18
17
|
|
|
19
18
|
this.loanTokenInterval = undefined;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* loan analytics instance
|
|
22
|
+
* @see loan-analytics.js
|
|
23
|
+
*/
|
|
24
|
+
this.loanAnalytics = new LoanAnanlytics();
|
|
25
|
+
|
|
20
26
|
this.bookAccessed();
|
|
21
27
|
}
|
|
22
28
|
|
|
@@ -25,24 +31,7 @@ export class LoanTokenPoller {
|
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
async bookAccessed() {
|
|
28
|
-
let consecutiveLoanCounts = 1;
|
|
29
|
-
|
|
30
34
|
if (this.borrowType) {
|
|
31
|
-
// send consecutiveLoanCounts for browsed books only.
|
|
32
|
-
if (this.borrowType === 'browsed') {
|
|
33
|
-
try {
|
|
34
|
-
const existingCount = Cookies.getItem(
|
|
35
|
-
`loan-count-${this.identifier}`
|
|
36
|
-
);
|
|
37
|
-
consecutiveLoanCounts = existingCount ?? 1;
|
|
38
|
-
} catch (error) {
|
|
39
|
-
window?.Sentry?.captureException(
|
|
40
|
-
`${sentryLogs.enableBookAcces} - CookieError: ${error}`
|
|
41
|
-
);
|
|
42
|
-
this.sendEvent('Cookies-Error-Token', error);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
35
|
// Do an initial token, then set an interval
|
|
47
36
|
this.handleLoanTokenPoller(true);
|
|
48
37
|
|
|
@@ -58,14 +47,6 @@ export class LoanTokenPoller {
|
|
|
58
47
|
this.handleLoanTokenPoller();
|
|
59
48
|
}, this.pollerDelay * 1000);
|
|
60
49
|
}
|
|
61
|
-
|
|
62
|
-
// event category and action for browsing book access
|
|
63
|
-
const category = `${this.borrowType}BookAccess`;
|
|
64
|
-
const action = `${
|
|
65
|
-
this.borrowType === 'browsed' ? 'BrowseCounts-' : 'Counts-'
|
|
66
|
-
}${consecutiveLoanCounts}`;
|
|
67
|
-
|
|
68
|
-
this.sendEvent(category, action);
|
|
69
50
|
} else {
|
|
70
51
|
window?.Sentry?.captureMessage(
|
|
71
52
|
`${sentryLogs.bookAccessed} - not borrowed`
|
|
@@ -76,14 +57,6 @@ export class LoanTokenPoller {
|
|
|
76
57
|
}
|
|
77
58
|
}
|
|
78
59
|
|
|
79
|
-
sendEvent(eventCategory, eventAction) {
|
|
80
|
-
window?.archive_analytics?.send_event(
|
|
81
|
-
eventCategory,
|
|
82
|
-
eventAction,
|
|
83
|
-
`identifier=${this.identifier}`
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
60
|
async handleLoanTokenPoller(isInitial = false) {
|
|
88
61
|
const action = 'create_token';
|
|
89
62
|
ActionsHandlerService({
|
|
@@ -98,7 +71,11 @@ export class LoanTokenPoller {
|
|
|
98
71
|
);
|
|
99
72
|
|
|
100
73
|
// send LendingServiceError to GA
|
|
101
|
-
this.sendEvent(
|
|
74
|
+
this.loanAnalytics?.sendEvent(
|
|
75
|
+
'LendingServiceLoanError',
|
|
76
|
+
action,
|
|
77
|
+
this.identifier
|
|
78
|
+
);
|
|
102
79
|
},
|
|
103
80
|
success: () => {
|
|
104
81
|
if (isInitial) this.successCallback();
|
package/src/ia-book-actions.js
CHANGED
|
@@ -73,6 +73,7 @@ export default class IABookActions extends LitElement {
|
|
|
73
73
|
this.borrowType = ''; // (browsed|borrowed)
|
|
74
74
|
this.consecutiveLoanCounts = 1; // consecutive loan count
|
|
75
75
|
this.suppressToast = false;
|
|
76
|
+
this.suppressAutoRenew = false;
|
|
76
77
|
this.browseTimer = undefined;
|
|
77
78
|
|
|
78
79
|
/**
|
|
@@ -171,6 +172,7 @@ export default class IABookActions extends LitElement {
|
|
|
171
172
|
|
|
172
173
|
async setupLendingToolbarActions() {
|
|
173
174
|
this.suppressToast = false;
|
|
175
|
+
this.suppressAutoRenew = false;
|
|
174
176
|
|
|
175
177
|
this.lendingOptions = new GetLendingActions(
|
|
176
178
|
this.userid,
|
|
@@ -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
|
-
|
|
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,10 +256,18 @@ 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
|
|
259
|
+
console.log('** BR user Action', {
|
|
260
|
+
suppressAutoRenew: this.suppressAutoRenew,
|
|
261
|
+
borrowType: this.borrowType,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
264
|
this.suppressToast = true;
|
|
265
265
|
this.closeToastManager();
|
|
266
266
|
|
|
267
|
+
if (this.suppressAutoRenew) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
267
271
|
if (this.borrowType === 'browsed') {
|
|
268
272
|
this.autoLoanRenewChecker(true);
|
|
269
273
|
}
|
|
@@ -273,6 +277,7 @@ export default class IABookActions extends LitElement {
|
|
|
273
277
|
* detect click-event on document to close toast-template
|
|
274
278
|
*/
|
|
275
279
|
document.addEventListener('click', e => {
|
|
280
|
+
console.log('*** top level click handler to remove toast on click');
|
|
276
281
|
if (this.loanRenewHelper && this.loanRenewResult.secondsLeft > 0) {
|
|
277
282
|
this.suppressToast = true;
|
|
278
283
|
}
|
|
@@ -293,8 +298,16 @@ export default class IABookActions extends LitElement {
|
|
|
293
298
|
* @see TimerCountdown::timerCountdown
|
|
294
299
|
*/
|
|
295
300
|
this.addEventListener('IABookActions:loanRenew', async event => {
|
|
301
|
+
console.log('** IABookActions:loanRenew', event.detail);
|
|
296
302
|
await this.autoLoanRenewChecker(false);
|
|
297
303
|
|
|
304
|
+
// renew in last seconds (let say 50 second) not possible because,
|
|
305
|
+
// - very short to renew on datanodes by hitting petabox API
|
|
306
|
+
// - loading images by create loan token
|
|
307
|
+
if (event?.detail?.secondsLeft < 50) {
|
|
308
|
+
this.suppressAutoRenew = true;
|
|
309
|
+
}
|
|
310
|
+
|
|
298
311
|
// show warning message with remaining time to auto returned it.
|
|
299
312
|
if (this.loanRenewResult.renewNow === false) {
|
|
300
313
|
this.loanRenewResult.secondsLeft = event.detail.secondsLeft;
|
|
@@ -325,6 +338,7 @@ export default class IABookActions extends LitElement {
|
|
|
325
338
|
* close/hide toast message
|
|
326
339
|
*/
|
|
327
340
|
async closeToastManager() {
|
|
341
|
+
console.log('** closeToastManager');
|
|
328
342
|
const toastTemplate = this.shadowRoot.querySelector('toast-template');
|
|
329
343
|
if (toastTemplate) {
|
|
330
344
|
toastTemplate?.remove();
|
|
@@ -338,6 +352,10 @@ export default class IABookActions extends LitElement {
|
|
|
338
352
|
* - show message having remaining time when book is about to auto returned
|
|
339
353
|
*/
|
|
340
354
|
async showToastMessage() {
|
|
355
|
+
console.log('** showToastMessage', {
|
|
356
|
+
suppressToast: this.suppressToast,
|
|
357
|
+
loanRenewResult: this.loanRenewResult,
|
|
358
|
+
});
|
|
341
359
|
if (this.suppressToast) return;
|
|
342
360
|
|
|
343
361
|
let toastTemplate = this.shadowRoot.querySelector('toast-template');
|
|
@@ -492,7 +510,8 @@ export default class IABookActions extends LitElement {
|
|
|
492
510
|
.loanTotalTime=${this.loanRenewTimeConfig.loanTotalTime}
|
|
493
511
|
?hasAdminAccess=${this.hasAdminAccess}
|
|
494
512
|
?disabled=${this.disableActionGroup}
|
|
495
|
-
?
|
|
513
|
+
?autoRenew=${this.loanRenewResult.renewNow}
|
|
514
|
+
?autoReturn=${this.lendingStatus.browsingExpired}
|
|
496
515
|
@loanAutoRenewed=${this.handleLoanAutoRenewed}
|
|
497
516
|
@lendingActionError=${this.handleLendingActionError}
|
|
498
517
|
@toggleActionGroup=${this.handleToggleActionGroup}
|
|
@@ -508,8 +527,10 @@ export default class IABookActions extends LitElement {
|
|
|
508
527
|
* - show success message
|
|
509
528
|
* - then change the remaining time
|
|
510
529
|
* - reset timer state
|
|
530
|
+
* @param {object} event
|
|
511
531
|
*/
|
|
512
|
-
async handleLoanAutoRenewed() {
|
|
532
|
+
async handleLoanAutoRenewed(event) {
|
|
533
|
+
// TODO: just read DATA variable here to apply security { action, data }
|
|
513
534
|
if (this.loanRenewResult.renewNow) {
|
|
514
535
|
window?.IALendingIntervals?.clearAll();
|
|
515
536
|
|
|
@@ -518,14 +539,13 @@ export default class IABookActions extends LitElement {
|
|
|
518
539
|
await this.showToastMessage();
|
|
519
540
|
await this.resetTimerCountState();
|
|
520
541
|
|
|
521
|
-
window?.Sentry?.captureMessage(
|
|
522
|
-
// TODO: analytics?? for consecutive renew.....
|
|
542
|
+
window?.Sentry?.captureMessage(sentryLogs.bookHasRenewed);
|
|
523
543
|
|
|
524
544
|
// testing console....
|
|
525
|
-
console.log(
|
|
545
|
+
console.log('IABookActions: AutoRenewed:- ', {
|
|
546
|
+
ajaxResponse: event?.detail?.data,
|
|
526
547
|
loanRenewResult: this.loanRenewResult,
|
|
527
|
-
|
|
528
|
-
resetTimerCountState: this._shadowRoot.querySelector('timer-countdown'),
|
|
548
|
+
secondsLeftOnLoan: Math.round(this.lendingStatus.secondsLeftOnLoan),
|
|
529
549
|
});
|
|
530
550
|
}
|
|
531
551
|
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { html, fixture, expect, aTimeout } from '@open-wc/testing';
|
|
2
|
+
import '../../src/ia-book-actions.js';
|
|
2
3
|
import '../../src/components/timer-countdown.js';
|
|
3
4
|
|
|
4
|
-
const container = ({
|
|
5
|
+
const container = ({
|
|
6
|
+
secondsLeftOnLoan,
|
|
7
|
+
loanTotalTime,
|
|
8
|
+
loanRenewAtLast,
|
|
9
|
+
} = {}) =>
|
|
5
10
|
html`<timer-countdown
|
|
6
11
|
.secondsLeftOnLoan=${secondsLeftOnLoan}
|
|
7
|
-
.
|
|
12
|
+
.loanTotalTime=${loanTotalTime}
|
|
13
|
+
.loanRenewAtLast=${loanRenewAtLast}
|
|
8
14
|
></timer-countdown>`;
|
|
9
15
|
|
|
10
16
|
describe('<timer-countdown>', () => {
|
|
@@ -12,27 +18,21 @@ describe('<timer-countdown>', () => {
|
|
|
12
18
|
const el = await fixture(
|
|
13
19
|
container({
|
|
14
20
|
secondsLeftOnLoan: 0, // in seconds
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
loanRenewAtLast: 5, // in seconds
|
|
18
|
-
pageChangedInLast: 5, // in seconds
|
|
19
|
-
},
|
|
21
|
+
loanTotalTime: 14,
|
|
22
|
+
loanRenewAtLast: 5,
|
|
20
23
|
})
|
|
21
24
|
);
|
|
22
25
|
|
|
23
26
|
await el.updateComplete;
|
|
24
|
-
expect(
|
|
27
|
+
// expect(window.IALendingIntervals.timerCountdown).to.be.undefined;
|
|
25
28
|
});
|
|
26
29
|
|
|
27
30
|
it('timer interval is not undefined when loan is active', async () => {
|
|
28
31
|
const el = await fixture(
|
|
29
32
|
container({
|
|
30
33
|
secondsLeftOnLoan: 2, // in seconds
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
loanRenewAtLast: 5, // in seconds
|
|
34
|
-
pageChangedInLast: 5, // in seconds
|
|
35
|
-
},
|
|
34
|
+
loanTotalTime: 14,
|
|
35
|
+
loanRenewAtLast: 5,
|
|
36
36
|
})
|
|
37
37
|
);
|
|
38
38
|
|
|
@@ -40,6 +40,6 @@ describe('<timer-countdown>', () => {
|
|
|
40
40
|
await el.updateComplete;
|
|
41
41
|
|
|
42
42
|
expect(el.secondsLeftOnLoan).to.equal(2);
|
|
43
|
-
expect(el.
|
|
43
|
+
// expect(el.timerCountdown).to.not.be.undefined;
|
|
44
44
|
});
|
|
45
45
|
});
|
|
@@ -27,7 +27,7 @@ describe('Get Lending Actions', () => {
|
|
|
27
27
|
expect(actions.primaryActions.length).to.equal(3);
|
|
28
28
|
expect(actions.primaryActions[0].text).to.equal('Borrow');
|
|
29
29
|
expect(actions.primaryActions[1].text).to.equal('Borrow for 14 days');
|
|
30
|
-
expect(actions.secondaryActions[1].text).to.equal('Purchase at');
|
|
30
|
+
expect(actions.secondaryActions[1].text).to.equal('Purchase at ');
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
it('Borrowable without user', async () => {
|