@internetarchive/ia-book-actions 1.4.4-alpha9 → 1.4.5
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/index.html +38 -1
- package/package.json +1 -1
- package/src/assets/data/info-icon.js +17 -0
- package/src/core/services/actions-handler/actions-handler-service.js +27 -4
- package/src/core/services/actions-handler/actions-handler.js +77 -36
- package/src/core/services/loan-analytics.js +0 -11
- package/src/core/services/loan-renew-helper.js +2 -2
- package/src/core/services/loan-token-poller.js +87 -19
- package/src/ia-book-actions.js +290 -245
- package/test/core/services/loan-token-poller.test.js +111 -15
- package/test/ia-book-actions.test.js +503 -35
- package/src/assets/data/info.js +0 -12
package/index.html
CHANGED
|
@@ -360,7 +360,33 @@
|
|
|
360
360
|
});
|
|
361
361
|
});
|
|
362
362
|
|
|
363
|
-
|
|
363
|
+
// WEBDEV-8322 repro: fail only create_token (via ?failAction=create_token),
|
|
364
|
+
// leaving renew_loan (and every other action) succeeding normally —
|
|
365
|
+
// see actions-handler-service.js's shouldReturnError.
|
|
366
|
+
document.querySelectorAll('.failCreateToken input[type=checkbox]').forEach((item) => {
|
|
367
|
+
if (window.location.href.indexOf('?failAction=create_token') !== -1) {
|
|
368
|
+
item.checked = true;
|
|
369
|
+
}
|
|
370
|
+
item.addEventListener('click', (e) => {
|
|
371
|
+
console.log(`[Demo] fail-create_token-only mode ${e.target.checked ? 'enabled' : 'disabled'}`);
|
|
372
|
+
|
|
373
|
+
var searchParams = new URLSearchParams(window.location.search);
|
|
374
|
+
var separate = '';
|
|
375
|
+
if (e.target.checked) {
|
|
376
|
+
separate = '?';
|
|
377
|
+
searchParams.set('failAction', 'create_token');
|
|
378
|
+
} else {
|
|
379
|
+
separate = '';
|
|
380
|
+
searchParams.delete('failAction');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (history.pushState) {
|
|
384
|
+
var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + separate + searchParams;
|
|
385
|
+
window.history.pushState({path:newurl},'',newurl);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
364
390
|
document.querySelectorAll('.userState input[type=checkbox]').forEach((item) => {
|
|
365
391
|
item.addEventListener('click', (e) => {
|
|
366
392
|
if (e.target.checked) {
|
|
@@ -726,6 +752,17 @@
|
|
|
726
752
|
</span>
|
|
727
753
|
</td>
|
|
728
754
|
</tr>
|
|
755
|
+
<tr colspan='5'>
|
|
756
|
+
<td class="failCreateToken">
|
|
757
|
+
<label><input type="checkbox" name="failCreateToken" value="failCreateToken" />Fail create_token only (renew_loan still succeeds)</label>
|
|
758
|
+
<span style="color: #666; font-size: 11px; display: block; padding: 0 10px 10px;">
|
|
759
|
+
WEBDEV-8322 repro: unlike the checkbox above, this leaves renew_loan
|
|
760
|
+
succeeding and fails only the create_token call right after it — turn
|
|
761
|
+
this on, then run scenario 2 → 3 (or 2 → 4) below. Expect the error
|
|
762
|
+
modal, and the bar/timer resetting to Borrow/0 (no retry).
|
|
763
|
+
</span>
|
|
764
|
+
</td>
|
|
765
|
+
</tr>
|
|
729
766
|
<tr>
|
|
730
767
|
<td class="userState">
|
|
731
768
|
<label><input type="checkbox" name="user" value="isAdmin" />isAdmin</label>
|
package/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
|
|
3
|
+
export const infoIcon = html`<svg
|
|
4
|
+
height="20"
|
|
5
|
+
width="20"
|
|
6
|
+
viewBox="0 0 100 100"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
>
|
|
9
|
+
<g fill="none" fill-rule="evenodd">
|
|
10
|
+
<path d="m0 0h100v100h-100z" fill="#fff" />
|
|
11
|
+
<path
|
|
12
|
+
d="m49.8315487 0h.1702245c6.7356878 0 13.1853038 1.31117332 19.3488483 3.93351997 6.1635444 2.62234664 11.4854233 6.15778963 15.9656369 10.60632903 4.4802135 4.4485394 8.0478347 9.7522946 10.7028636 15.9112655 2.655029 6.1589709 3.980878 12.6038012 3.9789971 19.3344909.0567419 6.6716279-1.1702933 13.0585776-3.6811042 19.1608491-2.510811 6.1022715-6.106803 11.5206067-10.7879759 16.2550055-9.7027949 9.7522946-21.4884754 14.6851412-35.3570414 14.79854h-.1702244c-6.7333236 0-13.1829397-1.3111733-19.3488483-3.93352-6.1659087-2.6223466-11.4877876-6.1577896-15.9656369-10.606329s-8.04547055-9.7522946-10.7028637-15.9112655c-2.65739314-6.1589709-3.9844243-12.6038012-3.98254337-19.3344909-.05674149-6.6716279 1.17029325-13.0585776 3.68110421-19.1608491 2.51081095-6.1022715 6.10680292-11.5206067 10.78797586-16.2550055 9.7027949-9.75229456 21.4884754-14.68514123 35.3570414-14.79854zm12.6566146 26.4757998c1.6745578-1.6828001 2.5118367-3.6747334 2.5118367-5.9757998 0-2.4126333-.8095238-4.4324583-2.4285714-6.059475s-3.6289796-2.440525-6.0297959-2.440525c-2.4008164 0-4.4107483.8135083-6.029796 2.440525-1.6745578 1.6270167-2.5118367 3.6468417-2.5118367 6.059475 0 2.1871753.8372789 4.1791086 2.5118367 5.9757998 1.6745579 1.6828001 3.6844898 2.5242002 6.029796 2.5242002 2.3453061 0 4.3274829-.8414001 5.9465306-2.5242002zm-12.1370589 52.7776981-1.2815282-.9486968c0-.4588935.398855-1.8938272 1.196565-4.3048011l12.7338588-39-23.0745876 3.6164609.2548896 3.873251c0-.1141289.4554971-.1997256 1.3664914-.2567901.9109942 0 1.623741.1723823 2.1382404.5171468.5121392.2306356.7965299.6039323.8531721 1.1198902 0 .8607225-.6549247 3.2134431-1.9647739 7.0581619l-8.1175252 24.1061729c-1.0242785 3.2716963-1.5080967 5.5388203-1.4514546 6.8013717.0566421 1.6643804.8826732 2.9839963 2.4780932 3.9588477 1.2532071.803658 2.8769482 1.205487 4.8712231 1.205487h.5982825c1.7653464-.0570645 3.8445846-.3875629 6.2377146-.9914952 2.3931299-.6039323 4.3590839-1.3933242 5.8978617-2.3681756 1.3098493-.803658 2.3919499-1.5359853 3.2463021-2.1969821.8543521-.6609968 1.3959925-1.1341564 1.6249211-1.4194788l.3433929-.3459533-2.8214861-3.3596708-2.9914125 2.0650205c-.79771.4588935-1.5104568.7454047-2.1382404.8595337z"
|
|
13
|
+
fill="#000"
|
|
14
|
+
fill-rule="nonzero"
|
|
15
|
+
/>
|
|
16
|
+
</g>
|
|
17
|
+
</svg> `;
|
|
@@ -18,7 +18,6 @@ export default async function ActionsHandlerService(options) {
|
|
|
18
18
|
let baseHost = '/services/loans/loan';
|
|
19
19
|
const location = window?.location;
|
|
20
20
|
|
|
21
|
-
// return error reponse when not production and has ?error=true param...
|
|
22
21
|
const tokenError = 'loan token not found. please try again later.';
|
|
23
22
|
const borrowError =
|
|
24
23
|
'This book is not available to borrow at this time. Please try again later.';
|
|
@@ -29,9 +28,18 @@ export default async function ActionsHandlerService(options) {
|
|
|
29
28
|
'renew_loan',
|
|
30
29
|
'return_loan',
|
|
31
30
|
];
|
|
31
|
+
const searchParams = new URLSearchParams(location?.search);
|
|
32
|
+
// ?error=true fails every erroneous action (e.g. simulates the book
|
|
33
|
+
// already being borrowed by someone else). ?failAction=create_token (or
|
|
34
|
+
// any action name) fails ONLY that action, so a renew_loan can succeed
|
|
35
|
+
// and the immediately-following create_token can be made to fail —
|
|
36
|
+
// reproducing WEBDEV-8322's "renewed, but create_token failed" bug
|
|
37
|
+
// without needing a real book.
|
|
32
38
|
const shouldReturnError =
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
location?.hostname !== 'archive.org' &&
|
|
40
|
+
erroneousActions.includes(option?.action) &&
|
|
41
|
+
(searchParams.get('error') === 'true' ||
|
|
42
|
+
searchParams.get('failAction') === option?.action);
|
|
35
43
|
|
|
36
44
|
const testHostname = ['localhost', 'internetarchive.github.io'];
|
|
37
45
|
let isTest = false;
|
|
@@ -51,7 +59,7 @@ export default async function ActionsHandlerService(options) {
|
|
|
51
59
|
})
|
|
52
60
|
.then(async response => {
|
|
53
61
|
// intentional error on localhost
|
|
54
|
-
if (shouldReturnError
|
|
62
|
+
if (shouldReturnError) {
|
|
55
63
|
return {
|
|
56
64
|
success: false,
|
|
57
65
|
error: option?.action === 'create_token' ? tokenError : borrowError,
|
|
@@ -95,5 +103,20 @@ export default async function ActionsHandlerService(options) {
|
|
|
95
103
|
window?.Sentry?.captureException(
|
|
96
104
|
`${sentryLogs.actionsHandlerService} - Error: ${error}`
|
|
97
105
|
);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Report it, don't just swallow it. A rejected fetch (offline, dropped
|
|
109
|
+
* connection) or a non-JSON body (a 405 returning HTML) lands here, and
|
|
110
|
+
* calling neither `success` nor `error` leaves every caller waiting on a
|
|
111
|
+
* callback that never comes.
|
|
112
|
+
*
|
|
113
|
+
* For renew_loan that's not merely a missing modal: IABookActions
|
|
114
|
+
* clears its loanRenewInProgress guard from these callbacks, so a
|
|
115
|
+
* silent failure latched it on for good and every later renewal
|
|
116
|
+
* attempt became a no-op. Backgrounding a tab on a flaky mobile
|
|
117
|
+
* connection is exactly when this fires.
|
|
118
|
+
*/
|
|
119
|
+
log(`[IABookActions] ✗ ${option.action} threw`, error);
|
|
120
|
+
option?.error?.({ error: String(error) });
|
|
98
121
|
}
|
|
99
122
|
}
|
|
@@ -6,7 +6,11 @@ import log from '../log.js';
|
|
|
6
6
|
|
|
7
7
|
import ActionsHandlerService from './actions-handler-service.js';
|
|
8
8
|
import LoanAnanlytics from '../loan-analytics.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
analyticsCategories,
|
|
11
|
+
analyticsActions,
|
|
12
|
+
analyticsLabels,
|
|
13
|
+
} from '../../config/analytics-event-and-category.js';
|
|
10
14
|
import * as Cookies from '../doc-cookies.js';
|
|
11
15
|
|
|
12
16
|
/**
|
|
@@ -172,32 +176,51 @@ export default class ActionsHandler extends LitElement {
|
|
|
172
176
|
action,
|
|
173
177
|
identifier: this.identifier,
|
|
174
178
|
success: async data => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
179
|
+
// Anything thrown in here is an unhandled rejection that dispatches
|
|
180
|
+
// NOTHING — and IABookActions only clears loanRenewInProgress when
|
|
181
|
+
// one of these two events arrives, so a throw here latches it on
|
|
182
|
+
// permanently (dead countdown, loan stuck "active"). Always exit
|
|
183
|
+
// through exactly one of the two dispatches below.
|
|
184
|
+
try {
|
|
185
|
+
log('RENEW_LOAN --- ', data, this.identifier);
|
|
186
|
+
const activeLoan = data.loan ? data.loan : undefined;
|
|
187
|
+
// Optional chaining: `data.loan` is genuinely absent on some
|
|
188
|
+
// failures, and `activeLoan` can be undefined here.
|
|
189
|
+
const isRenewal = activeLoan?.renewal;
|
|
190
|
+
|
|
191
|
+
if (activeLoan && isRenewal) {
|
|
192
|
+
// Await — loanAutoRenewed listeners read this same cache key
|
|
193
|
+
// immediately; dispatching before the write lands was a race.
|
|
194
|
+
await this.setBrowseTimeSession();
|
|
195
|
+
|
|
196
|
+
// Only record the renew analytics event once the renewal has
|
|
197
|
+
// actually succeeded — not merely attempted.
|
|
198
|
+
await this.loanAnanlytics?.storeLoanStatsCount(
|
|
199
|
+
this.identifier,
|
|
200
|
+
'autorenew'
|
|
201
|
+
);
|
|
202
|
+
const analyticsLabel =
|
|
203
|
+
renewType === 'auto'
|
|
204
|
+
? analyticsLabels.browseAutoRenew
|
|
205
|
+
: analyticsLabels.browseManualRenew;
|
|
206
|
+
this.loanAnanlytics?.sendEvent(
|
|
207
|
+
analyticsCategories.browse,
|
|
208
|
+
analyticsActions.browseRenew,
|
|
209
|
+
analyticsLabel,
|
|
210
|
+
this.identifier
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Dispatch the success outcome ONLY for a confirmed renewal —
|
|
214
|
+
// a `{loan: {renewal: false}}` response goes through
|
|
215
|
+
// dispatchActionError below instead, never both.
|
|
216
|
+
this.dispatchEvent(
|
|
217
|
+
new CustomEvent('loanAutoRenewed', {
|
|
218
|
+
detail: { action, data: { ...data, loan: activeLoan } },
|
|
219
|
+
})
|
|
220
|
+
);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
201
224
|
log('RENEW_LOAN ERROR --- ', {
|
|
202
225
|
action,
|
|
203
226
|
isRenewal,
|
|
@@ -213,14 +236,17 @@ export default class ActionsHandler extends LitElement {
|
|
|
213
236
|
error: true,
|
|
214
237
|
message: 'Loan renewal failed: no loan active.',
|
|
215
238
|
});
|
|
239
|
+
} catch (error) {
|
|
240
|
+
log('RENEW_LOAN THREW --- ', error);
|
|
241
|
+
window?.Sentry?.captureException(
|
|
242
|
+
`${sentryLogs.bookRenewFailed} - Exception: ${error}`
|
|
243
|
+
);
|
|
244
|
+
this.dispatchActionError(action, {
|
|
245
|
+
data,
|
|
246
|
+
error: true,
|
|
247
|
+
message: `Loan renewal failed: ${error}`,
|
|
248
|
+
});
|
|
216
249
|
}
|
|
217
|
-
|
|
218
|
-
// dispatch outcome of loan renewal
|
|
219
|
-
this.dispatchEvent(
|
|
220
|
-
new CustomEvent('loanAutoRenewed', {
|
|
221
|
-
detail: { action, data: { ...data, loan: activeLoan } },
|
|
222
|
-
})
|
|
223
|
-
);
|
|
224
250
|
},
|
|
225
251
|
error: data => {
|
|
226
252
|
this.dispatchActionError(action, data);
|
|
@@ -372,6 +398,12 @@ export default class ActionsHandler extends LitElement {
|
|
|
372
398
|
new Date().getTime() + this.loanTotalTime * 1000
|
|
373
399
|
);
|
|
374
400
|
|
|
401
|
+
log('[ActionsHandler] setBrowseTimeSession: resetting loanTime', {
|
|
402
|
+
identifier: this.identifier,
|
|
403
|
+
expireDate,
|
|
404
|
+
loanTotalTime: this.loanTotalTime,
|
|
405
|
+
});
|
|
406
|
+
|
|
375
407
|
// set a value
|
|
376
408
|
await this.localCache.set({
|
|
377
409
|
key: `${this.identifier}-loanTime`,
|
|
@@ -381,12 +413,20 @@ export default class ActionsHandler extends LitElement {
|
|
|
381
413
|
|
|
382
414
|
// delete pageChangedTime when book is auto renew at nth minute
|
|
383
415
|
await this.localCache.delete(`${this.identifier}-pageChangedTime`);
|
|
416
|
+
|
|
417
|
+
log('[ActionsHandler] setBrowseTimeSession: loanTime reset complete', {
|
|
418
|
+
identifier: this.identifier,
|
|
419
|
+
});
|
|
384
420
|
} catch (error) {
|
|
385
|
-
log(error);
|
|
421
|
+
log('[ActionsHandler] setBrowseTimeSession failed', error);
|
|
386
422
|
}
|
|
387
423
|
}
|
|
388
424
|
|
|
389
425
|
deleteLoanCookies() {
|
|
426
|
+
log('[ActionsHandler] deleteLoanCookies: expiring loan cookies', {
|
|
427
|
+
identifier: this.identifier,
|
|
428
|
+
});
|
|
429
|
+
|
|
390
430
|
const date = new Date();
|
|
391
431
|
date.setTime(date.getTime() - 24 * 60 * 60 * 1000); // one day ago
|
|
392
432
|
|
|
@@ -414,7 +454,8 @@ export default class ActionsHandler extends LitElement {
|
|
|
414
454
|
* @returns {void}
|
|
415
455
|
*/
|
|
416
456
|
setStickyAdminAccess(value) {
|
|
417
|
-
const domain =
|
|
457
|
+
const domain =
|
|
458
|
+
window.location.hostname === 'localhost' ? 'localhost' : '.archive.org';
|
|
418
459
|
const expires = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days from now
|
|
419
460
|
Cookies.setItem('sticky-admin-access', value, expires, '/', domain);
|
|
420
461
|
}
|
|
@@ -159,17 +159,6 @@ export default class LoanAnanlytics {
|
|
|
159
159
|
* @memberof LoanAnanlytics
|
|
160
160
|
*/
|
|
161
161
|
sendEvent(eventCategory, eventAction, label, extraParams) {
|
|
162
|
-
log(
|
|
163
|
-
'eventCategory:-',
|
|
164
|
-
eventCategory,
|
|
165
|
-
'||\teventAction:-',
|
|
166
|
-
eventAction,
|
|
167
|
-
'||\tlabel:-',
|
|
168
|
-
label,
|
|
169
|
-
'||\textraParams:-',
|
|
170
|
-
extraParams,
|
|
171
|
-
);
|
|
172
|
-
|
|
173
162
|
window?.archive_analytics?.send_event_no_sampling(
|
|
174
163
|
eventCategory,
|
|
175
164
|
eventAction,
|
|
@@ -26,7 +26,7 @@ export class LoanRenewHelper {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
handleLoanRenew() {
|
|
30
30
|
try {
|
|
31
31
|
if (this.hasPageChanged) {
|
|
32
32
|
return this.pageChanged(); // user clicked on page
|
|
@@ -99,7 +99,7 @@ export class LoanRenewHelper {
|
|
|
99
99
|
} else if (pageChangedTime >= pageChangeTimeFrame) {
|
|
100
100
|
this.result = {
|
|
101
101
|
texts: '',
|
|
102
|
-
renewNow: true,
|
|
102
|
+
renewNow: true, // viewed in last time frame
|
|
103
103
|
renewType: 'auto',
|
|
104
104
|
};
|
|
105
105
|
}
|
|
@@ -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
|
|
@@ -9,12 +10,39 @@ import { sentryLogs } from '../config/sentry-events.js';
|
|
|
9
10
|
* ActionsHandlerService is a function being used to execute
|
|
10
11
|
*/
|
|
11
12
|
export class LoanTokenPoller {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* @param {Object} options
|
|
15
|
+
* @param {string} options.identifier
|
|
16
|
+
* @param {string} options.borrowType
|
|
17
|
+
* @param {Function} options.successCallback - called after the initial
|
|
18
|
+
* loan token is created.
|
|
19
|
+
* @param {Function} options.errorCallback - called on any create_token
|
|
20
|
+
* failure (initial or a routine interval tick).
|
|
21
|
+
* @param {number} options.pollerDelay - interval between routine
|
|
22
|
+
* create_token checks, in seconds.
|
|
23
|
+
* @param {boolean} [options.skipInitialCall] - skip the immediate
|
|
24
|
+
* create_token call, only start the recurring interval. Used after a
|
|
25
|
+
* loan-expiry recovery renewal, where BookLoanService::
|
|
26
|
+
* attempt_to_renew_loan() already minted a valid access token as part
|
|
27
|
+
* of the renew_loan response itself — an immediate confirming
|
|
28
|
+
* create_token call is redundant.
|
|
29
|
+
*/
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
const {
|
|
32
|
+
identifier,
|
|
33
|
+
borrowType,
|
|
34
|
+
successCallback,
|
|
35
|
+
errorCallback,
|
|
36
|
+
pollerDelay,
|
|
37
|
+
skipInitialCall = false,
|
|
38
|
+
} = options;
|
|
39
|
+
|
|
40
|
+
this.identifier = identifier;
|
|
14
41
|
this.borrowType = borrowType;
|
|
15
42
|
this.successCallback = successCallback; // callback function to be called after loan token is created
|
|
16
43
|
this.errorCallback = errorCallback; // callback function to be called after loan token is created
|
|
17
44
|
this.pollerDelay = pollerDelay; // value in seconds
|
|
45
|
+
this.skipInitialCall = skipInitialCall === true;
|
|
18
46
|
|
|
19
47
|
this.loanTokenInterval = undefined;
|
|
20
48
|
|
|
@@ -33,8 +61,14 @@ export class LoanTokenPoller {
|
|
|
33
61
|
|
|
34
62
|
async bookAccessed() {
|
|
35
63
|
if (this.borrowType) {
|
|
36
|
-
|
|
37
|
-
|
|
64
|
+
if (this.skipInitialCall) {
|
|
65
|
+
log('[LoanTokenPoller] skipping initial create_token — already minted by the renewal response', {
|
|
66
|
+
identifier: this.identifier,
|
|
67
|
+
});
|
|
68
|
+
} else {
|
|
69
|
+
// Do an initial token, then set an interval
|
|
70
|
+
this.handleLoanTokenPoller(true);
|
|
71
|
+
}
|
|
38
72
|
|
|
39
73
|
// if this.borrowType = adminBorrowed,
|
|
40
74
|
// - we don't want to fetch token on interval
|
|
@@ -58,29 +92,63 @@ export class LoanTokenPoller {
|
|
|
58
92
|
}
|
|
59
93
|
}
|
|
60
94
|
|
|
95
|
+
/**
|
|
96
|
+
* @param {boolean} isInitial - the first create_token call right after
|
|
97
|
+
* bookAccessed()/a renewal, as opposed to a routine interval tick.
|
|
98
|
+
*/
|
|
61
99
|
async handleLoanTokenPoller(isInitial = false) {
|
|
62
100
|
const action = 'create_token';
|
|
101
|
+
log('[LoanTokenPoller] create_token requested', {
|
|
102
|
+
identifier: this.identifier,
|
|
103
|
+
isInitial,
|
|
104
|
+
});
|
|
63
105
|
ActionsHandlerService({
|
|
64
106
|
identifier: this.identifier,
|
|
65
107
|
action,
|
|
66
|
-
error: data =>
|
|
67
|
-
this.errorCallback({ detail: { action, data } });
|
|
68
|
-
|
|
69
|
-
// send error to Sentry
|
|
70
|
-
window?.Sentry?.captureMessage(
|
|
71
|
-
`${sentryLogs.handleLoanTokenPoller} - Error: ${JSON.stringify(data)}`
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
// send LendingServiceError to GA
|
|
75
|
-
this.loanAnalytics?.sendEvent(
|
|
76
|
-
'LendingServiceLoanError',
|
|
77
|
-
action,
|
|
78
|
-
this.identifier
|
|
79
|
-
);
|
|
80
|
-
},
|
|
108
|
+
error: data => this.handleTokenError(data, isInitial),
|
|
81
109
|
success: () => {
|
|
110
|
+
log('[LoanTokenPoller] create_token succeeded', {
|
|
111
|
+
identifier: this.identifier,
|
|
112
|
+
isInitial,
|
|
113
|
+
});
|
|
82
114
|
if (isInitial) this.successCallback();
|
|
83
115
|
},
|
|
84
116
|
});
|
|
85
117
|
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Reports a create_token failure via errorCallback — no retry, it's
|
|
121
|
+
* treated as terminal on the first failure. Split out from
|
|
122
|
+
* handleLoanTokenPoller so it's directly testable without needing to
|
|
123
|
+
* mock the network call.
|
|
124
|
+
*
|
|
125
|
+
* @param {Object} data - the error payload from ActionsHandlerService.
|
|
126
|
+
* @param {boolean} isInitial
|
|
127
|
+
*/
|
|
128
|
+
handleTokenError(data, isInitial) {
|
|
129
|
+
const action = 'create_token';
|
|
130
|
+
|
|
131
|
+
log('[LoanTokenPoller] create_token failed', {
|
|
132
|
+
identifier: this.identifier,
|
|
133
|
+
isInitial,
|
|
134
|
+
error: data?.error,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// isInitial rides along so the consumer can distinguish "the book won't
|
|
138
|
+
// open at all" from "a mid-read refresh blipped" — see
|
|
139
|
+
// IABookActions.handleLendingActionError.
|
|
140
|
+
this.errorCallback({ detail: { action, data, isInitial } });
|
|
141
|
+
|
|
142
|
+
// send error to Sentry
|
|
143
|
+
window?.Sentry?.captureMessage(
|
|
144
|
+
`${sentryLogs.handleLoanTokenPoller} - Error: ${JSON.stringify(data)}`
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// send LendingServiceError to GA
|
|
148
|
+
this.loanAnalytics?.sendEvent(
|
|
149
|
+
'LendingServiceLoanError',
|
|
150
|
+
action,
|
|
151
|
+
this.identifier
|
|
152
|
+
);
|
|
153
|
+
}
|
|
86
154
|
}
|