Version 1.0 alpha

- Core SendToPhone function has been removed from the chrome and made a module.
- Support for common protocols has been added (market, sms, mms, callto)
Issues
- Protocol handling does not work with Firefox 4b2+ due to a change in how Gecko handles such components.
- Frequent errors on send, (status 0) and (status 400)
This commit is contained in:
patrick.oreilly
2010-07-30 00:58:11 +00:00
parent 82f927d317
commit d7fade85b2
8 changed files with 592 additions and 201 deletions

View File

@@ -1,5 +1,7 @@
content sendtophone chrome/content/
skin sendtophone classic/1.0 chrome/skin/
resource sendtophone modules/
locale sendtophone en chrome/locale/en/
locale sendtophone de chrome/locale/de/
locale sendtophone es chrome/locale/es/

View File

@@ -8,7 +8,7 @@
<groupbox align="center" orient="horizontal">
<vbox>
<text value="Send To Phone" style="font-weight: bold; font-size: x-large;"/>
<text value="&version; 0.9.2"/>
<text value="&version; 1.0 alpha"/>
<separator class="thin"/>
<text value="&createdBy;" style="font-weight: bold;"/>
<text value="Alfonso Martínez de Lizarrondo &amp; Patrick O'Reilly"/>

View File

@@ -1,3 +1,19 @@
/*
Copyright 2010 Alfonso Martínez de Lizarrondo & Patrick O'Reilly
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
sendtophone.init = function()
{
// Try to install the toolbar button, but only once
@@ -6,6 +22,9 @@ sendtophone.init = function()
this.installToolbarButton();
this.prefs.setBoolPref( "installedButton", true ) ;
}
document.getElementById("contentAreaContextMenu")
.addEventListener("popupshowing", function (e){ sendtophone.showFirefoxContextMenu(e); }, false);
}
sendtophone.installToolbarButton = function()
@@ -35,12 +54,6 @@ sendtophone.installToolbarButton = function()
}
sendtophone.onFirefoxLoad = function() {
document.getElementById("contentAreaContextMenu")
.addEventListener("popupshowing", function (e){ sendtophone.showFirefoxContextMenu(e); }, false);
};
sendtophone.showFirefoxContextMenu = function(event) {
// show or hide the menuitem based on what the context menu is on
// see http://kb.mozillazine.org/Adding_items_to_menus
@@ -49,4 +62,3 @@ sendtophone.showFirefoxContextMenu = function(event) {
document.getElementById("context-sendtophone-text").hidden = !gContextMenu.isTextSelected;
};
window.addEventListener("load", function(e) { sendtophone.onFirefoxLoad(e); }, false);

View File

@@ -1,10 +1,24 @@
/*
Copyright 2010 Alfonso Martínez de Lizarrondo & Patrick O'Reilly
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Core functions
Components.utils.import("resource://sendtophone/sendtophone.js");
var sendtophone = {
baseUrl : '',
req : null,
apiVersion : 3,
loggedInUrl : "http://code.google.com/p/chrometophone/logo?login",
loggedOutUrl : "http://code.google.com/p/chrometophone/logo?logout",
apkUrl : "http://code.google.com/p/chrometophone/downloads/detail?name=chrometophone-android-v1.1.apk",
init: function()
{
@@ -20,12 +34,6 @@ var sendtophone = {
me.prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService)
.getBranch("extensions.sendtophone.") ;
// Allow the people to use their own server if they prefer to not trust this server
me.baseUrl = me.prefs.getCharPref( "appUrl" ) ;
me.sendUrl = me.baseUrl + '/send?ver=' + me.apiVersion;
me.logInUrl = me.baseUrl + '/signin?ver=' + me.apiVersion + '&extret=' + encodeURIComponent(me.loggedInUrl);
me.logOutUrl = me.baseUrl + '/signout?ver=' + me.apiVersion + '&extret=' + encodeURIComponent(me.loggedOutUrl);
me.init();
},
@@ -52,7 +60,7 @@ var sendtophone = {
break;
case 'page':
default:
var info = sendtophone.getInfo();
var info = this.getInfo();
title = info.title;
url = info.url;
selection = info.selection;
@@ -66,7 +74,7 @@ var sendtophone = {
if (selection.length > max_length)
selection = selection.substring(0, max_length);
this.sendToPhone(title, url, selection);
sendtophoneCore.send(title, url, selection);
/*
}
else
@@ -85,32 +93,6 @@ var sendtophone = {
text);
},
// Shows a message in a growl-like notification
popupNotification: function(text)
{
var title = this.strings.getString("SendToPhoneTitle");
var image = "chrome://sendtophone/skin/icon.png";
try {
// Avoid crash on Fedora 12.
// Reported on 8th June https://addons.mozilla.org/en-US/firefox/reviews/display/161941
var listener = {
observe: function(subject, topic, data) {}
};
Components.classes['@mozilla.org/alerts-service;1']
.getService(Components.interfaces.nsIAlertsService)
.showAlertNotification(image, title, text, false, '', listener);
} catch(e)
{
// prevents runtime error on platforms that don't implement nsIAlertsService
var win = Components.classes['@mozilla.org/embedcomp/window-watcher;1']
.getService(Components.interfaces.nsIWindowWatcher)
.openWindow(null, 'chrome://global/content/alerts/alert.xul',
'_blank', 'chrome,titlebar=no,popup=yes', null);
win.arguments = [image, title, text, false, ''];
}
},
onToolbarButtonCommand: function(e) {
// just reuse the function above.
sendtophone.onMenuItemCommand(e, 'page');
@@ -121,130 +103,27 @@ var sendtophone = {
win = doc.defaultView;
var href = doc.location.href;
// Is it the Google Maps page?
if (/https?:\/\/maps\.google\..{2,3}\//.test(href))
if (this.isMapsURL(href))
{
// Then try to send the current view:
var link = doc.getElementById('link');
if (link && link.href)
href = link.href;
}
return {
"title": doc.title,
"url": href,
"selection": win.getSelection().toString()
};
},
processXHR: function(url, method, data, callback)
isMapsURL: function(url)
{
if (!this.req)
this.req = new XMLHttpRequest();
var req = this.req;
req.open(method, url, true);
req.setRequestHeader('X-Extension', 'true'); // XSRF protector
if (method=='POST')
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.onreadystatechange = function()
{
// here this == req
if (this.readyState == 4)
{
var body = req.responseText;
if (req.status == 200)
{
// Check if the body is a html redirect
var redirectMatch = body.match(/<meta http-equiv="refresh" content="\d;\s*url=(&#39;)?(.*)\1">/);
if (redirectMatch)
{
var redirectUrl = redirectMatch[2].replace(/&amp;/g, '&');
if (redirectUrl == sendtophone.loggedOutUrl)
{
sendtophone.logoutSuccessful();
return;
}
// Do the redirect and use the original callback
sendtophone.processXHR( redirectUrl, 'GET', null, callback);
}
else
callback.call( sendtophone, req );
}
else
{
sendtophone.alert(sendtophone.strings.getString("ErrorOnSend") + ' (status ' + req.status + ')\r\n' + body);
}
}
};
// To send correctly cookies.
// Force the request to include cookies even though this chrome code
// is seen as a third-party, so the server knows the user for which we are
// requesting favorites (or anything else user-specific in the future).
// This only works in Firefox 3.6; in Firefox 3.5 the request will instead
// fail to send cookies if the user has disabled third-party cookies.
try {
req.channel.QueryInterface(Ci.nsIHttpChannelInternal).
forceAllowThirdPartyCookie = true;
}
catch(ex) { /* user is using Firefox 3.5 */ }
req.send( data );
},
sendToPhone: function(title, url, selection)
{
var data = 'title=' + encodeURIComponent(title) +
'&url=' + encodeURIComponent(url) + '&sel=' + encodeURIComponent(selection);
this.pendingMessage = data;
this.processXHR(this.sendUrl, 'POST', data, this.processSentData);
},
processSentData : function(req)
{
var body = req.responseText;
if (body.substring(0, 2) == 'OK')
{
delete this.pendingMessage;
this.popupNotification(this.strings.getString("InfoSent"));
return;
}
if (body.indexOf('LOGIN_REQUIRED') == 0)
{
var me = sendtophone;
me.popupNotification( me.strings.getString("LoginRequired") );
var lastTab = gBrowser.tabContainer.selectedIndex;
var tab = gBrowser.addTab(me.baseUrl + '/signin?ver=' + me.apiVersion + '&extret=' + encodeURIComponent(me.loggedInUrl));
//Open Google login page
gBrowser.selectedTab = tab;
var c2pTab = gBrowser.getBrowserForTab(tab);
//Add listener for callback URL
c2pTab.addEventListener("load", function () {
if(sendtophone.loggedInUrl==c2pTab.currentURI.spec){
//Resend URL from that Tab
sendtophone.loginSuccessful();
//Close Google login
gBrowser.removeCurrentTab();
//ReFocus on tab being sent
gBrowser.selectedTab = gBrowser.tabContainer.childNodes[lastTab];
}
}, true);
return;
}
if (body.indexOf('DEVICE_NOT_REGISTERED') == 0)
{
this.popupNotification(this.strings.getString("DeviceNotRegistered"));
var tab = gBrowser.addTab( this.apkUrl );
gBrowser.selectedTab = tab;
return;
}
this.alert(this.strings.getString("ErrorOnSend") + '\r\n' + body);
return url.match("http://maps\\.google\\.[a-z]{2,3}(\\.[a-z]{2})?[/?].*") || url.match("http://www\\.google\\.[a-z]{2,3}(\\.[a-z]{2})?/maps.*");
},
initPopup: function()
@@ -255,46 +134,9 @@ var sendtophone = {
logout: function()
{
var lastTab = gBrowser.tabContainer.selectedIndex;
var tab = gBrowser.addTab(this.logOutUrl);
// Open Google logout page
gBrowser.selectedTab = tab;
var c2pTab = gBrowser.getBrowserForTab(tab);
//Add listener for callback URL
c2pTab.addEventListener("load", function () {
if(sendtophone.loggedOutUrl==c2pTab.currentURI.spec){
sendtophone.logoutSuccessful();
// Close logout tab
gBrowser.removeCurrentTab();
// ReFocus on tab being sent
gBrowser.selectedTab = gBrowser.tabContainer.childNodes[lastTab];
sendtophoneCore.logout();
}
}, true);
/*
// This doesn't work if third party cookies are bloqued. Why???
this.processXHR(this.logOutUrl, 'GET', null, function(req)
{
// This will be called only if there's a problem
this.alert(this.strings.getString("LogoutError") + '\r\n' + req.responseText );
});
*/
},
logoutSuccessful: function()
{
this.popupNotification(this.strings.getString("LogoutSuccessful"));
},
loginSuccessful: function()
{
this.popupNotification( this.strings.getString("LoginSuccessful") );
// Send pending message
this.processXHR(this.sendUrl, 'POST', this.pendingMessage, this.processSentData);
}
};
window.addEventListener("load", sendtophone.onLoad, false);

