🚧 Added more settings & cleanup

main
Basil 6 months ago
parent 2975475501
commit 2bd709d5b9

5
.gitignore vendored

@ -2,8 +2,3 @@ dist/
node_modules/
.DS_Store
.vscode/
.firebaserc
.firebase/
firebase-debug.log
yarn.lock
plan8-klang-sync.js

11868
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,27 +0,0 @@
[
{
"relation": [
"delegate_permission/common.handle_all_urls"
],
"target": {
"namespace": "android_app",
"package_name": "com.google.android.apps.santatracker",
"sha256_cert_fingerprints": [
"F4:CB:0B:81:9F:14:B7:FB:BE:3E:61:3E:AF:86:01:10:B5:18:62:76:65:40:27:CD:52:5B:27:DC:23:2E:9C:8E",
"7B:5A:D5:51:80:A4:8A:1F:30:F3:53:77:C0:F9:E5:F9:11:BF:94:2F:B4:CF:83:EB:A2:55:A0:EB:F5:80:BE:EF"
]
}
},
{
"relation": [
"delegate_permission/common.handle_all_urls"
],
"target": {
"namespace": "android_app",
"package_name": "com.google.android.apps.santatracker.debug",
"sha256_cert_fingerprints": [
"89:53:FE:3E:7D:97:CF:46:7B:B1:70:C7:77:12:54:FB:59:D8:D0:9F:20:A8:32:4A:3A:9F:B0:0F:2A:7A:83:8E"
]
}
}
]

@ -1,176 +0,0 @@
<!DOCTYPE html>
<!--
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<title msgid="meta_title"></title>
<meta charset="utf-8" />
<script src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>
<style type="text/css">
html {
background: black;
}
body {
margin: 0;
}
.op {
transition: opacity 0.5s;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
#wrap {
background: black;
}
#loader {
background: #18854b;
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
body.loaded #loader {
opacity: 0;
}
.gone {
opacity: 0;
}
video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="op" id="loader">
<img src="images/icon-512.png" width="256" height="256" />
</div>
<div id="wrap">
<video id="player" class="op" playsinline></video>
<video id="playerExtra" class="op gone" playsinline></video>
</div>
<script type="module">
const allVideos = [
'comroom',
'carpool',
'jingle',
'museum',
'office',
'onvacation',
'penguinproof',
'reindeerworries',
'reload',
'santasback',
'satellite',
'selfies',
'slackingoff',
'temptation',
'tired',
'wheressanta',
'workshop',
];
allVideos.sort(() => Math.random() - 0.5); // shuffle order
cast.framework.CastReceiverContext.getInstance().start({disableIdleTimeout: true});
function urlForVideo(id) {
let quality = '1080p';
if (id === null) {
id = 'santafire';
quality = '900p';
}
return `https://firebasestorage.googleapis.com/v0/b/santa-api.appspot.com/o/videos%2F${id}_${quality}.mp4?alt=media`;
}
const player = /** @type {!HTMLVideoElement} */ (document.getElementById('player'));
const playerExtra = /** @type {!HTMLVideoElement} */ (document.getElementById('playerExtra'));
player.src = urlForVideo(null);
player.loop = true;
player.addEventListener('canplaythrough', () => {
document.body.classList.add('loaded');
player.play();
ready();
}, {once: true});
// Pick between 40-90 seconds, on 10-second intervals.
function randomTime() {
const raw = 40 + (Math.random() * 50);
const seconds = Math.round(raw / 10) * 10;
return seconds * 1000;
}
function ready() {
function playNext() {
// Videos will always shuffle the same way.
const id = allVideos.shift();
allVideos.push(id);
playerExtra.src = urlForVideo(id);
}
let timeout = 0;
const enqueueNext = () => {
window.clearTimeout(timeout);
const time = randomTime();
console.info('playing next video in', time);
timeout = window.setTimeout(playNext, time);
};
playerExtra.addEventListener('canplaythrough', () => {
player.pause();
playerExtra.play();
player.classList.add('gone');
playerExtra.classList.remove('gone');
});
const fadeToNext = () => {
playerExtra.removeAttribute('src');
player.play();
window.setTimeout(() => {
playerExtra.classList.add('gone');
player.classList.remove('gone');
enqueueNext();
}, 250);
};
playerExtra.addEventListener('error', (event) => {
console.warn('error in playerExtra', event.error, event.target.error);
window.setTimeout(() => {
if (playerExtra.paused) {
fadeToNext();
}
}, 2000);
});
playerExtra.addEventListener('ended', fadeToNext);
enqueueNext();
}
// for dev
document.body.onclick = () => {
player.play();
};
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 644 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

