@internetarchive/bookreader 5.0.0-54 → 5.0.0-56

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/src/BookReader.js CHANGED
@@ -31,7 +31,7 @@ import PACKAGE_JSON from '../package.json';
31
31
  import * as utils from './BookReader/utils.js';
32
32
  import { exposeOverrideable } from './BookReader/utils/classes.js';
33
33
  import { Navbar } from './BookReader/Navbar/Navbar.js';
34
- import { DEFAULT_OPTIONS } from './BookReader/options.js';
34
+ import { DEFAULT_OPTIONS, OptionsParseError } from './BookReader/options.js';
35
35
  /** @typedef {import('./BookReader/options.js').BookReaderOptions} BookReaderOptions */
36
36
  /** @typedef {import('./BookReader/options.js').ReductionFactor} ReductionFactor */
37
37
  /** @typedef {import('./BookReader/BookModel.js').PageIndex} PageIndex */
@@ -452,61 +452,56 @@ BookReader.prototype.readQueryString = function() {
452
452
  * Determines the initial mode for starting if a mode is not already
453
453
  * present in the params argument
454
454
  * @param {object} params
455
- * @return {number} the mode
455
+ * @return {1 | 2 | 3} the initial mode
456
456
  */
457
457
  BookReader.prototype.getInitialMode = function(params) {
458
- let nextMode;
459
-
460
458
  // if mobile breakpoint, we always show this.constMode1up mode
461
- const ifMobileBreakpoint = () => {
462
- // Use params or browser width to set view mode
463
- const windowWidth = $(window).width();
464
- return windowWidth && windowWidth <= this.onePageMinBreakpoint;
465
- };
466
- if ('undefined' != typeof(params.mode)) {
467
- nextMode = params.mode;
468
- } else if ((this.ui == 'full' && this.isFullscreenActive) || ifMobileBreakpoint()) {
469
- // In full mode OR device width, we set the default based on width
470
- nextMode = this.constMode1up;
459
+ const windowWidth = $(window).width();
460
+ const isMobile = windowWidth && windowWidth <= this.onePageMinBreakpoint;
461
+
462
+ let initialMode;
463
+ if (params.mode) {
464
+ initialMode = params.mode;
465
+ } else if (isMobile) {
466
+ initialMode = this.constMode1up;
471
467
  } else {
472
- nextMode = this.constMode2up;
468
+ initialMode = this.constMode2up;
473
469
  }
474
470
 
475
- if (!this.canSwitchToMode(nextMode)) {
476
- nextMode = this.constMode1up;
471
+ if (!this.canSwitchToMode(initialMode)) {
472
+ initialMode = this.constMode1up;
477
473
  }
478
474
 
479
475
  // override defaults mode via `options.defaults` metadata
480
476
  if (this.options.defaults) {
481
- nextMode = this.overridesBookMode();
477
+ try {
478
+ initialMode = _modeStringToNumber(this.options.defaults);
479
+ } catch (e) {
480
+ // Can ignore this error
481
+ }
482
482
  }
483
483
 
484
- return nextMode;
484
+ return initialMode;
485
485
  };
486
486
 
487
487
  /**
488
- * Overrides book mode using options.defaults param
489
- * @return {number} the mode
488
+ * Converts a mode string to a the mode numeric constant
489
+ * @param {'mode/1up'|'mode/2up'|'mode/thumb'} modeString
490
+ * @return {1 | 2 | 3}
490
491
  */
491
- BookReader.prototype.overridesBookMode = function() {
492
- let nextMode = 2; // set default 2 (mode/2up)
492
+ export function _modeStringToNumber(modeString) {
493
+ const MAPPING = {
494
+ 'mode/1up': 1,
495
+ 'mode/2up': 2,
496
+ 'mode/thumb': 3,
497
+ };
493
498
 
494
- switch (this.options.defaults) {
495
- case 'mode/1up':
496
- nextMode = this.constMode1up;
497
- break;
498
- case 'mode/2up':
499
- nextMode = this.constMode2up;
500
- break;
501
- case 'mode/thumb':
502
- nextMode = this.constModeThumb;
503
- break;
504
- default:
505
- break;
499
+ if (!(modeString in MAPPING)) {
500
+ throw new OptionsParseError(`Invalid mode string: ${modeString}`);
506
501
  }
507
502
 
508
- return nextMode;
509
- };
503
+ return MAPPING[modeString];
504
+ }
510
505
 
511
506
  /**
512
507
  * This is called by the client to initialize BookReader.
@@ -37,7 +37,7 @@
37
37
  }
38
38
 
39
39
  .icon-fullscreen-exit {
40
- background-image: url("icons/fullscreen-exit.svg");
40
+ background-image: url("icons/fullscreen_exit.svg");
41
41
  }
42
42
 
43
43
  .icon-thumb {
@@ -139,11 +139,20 @@ export class UrlPlugin {
139
139
  const urlStrPath = this.urlStateToUrlString(this.urlState);
140
140
  const concatenatedPath = urlStrPath !== '/' ? urlStrPath : '';
141
141
  if (this.urlMode == 'history') {
142
- if (window.history && window.history.replaceState) {
142
+ if (!window.history || !window.history.replaceState) {
143
+ this.options.urlMode = 'hash';
144
+ } else {
143
145
  const newUrlPath = `${this.urlHistoryBasePath}${concatenatedPath}`.trim().replace(/(\/+)/g, '/');
144
- window.history.replaceState({}, null, newUrlPath);
146
+ try {
147
+ window.history.replaceState({}, null, newUrlPath);
148
+ } catch (e) {
149
+ // DOMException on Chrome when in sandboxed iframe
150
+ this.urlMode = 'hash';
151
+ }
145
152
  }
146
- } else {
153
+ }
154
+
155
+ if (this.urlMode == 'hash') {
147
156
  window.location.replace('#' + concatenatedPath);
148
157
  }
149
158
  this.oldLocationHash = urlStrPath;
@@ -124,7 +124,7 @@ BookReader.prototype.urlStartLocationPolling = function() {
124
124
  */
125
125
  BookReader.prototype.urlUpdateFragment = function() {
126
126
  const allParams = this.paramsFromCurrent();
127
- const { urlMode, urlTrackIndex0, urlTrackedParams } = this.options;
127
+ const { urlTrackIndex0, urlTrackedParams } = this.options;
128
128
 
129
129
  if (!urlTrackIndex0
130
130
  && (typeof(allParams.index) !== 'undefined')
@@ -140,29 +140,36 @@ BookReader.prototype.urlUpdateFragment = function() {
140
140
  return validParams;
141
141
  }, {});
142
142
 
143
- const newFragment = this.fragmentFromParams(params, urlMode);
143
+ const newFragment = this.fragmentFromParams(params, this.options.urlMode);
144
144
  const currFragment = this.urlReadFragment();
145
145
  const currQueryString = this.getLocationSearch();
146
- const newQueryString = this.queryStringFromParams(params, currQueryString, urlMode);
146
+ const newQueryString = this.queryStringFromParams(params, currQueryString, this.options.urlMode);
147
147
  if (currFragment === newFragment && currQueryString === newQueryString) {
148
148
  return;
149
149
  }
150
150
 
151
- if (urlMode === 'history') {
152
- if (window.history && window.history.replaceState) {
151
+ if (this.options.urlMode === 'history') {
152
+ if (!window.history || !window.history.replaceState) {
153
+ this.options.urlMode = 'hash';
154
+ } else {
153
155
  const baseWithoutSlash = this.options.urlHistoryBasePath.replace(/\/+$/, '');
154
156
  const newFragmentWithSlash = newFragment === '' ? '' : `/${newFragment}`;
155
157
 
156
158
  const newUrlPath = `${baseWithoutSlash}${newFragmentWithSlash}${newQueryString}`;
157
- window.history.replaceState({}, null, newUrlPath);
158
- this.oldLocationHash = newFragment + newQueryString;
159
-
159
+ try {
160
+ window.history.replaceState({}, null, newUrlPath);
161
+ this.oldLocationHash = newFragment + newQueryString;
162
+ } catch (e) {
163
+ // DOMException on Chrome when in sandboxed iframe
164
+ this.options.urlMode = 'hash';
165
+ }
160
166
  }
161
- } else {
167
+ }
168
+
169
+ if (this.options.urlMode === 'hash') {
162
170
  const newQueryStringSearch = this.urlParamsFiltersOnlySearch(this.readQueryString());
163
171
  window.location.replace('#' + newFragment + newQueryStringSearch);
164
172
  this.oldLocationHash = newFragment + newQueryStringSearch;
165
-
166
173
  }
167
174
  };
168
175
 
@@ -9,6 +9,22 @@
9
9
  * http://www.gnu.org/licenses/gpl-3.0-standalone.html
10
10
  */
11
11
 
12
+ /**
13
+ * Check to see if the browser has cookies enabled.
14
+ * Accessing document.cookies errors if eg iframe with sandbox enabled.
15
+ * @returns {boolean}
16
+ */
17
+ export function areCookiesBlocked(doc = document) {
18
+ try {
19
+ doc.cookie;
20
+ return false;
21
+ } catch (e) {
22
+ return true;
23
+ }
24
+ }
25
+
26
+ const COOKIES_BLOCKED = areCookiesBlocked();
27
+
12
28
  /**
13
29
  * Get specific key's value stored in cookie
14
30
  *
@@ -17,7 +33,7 @@
17
33
  * @returns {string|null}
18
34
  */
19
35
  export function getItem(sKey) {
20
- if (!sKey) return null;
36
+ if (COOKIES_BLOCKED || !sKey) return null;
21
37
 
22
38
  return decodeURIComponent(
23
39
  // eslint-disable-next-line no-useless-escape
@@ -34,9 +50,11 @@ export function getItem(sKey) {
34
50
  * @param {string} [sDomain] domain name
35
51
  * @param {boolean} [bSecure]
36
52
  *
37
- * @returns {true}
53
+ * @returns {boolean}
38
54
  */
39
55
  export function setItem(sKey, sValue, vEnd, sPath, sDomain, bSecure) {
56
+ if (COOKIES_BLOCKED) return false;
57
+
40
58
  document.cookie = encodeURIComponent(sKey) + '=' + encodeURIComponent(sValue)
41
59
  + (vEnd ? `; expires=${vEnd.toUTCString()}` : '')
42
60
  + (sDomain ? `; domain=${sDomain}` : '')
@@ -56,6 +74,7 @@ export function setItem(sKey, sValue, vEnd, sPath, sDomain, bSecure) {
56
74
  * @returns {boolean}
57
75
  */
58
76
  export function removeItem(sKey, sPath, sDomain) {
77
+ if (COOKIES_BLOCKED) return false;
59
78
  // eslint-disable-next-line
60
79
  if (!hasItem(sKey)) return false;
61
80
 
@@ -1,5 +1,5 @@
1
1
 
2
- import BookReader from '@/src/BookReader.js';
2
+ import BookReader, {_modeStringToNumber} from '@/src/BookReader.js';
3
3
  import '@/src/plugins/plugin.resume.js';
4
4
  import '@/src/plugins/url/plugin.url.js';
5
5
 
@@ -279,35 +279,12 @@ describe('nextReduce', () => {
279
279
  expect(nextReduce(2, 'auto', SAMPLE_FACTORS).reduce).toBe(0.5);
280
280
  });
281
281
  });
282
+ });
282
283
 
283
- describe('Override book page mode using options.default param', () => {
284
- test('replace current mode with options.default is set mode/1up', () => {
285
- br.options.defaults = 'mode/1up';
286
-
287
- const nextModeNumber = br.overridesBookMode();
288
- expect(nextModeNumber).toBe(1);
289
- });
290
-
291
- test('replace current mode with options.default is set mode/2up', () => {
292
- br.options.defaults = 'mode/2up';
293
-
294
- const nextModeNumber = br.overridesBookMode();
295
- expect(nextModeNumber).toBe(2);
296
- });
297
-
298
- test('replace current mode with options.default is set mode/thumb', () => {
299
- br.options.defaults = 'mode/thumb';
300
-
301
- const nextModeNumber = br.overridesBookMode();
302
- expect(nextModeNumber).toBe(3);
303
- });
304
-
305
- test('test if options.default is NOT set', () => {
306
- br.options.defaults = null;
307
-
308
- // use mode/2up as default when no options.default metadata found
309
- const nextModeNumber = br.overridesBookMode();
310
- expect(nextModeNumber).toBe(2);
311
- });
284
+ describe('_modeStringToNumber', () => {
285
+ test('Returns correct number', () => {
286
+ expect(_modeStringToNumber('mode/1up')).toBe(1);
287
+ expect(_modeStringToNumber('mode/2up')).toBe(2);
288
+ expect(_modeStringToNumber('mode/thumb')).toBe(3);
312
289
  });
313
290
  });
@@ -114,6 +114,26 @@ describe('Plugin: URL controller', () => {
114
114
  expect(window.history.replaceState).toHaveBeenCalled();
115
115
  });
116
116
 
117
+ test('switches to hashMode if replaceState errors', () => {
118
+ window.history.replaceState = jest.fn(() => {
119
+ throw new Error('foo');
120
+ });
121
+ BookReader.prototype.currentIndex = jest.fn(() => 1);
122
+ BookReader.prototype.urlReadFragment = jest.fn(() => '');
123
+ BookReader.prototype.paramsFromCurrent = jest.fn(() => ({
124
+ index: 1,
125
+ mode: 2,
126
+ view: 'theater'
127
+ }));
128
+ BookReader.prototype.search = jest.fn();
129
+ br.options.urlMode = 'history';
130
+ br.init();
131
+ br.urlUpdateFragment();
132
+
133
+ expect(window.history.replaceState).toHaveBeenCalled();
134
+ expect(br.options.urlMode).toEqual('hash');
135
+ });
136
+
117
137
  test('does not update URL when search in query string', () => {
118
138
  window.history.replaceState = jest.fn();
119
139
  BookReader.prototype.currentIndex = jest.fn(() => 1);
@@ -13,3 +13,12 @@ describe('Helper function: set and get cookie item', () => {
13
13
  expect(docCookies.getItem('test-cookie')).toEqual('jack-sparow');
14
14
  });
15
15
  });
16
+
17
+ describe('isCookiesBlocked', () => {
18
+ test('return true if cookies are blocked', () => {
19
+ expect(docCookies.areCookiesBlocked({ get cookie() { throw new Error(); }})).toBeTruthy();
20
+ });
21
+ test('return false if cookies are not blocked', () => {
22
+ expect(docCookies.areCookiesBlocked({ cookie: 'blah' })).toBeFalsy();
23
+ });
24
+ });