@internetarchive/ia-book-actions 1.4.4-alpha10 → 1.4.4-alpha12
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
|
@@ -372,6 +372,12 @@ export default class ActionsHandler extends LitElement {
|
|
|
372
372
|
new Date().getTime() + this.loanTotalTime * 1000
|
|
373
373
|
);
|
|
374
374
|
|
|
375
|
+
log('[ActionsHandler] setBrowseTimeSession: resetting loanTime', {
|
|
376
|
+
identifier: this.identifier,
|
|
377
|
+
expireDate,
|
|
378
|
+
loanTotalTime: this.loanTotalTime,
|
|
379
|
+
});
|
|
380
|
+
|
|
375
381
|
// set a value
|
|
376
382
|
await this.localCache.set({
|
|
377
383
|
key: `${this.identifier}-loanTime`,
|
|
@@ -381,12 +387,20 @@ export default class ActionsHandler extends LitElement {
|
|
|
381
387
|
|
|
382
388
|
// delete pageChangedTime when book is auto renew at nth minute
|
|
383
389
|
await this.localCache.delete(`${this.identifier}-pageChangedTime`);
|
|
390
|
+
|
|
391
|
+
log('[ActionsHandler] setBrowseTimeSession: loanTime reset complete', {
|
|
392
|
+
identifier: this.identifier,
|
|
393
|
+
});
|
|
384
394
|
} catch (error) {
|
|
385
|
-
log(error);
|
|
395
|
+
log('[ActionsHandler] setBrowseTimeSession failed', error);
|
|
386
396
|
}
|
|
387
397
|
}
|
|
388
398
|
|
|
389
399
|
deleteLoanCookies() {
|
|
400
|
+
log('[ActionsHandler] deleteLoanCookies: expiring loan cookies', {
|
|
401
|
+
identifier: this.identifier,
|
|
402
|
+
});
|
|
403
|
+
|
|
390
404
|
const date = new Date();
|
|
391
405
|
date.setTime(date.getTime() - 24 * 60 * 60 * 1000); // one day ago
|
|
392
406
|
|
|
@@ -159,16 +159,16 @@ export default class LoanAnanlytics {
|
|
|
159
159
|
* @memberof LoanAnanlytics
|
|
160
160
|
*/
|
|
161
161
|
sendEvent(eventCategory, eventAction, label, extraParams) {
|
|
162
|
-
log(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
);
|
|
162
|
+
// log(
|
|
163
|
+
// 'eventCategory:-',
|
|
164
|
+
// eventCategory,
|
|
165
|
+
// '||\teventAction:-',
|
|
166
|
+
// eventAction,
|
|
167
|
+
// '||\tlabel:-',
|
|
168
|
+
// label,
|
|
169
|
+
// '||\textraParams:-',
|
|
170
|
+
// extraParams,
|
|
171
|
+
// );
|
|
172
172
|
|
|
173
173
|
window?.archive_analytics?.send_event_no_sampling(
|
|
174
174
|
eventCategory,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import ActionsHandlerService from './actions-handler/actions-handler-service.js';
|
|
3
3
|
import LoanAnanlytics from './loan-analytics.js';
|
|
4
4
|
import { sentryLogs } from '../config/sentry-events.js';
|
|
5
|
+
import log from './log.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* This class is used to create loan token for borrowed books
|
|
@@ -77,11 +78,21 @@ export class LoanTokenPoller {
|
|
|
77
78
|
*/
|
|
78
79
|
async handleLoanTokenPoller(isInitial = false, retryCount = 0) {
|
|
79
80
|
const action = 'create_token';
|
|
81
|
+
log('[LoanTokenPoller] create_token requested', {
|
|
82
|
+
identifier: this.identifier,
|
|
83
|
+
isInitial,
|
|
84
|
+
retryCount,
|
|
85
|
+
});
|
|
80
86
|
ActionsHandlerService({
|
|
81
87
|
identifier: this.identifier,
|
|
82
88
|
action,
|
|
83
89
|
error: data => this.handleTokenError(data, isInitial, retryCount),
|
|
84
90
|
success: () => {
|
|
91
|
+
log('[LoanTokenPoller] create_token succeeded', {
|
|
92
|
+
identifier: this.identifier,
|
|
93
|
+
isInitial,
|
|
94
|
+
retryCount,
|
|
95
|
+
});
|
|
85
96
|
if (isInitial) this.successCallback();
|
|
86
97
|
},
|
|
87
98
|
});
|
|
@@ -104,18 +115,37 @@ export class LoanTokenPoller {
|
|
|
104
115
|
typeof data?.error === 'string' &&
|
|
105
116
|
/do not currently have this book borrowed/i.test(data.error);
|
|
106
117
|
|
|
118
|
+
log('[LoanTokenPoller] create_token failed', {
|
|
119
|
+
identifier: this.identifier,
|
|
120
|
+
isInitial,
|
|
121
|
+
retryCount,
|
|
122
|
+
isStaleLoanReadError,
|
|
123
|
+
error: data?.error,
|
|
124
|
+
});
|
|
125
|
+
|
|
107
126
|
if (
|
|
108
127
|
isInitial &&
|
|
109
128
|
isStaleLoanReadError &&
|
|
110
129
|
retryCount < this.maxInitialTokenRetries
|
|
111
130
|
) {
|
|
112
131
|
const delay = this.initialTokenRetryDelay * (retryCount + 1);
|
|
132
|
+
log('[LoanTokenPoller] retrying create_token after stale-loan-read error', {
|
|
133
|
+
identifier: this.identifier,
|
|
134
|
+
nextRetryCount: retryCount + 1,
|
|
135
|
+
delay,
|
|
136
|
+
});
|
|
113
137
|
setTimeout(() => {
|
|
114
138
|
this.handleLoanTokenPoller(true, retryCount + 1);
|
|
115
139
|
}, delay);
|
|
116
140
|
return;
|
|
117
141
|
}
|
|
118
142
|
|
|
143
|
+
log('[LoanTokenPoller] giving up on create_token, reporting error', {
|
|
144
|
+
identifier: this.identifier,
|
|
145
|
+
isInitial,
|
|
146
|
+
retryCount,
|
|
147
|
+
});
|
|
148
|
+
|
|
119
149
|
this.errorCallback({ detail: { action, data } });
|
|
120
150
|
|
|
121
151
|
// send error to Sentry
|
package/src/ia-book-actions.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
@@ -440,7 +465,15 @@ export default class IABookActions extends LitElement {
|
|
|
440
465
|
* On error: handleLendingActionError() shows showLoanUnavailableModal().
|
|
441
466
|
*/
|
|
442
467
|
autoRenewExpiredLoan() {
|
|
443
|
-
if (this.loanRenewInProgress)
|
|
468
|
+
if (this.loanRenewInProgress) {
|
|
469
|
+
log('[IABookActions] autoRenewExpiredLoan: skipped, renewal already in progress', {
|
|
470
|
+
identifier: this.identifier,
|
|
471
|
+
});
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
log('[IABookActions] autoRenewExpiredLoan: starting silent renewal', {
|
|
475
|
+
identifier: this.identifier,
|
|
476
|
+
});
|
|
444
477
|
this.loanRenewInProgress = true;
|
|
445
478
|
this.recoveringFromLoanExpiry = true;
|
|
446
479
|
|
|
@@ -448,11 +481,17 @@ export default class IABookActions extends LitElement {
|
|
|
448
481
|
|
|
449
482
|
// Optimistically flip browsingExpired back to false so the action bar
|
|
450
483
|
// stays red (patronIsReadingAction) while the renew_loan request is in
|
|
451
|
-
// flight, instead of showing the blue "Borrow" state.
|
|
484
|
+
// flight, instead of showing the blue "Borrow" state. Deliberately NOT
|
|
485
|
+
// resetting secondsLeftOnLoan here — the renewal hasn't been confirmed
|
|
486
|
+
// yet (it can take several seconds, or genuinely fail), so showing a
|
|
487
|
+
// full hour before we know the outcome would be misleading. The real
|
|
488
|
+
// value is set once handleLoanAutoRenewed() confirms success;
|
|
489
|
+
// loanRenewInProgress (already true above) is the signal consumers
|
|
490
|
+
// should use to show a "renewing" state instead of trusting the
|
|
491
|
+
// countdown during this window.
|
|
452
492
|
this.lendingStatus = {
|
|
453
493
|
...this.lendingStatus,
|
|
454
494
|
browsingExpired: false,
|
|
455
|
-
secondsLeftOnLoan: this.loanRenewTimeConfig.loanTotalTime,
|
|
456
495
|
};
|
|
457
496
|
|
|
458
497
|
// Defer renewNow until after lendingStatus timer setup completes.
|
|
@@ -505,19 +544,14 @@ export default class IABookActions extends LitElement {
|
|
|
505
544
|
headerColor: '#194880',
|
|
506
545
|
showCloseButton: false,
|
|
507
546
|
closeOnBackdropClick: false,
|
|
508
|
-
message: html`<span
|
|
509
|
-
|
|
510
|
-
>
|
|
511
|
-
<span>${this.loanRenewHelper?.getMessageTexts(
|
|
512
|
-
warningTexts,
|
|
513
|
-
secondsLeft
|
|
514
|
-
)}</span>
|
|
547
|
+
message: html`<span>
|
|
548
|
+
${this.loanRenewHelper?.getMessageTexts(warningTexts, secondsLeft)}
|
|
515
549
|
<a
|
|
516
550
|
href="https://help.archive.org/help/borrowing-from-the-lending-library"
|
|
517
551
|
target="_blank"
|
|
518
552
|
title="Get more info on borrowing from The Lending Library"
|
|
519
553
|
data-event-click-tracking="BookReader|BrowsableMoreInfo"
|
|
520
|
-
style="display:inline-
|
|
554
|
+
style="display:inline-block;vertical-align:middle;line-height:0;margin-left:4px;"
|
|
521
555
|
>
|
|
522
556
|
${infoIcon}
|
|
523
557
|
</a>
|
|
@@ -552,7 +586,10 @@ export default class IABookActions extends LitElement {
|
|
|
552
586
|
* Execute when loan is expired
|
|
553
587
|
*/
|
|
554
588
|
async browseHasExpired() {
|
|
555
|
-
log('[IABookActions] browseHasExpired'
|
|
589
|
+
log('[IABookActions] browseHasExpired', {
|
|
590
|
+
identifier: this.identifier,
|
|
591
|
+
loanRenewInProgress: this.loanRenewInProgress,
|
|
592
|
+
});
|
|
556
593
|
window?.IALendingIntervals?.clearAll();
|
|
557
594
|
|
|
558
595
|
const currStatus = {
|
|
@@ -565,6 +602,9 @@ export default class IABookActions extends LitElement {
|
|
|
565
602
|
// remove respected key:value for loan-renew
|
|
566
603
|
await this.localCache.delete(`${this.identifier}-loanTime`);
|
|
567
604
|
await this.localCache.delete(`${this.identifier}-pageChangedTime`);
|
|
605
|
+
log('[IABookActions] browseHasExpired: cleared loanTime/pageChangedTime cache', {
|
|
606
|
+
identifier: this.identifier,
|
|
607
|
+
});
|
|
568
608
|
|
|
569
609
|
// show message after browsed book is expired.
|
|
570
610
|
this.loanRenewResult.renewNow = false;
|
|
@@ -791,9 +831,8 @@ export default class IABookActions extends LitElement {
|
|
|
791
831
|
}
|
|
792
832
|
|
|
793
833
|
log('[IABookActions] timer', {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
pageChangedInLast: this.loanRenewTimeConfig.pageChangedInLast,
|
|
834
|
+
whatShouldLeft: resyncd.whatShouldLeft,
|
|
835
|
+
whatIsleft: secondsLeft,
|
|
797
836
|
});
|
|
798
837
|
|
|
799
838
|
/**
|
|
@@ -952,6 +991,14 @@ export default class IABookActions extends LitElement {
|
|
|
952
991
|
const action = event?.detail?.action;
|
|
953
992
|
const errorMsg = event?.detail?.data?.error;
|
|
954
993
|
|
|
994
|
+
log('[IABookActions] handleLendingActionError', {
|
|
995
|
+
identifier: this.identifier,
|
|
996
|
+
action,
|
|
997
|
+
errorMsg,
|
|
998
|
+
loanRenewInProgress: this.loanRenewInProgress,
|
|
999
|
+
recoveringFromLoanExpiry: this.recoveringFromLoanExpiry,
|
|
1000
|
+
});
|
|
1001
|
+
|
|
955
1002
|
if (action === 'create_token') {
|
|
956
1003
|
// A create_token hiccup only affects BookReader's page-image access
|
|
957
1004
|
// token — it says nothing about how much time is left on the loan
|
|
@@ -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
|
|