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

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.
@@ -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-alpha1",
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