@internetarchive/ia-book-actions 0.0.7-7 → 0.0.8-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 CHANGED
@@ -104,14 +104,21 @@
104
104
  const modalConfig = new ModalConfig();
105
105
  modalConfig.headerColor = '#d9534f';
106
106
 
107
+ let lendingBarPostInit = function lendingBarPostInit() {
108
+ setTimeout(function() {
109
+ console.log('executed', 'br.init()');
110
+ }, 100);
111
+ }
112
+
107
113
  // set defaultLendingStatus for unavailable (without borrowables) book
108
114
  iaBookActions.userid = userid;
109
115
  iaBookActions.identifier = identifier;
110
116
  iaBookActions.bookTitle = 'Contemporary Black biography. Volume 39 : profiles from the interContemporary Black biography. Volume 39';
111
117
  iaBookActions.lendingStatus = canBrowseAndBorrow;
112
118
  iaBookActions.bwbPurchaseUrl = '';
113
- iaBookActions.barType = '';
114
119
  iaBookActions.modalConfig = modalConfig;
120
+ iaBookActions.lendingBarPostInit = lendingBarPostInit;
121
+ iaBookActions.tokenDelay = 2000; // 2 seconds
115
122
 
116
123
  let lendingStatus1 = iaBookActions.lendingStatus;
117
124
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.0.7-7",
3
+ "version": "0.0.8-1",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -22,7 +22,7 @@
22
22
  "@internetarchive/icon-bookmark": "^1.1.3",
23
23
  "@internetarchive/icon-close": "^1.1.3",
24
24
  "@internetarchive/icon-info": "^1.2.6",
25
- "@internetarchive/local-cache": "^0.1.1",
25
+ "@internetarchive/local-cache": "^0.2.0",
26
26
  "@internetarchive/modal-manager": "^0.1.0",
27
27
  "@internetarchive/shared-resize-observer": "^0.0.1",
28
28
  "lit-element": "^2.4.0",
@@ -27,7 +27,6 @@ export class CollapsibleActionGroup extends ActionsHandler {
27
27
  hasAdminAccess: { type: Boolean },
28
28
  dropdownArrow: { type: String },
29
29
  disabled: { type: Boolean },
30
- borrowType: { type: String },
31
30
  };
32
31
  }
33
32
 
@@ -46,15 +45,6 @@ export class CollapsibleActionGroup extends ActionsHandler {
46
45
  this.title = '';
47
46
  this.loaderIcon = 'https://archive.org/upload/images/tree/loading.gif';
48
47
  this.disabled = false;
49
- this.borrowType = ''; // (browsed|borrowed)
50
- this.consecutiveLoanCounts = 1; // consecutive loan count
51
- }
52
-
53
- firstUpdated() {
54
- // this is execute to fetch loan token
55
- if (this.borrowType) {
56
- this.emitEnableBookAccess();
57
- }
58
48
  }
59
49
 
60
50
  updated(changed) {
@@ -63,46 +53,6 @@ export class CollapsibleActionGroup extends ActionsHandler {
63
53
  }
64
54
  }
65
55
 
66
- /**
67
- * Dispatches event when patron is borrowing book.
68
- * Notes borrow type, and consecutive borrow counts
69
- *
70
- * @fires CollapsibleActionGroup#enableBookAccess
71
- */
72
- async emitEnableBookAccess() {
73
- // send consecutiveLoanCounts for browsed books only.
74
- if (this.borrowType === 'browsed') {
75
- try {
76
- const existingCount = await this.localCache.get(
77
- 'consecutive-loan-count'
78
- );
79
- if (existingCount !== undefined) {
80
- this.consecutiveLoanCounts = existingCount.value ?? 1;
81
- }
82
- } catch (error) {
83
- console.log(error);
84
- }
85
- }
86
-
87
- // event category and action for browsing book access
88
- const eventCategory = `${this.borrowType}BookAccess`;
89
- const eventAction = `${
90
- this.borrowType === 'browsed' ? 'BrowseCounts-' : 'Counts-'
91
- }${this.consecutiveLoanCounts}`;
92
-
93
- this.dispatchEvent(
94
- new CustomEvent('enableBookAccess', {
95
- detail: {
96
- event: {
97
- category: eventCategory, // browsedBookAccess | borrowedBookAccess
98
- action: eventAction, // (BrowseCounts-N|Counts-N)
99
- },
100
- borrowType: this.borrowType,
101
- },
102
- })
103
- );
104
- }
105
-
106
56
  /**
107
57
  * merge primaryActions and secondaryActions into dropdown
108
58
  */