@ -1,111 +0,0 @@
<!DOCTYPE html>
<!--
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<title data-title></title>
<meta property="og:title" itemprop="name" data-title />
<meta property="og:type" content="website" />
<meta property="og:image" itemprop="image" name="thumbnail" content="https://santatracker.google.com/images/og.png" />
<meta property="og:image:width" content="1333" />
<meta property="og:image:height" content="1000" />
<meta property="og:description" itemprop="description" name="description" msgid="village_explore" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@google" />
<meta name="twitter:title" data-title />
<meta name="twitter:description" msgid="village_explore" />
<meta name="twitter:image" content="https://santatracker.google.com/images/og.png" />
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
<link rel="manifest" crossorigin="use-credentials" href="./manifest.json" />
<link href="https://maps.gstatic.com" rel="preconnect" crossorigin />
<link href="https://fonts.gstatic.com" rel="preconnect" crossorigin />
<link href="https://santa-api.appspot.com" rel="preconnect" crossorigin />
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,600|Lobster|Google+Sans:400,500,700" rel="stylesheet" />
<!-- Icons -->
<link rel="apple-touch-icon" href="/images/icon-76.png" sizes="76x76" />
<link rel="apple-touch-icon" href="/images/icon-120.png" sizes="120x120" />
<link rel="apple-touch-icon" href="/images/icon-152.png" sizes="152x152" />
<link rel="apple-touch-icon" href="/images/icon-167.png" sizes="167x167" />
<link rel="apple-touch-icon" href="/images/icon-180.png" sizes="180x180" />
<link rel="apple-touch-icon" href="/images/icon-512.png" sizes="512x512" />
<link id="favicon" rel="shortcut icon" href="/images/favicon-16.png" sizes="16x16" />
<link id="favicon" rel="shortcut icon" href="/images/favicon-32.png" sizes="32x32" />
<link id="favicon" rel="shortcut icon" href="/images/favicon-64.png" sizes="64x64" />
<link id="favicon" rel="shortcut icon" href="/images/favicon-96.png" sizes="96x96" />
<!-- CSS -->
<style>
@keyframes loader-move {
50% { transform: translateY(-100%); }
}
body {
background: #1a844b;
margin: 0;
}
.loader {
position: fixed;
top: 80%;
left: calc(50% - 160px);
width: 320px;
display: flex;
justify-content: center;
}
.loader u {
transform: translateY(0);
width: 16px;
height: 16px;
border-radius: 16px;
margin: 5px;
animation: loader-move 1.6s cubic-bezier(0.50, 0.1, 0.50, 1) infinite;
}
.loader u:nth-child(1) {
background: #9fceff;
animation-delay: -0.80s;
}
.loader u:nth-child(2) {
background: #ffb1b1;
animation-delay: -0.60s;
}
.loader u:nth-child(3) {
background: #fff173;
animation-delay: -0.40s;
}
.loader u:nth-child(4) {
animation-delay: -0.20s;
background: #a8e9a2;
}
</style>
<script type="module" src="/loader.js"></script>
<!-- <script type="module" src="../static/entrypoint.js"></script> -->
</head>
<body class="loading">
<!-- <noscript>
<meta http-equiv="refresh" content="0;url=/upgrade.html" />
</noscript> -->
<div class="loader"><u></u><u></u><u></u><u></u></div>
</body>
</html>

