Vérifiez si l'utilisateur a installé une extension Chrome

97

Je suis en train de créer une extension Chrome, et pour que tout fonctionne comme je le souhaite, j'ai besoin d'un script JavaScript externe pour pouvoir détecter si un utilisateur a installé mon extension.

Par exemple: un utilisateur installe mon plugin, puis accède à un site Web avec mon script dessus. Le site Web détecte que mon extension est installée et met à jour la page en conséquence.

Est-ce possible?

Yehuda Katz
la source
2
Oui, il est possible de détecter des extensions, tant que vous connaissez votre identifiant d'extension (ce que je suis sûr que vous faites). Consultez ce site pour plus d'informations: blog.kotowicz.net/2012/02/intro-to-chrome-addons-hacking.html Passez à la section «Trouver vos addons un par un». Bonne chance!
Martin Hughes
La bonne façon de mettre en œuvre ceci est décrite par BJury ci-dessous.
Rahatur

Réponses:

46

Je suis sûr qu'il existe un moyen direct (appeler des fonctions sur votre extension directement, ou en utilisant les classes JS pour les extensions), mais une méthode indirecte (jusqu'à ce que quelque chose de mieux arrive):

Demandez à votre extension Chrome de rechercher un DIV ou un autre élément spécifique sur votre page, avec un identifiant très spécifique.

Par exemple:

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

Faites a getElementByIdet définissez le innerHTMLsur le numéro de version de votre extension ou quelque chose. Vous pouvez ensuite lire le contenu de ce côté client.

Encore une fois, vous devez utiliser une méthode directe s'il y en a une disponible.


EDIT: Méthode directe trouvée !!

Utilisez les méthodes de connexion disponibles ici: https://developer.chrome.com/extensions/extension#global-events

Non testé, mais vous devriez pouvoir le faire ...

var myPort=chrome.extension.connect('yourextensionid_qwerqweroijwefoijwef', some_object_to_send_on_connect);
Brad
la source
2
hmmm chrome.extension.connect ne semble fonctionner que lorsqu'il est exécuté à partir de l'extension (ou de toute extension). J'en ai besoin pour fonctionner à partir de n'importe quel script js aléatoire. Des idées?
Étrange, la documentation dit que cela devrait fonctionner. « Contrairement à l'autre chrome. * API, pièces de chrome.extension peuvent être utilisés par les scripts de contenu » et qu'elle énumère sendRequest(), onRequest, connect(), onRequestet getURL().
Brad
@James exécutez-vous le script qui utilise .connect () à partir d'un script hébergé? Je sais que Chrome s'efforce de ne pas faire de choses avec uniquement des fichiers locaux qui ne sont pas hébergés à des fins de sécurité. - Je vérifie.
JamesEggers
@James le script à partir duquel j'exécute .connect () est sur le même serveur si c'est ce que vous voulez dire?
23
La dernière méthode n'est plus valide , car la connectfonction a été déplacée vers l' chrome.runtimeespace de noms. Voir la réponse (et les commentaires) de BJury pour une version plus à jour
Xan
117

Chrome a désormais la possibilité d'envoyer des messages du site Web à l'extension.

Donc, dans l'extension background.js (content.js ne fonctionnera pas), ajoutez quelque chose comme:

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

Cela vous permettra ensuite de passer un appel depuis le site 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;
        }
    });

Vous pouvez ensuite vérifier la variable hasExtension. Le seul inconvénient est que l'appel est asynchrone, vous devez donc contourner ce problème d'une manière ou d'une autre.

Edit: Comme mentionné ci-dessous, vous devrez ajouter une entrée au manifest.json répertoriant les domaines qui peuvent envoyer un message à votre addon. Par exemple:

"externally_connectable": {
    "matches": ["*://localhost/*", "*://your.domain.com/*"]
},
BJury
la source
2
Cela fonctionne comme un charme. Un autre inconvénient est bien sûr que vous devez contrôler l'extension - vous ne pouvez pas l'utiliser pour voir si une extension tierce arbitraire est installée.
Eric P
2
@EricP La question originale indiquait qu'ils écrivaient l'extension, donc le problème est sans objet.
BJury
13
Vous devrez également ajouter ce qui suit à votre manifeste.json: "externally_connectable": {"matches": [" : // .yourdomain.com / *"]}
noname
3
Il devrait s'agir de {version: '1.0'} et non de {version: 1.0}, sinon vous obtiendrez 'Uncaught SyntaxError: Uncaught number' dans l'extension Inspect view console.
ET-CS
1
Du côté «vérification» (la page Web essayant de vérifier la disponibilité d'une extension donnée), chrome.runtime est indéfini, chrome 36 sous Linux.
reallynice le
22

Une autre méthode consiste à exposer une ressource accessible sur le Web , bien que cela permettra à n'importe quel site Web de tester si votre extension est installée.

Supposons que l'ID de votre extension soit aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, et que vous ajoutez un fichier (par exemple, une image pixel transparente) comme test.pngdans les fichiers de votre extension.

Ensuite, vous exposez ce fichier aux pages Web avec la web_accessible_resourcesclé manifeste:

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

Dans votre page Web, vous pouvez essayer de charger ce fichier par son URL complète (dans une <img>balise, via XHR, ou de toute autre manière):

chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/test.png

Si le fichier se charge, l'extension est installée. S'il y a une erreur lors du chargement de ce fichier, l'extension n'est pas installée.

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

À noter: s'il y a une erreur lors du chargement de ce fichier, cette erreur de pile réseau apparaîtra dans la console sans possibilité de la désactiver. Lorsque Chromecast a utilisé cette méthode, cela a suscité beaucoup de controverse à cause de cela; avec l'éventuelle solution très moche de simplement mettre sur liste noire des erreurs très spécifiques des outils de développement par l'équipe Chrome.


Remarque importante: cette méthode ne fonctionnera pas dans Firefox WebExtensions. Les ressources accessibles sur le Web exposent intrinsèquement l'extension à la prise d'empreintes digitales, car l'URL est prévisible en connaissant l'ID. Firefox a décidé de fermer ce trou en attribuant une URL aléatoire spécifique à l'instance aux ressources accessibles sur le Web:

Les fichiers seront alors disponibles en utilisant une URL comme:

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

Cet UUID est généré aléatoirement pour chaque instance de navigateur et n'est pas l'ID de votre extension. Cela empêche les sites Web de prendre des empreintes digitales sur les extensions qu'un utilisateur a installées.

Cependant, bien que l'extension puisse utiliser runtime.getURL()pour obtenir cette adresse, vous ne pouvez pas la coder en dur sur votre site Web.

Xan
la source
Bien que cette réponse obtienne le "jus" de stackoverflow.com/a/9216924/1504300 , IMHO ajoute des informations assez importantes telles que l'exposition de la ressource dans l'extension et le fait que vous pouvez utiliser une requête ajax pour vérifier l'existence (objet Image semble disponible uniquement sur HTML5 si je ne me trompe pas goo.gl/HBeI1i ). Avec les informations contenues dans cette réponse, j'ai pu résoudre le problème, je l'ai trouvé comme une solution "
prête à l'emploi
@niconic Cette réponse (étant de toute façon mauvaise en tant que lien uniquement) se réfère à la situation avant l'entrée en vigueur de la version 2 du manifeste. Auparavant, il n'était pas nécessaire de déclarer des ressources accessibles sur le Web.
Xan
19

J'ai pensé partager mes recherches à ce sujet. J'avais besoin de pouvoir détecter si une extension spécifique était installée pour que certains liens file: /// fonctionnent. Je suis tombé sur cet article ici Cela expliquait une méthode pour obtenir le manifest.json d'une extension.

J'ai ajusté un peu le code et j'ai trouvé:

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

Avec cela, vous devriez être en mesure d'utiliser Ext_Detect (ExtensionName, ExtensionID) pour détecter l'installation de n'importe quel nombre d'extensions.

Nato
la source
2
On dirait que Google a rendu les choses plus sécurisées, j'obtiens l'erreur suivante lorsque j'exécute Ext_Detect (): Refuser le chargement de l'extension chrome: // [my_extension_id] /manifest.json. Les ressources doivent être répertoriées dans la clé de manifeste web_accessible_resources pour être chargées par des pages en dehors de l'extension.
Lounge
Je suis en mesure de faire fonctionner cela avec la version 32.0.1700.107 m de Chrome à partir du 27/02/2014
JE Carter II
1
comme l'a dit @ Lounge9. Les ressources à l'intérieur des packages utilisant manifest_version 2 (ou version ultérieure) sont bloquées par défaut et doivent être ajoutées à la liste blanche pour être utilisées via cette propriété en ajoutant à manifest.json: "web_accessible_resources": ["manifest..json"],
ET-CS
En utilisant @BJury answer, vous pouvez également passer facilement des données de l'extension au script (par exemple la version de l'extension) et vous n'avez pas besoin d'exposer un fichier de l'extension.
ET-CS
1
Cela a fonctionné le mieux pour moi car notre extension sera utilisée sur plusieurs domaines et ne pourrait pas être prédéfinie car de nouveaux domaines sont ajoutés régulièrement. Au lieu d'accéder à manifest.json, j'ai créé un nouveau fichier version.json et mis le numéro de version à l'intérieur. Cela fonctionne exactement de la même manière.
Paul Haggo
7

