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.