@internetarchive/ia-book-actions 1.1.0-alpha13 → 1.1.0-alpha15

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
@@ -34,6 +34,8 @@
34
34
  --primaryCTAFillRGB: 25, 72, 128;
35
35
  --primaryErrorCTAFillRGB: 229, 28, 38;
36
36
  --primaryDisableCTAFill: #767676;
37
+ background: var(--secondaryBGColor);
38
+ color: var(--primaryTextColor);
37
39
  }
38
40
 
39
41
  fieldset {
@@ -76,6 +78,13 @@
76
78
  display: inline-grid;
77
79
  }
78
80
 
81
+ .action-button button {
82
+ padding: 6px;
83
+ cursor: pointer;
84
+ border: 2px solid #194880;
85
+ border-radius: 10px;
86
+ }
87
+
79
88
  body.modal-manager-open {
80
89
  overflow: hidden;
81
90
  }
@@ -128,10 +137,11 @@
128
137
  iaBookActions.modalConfig = modalConfig;
129
138
  iaBookActions.lendingBarPostInit = lendingBarPostInit;
130
139
  iaBookActions.tokenDelay = 2; // 2 seconds
140
+ iaBookActions.timerExecutionSeconds = 4 // 4 seconds
131
141
  iaBookActions.returnUrl = '';
132
142
  iaBookActions.localCache = localCache;
133
143
 
134
- let loanTotalTime2Min = { loanTotalTime: 65, loanRenewAtLast: 60, pageChangedInLast: 15 };
144
+ let loanTotalTime2Min = { loanTotalTime: 120, loanRenewAtLast: 80, pageChangedInLast: 15 };
135
145
  let loanTotalTime5Min = { loanTotalTime: 300, loanRenewAtLast: 180, pageChangedInLast: 15 };
136
146
  let loanTotalTime1Hour = { loanTotalTime: 3600, loanRenewAtLast: 660, pageChangedInLast: 900 };
137
147
 
@@ -286,6 +296,19 @@
286
296
  iaBookActions.browseHasExpired();
287
297
  });
288
298
 
