Jak wykryć zdarzenia online / offline w różnych przeglądarkach?


111

Próbuję dokładnie wykryć, kiedy przeglądarka przechodzi w tryb offline, korzystając ze zdarzeń online i offline HTML5.

Oto mój kod:

<script>
    // FIREFOX
    $(window).bind("online", applicationBackOnline); 
    $(window).bind("offline", applicationOffline);

    //IE
    window.onload = function() {
        document.body.ononline = IeConnectionEvent;
        document.body.onoffline = IeConnectionEvent;
    } 
</script>

Działa dobrze, gdy po prostu kliknę „Pracuj offline” w przeglądarce Firefox lub IE, ale działa to trochę przypadkowo, gdy faktycznie odłączam przewód.

Jaki jest najlepszy sposób na wykrycie tej zmiany? Chciałbym uniknąć powtarzania wywołań ajax z limitami czasu.


2
Zgadzam się z Trefexem, ale chciałbym też dodać, że obsługa wykrywania połączenia jest w najlepszym razie tandetna dla większości aplikacji: tylko dlatego, że przewód jest odłączony, nie oznacza to od razu utraty połączenia. Poleganie na metodzie, która nie sprawdza fizycznie, czy połączenie jest otwarte, nie może zagwarantować dokładnych wyników.
mattbasta

Dzięki za radę. Więc poleciłbyś metodę Ajax? to znaczy. nadal wysyłać połączenia XHR z limitami czasu?
Pierre Duplouy,

Implementacja przeglądarki Firefox (oraz IE i Opery) jest nieprawidłowa. Zobacz mój komentarz na ten temat tutaj: bugzilla.mozilla.org/show_bug.cgi?id=654579#c9
thewoolleyman

4
Możesz wypróbować Offline.js , bibliotekę open source stworzoną właśnie do tego celu.
Adam

Odpowiedzi:


70

Dostawcy przeglądarek nie mogą uzgodnić sposobu definiowania trybu offline. Niektóre przeglądarki mają funkcję Pracuj w trybie offline, którą uważają za odrębną od braku dostępu do sieci, co również różni się od dostępu do Internetu. Cała sprawa to bałagan. Niektórzy dostawcy przeglądarek aktualizują flagę navigator.onLine w przypadku utraty rzeczywistego dostępu do sieci, inni nie.

Ze specyfikacji:

Zwraca wartość false, jeśli agent użytkownika jest zdecydowanie w trybie offline (odłączony od sieci). Zwraca wartość true, jeśli agent użytkownika może być w trybie online.

Zdarzenia online i offline są wyzwalane, gdy zmienia się wartość tego atrybutu.

Atrybut navigator.onLine musi zwracać wartość false, jeśli agent użytkownika nie skontaktuje się z siecią, gdy użytkownik skorzysta z odsyłaczy lub gdy skrypt zażąda zdalnej strony (lub wie, że taka próba się nie powiedzie), aw przeciwnym razie musi zwrócić wartość true.

Na koniec specyfikacja zauważa:

Ten atrybut jest z natury zawodny. Komputer można podłączyć do sieci bez dostępu do Internetu.


23
Tylko Chrome ustawia poprawnie navigator.onLine w przypadku utraty łączności. Zarówno Safari, jak i Firefox nigdy nie ustawiają flagi na false, jeśli usuniesz połączenie internetowe.
chovy

2
@chovy i co powiesz na teraz? Niedawno testowałem to w przeglądarce Firefox / Chrome i uzyskałem oczekiwane rezultaty, widząc, że flaga jest ustawiana, kiedy wyłączam i włączam połączenie internetowe ..
James Cazzetta.

12
Dzisiaj 31.01.2017 otworzyłem OSX Chrome 55.0.2883.95, Safari 10.0.3 i FF 50.1.0. Cała część window.navigator.onLine wydaje się działać świetnie, gdy pozostawałem w sieci, ale usunąłem przewód z routera. Wszystkie zostały poprawnie wykryte w trybie offline.
nycynik

3
navigator.onLine jest obsługiwany we wszystkich głównych przeglądarkach (i jest od jakiegoś czasu): caniuse.com/#feat=online-status
Rafael Lüder

