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 /** 235 * Updates the given element's type by setting the rel or label attribute 236 * and removing the other attribute, if present. 237 * 238 * @param aElement {XMLElement} The XML element to update. 239 * @param aType {string} The type to set. 240 */ 241 setRelOrLabel: function gdata_setRelOrLabel(aElement, aType) { 242 if (!aElement || !aElement.setAttribute) { 243 throw "Invalid arguments passed to gdata.contacts.setRelOrLabel"; 244 } 245 246 var arr, 247 relAttr = "rel"; 248 switch (aElement.tagName) { 249 case "email": 250 case "gd:email": 251 arr = this.EMAIL_TYPES; 252 break; 253 case "im": 254 case "gd:im": 255 arr = this.IM_TYPES; 256 relAttr = "protocol"; 257 break; 258 case "phoneNumber": 259 case "gd:phoneNumber": 260 arr = this.PHONE_TYPES; 261 break; 262 case "structuredPostalAddress": 263 case "gd:structuredPostalAddress": 264 arr = this.POSTAL_ADDRESS_TYPES 265 break; 266 case "relation": 267 case "gd:relation": 268 arr = this.RELATION_TYPES; 269 break; 270 case "website": 271 case "gContact:website": 272 arr = this.WEBSITE_TYPES; 273 break; 274 default: 275 throw "Unrecognized tagName '" + aElement.tagName + "' in setRelOrLabel"; 276 } 277 278 // If it is NOT a custom type it should show up in arr. 279 if (!aType || (arr instanceof Array && arr.indexOf(aType) > -1 || arr[aType])) { 280 281 // Set a rel; website and relation elements need the rel to just be the 282 // type, everything else has a prefix and im elements need a protocol. 283 if (aElement.tagName == "website" || aElement.tagName == "relation") { 284 aElement.setAttribute(relAttr, aType); 285 } else if (aElement.tagName == "im") { 286 aElement.setAttribute("protocol", com.gContactSync.gdata.contacts.rel + "#" + aType); 287 aElement.setAttribute("rel", com.gContactSync.gdata.contacts.rel + "#other"); 288 } else { 289 aElement.setAttribute(relAttr, com.gContactSync.gdata.contacts.rel + "#" + aType); 290 } 291 // Remove a label, if present 292 if (aElement.hasAttribute("label")) { 293 aElement.removeAttribute("label"); 294 } 295 296 // Otherwise it IS a custom type so it should be a label 297 } else { 298 // Set a label 299 this.mCurrentElement.setAttribute("label", aType); 300 // Remove a rel, if present 301 if (this.mCurrentElement.hasAttribute("rel")) { 302 this.mCurrentElement.removeAttribute("rel"); 303 } 304 } 305 }, 306 /** Different types for a website */ 307 WEBSITE_TYPES: [ 308 "home-page", "blog", "profile", "home", "work", "other", "ftp" 309 ], 310 /** Different types of phones */ 311 PHONE_TYPES: [ 312 "work", "home", "work_fax", "mobile", "pager", "home_fax", "assistant", 313 "callback", "car", "company_main", "fax", "isdn", "main", "other_fax", 314 "radio", "telex", "tty_tdd", "work_mobile", "work_pager", "other" 315 ], 316 /** Different types for IM screennames */ 317 IM_TYPES: [ 318 "AIM", "GOOGLE_TALK", "ICQ", "YAHOO", "MSN", "JABBER", "SKYPE", "QQ" 319 ], 320 /** E-mail address categories */ 321 EMAIL_TYPES: [ 322 "other", "home", "work" 323 ], 324 /** Postal address categories */ 325 POSTAL_ADDRESS_TYPES: [ 326 "home", "work", "other" 327 ], 328 /** Tags that are valid an an organization tag */ 329 ORG_TAGS: { 330 orgDepartment: "1", 331 orgJobDescription: "1", 332 orgName: "1", 333 orgSymbol: "1", 334 orgTitle: "1" 335 }, 336 /** 337 * Returns true if the given tag is valid in an organization tag 338 * @returns {boolean} True if the given tag is valid in an organization tag. 339 */ 340 isOrgTag: function gdata_contacts_isOrgTag(aTagName) { 341 return this.ORG_TAGS[aTagName] ? true : false; 342 }, 343 /** Valid tags in a name tag */ 344 NAME_TAGS: { 345 givenName: "1", 346 additionalName: "1", 347 familyName: "1", 348 namePrefix: "1", 349 nameSuffix: "1", 350 fullName: "1" 351 }, 352 /** 353 * Returns true if the given tag is valid in an name tag 354 * @returns {boolean} True if the given tag is valid in a name tag. 355 */ 356 isNameTag: function gdata_contacts_isNameTag(aTagName) { 357 return this.NAME_TAGS[aTagName] ? true : false; 358 }, 359 /** Valid tags in a structuredAddress tag */ 360 ADDRESS_TAGS: { 361 housename: "1", 362 street: "1", 363 poBox: "1", 364 neighborhood: "1", 365 city: "1", 366 subregion: "1", 367 region: "1", 368 postcode: "1", 369 country: "1", 370 formattedAddress: "1" 371 }, 372 /** 373 * Returns true if the given tag is valid in a structuredAddress tag 374 * @returns {boolean} True if the given tag is valid in a structuredAddress 375 * tag. 376 */ 377 isAddressTag: function gdata_contacts_isAddressTag(aTagName) { 378 return this.ADDRESS_TAGS[aTagName] ? true : false; 379 }, 380 // different tagnames in the Atom feed, must be initialized 381 postalAddress: {}, 382 phoneNumber: {}, 383 email: {}, 384 im: {}, 385 id: {}, 386 updated: {}, 387 title: {}, 388 fullName: {}, 389 givenName: {}, 390 familyName: {}, 391 additionalName: {}, 392 namePrefix: {}, 393 nameSuffix: {}, 394 notes: {}, 395 orgName: {}, 396 orgTitle: {}, 397 organization: {}, 398 groupMembershipInfo: {}, 399 relation: {}, 400 nickname: {}, 401 birthday: {}, 402 website: {}, 403 /** Links in the contacts feed. The property name is the type of link 404 and the value is the value of the "rel" attribute */ 405 links: { 406 /** The Photo URL */ 407 PhotoURL: "http://schemas.google.com/contacts/2008/rel#photo", 408 /** The contact URL */ 409 SelfURL: "self", 410 /** The URL to edit the contact */ 411 EditURL: "edit" 412 }, 413 /** 414 * Returns the total number of contacts in an Atom document. 415 * @param aXML {XML Element} The Atom feed from Google. 416 */ 417 getNumberOfContacts: function gdata_contacts_getNumberOfContacts(aAtom) { 418 return aAtom.getElementsByTagNameNS("totalResults", 419 com.gContactSync.gdata.namespaces.OPEN_SEARCH.url); 420 } 421 }, 422 /** 423 * Returns true if there is at least one auth token. 424 * @returns {boolean} True if there is at least one auth token. 425 */ 426 isAuthValid: function gdata_isAuthValid() { 427 if (com.gContactSync.LoginManager.mNumAuthTokens === 0) 428 com.gContactSync.LoginManager.getAuthTokens(); 429 return com.gContactSync.LoginManager.mNumAuthTokens > 0; 430 }, 431 /** 432 * Backs up the Google contacts or groups feed to a file. 433 * @param aFeed {string} The feed to backup (as a string). 434 * @param aAccount {string} The username of the account. 435 * @param aPrefix {string} The prefix for the backup file. 436 * @param aSuffix {string} The suffix for the backup file. 437 * @return {boolean} True if the backup was successful. 438 */ 439 backupFeed: function gdata_backupFeed(aFeed, aAccount, aPrefix, aSuffix) { 440 var destFile = com.gContactSync.FileIO.getProfileDirectory(); 441 destFile.append(com.gContactSync.FileIO.fileNames.FOLDER_NAME); 442 destFile.append(com.gContactSync.FileIO.fileNames.GOOGLE_BACKUP_DIR); 443 destFile.append((aPrefix || "") + aAccount + (aSuffix || "")); 444 com.gContactSync.LOGGER.LOG("Beginning a backup of the Google Account:\n" + 445 aAccount + "\nto:\n" + destFile.path); 446 return com.gContactSync.FileIO.writeToFile(destFile, aFeed); 447 } 448 }; 449