@ -1,149 +0,0 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Loader code for Santa Tracker. Adds a <script ...> pointing to the entrypoint.
*
* This is transpiled down to ES5-compatible code before being run. It should not use modern
* library code, like `Promise`, until the support library is loaded.
*/
// import config from './src/:config.json';
import checkFallback from './src/fallback.js';
import * as load from './src/load.js';
// import {initialize} from './src/firebase.js';
import onInteractive from './src/interactive.js';
import isAndroidTWA from './src/android-twa.js';
const config = {
staticScope: 'http://localhost:3000'
}
// In prod, the documentElement has `lang="en"` or similar.
const documentLang = document.documentElement.lang || null;
const isProd = (documentLang !== null);
const fallback = checkFallback() || (location.search || '').match(/\bfallback=.*?\b/);
const ignoreErrors = (location.search || '').match(/\bignore=.*?\b/);
// Global error handler. Redirect if we fail to load the entrypoint.
let loaded = false;
window.onerror = (msg, file, line, col, error) => {
console.error('error (loaded=' + loaded + ')', msg, file, line, col, error);
if (location.hostname === 'santatracker.google.com' && !loaded && !ignoreErrors) {
window.location.href = 'error.html';
}
};
window.onunhandledrejection = (event) => {
console.warn('rejection (loaded=' + loaded + ')', event.reason);
if (location.hostname === 'santatracker.google.com' && !loaded && !ignoreErrors) {
window.location.href = 'error.html';
}
};
// Add this early. We get it very aggressively from Chrome and friends.
window.installEvent = null;
window.addEventListener('beforeinstallprompt', (event) => {
window.installEvent = event;
});
// window.sw = null;
// let hasInstalledServiceWorker = false;
// if ('serviceWorker' in navigator) {
// // Register the SW in the served language, not the request language (as this isn't available
// // on the naked domain anyway).
// const params = new URLSearchParams();
// if (isProd) {
// params.set('baseurl', config.baseurl);
// }
// window.sw = navigator.serviceWorker.register(`/sw.js?${params.toString()}`).catch((err) => {
// console.warn('sw failed to register', err);
// return null;
// });
// hasInstalledServiceWorker = Boolean(navigator.serviceWorker.controller);
// navigator.serviceWorker.addEventListener('controllerchange', () => {
// loaded = true; // pretend that we're loaded, so that Safari doesn't send us to an error page
// window.location.reload();
// });
// }
// Load support code for fallback browsers like IE11, non-Chromium Edge, and friends. This is
// needed before using Firebase, as it requires Promise and fetch. This always uses the deployed
// static version, as we only potentially replace it below after Firebase is ready.
if (fallback && isProd) {
load.supportScripts([
config.staticScope + 'support.js',
config.staticScope + 'node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js',
], () => {
WebComponents.waitFor(() => {
onInteractive(startup); // should be past DOMContentLoaded now
});
});
} else {
onInteractive(startup);
}
function sanitizeStaticScope(arg) {
const staticScopeUrl = new URL(arg, window.location);
if (!staticScopeUrl.pathname.match(/\/$/)) {
staticScopeUrl.pathname += '/';
}
return staticScopeUrl.toString();
}
function startup() {
// Check Android TWA, but force it if the "?android=1" param is set.
const startParams = new URLSearchParams(window.location.search);
isAndroidTWA(startParams.has('android'));
// Wait for both Firebase Remote Config and the Service Worker (optional), then load entrypoint.
// This is racey in that a Service Worker change might trigger a reload.
// const ready = Promise.all([initialize(), window.sw]);
// ready.then(([remoteConfig, registration]) => {
// if (remoteConfig.getBoolean('switchOff')) {
// throw new Error('switchOff');
// }
// Allow Firebase force or optional ?static=... for new releases.
// const forceStaticScope = remoteConfig.getString('staticScope') || null;
// if (forceStaticScope) {
// if (!isProd) {
// // This arguably makes no sense here, as the files probably have the wrong suffix (i18n),
// // and you control your own dev environment.
// console.warn('ignoring custom static scope for dev', forceStaticScope);
// } else {
// console.warn('using custom static scope', forceStaticScope);
// try {
// config.staticScope = sanitizeStaticScope(forceStaticScope);
// } catch (e) {
// // don't set an invalid URL
// }
// }
// }
document.body.setAttribute('static', config.staticScope);
// Load entrypoint.
const entrypoint = config.staticScope + (fallback ? 'fallback' : 'entrypoint') + (isProd ? '_' + documentLang : '') + '.js';
return load.script(entrypoint, fallback && isProd ? '' : 'module').then(() => {
loaded = true;
});
// });
}

