Add-ons für Chrome, Firefox und Opera selbst hosten
Ich habe eine Browser-Extension für Chrome, Firefox und Opera erstellt. Alle Dateien liegen bei mir lokal, ich habe also z.B. nicht Firefoxs Add-on Builder verwendet, sondern nur das SDK. Als die Extension fertig war, wollte ich sie auch selbst hosten. Ich meine, wie schwer kann das schon sein? Ja, nun …
In diesem Artikel gehen wir davon aus, dass sich die Browser-spezifischen Extension-Dateien in den Unterverzeichnissen Chrome/
, Firefox/
und Opera/
befinden.
Der Artikel ist lang geworden. Hier sind ein paar Sprungmarken:
Opera
In Operas config.xml
muss ein Verweis auf die XML-Datei auf dem hostenden Server stehen, die später nach Updates gefragt wird.
<widget …> … <update-description href="http://example.org/updates-opera.xml" /> </widget>
Dann packen wir die Extension ein. Dafür wird einfach alles in ein ZIP-Archiv mit der Datei-Endung OEX gepackt.
cd Opera/ zip -r MyExtension.oex *
Die XML-Datei für Updates updates-opera.xml
beinhaltet lediglich einen Pfad zur Extension, sowie die aktuelle Versionsnummer dieser.
<update-info xmlns="http://www.w3.org/ns/widgets" src="http://example.org/MyExtension.oex" version="1.1"></update-info>
Das war es auch schon. Ein Hindernis ist jedoch, dass die eigene Domain erst von den Benutzern als „Trusted Website“ in Opera eingetragen werden muss – wie auf diesem Screenshot zu sehen.
Chrome
In manifest.json
wird für Updates ein zusätzlicher Eintrag benötigt.
{ … "update_url": "http://example.org/updates-chrome.xml" }
Wird die Extension zum ersten Mal verpackt, lautet der Befehl:
google-chrome --pack-extension=Chrome/
Dabei wird ein Private Key erzeugt und hier mit dem Namen Chrome.pem
abgelegt. Diese Datei sollte geheim bleiben – also nicht einfach in ein öffentliches Repository hochladen!
Bei jedem späteren Mal, dass die Extension verpackt wird, lautet der Befehl dann:
google-chrome --pack-extension=Chrome/ --pack-extension-key=Chrome.pem
In der XML-Datei updates-chrome.xml
steht die ID der Extension, der Pfad zur Installations-Datei und die aktuelle Versionsnummer. Die ID erfährt man von der Chrome-Erweiterungs-Seite (chrome://extensions
) – dafür muss man ein Häkchen bei „Developer Mode“ setzen. Diese ID ändert sich nicht mehr, auch nicht mit späteren Updates.
<?xml version="1.0" encoding="UTF-8"?> <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0"> <app appid="replace-this-with-your-extension-id"> <updatecheck codebase="http://example.org/MyExtension.crx" version="1.1" /> </app> </gupdate>
Ein Problem bei der Installation ist, dass dies nicht direkt über die Website erfolgen kann. Die Extension muss vom Benutzer heruntergeladen und dann auf die Chrome-Erweiterungs-Seite (chrome://extensions
) gezogen werden (Drag&Drop). Updates funktionieren dann jedoch ganz normal.
Firefox
Ich sage es vorne weg: Firefox hat mich ordentlich angepisst. Mir ist klar, dass alles im Sinne von Sicherheit geschieht und durchaus sinnvoll ist. Aber aus Entwicklersicht war es eine Qual überhaupt erst einmal herauszufinden, wie vorzugehen ist. Erschwerend kam hinzu, dass ich kein SSL verwende. Dafür ist die Firefox-Extension die einzige, die für eine Installation keine größere Mitarbeit seitens des Benutzers erfordert.
Zunächst wird die Extension mit dem cfx tool aus dem SDK verpackt.
CFX=~/.firefox-addon-sdk-1.13.2/bin/cfx cd Firefox/ $CFX xpi --update-url http://example.org/updates-firefox.rdf
Nun müssen wir allerdings das für das XPI erstellte install.rdf
bearbeiten. Also extrahieren wir diesen Teil.
unzip MyExtension.xpi install.rdf
Nun benötigen wir ein weiteres Tool, genannt McCoy. Dafür gibt es eine grafische Variante von Mozilla selbst, oder eine gepatchte Version, die man über das Terminal verwenden kann, was ich hier mache.
Zuerst erstellen wir für unsere Extension einen Schlüssel. Ähnlich wie bei Chrome, wird dies nur einmalig gemacht und dann immer wieder verwendet.
MCCOY=~/.mccoy/mccoy $MCCOY -createKey "ThinkOfSomeKey"
Als nächstes fügen wir unseren Public Key in das install.rdf
ein und packen es wieder in das XPI.
$MCCOY -installRDF install.rdf -key "ThinkOfSomeKey" zip -f MyExtension.xpi install.rdf
Nun bilden wir einen Hash vom XPI, da wir diesen noch brauchen werden. Welches Hashing-Verfahren eingesetzt wird, ist relativ egal. Ich habe mich für sha256 entschieden.
XPI_HASH=$(sha256sum MyExtension.xpi | sed "s/ .*//g" -)
Gratuliere, wir sind ungefähr bei der Hälfte! Schauen wir uns jetzt etwas Leichtes an: Den Button auf der Website, um den Installations-Prozess einzuleiten. Dafür haben wir eine HTML-Seite mit einem Element, das die ID trigger_ff
hat. Stehe das Nachfolgende nun in einer JS-Datei namens foo.js
:
var trigger = document.getElementById( "trigger_ff" ); trigger.addEventListener( "click", function( e ) { e.preventDefault(); var params = { "MyExtension": { URL: "http://example.org/MyExtension.xpi", IconURL: "http://example.org/MyIcon_32x32.png", Hash: "sha256:%XPI_HASH%", toString: function() { return this.URL; } } }; InstallTrigger.install( params ); }, false );
Den Hash können wir beispielsweise so einfügen:
sed -i "s;%XPI_HASH%;sha256:$XPI_HASH;g" foo.js
Als nächstes widmen wir uns der RDF-Datei für Updates. Erstelle eine Datei updates-firefox.rdf
.
<?xml version="1.0" encoding="utf-8"?> <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"> <Description about="urn:mozilla:extension:MyExtension@example.org"> <em:updates> <Seq> <li> <Description> <em:version>1.1</em:version> <em:targetApplication> <Description> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <em:minVersion>18.0</em:minVersion> <em:maxVersion>19.*</em:maxVersion> <em:updateLink>http://example.org/MyExtension.xpi</em:updateLink> <em:updateHash>%XPI_HASH%</em:updateHash> </Description> </em:targetApplication> </Description> </li> </Seq> </em:updates> </Description> </RDF>
Die ID {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
wird nicht verändert, denn hierbei handelt es sich um die ID für Firefox – es wird gesagt, dass dies eine Extension für Firefox ist und z.B. nicht für Thunderbird.
Klickt der Benutzer den Installations-Button, erfährt Firefox den Hash und überprüft, ob die zu installierende Datei auch die ist, die hier angepriesen wurde. Dieser Vergleich findet auch bei Updates statt. Daher müssen wir auch hier den Platzhalter %XPI_HASH%
ersetzen.
sed -i "s;%XPI_HASH%;sha256:$XPI_HASH;g" updates-firefox.rdf
Hinweis: Der Hash muss für jede neue Version erneut errechnet und eingefügt werden!
Nun kommt wieder McCoy zum Einsatz, indem wir in das Update-RDF unsere Signatur einfügen.
$MCCOY -signRDF updates-firefox.rdf -key "ThinkOfSomeKey"
Wenn man möchte, kann man das erzeugte RDF auch noch überprüfen lassen.
$MCCOY -verifyRDF updates-firefox.rdf -key "ThinkOfSomeKey"
Geschafft.
Relevantes
Wer mein Build-Skript und Templates für die XML/RDF/JS-Dateien sehen möchte, findet diese in meinem GitHub-Repository.
Diese Tutorials und Dokumentation könnten von Interesse sein: