@internetarchive/bookreader 5.0.0-54 → 5.0.0-56

Sign up to get free protection for your applications and to get access to all the features.
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
+ });