@internetarchive/ia-book-actions 0.0.29 → 0.1.1
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 +58 -10
- package/package.json +5 -4
- package/src/assets/styles/collapsible-action-group.js +2 -1
- package/src/components/collapsible-action-group.js +27 -0
- package/src/components/info-icon.js +0 -9
- package/src/components/text-group.js +1 -1
- package/src/components/timer-countdown.js +178 -0
- package/src/core/config/analytics-event-and-category.js +1 -0
- package/src/core/config/sentry-events.js +20 -0
- package/src/core/services/actions-config.js +2 -2
- package/src/core/services/actions-handler/actions-handler-service.js +5 -2
- package/src/core/services/actions-handler/actions-handler.js +63 -5
- package/src/core/services/get-lending-actions.js +6 -11
- package/src/core/services/loan-renew-helper.js +156 -0
- package/src/core/services/loan-token-poller.js +19 -10
- package/src/ia-book-actions.js +339 -41
- package/test/components/collapsible-action-group.test.js +2 -2
- package/test/components/timer-countdown.test.js +45 -0
- package/test/core/services/get-lending-actions.test.js +4 -4
- package/test/core/services/loan-renew-helper.test.js +86 -0
- package/test/ia-book-actions.test.js +75 -12
- package/demo/with-bookreader.html +0 -146
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
|
|
3
|
+
import { nothing } from 'lit';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This class is used to determine if use is eligible for auto renew loan.
|
|
7
|
+
*/
|
|
8
|
+
export class LoanRenewHelper {
|
|
9
|
+
constructor(hasPageChanged, identifier, localCache, loanRenewTimeConfig) {
|
|
10
|
+
this.hasPageChanged = hasPageChanged;
|
|
11
|
+
this.identifier = identifier;
|
|
12
|
+
this.localCache = localCache;
|
|
13
|
+
this.loanRenewTimeConfig = loanRenewTimeConfig;
|
|
14
|
+
|
|
15
|
+
// messages for auto return machenism
|
|
16
|
+
this.loanRenewMessage = 'This book has been renewed for 1 hour.';
|
|
17
|
+
this.loanReturnWarning =
|
|
18
|
+
'This book will be automatically returned in #time #unitsOfTime unless you turn a page.';
|
|
19
|
+
|
|
20
|
+
// private props
|
|
21
|
+
this.result = {
|
|
22
|
+
texts: null,
|
|
23
|
+
renewNow: false,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
handleLoanRenew() {
|
|
28
|
+
try {
|
|
29
|
+
if (this.hasPageChanged) {
|
|
30
|
+
return this.pageChanged(); // user clicked on page
|
|
31
|
+
}
|
|
32
|
+
return this.autoChecker(); // auto checker at last 10th minute
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.log(error);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return nothing;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Trigger this function when user has browsed a book and change the post
|
|
42
|
+
* - every time user change the page, we set current time in indexedDB
|
|
43
|
+
* - also check if need to auto renew current loan
|
|
44
|
+
*
|
|
45
|
+
* @returns {this.result}
|
|
46
|
+
*/
|
|
47
|
+
async pageChanged() {
|
|
48
|
+
const {
|
|
49
|
+
loanRenewAtLast, // 50
|
|
50
|
+
} = this.loanRenewTimeConfig;
|
|
51
|
+
const currentTime = new Date();
|
|
52
|
+
const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
|
|
53
|
+
|
|
54
|
+
const lastTimeFrame = this.changeTime(loanTime, loanRenewAtLast, 'sub');
|
|
55
|
+
|
|
56
|
+
// if user viewed new page in last 10 minutes, renew immediately
|
|
57
|
+
if (lastTimeFrame !== null && currentTime >= lastTimeFrame) {
|
|
58
|
+
this.result.texts = this.loanRenewMessage;
|
|
59
|
+
this.result.renewNow = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.setPageChangedTime();
|
|
63
|
+
return this.result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Trigger this function when countdown hits at last 10th minute
|
|
68
|
+
* - if user is active, just renew the loan at last 10th minute
|
|
69
|
+
*
|
|
70
|
+
* @returns {this.result}
|
|
71
|
+
*/
|
|
72
|
+
async autoChecker() {
|
|
73
|
+
const { pageChangedInLast } = this.loanRenewTimeConfig;
|
|
74
|
+
const pageChangedTime = await this.localCache.get(
|
|
75
|
+
`${this.identifier}-pageChangedTime`
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// in last 15 min if user make any activity.
|
|
79
|
+
const pageChangeTimeFrame = this.changeTime(
|
|
80
|
+
new Date(),
|
|
81
|
+
pageChangedInLast,
|
|
82
|
+
'sub'
|
|
83
|
+
); // 15 seconds
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
pageChangedTime === undefined ||
|
|
87
|
+
pageChangedTime <= pageChangeTimeFrame
|
|
88
|
+
) {
|
|
89
|
+
this.result.texts = this.loanReturnWarning;
|
|
90
|
+
this.result.renewNow = false; // not viewed
|
|
91
|
+
} else if (pageChangedTime >= pageChangeTimeFrame) {
|
|
92
|
+
this.result.texts = '';
|
|
93
|
+
this.result.renewNow = true; // viewed in last time frame
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return this.result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* set current time in indexedDB when user viewed a new page
|
|
101
|
+
*/
|
|
102
|
+
async setPageChangedTime() {
|
|
103
|
+
await this.localCache.set({
|
|
104
|
+
key: `${this.identifier}-pageChangedTime`,
|
|
105
|
+
value: new Date(), // current time
|
|
106
|
+
ttl: Number(this.loanRenewTimeConfig.loanTotalTime),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Texts we want to show in toast template including remaining time
|
|
112
|
+
* - eg. 1 minute, 2 minutes
|
|
113
|
+
*
|
|
114
|
+
* @param {String} texts
|
|
115
|
+
* @param {Number} secondsLeft
|
|
116
|
+
*
|
|
117
|
+
* @returns {String} // texts will be appear in toast template
|
|
118
|
+
*/
|
|
119
|
+
getMessageTexts(texts, secondsLeft) {
|
|
120
|
+
const unitOfTime = 'minute';
|
|
121
|
+
|
|
122
|
+
let toastTexts = texts;
|
|
123
|
+
let timeLeft = secondsLeft;
|
|
124
|
+
|
|
125
|
+
// convert time from second to minute
|
|
126
|
+
timeLeft = Math.ceil(timeLeft / 60);
|
|
127
|
+
|
|
128
|
+
// replace #time variable with remaining time
|
|
129
|
+
toastTexts = toastTexts?.replace(/#time/, timeLeft);
|
|
130
|
+
|
|
131
|
+
// replace #unitsOfTime variable with minute/minutes
|
|
132
|
+
return toastTexts?.replace(
|
|
133
|
+
/#unitsOfTime/,
|
|
134
|
+
timeLeft !== 1 ? `${unitOfTime}s` : unitOfTime
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Helper function to get time difference
|
|
140
|
+
*
|
|
141
|
+
* @param {Date} date
|
|
142
|
+
* @param {Number} seconds
|
|
143
|
+
* @param {String} op - like sub, add, mul, sub
|
|
144
|
+
*
|
|
145
|
+
* @returns {Object} Date
|
|
146
|
+
*/
|
|
147
|
+
changeTime(date, seconds, op) {
|
|
148
|
+
if (date === undefined) return null;
|
|
149
|
+
|
|
150
|
+
if (op === 'sub') {
|
|
151
|
+
return new Date(date.getTime() - seconds * 1000);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return new Date(date.getTime() + seconds * 1000);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import ActionsHandlerService from './actions-handler/actions-handler-service.js';
|
|
2
|
+
import { sentryLogs } from '../config/sentry-events.js';
|
|
3
|
+
|
|
2
4
|
import * as Cookies from './doc-cookies.js';
|
|
3
5
|
|
|
4
6
|
/**
|
|
@@ -12,10 +14,10 @@ export class LoanTokenPoller {
|
|
|
12
14
|
this.borrowType = borrowType;
|
|
13
15
|
this.successCallback = successCallback; // callback function to be called after loan token is created
|
|
14
16
|
this.errorCallback = errorCallback; // callback function to be called after loan token is created
|
|
15
|
-
this.pollerDelay = pollerDelay; // value in
|
|
17
|
+
this.pollerDelay = pollerDelay; // value in seconds
|
|
16
18
|
|
|
17
19
|
this.loanTokenInterval = undefined;
|
|
18
|
-
this.
|
|
20
|
+
this.bookAccessed();
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
disconnectedInterval() {
|
|
@@ -31,7 +33,7 @@ export class LoanTokenPoller {
|
|
|
31
33
|
// }
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
async
|
|
36
|
+
async bookAccessed() {
|
|
35
37
|
let consecutiveLoanCounts = 1;
|
|
36
38
|
|
|
37
39
|
if (this.borrowType) {
|
|
@@ -43,7 +45,9 @@ export class LoanTokenPoller {
|
|
|
43
45
|
);
|
|
44
46
|
consecutiveLoanCounts = existingCount ?? 1;
|
|
45
47
|
} catch (error) {
|
|
46
|
-
window?.Sentry?.captureException(
|
|
48
|
+
window?.Sentry?.captureException(
|
|
49
|
+
`${sentryLogs.enableBookAcces} - CookieError: ${error}`
|
|
50
|
+
);
|
|
47
51
|
this.sendEvent('Cookies-Error-Token', error);
|
|
48
52
|
}
|
|
49
53
|
}
|
|
@@ -57,7 +61,7 @@ export class LoanTokenPoller {
|
|
|
57
61
|
if (this.borrowType !== 'adminBorrowed') {
|
|
58
62
|
this.loanTokenInterval = setInterval(() => {
|
|
59
63
|
this.handleLoanTokenPoller();
|
|
60
|
-
}, this.pollerDelay);
|
|
64
|
+
}, this.pollerDelay * 1000);
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
// event category and action for browsing book access
|
|
@@ -68,7 +72,10 @@ export class LoanTokenPoller {
|
|
|
68
72
|
|
|
69
73
|
this.sendEvent(category, action);
|
|
70
74
|
} else {
|
|
71
|
-
window?.Sentry?.captureMessage(
|
|
75
|
+
window?.Sentry?.captureMessage(
|
|
76
|
+
`${sentryLogs.bookAccessed} - not borrowed`
|
|
77
|
+
);
|
|
78
|
+
|
|
72
79
|
// if book is not browsed, just clear token polling interval
|
|
73
80
|
this.disconnectedInterval(); // stop token fetch api
|
|
74
81
|
}
|
|
@@ -88,12 +95,14 @@ export class LoanTokenPoller {
|
|
|
88
95
|
identifier: this.identifier,
|
|
89
96
|
action,
|
|
90
97
|
error: data => {
|
|
91
|
-
window?.Sentry?.captureMessage(
|
|
92
|
-
'handleLoanTokenPoller error',
|
|
93
|
-
JSON.stringify(data)
|
|
94
|
-
);
|
|
95
98
|
this.disconnectedInterval(); // stop token fetch api
|
|
96
99
|
this.errorCallback({ detail: { action, data } });
|
|
100
|
+
|
|
101
|
+
// send error to Sentry
|
|
102
|
+
window?.Sentry?.captureMessage(
|
|
103
|
+
`${sentryLogs.handleLoanTokenPoller} - Error: ${JSON.stringify(data)}`
|
|
104
|
+
);
|
|
105
|
+
|
|
97
106
|
// send LendingServiceError to GA
|
|
98
107
|
this.sendEvent('LendingServiceLoanError', action);
|
|
99
108
|
},
|