instantsearch.js 4.32.0 → 4.34.0

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +24 -1
  3. package/cjs/connectors/autocomplete/connectAutocomplete.js +2 -1
  4. package/cjs/connectors/infinite-hits/connectInfiniteHits.js +1 -1
  5. package/cjs/connectors/numeric-menu/connectNumericMenu.js +6 -4
  6. package/cjs/connectors/pagination/connectPagination.js +3 -3
  7. package/cjs/connectors/powered-by/connectPoweredBy.js +13 -4
  8. package/cjs/connectors/search-box/connectSearchBox.js +4 -3
  9. package/cjs/connectors/sort-by/connectSortBy.js +2 -1
  10. package/cjs/connectors/stats/connectStats.js +4 -4
  11. package/cjs/connectors/toggle-refinement/connectToggleRefinement.js +1 -1
  12. package/cjs/lib/InstantSearch.js +26 -3
  13. package/cjs/lib/infiniteHitsCache/sessionStorage.js +16 -12
  14. package/cjs/lib/routers/history.js +89 -42
  15. package/cjs/lib/utils/detect-insights-client.js +10 -1
  16. package/cjs/lib/utils/index.js +10 -1
  17. package/cjs/lib/utils/safelyRunOnBrowser.js +30 -0
  18. package/cjs/lib/version.js +1 -1
  19. package/cjs/lib/voiceSearchHelper/index.js +5 -0
  20. package/cjs/middlewares/createMetadataMiddleware.js +12 -3
  21. package/cjs/widgets/index/index.js +21 -3
  22. package/dist/instantsearch.development.d.ts +21 -2
  23. package/dist/instantsearch.development.js +227 -88
  24. package/dist/instantsearch.development.js.map +1 -1
  25. package/dist/instantsearch.development.min.d.ts +21 -2
  26. package/dist/instantsearch.production.d.ts +21 -2
  27. package/dist/instantsearch.production.min.d.ts +21 -2
  28. package/dist/instantsearch.production.min.js +2 -2
  29. package/dist/instantsearch.production.min.js.map +1 -1
  30. package/es/connectors/autocomplete/connectAutocomplete.js +2 -1
  31. package/es/connectors/infinite-hits/connectInfiniteHits.js +1 -1
  32. package/es/connectors/numeric-menu/connectNumericMenu.js +6 -4
  33. package/es/connectors/pagination/connectPagination.js +3 -3
  34. package/es/connectors/powered-by/connectPoweredBy.js +14 -5
  35. package/es/connectors/search-box/connectSearchBox.js +4 -3
  36. package/es/connectors/sort-by/connectSortBy.js +2 -1
  37. package/es/connectors/stats/connectStats.js +4 -4
  38. package/es/connectors/toggle-refinement/connectToggleRefinement.js +1 -1
  39. package/es/lib/InstantSearch.d.ts +2 -1
  40. package/es/lib/InstantSearch.js +26 -3
  41. package/es/lib/infiniteHitsCache/sessionStorage.js +17 -14
  42. package/es/lib/routers/history.d.ts +13 -2
  43. package/es/lib/routers/history.js +88 -42
  44. package/es/lib/utils/detect-insights-client.js +9 -1
  45. package/es/lib/utils/index.d.ts +1 -0
  46. package/es/lib/utils/index.js +2 -1
  47. package/es/lib/utils/safelyRunOnBrowser.d.ts +14 -0
  48. package/es/lib/utils/safelyRunOnBrowser.js +23 -0
  49. package/es/lib/version.d.ts +1 -1
  50. package/es/lib/version.js +1 -1
  51. package/es/lib/voiceSearchHelper/index.js +5 -0
  52. package/es/middlewares/createMetadataMiddleware.js +12 -3
  53. package/es/types/results.d.ts +7 -0
  54. package/es/widgets/index/index.js +21 -3
  55. package/package.json +5 -5
@@ -52,6 +52,7 @@ var connectAutocomplete = function connectAutocomplete(renderFn) {
52
52
  var _this = this;
53
53
 
54
54
  var helper = _ref4.helper,
55
+ state = _ref4.state,
55
56
  scopedResults = _ref4.scopedResults,
56
57
  instantSearchInstance = _ref4.instantSearchInstance;
57
58
 
@@ -79,7 +80,7 @@ var connectAutocomplete = function connectAutocomplete(renderFn) {
79
80
  };
80
81
  });