@ RafaelLüder Jak na dzień dzisiejszy, ale ta odpowiedź została napisana w styczniu 2011 roku!
Rebecca

34

Główni dostawcy przeglądarek różnią się co do tego, co oznacza „offline”.

Chrome i Safari automatycznie wykryją przejście do trybu „offline”, co oznacza, że ​​zdarzenia i właściwości „online” będą uruchamiane automatycznie po odłączeniu kabla sieciowego.

Firefox (Mozilla), Opera i IE mają inne podejście i traktują Cię jako „online”, chyba że wyraźnie wybierzesz „Tryb offline” w przeglądarce - nawet jeśli nie masz działającego połączenia sieciowego.

Istnieją ważne argumenty przemawiające za zachowaniem Firefoksa / Mozilli, które zostały przedstawione w komentarzach do tego zgłoszenia błędu:

https://bugzilla.mozilla.org/show_bug.cgi?id=654579

Ale odpowiadając na pytanie - nie można polegać na zdarzeniach / właściwościach online / offline, aby wykryć, czy rzeczywiście istnieje łączność sieciowa.

Zamiast tego musisz użyć alternatywnych podejść.

Sekcja „Uwagi” tego artykułu Mozilla Developer zawiera łącza do dwóch alternatywnych metod:

https://developer.mozilla.org/en/Online_and_offline_events

„Jeśli interfejs API nie jest zaimplementowany w przeglądarce, możesz użyć innych sygnałów, aby wykryć, czy jesteś w trybie offline, w tym nasłuchiwanie zdarzeń błędów AppCache i odpowiedzi z XMLHttpRequest”

To łącze do przykładu podejścia „nasłuchiwanie zdarzeń błędów AppCache”:

http://www.html5rocks.com/en/mobile/workingoffthegrid/#toc-appcache

... i przykład podejścia „nasłuchiwania błędów XMLHttpRequest”:

http://www.html5rocks.com/en/mobile/workingoffthegrid/#toc-xml-http-request

HTH, - Czad


1
Począwszy od przeglądarki Firefox 41: updates this property when the OS reports a change in network connectivity on Windows, Linux, and OS X.(zgodnie z wymienionymi dokumentami). Więc to nie tylko offline, jeśli przeglądasz w "trybie offline" przeglądarki
stary,

17

Obecnie istnieje biblioteka JavaScript typu open source, która wykonuje tę pracę: nazywa się Offline.js.

Automatycznie wyświetlaj użytkownikom wskazania online / offline.

https://github.com/HubSpot/offline

Koniecznie sprawdź pełne README . Zawiera zdarzenia, do których możesz się podłączyć.

Oto strona testowa . Nawiasem mówiąc, jest piękny / ma ładny interfejs zwrotny! :)

Offline.js Simulate UI to wtyczka Offline.js, która umożliwia testowanie, jak strony reagują na różne stany łączności bez konieczności używania metod brutalnej siły do ​​wyłączania rzeczywistej łączności.


2
Biblioteka faktycznie działa poprzez wielokrotne pobieranie lokalnych favicon pod maską. Moim zdaniem biblioteka jest zbyt „duża” i ma zbyt wiele funkcji; główna sztuczka polega na wielokrotnym pobieraniu favicon.
Karel Bílek

1
Nie wykrywa trybu offline, gdy odłączam kabel sieciowy
najzwyklejszy

15

Najlepszym sposobem, który działa teraz we wszystkich głównych przeglądarkach, jest następujący skrypt:

(function () {
    var displayOnlineStatus = document.getElementById("online-status"),
        isOnline = function () {
            displayOnlineStatus.innerHTML = "Online";
            displayOnlineStatus.className = "online";
        },
        isOffline = function () {
            displayOnlineStatus.innerHTML = "Offline";
            displayOnlineStatus.className = "offline";
        };

    if (window.addEventListener) {
        /*
            Works well in Firefox and Opera with the 
            Work Offline option in the File menu.
            Pulling the ethernet cable doesn't seem to trigger it.
            Later Google Chrome and Safari seem to trigger it well
        */
        window.addEventListener("online", isOnline, false);
        window.addEventListener("offline", isOffline, false);
    }
    else {
        /*
            Works in IE with the Work Offline option in the 
            File menu and pulling the ethernet cable
        */
        document.body.ononline = isOnline;
        document.body.onoffline = isOffline;
    }
})();