Une autre solution possible si vous possédez le site Web consiste à utiliser l' installation en ligne .

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

Je sais que c'est une vieille question, mais cette façon a été introduite dans Chrome 15 et j'ai donc pensé que je la listerais pour tous ceux qui recherchent une réponse.

PAEz
la source
12
Cela fonctionne très bien pour une application Chrome , mais pas pour une extension Chrome AFAIK
Eran Medan
Oui, ce site Web vous indique comment installer une extension en ligne, mais recommande apparemment "Les extensions peuvent communiquer avec la page d'intégration via des scripts de contenu pour lui faire savoir qu'elles sont déjà installées." au lieu de pouvoir utiliser chrome.app.isInstalled. Confused me too ...
rogerdpack
4

J'ai utilisé la méthode des cookies:

Dans mon fichier manifest.js, j'ai inclus un script de contenu qui ne s'exécute que sur mon site:

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

dans mon js / mysite.js, j'ai une ligne:

document.cookie = "extension_downloaded=True";

et dans ma page index.html, je recherche ce cookie.

if (document.cookie.indexOf('extension_downloaded') != -1){
    document.getElementById('install-btn').style.display = 'none';
}
Chase Roberts
la source
J'ai essayé toutes les solutions ci-dessus mais ne fonctionne pas, alors je vois votre réponse, c'est ce que je recherche!
John Doe
Cela ajoute désormais une surcharge à chaque requête HTTP.
mlissner
3

Vous pouvez demander à l'extension de définir un cookie et demander au JavaScript de votre site Web de vérifier si ce cookie est présent et de le mettre à jour en conséquence. Cette méthode et probablement la plupart des autres méthodes mentionnées ici pourraient bien sûr être contournées par l'utilisateur, à moins que vous n'essayiez de demander à l'extension de créer des cookies personnalisés en fonction de l'horodatage, etc., et que votre application les analyse côté serveur pour voir s'il s'agit vraiment d'un utilisateur avec le extension ou quelqu'un prétendant l'avoir en modifiant ses cookies.

Niklas
la source
5
le seul problème est que si un utilisateur supprime vos extensions. Le cookie restera probablement défini.
Chase Roberts
3

Il existe une autre méthode illustrée dans ce message sur Google Groupes . En bref, vous pouvez essayer de détecter si l'icône d'extension se charge correctement. Cela peut être utile si l'extension que vous recherchez n'est pas la vôtre.

Beau
la source
1
D'accord. Comment vérifier si une icône d'extension existe?
Michael Rogers
3

La page Web interagit avec l'extension via un script d'arrière-plan.

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
la source
Ne fonctionne pas sur Firefox WebExtensions, où externally_connectable n'est pas pris en charge.
mlissner
3

