(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= value); }); })); } }); return Promise.all(promises); } function generateFeatureProfile(features, navigator) { navigator = navigator || window.navigator; features = features || FEATURES; return buildFeaturesPromises(features, navigator).then(function(values) { // Build the signature: // - First, get a binary representation of all the feature flags. // the first 47 (!) are currently hardcoded as true. var hardcoded_len = 47; var bitfield = new FeaturesBitField(values.length + hardcoded_len); for (var i = 0; i < hardcoded_len; i++) { bitfield.set(i, true); } for (i = 0; i < values.length; i++) { bitfield.set(hardcoded_len + i, values[i]); } var profile = [ // First part is the base64 string built from the bitfield // containing the results from all the values; A '=' is prepended // to indicate that it's going to be a base64 signature. '=' + bitfield.toBase64(), // Second part is the number of features; bitfield.size, // Last part is a hardcoded version number, to bump whenever // we make changes. APP_FEATURES_VERSION ].join('.'); return profile; }); } /* * Utility function that converts an object to an array of objects. * Used because Promise.all() does not accept an object for its iterable, it * really wants an array :(. */ function mapPromisesObjectToArray(obj) { // Function that returns a function to use as the promise callback. var makeArrayEntry = function(key) { return function(value) { return {name: key, value: value}; }; }; return Object.keys(obj).map(function(key) { // Push a promise that resolves to a {name: ..., value: ...} object. return obj[key].then(makeArrayEntry(key)); }); } /* * Utility function that converts an array of objects to an object. * Companion to mapPromisesObjectToArray above. */ function mapArrayToObject(arr) { var obj = {}; for (var i = 0; i < arr.length; i ++) { // Build the object from each {name: ..., value: ...} object in the // array. obj[arr[i].name] = arr[i].value; } return obj; } /** * Check for extra features that do not have feature flags. * * Returns a promise that is fulfilled when all checks are complete. Its value * is an object containing booleans indicating whether the features are * enabled, like this: * { * addonsEnabled: true, * homescreensEnabled: false, * ... * } */ function checkForExtraFeatures(navigator) { navigator = navigator || window.navigator; if (typeof navigator.hasFeature === 'undefined') { return new Promise(function(resolve, reject) { // Resolve immediately with no data if we haven't hasFeature(). resolve({}); }) } var promises = { addonsEnabled: navigator.hasFeature('web-extensions'), homescreensEnabled: navigator.hasFeature('manifest.role.homescreen'), lateCustomizationEnabled: navigator.hasFeature('late-customization'), }; return Promise.all(mapPromisesObjectToArray(promises)).then(function(res) { return mapArrayToObject(res); }); } module.exports = { FeaturesBitField: FeaturesBitField, buildFeaturesPromises: buildFeaturesPromises, checkForExtraFeatures: checkForExtraFeatures, generateFeatureProfile: generateFeatureProfile, }; },{"./logger":3}],3:[function(require,module,exports){ function log() { // `console.log` wrapper that prefixes log statements. console.log('[yulelog]', Array.prototype.slice.call(arguments, 0).join(' ')); } module.exports = { log: log, }; },{}],4:[function(require,module,exports){ var activities = require('./activities'); var features = require('./features'); var logger = require('./logger'); var messages = require('./messages'); var translations = require('./translations'); logger.log('MKT_URL:', "https://marketplace.firefox.com"); function buildQS(profile, extraFeatures) { var qs = []; try { // navigator.mozMobileConnections is the new API. // navigator.mozMobileConnection is the legacy API. var conn = navigator.mozMobileConnections; var network; if (conn) { logger.log('navigator.mozMobileConnections available'); var mccs = []; var connData; for (var i = 0; i < conn.length; i++) { connData = conn[i]; // Testing lastKnownHomeNetwork first is important, because it's the // only one which contains the SPN. network = (connData.lastKnownHomeNetwork || connData.lastKnownNetwork || '-').split('-'); logger.log('navigator.mozMobileConnections[' + i + '].lastKnownNetwork:', connData.lastKnownNetwork); logger.log('navigator.mozMobileConnections[' + i + '].lastKnownHomeNetwork:', conn.lastKnownHomeNetwork); mccs.push({mcc: network[0], mnc: network[1], spn: network[2]}); } mccs = JSON.stringify(mccs); qs.push('mccs=' + mccs); logger.log('MCCs: ' + mccs); } else { logger.log('navigator.mozMobileConnections unavailable'); conn = navigator.mozMobileConnection; if (conn) { logger.log('navigator.mozMobileConnection available'); // `MCC`: Mobile Country Code // `MNC`: Mobile Network Code // `lastKnownHomeNetwork`: `{MCC}-{MNC}` (SIM's origin) // `lastKnownNetwork`: `{MCC}-{MNC}` (could be different network if roaming) network = (conn.lastKnownHomeNetwork || conn.lastKnownNetwork || '-').split('-'); qs.push('mcc=' + (network[0] || '')); qs.push('mnc=' + (network[1] || '')); logger.log('navigator.mozMobileConnection.lastKnownNetwork:', conn.lastKnownNetwork); logger.log('navigator.mozMobileConnection.lastKnownHomeNetwork:', conn.lastKnownHomeNetwork); logger.log('MCC: "' + network[0] + '", MNC: "' + network[1] + '"'); } else { logger.log('navigator.mozMobileConnection unavailable'); } } } catch(e) { // Fail gracefully if `navigator.mozMobileConnection(s)` // gives us problems. } if ('id' in navigator) { qs.push('nativepersona=true'); } if (profile) { logger.log('Generated profile: ' + profile); qs.push('pro=' + encodeURIComponent(profile)); } if (extraFeatures) { Object.keys(extraFeatures).forEach(function(key) { logger.log(key + ': ' + extraFeatures[key]); if (extraFeatures[key]) { qs.push(key + '=true'); } }); } return qs.join('&'); } // The iframe src is served over https, which means that if the system date // is too far behind, the user will just end up seeing a certificate error. // We check against an hardcoded year corresponding to the current certificate // creation date, and display an error message if necessary. function isSystemDateIncorrect() { logger.log('Checking for system date ...'); var rval = new Date().getFullYear() < 2015; if (rval) { logger.log('System date appears to be incorrect!'); } else { logger.log('System date appears to be OK.'); } return rval; } if (isSystemDateIncorrect()) { document.body.classList.add('dateerror'); document.querySelector('.try-again').addEventListener('click', function() { if (!isSystemDateIncorrect()) { window.location.reload(); } }, false); } else { // Build the iframe. If we have Promise and getFeature, we build the // profile signature first. if (typeof window.Promise !== 'undefined' && typeof navigator.getFeature !== 'undefined') { logger.log('navigator.getFeature and window.Promise available'); var allStartupPromises = Promise.all([ features.generateFeatureProfile(), features.checkForExtraFeatures(), ]); allStartupPromises.then(function(promises) { var featureProfile = promises[0]; var extraFeatures = promises[1]; launchIframe(featureProfile, extraFeatures); }); } else { logger.log('navigator.getFeature or window.Promise unavailable :('); launchIframe(); } // When refocussing the app, toggle the iframe based on `navigator.onLine`. window.addEventListener('focus', toggleOffline, false); toggleOffline(true); document.querySelector('.try-again').addEventListener('click', function() { toggleOffline(); }, false); } function launchIframe(profile, hasWebExtensions) { // Set the iframe src to the actual Marketplace. var iframe = document.getElementById('iframe'); iframe.onerror = function() { document.body.classList.add('offline'); }; iframe.src = "https://marketplace.firefox.com" + '/?' + buildQS(profile, hasWebExtensions); } function toggleOffline(init) { logger.log('Checking for network connection ...'); if (navigator.onLine === false) { // Hide iframe. logger.log('Network connection not found; hiding iframe ...'); document.body.classList.add('offline'); } else { // Show iframe. logger.log('Network connection found; showing iframe ...'); if (!init) { // Reload the page to reload the iframe. window.location.reload(); } } } // Set up messages, activities and translations late in order to load the // iframe as quickly as possible. translations.install(); messages.install(); activities.install(); },{"./activities":1,"./features":2,"./logger":3,"./messages":5,"./translations":6}],5:[function(require,module,exports){ var logger = require('./logger'); var activitiesInProgress = {}; var activitiesToSend = []; function postMessage(win, msg) { logger.log('postMessaging to ' + "https://marketplace.firefox.com" + ': ' + JSON.stringify(msg)); win.document.querySelector('iframe').contentWindow.postMessage(msg, "https://marketplace.firefox.com"); } function sendActivities(win) { logger.log('Sending activities: ' + JSON.stringify(activitiesToSend)); while (activitiesToSend.length) { postMessage(win, activitiesToSend.pop()); } // The next time we try to append something to `activitiesToSend`, // we'll have already called this function (`sendActivities`) // so just postMessage the message (`msg`) immediately. activitiesToSend = { push: function(msg) { postMessage(win, msg); } }; } function queueMessage(msg) { activitiesToSend.push(msg); } function install(window_, navigator) { var win = window_ || window; navigator = navigator || window.navigator; win.addEventListener('message', function(e) { // Receive postMessage from the iframe contents and do something with it. logger.log('Handled post message from ' + e.origin + ': ' + JSON.stringify(e.data)); if (e.origin !== "https://marketplace.firefox.com") { logger.log('Ignored post message from ' + e.origin + ': ' + JSON.stringify(e.data)); return; } if (e.data === 'loaded') { logger.log('Preparing to send activities ...'); sendActivities(win); } else if (e.data.type === 'fxa-watch') { logger.log('Registering FxA callbacks'); navigator.mozId.watch({ wantIssuer: 'firefox-accounts', loggedInUser: e.data.email, onready: function() {}, onlogin: function(a) {logger.log('fxa-login'); postMessage(win, {type: 'fxa-login', assertion: a});}, onlogout: function() {logger.log('fxa-logout'); postMessage(win, {type: 'fxa-logout'});} }); } else if (e.data.type === 'fxa-request') { navigator.mozId.request({oncancel: function(){postMessage(win, {type: 'fxa-cancel'});}}); } else if (e.data.type == 'activity-result' && e.data.id && activitiesInProgress[e.data.id]) { logger.log('Posting back result for activity id:', e.data.id); activitiesInProgress[e.data.id].postResult(e.data.result); delete activitiesInProgress[e.data.id]; } else if (e.data.type == 'activity-error' && e.data.id && activitiesInProgress[e.data.id]) { logger.log('Posting back error for activity id:', e.data.id); activitiesInProgress[e.data.id].postError(e.data.result); delete activitiesInProgress[e.data.id]; } }, false); } module.exports = { activitiesInProgress: activitiesInProgress, install: install, queueMessage: queueMessage, }; },{"./logger":3}],6:[function(require,module,exports){ function get_locale(locale) { var languages = [ 'bg', 'bn-BD', 'ca', 'cs', 'da', 'de', 'el', 'en-US', 'es', 'eu', 'fr', 'ga-IE', 'hr', 'hu', 'it', 'ja', 'ko', 'mk', 'nb-NO', 'nl', 'pa', 'pl', 'pt-BR', 'ro', 'ru', 'sk', 'sq', 'sr', 'sr-Latn', 'ta', 'tr', 'xh', 'zh-CN', 'zh-TW', 'zu', 'dbg' ]; var lang_expander = { 'en': 'en-US', 'ga': 'ga-IE', 'pt': 'pt-BR', 'sv': 'sv-SE', 'zh': 'zh-CN', 'sr': 'sr-Latn' }; if (languages.indexOf(locale) !== -1) { return locale; } locale = locale.split('-')[0]; if (languages.indexOf(locale) !== -1) { return locale; } if (locale in lang_expander) { locale = lang_expander[locale]; if (languages.indexOf(locale) !== -1) { return locale; } } return 'en-US'; } function install() { var transName; var transBlocks = document.querySelectorAll('[data-l10n]'); var qs_lang = /[\?&]lang=([\w\-]+)/i.exec(window.location.search); var locale = get_locale((qs_lang && qs_lang[1]) || navigator.language); var translations = { "offline-error-message": { "bg": "Извинявайте, трябва да сте свързани към мрежа, за да посетите Marketplace.", "cs": "Omlouváme se, ale pro přístup k Marketplace musíte být online.", "da": "Du skal være online for at få adgang til Marketplace.", "de": "Es tut uns leid, Sie müssen online sein, um auf den Marketplace zuzugreifen.", "el": "Λυπούμαστε, πρέπει να είστε συνδεδεμένοι στο διαδίκτυο για να χρησιμοποιήσετε το Marketplace.", "en-US": "Sorry, you need to be online to access the Marketplace.", "es": "Lo sentimos, necesitas estar conectado para acceder al Marketplace.", "eu": "Sentitzen dugu, Marketplace-era sartzeko linean egon behar duzu.", "fr": "Vous devez être en ligne pour accéder au Marketplace.", "hu": "Elnézést, a Marketplace eléréséhez internetkapcsolat szükséges.", "it": "Spiacenti, devi essere online per poter accedere al Marketplace.", "ja": "申し訳ありませんが、Marketplace へアクセスするにはインターネット接続が必要です。", "nb-NO": "Du må være tilkoblet for å få tilgang til Marketplace.", "nl": "Sorry, u dient online te zijn om de Marketplace te bezoeken.", "pa": "ਅਫਸੋਸ, ਮੰਡੀ ਵਰਤਣ ਲਈ ਤੁਹਾਨੂੰ ਆਨਲਾਈਨ ਹੋਣ ਦੀ ਲੋੜ ਹੈ।", "pl": "Przepraszamy, musisz być online, by mieć dostęp do Marketplace.", "pt-BR": "Desculpe, você precisa estar on-line para acessar o Marketplace.", "ru": "Для доступа к Marketplace вам необходимо подключиться к сети.", "sk": "Ospravedlňujeme sa, ale pre prístup k službe Marketplace musíte byť online.", "sq": "Na ndjeni, lypset të jeni i lidhur në linjë për të përdorur Marketplace-in.", "sr": "Жао нам је, морате бити на мрежи да бисте приступили Marketplace-у.", "sr-Latn": "Žao nam je, morate biti na mreži da biste pristupili Marketplace-u.", "tr": "Üzgünüz, Marketplace'e erişmek için çevrimiçi olmalısınız.", "xh": "Uxolo, kudingeka ube kwi-intaneti ukufikelela kwi-Marketplace", "zh-CN": "抱歉,您需要在线才能访问应用市场。", "zh-TW": "抱歉,您必須連線到網路才能使用 Marketplace。", "zu": "Siyaxolisa, kudingeka ukuthi ube semoyeni ukuze ukwazi ukufinyelela ku-Marketplace.", "dbg": "Şǿřřẏ, ẏǿŭ ƞḗḗḓ ŧǿ ƀḗ ǿƞŀīƞḗ ŧǿ ȧƈƈḗşş ŧħḗ Ḿȧřķḗŧƥŀȧƈḗ." }, "date-error-message": { "bg": "Извинявайте, часовникът на устройството е неточен.", "cs": "Omlouváme se, ale čas na vašem zařízení není nastaven korektně.", "da": "Beklager, men uret på din enhed ser ud til at være forkert indstillet.", "de": "Es tut uns leid, die Uhr Ihres Geräts scheint falsch eingestellt zu sein.", "el": "Λυπούμαστε, το ρολόι της συσκευής σας έχει ρυθμιστεί λανθασμένα. ", "en-US": "Sorry, your device clock appears to be set incorrectly.", "es": "Lo sentimos, el reloj de tu dispositivo parece estar configurado incorrectamente.", "eu": "Sentitzen dugu, dirudienez zure gailuaren erlojua gaizki konfiguratuta dago.", "fr": "Désolé, il semblerait que l'horloge de votre périphérique ne soit pas correctement configurée.", "hu": "Elnézést, úgy tűnik a készülék órája nem jól jár.", "it": "Spiacenti, l’orologio del dispositivo in uso sembra non essere impostato correttamente.", "ja": "申し訳ありませんが、お使いの端末の時刻設定が正しくないようです。", "nb-NO": "Beklager, men klokken på din enhet ser ut til å være feilinnstilt.", "nl": "Sorry, uw apparaatklok lijkt niet juist te zijn ingesteld.", "pa": "ਅਫਸੋਸ, ਤੁਹਾਡੇ ਯੰਤਰ ਦੀ ਘੜੀ ਗਲਤ ਚੱਲਦੀ ਜਾਪਦੀ ਹੈ।", "pl": "Przepraszamy, wygląda na to, że zegar twojego urządzenia jest ustawiony niepoprawnie.", "pt-BR": "Desculpe, o relógio do seu aparelho parece estar configurado incorretamente.", "ru": "К сожалению, похоже, что часы на вашем устройстве установлены неправильно.", "sk": "Ospravedlňujeme sa, ale čas na vašom zariadení nie je nastavený správne.", "sq": "Na ndjeni, ora e pajisjes suaj duket se nuk është rregulluar saktë.", "sr": "Жао нам је, изгледа да је Ваш сат уређаја погрешно подешен.", "sr-Latn": "Žao nam je, izgleda da je Vaš sat uređaja pogrešno podešen.", "tr": "Üzgünüz, cihazınızın saati yanlış ayarlanmış görünüyor.", "xh": "Uxolo, ixesha lesixhobo sakho libonakala lisetwe ngokungachanekanga.", "zh-CN": "抱歉,您的设备时钟似乎有误。", "zh-TW": "抱歉,您的裝置時間錯誤。", "zu": "Siyaxolisa, kubonakala sengathi iwashi ledivayisi yakho alihambi kahle.", "dbg": "Şǿřřẏ, ẏǿŭř ḓḗṽīƈḗ ƈŀǿƈķ ȧƥƥḗȧřş ŧǿ ƀḗ şḗŧ īƞƈǿřřḗƈŧŀẏ." }, "date-error-message-suggestion": { "bg": "Моля, настройте датата и часа на устройството ви, за да посетите Marketplace.", "cs": "Pro přístup k Marketplace si prosím nastavte na svém zařízení aktuální datum a čas.", "da": "Indstil dags dato og klokkeslæt på din enhed for at få adgang til Marketplace.", "de": "Bitte stellen Sie in Ihren Geräteeinstellungen das heutige Datum und die Uhrzeit ein, um auf den Marketplace zuzugreifen.", "el": "Παρακαλούμε ρυθμίστε την σημερινή ημερομηνία και ώρα από τις ρυθμίσεις της συσκευής σας, για να αποκτήσετε πρόσβαση στο Marketplace.", "en-US": "Please set today's date and time in your device settings to access the Marketplace.", "es": "Por favor establece la fecha y hora de hoy en las opciones de tu dispositivo para poder acceder a Marketplace.", "eu": "Ezarri gaurko eguna eta ordua zure gailuaren ezarpenetan Marketplace-era sartzeko.", "fr": "Merci de configurer correctement la date et l'heure dans les paramètres de votre appareil pour pouvoir accéder au Marketplace.", "hu": "Állítsa be a mai dátumot és időt a készülék beállításaiban a Marketplace eléréséhez.", "it": "Imposta ora e data correnti nelle impostazioni del dispositivo per accedere al Marketplace.", "ja": "端末の設定で現在の日時を正しく設定してから Marketplace へアクセスしてください。", "nb-NO": "Sett dagens dato og klokkeslett i enhetsinnstillingene for å få tilgang til Marketplace.", "nl": "Stel in uw apparaatinstellingen de datum en tijd van vandaag in om de Marketplace te benaderen.", "pa": "ਮੰਡੀ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ ਆਪਣੇ ਯੰਤਰ ਦੀ ਸੈਟਿੰਗ ਵਿੱਚ ਅੱਜ ਦਾ ਸਮਾਂ ਤੇ ਮਿਤੀ ਦਿਉ।", "pl": "Ustaw dzisiejszą datę i czas w ustawieniach swojego urządzenia, by mieć dostęp do Marketplace.", "pt-BR": "Por favor, atualize a data e a hora do seu aparelho para acessar o Marketplace.", "ru": "Пожалуйста, установите текущую дату и время в настройках вашего устройства для доступа к Marketplace.", "sk": "Pre prístup k službe Marketplace si prosím nastavte na svojom zariadení aktuálny dátum a čas.", "sq": "Ju lutemi, që të përdorni Marketplace-in, rregulloni te pajisja juaj datën e sotme dhe orën e tanishme.", "sr": "Молимо подесите данашњи датум и време у поставкама Вашег уређаја да бисте приступили Marketplace-у.", "sr-Latn": "Molimo podesite današnji datum i vreme u postavkama Vašeg uređaja da biste pristupili Marketplace-u.", "tr": "Marketplace'e erişmek için lütfen cihaz ayarlarınızdan bugünün tarihini ve saatini ayarlayın.", "xh": "Nceda usete umhla nexesha lanamhlanje kwiisetingi zesixhobo sakho ukufikelela kwi-Marketplace.", "zh-CN": "请在您的设备设置中设置今天的日期和时间以访问应用市场。", "zh-TW": "請將您的裝置時間設定至今天以使用 Marketplace。", "zu": "Uyacelwa usethe usuku lwanamuhla kanye nesikhathi kumasethingi edivayisi yakho ukuze ukwazi ukufinyelela ku-Marketplace.", "dbg": "Ƥŀḗȧşḗ şḗŧ ŧǿḓȧẏ'ş ḓȧŧḗ ȧƞḓ ŧīḿḗ īƞ ẏǿŭř ḓḗṽīƈḗ şḗŧŧīƞɠş ŧǿ ȧƈƈḗşş ŧħḗ Ḿȧřķḗŧƥŀȧƈḗ." }, "try-again": { "bg": "Повторен опит", "cs": "Zkusit znovu", "da": "Prøv igen", "de": "Erneut versuchen", "el": "Προσπαθήστε ξανά", "en-US": "Try again", "es": "Intenta de nuevo", "eu": "Saiatu berriro", "fr": "Réessayer", "hu": "Újra", "it": "Riprova", "ja": "再読み込み", "nb-NO": "Prøv igjen", "nl": "Opnieuw proberen", "pa": "ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰੋ", "pl": "Spróbuj ponownie", "pt-BR": "Tente novamente", "ru": "Попробовать снова", "sk": "Skúsiť znova", "sq": "Riprovoni", "sr": "Покушај поново", "sr-Latn": "Pokušaj ponovo", "tr": "Tekrar dene", "xh": "Zama kwakhona", "zh-CN": "重试", "zh-TW": "重試", "zu": "Zama futhi", "dbg": "Ŧřẏ ȧɠȧīƞ" } }; for (var i = 0; i < transBlocks.length; i++) { transName = transBlocks[i].dataset.l10n; if (transName in translations && locale in translations[transName]) { transBlocks[i].innerHTML = translations[transName][locale]; } } } module.exports = { install: install, }; },{}]},{},[4]);