View File

@@ -0,0 +1,285 @@
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the MLDonkey protocol handler 2.5.
*
* The Initial Developer of the Original Code is
* Simon Peter <dn.tlp@gmx.net>.
* Portions created by the Initial Developer are Copyright (C) 2003 - 2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Sven Koch
* Len Walter <len@unsw.edu.au>
* Dan Fritz <templar_of_ni@yahoo.se>
* David Ciecierski <dawid.ciecierski@gmail.com>
* Dennis Plöger <dennis@dieploegers.de>
* Dominik Röttsches <d-r@roettsches.de>
* Stefan Huber <stef@efan.ch>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/***** Defines *****/
// components defined in this file
const MARKETPROT_HANDLER_CONTRACTID =
"@mozilla.org/network/protocol;1?name=market";
const MARKETPROT_HANDLER_CID =
Components.ID("{76A4F09C-9B67-11DF-A9F4-6018E0D72085}");
const SMSPROT_HANDLER_CONTRACTID =
"@mozilla.org/network/protocol;1?name=sms";
const SMSPROT_HANDLER_CID =
Components.ID("{85F1603A-9B67-11DF-96B2-6118E0D72085}");
const SMSTOPROT_HANDLER_CONTRACTID =
"@mozilla.org/network/protocol;1?name=smsto";
const SMSTOPROT_HANDLER_CID =
Components.ID("{8A9EB484-9B67-11DF-9BAE-8818E0D72085}");
const MMSPROT_HANDLER_CONTRACTID =
"@mozilla.org/network/protocol;1?name=mms";
const MMSPROT_HANDLER_CID =
Components.ID("{A47959A0-9B70-11DF-92BC-6721E0D72085}");
const MMSTOPROT_HANDLER_CONTRACTID =
"@mozilla.org/network/protocol;1?name=mmsto";
const MMSTOPROT_HANDLER_CID =
Components.ID("{A9CE57D4-9B70-11DF-8AF1-6821E0D72085}");
const CALLTOPROT_HANDLER_CONTRACTID =
"@mozilla.org/network/protocol;1?name=callto";
const CALLTOPROT_HANDLER_CID =
Components.ID("{E8763476-9B6A-11DF-86FD-B81BE0D72085}");
// components used in this file
const NS_IOSERVICE_CID = "{7fe2aeb0-95d1-11df-981c-0800200c9a66}";
const NS_PREFSERVICE_CONTRACTID = "@mozilla.org/preferences-service;1";
const URI_CONTRACTID = "@mozilla.org/network/simple-uri;1";
const NS_WINDOWWATCHER_CONTRACTID = "@mozilla.org/embedcomp/window-watcher;1";
const INPUTSTREAMCHANNEL_CONTRACTID = "@mozilla.org/network/input-stream-channel;1";
const CATEGORY_MANAGER_CONTRACTID = "@mozilla.org/categorymanager;1";
const HTTP_HANDLER_CONTRACTID = "@mozilla.org/network/protocol;1?name=http";
// interfaces used in this file
const nsIProtocolHandler = Components.interfaces.nsIProtocolHandler;
const nsIURI = Components.interfaces.nsIURI;
const nsIPrefService = Components.interfaces.nsIPrefService;
const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher;
const nsIChannel = Components.interfaces.nsIChannel;
const nsIContentPolicy = Components.interfaces.nsIContentPolicy;
// some misc. constants
const PREF_BRANCH = "extensions.sendtophone.";
/***** PhoneByProtocolHandler *****/
function PhoneByProtocolHandler(scheme)
{
this.scheme = scheme;
}
// attribute defaults
PhoneByProtocolHandler.prototype.defaultPort = -1;
PhoneByProtocolHandler.prototype.protocolFlags = nsIProtocolHandler.URI_NORELATIVE;
PhoneByProtocolHandler.prototype.withinLoad = false;
PhoneByProtocolHandler.prototype.newURI = function(aSpec, aCharset, aBaseURI)
{
var uri = Components.classes[URI_CONTRACTID].createInstance(nsIURI);
uri.spec = aSpec;
return uri;
}
PhoneByProtocolHandler.prototype.loadPhoneToProtocol = function(aURI)
{
var s2pTitle;
var s2pURL = encodeURIComponent(decodeURI(aURI.spec));
// read preferences again (otherwise saved settings won't be used until after restart
ProtocolToPhoneModule.readPreferences(PREF_BRANCH);
// Core functions
if (typeof sendtophoneCore == "undefined")
Components.utils.import("resource://sendtophone/sendtophone.js");
if (!s2pURL.indexOf('market')){
s2pTitle = "Market Link";
}
else if (!s2pURL.indexOf('sms')){
s2pTitle = "Send SMS";
}
else if (!s2pURL.indexOf('mms')){
s2pTitle = "Send MMS";
}
else if (!s2pURL.indexOf('call')){
s2pTitle = "Call Number";
}
else{
s2pTitle = "Other";
}
sendtophoneCore.send(s2pTitle, cfgUrl + s2pURL, "")
return null;
}
PhoneByProtocolHandler.prototype.newChannel = function(aURI)
{
// pass URI to PhoneToProtocol
var chan = this.loadPhoneToProtocol(aURI);
// return a fake empty channel so current window doesn't change
if(chan == null) chan = Components.classes[INPUTSTREAMCHANNEL_CONTRACTID].createInstance(nsIChannel);
return chan;
}
/***** PhoneByProtocolHandlerFactory *****/
function PhoneByProtocolHandlerFactory(scheme)
{
this.scheme = scheme;
}
PhoneByProtocolHandlerFactory.prototype.createInstance = function(outer, iid)
{
if(outer != null) throw Components.results.NS_ERROR_NO_AGGREGATION;
if(!iid.equals(nsIProtocolHandler) && !iid.equals(nsIContentPolicy))
throw Components.results.NS_ERROR_INVALID_ARG;
return new PhoneByProtocolHandler(this.scheme);
}
var factory_market = new PhoneByProtocolHandlerFactory("market");
var factory_sms = new PhoneByProtocolHandlerFactory("sms");
var factory_smsto = new PhoneByProtocolHandlerFactory("smsto");
var factory_mms = new PhoneByProtocolHandlerFactory("mms");
var factory_mmsto = new PhoneByProtocolHandlerFactory("mmsto");
var factory_callto = new PhoneByProtocolHandlerFactory("callto");
/***** ProtocolToPhoneModule *****/
var ProtocolToPhoneModule = new Object();
ProtocolToPhoneModule.readPreferences = function(pref_branch)
{
// get preferences branch
var PrefService = Components.classes[NS_PREFSERVICE_CONTRACTID].getService(nsIPrefService);
var myPrefs = PrefService.getBranch(null); // Mozilla bug #107617
// read preferences (if available)
if(myPrefs.getPrefType(pref_branch + "url") == myPrefs.PREF_STRING)
cfgUrl = myPrefs.getCharPref(pref_branch + "url");
if(myPrefs.getPrefType(pref_branch + "protocols") == myPrefs.PREF_STRING)
cfgProtocols = myPrefs.getCharPref(pref_branch + "protocols");
}
ProtocolToPhoneModule.registerSelf = function(compMgr, fileSpec, location, type)
{
compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
// register market protocol handler
compMgr.registerFactoryLocation(MARKETPROT_HANDLER_CID,
"market protocol handler",
MARKETPROT_HANDLER_CONTRACTID,
fileSpec, location, type);
// register sms protocol handler
compMgr.registerFactoryLocation(SMSPROT_HANDLER_CID,
"sms protocol handler",
SMSPROT_HANDLER_CONTRACTID,
fileSpec, location, type);
// register smsto protocol handler
compMgr.registerFactoryLocation(SMSTOPROT_HANDLER_CID,
"smsto protocol handler",
SMSTOPROT_HANDLER_CONTRACTID,
fileSpec, location, type);
// register mms protocol handler
compMgr.registerFactoryLocation(MMSPROT_HANDLER_CID,
"mms protocol handler",
MMSPROT_HANDLER_CONTRACTID,
fileSpec, location, type);
// register mmsto protocol handler
compMgr.registerFactoryLocation(MMSTOPROT_HANDLER_CID,
"mmsto protocol handler",
MMSTOPROT_HANDLER_CONTRACTID,
fileSpec, location, type);
// register callto protocol handler
compMgr.registerFactoryLocation(CALLTOPROT_HANDLER_CID,
"calltoo protocol handler",
CALLTOPROT_HANDLER_CONTRACTID,
fileSpec, location, type);
}
ProtocolToPhoneModule.unregisterSelf = function(compMgr, fileSpec, location)
{
compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
// unregister our components
compMgr.unregisterFactoryLocation(MARKETPROT_HANDLER_CID, fileSpec);
compMgr.unregisterFactoryLocation(SMSPROT_HANDLER_CID, fileSpec);
compMgr.unregisterFactoryLocation(SMSTOPROT_HANDLER_CID, fileSpec);
compMgr.unregisterFactoryLocation(MMSPROT_HANDLER_CID, fileSpec);
compMgr.unregisterFactoryLocation(MMSTOPROT_HANDLER_CID, fileSpec);
compMgr.unregisterFactoryLocation(CALLTOPROT_HANDLER_CID, fileSpec);
}
ProtocolToPhoneModule.getClassObject = function(compMgr, cid, iid)
{
if(!iid.equals(Components.interfaces.nsIFactory))
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
// read preferences
this.readPreferences(PREF_BRANCH);
// return protocol handler factories
// To disable a protocol simply comment the line out.
if(cid.equals(MARKETPROT_HANDLER_CID)) { if(cfgProtocols.search("market") != -1) return factory_market; else return null; }
if(cid.equals(SMSPROT_HANDLER_CID)) { if(cfgProtocols.search("sms") != -1) return factory_sms; else return null; }
if(cid.equals(SMSTOPROT_HANDLER_CID)) { if (cfgProtocols.search("smsto") != -1) return factory_smsto; else return null; }
if(cid.equals(MMSPROT_HANDLER_CID)) { if(cfgProtocols.search("mms") != -1) return factory_mms; else return null; }
if(cid.equals(MMSTOPROT_HANDLER_CID)) { if (cfgProtocols.search("mmsto") != -1) return factory_mmsto; else return null; }
if(cid.equals(CALLTOPROT_HANDLER_CID)) { if (cfgProtocols.search("callto") != -1) return factory_callto; else return null; }
throw Components.results.NS_ERROR_NO_INTERFACE;
}
ProtocolToPhoneModule.canUnload = function(compMgr)
{
return true; // our objects can be unloaded
}
/***** Entrypoint *****/
function NSGetModule(compMgr, fileSpec)
{
return ProtocolToPhoneModule;
}

