@internetarchive/ia-book-actions 1.4.6-alpha1 → 1.4.6-alpha2
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
|
@@ -145,9 +145,13 @@ export default async function ActionsHandlerService(options) {
|
|
|
145
145
|
* connection is exactly when this fires.
|
|
146
146
|
*/
|
|
147
147
|
log(`[IABookActions] ✗ ${option.action} threw`, error);
|
|
148
|
+
// networkFailure marks "the service never answered" (like
|
|
149
|
+
// nonJsonResponse in parseServiceResponse) — see
|
|
150
|
+
// IABookActions.handleLendingActionError.
|
|
148
151
|
option?.error?.({
|
|
149
152
|
error: reachedResponse ? unexpectedError : networkError,
|
|
150
153
|
detail: String(error),
|
|
154
|
+
...(reachedResponse ? {} : { networkFailure: true }),
|
|
151
155
|
});
|
|
152
156
|
}
|
|
153
157
|
}
|
package/src/ia-book-actions.js
CHANGED
|
@@ -1017,33 +1017,35 @@ export default class IABookActions extends LitElement {
|
|
|
1017
1017
|
if (
|
|
1018
1018
|
action === 'create_token' &&
|
|
1019
1019
|
!isInitial &&
|
|
1020
|
-
eventData?.nonJsonResponse === true
|
|
1021
|
-
|
|
1020
|
+
(eventData?.nonJsonResponse === true ||
|
|
1021
|
+
eventData?.networkFailure === true)
|
|
1022
1022
|
) {
|
|
1023
|
-
//
|
|
1024
|
-
//
|
|
1025
|
-
//
|
|
1026
|
-
//
|
|
1027
|
-
//
|
|
1028
|
-
// and
|
|
1023
|
+
// The service never answered this routine refresh: something in
|
|
1024
|
+
// front of it did (the WAF's /about/400.html, an outage page), or the
|
|
1025
|
+
// request never got through. That says nothing about the loan, so
|
|
1026
|
+
// no modal and no session teardown — it's already reported to Sentry
|
|
1027
|
+
// (ActionsHandlerService/LoanTokenPoller), the current access token
|
|
1028
|
+
// keeps the book readable, and the poll interval is left running, so
|
|
1029
|
+
// the next tick tries again. The service's own answer (e.g. "You do
|
|
1030
|
+
// not currently have this book borrowed.") still falls through below.
|
|
1029
1031
|
//
|
|
1030
1032
|
// Not for the initial call: its success is what runs
|
|
1031
1033
|
// lendingBarPostInit() and opens the book (see startLoanTokenPoller),
|
|
1032
1034
|
// so suppressing it would leave a reader that never loads, silently.
|
|
1033
1035
|
log(
|
|
1034
|
-
'[IABookActions] create_token
|
|
1036
|
+
'[IABookActions] create_token routine refresh got no answer from the service — no modal',
|
|
1035
1037
|
{
|
|
1036
1038
|
identifier: this.identifier,
|
|
1037
1039
|
status: eventData?.status,
|
|
1040
|
+
errorMsg,
|
|
1038
1041
|
}
|
|
1039
1042
|
);
|
|
1040
1043
|
return;
|
|
1041
1044
|
}
|
|
1042
1045
|
|
|
1043
1046
|
if (action === 'create_token') {
|
|
1044
|
-
// Any other create_token failure — initial or a routine
|
|
1045
|
-
//
|
|
1046
|
-
// longer be confirmed (e.g. a stale loan-record
|
|
1047
|
+
// Any other create_token failure — initial, or a routine refresh the
|
|
1048
|
+
// service itself answered — means access can no longer be confirmed (e.g. a stale loan-record
|
|
1047
1049
|
// cache on some other node can make this request fail even though
|
|
1048
1050
|
// the loan is fine, or the loan may genuinely have just been
|
|
1049
1051
|
// returned via BookReaderImages.php's revokeAccess()). Don't retry
|
|
@@ -135,6 +135,7 @@ describe('ActionsHandlerService - thrown errors', () => {
|
|
|
135
135
|
|
|
136
136
|
expect(error.calledOnce).to.be.true;
|
|
137
137
|
expect(error.firstCall.args[0].error).to.equal(networkError);
|
|
138
|
+
expect(error.firstCall.args[0].networkFailure).to.be.true;
|
|
138
139
|
const [, context] = window.Sentry.captureException.firstCall.args;
|
|
139
140
|
expect(context.tags).to.deep.equal({
|
|
140
141
|
identifier: 'foo',
|
|
@@ -158,5 +159,7 @@ describe('ActionsHandlerService - thrown errors', () => {
|
|
|
158
159
|
expect(error.calledOnce).to.be.true;
|
|
159
160
|
expect(error.firstCall.args[0].error).to.equal(unexpectedError);
|
|
160
161
|
expect(error.firstCall.args[0].detail).to.contain('boom');
|
|
162
|
+
// the service did answer, so this isn't treated as "no answer"
|
|
163
|
+
expect(error.firstCall.args[0].networkFailure).to.be.undefined;
|
|
161
164
|
});
|
|
162
165
|
});
|
|
@@ -5,7 +5,10 @@ import Sinon from 'sinon';
|
|
|
5
5
|
|
|
6
6
|
import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
|
|
7
7
|
import { LocalCache } from '@internetarchive/local-cache';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
unexpectedResponseError,
|
|
10
|
+
networkError,
|
|
11
|
+
} from '../src/core/services/actions-handler/actions-handler-service.js';
|
|
9
12
|
|
|
10
13
|
beforeEach(() => {
|
|
11
14
|
const modalManager = document.createElement('modal-manager');
|
|
@@ -1734,93 +1737,97 @@ describe('Sentry reports (WEBDEV-9595)', () => {
|
|
|
1734
1737
|
});
|
|
1735
1738
|
});
|
|
1736
1739
|
|
|
1737
|
-
describe('handleLendingActionError -
|
|
1740
|
+
describe('handleLendingActionError - routine create_token refresh failures (WEBDEV-9595)', () => {
|
|
1738
1741
|
const htmlBadRequest = {
|
|
1739
1742
|
success: false,
|
|
1740
1743
|
error: unexpectedResponseError(400),
|
|
1741
1744
|
status: 400,
|
|
1742
1745
|
nonJsonResponse: true,
|
|
1743
1746
|
};
|
|
1747
|
+
// the service never answered: no modal on a routine refresh
|
|
1748
|
+
const noAnswer = {
|
|
1749
|
+
'an HTML 400 (WAF block)': htmlBadRequest,
|
|
1750
|
+
'an HTML 502': {
|
|
1751
|
+
...htmlBadRequest,
|
|
1752
|
+
error: unexpectedResponseError(502),
|
|
1753
|
+
status: 502,
|
|
1754
|
+
},
|
|
1755
|
+
'a blocked or failed request': {
|
|
1756
|
+
error: networkError,
|
|
1757
|
+
detail: 'TypeError: Failed to fetch',
|
|
1758
|
+
networkFailure: true,
|
|
1759
|
+
},
|
|
1760
|
+
};
|
|
1761
|
+
// the service answered "no": the loan may really be gone
|
|
1762
|
+
const serviceAnswer = {
|
|
1763
|
+
success: false,
|
|
1764
|
+
error: 'You do not currently have this book borrowed.',
|
|
1765
|
+
status: 400,
|
|
1766
|
+
};
|
|
1744
1767
|
const liveLoan = {
|
|
1745
1768
|
is_lendable: true,
|
|
1746
1769
|
user_has_borrowed: true,
|
|
1747
1770
|
browsingExpired: false,
|
|
1748
1771
|
secondsLeftOnLoan: 300,
|
|
1749
1772
|
};
|
|
1750
|
-
|
|
1751
|
-
it('shows no modal and keeps the session going on a routine refresh', async () => {
|
|
1773
|
+
const setup = async () => {
|
|
1752
1774
|
const el = await fixture(
|
|
1753
1775
|
container({
|
|
1754
1776
|
userid: '@user1',
|
|
1755
|
-
identifier: '
|
|
1777
|
+
identifier: 'token-refresh-failure',
|
|
1756
1778
|
lendingStatus: liveLoan,
|
|
1757
1779
|
})
|
|
1758
1780
|
);
|
|
1759
1781
|
await el.updateComplete;
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
el.tokenPoller = { disconnectedCallback: Sinon.spy() };
|
|
1782
|
+
return el;
|
|
1783
|
+
};
|
|
1763
1784
|
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1785
|
+
for (const [name, data] of Object.entries(noAnswer)) {
|
|
1786
|
+
it(`shows no modal and keeps the session going for ${name}`, async () => {
|
|
1787
|
+
const el = await setup();
|
|
1788
|
+
const showErrorModalSpy = Sinon.spy(el, 'showErrorModal');
|
|
1789
|
+
const clearAllSpy = Sinon.spy(window.IALendingIntervals, 'clearAll');
|
|
1790
|
+
el.tokenPoller = { disconnectedCallback: Sinon.spy() };
|
|
1791
|
+
|
|
1792
|
+
try {
|
|
1793
|
+
el.handleLendingActionError({
|
|
1794
|
+
detail: { action: 'create_token', isInitial: false, data },
|
|
1795
|
+
});
|
|
1796
|
+
|
|
1797
|
+
expect(showErrorModalSpy.called).to.be.false;
|
|
1798
|
+
// none of the create_token teardown ran
|
|
1799
|
+
expect(clearAllSpy.called).to.be.false;
|
|
1800
|
+
expect(el.tokenPoller.disconnectedCallback.called).to.be.false;
|
|
1801
|
+
expect(el.lendingStatus.secondsLeftOnLoan).to.equal(300);
|
|
1802
|
+
expect(el.lendingStatus.available_to_browse).to.be.undefined;
|
|
1803
|
+
} finally {
|
|
1804
|
+
clearAllSpy.restore();
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1768
1807
|
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
expect(el.tokenPoller.disconnectedCallback.called).to.be.false;
|
|
1773
|
-
expect(el.lendingStatus.secondsLeftOnLoan).to.equal(300);
|
|
1774
|
-
expect(el.lendingStatus.available_to_browse).to.be.undefined;
|
|
1775
|
-
} finally {
|
|
1776
|
-
clearAllSpy.restore();
|
|
1777
|
-
}
|
|
1778
|
-
});
|
|
1808
|
+
it(`still shows the modal for ${name} when the book is first opening`, async () => {
|
|
1809
|
+
const el = await setup();
|
|
1810
|
+
const showErrorModalSpy = Sinon.spy(el, 'showErrorModal');
|
|
1779
1811
|
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
userid: '@user1',
|
|
1784
|
-
identifier: 'html-400-initial',
|
|
1785
|
-
lendingStatus: liveLoan,
|
|
1786
|
-
})
|
|
1787
|
-
);
|
|
1788
|
-
await el.updateComplete;
|
|
1789
|
-
const showErrorModalSpy = Sinon.spy(el, 'showErrorModal');
|
|
1812
|
+
el.handleLendingActionError({
|
|
1813
|
+
detail: { action: 'create_token', isInitial: true, data },
|
|
1814
|
+
});
|
|
1790
1815
|
|
|
1791
|
-
|
|
1792
|
-
detail: { action: 'create_token', isInitial: true, data: htmlBadRequest },
|
|
1816
|
+
expect(showErrorModalSpy.calledOnce).to.be.true;
|
|
1793
1817
|
});
|
|
1818
|
+
}
|
|
1794
1819
|
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
it("still shows the modal for the service's own JSON 400 and for an HTML 5xx", async () => {
|
|
1799
|
-
const el = await fixture(
|
|
1800
|
-
container({
|
|
1801
|
-
userid: '@user1',
|
|
1802
|
-
identifier: 'html-400-other',
|
|
1803
|
-
lendingStatus: liveLoan,
|
|
1804
|
-
})
|
|
1805
|
-
);
|
|
1806
|
-
await el.updateComplete;
|
|
1820
|
+
it("still shows the modal and resets the bar when the service itself says no", async () => {
|
|
1821
|
+
const el = await setup();
|
|
1807
1822
|
const showErrorModalSpy = Sinon.spy(el, 'showErrorModal');
|
|
1808
1823
|
|
|
1809
1824
|
el.handleLendingActionError({
|
|
1810
|
-
detail: {
|
|
1811
|
-
action: 'create_token',
|
|
1812
|
-
isInitial: false,
|
|
1813
|
-
data: { error: 'You do not currently have this book borrowed.' },
|
|
1814
|
-
},
|
|
1815
|
-
});
|
|
1816
|
-
el.handleLendingActionError({
|
|
1817
|
-
detail: {
|
|
1818
|
-
action: 'create_token',
|
|
1819
|
-
isInitial: false,
|
|
1820
|
-
data: { ...htmlBadRequest, status: 502 },
|
|
1821
|
-
},
|
|
1825
|
+
detail: { action: 'create_token', isInitial: false, data: serviceAnswer },
|
|
1822
1826
|
});
|
|
1823
1827
|
|
|
1824
|
-
expect(
|
|
1828
|
+
expect(
|
|
1829
|
+
showErrorModalSpy.calledOnceWith(serviceAnswer.error, 'create_token')
|
|
1830
|
+
).to.be.true;
|
|
1831
|
+
expect(el.lendingStatus.secondsLeftOnLoan).to.equal(0);
|
|
1825
1832
|
});
|
|
1826
1833
|
});
|