Sprawdź, czy użytkownik ma zainstalowane rozszerzenie Chrome


100

Jestem w trakcie tworzenia rozszerzenia do Chrome i aby wszystko działało tak, jak bym chciał, potrzebuję zewnętrznego skryptu JavaScript, aby móc wykryć, czy użytkownik ma zainstalowane moje rozszerzenie.

Na przykład: użytkownik instaluje moją wtyczkę, a następnie przechodzi do witryny internetowej z moim skryptem. Witryna wykrywa, że ​​moje rozszerzenie jest zainstalowane i odpowiednio aktualizuje stronę.

czy to możliwe?


2
Tak, możliwe jest wykrycie rozszerzeń, o ile znasz swój identyfikator rozszerzenia (na pewno wiesz). Sprawdź tę stronę, aby uzyskać więcej informacji: blog.kotowicz.net/2012/02/intro-to-chrome-addons-hacking.html Przejdź do sekcji „Wyszukiwanie dodatków jeden po drugim”. Powodzenia!
Martin Hughes

Właściwy sposób realizacji tego jest opisany przez BJury poniżej.
Rahatur

Odpowiedzi:


46

Jestem pewien, że istnieje sposób bezpośredni (wywołanie funkcji bezpośrednio na twoim rozszerzeniu lub użycie klas JS do rozszerzeń), ale metoda pośrednia (dopóki nie pojawi się coś lepszego):

Poproś rozszerzenie do Chrome o wyszukanie na stronie określonego DIV lub innego elementu z bardzo konkretnym identyfikatorem.

Na przykład:

<div id="ExtensionCheck_JamesEggersAwesomeExtension"></div>

Zrób a getElementByIdi ustaw innerHTMLnumer wersji swojego rozszerzenia lub coś w tym stylu. Następnie możesz przeczytać zawartość tej strony klienta.

Ponownie jednak powinieneś użyć metody bezpośredniej, jeśli jest dostępna.


EDYCJA: Znaleziono metodę bezpośrednią !!

Skorzystaj z metod połączenia, które znajdziesz tutaj: https://developer.chrome.com/extensions/extension#global-events

Nie przetestowano, ale powinieneś być w stanie to zrobić ...

var myPort=chrome.extension.connect('yourextensionid_qwerqweroijwefoijwef', some_object_to_send_on_connect);

3
hmmm chrome.extension.connect wydaje się działać tylko wtedy, gdy jest uruchamiany z poziomu rozszerzenia (lub dowolnego rozszerzenia). Potrzebuję go do działania z dowolnego losowego skryptu js. Jakieś pomysły?

Dziwne, dokumentacja mówi, że powinno działać. „W przeciwieństwie do innych chrom. * API, części chrome.extension może być używany przez skrypty treści” i wykazy sendRequest(), onRequest, connect(), onRequest, i getURL().
Brad,

@James czy wykonujesz skrypt, który używa .connect () z hostowanego skryptu? Wiem, że Chrome stara się nie robić rzeczy tylko z lokalnymi plikami, które nie są hostowane ze względów bezpieczeństwa. - Tylko sprawdzam.
JamesEggers

@James, skrypt, z którego wykonuję .connect () znajduje się na tym samym serwerze, jeśli to masz na myśli?

23
Ostatnia metoda nie jest już poprawna , ponieważ connectfunkcja została przeniesiona do chrome.runtimeprzestrzeni nazw. Zobacz odpowiedź (i komentarze) BJury, aby uzyskać bardziej aktualną wersję
Xan

120

Chrome ma teraz możliwość wysyłania wiadomości ze strony internetowej do rozszerzenia.

Więc w rozszerzeniu background.js (content.js nie będzie działać) dodaj coś takiego:

chrome.runtime.onMessageExternal.addListener(
    function(request, sender, sendResponse) {
        if (request) {
            if (request.message) {
                if (request.message == "version") {
                    sendResponse({version: 1.0});
                }
            }
        }
        return true;
    });

Umożliwi to nawiązanie połączenia ze strony internetowej:

var hasExtension = false;

chrome.runtime.sendMessage(extensionId, { message: "version" },
    function (reply) {
        if (reply) {
            if (reply.version) {
                if (reply.version >= requiredVersion) {
                    hasExtension = true;
                }
            }
        }
        else {
          hasExtension = false;
        }
    });

Następnie możesz sprawdzić zmienną hasExtension. Jedyną wadą jest to, że wywołanie jest asynchroniczne, więc musisz jakoś to obejść.

Edycja: Jak wspomniano poniżej, musisz dodać wpis do pliku manifest.json zawierający listę domen, z których można wysyłać wiadomości do Twojego dodatku. Na przykład:

"externally_connectable": {
    "matches": ["*://localhost/*", "*://your.domain.com/*"]
},

2
To działa jak urok. Inną wadą jest oczywiście to, że musisz kontrolować rozszerzenie - nie możesz tego użyć, aby sprawdzić, czy jest zainstalowane dowolne rozszerzenie innej firmy.
Eric P,

2
@EricP W pierwotnym pytaniu stwierdzono, że piszą rozszerzenie, więc sprawa jest dyskusyjna.
BJury

13
Będziesz także musiał dodać do swojego manifest.json: "externally_connectable": {"pasuje": [" : // .twojadomena.com / *"]}
noname

3
Powinien to być {version: '1.0'}, a nie {version: 1.0}, bo w przeciwnym razie w rozszerzeniu pojawi się komunikat „Uncaught SyntaxError: Nieoczekiwany numer”. Sprawdź konsolę widoku.
ET-CS

1
Po stronie „sprawdzania” (strona próbująca sprawdzić dostępność danego rozszerzenia), chrome.runtime jest niezdefiniowane, chrome 36 na Linuksie.
fajnie

22

Inną metodą jest udostępnienie zasobu dostępnego w sieci , chociaż pozwoli to dowolnej witrynie na sprawdzenie, czy rozszerzenie jest zainstalowane.

Załóżmy, że identyfikator rozszerzenia to aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai dodajesz plik (powiedzmy przezroczysty obraz pikselowy) jakotest.png w plikach rozszerzenia.

Następnie udostępniasz ten plik na stronach internetowych za pomocą web_accessible_resourcesklucza manifestu:

  "web_accessible_resources": [
    "test.png"
  ],

Na swojej stronie internetowej możesz spróbować załadować ten plik <img>, używając pełnego adresu URL (w tagu, przez XHR lub w inny sposób):

chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/test.png

Jeśli plik zostanie załadowany, rozszerzenie zostanie zainstalowane. Jeśli podczas ładowania tego pliku wystąpi błąd, rozszerzenie nie jest zainstalowane.

// Code from https://groups.google.com/a/chromium.org/d/msg/chromium-extensions/8ArcsWMBaM4/2GKwVOZm1qMJ
function detectExtension(extensionId, callback) { 
  var img; 
  img = new Image(); 
  img.src = "chrome-extension://" + extensionId + "/test.png"; 
  img.onload = function() { 
    callback(true); 
  }; 
  img.onerror = function() { 
    callback(false); 
  };
}

Uwaga: jeśli wystąpi błąd podczas ładowania tego pliku, wspomniany stos sieciowy błąd pojawi się w konsoli bez możliwości jego wyciszenia. Kiedy Chromecast używał tej metody, wywołało to sporo kontrowersji z tego powodu; z ostatecznym bardzo brzydkim rozwiązaniem polegającym na umieszczeniu na czarnej liście bardzo konkretnych błędów z Narzędzi dla programistów przez zespół Chrome.


Ważna uwaga: ta metoda nie będzie działać w Firefox WebExtensions. Zasoby dostępne w sieci z natury narażają rozszerzenie na odciski palców, ponieważ adres URL jest przewidywalny dzięki znajomości identyfikatora. Firefox postanowił zamknąć tę lukę, przypisując losowy adres URL specyficzny dla instancji do zasobów dostępnych w sieci:

Pliki będą wtedy dostępne pod adresem URL takim jak:

moz-extension://<random-UUID>/<path/to/resource>

Ten UUID jest generowany losowo dla każdej instancji przeglądarki i nie jest identyfikatorem Twojego rozszerzenia. Zapobiega to pobieraniu przez strony internetowe odcisków palców zainstalowanych przez użytkownika rozszerzeń.

Jednak chociaż rozszerzenie może użyć runtime.getURL()do uzyskania tego adresu, nie można go na stałe zakodować w swojej witrynie.


Chociaż ta odpowiedź zawiera "sok" ze stackoverflow.com/a/9216924/1504300 , IMHO dodaje kilka całkiem ważnych informacji, takich jak ujawnienie zasobu w rozszerzeniu oraz fakt, że możesz użyć żądania AJAX do sprawdzenia istnienia (obiekt Image wydaje się dostępny tylko w HTML5, jeśli się nie mylę goo.gl/HBeI1i ). Z informacji w tej odpowiedzi udało mi się rozwiązać problem, znalazłem go jak „out of the box” rozwiązanie
reallynice

@niconic Ta odpowiedź (w każdym razie zła jako tylko link) odnosi się do sytuacji przed wejściem w życie manifestu wersji 2. Wcześniej nie trzeba było deklarować dostępności zasobów w sieci.
Xan

19

Pomyślałem, że podzielę się moimi badaniami na ten temat. Musiałem być w stanie wykryć, czy jest zainstalowane określone rozszerzenie, aby niektóre linki file: /// działały. Natknąłem się tutaj na ten artykuł. Wyjaśniło to metodę uzyskiwania pliku manifest.json rozszerzenia.

Poprawiłem nieco kod i wymyśliłem:

function Ext_Detect_NotInstalled(ExtName, ExtID) {
  console.log(ExtName + ' Not Installed');
  if (divAnnounce.innerHTML != '')
    divAnnounce.innerHTML = divAnnounce.innerHTML + "<BR>"

  divAnnounce.innerHTML = divAnnounce.innerHTML + 'Page needs ' + ExtName + ' Extension -- to intall the LocalLinks extension click <a href="https://chrome.google.com/webstore/detail/locallinks/' + ExtID + '">here</a>';
}

function Ext_Detect_Installed(ExtName, ExtID) {
  console.log(ExtName + ' Installed');
}

var Ext_Detect = function (ExtName, ExtID) {
  var s = document.createElement('script');
  s.onload = function () { Ext_Detect_Installed(ExtName, ExtID); };
  s.onerror = function () { Ext_Detect_NotInstalled(ExtName, ExtID); };
  s.src = 'chrome-extension://' + ExtID + '/manifest.json';
  document.body.appendChild(s);
}

var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;

if (is_chrome == true) {
  window.onload = function () { Ext_Detect('LocalLinks', 'jllpkdkcdjndhggodimiphkghogcpida'); };
}

Dzięki temu powinieneś być w stanie użyć Ext_Detect (ExtensionName, ExtensionID) do wykrywania instalacji dowolnej liczby rozszerzeń.


3
Wygląda na to, że Google poprawiło bezpieczeństwo. Po uruchomieniu Ext_Detect () pojawia się następujący błąd: Odmawianie ładowania rozszerzenia chrome: // [my_extension_id] /manifest.json. Zasoby muszą być wymienione w kluczu manifestu web_accessible_resources, aby mogły być ładowane przez strony spoza rozszerzenia.
Lounge

Jestem w stanie zmusić to do pracy z wersją 32.0.1700.107 m Chrome na dzień 27.02.2014
JE Carter II

1
jak powiedział @ Lounge9. Zasoby w pakietach korzystających z manifest_version 2 (lub nowszej) są domyślnie blokowane i muszą zostać umieszczone na białej liście, aby można było ich używać za pośrednictwem tej usługi, dodając do manifest.json: "web_accessible_resources": ["manifest..json"],
ET-CS