View File

@@ -1,5 +1,7 @@
pref("extensions.sendtophone.installedButton", false);
pref("extensions.sendtophone.appUrl", "https://chrometophone.appspot.com");
pref("extensions.sendtophone.url", "http://smallroomstudios.net/s2p.php?ml=");
pref("extensions.sendtophone.protocols", "market smsto sms mms mmsto callto");
// https://developer.mozilla.org/en/Localizing_extension_descriptions
pref("extensions.sendtophone@martinezdelizarrondo.com.description", "chrome://sendtophone/locale/overlay.properties");

View File

@@ -4,7 +4,7 @@
<em:id>sendtophone@martinezdelizarrondo.com</em:id>
<em:type>2</em:type>
<em:name>Send To Phone</em:name>
<em:version>0.9.2</em:version>
<em:version>1.0 alpha</em:version>
<em:creator>Alfonso Martínez de Lizarrondo</em:creator>
<em:contributor>Patrick O'Reilly</em:contributor>
<em:description>Send links to your Android 2.2 phone using the new C2DM service. Based on the ChromeToPhone extension.</em:description>
@@ -14,7 +14,7 @@
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- Firefox -->
<em:minVersion>3.0</em:minVersion>
<em:maxVersion>4.0b2pre</em:maxVersion>
<em:maxVersion>3.8.6</em:maxVersion>
</Description>
</em:targetApplication>
</Description>