Źródło: http://robertnyman.com/html5/offline/online-offline-events.html


3
Jak wyraźnie zaznaczają komentarze w samym kodzie - nie działa w Firefoksie / Chrome, jeśli odłączysz kabel Ethernet lub wyłączysz wifi.
Manish

Próbowałem wejść na link „Źródło” i odłączyłem kabel Ethernet, pokazało się „Jesteś offline” w IE, ale nie w Firefoksie / Chrome dla mnie (używam najnowszych wersji wszystkich przeglądarek). Może coś mi brakuje?
Manish

13

Od niedawna navigator.onLinepokazuje to samo we wszystkich głównych przeglądarkach, a zatem jest użyteczne.

if (navigator.onLine) {
  // do things that need connection
} else {
  // do things that don't need connection
}

Najstarsze wersje obsługujące to we właściwy sposób to: Firefox 41 , IE 9, Chrome 14 i Safari 5.

Obecnie będzie to reprezentowało prawie całe spektrum użytkowników, ale zawsze powinieneś sprawdzić jakie możliwości mają użytkownicy Twojej strony.

Przed FF 41 pokazywał się tylko falsewtedy, gdy użytkownik ręcznie przestawił przeglądarkę w tryb offline. W IE 8 właściwość znajdowała się na body, zamiast window.

źródło: caniuse


11

window.navigator.onLineAtrybut i związane z nim wydarzenia są obecnie niepewne na niektórych przeglądarek internetowych ( zwłaszcza stacjonarnych Firefox ) jako @Junto wymienione, więc napisałem trochę funkcji (przy użyciu jQuery), które okresowo sprawdzać stan połączeń sieciowych i podnieść odpowiedni offlinei onlinewydarzenia:

// Global variable somewhere in your app to replicate the 
// window.navigator.onLine variable (it is not modifiable). It prevents
// the offline and online events to be triggered if the network
// connectivity is not changed
var IS_ONLINE = true;

function checkNetwork() {
  $.ajax({
    // Empty file in the root of your public vhost
    url: '/networkcheck.txt',
    // We don't need to fetch the content (I think this can lower
    // the server's resources needed to send the HTTP response a bit)
    type: 'HEAD',
    cache: false, // Needed for HEAD HTTP requests
    timeout: 2000, // 2 seconds
    success: function() {
      if (!IS_ONLINE) { // If we were offline
        IS_ONLINE = true; // We are now online
        $(window).trigger('online'); // Raise the online event
      }
    },
    error: function(jqXHR) {
      if (jqXHR.status == 0 && IS_ONLINE) {
        // We were online and there is no more network connection
        IS_ONLINE = false; // We are now offline
        $(window).trigger('offline'); // Raise the offline event
      } else if (jqXHR.status != 0 && !IS_ONLINE) {
        // All other errors (404, 500, etc) means that the server responded,
        // which means that there are network connectivity
        IS_ONLINE = true; // We are now online
        $(window).trigger('online'); // Raise the online event
      }
    }
  });
}

Możesz go używać w ten sposób:

// Hack to use the checkNetwork() function only on Firefox 
// (http://stackoverflow.com/questions/5698810/detect-firefox-browser-with-jquery/9238538#9238538)
// (But it may be too restrictive regarding other browser
// who does not properly support online / offline events)
if (!(window.mozInnerScreenX == null)) {
    window.setInterval(checkNetwork, 30000); // Check the network every 30 seconds
}

Aby słuchać wydarzeń offline i online (z pomocą jQuery):

$(window).bind('online offline', function(e) {
  if (!IS_ONLINE || !window.navigator.onLine) {
    alert('We have a situation here');
  } else {
    alert('Battlestation connected');
  }
});

7

navigator.onLine to bałagan

Stoję przed tym, gdy próbuję wykonać połączenie AJAX do serwera.

Istnieje kilka możliwych sytuacji, gdy klient jest offline:

  • po przekroczeniu limitu czasu wywołania ajax i pojawia się błąd
  • wywołanie Ajax zwraca sukces, ale komunikat jest pusty
  • wywołanie AJAX nie jest wykonywane, ponieważ przeglądarka tak zdecyduje (może być tak, gdy navigator.onLine staje się po chwili fałszywy)