@@ -16,18 +16,11 @@ export default class ActionsHandler extends LitElement {
16
16
  constructor(identifier) {
17
17
  super();
18
18
  this.identifier = identifier;
19
- this.ajaxTimeout = 7000;
20
- this.loanTokenPollingDelay =
21
- window.location.pathname === '/demo/' ? 2000 : 120000; // 120000 ms = 2 min
22
- this.loanTokenInterval = undefined;
19
+ this.ajaxTimeout = 6000;
23
20
  this.localCache = new LocalCache();
24
21
  this.bindEvents();
25
22
  }
26
23
 
27
- disconnectedCallback() {
28
- this.loanTokenInterval = undefined;
29
- }
30
-
31
24
  sendEvent(eventCategory, eventAction) {
32
25
  // eslint-disable-next-line no-console
33
26
  console?.log('Book action: ', { eventCategory, eventAction });
@@ -102,26 +95,6 @@ export default class ActionsHandler extends LitElement {
102
95
  const { category, action } = detail.event;
103
96
  this.sendEvent(category, action);
104
97
  });
105
-
106
- this.addEventListener('enableBookAccess', ({ detail }) => {
107
- const borrowType = detail?.borrowType;
108
-
109
- // fetch loan token for browsed/borrowed book and set an interval
110
- if (borrowType) {
111
- // Do an initial token, then set an interval
112
- this.handleLoanTokenPoller();
113
-
114
- this.loanTokenInterval = setInterval(() => {
115
- this.handleLoanTokenPoller();
116
- }, this.loanTokenPollingDelay);
117
-
118
- const { category, action } = detail?.event;
119
- this.sendEvent(category, action);
120
- } else {
121
- // if book is not browsed, just clear token polling interval
122
- clearInterval(this.loanTokenInterval);
123
- }
124
- });
125
98
  }
126
99
 
127
100
  handleBrowseIt() {
@@ -205,18 +178,6 @@ export default class ActionsHandler extends LitElement {
205
178
  });
206
179
  }
207
180
 
208
- handleLoanTokenPoller() {
209
- const action = 'create_token';
210
- ActionsHandlerService({
211
- identifier: this.identifier,
212
- action,
213
- error: data => {
214
- this.dispatchActionError(action, data);
215
- clearInterval(this.loanTokenInterval); // stop token fetch api
216
- },
217
- });
218
- }
219
-
220
181
  /**
221
182
  * Dispatches event when an error occured on action
222
183
  * Notes:- toggle <collapsible-action-group> visibility (enable/disable).
@@ -280,7 +241,7 @@ export default class ActionsHandler extends LitElement {
280
241
  async setConsecutiveLoanCounts(action = '') {
281
242
  try {
282
243
  let newCount = 1;
283
- const storageKey = `consecutive-loan-count`;
244
+ const storageKey = `loan-count-${this.identifier}`;
284
245
  const existingCount = await this.localCache.get(storageKey);
285
246
 
286
247
  // increase browse-count by 1 when you consecutive reading a book.
@@ -0,0 +1,87 @@
1
+ import { LocalCache } from '@internetarchive/local-cache';
2
+ import ActionsHandlerService from './actions-handler/actions-handler-service.js';
3
+
4
+ /**
5
+ * This class is used to create loan token for borrowed books
6
+ *
7
+ * ActionsHandlerService is a function being used to execute
8
+ */
9
+ export class LoanTokenPoller {
10
+ constructor(id, borrowType, successCallback, errorCallback, pollerDelay) {
11
+ this.identifier = id;
12
+ this.borrowType = borrowType;
13
+ this.successCallback = successCallback; // callback function to be called after loan token is created
14
+ this.errorCallback = errorCallback; // callback function to be called after loan token is created
15
+ this.pollerDelay = pollerDelay; // value in ms (1000 ms = 1 sec)
16
+
17
+ this.loanTokenInterval = undefined;
18
+ this.localCache = new LocalCache();
19
+ this.enableBookAccess();
20
+ }
21
+
22
+ disconnectedInterval() {
23
+ clearInterval(this.loanTokenInterval);
24
+ this.loanTokenInterval = undefined;
25
+ }
26
+
27
+ async enableBookAccess() {
28
+ let consecutiveLoanCounts = 1;
29
+
30
+ if (this.borrowType) {
31
+ // send consecutiveLoanCounts for browsed books only.
32
+ if (this.borrowType === 'browsed') {
33
+ try {
34
+ const existingCount = await this.localCache.get(
35
+ `loan-count-${this.identifier}`
36
+ );
37
+ consecutiveLoanCounts = existingCount ?? 1;
38
+ } catch (error) {
39
+ console.log(error);
40
+ }
41
+ }
42
+
43
+ // Do an initial token, then set an interval
44
+ this.handleLoanTokenPoller(true);
45
+
46
+ this.loanTokenInterval = setInterval(() => {
47
+ this.handleLoanTokenPoller();
48
+ }, this.pollerDelay);
49
+
50
+ // event category and action for browsing book access
51
+ const category = `${this.borrowType}BookAccess`;
52
+ const action = `${
53
+ this.borrowType === 'browsed' ? 'BrowseCounts-' : 'Counts-'
54
+ }${consecutiveLoanCounts}`;
55
+
56
+ this.sendEvent(category, action);
57
+ } else {
58
+ // if book is not browsed, just clear token polling interval
59
+ this.disconnectedInterval(); // stop token fetch api
60
+ }
61
+ }
62
+
63
+ sendEvent(eventCategory, eventAction) {
64
+ // eslint-disable-next-line no-console
65
+ console?.log('Book action: ', { eventCategory, eventAction });
66
+ window?.archive_analytics?.send_event_no_sampling(
67
+ eventCategory,
68
+ eventAction,
69
+ `identifier=${this.identifier}`
70
+ );
71
+ }
72
+
73
+ async handleLoanTokenPoller(isInitial = false) {
74
+ const action = 'create_token';
75
+ ActionsHandlerService({
76
+ identifier: this.identifier,
77
+ action,
78
+ error: data => {
79
+ this.disconnectedInterval(); // stop token fetch api
80
+ this.errorCallback({ detail: { action, data } });
81
+ },
82
+ success: () => {
83
+ if (isInitial) this.successCallback();
84
+ },
85
+ });
86
+ }
87
+ }
@@ -11,6 +11,7 @@ import './components/info-icon.js';
11
11
 