299
+ document.querySelector('#resync_timer').addEventListener('click', async (e) => {
300
+ if (iaBookActions.lendingStatus.user_has_browsed) {
301
+ document.querySelector('ia-book-actions').dispatchEvent(
302
+ new Event('visibilitychange', {
303
+ detail: {},
304
+ bubbles: true,
305
+ composed: true,
306
+ })
307
+ );
308
+ }
309
+ await new Promise(resolve => setTimeout(resolve, 5000));
310
+ });
311
+
289
312
  document.querySelectorAll('.availableToBorrow input[type=radio]').forEach((item) => {
290
313
  item.addEventListener('click', (e) => {
291
314
  if (e.target.value === 'available_to_borrow') {
@@ -359,9 +382,6 @@
359
382
  </td>
360
383
  <td class="searchParam">
361
384
  <label><input type="checkbox" name="search" value="Search Param" />Search Param</label>
362
- <td>
363
- <button class="pageChangedEvent">Page Change Event</button>
364
- </td>
365
385
  </tr>
366
386
  </table>
367
387
  </fieldset>
@@ -385,12 +405,6 @@
385
405
  <label><input type="radio" name="browse" value="available_to_browse" />available_to_browse</label>
386
406
  <label><input type="radio" name="browse" value="user_has_browsed" />user_has_browsed</label>
387
407
  <label><input type="radio" name="browse" value="browsingExpired" />browsingExpired</label>
388
- <div>
389
- <button id="show_warning_modal">When Browsing (`user_has_browsed`), show Warning Modal</button>
390
- </div>
391
- <div>
392
- <button id="show_expired_modal">While Browsing (`user_has_browsed`), Expire book & show its modal</button>
393
- </div>
394
408
  </td>
395
409
  <td class="availableToBorrow">
396
410
  <label><input type="radio" name="borrow" value="available_to_borrow" />available_to_borrow</label>
@@ -410,6 +424,29 @@
410
424
  font-weight: bold; display: none" name="purchase" value="Reflect" />
411
425
  </td>
412
426
  </tr>
427
+ <tr>
428
+ <td>
429
+ <fieldset style="margin-top: 10px;">
430
+ <legend style="color: black;">controls for browsed book</legend>
431
+ <table aria-hidden="true">
432
+ <tbody><tr>
433
+ <td class="action-button">
434
+ <button class="pageChangedEvent">Page Change Event</button>
435
+ </td>
436
+ <td class="action-button">
437
+ <button id="show_warning_modal">When Browsing (`user_has_browsed`),<br> show Warning Modal</button>
438
+ </td>
439
+ <td class="action-button">
440
+ <button id="show_expired_modal">While Browsing (`user_has_browsed`),<br> Expire book & show its modal</button>
441
+ </td>
442
+ <td class="action-button">
443
+ <button id="resync_timer">While Browsing (`user_has_browsed`),<br> resync timer</button>
444
+ </td>
445
+ </tr>
446
+ </tbody></table>
447
+ </fieldset>
448
+ </td>
449
+ </tr>
413
450
  </table>
414
451
  </fieldset>
415
452
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "1.1.0-alpha13",
3
+ "version": "1.1.0-alpha15",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -14,8 +14,8 @@
14
14
  "lint": "eslint --ext .js,.html . --ignore-path .gitignore && prettier \"**/*.js\" --check --ignore-path .gitignore",
15
15
  "format": "eslint --ext .js,.html . --fix --ignore-path .gitignore && prettier \"**/*.js\" --write --ignore-path .gitignore",
16
16
  "test": "yarn run lint && yarn test:fast",
17
- "test:fast": "web-test-runner --coverage",
18
- "test:watch": "web-test-runner --watch"
17
+ "test:fast": "web-test-runner --coverage --playwright --browsers chromium firefox webkit",
18
+ "test:watch": "web-test-runner --watch --playwright --browsers chromium firefox webkit"
19
19
  },
20
20
  "dependencies": {
21
21
  "@internetarchive/icon-info": "^1.2.6",
@@ -118,7 +118,7 @@ export const canBrowseAndBorrow = {
118
118
  loanStartDate: '2021-07-30 09:57:40',
119
119
  loansUrl: '/details/@neeraj-archive?tab=loans#loans-on-loan',
120
120
  maxLoans: 10,
121
- secondsLeftOnLoan: 170,
121
+ secondsLeftOnLoan: 155,
122
122
  shouldProtectImages: true,
123
123
  totalWaitlistCount: 0,
124
124
  userHasBorrowed: false,
@@ -47,7 +47,7 @@ export default class TimerCountdown extends LitElement {
47
47
  }
48
48
 
49
49
  render() {
50
- const viewClass = this.displayTime ? 'view' : 'hide1';
50
+ const viewClass = this.displayTime ? 'view' : 'hide';
51
51
  return html`
52
52
  <button
53
53
  id="timer-counter"
@@ -59,6 +59,7 @@ export default class IABookActions extends LitElement {
59
59
  disableActionGroup: { type: Boolean },
60
60
  modal: { Object },
61
61
  tokenDelay: { type: Number },
62
+ timerExecutionSeconds: { type: Number },
62
63
  localCache: { type: Object },
63
64
  loanRenewTimeConfig: { type: Object },
64
65
  loanRenewResult: { type: Object },
@@ -79,6 +80,7 @@ export default class IABookActions extends LitElement {
79
80
  this.sharedObserver = undefined;
80
81
  this.disableActionGroup = false;
81
82
  this.tokenDelay = 120; // in seconds
83
+ this.timerExecutionSeconds = 30;
82
84
 
83
85
  // private props
84
86
  this.postInitComplete = false;
@@ -89,10 +91,6 @@ export default class IABookActions extends LitElement {
89
91
  this.lendingOptions = {};
90
92
  this.borrowType = null; // 'browsed'|'borrowed'
91
93
  this.browseTimer = undefined; // timeout
92
- this.timerExecutionSeconds = 10;
93
-
94
- /** @deprecated */
95
- this.toast = undefined;
96
94
 
97
95
  /**
98
96
  * when user click on [return the book] button on warning modal
@@ -117,7 +115,7 @@ export default class IABookActions extends LitElement {
117
115
  * contains one hour auto-loan-renew response
118
116
  *
119
117
  * @type {object} loanRenewResult
120
- * @property {string} texts - texts messages need to show in toast message
118
+ * @property {string} texts - texts messages shows in modal
121
119
  * @property {boolean} renewNow - key to determine if need to renew now
122
120
  * @property {number} secondsLeft - seconds left in active loan
123
121
  */
@@ -132,10 +130,13 @@ export default class IABookActions extends LitElement {
132
130
  this.disconnectResizeObserver();
133
131
  }
134
132
 
133
+ /**
134
+ * send log messages to sentry
135
+ * @param {string} msg
136
+ */
135
137
  sentryCaptureMsg(msg) {
136
138
  log(window?.Sentry);
137
- log(window?.Sentry?.captureMessage(msg));
138
- if (window?.Sentry) window?.Sentry?.captureMessage(msg);
139
+ window?.Sentry?.captureMessage(msg);
139
140
  }
140
141
 
141
142
  firstUpdated() {
@@ -291,21 +292,35 @@ export default class IABookActions extends LitElement {
291
292
 
292
293
  /**
293
294
  * auto-renew interval may stale when tab is in background,
294
- * when user jump on the books tab,
295
+ * so when user open that tab again,
295
296
  * the [visibilitychange] event can trigger timer to show relavent messages
296
297
  */
297
- document.addEventListener("visibilitychange", async () => {
298
+ document.addEventListener('visibilitychange', async () => {
298
299
  if (!document.hidden) {
299
- log('visibilitychange is not hidden time: ------------------ ', new Date().getMinutes(), new Date().getSeconds());
300
-
301
- const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
302
- const secondsLeft = Math.round((loanTime - new Date()) / 1000); // different in seconds
303
-
304
- if (secondsLeft >= this.timerExecutionSeconds) {
305
- this.setIntervalExecutionBlock(Number(secondsLeft));
306
- } else {
307
- this.browseHasExpired();
308
- this.disconnectedCallback();
300
+ log(
301
+ 'visibilitychange event execute:------------------ ',
302
+ new Date().getMinutes(),
303
+ new Date().getSeconds(),
304
+ this.borrowType
305
+ );
306
+
307
+ if (
308
+ this.borrowType === 'browsed' &&
309
+ this.lendingStatus.browsingExpired === false
310
+ ) {
311
+ const loanTime = await this.localCache.get(
312
+ `${this.identifier}-loanTime`
313
+ );
314
+
315
+ // number of seconds left in current loan
316
+ const secondsLeft = Math.round((loanTime - new Date()) / 1000);
317
+
318
+ if (secondsLeft >= this.timerExecutionSeconds) {
319
+ this.loanStatusCheckInterval(Number(secondsLeft));
320
+ } else {
321
+ this.browseHasExpired();
322
+ this.disconnectedCallback();
323
+ }
309
324
  }
310
325
  }
311
326
  });
@@ -488,31 +503,6 @@ export default class IABookActions extends LitElement {
488
503
  await this.modal?.showModal({ config, customModalContent });
489
504
  }
490
505
 
491
- /**
492
- * @deprecated
493
- * create / select the toast-template component on DOM
494
- *
495
- * @memberof IABookActions
496
- */
497
- async useToastManager() {
498
- if (!this.toast) {
499
- this.toast = this.shadowRoot.querySelector('toast-template');
500
-
501
- if (!this.toast) this.toast = document.createElement('toast-template');
502
- }
503
-
504
- await this.shadowRoot.appendChild(this.toast);
505
- }
506
-
507
- /**
508
- * @deprecated
509
- * close/hide toast message
510
- */
511
- async closeToastManager() {
512
- this.toast = this.shadowRoot.querySelector('toast-template');
513
- this.toast?.remove();
514
- }
515
-
516
506
  /**
517
507
  * Execute when loan is expired
518
508
  */
@@ -641,7 +631,10 @@ export default class IABookActions extends LitElement {
641
631
  if (this.loanRenewResult.renewNow) {
642
632
  // Now, let's reset loan duration & this.lendingStatus
643
633
  const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
644
- const secondsLeft = Math.round((loanTime - new Date()) / 1000); // different in seconds
634
+
635
+ // number of seconds left in current loan
636
+ const secondsLeft = Math.round((loanTime - new Date()) / 1000);
637
+ log(loanTime, secondsLeft);
645
638
 
646
639
  log('IABookActions: handleLoanAutoRenewed --- ', {
647
640
  ajaxResponse: detail?.data,
@@ -678,7 +671,7 @@ export default class IABookActions extends LitElement {
678
671
 
679
672
  window.IALendingIntervals.timerCountdown = setInterval(async () => {
680
673
  // interval execution block
681
- await this.setIntervalExecutionBlock(secondsLeft);
674
+ await this.loanStatusCheckInterval(secondsLeft);
682
675
  }, this.timerExecutionSeconds * 1000);
683
676
  }
684
677
 
@@ -687,10 +680,10 @@ export default class IABookActions extends LitElement {
687
680
  * - if setInterval timer gone off, let's resync
688
681
  * - loan-renew attempt to renew the loan OR show relavant message
689
682
  * - if loan has expired, clear interval timers
690
- *
691
- * @param {Number} secondsLeftOnLoan
683
+ *
684
+ * @param {Number} secondsLeftOnLoan
692
685
  */
693
- async setIntervalExecutionBlock(secondsLeftOnLoan) {
686
+ async loanStatusCheckInterval(secondsLeftOnLoan) {
694
687
  let secondsLeft = secondsLeftOnLoan;
695
688
  secondsLeft -= this.timerExecutionSeconds;
696
689
  secondsLeft = Math.round(secondsLeft); // round number
@@ -700,21 +693,30 @@ export default class IABookActions extends LitElement {
700
693
  // if really updated, escape from here
701
694
  const resyncd = await this.reSyncTimerIfGoneOff(secondsLeft);
702
695
 
703
- if (resyncd?.hasSynced) {
696
+ if (resyncd.hasSynced) {
704
697
  secondsLeft = resyncd.whatShouldLeft;
705
698
  log('startTimerCountdown --- stale, timer has resyncd', {
706
699
  secondsLeft,
707
700
  });
708
701
  }
709
702
 
710
- log('startTimerCountdown --- countdown still valid. continue...', {
711
- resyncd,
712
- secondsLeft,
713
- loanRenewAtLast: this.loanRenewTimeConfig.loanRenewAtLast,
714
- }, 'time: ', new Date().getMinutes(), ':', new Date().getSeconds(), ', timerDelay: ', this.timerExecutionSeconds);
703
+ log(
704
+ 'startTimerCountdown --- countdown still valid. continue...',
705
+ {
706
+ resyncd,
707
+ secondsLeft,
708
+ loanRenewAtLast: this.loanRenewTimeConfig.loanRenewAtLast,
709
+ },
710
+ 'time: ',
711
+ new Date().getMinutes(),
712
+ ':',
713
+ new Date().getSeconds(),
714
+ ', timerDelay: ',
715
+ this.timerExecutionSeconds
716
+ );
715
717
 
716
718
  /**
717
- * execute from last 10th minute to 0th minute
719
+ * execute from last 10th minutes to 0th minute
718
720
  * - 10th - to check if user has viewed
719
721
  * - till 0th - to show warning msg with remaining time to auto expired
720
722
  * @see IABookActions::bindLoanRenewEvents
@@ -734,8 +736,7 @@ export default class IABookActions extends LitElement {
734
736
  * helper function to determine if timer is not in sync properly
735
737
  *
736
738
  * @param {number} timerSecondsLeft - actual seconds left get from setInterval
737
- *
738
- * @memberof TimerCountdown
739
+ * @returns {Object} { hasSynced: [boolean], whatShouldLeft: [number] }
739
740
  */
740
741
  async reSyncTimerIfGoneOff(timerSecondsLeft) {
741
742
  const currentTime = new Date();
@@ -770,10 +771,10 @@ export default class IABookActions extends LitElement {
770
771
 
771
772
  if (whatIsleft !== whatShouldLeft) {
772
773
  log(`reSyncTimerIfGoneOff --- let's re-sync.`);
773
-
774
774
  return { hasSynced: true, whatShouldLeft };
775
775
  }
776
- return false;
776
+
777
+ return { hasSynced: false, whatShouldLeft };
777
778
  }
778
779
 
779
780
  /**
@@ -985,19 +986,11 @@ export default class IABookActions extends LitElement {
985
986
  .lending-wrapper {
986
987
  width: 100%;
987
988
  margin: 0 auto;
988
- background: var(--primaryBGColor, #000);
989
- color: var(--primaryTextColor, #fff);
990
989
  display: inline-flex;
991
990
  align-items: center;
992
991
  justify-content: center;
993
992
  flex-wrap: wrap;
994
993
  }
995
-
996
- toast-template {
997
- --toastTopMargin: 80px;
998
- --toastBGColor: #333;
999
- --toastFontColor: #fff;
1000
- }
1001
994
  `;
1002
995
  }
1003
996
  }
@@ -376,7 +376,8 @@ describe('Browsing expired status', () => {
376
376
  });
377
377
  });
378
378
 
379
- describe('Auto renew one hour loan', () => {
379
+ describe('Auto renew one hour loan', async () => {
380
+ await aTimeout(5000);
380
381
  it('Book is browsing and renewed it now', async () => {
381
382
  const loanTime = 88;
382
383
  const el = await fixture(
@@ -449,6 +450,61 @@ describe('Auto renew one hour loan', () => {
449
450
  });
450
451
  });
451
452
 
453
+ describe('Visibility change API for document', async () => {
454
+ await aTimeout(5000);
455
+
456
+ beforeEach(() => {
457
+ Object.defineProperty(document, 'hidden', {
458
+ value: false,
459
+ configurable: true,
460
+ });
461
+ });
462
+
463
+ afterEach(() => {
464
+ Object.defineProperty(document, 'hidden', {
465
+ value: true,
466
+ configurable: true,
467
+ });
468
+ });
469
+
470
+ it('when book is not expired', async () => {
471
+ const el = await fixture(
472
+ container({
473
+ userid: '@user1',
474
+ identifier: 'foobar',
475
+ lendingStatus: {
476
+ user_has_browsed: true,
477
+ browsingExpired: false,
478
+ },
479
+ })
480
+ );
481
+ await el.updateComplete;
482
+
483
+ const spy = Sinon.spy(el, 'loanStatusCheckInterval');
484
+ document.dispatchEvent(new Event('visibilitychange'));
485
+
486
+ expect(spy.calledOnce).to.be.true;
487
+ });
488
+
489
+ it('when book is already expired', async () => {
490
+ const el = await fixture(
491
+ container({
492
+ userid: '@user1',
493
+ identifier: 'foobar',
494
+ lendingStatus: {
495
+ user_has_browsed: true,
496
+ },
497
+ })
498
+ );
499
+ await el.updateComplete;
500
+
501
+ const spy = Sinon.spy(el, 'browseHasExpired');
502
+ document.dispatchEvent(new Event('visibilitychange'));
503
+
504
+ expect(spy.calledOnce).to.be.true;
505
+ });
506
+ });
507
+
452
508
  describe('Shared Resize Observer', () => {
453
509
  it('can receive a Shared Resize Observer', async () => {
454
510
  const sharedObserverStub = new SharedResizeObserver();