Rozwiązaniem, którego używam, jest samodzielne kontrolowanie statusu za pomocą javascript. Ustawiam warunek udanego połączenia, w każdym innym przypadku zakładam, że klient jest offline. Coś takiego:

var offline;
pendingItems.push(item);//add another item for processing
updatePendingInterval = setInterval("tryUpdatePending()",30000);
tryUpdatePending();

    function tryUpdatePending() {

        offline = setTimeout("$('#offline').show()", 10000);
        $.ajax({ data: JSON.stringify({ items: pendingItems }), url: "WebMethods.aspx/UpdatePendingItems", type: "POST", dataType: "json", contentType: "application/json; charset=utf-8",
          success: function (msg) {
            if ((!msg) || msg.d != "ok")
              return;
            pending = new Array(); //empty the pending array
            $('#offline').hide();
            clearTimeout(offline);
            clearInterval(updatePendingInterval);
          }
        });
      }

5

W HTML5 możesz użyć navigator.onLinewłaściwości. Popatrz tutaj:

http://www.w3.org/TR/offline-webapps/#related

Prawdopodobnie twoje obecne zachowanie jest losowe, ponieważ javascript przygotowuje tylko zmienną "browser", a następnie wie, czy jesteś offline i online, ale tak naprawdę nie sprawdza połączenia sieciowego.

Daj nam znać, jeśli tego szukasz.

Z poważaniem,


Dzięki za pomoc Trefex. Zmieniłem kod i teraz sprawdzam tylko właściwość navigator.onLine, jednak zachowuję się tak samo jak wcześniej. Proszę spojrzeć na komentarz Mattbasty.
Pierre Duplouy

Cześć Pedro, zgadzam się z mattbasta, ale mam nadzieję, że to zadziała dla Ciebie :) Zdecydowanie użyłbym metody Ajax do odpytywania jakiegoś adresu URL, o którym wiesz, że jest zawsze aktywny, a wtedy będziesz wiedział, czy połączenie zostanie utracone, czy nie. Z drugiej strony, dlaczego wymagasz dokładnego wykrywania statusu online / offline? Może gdybyśmy wiedzieli więcej, byłoby inne rozwiązanie Twojego problemu. Daj nam znać,
Trefex,

1
Ok, dzięki :) Pomyślałem, że byłoby lepiej dla użytkownika, gdyby aplikacja była w stanie automatycznie wykryć zmianę w łączności (nie ma potrzeby ręcznego włączania trybu offline w FF lub IE). W ten sposób, gdy aplikacja przejdzie w tryb offline, użyje swojej lokalnej pamięci podręcznej zamiast wysyłać zapytania do serwera. Znalazłem ten post od Johna Resiga, który prawie wyjaśnia, dlaczego to nie działa: ejohn.org/blog/offline-events
Pierre Duplouy,

Dziękuję za ten wpis na blogu. Rzetelnie dogłębna analiza i od razu do rzeczy. Myślę, że to, co chcesz osiągnąć, najlepiej będzie, jeśli wyślesz zapytanie do jakiegoś serwera (może własnego), a następnie przełączysz się na lokalną pamięć podręczną, gdy jest x liczba limitów czasu. Co myślisz ?
Trefex,

Tak, myślę, że to najlepsza opcja - biorąc pod uwagę obecny stan techniki. Mam nadzieję, że wszystkie przeglądarki w końcu będą w stanie samodzielnie wykryć faktyczną utratę połączenia: korzystanie z navigator.onLine jest dość proste i nie powinno być bardziej skomplikowane. Nie sądzisz?
Pierre Duplouy,

3

Proszę znaleźć moduł require.js, który napisałem dla trybu offline.

