PageSourceSearch

https://sfist.com/assets/js/weather.js?v=ca33af4392

js sfist.com collected 2026-10-01 08:30:35 UTC 4,389 bytes, 100 lines download raw bytes

1(function () {
2    'use strict';
3
4    var SCHEMA = 1;
5    var CACHE_KEY = 'sfist-weather-v1';
6    var CACHE_TTL = 15 * 60 * 1000;
7    var MAX_AGE = 3 * 60 * 60 * 1000;
8    var REQUEST_TIMEOUT = 5000;
9    var CONDITIONS = [
10        'Clear', 'Mostly sunny', 'Partly cloudy', 'Cloudy', 'Morning fog',
11        'Afternoon fog', 'Light rain', 'Showers', 'Storms', 'Windy', 'Forecast'
12    ];
13
14    function validPayload(payload, now) {
15        if (!payload || payload.schema !== SCHEMA || payload.location !== 'San Francisco' || payload.unit !== 'F') return false;
16        if (!Number.isInteger(payload.temperature) || payload.temperature < -20 || payload.temperature > 130) return false;
17        if (!Number.isInteger(payload.high) || payload.high < -20 || payload.high > 130) return false;
18        if (CONDITIONS.indexOf(payload.condition) === -1) return false;
19        var collected = Date.parse(payload.collectedAt);
20        var expires = Date.parse(payload.expiresAt);
21        return Number.isFinite(collected) && Number.isFinite(expires) &&
22            collected <= now + 5 * 60 * 1000 && now - collected <= MAX_AGE && expires > now;
23    }
24
25    function readCache(now) {
26        try {
27            var cached = JSON.parse(localStorage.getItem(CACHE_KEY));
28            if (!cached || !validPayload(cached.payload, now)) return null;
29            return cached;
30        } catch (error) {
31            return null;
32        }
33    }
34
35    function writeCache(payload, now) {
36        try {
37            localStorage.setItem(CACHE_KEY, JSON.stringify({cachedAt: now, payload: payload}));
38        } catch (error) {
39            // Storage can be unavailable in privacy modes; rendering still works.
40        }
41    }
42
43    function renderWeather(element, payload) {
44        var temperature = element.querySelector('[data-weather-temperature]');
45        var detail = element.querySelector('[data-weather-detail]');
46        if (!temperature || !detail) return;
47        temperature.textContent = payload.temperature + '°';
48        detail.textContent = payload.condition + ' · High ' + payload.high + '°';
49        element.setAttribute('aria-label', 'San Francisco weather: ' + payload.temperature + '°, ' + payload.condition.toLowerCase() + ', high ' + payload.high + '°');
50        element.setAttribute('aria-hidden', 'false');
51        element.dataset.weatherState = 'ready';
52        element.title = 'National Weather Service forecast, updated ' + new Date(payload.collectedAt).toLocaleTimeString([], {hour: 'numeric', minute: '2-digit'});
53    }
54
55    async function fetchWeather(endpoint) {
56        var controller = typeof AbortController === 'function' ? new AbortController() : null;
57        var timer = controller ? window.setTimeout(function () { controller.abort(); }, REQUEST_TIMEOUT) : null;
58        try {
59            var response = await fetch(endpoint, {
60                method: 'GET',
61                mode: 'cors',
62                credentials: 'omit',
63                headers: {Accept: 'application/json'},
64                signal: controller ? controller.signal : undefined
65            });
66            if (!response.ok) throw new Error('Weather endpoint returned ' + response.status);
67            return response.json();
68        } finally {
69            if (timer) window.clearTimeout(timer);
70        }
71    }
72
73    async function initializeWeather(element, now) {
74        var cached = readCache(now);
75        if (cached) {
76            renderWeather(element, cached.payload);
77            if (now - Number(cached.cachedAt || 0) < CACHE_TTL) return;
78        }
79        try {
80            var payload = await fetchWeather(element.dataset.weatherEndpoint);
81            var responseTime = Date.now();
82            if (!validPayload(payload, responseTime)) throw new Error('Weather payload failed validation');
83            writeCache(payload, responseTime);
84            renderWeather(element, payload);
85        } catch (error) {
86            if (!cached) {
87                element.dataset.weatherState = 'unavailable';
88                element.setAttribute('aria-hidden', 'true');
89            }
90        }
91    }
92
93    if (typeof module !== 'undefined' && module.exports) {
94        module.exports = {validPayload: validPayload, renderWeather: renderWeather};
95        return;
96    }
97
98    var weather = document.querySelector('[data-weather-endpoint]');
99    if (weather) initializeWeather(weather, Date.now());
100}());

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.