Compruebe si el usuario tiene instalada una extensión de Chrome

96

Estoy en el proceso de crear una extensión de Chrome, y para que todo funcione como me gustaría, necesito un script JavaScript externo para poder detectar si un usuario tiene instalada mi extensión.

Por ejemplo: un usuario instala mi complemento y luego va a un sitio web con mi secuencia de comandos. El sitio web detecta que mi extensión está instalada y actualiza la página en consecuencia.

es posible?

Yehuda Katz
fuente
2
Sí, es posible detectar extensiones, siempre que sepa su ID de extensión (lo cual estoy seguro de que sabe). Consulte este sitio para obtener más información: blog.kotowicz.net/2012/02/intro-to-chrome-addons-hacking.html Vaya a la sección "Encontrar sus complementos uno por uno". ¡Buena suerte!
Martin Hughes
BJury describe a continuación la forma correcta de implementar esto.
Rahatur
esta publicación ayudó: ide.hey.network/post/5c3b6c7aa7af38479accc0c7
nab.

Respuestas:

46

Estoy seguro de que hay una forma directa (llamar a funciones en su extensión directamente, o usando las clases JS para extensiones), pero un método indirecto (hasta que aparezca algo mejor):

Haga que su extensión de Chrome busque un DIV específico u otro elemento en su página, con una ID muy específica.

Por ejemplo:

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

Haga una getElementByIdy configure el innerHTMLnúmero de versión de su extensión o algo así. Luego puede leer el contenido de ese lado del cliente.

Sin embargo, nuevamente, debe usar un método directo si hay uno disponible.


EDITAR: ¡Método directo encontrado!

Utilice los métodos de conexión que se encuentran aquí: https://developer.chrome.com/extensions/extension#global-events

No probado, pero debería poder hacer ...

var myPort=chrome.extension.connect('yourextensionid_qwerqweroijwefoijwef', some_object_to_send_on_connect);
Puntilla
fuente
2
hmmm chrome.extension.connect solo parece funcionar cuando se ejecuta desde la extensión (o cualquier extensión). Necesito que funcione desde cualquier script js aleatorio. ¿Algunas ideas?
Extraño, la documentación dice que debería funcionar. "A diferencia de la otra cromo. * API, partes de chrome.extension pueden ser utilizados por los scripts de contenido" y enumera sendRequest(), onRequest, connect(), onRequest, y getURL().
Brad
@James, ¿está ejecutando el script que usa .connect () desde un script alojado? Sé que Chrome se esfuerza por no hacer cosas solo con archivos locales que no están alojados por motivos de seguridad. - Solo revisando.
JamesEggers
@James, el script que estoy ejecutando .connect () está en el mismo servidor, si eso es lo que quieres decir.
23
El último método ya no es válido , ya que la connectfunción se movió al chrome.runtimeespacio de nombres. Vea la respuesta (y comentarios) de BJury para una versión más actualizada
Xan
116

Chrome ahora tiene la capacidad de enviar mensajes desde el sitio web a la extensión.

Entonces, en la extensión background.js (content.js no funcionará) agregue 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;
    });

Esto le permitirá realizar una llamada desde el sitio web:

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

Luego puede verificar la variable hasExtension. El único inconveniente es que la llamada es asincrónica, por lo que debe solucionarlo de alguna manera.

Editar: como se menciona a continuación, deberá agregar una entrada al manifest.json que enumere los dominios que pueden enviar mensajes a su complemento. P.ej:

"externally_connectable": {
    "matches": ["*://localhost/*", "*://your.domain.com/*"]
},
Jurado
fuente
2
Esto funciona a las mil maravillas. Otro inconveniente es, por supuesto, que debe controlar la extensión; no puede usar esto para ver si está instalada una extensión de terceros arbitraria.
Eric P
2
@EricP La pregunta original decía que estaban escribiendo la extensión, por lo que el problema es discutible.
BJury
12
También tendrá que agregar lo siguiente a su manifest.json: "externally_connectable": {"matches": [" : // .yourdomain.com / *"]}
noname
3
Debería ser {versión: '1.0'} y no {versión: 1.0} o de lo contrario obtendrá 'Error de sintaxis no detectado: número inesperado' en la extensión Inspeccionar vista de la consola.
ET-CS
1
En el lado de "comprobación" (la página web que intenta comprobar la disponibilidad de una extensión determinada), chrome.runtime no está definido, chrome 36 en Linux.
realmente agradable
22

Otro método es exponer un recurso accesible desde la web , aunque esto permitirá que cualquier sitio web pruebe si su extensión está instalada.

Suponga que el ID de su extensión es aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, y agrega un archivo (digamos, una imagen de píxel transparente) como test.pngen los archivos de su extensión.

Luego, expone este archivo a las páginas web con la web_accessible_resourcesclave de manifiesto:

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

En su página web, puede intentar cargar este archivo por su URL completa (en una <img>etiqueta, a través de XHR o de cualquier otra forma):

chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/test.png

