PageSourceSearch

https://jetsetfm.com/wp-content/plugins/jetsetfm-growth/assets/growth.js

js jetsetfm.com collected 2026-10-02 00:03:06 UTC 12,446 bytes, 272 lines download raw bytes

1(function () {
2  'use strict';
3
4  const config = window.JSFMGrowth || {};
5  const audio = document.querySelector('[data-jsfm-audio]');
6  const playButtons = document.querySelectorAll('[data-jsfm-play]');
7  const player = document.querySelector('[data-jsfm-player]');
8  let listenedSeconds = Number(sessionStorage.getItem('jsfmListenedSeconds') || 0);
9  let listenTimer = null;
10  let activeAudio = null;
11
12  function identifier(storage, key) {
13    let value = storage.getItem(key);
14    if (!value) {
15      value = window.crypto?.randomUUID?.() || (Date.now().toString(16) + Math.random().toString(16).slice(2));
16      storage.setItem(key, value);
17    }
18    return value;
19  }
20
21  const sessionId = identifier(sessionStorage, 'jsfmSessionId');
22  const listenerId = identifier(localStorage, 'jsfmListenerId');
23
24  function attribution() {
25    const params = new URLSearchParams(window.location.search);
26    const incoming = {
27      campaign: params.get('utm_campaign') || '',
28      source: params.get('utm_source') || '',
29      medium: params.get('utm_medium') || ''
30    };
31    if (incoming.campaign || incoming.source || incoming.medium) {
32      localStorage.setItem('jsfmAttribution', JSON.stringify(incoming));
33      return incoming;
34    }
35    try {
36      return JSON.parse(localStorage.getItem('jsfmAttribution') || '{}');
37    } catch (error) {
38      return {};
39    }
40  }
41
42  const conversion = document.querySelector('.jsfm-conversion');
43  if (conversion && conversion !== document.body.firstElementChild) {
44    document.body.insertBefore(conversion, document.body.firstChild);
45  }
46
47  function campaign() {
48    const params = new URLSearchParams(window.location.search);
49    return params.get('utm_campaign') || document.querySelector('[data-campaign]')?.dataset.campaign || attribution().campaign || '';
50  }
51
52  function market() {
53    const params = new URLSearchParams(window.location.search);
54    return params.get('utm_content') || document.documentElement.lang || 'en-US';
55  }
56
57  function track(event, details) {
58    const source = attribution();
59    const payload = Object.assign({
60      event,
61      session_id: sessionId,
62      listener_id: listenerId,
63      campaign: campaign(),
64      source: source.source || '',
65      medium: source.medium || '',
66      market: market(),
67      path: window.location.pathname,
68      platform: /Android|iPhone|iPad|Mobile/i.test(navigator.userAgent) ? 'mobile-web' : 'desktop-web'
69    }, details || {});
70    window.dataLayer = window.dataLayer || [];
71    window.dataLayer.push(payload);
72    window.dispatchEvent(new CustomEvent('jetsetfm:' + event, { detail: payload }));
73    if (config.restUrl) {
74      navigator.sendBeacon?.(config.restUrl + 'analytics', new Blob([JSON.stringify(payload)], { type: 'application/json' }));
75    }
76  }
77
78  function setPlaying(playing) {
79    playButtons.forEach((button) => {
80      button.textContent = playing ? 'Pause' : 'Listen Live';
81      button.setAttribute('aria-label', playing ? 'Pause JetSetFM' : 'Play JetSetFM');
82    });
83    player?.classList.toggle('is-playing', playing);
84  }
85
86  function beginListening(media) {
87    activeAudio = media;
88    setPlaying(true);
89    if (!sessionStorage.getItem('jsfmListenStarted')) {
90      track('listen_start');
91      sessionStorage.setItem('jsfmListenStarted', '1');
92      const prior = Number(localStorage.getItem('jsfmLastListen') || 0);
93      if (prior && Date.now() - prior <= 7 * 24 * 60 * 60 * 1000) track('listen_return_7d');
94      localStorage.setItem('jsfmLastListen', String(Date.now()));
95    }
96    clearInterval(listenTimer);
97    listenTimer = window.setInterval(() => {
98      if (!activeAudio || activeAudio.paused || activeAudio.ended) return;
99      listenedSeconds += 1;
100      sessionStorage.setItem('jsfmListenedSeconds', String(listenedSeconds));
101      if (listenedSeconds >= 1800 && !sessionStorage.getItem('jsfmListen30Reported')) {
102        track('listen_30_minutes');
103        sessionStorage.setItem('jsfmListen30Reported', '1');
104      }
105    }, 1000);
106  }
107
108  function stopListening(media) {
109    if (activeAudio !== media) return;
110    clearInterval(listenTimer);
111    setPlaying(false);
112  }
113
114  function observeAudio(media) {
115    if (media.dataset.jsfmObserved) return;
116    media.dataset.jsfmObserved = '1';
117    media.addEventListener('playing', () => beginListening(media));
118    media.addEventListener('pause', () => stopListening(media));
119    media.addEventListener('ended', () => stopListening(media));
120  }
121
122  function isGenericAlt(alt) {
123    return ['', 'image', 'cover', 'chart track'].includes(String(alt || '').trim().toLowerCase());
124  }
125
126  function altFromSrc(src, currentAlt) {
127    const known = [
128      ['1000x1000bb-1', "Album artwork for I Ain't No Joke by Eric B. & Rakim"],
129      ['1000x1000bb-2', 'Album artwork for Christmas In Hollis by Run-DMC'],
130      ['1000x1000bb-3', 'Album artwork for Needle To the Groove by Mantronix'],
131      ['jetsetfm-hip-hop', 'JetSetFM hip-hop radio culture feature artwork'],
132      ['jetsetfm-disco', 'JetSetFM disco music history feature artwork'],
133      ['jetsetfm-radio-music', 'JetSetFM internet radio music feature artwork'],
134      ['pexels-photo-3642350', 'Vintage cassette tapes representing classic hip-hop tracks'],
135      ['jet600x600', 'JetSetFM station artwork']
136    ];
137    const lowerSrc = String(src || '').toLowerCase();
138    const match = known.find(([needle]) => lowerSrc.includes(needle));
139    if (match) return match[1];
140    const normalized = String(currentAlt || '').trim().toLowerCase();
141    if (normalized === 'chart track') return 'Chart track artwork';
142    if (normalized === 'cover') return 'Album cover artwork';
143    return 'JetSetFM image';
144  }
145
146  function repairImageAlt(image) {
147    if (!(image instanceof HTMLImageElement) || !isGenericAlt(image.getAttribute('alt'))) return;
148    image.setAttribute('alt', altFromSrc(image.currentSrc || image.src, image.getAttribute('alt')));
149  }
150
151  document.querySelectorAll('audio').forEach(observeAudio);
152  document.querySelectorAll('img').forEach(repairImageAlt);
153  new MutationObserver((records) => records.forEach((record) => record.addedNodes.forEach((node) => {
154    if (!(node instanceof Element)) return;
155    if (node.matches('audio')) observeAudio(node);
156    node.querySelectorAll?.('audio').forEach(observeAudio);
157    if (node.matches('img')) repairImageAlt(node);
158    node.querySelectorAll?.('img').forEach(repairImageAlt);
159  }))).observe(document.documentElement, { childList: true, subtree: true });
160
161  playButtons.forEach((button) => button.addEventListener('click', async () => {
162    if (!audio) return;
163    if (audio.paused) {
164      try {
165        await audio.play();
166      } catch (error) {
167        setPlaying(false);
168      }
169    } else {
170      audio.pause();
171    }
172  }));
173
174  document.querySelectorAll('[data-jsfm-event]').forEach((element) => {
175    element.addEventListener('click', () => track(element.dataset.jsfmEvent, { store: element.dataset.store || '' }));
176  });
177
178  function clearCurrentMetadata(message) {
179    document.querySelectorAll('[data-jsfm-track-title]').forEach((el) => { el.textContent = message; });
180    document.querySelectorAll('[data-jsfm-track-artist]').forEach((el) => { el.textContent = ''; });
181    document.querySelectorAll('[data-jsfm-now-playing]').forEach((el) => { el.textContent = message; });
182  }
183
184  let metadataLoading = false;
185  async function loadMetadata() {
186    if (!config.restUrl) return;
187    if (metadataLoading) return;
188    metadataLoading = true;
189    const controller = new AbortController();
190    const timeout = window.setTimeout(() => controller.abort(), 8000);
191    try {
192      const response = await fetch(config.restUrl + 'now-playing', { cache: 'no-store', signal: controller.signal });
193      if (!response.ok) throw new Error('Metadata unavailable');
194      const data = await response.json();
195      const checkedAt = Date.parse(data?.checked_at || '');
196      const fresh = Number.isFinite(checkedAt) && Date.now() - checkedAt <= 120000 && checkedAt - Date.now() <= 60000;
197      if (!fresh) {
198        clearCurrentMetadata('Track information temporarily unavailable');
199      } else if (data.status === 'break') {
200        clearCurrentMetadata('Station break');
201      } else if (data.status === 'playing' && typeof data.current?.title === 'string' && data.current.title.trim()) {
202        document.querySelectorAll('[data-jsfm-track-title]').forEach((el) => { el.textContent = data.current.title; });
203        document.querySelectorAll('[data-jsfm-track-artist]').forEach((el) => { el.textContent = data.current.artist; });
204        document.querySelectorAll('[data-jsfm-now-playing]').forEach((el) => { el.textContent = 'Now playing: ' + data.current.artist + ' - ' + data.current.title; });
205      } else {
206        clearCurrentMetadata('Track information temporarily unavailable');
207      }
208      if (Array.isArray(data.recent)) {
209        document.querySelectorAll('[data-jsfm-recent-list]').forEach((list) => {
210          list.replaceChildren(...data.recent.slice(0, 10).map((track) => {
211            const item = document.createElement('li');
212            item.textContent = track.artist + ' - ' + track.title;
213            return item;
214          }));
215        });
216      }
217    } catch (error) {
218      // The live stream remains available when metadata is temporarily unavailable.
219      clearCurrentMetadata('Track information temporarily unavailable');
220    } finally {
221      window.clearTimeout(timeout);
222      metadataLoading = false;
223    }
224  }
225  loadMetadata();
226  window.setInterval(loadMetadata, 30000);
227
228  document.querySelectorAll('[data-jsfm-reminder]').forEach((button) => button.addEventListener('click', () => {
229    const chicagoParts = Object.fromEntries(new Intl.DateTimeFormat('en-US', {
230      timeZone: 'America/Chicago', year: 'numeric', month: '2-digit', day: '2-digit', weekday: 'short'
231    }).formatToParts(new Date()).filter((part) => part.type !== 'literal').map((part) => [part.type, part.value]));
232    const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
233    const base = new Date(Date.UTC(Number(chicagoParts.year), Number(chicagoParts.month) - 1, Number(chicagoParts.day)));
234    const daysUntilFriday = (5 - weekdays.indexOf(chicagoParts.weekday) + 7) % 7 || 7;
235    base.setUTCDate(base.getUTCDate() + daysUntilFriday);
236    const localDate = base.toISOString().slice(0, 10).replace(/-/g, '');
237    const stamp = (date) => date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
238    const ics = ['BEGIN:VCALENDAR', 'VERSION:2.0', 'X-WR-TIMEZONE:America/Chicago', 'BEGIN:VEVENT', 'UID:[email protected]', 'DTSTAMP:' + stamp(new Date()), 'DTSTART;TZID=America/Chicago:' + localDate + 'T190000', 'DTEND;TZID=America/Chicago:' + localDate + 'T210000', 'RRULE:FREQ=WEEKLY;BYDAY=FR', 'SUMMARY:' + (config.showName || 'The First Class Friday Flight'), 'URL:https://jetsetfm.com/', 'END:VEVENT', 'END:VCALENDAR'].join('\r\n');
239    const link = document.createElement('a');
240    link.href = URL.createObjectURL(new Blob([ics], { type: 'text/calendar' }));
241    link.download = 'jetsetfm-friday-flight.ics';
242    link.click();
243    URL.revokeObjectURL(link.href);
244    track('show_reminder');
245  }));
246
247  async function submitForm(form, endpoint, event) {
248    const status = form.querySelector('[data-jsfm-status]');
249    const payload = Object.fromEntries(new FormData(form).entries());
250    status.textContent = 'Sending...';
251    try {
252      const response = await fetch(config.restUrl + endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
253      const data = await response.json();
254      status.textContent = data.message || (response.ok ? 'Done.' : 'Unable to send.');
255      if (response.ok) {
256        form.reset();
257        track(event);
258      }
259    } catch (error) {
260      status.textContent = 'Unable to send right now. Please try again.';
261    }
262  }
263
264  document.querySelectorAll('[data-jsfm-email-form]').forEach((form) => form.addEventListener('submit', (event) => {
265    event.preventDefault(); submitForm(form, 'email-signup', 'email_signup');
266  }));
267  document.querySelectorAll('[data-jsfm-request-form]').forEach((form) => form.addEventListener('submit', (event) => {
268    event.preventDefault(); submitForm(form, 'track-request', 'track_request');
269  }));
270
271  document.querySelectorAll('a[href*="support-jetsetfm"], a[href*="buymeacoffee.com/jetsetfm"]').forEach((link) => link.addEventListener('click', () => track('support_conversion')));
272}());

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.