81
82
  return {
82
- currentRefinement: helper.state.query || '',
83
+ currentRefinement: state.query || '',
83
84
  indices: indices,
84
85
  refine: connectorState.refine,
85
86
  widgetParams: widgetParams
@@ -165,7 +165,7 @@ var connectInfiniteHits = function connectInfiniteHits(renderFn) {
165
165
  index: helper.getIndex(),
166
166
  widgetType: this.$$type
167
167
  });
168
- isFirstPage = helper.state.page === undefined || getFirstReceivedPage(helper.state, cachedHits) === 0;
168
+ isFirstPage = state.page === undefined || getFirstReceivedPage(state, cachedHits) === 0;
169
169
  } else {
170
170
  var _state$page3 = state.page,
171
171
  _page = _state$page3 === void 0 ? 0 : _state$page3;
@@ -234,6 +234,8 @@ function isRefined(state, attribute, option) {
234
234
  if (option.start !== undefined && option.end !== undefined) {
235
235
  if (option.start === option.end) {
236
236
  return hasNumericRefinement(currentRefinements, '=', option.start);
237
+ } else {
238
+ return hasNumericRefinement(currentRefinements, '>=', option.start) && hasNumericRefinement(currentRefinements, '<=', option.end);
237
239
  }
238
240
  }
239
241
 
@@ -287,17 +289,17 @@ function getRefinedState(state, attribute, facetValue) {
287
289
  if (refinedOption.start !== undefined) {
288
290
  if (hasNumericRefinement(currentRefinements, '>=', refinedOption.start)) {
289
291
  resolvedState = resolvedState.removeNumericRefinement(attribute, '>=', refinedOption.start);
290
- } else {
291
- resolvedState = resolvedState.addNumericRefinement(attribute, '>=', refinedOption.start);
292
292
  }
293
+
294
+ resolvedState = resolvedState.addNumericRefinement(attribute, '>=', refinedOption.start);
293
295
  }
294
296
 
295
297
  if (refinedOption.end !== undefined) {
296
298
  if (hasNumericRefinement(currentRefinements, '<=', refinedOption.end)) {
297
299
  resolvedState = resolvedState.removeNumericRefinement(attribute, '<=', refinedOption.end);
298
- } else {
299
- resolvedState = resolvedState.addNumericRefinement(attribute, '<=', refinedOption.end);
300
300
  }
301
+
302
+ resolvedState = resolvedState.addNumericRefinement(attribute, '<=', refinedOption.end);
301
303
  }
302
304
 
303
305
  if (typeof resolvedState.page === 'number') {
@@ -78,6 +78,7 @@ var connectPagination = function connectPagination(renderFn) {
78
78
  getWidgetRenderState: function getWidgetRenderState(_ref6) {
79
79
  var results = _ref6.results,
80
80
  helper = _ref6.helper,
81
+ state = _ref6.state,
81
82
  createURL = _ref6.createURL;
82
83
 
83
84
  if (!connectorState.refine) {
@@ -88,14 +89,13 @@ var connectPagination = function connectPagination(renderFn) {
88
89
  }
89
90
 
90
91
  if (!connectorState.createURL) {
91
- connectorState.createURL = function (state) {
92
+ connectorState.createURL = function (helperState) {
92
93
  return function (page) {
93
- return createURL(state.setPage(page));
94
+ return createURL(helperState.setPage(page));
94
95
  };
95
96
  };
96
97
  }
97
98
 
98
- var state = helper.state;
99
99
  var page = state.page || 0;
100
100
  var nbPages = getMaxPage(results || {
101
101
  nbPages: 0
@@ -4,7 +4,7 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
4
4
 
5
5
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
6
6
 
7
- import { checkRendering, createDocumentationMessageGenerator, noop } from '../../lib/utils';
7
+ import { safelyRunOnBrowser, checkRendering, createDocumentationMessageGenerator, noop } from '../../lib/utils';
8
8
  var withUsage = createDocumentationMessageGenerator({
9
9
  name: 'powered-by',
10
10
  connector: true
@@ -17,11 +17,20 @@ var withUsage = createDocumentationMessageGenerator({
17
17
  var connectPoweredBy = function connectPoweredBy(renderFn) {
18
18
  var unmountFn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
19
19
  checkRendering(renderFn, withUsage());
20
- var defaultUrl = 'https://www.algolia.com/?' + 'utm_source=instantsearch.js&' + 'utm_medium=website&' + "utm_content=".concat(typeof window !== 'undefined' && window.location ? window.location.hostname : '', "&") + 'utm_campaign=poweredby';
20
+ var defaultUrl = 'https://www.algolia.com/?' + 'utm_source=instantsearch.js&' + 'utm_medium=website&' + "utm_content=".concat(safelyRunOnBrowser(function (_ref) {
21
+ var _window$location;
22
+
23
+ var window = _ref.window;
24
+ return ((_window$location = window.location) === null || _window$location === void 0 ? void 0 : _window$location.hostname) || '';
25
+ }, {
26
+ fallback: function fallback() {
27
+ return '';
28
+ }
29
+ }), "&") + 'utm_campaign=poweredby';
21
30
  return function (widgetParams) {
22
- var _ref = widgetParams || {},
23
- _ref$url = _ref.url,
24
- url = _ref$url === void 0 ? defaultUrl : _ref$url;
31
+ var _ref2 = widgetParams || {},
32
+ _ref2$url = _ref2.url,
33
+ url = _ref2$url === void 0 ? defaultUrl : _ref2$url;
25
34
 
26
35
  return {
27
36
  $$type: 'ais.poweredBy',
@@ -63,11 +63,12 @@ var connectSearchBox = function connectSearchBox(renderFn) {
63
63
  },
64
64
  getWidgetRenderState: function getWidgetRenderState(_ref3) {
65
65
  var helper = _ref3.helper,
66
- searchMetadata = _ref3.searchMetadata;
66
+ searchMetadata = _ref3.searchMetadata,
67
+ state = _ref3.state;
67
68
 
68
69
  if (!_refine) {
69
70
  var setQueryAndSearch = function setQueryAndSearch(query) {
70
- if (query !== helper.state.query) {
71
+ if (query !== state.query) {
71
72
  helper.setQuery(query).search();
72
73
  }
73
74
  };
@@ -84,7 +85,7 @@ var connectSearchBox = function connectSearchBox(renderFn) {
84
85
 
85
86
  _clear = clear(helper);
86
87
  return {
87
- query: helper.state.query || '',
88
+ query: state.query || '',
88
89
  refine: _refine,
89
90
  clear: _cachedClear,
90
91
  widgetParams: widgetParams,
@@ -64,6 +64,7 @@ var connectSortBy = function connectSortBy(renderFn) {
64
64
  getWidgetRenderState: function getWidgetRenderState(_ref3) {
65
65
  var results = _ref3.results,
66
66
  helper = _ref3.helper,
67
+ state = _ref3.state,
67
68
  parent = _ref3.parent;
68
69
 
69
70
  if (!connectorState.initialIndex && parent) {
@@ -77,7 +78,7 @@ var connectSortBy = function connectSortBy(renderFn) {
77
78
  }
78
79
 
79
80
  return {
80
- currentRefinement: helper.state.index,
81
+ currentRefinement: state.index,
81
82
  options: transformItems(items),
82
83
  refine: connectorState.setIndex,
83
84
  hasNoResults: results ? results.nbHits === 0 : true,
@@ -42,18 +42,18 @@ var connectStats = function connectStats(renderFn) {
42
42
  },
43
43
  getWidgetRenderState: function getWidgetRenderState(_ref) {
44
44
  var results = _ref.results,
45
- helper = _ref.helper;
45
+ state = _ref.state;
46
46
 
47
47
  if (!results) {
48
48
  return {
49
- hitsPerPage: helper.state.hitsPerPage,
49
+ hitsPerPage: state.hitsPerPage,
50
50
  nbHits: 0,
51
51
  nbSortedHits: undefined,
52
52
  areHitsSorted: false,
53
53
  nbPages: 0,
54
- page: helper.state.page || 0,
54
+ page: state.page || 0,
55
55
  processingTimeMS: -1,
56
- query: helper.state.query || '',
56
+ query: state.query || '',
57
57
  widgetParams: widgetParams
58
58
  };
59
59
  }
@@ -177,7 +177,7 @@ var connectToggleRefinement = function connectToggleRefinement(renderFn) {
177
177
  createURL = _ref6.createURL,
178
178
  instantSearchInstance = _ref6.instantSearchInstance;
179
179
  var isRefined = results ? on.every(function (v) {
180
- return helper.state.isDisjunctiveFacetRefined(attribute, v);
180
+ return state.isDisjunctiveFacetRefined(attribute, v);
181
181
  }) : on.every(function (v) {
182
182
  return state.isDisjunctiveFacetRefined(attribute, v);
183
183
  });
@@ -2,7 +2,7 @@
2
2
  import type { AlgoliaSearchHelper } from 'algoliasearch-helper';
3
3
  import EventEmitter from 'events';
4
4
  import type { IndexWidget } from '../widgets/index/index';
5
- import type { InsightsClient as AlgoliaInsightsClient, SearchClient, Widget, UiState, CreateURL, Middleware, MiddlewareDefinition, RenderState } from '../types';
5
+ import type { InsightsClient as AlgoliaInsightsClient, SearchClient, Widget, UiState, CreateURL, Middleware, MiddlewareDefinition, RenderState, InitialResults } from '../types';
6
6
  import type { RouterProps } from '../middlewares/createRouterMiddleware';
7
7
  import type { InsightsEvent } from '../middlewares/createInsightsMiddleware';
8
8
  /**
@@ -106,6 +106,7 @@ declare class InstantSearch<TUiState extends UiState = UiState, TRouteState = TU
106
106
  _searchStalledTimer: any;
107
107
  _isSearchStalled: boolean;
108
108
  _initialUiState: UiState;
109
+ _initialResults: InitialResults | null;
109
110
  _createURL: CreateURL<UiState>;
110
111
  _searchFunction?: InstantSearchOptions['searchFunction'];
111
112
  _mainHelperSearch?: AlgoliaSearchHelper['search'];
@@ -91,6 +91,8 @@ var InstantSearch = /*#__PURE__*/function (_EventEmitter) {
91
91
 
92
92
  _defineProperty(_assertThisInitialized(_this), "_initialUiState", void 0);
93
93
 
94
+ _defineProperty(_assertThisInitialized(_this), "_initialResults", void 0);
95
+
94
96
  _defineProperty(_assertThisInitialized(_this), "_createURL", void 0);
95
97
 
96
98
  _defineProperty(_assertThisInitialized(_this), "_searchFunction", void 0);
@@ -195,6 +197,7 @@ var InstantSearch = /*#__PURE__*/function (_EventEmitter) {
195
197
  _this._isSearchStalled = false;
196
198
  _this._createURL = defaultCreateURL;
197
199
  _this._initialUiState = initialUiState;
200
+ _this._initialResults = null;
198
201
 
199
202
  if (searchFunction) {
200
203
  _this._searchFunction = searchFunction;
@@ -437,9 +440,27 @@ var InstantSearch = /*#__PURE__*/function (_EventEmitter) {
437
440
  parent: null,
438
441
  uiState: this._initialUiState
439
442
  });
440
- this.scheduleSearch(); // Keep the previous reference for legacy purpose, some pattern use
443
+
444
+ if (this._initialResults) {
445
+ var originalScheduleSearch = this.scheduleSearch; // We don't schedule a first search when initial results are provided
446
+ // because we already have the results to render. This skips the initial
447
+ // network request on the browser on `start`.
448
+
449
+ this.scheduleSearch = defer(noop); // We also skip the initial network request when widgets are dynamically
450
+ // added in the first tick (that's the case in all the framework-based flavors).
451
+ // When we add a widget to `index`, it calls `scheduleSearch`. We can rely
452
+ // on our `defer` util to restore the original `scheduleSearch` value once
453
+ // widgets are added to hook back to the regular lifecycle.
454
+
455
+ defer(function () {
456
+ _this3.scheduleSearch = originalScheduleSearch;
457
+ })();
458
+ } else {
459
+ this.scheduleSearch();
460
+ } // Keep the previous reference for legacy purpose, some pattern use
441
461
  // the direct Helper access `search.helper` (e.g multi-index).
442
462
 
463
+
443
464
  this.helper = this.mainIndex.getHelper(); // track we started the search if we add more widgets,
444
465
  // to init them directly after add
445
466
 
@@ -501,15 +522,17 @@ var InstantSearch = /*#__PURE__*/function (_EventEmitter) {
501
522
  var nextUiState = typeof uiState === 'function' ? uiState(this.mainIndex.getWidgetUiState({})) : uiState;
502
523
 
503
524
  var setIndexHelperState = function setIndexHelperState(indexWidget) {
525
+ var nextIndexUiState = nextUiState[indexWidget.getIndexId()] || {};
526
+
504
527
  if (process.env.NODE_ENV === 'development') {
505
528
  checkIndexUiState({
506
529
  index: indexWidget,
507
- indexUiState: nextUiState[indexWidget.getIndexId()]
530
+ indexUiState: nextIndexUiState
508
531
  });
509
532
  }
510
533
 
511
534
  indexWidget.getHelper().setState(indexWidget.getWidgetSearchParameters(indexWidget.getHelper().state, {
512
- uiState: nextUiState[indexWidget.getIndexId()]
535
+ uiState: nextIndexUiState
513
536
  }));
514
537
  indexWidget.getWidgets().filter(isIndexWidget).forEach(setIndexHelperState);
515
538
  };
@@ -2,7 +2,7 @@ function _objectWithoutProperties(source, excluded) { if (source == null) return
2
2
 
3
3
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
4
4
 
5
- import { isEqual } from '../utils';
5
+ import { isEqual, safelyRunOnBrowser } from '../utils';
6
6
 
7
7
  function getStateWithoutPage(state) {
8
8
  var _ref = state || {},
@@ -13,28 +13,27 @@ function getStateWithoutPage(state) {
13
13
  }
14
14
 
15
15
  var KEY = 'ais.infiniteHits';
16
-
17
- function hasSessionStorage() {
18
- return typeof window !== 'undefined' && typeof window.sessionStorage !== 'undefined';
19
- }
20
-
21
16
  export default function createInfiniteHitsSessionStorageCache() {
22
17
  return {
23
18
  read: function read(_ref2) {
24
19
  var state = _ref2.state;
20
+ var sessionStorage = safelyRunOnBrowser(function (_ref3) {
21
+ var window = _ref3.window;
22
+ return window.sessionStorage;
23
+ });
25
24
 
26
- if (!hasSessionStorage()) {
25
+ if (!sessionStorage) {
27
26
  return null;
28
27
  }
29
28
 
30
29
  try {
31
30
  var cache = JSON.parse( // @ts-expect-error JSON.parse() requires a string, but it actually accepts null, too.
32
- window.sessionStorage.getItem(KEY));
31
+ sessionStorage.getItem(KEY));
33
32
  return cache && isEqual(cache.state, getStateWithoutPage(state)) ? cache.hits : null;
34
33
  } catch (error) {
35
34
  if (error instanceof SyntaxError) {
36
35
  try {
37
- window.sessionStorage.removeItem(KEY);
36
+ sessionStorage.removeItem(KEY);
38
37
  } catch (err) {// do nothing
39
38
  }
40
39
  }
@@ -42,16 +41,20 @@ export default function createInfiniteHitsSessionStorageCache() {
42
41
  return null;
43
42
  }
44
43
  },
45
- write: function write(_ref3) {
46
- var state = _ref3.state,
47
- hits = _ref3.hits;
44
+ write: function write(_ref4) {
45
+ var state = _ref4.state,
46
+ hits = _ref4.hits;
47
+ var sessionStorage = safelyRunOnBrowser(function (_ref5) {
48
+ var window = _ref5.window;
49
+ return window.sessionStorage;
50
+ });
48
51
 
49
- if (!hasSessionStorage()) {
52
+ if (!sessionStorage) {
50
53
  return;
51
54
  }
52
55
 
53
56
  try {
54
- window.sessionStorage.setItem(KEY, JSON.stringify({
57
+ sessionStorage.setItem(KEY, JSON.stringify({
55
58
  state: getStateWithoutPage(state),
56
59
  hits: hits
57
60
  }));
@@ -14,6 +14,7 @@ declare type BrowserHistoryArgs<TRouteState> = {
14
14
  writeDelay: number;
15
15
  createURL: CreateURL<TRouteState>;
16
16
  parseURL: ParseURL<TRouteState>;
17
+ getLocation(): Location;
17
18
  };
18
19
  declare class BrowserHistory<TRouteState> implements Router<TRouteState> {
19
20
  /**
@@ -38,13 +39,23 @@ declare class BrowserHistory<TRouteState> implements Router<TRouteState> {
38
39
  * It should be symmetrical to `createURL`.
39
40
  */
40
41
  private readonly parseURL;
42
+ /**
43
+ * Returns the location to store in the history.
44
+ * @default () => window.location
45
+ */
46
+ private readonly getLocation;
41
47
  private writeTimer?;
42
48
  private _onPopState;
49
+ /**
50
+ * Indicates if history.pushState should be executed.
51
+ * It needs to avoid pushing state to history in case of back/forward in browser
52
+ */
53
+ private shouldPushState;
43
54
  /**
44
55
  * Initializes a new storage provider that syncs the search state to the URL
45
56
  * using web APIs (`window.location.pushState` and `onpopstate` event).
46
57
  */
47
- constructor({ windowTitle, writeDelay, createURL, parseURL, }: BrowserHistoryArgs<TRouteState>);
58
+ constructor({ windowTitle, writeDelay, createURL, parseURL, getLocation, }: BrowserHistoryArgs<TRouteState>);
48
59
  /**
49
60
  * Reads the URL and returns a syncable UI search state.
50
61
  */
@@ -71,5 +82,5 @@ declare class BrowserHistory<TRouteState> implements Router<TRouteState> {
71
82
  */
72
83
  dispose(): void;
73
84
  }
74
- export default function historyRouter<TRouteState = UiState>({ createURL, parseURL, writeDelay, windowTitle, }?: Partial<BrowserHistoryArgs<TRouteState>>): BrowserHistory<TRouteState>;
85
+ export default function historyRouter<TRouteState = UiState>({ createURL, parseURL, writeDelay, windowTitle, getLocation, }?: Partial<BrowserHistoryArgs<TRouteState>>): BrowserHistory<TRouteState>;
75
86
  export {};
@@ -7,9 +7,12 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
7
7
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
8
8
 
9
9
  import qs from 'qs';
10
+ import { safelyRunOnBrowser } from '../utils';
10
11
 
11
12
  var setWindowTitle = function setWindowTitle(title) {
12
13
  if (title) {
14
+ // This function is only executed on browsers so we can disable this check.
15
+ // eslint-disable-next-line no-restricted-globals
13
16
  window.document.title = title;
14
17
  }
15
18
  };
@@ -20,11 +23,14 @@ var BrowserHistory = /*#__PURE__*/function () {
20
23
  * using web APIs (`window.location.pushState` and `onpopstate` event).
21
24
  */
22
25
  function BrowserHistory(_ref) {
26
+ var _this = this;
27
+
23
28
  var windowTitle = _ref.windowTitle,
24
29
  _ref$writeDelay = _ref.writeDelay,
25
30
  writeDelay = _ref$writeDelay === void 0 ? 400 : _ref$writeDelay,
26
31
  createURL = _ref.createURL,
27
- parseURL = _ref.parseURL;
32
+ parseURL = _ref.parseURL,
33
+ getLocation = _ref.getLocation;
28
34
 
29
35
  _classCallCheck(this, BrowserHistory);
30
36
 
@@ -36,15 +42,23 @@ var BrowserHistory = /*#__PURE__*/function () {
36
42
 
37
43
  _defineProperty(this, "parseURL", void 0);
38
44
 
45
+ _defineProperty(this, "getLocation", void 0);
46
+
39
47
  _defineProperty(this, "writeTimer", void 0);
40
48
 
49
+ _defineProperty(this, "shouldPushState", true);
50
+
41
51
  this.windowTitle = windowTitle;
42
52
  this.writeTimer = undefined;
43
53
  this.writeDelay = writeDelay;
44
54
  this._createURL = createURL;
45
55
  this.parseURL = parseURL;
46
- var title = this.windowTitle && this.windowTitle(this.read());
47
- setWindowTitle(title);
56
+ this.getLocation = getLocation;
57
+ safelyRunOnBrowser(function () {
58
+ var title = _this.windowTitle && _this.windowTitle(_this.read());
59
+
60
+ setWindowTitle(title);
61
+ });
48
62
  }
49
63
  /**
50
64
  * Reads the URL and returns a syncable UI search state.
@@ -56,7 +70,7 @@ var BrowserHistory = /*#__PURE__*/function () {
56
70
  value: function read() {
57
71
  return this.parseURL({
58
72
  qsModule: qs,
59
- location: window.location
73
+ location: this.getLocation()
60
74
  });
61
75
  }
62
76
  /**
@@ -66,20 +80,30 @@ var BrowserHistory = /*#__PURE__*/function () {
66
80
  }, {
67
81
  key: "write",
68
82
  value: function write(routeState) {
69
- var _this = this;
83
+ var _this2 = this;
70
84
 
71
- var url = this.createURL(routeState);
72
- var title = this.windowTitle && this.windowTitle(routeState);
85
+ safelyRunOnBrowser(function (_ref2) {
86
+ var window = _ref2.window;
73
87
 
74
- if (this.writeTimer) {
75
- window.clearTimeout(this.writeTimer);
76
- }
88
+ var url = _this2.createURL(routeState);
89
+
90
+ var title = _this2.windowTitle && _this2.windowTitle(routeState);
91
+
92
+ if (_this2.writeTimer) {
93
+ clearTimeout(_this2.writeTimer);
94
+ }
95
+
96
+ _this2.writeTimer = setTimeout(function () {
97
+ setWindowTitle(title);
98
+
99
+ if (_this2.shouldPushState) {
100
+ window.history.pushState(routeState, title || '', url);
101
+ }
77
102
 
78
- this.writeTimer = window.setTimeout(function () {
79
- setWindowTitle(title);
80
- window.history.pushState(routeState, title || '', url);
81
- _this.writeTimer = undefined;
82
- }, this.writeDelay);
103
+ _this2.shouldPushState = true;
104
+ _this2.writeTimer = undefined;
105
+ }, _this2.writeDelay);
106
+ });
83
107
  }
84
108
  /**
85
109
  * Sets a callback on the `onpopstate` event of the history API of the current page.
@@ -89,26 +113,30 @@ var BrowserHistory = /*#__PURE__*/function () {
89
113
  }, {
90
114
  key: "onUpdate",
91
115
  value: function onUpdate(callback) {
92
- var _this2 = this;
116
+ var _this3 = this;
93
117
 
94
118
  this._onPopState = function (event) {
95
- if (_this2.writeTimer) {
96
- window.clearTimeout(_this2.writeTimer);
97
- _this2.writeTimer = undefined;
119
+ if (_this3.writeTimer) {
120
+ clearTimeout(_this3.writeTimer);
121
+ _this3.writeTimer = undefined;
98
122
  }
99
123
 
124
+ _this3.shouldPushState = false;
100
125
  var routeState = event.state; // At initial load, the state is read from the URL without update.
101
126
  // Therefore the state object is not available.
102
127
  // In this case, we fallback and read the URL.
103
128
 
104
129
  if (!routeState) {
105
- callback(_this2.read());
130
+ callback(_this3.read());
106
131
  } else {
107
132
  callback(routeState);
108
133
  }
109
134
  };
110
135
 
111
- window.addEventListener('popstate', this._onPopState);
136
+ safelyRunOnBrowser(function (_ref3) {
137
+ var window = _ref3.window;
138
+ window.addEventListener('popstate', _this3._onPopState);
139
+ });
112
140
  }
113
141
  /**
114
142
  * Creates a complete URL from a given syncable UI state.
@@ -124,7 +152,7 @@ var BrowserHistory = /*#__PURE__*/function () {
124
152
  return this._createURL({
125
153
  qsModule: qs,
126
154
  routeState: routeState,
127
- location: window.location
155
+ location: this.getLocation()
128
156
  });
129
157
  }
130
158
  /**
@@ -134,12 +162,18 @@ var BrowserHistory = /*#__PURE__*/function () {
134
162
  }, {
135
163
  key: "dispose",
136
164
  value: function dispose() {
137
- if (this._onPopState) {
138
- window.removeEventListener('popstate', this._onPopState);
139
- }
165
+ var _this4 = this;
166
+
167
+ safelyRunOnBrowser(function (_ref4) {
168
+ var window = _ref4.window;
169
+
170
+ if (_this4._onPopState) {
171
+ window.removeEventListener('popstate', _this4._onPopState);
172
+ }
173
+ });
140
174
 
141
175
  if (this.writeTimer) {
142
- window.clearTimeout(this.writeTimer);
176
+ clearTimeout(this.writeTimer);
143
177
  }
144
178
 
145
179
  this.write({});
@@ -150,12 +184,12 @@ var BrowserHistory = /*#__PURE__*/function () {
150
184
  }();
151
185
 
152
186
  export default function historyRouter() {
153
- var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
154
- _ref2$createURL = _ref2.createURL,
155
- createURL = _ref2$createURL === void 0 ? function (_ref3) {
156
- var qsModule = _ref3.qsModule,
157
- routeState = _ref3.routeState,
158
- location = _ref3.location;
187
+ var _ref5 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
188
+ _ref5$createURL = _ref5.createURL,
189
+ createURL = _ref5$createURL === void 0 ? function (_ref6) {
190
+ var qsModule = _ref6.qsModule,
191
+ routeState = _ref6.routeState,
192
+ location = _ref6.location;
159
193
  var protocol = location.protocol,
160
194
  hostname = location.hostname,
161
195
  _location$port = location.port,
@@ -171,11 +205,11 @@ export default function historyRouter() {
171
205
  }
172
206
 
173
207
  return "".concat(protocol, "//").concat(hostname).concat(portWithPrefix).concat(pathname, "?").concat(queryString).concat(hash);
174
- } : _ref2$createURL,
175
- _ref2$parseURL = _ref2.parseURL,
176
- parseURL = _ref2$parseURL === void 0 ? function (_ref4) {
177
- var qsModule = _ref4.qsModule,
178
- location = _ref4.location;
208
+ } : _ref5$createURL,
209
+ _ref5$parseURL = _ref5.parseURL,
210
+ parseURL = _ref5$parseURL === void 0 ? function (_ref7) {
211
+ var qsModule = _ref7.qsModule,
212
+ location = _ref7.location;
179
213
  // `qs` by default converts arrays with more than 20 items to an object.
180
214
  // We want to avoid this because the data structure manipulated can therefore vary.
181
215
  // Setting the limit to `100` seems a good number because the engine's default is 100
@@ -189,15 +223,27 @@ export default function historyRouter() {
189
223
  return qsModule.parse(location.search.slice(1), {
190
224
  arrayLimit: 99
191
225
  });
192
- } : _ref2$parseURL,
193
- _ref2$writeDelay = _ref2.writeDelay,
194
- writeDelay = _ref2$writeDelay === void 0 ? 400 : _ref2$writeDelay,
195
- windowTitle = _ref2.windowTitle;
226
+ } : _ref5$parseURL,
227
+ _ref5$writeDelay = _ref5.writeDelay,
228
+ writeDelay = _ref5$writeDelay === void 0 ? 400 : _ref5$writeDelay,
229
+ windowTitle = _ref5.windowTitle,
230
+ _ref5$getLocation = _ref5.getLocation,
231
+ getLocation = _ref5$getLocation === void 0 ? function () {
232
+ return safelyRunOnBrowser(function (_ref8) {
233
+ var window = _ref8.window;
234
+ return window.location;
235
+ }, {
236
+ fallback: function fallback() {
237
+ throw new Error('You need to provide `getLocation` to the `history` router in environments where `window` does not exist.');
238
+ }
239
+ });
240
+ } : _ref5$getLocation;
196
241
 
197
242
  return new BrowserHistory({
198
243
  createURL: createURL,
199
244
  parseURL: parseURL,
200
245
  writeDelay: writeDelay,
201
- windowTitle: windowTitle
246
+ windowTitle: windowTitle,
247
+ getLocation: getLocation
202
248
  });
203
249
  }
@@ -1,3 +1,11 @@
1
+ import { safelyRunOnBrowser } from './safelyRunOnBrowser';
1
2
  export default function hasDetectedInsightsClient() {
2
- return typeof window !== 'undefined' && Boolean(window.AlgoliaAnalyticsObject);
3
+ return safelyRunOnBrowser(function (_ref) {
4
+ var window = _ref.window;
5
+ return Boolean(window.AlgoliaAnalyticsObject);
6
+ }, {
7
+ fallback: function fallback() {
8
+ return false;
9
+ }
10
+ });
3
11
  }
@@ -45,3 +45,4 @@ export { createConcurrentSafePromise } from './createConcurrentSafePromise';
45
45
  export { debounce } from './debounce';
46
46
  export { serializePayload, deserializePayload } from './serializer';
47
47
  export { getWidgetAttribute } from './getWidgetAttribute';
48
+ export { safelyRunOnBrowser } from './safelyRunOnBrowser';
@@ -44,4 +44,5 @@ export { convertNumericRefinementsToFilters } from './convertNumericRefinementsT
44
44
  export { createConcurrentSafePromise } from './createConcurrentSafePromise';
45
45
  export { debounce } from './debounce';
46
46
  export { serializePayload, deserializePayload } from './serializer';
47
- export { getWidgetAttribute } from './getWidgetAttribute';
47
+ export { getWidgetAttribute } from './getWidgetAttribute';
48
+ export { safelyRunOnBrowser } from './safelyRunOnBrowser';