Korzystając z odpowiedzi @BJury możesz również łatwo przekazać dane z rozszerzenia do skryptu (na przykład wersję rozszerzenia) i nie musisz ujawniać żadnego pliku z rozszerzenia.
ET-CS

1
To zadziałało najlepiej, ponieważ nasze rozszerzenie będzie używane w wielu domenach i nie może być wstępnie zdefiniowane, ponieważ nowe domeny są dodawane regularnie. Zamiast otwierać plik manifest.json utworzyłem nowy plik version.json i umieściłem w nim numer wersji. Działa to tak samo.
Paul Haggo

7

Innym możliwym rozwiązaniem, jeśli jesteś właścicielem witryny, jest instalacja bezpośrednia .

if (chrome.app.isInstalled) {
  // extension is installed.
}

Wiem, że to stare pytanie, ale w ten sposób zostało wprowadzone w Chrome 15, więc pomyślałem, że wymienię je dla każdego, kto dopiero teraz szuka odpowiedzi.


12
Działa to świetnie w przypadku aplikacji Chrome , ale nie w przypadku rozszerzenia Chrome AFAIK
Eran Medan,

Tak, ta witryna internetowa informuje, jak zainstalować rozszerzenie, ale najwyraźniej zaleca „Rozszerzenia mogą komunikować się ze stroną osadzania za pośrednictwem skryptów zawartości, aby poinformować ją, że są już zainstalowane”. zamiast móc używać chrome.app.isInstalled. Mnie też
zdezorientował


4

Wykorzystałem metodę cookies:

W moim pliku manifest.js umieściłem skrypt zawartości, który działa tylko w mojej witrynie:

 "content_scripts": [
        {
        "matches": [
            "*://*.mysite.co/*"
            ],
        "js": ["js/mysite.js"],
        "run_at": "document_idle"
        }
    ], 

w moim js / mysite.js mam jedną linię:

document.cookie = "extension_downloaded=True";

i na mojej stronie index.html szukam tego pliku cookie.

if (document.cookie.indexOf('extension_downloaded') != -1){
    document.getElementById('install-btn').style.display = 'none';
}

Wypróbowałem wszystkie powyższe rozwiązania, ale nie działa, wtedy widzę twoją odpowiedź, tego właśnie szukam!
John Doe,

Powoduje to odtąd dodatkowe obciążenie każdego żądania HTTP.
mlissner

3

Rozszerzenie może ustawić plik cookie i poprosić JavaScript witryny, aby sprawdzał, czy ten plik cookie jest obecny, i odpowiednio aktualizuje. Ta i prawdopodobnie większość innych wymienionych tutaj metod może oczywiście zostać omówione przez użytkownika, chyba że spróbujesz i rozszerzenie utworzy niestandardowe pliki cookie w zależności od sygnatur czasowych itp., A aplikacja przeanalizuje je po stronie serwera, aby sprawdzić, czy naprawdę jest to użytkownik z rozszerzenie lub ktoś udaje, że je posiada, modyfikując swoje pliki cookie.


5
jedynym problemem jest to, że jeśli użytkownik usunie Twoje rozszerzenia. Plik cookie prawdopodobnie pozostanie ustawiony.
Chase Roberts


3

Strona internetowa współdziała z rozszerzeniem poprzez skrypt działający w tle.

manifest.json:

"background": {
    "scripts": ["background.js"],
    "persistent": true
},
"externally_connectable": {
    "matches": ["*://(domain.ext)/*"]
},

background.js:
chrome.runtime.onMessageExternal.addListener(function(msg, sender, sendResponse) {
    if ((msg.action == "id") && (msg.value == id))
    {
        sendResponse({id : id});
    }
});

page.html:

<script>
var id = "some_ext_id";
chrome.runtime.sendMessage(id, {action: "id", value : id}, function(response) {
    if(response && (response.id == id)) //extension installed
    {
        console.log(response);
    }
    else //extension not installed
    {
        console.log("Please consider installig extension");
    }

});
</script>

