Verifique se o usuário tem uma extensão do Chrome instalada

97

Estou no processo de construção de uma extensão do Chrome e, para que tudo funcione da maneira que eu gostaria, preciso de um script JavaScript externo para detectar se um usuário tem minha extensão instalada.

Por exemplo: um usuário instala meu plug-in e, em seguida, vai para um site com meu script nele. O site detecta que minha extensão está instalada e atualiza a página de acordo.

Isso é possível?

Yehuda Katz
fonte
2
Sim, é possível detectar extensões, desde que você saiba o ID do seu ramal (o que tenho certeza que conhece). Verifique este site para obter mais informações: blog.kotowicz.net/2012/02/intro-to-chrome-addons-hacking.html Vá para a seção sobre 'Encontrar seus complementos um por um'. Boa sorte!
Martin Hughes
A maneira adequada de implementar isso é descrita por BJury abaixo.
Rahatur

Respostas:

46

Tenho certeza de que existe uma maneira direta (chamando funções em sua extensão diretamente ou usando as classes JS para extensões), mas um método indireto (até que algo melhor apareça):

Faça com que sua extensão do Chrome procure um DIV específico ou outro elemento em sua página, com um ID muito específico.

Por exemplo:

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

Faça um getElementByIde defina o innerHTMLpara o número da versão de sua extensão ou algo assim. Você pode então ler o conteúdo desse lado do cliente.

Porém, novamente, você deve usar um método direto, se houver um disponível.


EDIT: Método direto encontrado !!

Use os métodos de conexão encontrados aqui: https://developer.chrome.com/extensions/extension#global-events

Não testado, mas você deve ser capaz de fazer ...

var myPort=chrome.extension.connect('yourextensionid_qwerqweroijwefoijwef', some_object_to_send_on_connect);
Brad
fonte
2
hmmm chrome.extension.connect só parece funcionar quando executado de dentro da extensão (ou qualquer extensão). Eu preciso que funcione a partir de qualquer script js aleatório. Alguma ideia?
Estranho, a documentação diz que deve funcionar. "Ao contrário dos outros cromo. * APIs, partes do chrome.extension pode ser usado por scripts de conteúdo" e listas sendRequest(), onRequest, connect(), onRequest, e getURL().
Brad
@James você está executando o script que usa .connect () de um script hospedado? Eu sei que o Chrome se esforça para não fazer coisas apenas com arquivos locais que não são hospedados para fins de segurança. - Apenas checando.
JamesEggers
@James, o script do qual estou executando .connect () está no mesmo servidor, se é isso que você quer dizer?
23
O último método não é mais válido , pois a connectfunção foi movida para o chrome.runtimenamespace. Veja a resposta (e comentários) de BJury para uma versão mais atualizada
Xan
117

O Chrome agora tem a capacidade de enviar mensagens do site para a extensão.

Portanto, na extensão background.js (content.js não funcionará), adicione algo como:

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

Isso permitirá que você faça uma chamada do site:

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;
        }
    });

Você pode então verificar a variável hasExtension. A única desvantagem é que a chamada é assíncrona, então você precisa contornar isso de alguma forma.

Editar: conforme mencionado abaixo, você precisará adicionar uma entrada ao manifest.json listando os domínios que podem enviar mensagens ao seu addon. Por exemplo:

"externally_connectable": {
    "matches": ["*://localhost/*", "*://your.domain.com/*"]
},
BJury
fonte
2
Isso funciona como um encanto. Outra desvantagem, é claro, é que você precisa controlar a extensão - você não pode usar isso para ver se uma extensão arbitrária de terceiros está instalada.
Eric P
2
@EricP A questão original afirmava que eles estavam escrevendo a extensão, então a questão é discutível.
BJury
13
Você também terá que adicionar o seguinte ao seu manifest.json: "externally_connectable": {"corresponde": [" : // .seudominio.com / *"]}
noname
3
Deve ser {version: '1.0'} e não {version: 1.0} ou então você obterá 'Uncaught SyntaxError: Número inesperado' na extensão Inspecionar console de exibição.
ET-CS
1
No lado da "verificação" (a página da web que tenta verificar a disponibilidade de uma determinada extensão), chrome.runtime é indefinido, chrome 36 no linux.
realmente bom
22

Outro método é expor um recurso acessível pela web , embora isso permita que qualquer site teste se sua extensão está instalada.

Suponha que o ID da sua extensão seja aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, e você adicione um arquivo (digamos, uma imagem de pixel transparente) como test.pngnos arquivos da sua extensão.

Em seguida, você expõe esse arquivo às páginas da web com a web_accessible_resourceschave de manifesto:

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

Em sua página da web, você pode tentar carregar este arquivo pelo URL completo (em uma <img>tag, via XHR ou de qualquer outra forma):

chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/test.png

Se o arquivo carregar, a extensão está instalada. Se houver um erro ao carregar este arquivo, a extensão não está instalada.