View File

@@ -0,0 +1,248 @@
/*
Copyright 2010 Alfonso Martínez de Lizarrondo & Patrick O'Reilly
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// https://developer.mozilla.org/en/JavaScript_code_modules/Using_JavaScript_code_modules
var EXPORTED_SYMBOLS = ["sendtophoneCore"];
const Cc = Components.classes;
const Ci = Components.interfaces;
var sendtophoneCore = {
req : null,
apiVersion : 4,
loggedInUrl : "http://code.google.com/p/chrometophone/logo?login",
loggedOutUrl : "http://code.google.com/p/chrometophone/logo?logout",
apkUrl : "http://code.google.com/p/chrometophone/downloads/detail?name=chrometophone-android-v1.2.apk",
init: function()
{
this.strings = Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Ci.nsIStringBundleService)
.createBundle("chrome://sendtophone/locale/overlay.properties");
var prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService)
.getBranch("extensions.sendtophone.") ;
// Allow the people to use their own server if they prefer to not trust this server
var baseUrl = prefs.getCharPref( "appUrl" ) ;
this.sendUrl = baseUrl + '/send?ver=' + this.apiVersion;
this.logInUrl = baseUrl + '/signin?ver=' + this.apiVersion + '&extret=' + encodeURIComponent(this.loggedInUrl);
this.logOutUrl = baseUrl + '/signout?ver=' + this.apiVersion + '&extret=' + encodeURIComponent(this.loggedOutUrl);
},
getString: function(name)
{
return this.strings.GetStringFromName(name);
},
// Shows a message in a modal alert
alert: function(text)
{
var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Ci.nsIPromptService);
promptService.alert(null, this.getString("SendToPhoneTitle"),
text);
},
// Shows a message in a growl-like notification
popupNotification: function(text)
{
var title = this.getString("SendToPhoneTitle");
var image = "chrome://sendtophone/skin/icon.png";
try {
// Avoid crash on Fedora 12.
// Reported on 8th June https://addons.mozilla.org/en-US/firefox/reviews/display/161941
var listener = {
observe: function(subject, topic, data) {}
};
Cc['@mozilla.org/alerts-service;1']
.getService(Ci.nsIAlertsService)
.showAlertNotification(image, title, text, false, '', listener);
} catch(e)
{
// prevents runtime error on platforms that don't implement nsIAlertsService
var win = Cc['@mozilla.org/embedcomp/window-watcher;1']
.getService(Ci.nsIWindowWatcher)
.openWindow(null, 'chrome://global/content/alerts/alert.xul',
'_blank', 'chrome,titlebar=no,popup=yes', null);
win.arguments = [image, title, text, false, ''];
}
},
processXHR: function(url, method, data, callback)
{
if (!this.req)
this.req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
var req = this.req;
req.open(method, url, true);
req.setRequestHeader('X-Same-Domain', 'true'); // XSRF protector
if (method=='POST')
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.onreadystatechange = function()
{
// here this == req
if (this.readyState == 4)
{
var body = req.responseText;
if (req.status == 200)
{
// Check if the body is a html redirect
var redirectMatch = body.match(/<meta http-equiv="refresh" content="\d;\s*url=(&#39;)?(.*)\1">/);
if (redirectMatch)
{
var redirectUrl = redirectMatch[2].replace(/&amp;/g, '&');
if (redirectUrl == sendtophoneCore.loggedOutUrl)
{
sendtophoneCore.logoutSuccessful();
return;
}
// Do the redirect and use the original callback
sendtophoneCore.processXHR( redirectUrl, 'GET', null, callback);
}
else
callback.call( sendtophoneCore, req );
}
else
{
sendtophoneCore.alert(sendtophoneCore.getString("ErrorOnSend") + ' (status ' + req.status + ')\r\n' + body);
}
}
};
// To send correctly cookies.
// Force the request to include cookies even though this chrome code
// is seen as a third-party, so the server knows the user for which we are
// requesting favorites (or anything else user-specific in the future).
// This only works in Firefox 3.6; in Firefox 3.5 the request will instead
// fail to send cookies if the user has disabled third-party cookies.
try {
req.channel.QueryInterface(Ci.nsIHttpChannelInternal).
forceAllowThirdPartyCookie = true;
}
catch(ex) { /* user is using Firefox 3.5 */ }
req.send( data );
},
// Main function
// This is the only method that has to be called from outside this module
send: function(title, url, selection)
{
if (!this.sendUrl)
this.init();
var data = 'title=' + encodeURIComponent(title) +
'&url=' + encodeURIComponent(url) + '&sel=' + encodeURIComponent(selection);
this.pendingMessage = data;
this.processXHR(this.sendUrl, 'POST', data, this.processSentData);
},
processSentData : function(req)
{
var body = req.responseText;
if (body.substring(0, 2) == 'OK')
{
delete this.pendingMessage;
this.popupNotification(this.getString("InfoSent"));
return;
}
if (body.indexOf('LOGIN_REQUIRED') == 0)
{
this.popupNotification( this.getString("LoginRequired") );
//Open Google login page and close tab when done
this.openTab(this.logInUrl, this.loggedInUrl, function() {sendtophoneCore.loginSuccessful();} );
return;
}
if (body.indexOf('DEVICE_NOT_REGISTERED') == 0)
{
this.popupNotification(this.getString("DeviceNotRegistered"));
// Open tab with apk download
this.openTab(me.apkUrl);
return;
}
this.alert(this.getString("ErrorOnSend") + '\r\n' + body);
},
logout: function()
{
// Open Google logout page, and close tab when finished
this.openTab(this.logOutUrl, this.loggedOutUrl, function() {sendtophoneCore.logoutSuccessful();} );
/*
// This doesn't work if third party cookies are bloqued. Why???
this.processXHR(this.logOutUrl, 'GET', null, function(req)
{
// This will be called only if there's a problem
this.alert(this.getString("LogoutError") + '\r\n' + req.responseText );
});
*/
},
openTab: function(url, successUrl, callback)
{
var gBrowser = Cc["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.activeWindow
.gBrowser;
var lastTab = gBrowser.tabContainer.selectedIndex;
var tab = gBrowser.addTab(url);
gBrowser.selectedTab = tab;
if (successUrl && callback)
{
var c2pTab = gBrowser.getBrowserForTab(tab);
//Add listener for callback URL
c2pTab.addEventListener("load", function () {
if(successUrl==c2pTab.currentURI.spec){
callback();
// Close tab
gBrowser.removeCurrentTab();
// ReFocus on tab being sent
gBrowser.selectedTab = gBrowser.tabContainer.childNodes[lastTab];
}
}, true);
}
},
logoutSuccessful: function()
{
this.popupNotification(this.getString("LogoutSuccessful"));
},
loginSuccessful: function()
{
this.popupNotification( this.getString("LoginSuccessful") );
// Send pending message
this.processXHR(this.sendUrl, 'POST', this.pendingMessage, this.processSentData);
}
};