Nie działa w Firefox WebExtensions, gdzie externally_connectable nie jest obsługiwana.
mlissner

3

Twoje rozszerzenie może wchodzić w interakcje z witryną (np. Zmieniać zmienne), a witryna może to wykryć.

Ale powinien istnieć lepszy sposób, aby to zrobić. Ciekawe, jak Google to robi na swojej galerii rozszerzeń (zaznaczone są już zainstalowane aplikacje).

Edytować:

Galeria korzysta z funkcji chrome.management.get . Przykład:

chrome.management.get("mblbciejcodpealifnhfjbdlkedplodp", function(a){console.log(a);});

Ale możesz uzyskać dostęp do metody tylko ze stron z odpowiednimi uprawnieniami.


1
tak, wymagałoby to rozszerzenia do interakcji z każdą witryną na każdej karcie, która byłaby wolna / trudna do wdrożenia i zawiera błędy: - /

Problem polega na tym, że komunikacja w drugą stronę (strona do rozszerzenia) nie jest możliwa ze względu na model zabezpieczeń Chrome. Jeśli nie chcesz wchodzić w interakcję, skorzystaj z metody cookie.
Fox32,

2
Drogi @ Fox32, chrome.management.get ..., zwraca ten błąd:Uncaught TypeError: Cannot read property 'get' of undefined
Hosein Aqajani,

3

Wiele odpowiedzi tutaj do tej pory dotyczy tylko Chrome lub wiąże się z karą narzutu HTTP. Rozwiązanie, z którego korzystamy, jest trochę inne:

1. Dodaj nowy obiekt do listy manifestu content_scripts w następujący sposób:

{
  "matches": ["https://www.yoursite.com/*"],
  "js": [
    "install_notifier.js"
  ],
  "run_at": "document_idle"
}

Umożliwi to uruchomienie kodu z pliku install_notifier.js w tej witrynie (jeśli nie masz tam jeszcze uprawnień).

2. Wyślij wiadomość do każdej witryny w powyższym kluczu manifestu.

Dodaj coś takiego do pliku install_notifier.js (zwróć uwagę, że jest to użycie zamknięcia, aby zmienne nie były globalne, ale nie jest to bezwzględnie konieczne):

// Dispatch a message to every URL that's in the manifest to say that the extension is
// installed.  This allows webpages to take action based on the presence of the
// extension and its version. This is only allowed for a small whitelist of
// domains defined in the manifest.
(function () {
  let currentVersion = chrome.runtime.getManifest().version;
  window.postMessage({
    sender: "my-extension",
    message_name: "version",
    message: currentVersion
  }, "*");
})();

Twoja wiadomość może mówić wszystko, ale warto wysłać wersję, aby wiedzieć, z czym masz do czynienia. Następnie...

3. W witrynie posłuchaj tego komunikatu.

Dodaj to gdzieś do swojej witryny:

window.addEventListener("message", function (event) {
  if (event.source == window &&
    event.data.sender &&
    event.data.sender === "my-extension" &&
    event.data.message_name &&
    event.data.message_name === "version") {
    console.log("Got the message");
  }
});

Działa to w przeglądarkach Firefox i Chrome i nie powoduje obciążenia HTTP ani nie manipuluje stroną.


2

Oto inne nowoczesne podejście:

const checkExtension = (id, src, callback) => {
    let e = new Image()
    e.src = 'chrome-extension://'+ id +'/'+ src
    e.onload = () => callback(1), e.onerror = () => callback(0)
}

// "src" must be included to "web_accessible_resources" in manifest.json
checkExtension('gighmmpiobklfepjocnamgkkbiglidom', 'icons/icon24.png', (ok) => {
    console.log('AdBlock: %s', ok ? 'installed' : 'not installed')
})
checkExtension('bhlhnicpbhignbdhedgjhgdocnmhomnp', 'images/checkmark-icon.png', (ok) => {
    console.log('ColorZilla: %s', ok ? 'installed' : 'not installed')
})