// 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); 
  };
}

Nota: se houver um erro ao carregar este arquivo, o referido erro de pilha de rede aparecerá no console sem possibilidade de silenciá-lo. Quando o Chromecast usou esse método, causou um pouco de controvérsia por causa disso; com a eventual solução muito desagradável de simplesmente colocar na lista negra erros muito específicos das Ferramentas de Desenvolvimento pela equipe do Chrome.


Observação importante: este método não funcionará no Firefox WebExtensions. Os recursos acessíveis pela Web expõem de maneira inerente a extensão à impressão digital, uma vez que o URL é previsível ao conhecer o ID. O Firefox decidiu fechar essa lacuna atribuindo um URL aleatório específico da instância para recursos acessíveis na web:

Os arquivos estarão disponíveis em um URL como:

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

Este UUID é gerado aleatoriamente para cada instância do navegador e não é o ID da sua extensão. Isso evita que os sites registrem as impressões digitais das extensões que um usuário instalou.

No entanto, embora a extensão possa ser usada runtime.getURL()para obter esse endereço, você não pode codificá-lo no seu site.

Xan
fonte
Embora esta resposta obtenha o "suco" de stackoverflow.com/a/9216924/1504300 , IMHO adiciona algumas informações muito importantes, como expor o recurso na extensão e o fato de que você pode usar uma solicitação ajax para verificar a existência (objeto de imagem parece estar disponível apenas em HTML5 se não me engano goo.gl/HBeI1i ). Com as informações desta resposta, consegui resolver o problema, achei uma solução "
pronta para usar
@niconic Essa resposta (sendo ruim como apenas link de qualquer maneira) refere-se à situação antes da versão 2 do manifesto entrar em vigor. Anteriormente, não era necessário declarar recursos acessíveis pela web.
Xan
19

Pensei em compartilhar minha pesquisa sobre isso. Eu precisava ser capaz de detectar se uma extensão específica foi instalada para alguns links file: /// funcionarem. Encontrei este artigo aqui. Isso explica um método para obter o manifest.json de uma extensão.

Ajustei um pouco o código e cheguei a:

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'); };
}

Com isso, você deve ser capaz de usar Ext_Detect (ExtensionName, ExtensionID) para detectar a instalação de qualquer número de extensões.

Nato
fonte
2
Parece que o Google tornou as coisas mais seguras. Recebo o seguinte erro quando executo Ext_Detect (): Negando carregamento de chrome-extension: // [my_extension_id] /manifest.json. Os recursos devem ser listados na chave de manifesto web_accessible_resources para serem carregados por páginas fora da extensão.
Lounge9
Consigo fazer isso funcionar com a versão 32.0.1700.107 m do Chrome em 27/02/2014
JE Carter II
1
como @ Lounge9 disse. Os recursos dentro dos pacotes que usam manifest_version 2 (ou superior) são bloqueados por padrão e devem ser colocados na lista de permissões para uso por meio dessa propriedade adicionando a manifest.json: "web_accessible_resources": ["manifest..json"],
ET-CS
Usando a resposta @BJury, você também pode passar dados facilmente da extensão para o script (por exemplo, a versão da extensão) e não precisa expor nenhum arquivo da extensão.
ET-CS
1
Isso funcionou melhor para mim porque nossa extensão será usada em vários domínios e não pode ser predefinida, pois novos domínios são adicionados regularmente. Em vez de acessar manifest.json, criei um novo arquivo version.json e coloquei o número da versão dentro. Isso funciona da mesma forma.
Paul Haggo
7

Outra solução possível, se você for o proprietário do site, é usar a instalação embutida .

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

Eu sei que esta é uma pergunta antiga, mas essa forma foi introduzida no Chrome 15 e então pensei em listá-la para qualquer um que esteja procurando uma resposta.

PAEz
fonte
12
Isso funciona muito bem para um aplicativo do Chrome , mas não para uma extensão do Chrome AFAIK
Eran Medan
Sim, esse site informa como instalar uma extensão em linha , mas aparentemente recomenda "As extensões podem se comunicar com a página de incorporação por meio de scripts de conteúdo para avisá-la de que já estão instaladas." em vez de poder usar chrome.app.isInstalled. Também me confundiu ...
rogerdpack
4

Usei o método do cookie:

No meu arquivo manifest.js, incluí um script de conteúdo que só é executado no meu site:

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

em meu js / mysite.js, tenho uma linha:

document.cookie = "extension_downloaded=True";

e na minha página index.html procuro esse cookie.

if (document.cookie.indexOf('extension_downloaded') != -1){
    document.getElementById('install-btn').style.display = 'none';
}
Chase Roberts
fonte
Tentei todas as soluções acima mas não funcionou, então vejo a sua resposta, é isto que procuro!
John Doe
Isso adiciona sobrecarga a cada solicitação HTTP daqui em diante.
mlissner
3