define(['offline'], function (Offline) {
    //Tested with Chrome and IE11 Latest Versions as of 20140412
    //Offline.js - http://github.hubspot.com/offline/ 
    //Offline.js is a library to automatically alert your users 
    //when they've lost internet connectivity, like Gmail.
    //It captures AJAX requests which were made while the connection 
    //was down, and remakes them when it's back up, so your app 
    //reacts perfectly.

    //It has a number of beautiful themes and requires no configuration.
    //Object that will be exposed to the outside world. (Revealing Module Pattern)

    var OfflineDetector = {};

    //Flag indicating current network status.
    var isOffline = false;

    //Configuration Options for Offline.js
    Offline.options = {
        checks: {
            xhr: {
                //By default Offline.js queries favicon.ico.
                //Change this to hit a service that simply returns a 204.
                url: 'favicon.ico'
            }
        },

        checkOnLoad: true,
        interceptRequests: true,
        reconnect: true,
        requests: true,
        game: false
    };

    //Offline.js raises the 'up' event when it is able to reach
    //the server indicating that connection is up.
    Offline.on('up', function () {
        isOffline = false;
    });

    //Offline.js raises the 'down' event when it is unable to reach
    //the server indicating that connection is down.
    Offline.on('down', function () {
        isOffline = true;
    });

    //Expose Offline.js instance for outside world!
    OfflineDetector.Offline = Offline;

    //OfflineDetector.isOffline() method returns the current status.
    OfflineDetector.isOffline = function () {
        return isOffline;
    };

    //start() method contains functionality to repeatedly
    //invoke check() method of Offline.js.
    //This repeated call helps in detecting the status.
    OfflineDetector.start = function () {
        var checkOfflineStatus = function () {
            Offline.check();
        };
        setInterval(checkOfflineStatus, 3000);
    };

    //Start OfflineDetector
    OfflineDetector.start();
    return OfflineDetector;
});

Przeczytaj ten wpis na blogu i daj mi znać, co myślisz. http://zen-and-art-of-programming.blogspot.com/2014/04/html-5-offline-application-development.html Zawiera przykładowy kod wykorzystujący offline.js do wykrywania, kiedy klient jest w trybie offline.


3
Zwróć uwagę, że odradza się udzielanie odpowiedzi zawierających tylko łącze , odpowiedzi SO powinny być końcowym punktem poszukiwania rozwiązania (w porównaniu z kolejnym zatrzymaniem odniesień, które z czasem stają się nieaktualne). Proszę rozważyć dodanie tutaj samodzielnego streszczenia, zachowując link jako odniesienie.
kleopatra

Cześć, opublikowałem moduł require.js wraz z odnośnikiem. Dzieki za sugestie.
Srihari Sridharan

2

możesz łatwo wykryć offline między przeglądarkami, jak poniżej

var randomValue = Math.floor((1 + Math.random()) * 0x10000)

$.ajax({
      type: "HEAD",
      url: "http://yoururl.com?rand=" + randomValue,
      contentType: "application/json",
      error: function(response) { return response.status == 0; },
      success: function() { return true; }
   });

możesz zamienić yoururl.com na document.location.pathname.

Sedno rozwiązania polega na tym, że spróbuj połączyć się z nazwą domeny, jeśli nie możesz się połączyć - jesteś offline. działa w różnych przeglądarkach.


biorąc pod uwagę, że po nazwie domeny nie ma nazwy url, nie ma zmiany w uzyskaniu 404
harishr

Czasami nie, na przykład strona główna mojego api to 404
Ben Aubin

jakie znaczenie ma strona główna twojego api? nie rozumiem. ponieważ kiedy trafia na serwer, nie ma adresu URL - więc nie ma przetwarzania, nawet jeśli twoja strona główna ma 404 - to nie ma znaczenia. może być, jeśli możesz podać przykładowy kod, który spróbuję i zrozumiem
zgłaszany

Nie tylko moje interfejsy API, ale wiele witryn nie ma strony głównej. Sprawdź, czy kod stanu i otrzymane dane są puste, to najlepszy sposób, aby upewnić się, że nie jest to zwykły, oczekiwany błąd
Ben Aubin

moje webapi nie ma również strony głównej, ale powyższe nadal działa. dlatego jestem zdezorientowany. jakie znaczenie ma strona główna, biorąc pod uwagę, że działa
harishr

2

Korzystam z opcji FALLBACK w manifeście pamięci podręcznej HTML5, aby sprawdzić, czy moja aplikacja html5 jest online, czy offline:

FALLBACK:
/online.txt /offline.txt

Na stronie html używam javascript do czytania zawartości pliku txt online / offline:

<script>$.get( "urlto/online.txt", function( data ) {
$( ".result" ).html( data );
alert( data );
});</script>

