1// Copyright (C) 2021 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {featureFlags} from '../common/feature_flags'; 16import {globals} from './globals'; 17 18let lastReloadDialogTime = 0; 19const kMinTimeBetweenDialogsMs = 10000; 20const changedPaths = new Set<string>(); 21 22export function initLiveReloadIfLocalhost() { 23 if (!location.origin.startsWith('http://localhost:')) return; 24 if (globals.embeddedMode) return; 25 26 const monitor = new EventSource('/live_reload'); 27 monitor.onmessage = (msg) => { 28 const change = msg.data; 29 console.log('Live reload:', change); 30 changedPaths.add(change); 31 if (change.endsWith('.css')) { 32 reloadCSS(); 33 } else if (change.endsWith('.html') || change.endsWith('.js')) { 34 reloadDelayed(); 35 } 36 }; 37 monitor.onerror = (err) => { 38 // In most cases the error is fired on reload, when the socket disconnects. 39 // Delay the error and the reconnection, so in the case of a reload we don't 40 // see any midleading message. 41 setTimeout(() => console.error('LiveReload SSE error', err), 1000); 42 }; 43} 44 45function reloadCSS() { 46 const css = document.querySelector('link[rel=stylesheet]') as HTMLLinkElement; 47 if (!css) return; 48 const parent = css.parentElement!; 49 parent.removeChild(css); 50 parent.appendChild(css); 51} 52 53const rapidReloadFlag = featureFlags.register({ 54 id: 'rapidReload', 55 name: 'Development: rapid live reload', 56 defaultValue: false, 57 description: 'During development, instantly reload the page on change. ' + 58 'Enables lower latency of live reload at the cost of potential ' + 59 'multiple re-reloads.', 60 devOnly: true, 61}); 62 63function reloadDelayed() { 64 setTimeout(() => { 65 let pathsStr = ''; 66 for (const path of changedPaths) { 67 pathsStr += path + '\n'; 68 } 69 changedPaths.clear(); 70 if (Date.now() - lastReloadDialogTime < kMinTimeBetweenDialogsMs) return; 71 const reload = 72 rapidReloadFlag.get() || confirm(`${pathsStr}changed, click to reload`); 73 lastReloadDialogTime = Date.now(); 74 if (reload) { 75 window.location.reload(); 76 } 77 }, rapidReloadFlag.get() ? 0 : 1000); 78} 79