@internetarchive/ia-book-actions 1.4.4-alpha16 → 1.4.4-alpha17

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-alpha16",
10
+ "version": "1.4.4-alpha17",
11
11
  "main": "index.js",
12
12
  "module": "index.js",
13
13
  "publishConfig": {
@@ -26,6 +26,11 @@ export class LoanTokenPoller {
26
26
  * attempt_to_renew_loan() already minted a valid access token as part
27
27
  * of the renew_loan response itself — an immediate confirming
28
28
  * create_token call is redundant.
29
+ * @param {Function} [options.onTokenRefreshed] - called after EVERY
30
+ * successful create_token, initial or routine — unlike successCallback,
31
+ * which only fires for the initial one. Lets the consumer retry any
32
+ * page image that failed during a prior access gap, now that access is
33
+ * confirmed good again.
29
34
  */
30
35
  constructor(options = {}) {
31
36
  const {
@@ -35,6 +40,7 @@ export class LoanTokenPoller {
35
40
  errorCallback,
36
41
  pollerDelay,
37
42
  skipInitialCall = false,
43
+ onTokenRefreshed,
38
44
  } = options;
39
45
 
40
46
  this.identifier = identifier;
@@ -43,6 +49,7 @@ export class LoanTokenPoller {
43
49
  this.errorCallback = errorCallback; // callback function to be called after loan token is created
44
50
  this.pollerDelay = pollerDelay; // value in seconds
45
51
  this.skipInitialCall = skipInitialCall === true;
52
+ this.onTokenRefreshed = onTokenRefreshed;
46
53
 
47
54
  this.loanTokenInterval = undefined;
48
55
 
@@ -111,6 +118,7 @@ export class LoanTokenPoller {
111
118
  identifier: this.identifier,
112
119
  isInitial,
113
120
  });
121
+ this.onTokenRefreshed?.();
114
122
  if (isInitial) this.successCallback();
115
123
  },
116
124
  });
@@ -55,6 +55,10 @@ export default class IABookActions extends LitElement {
55
55
  type: Function,
56
56
  attribute: false,
57
57
  },
58
+ reloadPageImages: {
59
+ type: Function,
60
+ attribute: false,
61
+ },
58
62
  barType: { type: String },
59
63
  sharedObserver: { attribute: false },
60
64
  disableActionGroup: { type: Boolean },
@@ -77,6 +81,7 @@ export default class IABookActions extends LitElement {
77
81
  this.width = 0;
78
82
  this.bwbPurchaseUrl = '';
79
83
  this.lendingBarPostInit = () => {};
84
+ this.reloadPageImages = () => {};
80
85
  this.barType = 'action'; // 'title'|'action'
81
86
  this.sharedObserver = undefined;
82
87
  this.disableActionGroup = false;
@@ -762,6 +767,12 @@ export default class IABookActions extends LitElement {
762
767
  // triggered this renewal.
763
768
  this.skipNextInitialTokenCall = true;
764
769
 
770
+ // Retry any page image that failed during the gap this renewal just
771
+ // closed, right away rather than waiting for the next routine
772
+ // create_token tick (up to tokenDelay seconds away, now that the
773
+ // initial one above is skipped).
774
+ this.reloadPageImages();
775
+
765
776
  if (this.recoveringFromLoanExpiry) {
766
777
  // Only now is it safe to re-initialize BookReader — not for a
767
778
  // routine top-up, where that would be disruptive. Call it
@@ -935,6 +946,13 @@ export default class IABookActions extends LitElement {
935
946
  const errorCallback = eventObj => {
936
947
  this.handleLendingActionError(eventObj);
937
948
  };
949
+ // BookReader has no self-healing for a page <img> that failed while
950
+ // access was briefly invalid — retry any of those now that this
951
+ // create_token confirms access is good again, on every success, not
952
+ // just the initial one.
953
+ const onTokenRefreshed = () => {
954
+ this.reloadPageImages();
955
+ };
938
956
 
939
957
  // Tear down any previous poller first, so only one is ever controlling
940
958
  // window.IALendingIntervals.tokenPoller at a time.
@@ -946,6 +964,7 @@ export default class IABookActions extends LitElement {
946
964
  errorCallback,
947
965
  pollerDelay: this.tokenDelay, // in seconds
948
966
  skipInitialCall,
967
+ onTokenRefreshed,
949
968
  });
950
969
  }
951
970
 
@@ -1130,6 +1149,10 @@ export default class IABookActions extends LitElement {
1130
1149
  justify-content: center;
1131
1150
  flex-wrap: wrap;
1132
1151
  }
1152
+
1153
+ #action-bar-modal {
1154
+ --modalWidth: 36rem;
1155
+ }
1133
1156
  `;
1134
1157
  }
1135
1158
  }
@@ -1,4 +1,4 @@
1
- import { expect } from '@open-wc/testing';
1
+ import { expect, aTimeout } from '@open-wc/testing';
2
2
  import Sinon from 'sinon';
3
3
 
4
4
  import { LoanTokenPoller } from '../../../src/core/services/loan-token-poller.js';
@@ -152,3 +152,45 @@ describe('skipInitialCall option', () => {
152
152
  poller.disconnectedCallback();
153
153
  });
154
154
  });
155
+
156
+ describe('onTokenRefreshed option', () => {
157
+ // BookReader has no self-healing for a page <img> that failed while
158
+ // access was briefly invalid, so the consumer needs a signal on EVERY
159
+ // create_token success (not just the initial one) to know it's safe to
160
+ // retry those images.
161
+ it('fires on the initial create_token success', async () => {
162
+ const onTokenRefreshed = Sinon.spy();
163
+ const poller = new LoanTokenPoller({
164
+ identifier: 'foo',
165
+ borrowType: 'browsed',
166
+ successCallback: () => {},
167
+ errorCallback: () => {},
168
+ pollerDelay: 120,
169
+ onTokenRefreshed,
170
+ });
171
+
172
+ await aTimeout(50);
173
+
174
+ expect(onTokenRefreshed.called).to.be.true;
175
+ poller.disconnectedCallback();
176
+ });
177
+
178
+ it('fires on a routine (non-initial) create_token success too', async () => {
179
+ const onTokenRefreshed = Sinon.spy();
180
+ const poller = new LoanTokenPoller({
181
+ identifier: 'foo',
182
+ borrowType: 'browsed',
183
+ successCallback: () => {},
184
+ errorCallback: () => {},
185
+ pollerDelay: 120,
186
+ onTokenRefreshed,
187
+ });
188
+ onTokenRefreshed.resetHistory();
189
+
190
+ await poller.handleLoanTokenPoller(false);
191
+ await aTimeout(50);
192
+
193
+ expect(onTokenRefreshed.called).to.be.true;
194
+ poller.disconnectedCallback();
195
+ });
196
+ });
@@ -958,6 +958,7 @@ describe('autoRenewExpiredLoan', () => {
958
958
  );
959
959
  await el.updateComplete;
960
960
  el.postInitComplete = true;
961
+ el.reloadPageImages = Sinon.spy();
961
962
 
962
963
  await el.localCache.set({
963
964
  key: 'foobar-loanTime',
@@ -985,6 +986,9 @@ describe('autoRenewExpiredLoan', () => {
985
986
  await el.updateComplete;
986
987
 
987
988
  expect(startTokenPollerSpy.calledOnceWith(true)).to.be.true;
989
+ // Retried right away rather than waiting for the (now-skipped) initial
990
+ // create_token or the next routine tick.
991
+ expect(el.reloadPageImages.called).to.be.true;
988
992
  });
989
993
 
990
994
  it('calls lendingBarPostInit directly on a recovery renewal and skips the initial create_token', async () => {
@@ -1006,6 +1010,7 @@ describe('autoRenewExpiredLoan', () => {
1006
1010
  );
1007
1011
  await el.updateComplete;
1008
1012
  el.lendingBarPostInit = Sinon.spy();
1013
+ el.reloadPageImages = Sinon.spy();
1009
1014
 
1010
1015
  await el.localCache.set({
1011
1016
  key: 'foobar-loanTime',
@@ -1030,6 +1035,7 @@ describe('autoRenewExpiredLoan', () => {
1030
1035
  // create_token success callback.
1031
1036
  expect(el.lendingBarPostInit.calledOnce).to.be.true;
1032
1037
  expect(el.postInitComplete).to.be.true;
1038
+ expect(el.reloadPageImages.called).to.be.true;
1033
1039
 
1034
1040
  await el.updateComplete;
1035
1041
  await aTimeout(150); // let the 100ms token-poller-restart check run