Uploaded missing file, small refactoring, OverlayPlugin support (hopefully)
This commit is contained in:
		
							parent
							
								
									0ffdfacafe
								
							
						
					
					
						commit
						c1dda4bacc
					
				
							
								
								
									
										43
									
								
								src/ACTListener.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/ACTListener.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| const getHost = () => /HOST_PORT=(wss?:\/\/.+)/.exec(window.location.search) | ||||
| 
 | ||||
| export default function listenToACT(callback) { | ||||
| 	if (!getHost()) return listenOverlayPlugin(callback) | ||||
| 	return listenActWebSocket(callback) | ||||
| } | ||||
| 
 | ||||
| function listenActWebSocket(callback) { | ||||
| 	const url = new URLSearchParams(window.location.search) | ||||
| 	const wsUri = `${url.get("HOST_PORT")}BeforeLogLineRead` || undefined | ||||
| 	const ws = new WebSocket(wsUri) | ||||
| 	ws.onerror = () => ws.close() | ||||
| 	ws.onclose = () => | ||||
| 		setTimeout(() => { | ||||
| 			listenActWebSocket(callback) | ||||
| 		}, 1000) | ||||
| 	ws.onmessage = function(e, m) { | ||||
| 		if (e.data === ".") return ws.send(".") | ||||
| 
 | ||||
| 		const obj = JSON.parse(e.data) | ||||
| 		if (obj.msgtype === "SendCharName") { | ||||
| 			return callback(obj.msg) | ||||
| 		} else if (obj.msgtype === "Chat") { | ||||
| 			return callback(...obj.msg.split("|")) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return () => { | ||||
| 		ws.close() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function listenOverlayPlugin(callback) { | ||||
| 	const listener = e => { | ||||
| 		callback(...e.detail) | ||||
| 	} | ||||
| 
 | ||||
| 	document.addEventListener("onLogLine", listener) | ||||
| 
 | ||||
| 	return () => { | ||||
| 		document.removeEventListener("onLogLine", listener) | ||||
| 	} | ||||
| } | ||||
| @ -1,30 +0,0 @@ | ||||
| const handleCodes = new Set([ | ||||
| 	'00', | ||||
| 	'01', | ||||
| 	'02', | ||||
| 	'21', | ||||
| 	'22', | ||||
| 	'33' | ||||
| ]) | ||||
| 
 | ||||
| export default function listenActWebSocket( callback ) { | ||||
|   const url = new URLSearchParams(window.location.search) | ||||
|   const wsUri = `${url.get("HOST_PORT")}BeforeLogLineRead` || undefined | ||||
|   const ws = new WebSocket(wsUri) | ||||
|   ws.onerror = () => ws.close() | ||||
|   ws.onclose = () => setTimeout(() => { listenActWebSocket( callback ) }, 1000) | ||||
|   ws.onmessage = function(e, m) { | ||||
|     if (e.data === ".") return ws.send(".") //PING
 | ||||
| 
 | ||||
|     const obj = JSON.parse(e.data); | ||||
|     if (obj.msgtype === "SendCharName") { | ||||
|       return callback(obj.msg, null) | ||||
|     } else if (obj.msgtype === "Chat") { | ||||
|       const code = obj.msg.substring(0, 2) //first 2 numbers POG
 | ||||
| 
 | ||||
|       if (handleCodes.has(code)) return callback(obj.msg, code) //NetworkAbility or NetworkAoeAbility
 | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return ws | ||||
| } | ||||
| @ -1,10 +1,10 @@ | ||||
| import React from 'react' | ||||
| import './css/Action.css' | ||||
| import React from "react" | ||||
| import "./css/Action.css" | ||||
| 
 | ||||
| const gcdOverrides = new Set([ | ||||
| 	15997, //standard step
 | ||||
| 	15998, //technical step
 | ||||
| 	15999,  | ||||
| 	15999, | ||||
| 	16000, | ||||
| 	16001, | ||||
| 	16002, //step actions
 | ||||
| @ -28,13 +28,13 @@ const ogcdOverrides = new Set([ | ||||
| 
 | ||||
| export default function Action({ actionId, additionalClasses }) { | ||||
| 	const [apiData, setApiData] = React.useState() | ||||
| 	 | ||||
| 
 | ||||
| 	React.useEffect(() => { | ||||
| 		let current = true | ||||
| 		void (async () => { | ||||
| 			const data = await ( | ||||
| 				await fetch(`https://xivapi.com/Action/${actionId}`, { mode: 'cors' }) | ||||
| 			).json() | ||||
| 			const data = await (await fetch(`https://xivapi.com/Action/${actionId}`, { | ||||
| 				mode: "cors" | ||||
| 			})).json() | ||||
| 			if (current) { | ||||
| 				setApiData(data) | ||||
| 			} | ||||
| @ -44,16 +44,21 @@ export default function Action({ actionId, additionalClasses }) { | ||||
| 			current = false | ||||
| 		} | ||||
| 	}, [actionId]) | ||||
| 	 | ||||
| 
 | ||||
| 	if (apiData === undefined || !apiData.Icon) { | ||||
| 		return null | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| 	return ( | ||||
| 		<img | ||||
| 			className={(gcdOverrides.has(actionId) || (!ogcdOverrides.has(actionId) && apiData.ActionCategory.ID !== 4)) ? `gcd ${additionalClasses}` : `ogcd ${additionalClasses}`} | ||||
| 			className={ | ||||
| 				gcdOverrides.has(actionId) || | ||||
| 				(!ogcdOverrides.has(actionId) && apiData.ActionCategory.ID !== 4) | ||||
| 					? `gcd ${additionalClasses}` | ||||
| 					: `ogcd ${additionalClasses}` | ||||
| 			} | ||||
| 			src={`https://xivapi.com/${apiData.Icon}`} | ||||
| 			alt={apiData.Name || ''} | ||||
| 			alt={apiData.Name || ""} | ||||
| 		/> | ||||
| 	) | ||||
| } | ||||
|  | ||||
							
								
								
									
										156
									
								
								src/App.js
									
									
									
									
									
								
							
							
						
						
									
										156
									
								
								src/App.js
									
									
									
									
									
								
							| @ -1,100 +1,93 @@ | ||||
| import React from 'react' | ||||
| import listenActWebSocket from './ACTWebsocket' | ||||
| import './css/App.css' | ||||
| import Action from './Action' | ||||
| import RotationContainer from './Rotation' | ||||
| import ReactDOM from 'react-dom' | ||||
| import React from "react" | ||||
| import listenToACT from "./ACTListener" | ||||
| import "./css/App.css" | ||||
| import Action from "./Action" | ||||
| import RotationContainer from "./Rotation" | ||||
| import ReactDOM from "react-dom" | ||||
| 
 | ||||
| const handleCodes = new Set(["00", "01", "02", "21", "22", "33"]) | ||||
| 
 | ||||
| export default function App() { | ||||
| 	// NOTE: unlike class state, useState doesn't do object merging; instead, it directly holds values
 | ||||
| 	const [actionList, setActionList] = React.useState([]) | ||||
| 	const [encounterList, setEncounterList] = React.useState([]) | ||||
| 
 | ||||
| 	React.useEffect(() => { | ||||
| 		 | ||||
| 		// These values are only used internally by the handler,
 | ||||
| 		// we don't need to notify React that they were updated,
 | ||||
| 		// or keep their updates synchronized with actionList updates.
 | ||||
| 		//
 | ||||
| 		// This means we don't have to keep them in State (or Reducer)!
 | ||||
| 		let selfId | ||||
| 		let lastTimestamp = '' | ||||
| 		let lastTimestamp = "" | ||||
| 		let lastAction = -1 | ||||
| 		let currentZone = 'Unknown' | ||||
| 		let currentZone = "Unknown" | ||||
| 
 | ||||
| 		// we need keys to persist for each push, even if we shorten the array later,
 | ||||
| 		// so we store the key with the action; can't just use array index due to CSS
 | ||||
| 		let lastKey = 1 | ||||
| 
 | ||||
| 		// listenActWebSocket should be changed to return the websocket,
 | ||||
| 		// and this effect should return a function that disconnects the websocket
 | ||||
| 		//
 | ||||
| 		// like "return () => { ws.close() }"
 | ||||
| 		let ws = listenActWebSocket((data, code) => { | ||||
| 			const openNewEncounter = (timestamp) => { | ||||
| 		let closeFn = listenToACT((...logSplit) => { | ||||
| 			const openNewEncounter = () => { | ||||
| 				setEncounterList(encounterList => { | ||||
| 					if(encounterList[0] && encounterList[0].rotation && encounterList[0].rotation.length <= 0) { | ||||
| 					if ( | ||||
| 						encounterList[0] && | ||||
| 						encounterList[0].rotation && | ||||
| 						encounterList[0].rotation.length <= 0 | ||||
| 					) { | ||||
| 						encounterList.shift() | ||||
| 					} | ||||
| 					 | ||||
| 
 | ||||
| 					encounterList.unshift({ | ||||
| 						name: currentZone, | ||||
| 						rotation: [] | ||||
| 					}) | ||||
| 					 | ||||
| 					return encounterList.slice(0,3) | ||||
| 
 | ||||
| 					return encounterList.slice(0, 3) | ||||
| 				}) | ||||
| 			} | ||||
| 			 | ||||
| 			if (data.charID) { | ||||
| 				selfId = data.charID | ||||
| 
 | ||||
| 			if (logSplit.length === 1 && logSplit[0].charID) { | ||||
| 				selfId = logSplit[0].charID | ||||
| 				openNewEncounter() | ||||
| 				return | ||||
| 			} | ||||
| 			 | ||||
| 			switch(code) { | ||||
| 				case '00': | ||||
| 					const [, , refCode, , message] = data.split('|') | ||||
| 					if(refCode === '0038' && message === 'end') openNewEncounter() | ||||
| 
 | ||||
| 			const [ | ||||
| 				logCode, | ||||
| 				logTimestamp, | ||||
| 				logParameter1, | ||||
| 				logParameter2, | ||||
| 				logParameter3 | ||||
| 			] = logSplit | ||||
| 
 | ||||
| 			if (!handleCodes.has(logCode)) return | ||||
| 
 | ||||
| 			switch (logCode) { | ||||
| 				case "00": | ||||
| 					if (logParameter1 === "0038" && logParameter3 === "end") | ||||
| 						openNewEncounter() | ||||
| 					return | ||||
| 				case '01': | ||||
| 					const [, , , zoneName] = data.split('|') | ||||
| 					currentZone = zoneName | ||||
| 				case "01": | ||||
| 					currentZone = logParameter2 | ||||
| 					return | ||||
| 				case '02': | ||||
| 					const [, , logCharIdHex] = data.split('|') | ||||
| 					selfId = parseInt(logCharIdHex, 16) | ||||
| 				case "02": | ||||
| 					selfId = parseInt(logParameter1, 16) | ||||
| 					openNewEncounter() | ||||
| 					return | ||||
| 				case '33': | ||||
| 					const [, , , controlCode] = data.split('|') | ||||
| 					if(controlCode === '40000012' || controlCode === '40000010') openNewEncounter() | ||||
| 				case "33": | ||||
| 					if (logParameter2 === "40000012" || logParameter2 === "40000010") | ||||
| 						openNewEncounter() | ||||
| 					return | ||||
| 				default: | ||||
| 					break | ||||
| 			} | ||||
| 			 | ||||
| 			//if it's not any of these it must be Network(AOE)Ability, the bulk of what we want to handle
 | ||||
| 
 | ||||
| 			if (selfId === undefined) return | ||||
| 
 | ||||
| 			const [, logTimestamp, logCharIdHex, , logActionIdHex] = data.split('|') | ||||
| 			if (parseInt(logParameter1, 16) !== selfId) return | ||||
| 
 | ||||
| 			// microoptimization: since selfId updates way less often,
 | ||||
| 			// save selfId as data.charID.toString(16), that way you don't need to
 | ||||
| 			// parse logCharIdHex every time
 | ||||
| 			if (parseInt(logCharIdHex, 16) !== selfId) return | ||||
| 
 | ||||
| 			// we do a mathematical comparison with action though so can't optimize this away
 | ||||
| 			const action = parseInt(logActionIdHex, 16) | ||||
| 			const action = parseInt(logParameter3, 16) | ||||
| 
 | ||||
| 			if ( | ||||
| 				action <= 8 || | ||||
| 				(logTimestamp === lastTimestamp && action === lastAction) | ||||
| 			) | ||||
| 				return | ||||
| 				 | ||||
| 			if((Date.now() - Date.parse(lastTimestamp)) > 120000) openNewEncounter()//last action > 120s ago
 | ||||
| 
 | ||||
| 			if (Date.now() - Date.parse(lastTimestamp) > 120000) openNewEncounter() //last action > 120s ago
 | ||||
| 
 | ||||
| 			lastTimestamp = logTimestamp | ||||
| 			lastAction = action | ||||
| @ -102,48 +95,55 @@ export default function App() { | ||||
| 			const key = (lastKey % 256) + 1 | ||||
| 			lastKey = key | ||||
| 
 | ||||
| 			// This is pretty silly but it's the neatest way to handle the updates going
 | ||||
| 			// out at the same time, without finding some way to merge the action lists....
 | ||||
| 			ReactDOM.unstable_batchedUpdates(() => { | ||||
| 				setActionList(actionList => actionList.concat({ action, key })) | ||||
| 				setEncounterList(encounterList => { | ||||
| 					if(!encounterList[0]) { | ||||
| 					if (!encounterList[0]) { | ||||
| 						encounterList[0] = { | ||||
| 							name: currentZone, | ||||
| 							rotation: [] | ||||
| 						} | ||||
| 					} | ||||
| 				 | ||||
| 					encounterList[0].rotation.push( action ) | ||||
| 				 | ||||
| 
 | ||||
| 					encounterList[0].rotation.push(action) | ||||
| 
 | ||||
| 					return encounterList | ||||
| 				}) | ||||
| 			}) | ||||
| 
 | ||||
| 			// This _probably_ should be done as a separate React.useEffect instead,
 | ||||
| 			// which runs as an effect whenever the value of actionList changes.
 | ||||
| 			// The problem there is, it would have to detect whether the list grew
 | ||||
| 			// since the last time it was called, otherwise it'd react (heh) to its own
 | ||||
| 			// updates.
 | ||||
| 			//
 | ||||
| 			// Easier to pair it with the previous set.
 | ||||
| 			setTimeout(() => { | ||||
| 				setActionList(actionList => actionList.slice(1)) | ||||
| 			}, 10000) | ||||
| 		}) | ||||
| 		 | ||||
| 		return () => { ws.close() } | ||||
| 
 | ||||
| 		return () => { | ||||
| 			closeFn() | ||||
| 		} | ||||
| 	}, []) | ||||
| 
 | ||||
| 	return ( | ||||
| 		<div className='container'> | ||||
| 			<div className='actions'> | ||||
| 				{actionList.map(({ action, key }) => ( | ||||
| 					<Action key={key} actionId={action} additionalClasses='action-move' /> | ||||
| 		<> | ||||
| 			<div className="container"> | ||||
| 				<div className="actions"> | ||||
| 					{actionList.map(({ action, key }) => ( | ||||
| 						<Action | ||||
| 							key={key} | ||||
| 							actionId={action} | ||||
| 							additionalClasses="action-move" | ||||
| 						/> | ||||
| 					))} | ||||
| 				</div> | ||||
| 				{encounterList.map((encounter, i) => ( | ||||
| 					<RotationContainer | ||||
| 						key={i} | ||||
| 						encounterId={i} | ||||
| 						name={encounter.name} | ||||
| 						actionList={encounter.rotation} | ||||
| 					/> | ||||
| 				))} | ||||
| 			</div> | ||||
| 			{encounterList.map((encounter, i) => ( | ||||
| 				<RotationContainer key={i} encounterId={i} name={encounter.name} actionList={encounter.rotation} /> | ||||
| 			))} | ||||
| 		</div> | ||||
| 		</> | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										0
									
								
								src/Config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/Config.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										33
									
								
								src/Rotation.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/Rotation.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| import React from "react" | ||||
| import "./css/Rotation.css" | ||||
| import Action from "./Action" | ||||
| 
 | ||||
| export default function RotationContainer({ encounterId, name, actionList }) { | ||||
| 	const [open, setOpen] = React.useState(false) | ||||
| 
 | ||||
| 	return ( | ||||
| 		<> | ||||
| 			<button | ||||
| 				className={open ? "rotation-button expanded" : "rotation-button"} | ||||
| 				onClick={() => { | ||||
| 					setOpen(open => !open) | ||||
| 				}} | ||||
| 			> | ||||
| 				{encounterId === 0 ? "Current Rotation" : name} | ||||
| 			</button> | ||||
| 			<RotationContents expanded={open} actionList={actionList} /> | ||||
| 		</> | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| function RotationContents({ expanded, actionList }) { | ||||
| 	if (!expanded) return null | ||||
| 
 | ||||
| 	return ( | ||||
| 		<div className="rotation-list"> | ||||
| 			{actionList.map((action, i) => ( | ||||
| 				<Action key={i} actionId={action} additionalClasses="action-rotation" /> | ||||
| 			))} | ||||
| 		</div> | ||||
| 	) | ||||
| } | ||||
| @ -16,6 +16,16 @@ | ||||
| 	vertical-align: top; | ||||
| } | ||||
| 
 | ||||
| .action-rotation.gcd { | ||||
| 	margin-left: 1em; | ||||
| } | ||||
| 
 | ||||
| .action-rotation.gcd::before { | ||||
| 	content: '\25B6'; /* Unicode character for right arrow */ | ||||
| 	font-size: 13px; | ||||
| 	color: rgba(255, 255, 255, 0.5); | ||||
| } | ||||
| 
 | ||||
| @keyframes action-move { | ||||
| 	from { | ||||
| 		transform: translateX(calc(100vw - 3rem)); | ||||
|  | ||||
| @ -1,3 +1,20 @@ | ||||
| body { | ||||
| 	margin: 0; | ||||
| 	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", | ||||
| 		"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", | ||||
| 		sans-serif; | ||||
| 	-webkit-font-smoothing: antialiased; | ||||
| 	-moz-osx-font-smoothing: grayscale; | ||||
| 	font-size: 16px; | ||||
| } | ||||
| 
 | ||||
| html { | ||||
| 	height: 100vh; | ||||
| 	overflow: hidden; | ||||
| 	background-image: none; | ||||
| 	background-repeat: no-repeat; | ||||
| } | ||||
| 
 | ||||
| .actions { | ||||
| 	padding-top: 1rem; | ||||
| 	background: linear-gradient(180deg,  | ||||
|  | ||||
							
								
								
									
										32
									
								
								src/css/Rotation.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/css/Rotation.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| .rotation-list { | ||||
| 	padding: 0 18px; | ||||
| 	background-color: rgba(80, 80, 80, 0.3); | ||||
| 	display: block; | ||||
| } | ||||
| 
 | ||||
| .rotation-button { | ||||
| 	background-color: rgba(100, 100, 100, 0.3); | ||||
| 	color: rgba(255, 255, 255, 0.5); | ||||
| 	padding: 0.1em; | ||||
| 	width: 100vw; | ||||
| 	border: none; | ||||
| 	outline: none; | ||||
| 	transition: 0.4s; | ||||
| 	display: block; | ||||
| } | ||||
| 
 | ||||
| .rotation-button.expanded, .rotation-button:hover { | ||||
| 	background-color: rgba(120, 120, 120, 0.3); | ||||
| } | ||||
| 
 | ||||
| .rotation-button:after { | ||||
| 	content: '\25BC'; | ||||
| 	font-size: 13px; | ||||
| 	color: rgb(119,119,119); | ||||
| 	float: right; | ||||
| 	margin-left: 5px; | ||||
| } | ||||
| 
 | ||||
| .rotation-button.expanded:after { | ||||
| 	content: "\25B2"; | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								src/index.js
									
									
									
									
									
								
							| @ -1,12 +1,5 @@ | ||||
| import React from 'react'; | ||||
| import ReactDOM from 'react-dom'; | ||||
| import './css/index.css'; | ||||
| import App from './App'; | ||||
| import * as serviceWorker from './serviceWorker'; | ||||
| import React from 'react' | ||||
| import ReactDOM from 'react-dom' | ||||
| import App from './App' | ||||
| 
 | ||||
| ReactDOM.render(<App />, document.getElementById('root')); | ||||
| 
 | ||||
| // If you want your app to work offline and load faster, you can change
 | ||||
| // unregister() to register() below. Note this comes with some pitfalls.
 | ||||
| // Learn more about service workers: https://bit.ly/CRA-PWA
 | ||||
| serviceWorker.unregister(); | ||||
| ReactDOM.render(<App />, document.getElementById('root')) | ||||
|  | ||||
| @ -1,135 +0,0 @@ | ||||
| // This optional code is used to register a service worker.
 | ||||
| // register() is not called by default.
 | ||||
| 
 | ||||
| // This lets the app load faster on subsequent visits in production, and gives
 | ||||
| // it offline capabilities. However, it also means that developers (and users)
 | ||||
| // will only see deployed updates on subsequent visits to a page, after all the
 | ||||
| // existing tabs open on the page have been closed, since previously cached
 | ||||
| // resources are updated in the background.
 | ||||
| 
 | ||||
| // To learn more about the benefits of this model and instructions on how to
 | ||||
| // opt-in, read https://bit.ly/CRA-PWA
 | ||||
| 
 | ||||
| const isLocalhost = Boolean( | ||||
|   window.location.hostname === 'localhost' || | ||||
|     // [::1] is the IPv6 localhost address.
 | ||||
|     window.location.hostname === '[::1]' || | ||||
|     // 127.0.0.1/8 is considered localhost for IPv4.
 | ||||
|     window.location.hostname.match( | ||||
|       /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ | ||||
|     ) | ||||
| ); | ||||
| 
 | ||||
| export function register(config) { | ||||
|   if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { | ||||
|     // The URL constructor is available in all browsers that support SW.
 | ||||
|     const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); | ||||
|     if (publicUrl.origin !== window.location.origin) { | ||||
|       // Our service worker won't work if PUBLIC_URL is on a different origin
 | ||||
|       // from what our page is served on. This might happen if a CDN is used to
 | ||||
|       // serve assets; see https://github.com/facebook/create-react-app/issues/2374
 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     window.addEventListener('load', () => { | ||||
|       const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; | ||||
| 
 | ||||
|       if (isLocalhost) { | ||||
|         // This is running on localhost. Let's check if a service worker still exists or not.
 | ||||
|         checkValidServiceWorker(swUrl, config); | ||||
| 
 | ||||
|         // Add some additional logging to localhost, pointing developers to the
 | ||||
|         // service worker/PWA documentation.
 | ||||
|         navigator.serviceWorker.ready.then(() => { | ||||
|           console.log( | ||||
|             'This web app is being served cache-first by a service ' + | ||||
|               'worker. To learn more, visit https://bit.ly/CRA-PWA' | ||||
|           ); | ||||
|         }); | ||||
|       } else { | ||||
|         // Is not localhost. Just register service worker
 | ||||
|         registerValidSW(swUrl, config); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function registerValidSW(swUrl, config) { | ||||
|   navigator.serviceWorker | ||||
|     .register(swUrl) | ||||
|     .then(registration => { | ||||
|       registration.onupdatefound = () => { | ||||
|         const installingWorker = registration.installing; | ||||
|         if (installingWorker == null) { | ||||
|           return; | ||||
|         } | ||||
|         installingWorker.onstatechange = () => { | ||||
|           if (installingWorker.state === 'installed') { | ||||
|             if (navigator.serviceWorker.controller) { | ||||
|               // At this point, the updated precached content has been fetched,
 | ||||
|               // but the previous service worker will still serve the older
 | ||||
|               // content until all client tabs are closed.
 | ||||
|               console.log( | ||||
|                 'New content is available and will be used when all ' + | ||||
|                   'tabs for this page are closed. See https://bit.ly/CRA-PWA.' | ||||
|               ); | ||||
| 
 | ||||
|               // Execute callback
 | ||||
|               if (config && config.onUpdate) { | ||||
|                 config.onUpdate(registration); | ||||
|               } | ||||
|             } else { | ||||
|               // At this point, everything has been precached.
 | ||||
|               // It's the perfect time to display a
 | ||||
|               // "Content is cached for offline use." message.
 | ||||
|               console.log('Content is cached for offline use.'); | ||||
| 
 | ||||
|               // Execute callback
 | ||||
|               if (config && config.onSuccess) { | ||||
|                 config.onSuccess(registration); | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         }; | ||||
|       }; | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       console.error('Error during service worker registration:', error); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function checkValidServiceWorker(swUrl, config) { | ||||
|   // Check if the service worker can be found. If it can't reload the page.
 | ||||
|   fetch(swUrl) | ||||
|     .then(response => { | ||||
|       // Ensure service worker exists, and that we really are getting a JS file.
 | ||||
|       const contentType = response.headers.get('content-type'); | ||||
|       if ( | ||||
|         response.status === 404 || | ||||
|         (contentType != null && contentType.indexOf('javascript') === -1) | ||||
|       ) { | ||||
|         // No service worker found. Probably a different app. Reload the page.
 | ||||
|         navigator.serviceWorker.ready.then(registration => { | ||||
|           registration.unregister().then(() => { | ||||
|             window.location.reload(); | ||||
|           }); | ||||
|         }); | ||||
|       } else { | ||||
|         // Service worker found. Proceed as normal.
 | ||||
|         registerValidSW(swUrl, config); | ||||
|       } | ||||
|     }) | ||||
|     .catch(() => { | ||||
|       console.log( | ||||
|         'No internet connection found. App is running in offline mode.' | ||||
|       ); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function unregister() { | ||||
|   if ('serviceWorker' in navigator) { | ||||
|     navigator.serviceWorker.ready.then(registration => { | ||||
|       registration.unregister(); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user