1 /* ***** BEGIN LICENSE BLOCK *****
  2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3  *
  4  * The contents of this file are subject to the Mozilla Public License Version
  5  * 1.1 (the "License"); you may not use this file except in compliance with
  6  * the License. You may obtain a copy of the License at
  7  * http://www.mozilla.org/MPL/
  8  *
  9  * Software distributed under the License is distributed on an "AS IS" basis,
 10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 11  * for the specific language governing rights and limitations under the
 12  * License.
 13  *
 14  * The Original Code is gContactSync.
 15  *
 16  * The Initial Developer of the Original Code is
 17  * Josh Geenen <gcontactsync@pirules.org>.
 18  * Portions created by the Initial Developer are Copyright (C) 2008-2009
 19  * the Initial Developer. All Rights Reserved.
 20  *
 21  * Contributor(s):
 22  *
 23  * Alternatively, the contents of this file may be used under the terms of
 24  * either the GNU General Public License Version 2 or later (the "GPL"), or
 25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 26  * in which case the provisions of the GPL or the LGPL are applicable instead
 27  * of those above. If you wish to allow use of your version of this file only
 28  * under the terms of either the GPL or the LGPL, and not to allow others to
 29  * use your version of this file under the terms of the MPL, indicate your
 30  * decision by deleting the provisions above and replace them with the notice
 31  * and other provisions required by the GPL or the LGPL. If you do not delete
 32  * the provisions above, a recipient may use your version of this file under
 33  * the terms of any one of the MPL, the GPL or the LGPL.
 34  *
 35  * ***** END LICENSE BLOCK ***** */
 36 
 37 if (!com) var com = {}; // A generic wrapper variable
 38 // A wrapper for all GCS functions and variables
 39 if (!com.gContactSync) com.gContactSync = {};
 40 
 41 window.addEventListener("load",
 42   /** Initializes the gdata class when the window has finished loading */
 43   function gCS_gdataLoadListener(e) {
 44     com.gContactSync.gdata.contacts.init();
 45   },
 46 false);
 47 
 48 /**
 49  * Stores information on using the Google Data Apiā„¢ protocol, specifically the
 50  * contacts portion of the protocol.
 51  * http://code.google.com/apis/contacts/
 52  * @class
 53  */
 54 com.gContactSync.gdata = {
 55   AUTH_URL:              "https://www.google.com/accounts/ClientLogin",
 56   AUTH_REQUEST_TYPE:     "POST",
 57   AUTH_SUB_SESSION_URL:  "https://www.google.com/accounts/AuthSubSessionToken",
 58   AUTH_SUB_SESSION_TYPE: "GET",
 59   AUTH_SUB_REVOKE_URL:   "https://www.google.com/accounts/AuthSubRevokeToken",
 60   AUTH_SUB_REVOKE_TYPE:  "GET",
 61   O_AUTH_URL:            "https://www.google.com/accounts/AuthSubRequest?scope=https%3A%" +
 62                          "2F%2Fwww.google.com%2Fm8%2Ffeeds%2F&session=1&secure=0&next=" +
 63                          "http%3A%2F%2Fpirules.org%2Ftools%2Fgcs%2Findex.php",
 64   O_AUTH_TYPE:           "GET",
 65   /**
 66    * Sets up the body for an authentication request given the e-mail address
 67    * and password.
 68    * @param aEmail     {string} The user's e-mail address
 69    * @param aPassword  {string} The user's password
 70    * @returns {string} The body for an authentication request.
 71    */
 72   makeAuthBody: function gdata_makeAuthBody(aEmail, aPassword) {
 73     // NOTE: leave accountType as HOSTED_OR_GOOGLE or Google Apps for your
 74     // domain accounts won't work
 75     // fix the username (remove whitespace)
 76     aEmail = com.gContactSync.fixUsername(aEmail);
 77     return "accountType=HOSTED_OR_GOOGLE&Email=" + encodeURIComponent(aEmail) +
 78            "&Passwd=" + encodeURIComponent(aPassword) +
 79            "&service=cp&source=Josh-gContactSync-0-3";
 80   },
 81   /**
 82    * Returns the email address of the given ID.
 83    * @returns The e-mail address from an ID.
 84    */
 85   getEmailFromId: function gdata_getEmailFromId(aId) {
 86     if (!aId || !aId.indexOf || aId === "")
 87       return "";
 88     // typical ID:
 89     // http://www.google.com/m8/feeds/contacts/address%40gmail.com/base/...
 90     var startStr = "/feeds/contacts/",
 91         start    = aId.indexOf(startStr) + startStr.length,
 92         endStr   = "/base/",
 93         end      = aId.indexOf(endStr),
 94         address;
 95     if (start >= end)
 96       return "";
 97     address = decodeURIComponent(aId.substring(start, end));
 98     com.gContactSync.LOGGER.VERBOSE_LOG("found address: " + address + " from ID: " + aId);
 99     return address;
100   },
101   /** Namespaces used in the API */
102   namespaces: {
103     /** The APP namespace */
104     APP:         new com.gContactSync.Namespace("http://www.w3.org/2007/app",
105                                                 "app:"),
106     /** The ATOM namespace */
107     ATOM:        new com.gContactSync.Namespace("http://www.w3.org/2005/Atom",
108                                                 "atom:"),
109     /** The GD namespace */
110     GD:          new com.gContactSync.Namespace("http://schemas.google.com/g/2005",
111                                                 "gd:"),
112     /** The GCONTACT namespace */
113     GCONTACT:    new com.gContactSync.Namespace("http://schemas.google.com/contact/2008",
114                                                 "gContact:"),
115     /** The OPEN SEARCH namespace */
116     OPEN_SEARCH: new com.gContactSync.Namespace("http://a9.com/-/spec/opensearch/1.1/",
117                                                 "openSearch:"),
118     /** The BATCH namespace */
119     BATCH:       new com.gContactSync.Namespace("http://schemas.google.com/gdata/batch",
120                                                 "batch:")
121   },
122   /** some things related to contacts, such as related URLs and HTTP Request
123    * types
124    */
125   contacts: {
126     /** The URL to get all contacts (full) */
127     GET_ALL_URL:      "https://www.google.com/m8/feeds/contacts/default/full?" +
128                       "max-results=",
129     /** The URL to get all contacts (thin) */
130     GET_ALL_THIN_URL: "https://www.google.com/m8/feeds/contacts/default/thin?" +
131                       "max-results=",
132     /** The URL to get all groups (max 1000) */
133     GROUPS_URL:       "https://www.google.com/m8/feeds/groups/default/full?" +
134                       "max-results=1000",
135     /** The URL to add a group */
136     ADD_GROUP_URL:    "https://www.google.com/m8/feeds/groups/default/full",
137     /** The URL to add a contact */
138     ADD_URL:          "https://www.google.com/m8/feeds/contacts/default/full",
139     /** Types of relations (people somehow associated with the contact) */
140     RELATION_TYPES: {      
141       "assistant":        1,
142       "brother":          1,
143       "child":            1,
144       "domestic-partner": 1,
145       "father":           1,
146       "friend":           1,
147       "manager":          1,
148       "mother":           1,
149       "parent":           1,
150       "partner":          1,
151       "referred-by":      1,
152       "relative":         1,
153       "sister":           1,
154       "spouse":           1
155     },
156     /** Types of HTTP requests */
157     requestTypes: {
158       GET_ALL: "GET",
159       GET:     "GET",
160       UPDATE:  "PUT", // NOTE: should be set to POST and overridden
161       ADD:     "POST",
162       DELETE:  "DELETE"  // NOTE: should be set to POST and overridden
163     },
164     /** Different "types" of contact elements */
165     types: {
166       /** Has a type (#home, #work, #other, etc.) and the value is stored in a
167        * child node */
168       TYPED_WITH_CHILD: 0,
169       /** has a type and the value is stored in an attribute */
170       TYPED_WITH_ATTR: 1,
171       UNTYPED: 2,
172       /** The type is stored in the element's parent */
173       PARENT_TYPED: 3
174     },
175     /** The prefix for rel attributes */
176     rel: "http://schemas.google.com/g/2005",
177     /**
178      * Initializes the values of the tagnames with an GElement object containing
179      * information about how an Atom/XML representation of a contact from Google
180      * is stored.
181      */
182     init: function gdata_contacts_init() {
183       var GElement             = com.gContactSync.GElement,
184           untyped              = this.types.UNTYPED,
185           typedWithChild       = this.types.TYPED_WITH_CHILD,
186           typedWithAttr        = this.types.TYPED_WITH_ATTR,
187           parentTyped          = this.types.PARENT_TYPED,
188           gd                   = com.gContactSync.gdata.namespaces.GD,
189           atom                 = com.gContactSync.gdata.namespaces.ATOM,
190           gcontact             = com.gContactSync.gdata.namespaces.GCONTACT;
191       this.postalAddress       = new GElement(typedWithChild, "postalAddress",
192                                              gd, this.POSTAL_ADDRESS_TYPES);
193       this.phoneNumber         = new GElement(typedWithChild, "phoneNumber", gd,
194                                               this.PHONE_TYPES);
195       this.email               = new GElement(typedWithAttr, "email", gd,
196                                               this.EMAIL_TYPES, "address");
197       this.im                  = new GElement(typedWithAttr, "im", gd,
198                                               this.IM_TYPES, "address");
199       this.id                  = new GElement(untyped, "id", atom);
200       this.updated             = new GElement(untyped, "updated", atom);
201       this.title               = new GElement(untyped, "title", atom);
202       this.fullName            = new GElement(untyped, "fullName", gd);
203       this.givenName           = new GElement(untyped, "givenName", gd);
204       this.familyName          = new GElement(untyped, "familyName", gd);
205       this.additionalName      = new GElement(untyped, "additionalName", gd);
206       this.namePrefix          = new GElement(untyped, "namePrefix", gd);
207       this.nameSuffix          = new GElement(untyped, "nameSuffix", gd);
208       this.notes               = new GElement(untyped, "content", atom);
209       this.orgName             = new GElement(untyped, "orgName", gd);
210       this.orgTitle            = new GElement(untyped, "orgTitle", gd);
211       this.orgJobDescription   = new GElement(untyped, "orgJobDescription", gd);
212       this.orgDepartment       = new GElement(untyped, "orgDepartment", gd);
213       this.orgSymbol           = new GElement(untyped, "orgSymbol", gd);
214       this.birthday            = new GElement(untyped, "birthday", gcontact);
215       this.organization        = new GElement(typedWithAttr, "organization",
216                                               gd, ["other"]);
217       this.groupMembershipInfo = new GElement(untyped, "groupMembershipInfo",
218                                               gcontact);
219       this.relation            = new GElement(typedWithChild, "relation",
220                                               gcontact,
221                                               this.RELATION_TYPES);
222       this.nickname            = new GElement(untyped, "nickname",
223                                               gcontact);
224       this.website             = new GElement(typedWithAttr, "website",
225                                               gcontact,
226                                               this.WEBSITE_TYPES, "href");
227       this.formattedAddress    = new GElement(parentTyped, "formattedAddress", gd);
228       this.street              = new GElement(parentTyped, "street",           gd);
229       this.city                = new GElement(parentTyped, "city",             gd);
230       this.region              = new GElement(parentTyped, "region",           gd);
231       this.postcode            = new GElement(parentTyped, "postcode",         gd);
232       this.country             = new GElement(parentTyped, "country",          gd);
233     },
234     /** Different types for a website */
235     WEBSITE_TYPES: [
236       "home-page", "blog", "profile", "home", "work", "other", "ftp"
237     ],
238     /** Different types of phones */
239     PHONE_TYPES: [
240       "work", "home", "work_fax", "mobile", "pager", "home_fax", "assistant",
241       "callback", "car", "company_main", "fax", "isdn", "main", "other_fax",
242       "radio", "telex", "tty_tdd", "work_mobile", "work_pager", "other"
243     ],
244     /** Different types for IM screennames */
245     IM_TYPES: [
246       "AIM", "GOOGLE_TALK", "ICQ", "YAHOO", "MSN", "JABBER", "SKYPE", "QQ"
247     ],
248     /** E-mail address categories */
249     EMAIL_TYPES: [
250       "other", "home", "work"
251     ],
252     /** Postal address categories */
253     POSTAL_ADDRESS_TYPES: [
254       "home", "work", "other"
255     ],
256     /** Tags that are valid an an organization tag */
257     ORG_TAGS: {
258       orgDepartment:     "1",
259       orgJobDescription: "1",
260       orgName:           "1",
261       orgSymbol:         "1",
262       orgTitle:          "1"
263     },
264     /**
265      * Returns true if the given tag is valid in an organization tag
266      * @returns {boolean} True if the given tag is valid in an organization tag.
267      */
268     isOrgTag: function gdata_contacts_isOrgTag(aTagName) {
269       return this.ORG_TAGS[aTagName] ? true : false;
270     },
271     /** Valid tags in a name tag */
272     NAME_TAGS: {
273       givenName:         "1",
274       additionalName:    "1",
275       familyName:        "1",
276       namePrefix:        "1",
277       nameSuffix:        "1",
278       fullName:          "1"
279     },
280     /**
281      * Returns true if the given tag is valid in an name tag
282      * @returns {boolean} True if the given tag is valid in a name tag.
283      */
284     isNameTag: function gdata_contacts_isNameTag(aTagName) {
285       return this.NAME_TAGS[aTagName] ? true : false;
286     },
287     /** Valid tags in a structuredAddress tag */
288     ADDRESS_TAGS: {
289       housename:        "1",
290       street:           "1",
291       poBox:            "1",
292       neighborhood:     "1",
293       city:             "1",
294       subregion:        "1",
295       region:           "1",
296       postcode:         "1",
297       country:          "1",
298       formattedAddress: "1"
299     },
300     /**
301      * Returns true if the given tag is valid in a structuredAddress tag
302      * @returns {boolean} True if the given tag is valid in a structuredAddress
303      *                  tag.
304      */
305     isAddressTag: function gdata_contacts_isAddressTag(aTagName) {
306       return this.ADDRESS_TAGS[aTagName] ? true : false;
307     },
308     // different tagnames in the Atom feed, must be initialized
309     postalAddress:       {},
310     phoneNumber:         {},
311     email:               {},
312     im:                  {},
313     id:                  {},
314     updated:             {},
315     title:               {},
316     fullName:            {},
317     givenName:           {},
318     familyName:          {},
319     additionalName:      {},
320     namePrefix:          {},
321     nameSuffix:          {},
322     notes:               {},
323     orgName:             {},
324     orgTitle:            {},
325     organization:        {},
326     groupMembershipInfo: {},
327     relation:            {},
328     nickname:            {},
329     birthday:            {},
330     website:             {},
331     /** Links in the contacts feed.  The property name is the type of link
332         and the value is the value of the "rel" attribute */
333     links: {
334       /** The Photo URL */
335       PhotoURL: "http://schemas.google.com/contacts/2008/rel#photo",
336       /** The contact URL */
337       SelfURL:  "self",
338       /** The URL to edit the contact */
339       EditURL:  "edit"
340     },
341     /**
342      * Returns the total number of contacts in an Atom document.
343      * @param aXML {XML Element} The Atom feed from Google.
344      */
345     getNumberOfContacts: function gdata_contacts_getNumberOfContacts(aAtom) {
346       return aAtom.getElementsByTagNameNS("totalResults",
347                                           com.gContactSync.gdata.namespaces.OPEN_SEARCH.url);
348     }
349   },
350   /**
351    * Returns true if there is at least one auth token.
352    * @returns {boolean} True if there is at least one auth token.
353    */ 
354   isAuthValid: function gdata_isAuthValid() {
355     if (com.gContactSync.LoginManager.mNumAuthTokens === 0)
356       com.gContactSync.LoginManager.getAuthTokens();
357     return com.gContactSync.LoginManager.mNumAuthTokens > 0;
358   },
359   /**
360    * Backs up the Google contacts or groups feed to a file.
361    * @param aFeed {string}    The feed to backup (as a string).
362    * @param aAccount {string} The username of the account.
363    * @param aPrefix {string}  The prefix for the backup file.
364    * @param aSuffix {string}  The suffix for the backup file.
365    * @return {boolean} True if the backup was successful.
366    */
367   backupFeed: function gdata_backupFeed(aFeed, aAccount, aPrefix, aSuffix) {
368     var destFile = com.gContactSync.FileIO.getProfileDirectory();
369     destFile.append(com.gContactSync.FileIO.fileNames.FOLDER_NAME);
370     destFile.append(com.gContactSync.FileIO.fileNames.GOOGLE_BACKUP_DIR);
371     destFile.append((aPrefix || "") + aAccount + (aSuffix || ""));
372     com.gContactSync.LOGGER.LOG("Beginning a backup of the Google Account:\n" +
373                                 aAccount + "\nto:\n" + destFile.path);
374     return com.gContactSync.FileIO.writeToFile(destFile, aFeed);
375   }
376 };
377