@ -1,44 +0,0 @@
{
"name": "Google Santa Tracker",
"short_name": "Santa",
"start_url": "./?utm_source=web_app_manifest&utm_medium=web_app_manifest",
"scope": "/",
"display": "standalone",
"orientation": "any",
"theme_color": "#416d98",
"background_color": "#3ec4f0",
"icons": [
{
"src": "/images/favicon-32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "/images/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/images/icon-256.png",
"sizes": "256x256",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/images/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
],
"related_applications": [
{
"platform": "web"
},
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=com.google.android.apps.santatracker"
}
]
}

@ -1,3 +0,0 @@
Disallow: /upgrade.html
Disallow: /cast.html
Disallow: /error.html

@ -1,41 +0,0 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Safeguard sessionStorage in case a browser's Private mode prevents use.
export const sessionStorage = window.sessionStorage || {};
/**
* Checks whether this is an Android TWA. Note that this has side-effects and persists this state
* for the current session.
*
* @param {boolean=} force whether to force enable
* @return {boolean} whether this is an Android TWA load
*/
export default function isAndroidTWA(force = false) {
// NOTE: This detection may fail when the user swipes down and refreshes the page, so we
// should persist the state somehow, e.g., local storage or URL modification. See:
// https://stackoverflow.com/q/54580414
if (sessionStorage['android-twa'] ||
document.referrer.startsWith('android-app://com.google.android.apps.santatracker') ||
force) {
sessionStorage['android-twa'] = true;
document.body.setAttribute('data-mode', 'android');
return true;
}
return false;
}

@ -1,63 +0,0 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Determines load type. Santa Tracker supports modern browsers like Chrome (and Chromium-based
* browsers), Firefox and Safari. We load a fallback environment (and polyfills) if the browser
* does not hit minimum standards.
*
* @return {boolean} whether to load fallback environment
*/
export default function() {
try {
if (!('ShadowRoot' in window)) {
throw 'Shadow DOM';
}
if (!('customElements' in window)) {
throw 'Custom Elements';
}
if (!CSS.supports("(--foo: red)")) {
// need CSS variable support for most modern scenes and the modern entrypoint
throw 'CSS Variables';
}
if (!('noModule' in HTMLScriptElement.prototype)) {
// modern code is loaded as modules
throw '<script type="module">';
}
if (!('URLSearchParams' in window)) {
// stops IE11
throw 'URLSearchParams';
}
if (!('Symbol' in window)) {
// stops IE11
throw 'Symbol';
}
if (!('includes' in String.prototype && 'startsWith' in String.prototype && 'includes' in Array.prototype && 'from' in Array)) {
// stops IE11 and browsers without standard niceities
throw 'arraylike helpers';
}
if (!('append' in document.head)) {
// friendly node helpers (nb. do NOT use 'body', doesn't exist yet)
throw 'append';
}
} catch (e) {
console.warn('loading fallback, failure:', e);
return true;
}
return false;
}