Votre extension pourrait interagir avec le site Web (par exemple, modifier des variables) et votre site Web pourrait le détecter.

Mais il devrait y avoir une meilleure façon de le faire. Je me demande comment Google le fait sur sa galerie d'extensions (les applications déjà installées sont marquées).

Éditer:

La galerie utilise la fonction chrome.management.get . Exemple:

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

Mais vous ne pouvez accéder à la méthode qu'à partir de pages avec les bonnes autorisations.

Fox32
la source
1
vous qui exigerait que l'extension interagisse avec chaque site sur chaque onglet qui serait lent / difficile à implémenter et bogué: - /
Le problème est que la communication dans l'autre sens (page à extension) n'est pas possible, en raison du modèle de sécurité de chrome. Si vous ne voulez pas opter pour le mode «interactif», prenez le chemin des cookies.
Fox32
2
Cher @ Fox32, chrome.management.get ..., renvoie cette erreur:Uncaught TypeError: Cannot read property 'get' of undefined
Hosein Aqajani
3

La plupart des réponses ici jusqu'à présent sont uniquement Chrome ou entraînent une surcharge HTTP. La solution que nous utilisons est un peu différente:

1. Ajoutez un nouvel objet à la liste manifest content_scripts comme ceci:

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

Cela permettra au code de install_notifier.js de s'exécuter sur ce site (si vous n'y disposiez pas déjà des autorisations).

2. Envoyez un message à chaque site dans la clé manifeste ci-dessus.

Ajoutez quelque chose comme ceci à install_notifier.js (notez que cela utilise une fermeture pour empêcher les variables d'être globales, mais ce n'est pas strictement nécessaire):

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

Votre message peut dire n'importe quoi, mais il est utile d'envoyer la version pour savoir à quoi vous avez affaire. Ensuite...

3. Sur votre site Web, écoutez ce message.

Ajoutez ceci à votre site Web quelque part:

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

Cela fonctionne dans Firefox et Chrome, et n'entraîne pas de surcharge HTTP ni de manipulation de la page.

mlissner
la source
0

Si vous contrôlez l'extension Chrome, vous pouvez essayer ce que j'ai fait:

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

Puis:

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

}

Cela semble un peu piraté, mais je n'ai pas pu faire fonctionner les autres méthodes et je crains que Chrome ne modifie son API ici. Il est peu probable que cette méthode cesse de fonctionner de sitôt.

gwg
la source
comme mentionné ci-dessus - j'avais testé cela mais l'ordre des opérations semble étrange - quel script est exécuté en premier, etc.?
Brady Moritz
0

Vous pouvez également utiliser une méthode multi-navigateurs que j'ai utilisée. Utilise le concept d'ajout d'un div.

dans votre script de contenu (chaque fois que le script se charge, il devrait le faire)

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

dans votre site Web, vous affirmez quelque chose comme,

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

Et lancez un message approprié.

Prakash Palnati
la source
J'avais testé cela mais l'ordre des opérations semble étrange - quel script est exécuté en premier, etc.?
Brady Moritz
@BradyMoritz dans le même ordre que dans la réponse. Ajoutez d'abord la classe, puis affirmez.
Prakash Palnati
il semblait que mon script de contenu ne fonctionnait pas nécessairement avant mon script sur la page?
Brady Moritz
Cela, je pense que c'est facile à résoudre. Vous pouvez vous assurer que votre script de contenu s'exécute juste après avoir atteint l'URL / la route requise à l'aide de regex.Avez-vous essayé?
Prakash Palnati
0

Si vous essayez de détecter une extension de n'importe quel site Web, cet article a aidé: https://ide.hey.network/post/5c3b6c7aa7af38479accc0c7

Fondamentalement, la solution serait simplement d'essayer d'obtenir un fichier spécifique (manifest.json ou une image) à partir de l'extension en spécifiant son chemin. Voici ce que j'ai utilisé. Travaille vraiment:

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.
la source
0

Voici une autre approche moderne:

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
la source