12
12
  import GetLendingActions from './core/services/get-lending-actions.js';
13
13
  import { mobileContainerWidth } from './core/config/constants.js';
14
+ import { LoanTokenPoller } from './core/services/loan-token-poller.js';
14
15
 
15
16
  export default class IABookActions extends LitElement {
16
17
  static get properties() {
@@ -21,10 +22,15 @@ export default class IABookActions extends LitElement {
21
22
  lendingStatus: { type: Object },
22
23
  width: { type: Number },
23
24
  bwbPurchaseUrl: { type: String },
25
+ lendingBarPostInit: {
26
+ type: Function,
27
+ attribute: false,
28
+ },
24
29
  barType: { type: String },
25
30
  sharedObserver: { attribute: false },
26
31
  disableActionGroup: { type: Boolean },
27
32
  modal: { Object },
33
+ tokenDelay: { type: Number },
28
34
  };
29
35
  }
30
36
 
@@ -36,10 +42,12 @@ export default class IABookActions extends LitElement {
36
42
  this.lendingStatus = {};
37
43
  this.width = 0;
38
44
  this.bwbPurchaseUrl = '';
45
+ this.lendingBarPostInit = () => {};
39
46
  this.barType = 'action'; // 'title'|'action'
40
47
  this.sharedObserver = undefined;
41
48
  this.disableActionGroup = false;
42
49
  this.modal = undefined;
50
+ this.tokenDelay = 120000; // 2 minutes
43
51
 
44
52
  // private props
45
53
  this.primaryActions = [];
@@ -47,6 +55,8 @@ export default class IABookActions extends LitElement {
47
55
  this.primaryColor = 'primary';
48
56
  this.secondaryActions = [];
49
57
  this.lendingOptions = {};
58
+ this.borrowType = ''; // (browsed|borrowed)
59
+ this.consecutiveLoanCounts = 1; // consecutive loan count
50
60
  }
51
61
 
52
62
  disconnectedCallback() {
@@ -58,8 +68,6 @@ export default class IABookActions extends LitElement {
58
68
  this.sharedObserver = new SharedResizeObserver();
59
69
  this.setupResizeObserver();
60
70
  }
61
-
62
- this.setupLendingToolbarActions();
63
71
  }
64
72
 
65
73
  updated(changed) {
@@ -153,14 +161,26 @@ export default class IABookActions extends LitElement {
153
161
  // when browse is completed, we shows browse-again button
154
162
  this.startBrowseTimer();
155
163
  }
164
+
165
+ // early return if not borrowed or no action-bar
166
+ if (!this.borrowType || this.barType === 'title') {
167
+ this.lendingBarPostInit();
168
+ return;
169
+ }
170
+
171
+ this.startLoanTokenPoller();
156
172
  }
157
173
 
158
174
  render() {
159
- return html`
160
- <section class="lending-wrapper">
161
- ${this.barType === 'title' ? this.bookTitleBar : this.bookActionBar}
162
- </section>
163
- `;
175
+ if (this.barType === 'title') {
176
+ return html`<section class="lending-wrapper">
177
+ ${this.bookTitleBar}
178
+ </section>`;
179
+ }
180
+
181
+ return html`<section class="lending-wrapper">
182
+ ${this.bookActionBar}
183
+ </section>`;
164
184
  }
165
185
 
166
186
  get bookTitleBar() {
@@ -190,6 +210,40 @@ export default class IABookActions extends LitElement {
190
210
  `;
191
211
  }
192
212
 
213
+ /**
214
+ * enable access of borrowed/browsed books
215
+ */
216
+ startLoanTokenPoller() {
217
+ if (this.tokenPoller) {
218
+ this.tokenPoller.disconnectedInterval();
219
+ }
220
+ const successCallback = () => {
221
+ this.lendingBarPostInit();
222
+ };
223
+ const errorCallback = eventObj => {
224
+ this.handleLendingActionError(eventObj);
225
+ };
226
+
227
+ /**
228
+ * LoanTokenPoller is a class that polls the loan token
229
+ * it takes 5 params
230
+ * 1. this.identifier
231
+ * 2. this.borrowType
232
+ * 3. successCallback - it used to
233
+ * - disptach lendingFlow::PostInit event
234
+ * - initialize bookreader using br.init()
235
+ * 4. errorCallback
236
+ * 5. tokenPollerDelay
237
+ */
238
+ this.tokenPoller = new LoanTokenPoller(
239
+ this.identifier,
240
+ this.borrowType,
241
+ successCallback,
242
+ errorCallback,
243
+ this.tokenDelay // 1000 ms = 1 sec
244
+ );
245
+ }
246
+
193
247
  /*
194
248
  * custom event handler to toggle action group visibility
195
249
  *
@@ -257,12 +311,18 @@ export default class IABookActions extends LitElement {
257
311
  });
258
312
 
259
313
  if (action === 'create_token') {
314
+ const refreshButton = html`<button
315
+ style="background:none;font-size:inherit;border:0;padding:0;color:#0000ee;cursor:pointer;text-decoration:underline"
316
+ @click=${() => window.location.reload(true)}
317
+ >
318
+ refresh
319
+ </button>`;
320
+
260
321
  modalConfig.closeOnBackdropClick = false;
261
322
  modalConfig.showCloseButton = false;
262
323
  modalConfig.message = html` Uh oh, something went wrong trying to access
263
324
  this book.<br />
264
- Please <a href="${() => window.location.reload(true)}">refresh</a> to
265
- try again or send us an email to
325
+ Please ${refreshButton} to try again or send us an email to
266
326
  <a
267
327
  href="mailto:info@archive.org?subject=Help: cannot access my borrowed book: ${this
268
328
  .identifier}"
@@ -0,0 +1,40 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import Sinon from 'sinon';
3
+
4
+ import { LocalCache } from '@internetarchive/local-cache';
5
+ import { LoanTokenPoller } from '../../../src/core/services/loan-token-poller.js';
6
+
7
+ describe('Get Loan Token', () => {
8
+ it('get loan token for browsed books', async () => {
9
+ const localCacheStub = new LocalCache();
10
+ const localCacheGetSpy = Sinon.spy(localCacheStub, 'get');
11
+
12
+ const tokenPoller = new LoanTokenPoller(
13
+ 'identifier1',
14
+ 'browsed',
15
+ () => {
16
+ console.log('success callback is executed!');
17
+ },
18
+ () => {
19
+ console.log('error callback is executed!');
20
+ },
21
+ 2000 // 2 minutes'
22
+ );
23
+ const successCallbackSpy = Sinon.stub(tokenPoller, 'successCallback');
24
+ successCallbackSpy();
25
+ expect(successCallbackSpy.callCount).to.equal(1);
26
+
27
+ localCacheGetSpy();
28
+ expect(localCacheGetSpy.callCount).to.equal(1);
29
+
30
+ expect(tokenPoller.localCache.namespace).to.equal('LocalCache');
31
+ expect(tokenPoller.localCache.get).to.be.a('function');
32
+
33
+ tokenPoller.disconnectedInterval(); // clear token poller interval
34
+ expect(tokenPoller.loanTokenInterval).to.equal(undefined);
35
+
36
+ tokenPoller.handleLoanTokenPoller(true);
37
+ expect(tokenPoller.errorCallback).to.be.a('function');
38
+ expect(tokenPoller.successCallback).have;
39
+ });
40
+ });