@ -1,73 +0,0 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Initialize function to prepare Firebase and Remote Config.
*/
import firebase from 'firebase/app';
import 'firebase/remote-config';
import defaults from './remote-config-defaults.js';
import isAndroidTWA from './android-twa.js';
export const firebaseConfig = {
apiKey: 'AIzaSyBrNcGcna0TMn2uLRxhMBwxVwXUBjlZqzU',
authDomain: 'santa-api.firebaseapp.com',
databaseURL: 'https://santa-api.firebaseio.com',
projectId: 'santa-api',
storageBucket: 'santa-api.appspot.com',
messagingSenderId: '593146395815',
appId: '1:593146395815:web:d766962076fbbd13492f82',
measurementId: 'G-EWRYGZS6D3',
};
export function initialize() {
if (isAndroidTWA()) {
// Swap for TWA (dev and prod)
firebaseConfig.appId = '1:593146395815:web:aefb4c5b5e01137f492f82';
firebaseConfig.measurementId = 'G-0X2VE68GZD';
} else if (window.location.hostname !== 'santatracker.google.com') {
// Swap for dev
firebaseConfig.appId = '1:593146395815:web:54c339298196fd10492f82';
firebaseConfig.measurementId = 'G-GPEHME4LVG';
}
firebase.initializeApp(firebaseConfig);
// Fetch RC, with a fetch timeout of 30s and a key expiry of ~1 minute. This is reset later via
// the config itself.
let minimumFetchIntervalMillis = 1000 * 60;
if (!navigator.onLine) {
// If the browser thinks we're offline, then allow a much larger range of cached keys (~12
// hours, the default).
minimumFetchIntervalMillis *= (12 * 60);
}
const remoteConfig = firebase.remoteConfig();
remoteConfig.settings = {
fetchTimeoutMillis: 30 * 1000,
minimumFetchIntervalMillis,
};
remoteConfig.defaultConfig = defaults;
window.firebase = firebase; // side-effect
return remoteConfig.fetchAndActivate().catch((err) => {
ga('send', 'event', 'config', 'failure', 'firebase', {nonInteraction: true});
console.warn('could not fetch remoteConfig, using defaults', err);
}).then(() => {
return remoteConfig;
});
}

@ -1,36 +0,0 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let done = (document.readyState === 'interactive' || document.readyState === 'complete');
let all;
if (!done) {
all = [];
window.addEventListener('DOMContentLoaded', () => {
done = true;
all.forEach((fn) => fn());
all = undefined;
});
}
export default function onInteractive(fn) {
if (!done) {
all.push(fn);
} else {
fn();
}
}

@ -1,81 +0,0 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Loads the passed script.
*
* @param {string} src
* @param {?string=} type
* @return {!Promise<void>}
*/
export function script(src, type=null) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
if (type) {
script.type = type;
}
script.setAttribute('crossorigin', 'anonymous');
script.onload = () => resolve();
script.onerror = reject;
document.head.append(script);
});
}
/**
* Loads a number of passed scripts, without Promise. These unfortunately load in-order.
*
* @param {!Array<string>} scripts to load
* @param {function(): void} callback to call when done
*/
export function supportScripts(scripts, callback) {
const next = () => {
const src = scripts.shift();
if (src === undefined) {
callback();
return;
}
const script = document.createElement('script');
script.src = src;
script.setAttribute('crossorigin', 'anonymous');
script.onload = next;
script.onerror = () => {
console.warn('cannot load', src);
next();
};
document.head.appendChild(script);
};
next();
}
/**
* @param {string} src to load as global CSS
* @return {!Promise<void>}
*/
export function css(src) {
return new Promise((resolve, reject) => {
const css = document.createElement('link');
css.href = src;
css.type = 'stylesheet';
css.onload = () => resolve();
css.onerror = reject;
document.head.append(css);
});
}