W trybie offline skrypt odczyta zawartość offline.txt. Na podstawie tekstu w plikach możesz wykryć, czy strona internetowa jest w trybie online lub offline.


0

Oto moje rozwiązanie.

Przetestowano w IE, Opera, Chrome, FireFox, Safari, jako Phonegap WebApp na IOS 8 i jako Phonegap WebApp na Android 4.4.2

To rozwiązanie nie działa z programem FireFox na hoście lokalnym.

==================================================== ===============================

onlineCheck.js (ścieżka pliku: „root / js / onlineCheck.js):

var isApp = false;

function onLoad() {
        document.addEventListener("deviceready", onDeviceReady, false);
}

function onDeviceReady() {
    isApp = true;
    }


function isOnlineTest() {
    alert(checkOnline());
}

function isBrowserOnline(no,yes){
    //Didnt work local
    //Need "firefox.php" in root dictionary
    var xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHttp');
    xhr.onload = function(){
        if(yes instanceof Function){
            yes();
        }
    }
    xhr.onerror = function(){
        if(no instanceof Function){
            no();
        }
    }
    xhr.open("GET","checkOnline.php",true);
    xhr.send();
}

function checkOnline(){

    if(isApp)
    {
        var xhr = new XMLHttpRequest();
        var file = "http://dexheimer.cc/apps/kartei/neu/dot.png";

        try {
            xhr.open('HEAD', file , false); 
            xhr.send(null);

            if (xhr.status >= 200 && xhr.status < 304) {
                return true;
            } else {
                return false;
            }
        } catch (e) 
        {
            return false;
        }
    }else
    {
        var tmpIsOnline = false;

        tmpIsOnline = navigator.onLine;

        if(tmpIsOnline || tmpIsOnline == "undefined")
        {
            try{
                //Didnt work local
                //Need "firefox.php" in root dictionary
                var xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHttp');
                xhr.onload = function(){
                    tmpIsOnline = true;
                }
                xhr.onerror = function(){
                    tmpIsOnline = false;
                }
                xhr.open("GET","checkOnline.php",false);
                xhr.send();
            }catch (e){
                tmpIsOnline = false;
            }
        }
        return tmpIsOnline;

    }
}

==================================================== ===============================

index.html (ścieżka pliku: "root / index.html"):

<!DOCTYPE html>
<html>


<head>
    ...

    <script type="text/javascript" src="js/onlineCheck.js" ></script>

    ...

</head>

...

<body onload="onLoad()">

...

    <div onclick="isOnlineTest()">  
        Online?
    </div>
...
</body>

</html>

==================================================== ===============================

checkOnline.php (ścieżka_pliku: "root"):

<?php echo 'true'; ?> 


0

Korzystanie z treści dokumentu:

<body ononline="onlineConditions()" onoffline="offlineConditions()">(...)</body>

Korzystanie ze zdarzenia JavaScript:

window.addEventListener('load', function() {

  function updateOnlineStatus() {

    var condition = navigator.onLine ? "online" : "offline";
    if( condition == 'online' ){
        console.log( 'condition: online')
    }else{
        console.log( 'condition: offline')
    }

  }

  window.addEventListener('online',  updateOnlineStatus );
  window.addEventListener('offline', updateOnlineStatus );

});

Odniesienie : Treść
dokumentu: Zdarzenie on
- line Zdarzenie JavaScript: Zdarzenie online i offline

Dodatkowe przemyślenia:
Wysyłanie wiadomości „połączenie sieciowe to nie to samo, co połączenie internetowe”. Problem z powyższych metod: Możesz sprawdzić połączenie internetowe raz za pomocą Ajax podczas uruchamiania aplikacji i skonfigurować tryb online / offline. Utwórz przycisk ponownego łączenia, aby użytkownik mógł przejść do trybu online. I dodaj do każdego nieudanego żądania Ajax funkcję, która przeniesie użytkownika z powrotem do trybu offline.


1
To nie zadziała: window.addEventListener('online', updateOnlineStatus(event) );ponieważ natychmiast wywołujesz funkcję updateOnlineStatus (). Powinno byćwindow.addEventListener('online', updateOnlineStatus );
Sébastien Rosset
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.