@internetarchive/ia-book-actions 1.4.4-alpha10 → 1.4.4-alpha11

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/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "author": "Internet Archive",
9
9
  "license": "AGPL-3.0-only",
10
- "version": "1.4.4-alpha10",
10
+ "version": "1.4.4-alpha11",
11
11
  "main": "index.js",
12
12
  "module": "index.js",
13
13
  "publishConfig": {
@@ -61,6 +61,7 @@ export default class IABookActions extends LitElement {
61
61
  modal: { Object },
62
62
  tokenDelay: { type: Number },
63
63
  timerExecutionSeconds: { type: Number },
64
+ userActionGracePeriod: { type: Number },
64
65
  localCache: { type: Object },
65
66
  loanRenewTimeConfig: { type: Object },
66
67
  loanRenewResult: { type: Object },
@@ -83,6 +84,14 @@ export default class IABookActions extends LitElement {
83
84
  this.tokenDelay = 120; // in seconds
84
85
  this.timerExecutionSeconds = 30;
85
86
 
87
+ /**
88
+ * How long (ms) after bindLoanRenewEvents() runs to ignore
89
+ * BookReader:userAction events — BookReader fires that event on its
90
+ * own init-time jump to the last-read page, not just on genuine
91
+ * interaction. @see bindLoanRenewEvents
92
+ */
93
+ this.userActionGracePeriod = 2000;
94
+
86
95
  // private props
87
96
  this.postInitComplete = false;
88
97
  this.primaryActions = [];
@@ -283,7 +292,14 @@ export default class IABookActions extends LitElement {
283
292
 
284
293
  this.borrowType = actions.borrowType ? actions.borrowType : null;
285
294
 
286
- if (this.borrowType === 'browsed') {
295
+ // Don't (re)start the countdown while a renewal is in flight — the
296
+ // optimistic browsingExpired flip in autoRenewExpiredLoan() (which
297
+ // triggers this same setupLendingToolbarActions() call) happens before
298
+ // renewal is confirmed, so secondsLeftOnLoan could still be a stale
299
+ // value from before the renewal. Wait for handleLoanAutoRenewed() to
300
+ // confirm the real value and clear loanRenewInProgress before showing
301
+ // a countdown again.
302
+ if (this.borrowType === 'browsed' && !this.loanRenewInProgress) {
287
303
  // start timer for loan-renew
288
304
  await this.startTimerCountdown();
289
305
 
@@ -327,10 +343,19 @@ export default class IABookActions extends LitElement {
327
343
  * 2. IABookActions:loanRenew - dispatched from timer-countdown component
328
344
  */
329
345
  bindLoanRenewEvents() {
346
+ this.userActionReadyAt = Date.now() + this.userActionGracePeriod;
347
+
330
348
  /**
331
349
  * dispatched this event from bookreader page changed
332
350
  */
333
351
  window.addEventListener('BookReader:userAction', () => {
352
+ if (Date.now() < this.userActionReadyAt) {
353
+ log(
354
+ '[IABookActions] BookReader:userAction ignored — within startup grace period'
355
+ );
356
+ return;
357
+ }
358
+
334
359
  // Capture before autoRenewExpiredLoan() runs — it synchronously flips
335
360
  // lendingStatus.browsingExpired to false as an optimistic UI update,
336
361
  // so checking the live property afterward would always see it as
@@ -448,11 +473,17 @@ export default class IABookActions extends LitElement {
448
473
 
449
474
  // Optimistically flip browsingExpired back to false so the action bar
450
475
  // stays red (patronIsReadingAction) while the renew_loan request is in
451
- // flight, instead of showing the blue "Borrow" state.
476
+ // flight, instead of showing the blue "Borrow" state. Deliberately NOT
477
+ // resetting secondsLeftOnLoan here — the renewal hasn't been confirmed
478
+ // yet (it can take several seconds, or genuinely fail), so showing a
479
+ // full hour before we know the outcome would be misleading. The real
480
+ // value is set once handleLoanAutoRenewed() confirms success;
481
+ // loanRenewInProgress (already true above) is the signal consumers
482
+ // should use to show a "renewing" state instead of trusting the
483
+ // countdown during this window.
452
484
  this.lendingStatus = {
453
485
  ...this.lendingStatus,
454
486
  browsingExpired: false,
455
- secondsLeftOnLoan: this.loanRenewTimeConfig.loanTotalTime,
456
487
  };
457
488
 
458
489
  // Defer renewNow until after lendingStatus timer setup completes.
@@ -505,19 +536,14 @@ export default class IABookActions extends LitElement {
505
536
  headerColor: '#194880',
506
537
  showCloseButton: false,
507
538
  closeOnBackdropClick: false,
508
- message: html`<span
509
- style="display:inline-flex;align-items:center;flex-wrap:nowrap;gap:4px;"
510
- >
511
- <span>${this.loanRenewHelper?.getMessageTexts(
512
- warningTexts,
513
- secondsLeft
514
- )}</span>
539
+ message: html`<span>
540
+ ${this.loanRenewHelper?.getMessageTexts(warningTexts, secondsLeft)}
515
541
  <a
516
542
  href="https://help.archive.org/help/borrowing-from-the-lending-library"
517
543
  target="_blank"
518
544
  title="Get more info on borrowing from The Lending Library"
519
545
  data-event-click-tracking="BookReader|BrowsableMoreInfo"
520
- style="display:inline-flex;flex-shrink:0;line-height:0;"
546
+ style="display:inline-block;vertical-align:middle;line-height:0;margin-left:4px;"
521
547
  >
522
548
  ${infoIcon}
523
549
  </a>
@@ -89,6 +89,8 @@ describe('<ia-book-actions>', () => {
89
89
  identifier: 'foobar',
90
90
  })
91
91
  );
92
+ // Not testing the BookReader-init grace period here — bypass it.
93
+ el.userActionReadyAt = 0;
92
94
 
93
95
  const spy = Sinon.spy(el, 'autoLoanRenewChecker');
94
96
  el.lendingStatus = { ...el.lendingStatus, browsingExpired: false };
@@ -111,6 +113,8 @@ describe('<ia-book-actions>', () => {
111
113
  lendingStatus: { user_has_browsed: true, browsingExpired: true },
112
114
  })
113
115
  );
116
+ // Not testing the BookReader-init grace period here — bypass it.
117
+ el.userActionReadyAt = 0;
114
118
 
115
119
  const spy = Sinon.spy(el, 'autoRenewExpiredLoan');
116
120
  el.borrowType = 'browsed';
@@ -121,6 +125,34 @@ describe('<ia-book-actions>', () => {
121
125
 
122
126
  expect(spy.calledOnce).to.be.true;
123
127
  });
128
+
129
+ it('ignores BookReader:userAction fired within the startup grace period (WEBDEV-8322 follow-up)', async () => {
130
+ // Regression: BookReader.jumpToIndex() fires its own 'userAction'
131
+ // event any time it runs — including BookReader's own init-time jump
132
+ // to the reader's last-read page (updateFromParams(), called from
133
+ // init()), which has nothing to do with the patron doing anything.
134
+ // Without this grace period, a plain page refresh on an already-
135
+ // expired loan would silently auto-renew before the patron did
136
+ // anything at all.
137
+ const el = await fixture(
138
+ container({
139
+ userid: '@user1',
140
+ identifier: 'foobar',
141
+ lendingStatus: { user_has_browsed: true, browsingExpired: true },
142
+ })
143
+ );
144
+ el.borrowType = 'browsed';
145
+ await el.updateComplete;
146
+
147
+ const spy = Sinon.spy(el, 'autoRenewExpiredLoan');
148
+
149
+ // Fired immediately after bindLoanRenewEvents() ran (in firstUpdated())
150
+ // — well within the default grace period.
151
+ window.dispatchEvent(new Event('BookReader:userAction'));
152
+ await el.updateComplete;
153
+
154
+ expect(spy.called).to.be.false;
155
+ });
124
156
  });
125
157
 
126
158
  describe('Primary Actions data', () => {
@@ -595,6 +627,36 @@ describe('autoRenewExpiredLoan', () => {
595
627
  expect(el.primaryColor).to.equal('danger');
596
628
  });
597
629
 
630
+ it('does not (re)start the countdown during the optimistic pre-confirmation window (WEBDEV-8322 follow-up)', async () => {
631
+ // Regression: the optimistic browsingExpired flip above triggers
632
+ // setupLendingToolbarActions() before renew_loan is even dispatched,
633
+ // let alone confirmed. Without gating on loanRenewInProgress, that
634
+ // would (re)start the timer-countdown interval using whatever stale
635
+ // secondsLeftOnLoan was left over from before the loan expired,
636
+ // showing a wrong/flickering value until handleLoanAutoRenewed()
637
+ // corrects it moments later.
638
+ const el = await fixture(
639
+ container({
640
+ userid: '@user1',
641
+ identifier: 'foobar',
642
+ lendingStatus: {
643
+ user_has_browsed: true,
644
+ available_to_browse: true,
645
+ browsingExpired: true,
646
+ },
647
+ })
648
+ );
649
+ await el.updateComplete;
650
+
651
+ const startTimerCountdownSpy = Sinon.spy(el, 'startTimerCountdown');
652
+
653
+ el.autoRenewExpiredLoan();
654
+ await el.updateComplete;
655
+
656
+ expect(el.loanRenewInProgress).to.be.true;
657
+ expect(startTimerCountdownSpy.called).to.be.false;
658
+ });
659
+
598
660
  it('sets loanRenewInProgress and triggers renewNow after timeout', async () => {
599
661
  const el = await fixture(
600
662
  container({
@@ -882,6 +944,8 @@ describe('BookReader:userAction race regression (WEBDEV-8322)', () => {
882
944
  );
883
945
  await el.updateComplete;
884
946
  expect(el.borrowType).to.equal('browsed');
947
+ // Not testing the BookReader-init grace period here — bypass it.
948
+ el.userActionReadyAt = 0;
885
949
 
886
950
  el.lendingStatus = { ...baseStatus, browsingExpired: true };
887
951
  await el.updateComplete;
@@ -923,6 +987,8 @@ describe('BookReader:userAction race regression (WEBDEV-8322)', () => {
923
987
  );
924
988
  await el.updateComplete;
925
989
  expect(el.borrowType).to.equal('browsed');
990
+ // Not testing the BookReader-init grace period here — bypass it.
991
+ el.userActionReadyAt = 0;
926
992
 
927
993
  el.loanRenewInProgress = true;
928
994