@ -1,62 +0,0 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Provides defaults for Firebase Remote Config.
*
* This is a sensible, low-key fallback configuration in case the RC service fails for new users
* (existing users will always use cached values, as there's no real way to indicate that they
* could be fundamentally out-of-date).
*/
var defaults = {
featured: {},
nav: ['@cityquiz','@rocketsleigh','@dasherdancer','@snowballrun','@presenttoss','@penguinswim','santaselfie','codeboogie','jetpack','jamband','snowball','elfmaker','codelab','wrapbattle','penguindash','build','matching','museum','boatload','takeoff','gumball','presentbounce','glider','speedsketch','santascanvas','seasonofgiving','penguinproof','traditions','wheressanta','santasearch','translations','runner','snowbox','mercator','windtunnel'],
fallbackIndexScene: 'retro',
indexScene: 'modvil',
switchOff: false,
upgradeToVersion: '',
sceneRedirect: {'educators':'familyguide','press':'familyguide','tracker':'','village':''},
routeUrl: 'https://firebasestorage.googleapis.com/v0/b/santa-tracker-firebase.appspot.com/o/route%2Fsanta_|LANG|.json?alt=media&2018b',
sceneLock: {},
videos: ['carpool','comroom','jingle','liftoff','museum','office','onvacation','penguinproof','reindeerworries','reload','santasback','satellite','selfies','slackingoff','takeoff','temptation','tired','wheressanta','workshop','likealight','yulelog'],
refreshEvery: 60, // this is low, _because_ we're offline
useGeoIP: true,
showTracker: false,
routeJitter: 10,
};
var now = new Date();
if (now.getMonth() === 9 || now.getMonth() === 10) {
// Oct-Nov
} else if (now.getMonth() !== 11) {
// Jan-Sep
} else {
// Dec
}
// Firebase Remote Config only returns strings, so wrap everything.
for (const key in defaults) {
const v = defaults[key];
if (typeof v !== 'string') {
defaults[key] = JSON.stringify(defaults[key]);
}
}
export default defaults;

@ -1,206 +0,0 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Service Worker for Santa Tracker.
*/
const debug = false;
const swUrl = new URL(self.location);
const baseurl = swUrl.searchParams.get('baseurl') || '';
console.info('SW', baseurl);
const STATIC_VERSION_HEADER = 'X-Santa-Version';
const IGNORE_PROD = ['cast', 'error', 'upgrade'];
const IGNORE_STATIC_PREFIX = ['/audio/', '/scenes/'];
const PRECACHE = [
'/',
'/error.html',
'/manifest.json',
'/loader.js',
'/images/favicon-32.png',
'/images/icon-192.png',
'/images/icon-256.png',
'/images/icon-512.png',
];
let replacingPreviousServiceWorker = false;
self.addEventListener('install', (event) => {
console.info('SW install');
const call = async () => {
// This is non-null if there was a previous Service Worker registered. Record for "activate", so
// that a lack of current architecture can be seen as a reason to reload our clients.
if (self.registration.active) {
replacingPreviousServiceWorker = true;
}
const prodCache = await caches.open('prod');
try {
await Promise.all(PRECACHE.map((url) => prodCache.add(url)));
} catch (e) {
console.error('failed to fetch', e);
}
console.info('precached', PRECACHE.length, 'prod URLs');
await self.skipWaiting();
};
event.waitUntil(call());
});
self.addEventListener('activate', (event) => {
console.info('SW activate, replacing:', replacingPreviousServiceWorker);
const call = async () => {
// We can't use client.navigate here, as Safari doesn't support it. The 'controllerchange'
// event in the foreground handles this.
await self.clients.claim();
};
event.waitUntil(call());
});
function staticRequestPath(url) {
if (!baseurl) {
// do nothing, not prod
} else if (baseurl.startsWith('/')) {
// for staging on a single domain
const u = new URL(url);
if (u.hostname === location.hostname && u.pathname.startsWith(baseurl)) {
return u.pathname.substr(baseurl.length);
}
} else if (url.startsWith(baseurl)) {
return url.substr(baseurl.length);
}
}
/**
* @param {string} raw pathname
* @return {{intl: string, pathname: string}}
*/
function splitProdPath(raw) {
// get prefix, will be blank or e.g. "/intl/foo-BAR"
const intlMatch = raw.match(/^(\/intl\/.*?)\//);
const intlPrefix = intlMatch ? intlMatch[1] : '';
let pathname = raw.substr(intlPrefix.length);
const routeMatch = pathname.match(/^\/(?:(\w+)\.html|)(\?|$)/);
if (routeMatch && !IGNORE_PROD.includes(routeMatch[1])) {
pathname = '/'; // don't use /index.html, as our Go server redirects in staging (breaks Safari)
}
return {intl: intlPrefix, pathname};
}
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') {
return;
}
const url = new URL(event.request.url);
// Don't check domain for static requests; in dev, it's the same domain.
const naked = staticRequestPath(event.request.url);
if (naked)