@internetarchive/bookreader 5.0.0-48 → 5.0.0-48-alpha0

Sign up to get free protection for your applications and to get access to all the features.
@@ -82,8 +82,6 @@ const initializeBookReader = (brManifest) => {
82
82
  // we expect this at the global level
83
83
  BookReaderJSIAinit(brManifest.data, options);
84
84
 
85
- const isRestricted = brManifest.data.isRestricted;
86
- window.dispatchEvent(new CustomEvent('contextmenu', { detail: { isRestricted } }));
87
85
  if (customAutoflipParams.autoflip) {
88
86
  br.autoToggle(customAutoflipParams);
89
87
  }
package/CHANGELOG.md CHANGED
@@ -1,7 +1,3 @@
1
- # 5.0.0-48
2
- - Fix: move analytics to sample bucket @iisa
3
- - Dev: update dependencies (concurrently, jest) @renovate
4
-
5
1
  # 5.0.0-47
6
2
  - Fix: XSS vulnerability in search results @latonv
7
3
  - Dev: Update jQuery to v3 **BREAKING** @cdrini
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-48",
3
+ "version": "5.0.0-48-alpha0",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -449,10 +449,10 @@ export class BookNavigator extends LitElement {
449
449
  /** Display an element's context menu */
450
450
  manageContextMenuVisibility(e) {
451
451
  if (window.archive_analytics) {
452
- window.archive_analytics?.send_event(
452
+ window.archive_analytics?.send_event_no_sampling(
453
453
  'BookReader',
454
454
  `contextmenu-${this.bookIsRestricted ? 'restricted' : 'unrestricted'}`,
455
- e.target?.classList?.value
455
+ e.target.classList.value
456
456
  );
457
457
  }
458
458
  if (!this.bookIsRestricted) {
@@ -2,41 +2,11 @@ import { html } from 'lit';
2
2
  import '@internetarchive/icon-dl/icon-dl';
3
3
  import './downloads';
4
4
 
5
- const menuBase = {
6
- pdf: {
7
- type: 'Encrypted Adobe PDF',
8
- url: '#',
9
- note: 'PDF files contain high quality images of pages.',
10
- },
11
- lcppdf: {
12
- type: 'Get LCP PDF',
13
- url: '#',
14
- note: 'PDF files contain high quality images of pages.',
15
- },
16
- lcpepub: {
17
- type: 'Get LCP ePub',
18
- url: '#',
19
- note: 'ePub files are smaller in size, but may contain errors.',
20
- },
21
- epub: {
22
- type: 'Encrypted Adobe ePub',
23
- url: '#',
24
- note: 'ePub files are smaller in size, but may contain errors.',
25
- },
26
- };
27
-
28
- const publicMenuBase = {
29
- pdf: "PDF",
30
- epub: "ePub",
31
- lcppdf: "LCP PDF",
32
- lcpepub: "LCP ePub",
33
- };
34
-
35
5
  export default class DownloadsProvider {
36
6
 
37
7
  constructor({ bookreader }) {
38
8
  this.icon = html`<ia-icon-dl style="width: var(--iconWidth); height: var(--iconHeight);"></ia-icon-dl>`;
39
- this.label = 'Downloadable files';
9
+ this.label = 'Read offline';
40
10
  this.menuDetails = '';
41
11
  this.downloads = [];
42
12
  this.id = 'downloads';
@@ -54,23 +24,21 @@ export default class DownloadsProvider {
54
24
  }
55
25
 
56
26
  /**
57
- * Generates Download Menu Info for available types
58
- * sets global `downloads`
27
+ * Restructures available download type data for the renderer
28
+ * Sets global `downloads`
59
29
  * @param availableTypes
60
30
  */
61
31
  computeAvailableTypes(availableTypes = []) {
62
32
  const menuData = availableTypes.reduce((found, incoming = []) => {
63
33
  const [ type = '', link = '' ] = incoming;
64
- const formattedType = type.toLowerCase();
65
- const downloadOption = menuBase[formattedType] || null;
66
-
67
- if (downloadOption) {
68
- const menuButtonText = this.isBookProtected ? menuBase[formattedType].type : publicMenuBase[formattedType];
69
- const menuInfo = Object.assign({}, downloadOption, { url: link, type: menuButtonText});
70
- found.push(menuInfo);
34
+ if (!type) return found;
35
+ let formattedType = type.toLowerCase();
36
+ if ((formattedType === 'pdf' || formattedType === 'epub') && this.isbookProtected) {
37
+ formattedType = `adobe${formattedType}`;
71
38
  }
39
+ found[formattedType] = link;
72
40
  return found;
73
- }, []);
41
+ }, {});
74
42
 
75
43
  this.downloads = menuData;
76
44
  }
@@ -1,5 +1,6 @@
1
1
  import { css, html, LitElement, nothing } from 'lit';
2
2
  import buttonStyles from '../assets/button-base.js';
3
+
3
4
  export class IABookDownloads extends LitElement {
4
5
  static get properties() {
5
6
  return {
@@ -18,6 +19,26 @@ export class IABookDownloads extends LitElement {
18
19
  this.isBookProtected = false;
19
20
  }
20
21
 
22
+ render() {
23
+ return html`
24
+ ${this.header}
25
+ ${this.loanExpiryMessage}
26
+ ${this.renderDownloadOptions()}
27
+ `;
28
+ }
29
+
30
+ get header() {
31
+ if (!this.renderHeader) {
32
+ return nothing;
33
+ }
34
+ return html`
35
+ <header>
36
+ <h3>Read offline</h3>
37
+ ${this.formatsCount}
38
+ </header>
39
+ `;
40
+ }
41
+
21
42
  get formatsCount() {
22
43
  const count = this.downloads.length;
23
44
  return count ? html`<p>${count} format${count > 1 ? 's' : ''}</p>` : html``;
@@ -30,65 +51,80 @@ export class IABookDownloads extends LitElement {
30
51
  }
31
52
 
32
53
  renderDownloadOptions() {
33
- return this.downloads.map(option => (
34
- html`
35
- <li>
36
- <a class="ia-button link primary" href="${option.url}">Get ${option.type}</a>
37
- ${option.note ? html`<p>${option.note}</p>` : html``}
38
- </li>
39
- `
40
- ));
41
- }
42
-
43
- /**
44
- * checks if downloads list contains an LCP option
45
- * @return {boolean}
46
- */
47
- get hasLCPOption() {
48
- const regex = /^(LCP)/g;
49
- const lcpAvailable = this.downloads.some(option => option.type?.match(regex));
50
- return lcpAvailable;
51
- }
52
-
53
- get header() {
54
- if (!this.renderHeader) {
55
- return nothing;
56
- }
54
+ const downloadOptions = [];
55
+ ['pdf', 'epub', 'lcppdf', 'lcpepub', 'adobepdf', 'adobeepub'].forEach(format => {
56
+ if (this.downloads[format]) {
57
+ downloadOptions.push(this.downloadOption(format, this.downloads[format]));
58
+ }
59
+ });
57
60
  return html`
58
- <header>
59
- <h3>Downloadable files</h3>
60
- ${this.formatsCount}
61
- </header>
61
+ <ul>
62
+ ${downloadOptions}
63
+ </ul>
62
64
  `;
63
65
  }
64
66
 
65
- get accessProtectedBook() {
67
+ downloadOption(format, link) {
68
+ if (/^adobe/.test(format)) {
69
+ return html`
70
+ <li>
71
+ <a
72
+ href="${link}"
73
+ data-event-click-tracking="BookReader|Download-${format}"
74
+ >${this.menuText[format].linkText}</a>
75
+ ${this.menuText[format].message ?? nothing}
76
+ </li>
77
+ `;
78
+ }
66
79
  return html`
67
- <p>To access downloaded books, you need Adobe-compliant software on your device. The Internet Archive will administer this loan, but Adobe may also collect some information.</p>
68
- <a class="ia-button external primary" href="https://www.adobe.com/solutions/ebook/digital-editions/download.html" rel="noopener noreferrer" target="_blank">Install Adobe Digital Editions</a>
80
+ <li>
81
+ <a class="ia-button link primary"
82
+ href="${link}"
83
+ data-event-click-tracking="BookReader|Download-${format}"
84
+ >${this.menuText[format].linkText}</a>
85
+ ${this.menuText[format].message ? html`<p>${this.menuText[format].message}</p>` : nothing}
86
+ </li>
69
87
  `;
70
88
  }
71
89
 
72
- get installSimplyEAldikoThoriumMsg() {
90
+ get menuText() {
91
+ return {
92
+ pdf: {
93
+ linkText: 'Get high-resolution PDF',
94
+ message: html``,
95
+ },
96
+ epub: {
97
+ linkText: 'Get text-based ebook',
98
+ message: html`Smaller size. May contain some errors.`,
99
+ },
100
+ lcppdf: {
101
+ linkText: 'Get high-resolution PDF',
102
+ message: this.lcpNote,
103
+ },
104
+ lcpepub: {
105
+ linkText: 'Get text-based ebook',
106
+ message: html`Smaller size. May contain some errors. ${this.lcpNote}`,
107
+ },
108
+ adobepdf: {
109
+ linkText:'Get legacy Adobe DRM PDF version here.',
110
+ message: this.adobeNote,
111
+ },
112
+ adobeepub: {
113
+ linkText: 'Get legacy Adobe DRM EPUB version here.',
114
+ message: this.adobeNote,
115
+ },
116
+ };
117
+ };
118
+
119
+ get lcpNote() {
73
120
  return html`
74
- <p>For LCP downloads, make sure you have SimplyE or Aldiko Next installed on mobile or Thorium on desktop.</p>
75
- <ul>
76
- <li><a href="https://librarysimplified.org/simplye/" rel="noopener noreferrer nofollow" target="_blank">Install SimplyE</a></li>
77
- <li><a href="https://www.demarque.com/en-aldiko" rel="noopener noreferrer nofollow" target="_blank">Install Aldiko</a></li>
78
- <li><a href="https://www.edrlab.org/software/thorium-reader/" rel="noopener noreferrer nofollow" target="_blank">Install Thorium</a></li>
79
- </ul>
80
- `;
121
+ Requires having an LCP-compatible e-reader installed like Aldiko Next (<a href="https://apps.apple.com/us/app/aldiko-next/id1476410111">iOS</a>, <a href="https://play.google.com/store/apps/details?id=com.aldiko.android">Android</a>) or <a href="https://www.edrlab.org/software/thorium-reader/">Thorium</a>.
122
+ `;
81
123
  }
82
124
 
83
- render() {
125
+ get adobeNote() {
84
126
  return html`
85
- ${this.header}
86
- ${this.loanExpiryMessage}
87
- <ul>${this.renderDownloadOptions()}</ul>
88
- ${this.hasLCPOption
89
- ? this.installSimplyEAldikoThoriumMsg
90
- : (this.isBookProtected ? this.accessProtectedBook : nothing)
91
- }
127
+ Requires a compatible e-reader like <a hef="https://www.adobe.com/solutions/ebook/digital-editions.html">Adobe Digital Editions</a>.
92
128
  `;
93
129
  }
94
130
 
@@ -104,7 +104,7 @@ export default class VolumesProvider {
104
104
  if (!window.archive_analytics) {
105
105
  return;
106
106
  }
107
- window.archive_analytics?.send_event(
107
+ window.archive_analytics?.send_event_no_sampling(
108
108
  'BookReader',
109
109
  `VolumesSort|${orderBy}`,
110
110
  window.location.path,
@@ -46,13 +46,6 @@ window.ResizeObserver = class ResizeObserver {
46
46
  disconnect = sinon.fake()
47
47
  };
48
48
 
49
- beforeEach(() => {
50
- window.archive_analytics = {
51
- send_event_no_sampling: sinon.fake(),
52
- send_event: sinon.fake()
53
- };
54
- });
55
-
56
49
  afterEach(() => {
57
50
  window.br = null;
58
51
  fixtureCleanup();
@@ -523,6 +516,8 @@ describe('<book-navigator>', () => {
523
516
  describe('Handles Restricted Books', () => {
524
517
  describe('contextMenu is prevented when book is restricted', () => {
525
518
  it('watches on `div.BRscreen`', async () => {
519
+ window.archive_analytics = { send_event_no_sampling: sinon.fake() };
520
+
526
521
  const el = fixtureSync(container());
527
522
  const brStub = {
528
523
  options: { restricted: true },
@@ -534,8 +529,9 @@ describe('<book-navigator>', () => {
534
529
  const elSpy = sinon.spy(el.manageContextMenuVisibility);
535
530
  await el.elementUpdated;
536
531
 
537
- expect(window.archive_analytics.send_event_no_sampling.called).toEqual(false);
538
- expect(window.archive_analytics.send_event.called).toEqual(false);
532
+ expect(window.archive_analytics.send_event_no_sampling.called).toEqual(
533
+ false
534
+ );
539
535
  expect(elSpy.called).toEqual(false);
540
536
 
541
537
  const body = document.querySelector('body');
@@ -554,13 +550,14 @@ describe('<book-navigator>', () => {
554
550
 
555
551
  // analytics fires
556
552
  expect(window.archive_analytics.send_event_no_sampling.called).toEqual(
557
- false
553
+ true
558
554
  );
559
- expect(window.archive_analytics.send_event.called).toEqual(true);
560
555
  // we prevent default
561
556
  expect(preventDefaultSpy.called).toEqual(true);
562
557
  });
563
558
  it('watches on `img.BRpageimage`', async () => {
559
+ window.archive_analytics = { send_event_no_sampling: sinon.fake() };
560
+
564
561
  const el = fixtureSync(container());
565
562
  const brStub = {
566
563
  options: { restricted: true },
@@ -571,8 +568,9 @@ describe('<book-navigator>', () => {
571
568
 
572
569
  await el.elementUpdated;
573
570
 
574
- expect(window.archive_analytics.send_event_no_sampling.called).toEqual(false);
575
- expect(window.archive_analytics.send_event.called).toEqual(false);
571
+ expect(window.archive_analytics.send_event_no_sampling.called).toEqual(
572
+ false
573
+ );
576
574
 
577
575
  const body = document.querySelector('body');
578
576
  // const element stub for img.BRpageimage
@@ -588,13 +586,14 @@ describe('<book-navigator>', () => {
588
586
  imgBRpageimage.dispatchEvent(contextMenuEvent);
589
587
 
590
588
  // analytics fires
591
- expect(window.archive_analytics.send_event_no_sampling.called).toEqual(false);
592
- expect(window.archive_analytics.send_event.called).toEqual(true);
589
+ expect(window.archive_analytics.send_event_no_sampling.called).toEqual(true);
593
590
  // we prevent default
594
591
  expect(preventDefaultSpy.called).toEqual(true);
595
592
  });
596
593
  });
597
594
  it('Allows unrestricted books access to context menu', async () => {
595
+ window.archive_analytics = { send_event_no_sampling: sinon.fake() };
596
+
598
597
  const el = fixtureSync(container());
599
598
  const brStub = {
600
599
  options: { restricted: false },
@@ -608,8 +607,6 @@ describe('<book-navigator>', () => {
608
607
  expect(window.archive_analytics.send_event_no_sampling.called).toEqual(
609
608
  false
610
609
  );
611
- expect(window.archive_analytics.send_event.called).toEqual(false);
612
-
613
610
 
614
611
  const body = document.querySelector('body');
615
612
  // const element stub for img.BRpageimage
@@ -625,8 +622,7 @@ describe('<book-navigator>', () => {
625
622
  imgBRpageimage.dispatchEvent(contextMenuEvent);
626
623
 
627
624
  // analytics fires
628
- expect(window.archive_analytics.send_event_no_sampling.called).toEqual(false);
629
- expect(window.archive_analytics.send_event.called).toEqual(true);
625
+ expect(window.archive_analytics.send_event_no_sampling.called).toEqual(true);
630
626
  // we do not prevent default
631
627
  expect(preventDefaultSpy.called).toEqual(false);
632
628
  });
File without changes