Você pode fazer com que a extensão defina um cookie e o JavaScript do seu site verifique se esse cookie está presente e se atualize de acordo. Este e provavelmente a maioria dos outros métodos mencionados aqui podem, claro, ser contornados pelo usuário, a menos que você tente e faça com que a extensão crie cookies personalizados dependendo dos carimbos de data / hora, etc., e faça seu aplicativo analisá-los do lado do servidor para ver se é realmente um usuário com o extensão ou alguém fingindo ter, modificando seus cookies.

Niklas
fonte
5
o único problema é que se um usuário excluir suas extensões. O cookie provavelmente permanecerá definido.
Chase Roberts,
3

Há outro método mostrado nesta postagem do Grupos do Google . Resumindo, você pode tentar detectar se o ícone da extensão carrega com sucesso. Isso pode ser útil se a extensão que você está verificando não for a sua.

Beau
fonte
1
Está bem. Como verificamos se existe um ícone de extensão?
Michael Rogers
3

A página da Web interage com a extensão por meio do script de plano de fundo.

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>
Dawid Szymański
fonte
Não funciona no Firefox WebExtensions, onde externally_connectable não é compatível.
mlissner
3

Sua extensão pode interagir com o site (por exemplo, alterar variáveis) e seu site pode detectar isso.

Mas deve haver uma maneira melhor de fazer isso. Eu me pergunto como o Google está fazendo isso em sua galeria de extensões (os aplicativos já instalados estão marcados).

Editar:

A galeria usa a função chrome.management.get . Exemplo:

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

Mas você só pode acessar o método em páginas com as permissões corretas.

Fox32
fonte
1
vocês que exigiriam a extensão para interagir com todos os sites em todas as guias, o que seria lento / difícil de implementar e com erros: - /
O problema é que a comunicação na outra direção (página para extensão) não é possível, por causa do modelo de segurança do cromo. Se você não quiser seguir o caminho de 'interação', escolha o caminho dos cookies.
Fox32
2
Caro @ Fox32, chrome.management.get ..., retorna este erro:Uncaught TypeError: Cannot read property 'get' of undefined
Hosein Aqajani
3

Muitas das respostas aqui até agora são apenas Chrome ou incorrem em uma penalidade de sobrecarga de HTTP. A solução que estamos usando é um pouco diferente:

1. Adicione um novo objeto à lista content_scripts do manifesto da seguinte forma:

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

Isso permitirá que o código em install_notifier.js seja executado nesse site (se você ainda não tiver permissões lá).

2. Envie uma mensagem para cada site na chave de manifesto acima.

Adicione algo assim a install_notifier.js (observe que isso está usando um encerramento para evitar que as variáveis ​​sejam globais, mas isso não é estritamente necessário):

// 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
  }, "*");
})();

Sua mensagem pode dizer qualquer coisa, mas é útil enviar a versão para que você saiba com o que está lidando. Então...

3. Em seu site, ouça essa mensagem.

Adicione isso ao seu site em algum lugar:

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");
  }
});

Isso funciona no Firefox e no Chrome e não incorre em sobrecarga de HTTP ou manipula a página.

mlissner
fonte
0

Se você tem controle sobre a extensão do Chrome, pode tentar o que eu fiz:

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

E depois:

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

}

Parece um pouco maluco, mas não consegui fazer os outros métodos funcionarem e me preocupo com o Chrome mudando sua API aqui. É duvidoso que esse método pare de funcionar tão cedo.

gwg
fonte
como mencionado acima - eu testei isso, mas a ordem das operações parece estranha - qual script é executado primeiro etc?
Brady Moritz
0

Você também pode usar um método de navegador cruzado que eu usei. Usa o conceito de adicionar um div.

em seu script de conteúdo (sempre que o script carrega, ele deve fazer isso)

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

em seu site, você afirma algo como,

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

E lance a mensagem apropriada.

Prakash Palnati
fonte
Eu testei isso, mas a ordem das operações parece estranha - qual script é executado primeiro etc?
Brady Moritz
@BradyMoritz na mesma ordem da resposta. Adicione a classe primeiro e depois afirme.
Prakash Palnati
parecia que meu script de conteúdo não estava necessariamente em execução antes do meu script in-page?
Brady Moritz
Acho isso fácil de consertar. Você pode ter certeza de que seu script de conteúdo será executado logo após acessar a URL / rota necessária usando regex. Você já tentou isso?
Prakash Palnati
0

Se você está tentando detectar qualquer extensão de qualquer site, esta postagem ajudou: https://ide.hey.network/post/5c3b6c7aa7af38479accc0c7

Basicamente, a solução seria simplesmente tentar obter um arquivo específico (manifest.json ou uma imagem) da extensão especificando seu caminho. Aqui está o que usei. Definitivamente funcionando:

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)
}
nab.
fonte
0

Aqui está uma outra abordagem moderna:

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')
})
K-Gun
fonte