Si el archivo se carga, entonces se instala la extensión. Si hay un error al cargar este archivo, la extensión no 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: si hay un error al cargar este archivo, dicho error de pila de red aparecerá en la consola sin posibilidad de silenciarlo. Cuando Chromecast usó este método, causó bastante controversia debido a esto; con la eventual solución muy fea de simplemente incluir en la lista negra errores muy específicos de Dev Tools por el equipo de Chrome.


Nota importante: este método no funcionará en Firefox WebExtensions. Los recursos accesibles a través de la web exponen de forma inherente la extensión a la toma de huellas digitales, ya que la URL es predecible al conocer la ID. Firefox decidió cerrar ese agujero asignando una URL aleatoria específica de la instancia a los recursos accesibles en la web:

Los archivos estarán disponibles usando una URL como:

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

Este UUID se genera aleatoriamente para cada instancia del navegador y no es el ID de su extensión. Esto evita que los sitios web tomen las huellas digitales de las extensiones que un usuario ha instalado.

Sin embargo, si bien la extensión puede usarse runtime.getURL()para obtener esta dirección, no puede codificarla en su sitio web.

Xan
fuente
Aunque esta respuesta obtiene el "jugo" de stackoverflow.com/a/9216924/1504300 , en mi humilde opinión agrega información bastante importante, como exponer el recurso en la extensión y el hecho de que puede usar una solicitud ajax para verificar la existencia (objeto de imagen parece disponible solo en HTML5 si no me equivoco goo.gl/HBeI1i ). Con la información en esta respuesta, he podido resolver el problema, lo encontré como una solución "lista para
usar
@niconic Esa respuesta (siendo mala como enlace solo de todos modos) se refiere a la situación antes de que la versión 2 del manifiesto entrara en vigencia. Anteriormente, no era necesario declarar los recursos accesibles desde la web.
Xan
19

Pensé en compartir mi investigación sobre esto. Necesitaba poder detectar si se instaló una extensión específica para que funcionaran algunos enlaces file: ///. Encontré este artículo aquí. Esto explica un método para obtener el archivo manifest.json de una extensión.

Ajusté un poco el código y se me ocurrió:

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

Con esto, debería poder usar Ext_Detect (ExtensionName, ExtensionID) para detectar la instalación de cualquier número de extensiones.

OTAN
fuente
2
Parece que Google ha hecho las cosas más seguras, obtengo el siguiente error cuando ejecuto Ext_Detect (): Denying load of chrome-extension: // [my_extension_id] /manifest.json. Los recursos deben incluirse en la clave de manifiesto web_accessible_resources para que las páginas fuera de la extensión los carguen.
Salón 9
Puedo hacer que esto funcione con la versión 32.0.1700.107 m de Chrome a partir del 27/02/2014
JE Carter II
1
como dijo @ Lounge9. Los recursos dentro de los paquetes que usan manifest_version 2 (o superior) están bloqueados de manera predeterminada y deben incluirse en la lista blanca para su uso a través de esta propiedad agregando a manifest.json: "web_accessible_resources": ["manifest..json"],
ET-CS
Con la respuesta de @BJury, también puede pasar datos fácilmente desde la extensión al script (por ejemplo, la versión de la extensión) y no necesita exponer ningún archivo de la extensión.
ET-CS
1
Esto funcionó mejor para mí porque nuestra extensión se utilizará en varios dominios y no se pudo predefinir ya que se agregan nuevos dominios con regularidad. En lugar de acceder a manifest.json, creé un nuevo archivo version.json y puse el número de versión dentro. Esto funciona igual.
Paul Haggo
7

Otra posible solución si es propietario del sitio web es utilizar la instalación en línea .

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

Sé que esta es una pregunta antigua, pero de esta manera se introdujo en Chrome 15, por lo que pensé en enumerarla para cualquiera que esté buscando una respuesta.

PAEz
fuente
12
Esto funciona muy bien para una aplicación de Chrome , pero no para una extensión de Chrome AFAIK
Eran Medan
Sí, ese sitio web le dice cómo instalar en línea una extensión, pero aparentemente recomienda "Las extensiones pueden comunicarse con la página de inserción a través de scripts de contenido para hacerle saber que ya están instaladas". en lugar de poder usar chrome.app.isInstalled. También me confundió ...
Rogerdpack
4

Usé el método de las cookies:

En mi archivo manifest.js incluí un script de contenido que solo se ejecuta en mi sitio:

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

en mi js / mysite.js tengo una línea:

document.cookie = "extension_downloaded=True";

y en mi página index.html busco esa cookie.

if (document.cookie.indexOf('extension_downloaded') != -1){
    document.getElementById('install-btn').style.display = 'none';
}
Chase Roberts
fuente
Probé todas las soluciones anteriores pero no funcionan, luego veo su respuesta, ¡esto es lo que estoy buscando!
John Doe
Esto agrega sobrecarga a cada solicitud HTTP de ahora en adelante.
mlissner
3

