1 | import React from "react"
|
2 | import PropTypes from "prop-types"
|
3 | import loader from "./loader"
|
4 | import redirects from "./redirects.json"
|
5 | import { apiRunner } from "./api-runner-browser"
|
6 | import emitter from "./emitter"
|
7 | import { navigate as reachNavigate } from "@reach/router"
|
8 | import { parsePath } from "gatsby-link"
|
9 |
|
10 |
|
11 | const redirectMap = redirects.reduce((map, redirect) => {
|
12 | map[redirect.fromPath] = redirect
|
13 | return map
|
14 | }, {})
|
15 |
|
16 | function maybeRedirect(pathname) {
|
17 | const redirect = redirectMap[pathname]
|
18 |
|
19 | if (redirect != null) {
|
20 | if (process.env.NODE_ENV !== `production`) {
|
21 | const pageResources = loader.loadPageSync(pathname)
|
22 |
|
23 | if (pageResources != null) {
|
24 | console.error(
|
25 | `The route "${pathname}" matches both a page and a redirect; this is probably not intentional.`
|
26 | )
|
27 | }
|
28 | }
|
29 |
|
30 | window.___replace(redirect.toPath)
|
31 | return true
|
32 | } else {
|
33 | return false
|
34 | }
|
35 | }
|
36 |
|
37 | const onPreRouteUpdate = (location, prevLocation) => {
|
38 | if (!maybeRedirect(location.pathname)) {
|
39 | apiRunner(`onPreRouteUpdate`, { location, prevLocation })
|
40 | }
|
41 | }
|
42 |
|
43 | const onRouteUpdate = (location, prevLocation) => {
|
44 | if (!maybeRedirect(location.pathname)) {
|
45 | apiRunner(`onRouteUpdate`, { location, prevLocation })
|
46 |
|
47 |
|
48 | window.__navigatingToLink = false
|
49 | }
|
50 | }
|
51 |
|
52 | const navigate = (to, options = {}) => {
|
53 |
|
54 | if (!options.replace) {
|
55 | window.__navigatingToLink = true
|
56 | }
|
57 |
|
58 | let { pathname } = parsePath(to)
|
59 | const redirect = redirectMap[pathname]
|
60 |
|
61 |
|
62 |
|
63 | if (redirect) {
|
64 | to = redirect.toPath
|
65 | pathname = parsePath(to).pathname
|
66 | }
|
67 |
|
68 |
|
69 |
|
70 | if (window.___swUpdated) {
|
71 | window.location = pathname
|
72 | return
|
73 | }
|
74 |
|
75 |
|
76 |
|
77 | const timeoutId = setTimeout(() => {
|
78 | emitter.emit(`onDelayedLoadPageResources`, { pathname })
|
79 | apiRunner(`onRouteUpdateDelayed`, {
|
80 | location: window.location,
|
81 | })
|
82 | }, 1000)
|
83 |
|
84 | loader.loadPage(pathname).then(pageResources => {
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 | if (!pageResources || pageResources.status === `error`) {
|
92 | window.history.replaceState({}, ``, location.href)
|
93 | window.location = pathname
|
94 | }
|
95 |
|
96 |
|
97 | if (process.env.NODE_ENV === `production` && pageResources) {
|
98 | if (
|
99 | pageResources.page.webpackCompilationHash !==
|
100 | window.___webpackCompilationHash
|
101 | ) {
|
102 |
|
103 | if (
|
104 | `serviceWorker` in navigator &&
|
105 | navigator.serviceWorker.controller !== null &&
|
106 | navigator.serviceWorker.controller.state === `activated`
|
107 | ) {
|
108 | navigator.serviceWorker.controller.postMessage({
|
109 | gatsbyApi: `resetWhitelist`,
|
110 | })
|
111 | }
|
112 |
|
113 | console.log(`Site has changed on server. Reloading browser`)
|
114 | window.location = pathname
|
115 | }
|
116 | }
|
117 | reachNavigate(to, options)
|
118 | clearTimeout(timeoutId)
|
119 | })
|
120 | }
|
121 |
|
122 | function shouldUpdateScroll(prevRouterProps, { location }) {
|
123 | const { pathname, hash } = location
|
124 | const results = apiRunner(`shouldUpdateScroll`, {
|
125 | prevRouterProps,
|
126 |
|
127 | pathname,
|
128 | routerProps: { location },
|
129 | getSavedScrollPosition: args => this._stateStorage.read(args),
|
130 | })
|
131 | if (results.length > 0) {
|
132 | return results[0]
|
133 | }
|
134 |
|
135 | if (prevRouterProps) {
|
136 | const {
|
137 | location: { pathname: oldPathname },
|
138 | } = prevRouterProps
|
139 | if (oldPathname === pathname) {
|
140 |
|
141 |
|
142 | return hash ? hash.slice(1) : [0, 0]
|
143 | }
|
144 | }
|
145 | return true
|
146 | }
|
147 |
|
148 | function init() {
|
149 |
|
150 | window.__navigatingToLink = false
|
151 |
|
152 | window.___loader = loader
|
153 | window.___push = to => navigate(to, { replace: false })
|
154 | window.___replace = to => navigate(to, { replace: true })
|
155 | window.___navigate = (to, options) => navigate(to, options)
|
156 |
|
157 |
|
158 | maybeRedirect(window.location.pathname)
|
159 | }
|
160 |
|
161 |
|
162 | class RouteUpdates extends React.Component {
|
163 | constructor(props) {
|
164 | super(props)
|
165 | onPreRouteUpdate(props.location, null)
|
166 | }
|
167 |
|
168 | componentDidMount() {
|
169 | onRouteUpdate(this.props.location, null)
|
170 | }
|
171 |
|
172 | componentDidUpdate(prevProps, prevState, shouldFireRouteUpdate) {
|
173 | if (shouldFireRouteUpdate) {
|
174 | onRouteUpdate(this.props.location, prevProps.location)
|
175 | }
|
176 | }
|
177 |
|
178 | getSnapshotBeforeUpdate(prevProps) {
|
179 | if (this.props.location.pathname !== prevProps.location.pathname) {
|
180 | onPreRouteUpdate(this.props.location, prevProps.location)
|
181 | return true
|
182 | }
|
183 |
|
184 | return false
|
185 | }
|
186 |
|
187 | render() {
|
188 | return this.props.children
|
189 | }
|
190 | }
|
191 |
|
192 | RouteUpdates.propTypes = {
|
193 | location: PropTypes.object.isRequired,
|
194 | }
|
195 |
|
196 | export { init, shouldUpdateScroll, RouteUpdates }
|