PageSourceSearch

https://www.myketowerspr.com/assets/wix.js?v=c6755834

js myketowerspr.com collected 2026-10-02 12:41:45 UTC 6,628 bytes, 156 lines download raw bytes

1/* ============================================================================
2   WIX HEADLESS BACKEND
3   ----------------------------------------------------------------------------
4   Reads tour dates, releases and videos out of Wix CMS collections so Dexter
5   can edit them in the Wix dashboard instead of editing this repo.
6
7   Design rule: the site NEVER waits on Wix and never breaks because of it.
8   Pages render instantly from the arrays in data.js, then quietly re-render if
9   and when Wix answers with something different. Wix down, client ID missing,
10   collection renamed, offline, the site still works, just with whatever was
11   bundled at deploy time.
12
13   Auth is the OAuth "anonymous / visitor" grant. It needs only the Client ID,
14   which is public by design and safe to ship in the browser. The client SECRET
15   is never used here, it only exists inside the Netlify function.
16   ============================================================================ */
17(function () {
18  'use strict';
19
20  var CFG   = (window.CONFIG && window.CONFIG.wix) || {};
21  var BASE  = 'https://www.wixapis.com';
22  var token = null;                 // held in memory for this page load only
23
24  function enabled() { return !!CFG.clientId; }
25
26  /* ----------------------------------------------------------------- auth -- */
27  function getToken() {
28    if (token) return Promise.resolve(token);
29    return fetch(BASE + '/oauth2/token', {
30      method: 'POST',
31      headers: { 'Content-Type': 'application/json' },
32      body: JSON.stringify({ clientId: CFG.clientId, grantType: 'anonymous' })
33    })
34    .then(function (r) { if (!r.ok) throw new Error('token ' + r.status); return r.json(); })
35    .then(function (j) { token = j.access_token; return token; });
36  }
37
38  /* ---------------------------------------------------------------- query -- */
39  function query(collectionId, sort) {
40    if (!collectionId) return Promise.resolve(null);
41    return getToken().then(function (tk) {
42      return fetch(BASE + '/wix-data/v2/items/query', {
43        method: 'POST',
44        headers: {
45          'Content-Type': 'application/json',
46          // Raw token. NOT "Bearer <token>": Wix rejects the Bearer prefix.
47          'Authorization': tk
48        },
49        body: JSON.stringify({
50          dataCollectionId: collectionId,
51          query: { sort: sort || [], paging: { limit: 100, offset: 0 } }
52        })
53      });
54    })
55    .then(function (r) { if (!r.ok) throw new Error(collectionId + ' ' + r.status); return r.json(); })
56    .then(function (j) { return (j.dataItems || []).map(function (d) { return d.data || d; }); });
57  }
58
59  /* ------------------------------------------------------------- mapping -- */
60  // Lenient on field names so a slightly different column in the CMS doesn't
61  // silently produce blank rows. First match wins.
62  function pick(row) {
63    for (var i = 1; i < arguments.length; i++) {
64      var k = arguments[i];
65      if (row[k] !== undefined && row[k] !== null && row[k] !== '') return row[k];
66    }
67    return '';
68  }
69
70  function mapTour(rows) {
71    return rows.map(function (r) {
72      var country = pick(r, 'country', 'countryEs', 'country_es', 'pais');
73      return {
74        date:   pick(r, 'date', 'startDate', 'fecha'),
75        city:   pick(r, 'city', 'ciudad'),
76        venue:  pick(r, 'venue', 'venueName', 'recinto'),
77        ticket: pick(r, 'ticketUrl', 'tickets', 'ticket', 'boletos'),
78        country: {
79          es: country || pick(r, 'countryEn', 'country_en'),
80          en: pick(r, 'countryEn', 'country_en') || country
81        }
82      };
83    }).filter(function (d) { return d.date && d.city; });
84  }
85
86  function mapReleases(rows) {
87    return rows.map(function (r) {
88      var kind = String(pick(r, 'kind', 'type', 'tipo') || 'single').toLowerCase();
89      if (['album','álbum','lp'].indexOf(kind) > -1) kind = 'album';
90      else if (kind === 'ep') kind = 'ep';
91      else kind = 'single';
92      var img = pick(r, 'coverUrl', 'cover', 'image', 'artwork', 'portada');
93      // Wix image fields come back as "wix:image://v1/<id>/...", turn that
94      // into a URL a browser can actually load.
95      var m = String(img).match(/^wix:image:\/\/v1\/([^/]+)/);
96      if (m) img = 'https://static.wixstatic.com/media/' + m[1];
97      return {
98        t: pick(r, 'title', 'name', 'titulo'),
99        k: kind,
100        u: pick(r, 'url', 'smartLink', 'link', 'enlace'),
101        img: img,
102        isNew: !!pick(r, 'isNew', 'nuevo')
103      };
104    }).filter(function (r) { return r.t && r.img; });
105  }
106
107  function mapVideos(rows) {
108    return rows.map(function (r) {
109      var raw = String(pick(r, 'youtubeId', 'videoId', 'youtube', 'url', 'link'));
110      var m = raw.match(/(?:embed\/|youtu\.be\/|[?&]v=)([A-Za-z0-9_-]{11})/);
111      var id = m ? m[1] : (/^[A-Za-z0-9_-]{11}$/.test(raw) ? raw : '');
112      var sub = pick(r, 'subtitle', 'subtitleEs', 'subtitle_es', 'subtitulo');
113      return {
114        id: id,
115        t: pick(r, 'title', 'name', 'titulo'),
116        sub: { es: sub, en: pick(r, 'subtitleEn', 'subtitle_en') || sub }
117      };
118    }).filter(function (v) { return v.id && v.t; });
119  }
120
121  /* ------------------------------------------------------------------ load -- */
122  // Resolves with whatever it managed to fetch. Never rejects.
123  window.MTBackend = {
124    enabled: enabled,
125
126    load: function () {
127      if (!enabled()) return Promise.resolve({ source: 'local' });
128
129      var C = CFG.collections || {};
130      var jobs = [
131        query(C.tour,     [{ fieldName: 'date',       order: 'ASC'  }]).then(mapTour).catch(warn('tour')),
132        query(C.releases, [{ fieldName: 'releasedAt', order: 'DESC' }]).then(mapReleases).catch(warn('releases')),
133        query(C.videos,   [{ fieldName: '_createdDate', order: 'DESC' }]).then(mapVideos).catch(warn('videos'))
134      ];
135
136      return Promise.all(jobs).then(function (res) {
137        var out = { source: 'wix', changed: false };
138        if (res[0] && res[0].length) { window.DATES    = res[0]; out.changed = true; }
139        if (res[1] && res[1].length) { window.RELEASES = res[1]; out.changed = true; }
140        if (res[2] && res[2].length) { window.VIDEOS   = res[2]; out.changed = true; }
141        return out;
142      }).catch(function (e) {
143        console.warn('[wix] falling back to bundled content:', e && e.message);
144        return { source: 'local' };
145      });
146    }
147  };
148
149  function warn(what) {
150    return function (e) {
151      // A single failed collection shouldn't take the other two down with it.
152      console.warn('[wix] could not load "' + what + '", using bundled copy.', e && e.message);
153      return null;
154    };
155  }
156})();

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.