Puede hacer que la extensión establezca una cookie y que el JavaScript de su sitio web compruebe si esa cookie está presente y se actualiza en consecuencia. Este y probablemente la mayoría de los otros métodos mencionados aquí, por supuesto, podrían ser evitados por el usuario, a menos que intente que la extensión cree cookies personalizadas según las marcas de tiempo, etc., y haga que su aplicación las analice del lado del servidor para ver si realmente es un usuario con el extensión o alguien que pretende tenerlo modificando sus cookies.

Niklas
fuente
5
el único problema es que si un usuario elimina sus extensiones. La cookie probablemente permanecerá configurada.
Chase Roberts
3

Hay otro método que se muestra en esta publicación de Grupos de Google . En resumen, puede intentar detectar si el icono de la extensión se carga correctamente. Esto puede resultar útil si la extensión que está buscando no es la suya.

Galán
fuente
1
Okay. ¿Cómo comprobamos si existe un icono de extensión?
Michael Rogers
3

La página web interactúa con la extensión a través de un script en segundo plano.

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
fuente
No funciona en Firefox WebExtensions, donde no se admite externally_connectable.
mlissner
3

Su extensión podría interactuar con el sitio web (por ejemplo, cambiando variables) y su sitio web podría detectar esto.

Pero debería haber una mejor manera de hacer esto. Me pregunto cómo lo está haciendo Google en su galería de extensiones (las aplicaciones ya instaladas están marcadas).

Editar:

La galería usa la función chrome.management.get . Ejemplo:

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

Pero solo puede acceder al método desde páginas con los permisos adecuados.

Fox32
fuente
1
ustedes que requerirían que la extensión interactúe con cada sitio en cada pestaña, lo que sería lento / difícil de implementar y con errores: - /
El problema es que la comunicación en la otra dirección (página a extensión) no es posible, debido al modelo de seguridad de Chrome. Si no desea seguir el camino de la 'interacción', elija el camino de las cookies.
Fox32
2
Estimado @ Fox32, chrome.management.get ..., devuelve este error:Uncaught TypeError: Cannot read property 'get' of undefined
Hosein Aqajani
3

Muchas de las respuestas aquí hasta ahora son solo para Chrome o incurren en una penalización por sobrecarga HTTP. La solución que estamos usando es un poco diferente:

1. Agregue un nuevo objeto a la lista content_scripts del manifiesto de la siguiente manera:

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

Esto permitirá que el código en install_notifier.js se ejecute en ese sitio (si aún no tenía permisos allí).

2. Envíe un mensaje a todos los sitios en la clave de manifiesto anterior.

Agregue algo como esto a install_notifier.js (tenga en cuenta que esto está usando un cierre para evitar que las variables sean globales, pero eso no es estrictamente necesario):

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

Su mensaje podría decir cualquier cosa, pero es útil enviar la versión para que sepa a qué se enfrenta. Luego...

3. En su sitio web, escuche ese mensaje.

Agregue esto a su sitio web en algún 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");
  }
});

Esto funciona en Firefox y Chrome, y no genera una sobrecarga HTTP ni manipula la página.

mlissner
fuente
0

Si tienes control sobre la extensión de Chrome, puedes probar lo que hice:

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

Y entonces:

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

}

Se siente un poco hack, pero no pude hacer que los otros métodos funcionen, y me preocupa que Chrome cambie su API aquí. Es dudoso que este método deje de funcionar pronto.

gwg
fuente
como se mencionó anteriormente, había probado esto pero el orden de las operaciones parece extraño, ¿qué script se ejecuta primero, etc.?
Brady Moritz
0

También puede usar un método de navegador cruzado que yo he usado. Utiliza el concepto de agregar un div.

en su secuencia de comandos de contenido (siempre que se cargue la secuencia de comandos, debería hacer esto)

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

en su sitio web afirma algo como,

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

Y lanza el mensaje apropiado.

Prakash Palnati
fuente
Lo había probado, pero el orden de las operaciones parece extraño: ¿qué script se ejecuta primero, etc.?
Brady Moritz
@BradyMoritz el mismo orden que en la respuesta. Agregue la clase primero y luego confirme.
Prakash Palnati
¿Sin embargo, parecía que mi script de contenido no se estaba ejecutando necesariamente antes que mi script en la página?
Brady Moritz
Eso, creo que es fácil de arreglar. Puede asegurarse de que su secuencia de comandos de contenido se ejecute justo después de presionar la URL / ruta requerida usando expresiones regulares. ¿Lo ha intentado?
Prakash Palnati
0

Si está intentando detectar cualquier extensión de cualquier sitio web, esta publicación ayudó: https://ide.hey.network/post/5c3b6c7aa7af38479accc0c7

Básicamente, la solución sería simplemente intentar obtener un archivo específico (manifest.json o una imagen) de la extensión especificando su ruta. Esto es lo que usé. 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)
}
coger.
fuente
0

Aquí hay otro enfoque moderno:

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
fuente