1
pamiętaj, aby zaktualizować manifest, aby umożliwić dostęp do zasobów: "web_accessible_resources": ["icons / *. png"]
Derek Wade

0

Jeśli masz kontrolę nad rozszerzeniem Chrome, możesz spróbować tego, co zrobiłem:

// Inside Chrome extension
var div = document.createElement('div');
div.setAttribute('id', 'myapp-extension-installed-div');
document.getElementsByTagName('body')[0].appendChild(div);

I wtedy:

// On web page that needs to detect extension
if ($('#myapp-extension-installed-div').length) {

}

Wydaje się to trochę hakerskie, ale nie mogłem uruchomić innych metod i martwię się, że Chrome zmieni tutaj swoje API. Wątpliwe, że ta metoda wkrótce przestanie działać.


jak wspomniano powyżej - przetestowałem to, ale kolejność operacji wydaje się dziwna - który skrypt jest uruchamiany jako pierwszy itp.?
Brady Moritz

0

Możesz także użyć metody cross-browser, której użyłem. Używa koncepcji dodawania div.

w skrypcie treści (powinien to zrobić za każdym razem, gdy skrypt się ładuje)

if ((window.location.href).includes('*myurl/urlregex*')) {
        $('html').addClass('ifextension');
        }

na swojej stronie zapewniasz coś takiego,

if (!($('html').hasClass('ifextension')){}

I wyślij odpowiednią wiadomość.


Przetestowałem to, ale kolejność operacji wydaje się dziwna - który skrypt jest uruchamiany jako pierwszy itp.?
Brady Moritz

@BradyMoritz w tej samej kolejności, co w odpowiedzi. Najpierw dodaj klasę, a następnie potwierdź.
Prakash Palnati

wyglądało na to, że mój skrypt zawartości niekoniecznie działał przed moim skryptem na stronie?
Brady Moritz

Myślę, że łatwo to naprawić. Możesz upewnić się, że skrypt zawartości działa zaraz po trafieniu do wymaganego adresu URL / trasy za pomocą wyrażenia regularnego. Czy próbowałeś tego?
Prakash Palnati

0

Jeśli próbujesz wykryć jakiekolwiek rozszerzenie z dowolnej strony internetowej, ten post pomógł: https://ide.hey.network/post/5c3b6c7aa7af38479accc0c7

Zasadniczo rozwiązaniem byłoby po prostu spróbować pobrać określony plik (manifest.json lub obraz) z rozszerzenia, określając jego ścieżkę. Oto, czego użyłem. Zdecydowanie działające:

const imgExists = function(_f, _cb) {
    const __i = new Image();
    __i.onload = function() {
        if (typeof _cb === 'function') {
            _cb(true);
        }
    }
    __i.onerror = function() {
        if (typeof _cb === 'function') {
            _cb(false);
        }
    }
    __i.src = _f;
    __i = null;
});

try {
    imgExists("chrome-extension://${CHROME_XT_ID}/xt_content/assets/logo.png", function(_test) {
        console.log(_test ? 'chrome extension installed !' : 'chrome extension not installed..');
        ifrm.xt_chrome = _test;
        // use that information
    });
} catch (e) {
    console.log('ERROR', e)
}

0

Oto, jak możesz wykryć określone zainstalowane rozszerzenie i wyświetlić komunikat ostrzegawczy.

Najpierw musisz otworzyć plik manifestu rozszerzenia, przechodząc do chrome-extension: //extension_id_here_hkdppipefbchgpohn/manifest.json i poszukaj dowolnej nazwy pliku w sekcji „web_accessible_resources”.

<div class="chromewarning" style="display:none">
    <script type="text/javascript">
                            $.get("chrome-extension://extension_id_here_hkdppipefbchgpohn/filename_found_in_ web_accessible_resources.png").done(function () {
                              $(".chromewarning").show();
                            }).fail(function () {
                             //  alert("failed.");
                            });
                        </script>
                        <p>We have detected a browser extension that conflicts with learning modules in this course.</p>
            </div>
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.