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) 2010 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 /** 42 * This class is used to import contacts using OAuth. 43 * This requires some interaction with a remote website (pirules.org) for 44 * authentication. 45 * 46 * pirules.org stores the following information for each source 47 * - oauth_consumer_key 48 * - oauth_consumer_secret 49 * - base API URL 50 * - @me/@self URL 51 * - @me/@all or @me/@friends URL 52 * etc. 53 * It also reorganizes and signs the parameters. 54 * 55 * TODO List: 56 * - Attempt to get more contact info from MySpace 57 * 58 * @class 59 */ 60 com.gContactSync.Import = { 61 /** The 'source' from which contacts are imported (Plaxo, Google, etc.) */ 62 mSource: "", 63 /** This is used internally to track whether an import is in progress */ 64 mStarted: false, 65 /** A reference to the window TODO remove */ 66 mWindow: {}, 67 /** Map for Plaxo only */ 68 mMapplaxo: { 69 /** The user's ID */ 70 id: "PlaxoID", 71 /** An array of the user's photos */ 72 photos: "PlaxoPhotos" 73 }, 74 /** Map for MySpace only */ 75 mMapmyspace: { 76 /** The user's MySpace ID */ 77 id: "MySpaceID", 78 /** 79 * The 'nickname' (MySpace only). This is mapped w/ DisplayName because it 80 * is basically all that MySpace gives. 81 */ 82 nickname: "DisplayName", 83 /** The user's thumbnail */ 84 thumbnailUrl: "MySpaceThumbnail", 85 /** The URL to the contact's profile */ 86 profileUrl: "WebPage2" 87 }, 88 /** Map for Facebook only */ 89 mMapfacebook: { 90 // TODO birthday 91 /** Name is a simple attribute */ 92 name: "DisplayName", 93 /** ID is also a simple attribute */ 94 id: "FacebookID", 95 /** A link to the user's Facebook profile */ 96 link: "WebPage1", 97 /** A link to the user's website */ 98 website: "WebPage2", 99 /** The user's 'About' text */ 100 about: "Notes", 101 /** The user's public profile photo */ 102 //picture: "FacebookProfilePhoto", 103 /** The user's hometown */ 104 hometown: { 105 /** The name of the contact's hometown */ 106 name: "Hometown" 107 }, 108 /** The contact's current location */ 109 location: { 110 /** The name of the contact's current location */ 111 name: "Location" 112 }, 113 /** An array of a user's job history */ 114 work: { 115 /** The most recent job */ 116 0: "", 117 /** The second most recent job */ 118 1: "Second", 119 /** The third most recent job */ 120 2: "Third", 121 /** Employer information (name and Facebook ID) */ 122 employer: { 123 /** The name of the company */ 124 name: "Company" 125 }, 126 /** Contact's position in the company */ 127 position: { 128 /** The name of the contact's position in the company */ 129 name: "JobTitle" 130 }, 131 /** The date when the contact started working for the company */ 132 start_date: "WorkStartDate", 133 /** The date when the contact stopped working for the company */ 134 end_date: "WorkEndDate" 135 } 136 }, 137 /** Maps Twitter attributes to TB */ 138 mMaptwitter: { 139 /** The actual name of the user */ 140 name: "DisplayName", 141 /** The screenname */ 142 screen_name: "NickName", 143 /** The internal Twitter ID */ 144 id: "TwitterID", 145 /** The user's profile image */ 146 profile_image_url: "TwitterImageURL", 147 /** The user's homepage */ 148 url: "WebPage2", 149 /** The user's description */ 150 description: "Notes" 151 }, 152 /** Maps Portable Contacts attributes to TB nsIAbCard attributes */ 153 mMap: { 154 /** name is complex */ 155 name: { 156 /** The given name for a contact */ 157 givenName: "FirstName", 158 /** The contact's last name */ 159 familyName: "LastName", 160 /** A contact's formatted name */ 161 formatted: "DisplayName", 162 /** A contact's display name */ 163 displayName: "DisplayName" 164 }, 165 /** The gender of the contact */ 166 gender: "Gender", 167 /** The contact's first (given) name */ 168 first_name: "FirstName", 169 /** The contact's last (family) name */ 170 last_name: "LastName", 171 /** A contact's display name */ 172 displayName: "DisplayName", 173 /** The contact's nickname (alias) */ 174 nickName: "NickName", 175 /** emails is an array of a contact's e-mail addresses */ 176 emails: { 177 /** The prefix for the first e-mail address */ 178 0: "Primary", 179 /** The prefix for the second e-mail address */ 180 1: "Secondary", 181 /** The prefix for the third e-mail address */ 182 2: "Third", 183 /** The prefix for the fourth e-mail address */ 184 3: "Fourth", 185 /** The prefix for the fifth e-mail address */ 186 4: "Fifth", 187 /** The suffix for an e-mail address */ 188 value: "Email", 189 /** The suffix for an e-mail's type (work, home, etc.) */ 190 type: "EmailType" 191 }, 192 /** 193 * phoneNumbers is an array of a contact's phone numbers in the form: 194 * {"type":"Home","value":"(123) 456-7890"} 195 */ 196 phoneNumbers: { 197 0: "Work", 198 1: "Home", 199 2: "Fax", 200 3: "Cell", 201 4: "Pager", 202 value: "Phone", // note that TB is inconsistent here 203 // {Home|Work}Phone and {Fax|Cellular|Pager}Number 204 type: "PhoneType" 205 }, 206 /** 207 * addresses is an array of a contact's postal addresses in the form: 208 * {"type":"Home","formatted":"1234 Main St"} 209 */ 210 addresses: { 211 0: "", 212 1: "", 213 2: "", 214 type: "", 215 formatted: "<type>Address" 216 }, 217 /** 218 * Links to a user's websites. 219 */ 220 urls: { 221 0: "WebPage1", 222 1: "WebPage2", 223 type: "Type", 224 value: "" 225 }, 226 /** An array of a user's job history */ 227 organizations: { 228 /** The most recent job */ 229 0: "", 230 /** The second most recent job */ 231 1: "Second", 232 /** The third most recent job */ 233 2: "Third", 234 /** The person's job title */ 235 title: "JobTitle", 236 /** The person's company */ 237 name: "Company" 238 } 239 }, 240 /** Commands to execute when offline during an HTTP Request */ 241 mOfflineFunction: function Import_offlineFunc(httpReq) { 242 com.gContactSync.alertError(com.gContactSync.StringBundle.getStr('importOffline')); 243 com.gContactSync.Overlay.setStatusBarText(com.gContactSync.StringBundle.getStr('offlineImportStatusText')); 244 }, 245 /** 246 * Stores <em>encoded</em> OAuth variables, such as the oauth_token, 247 * oauth_token_secret, and oauth_verifier 248 */ 249 mOAuth: { 250 /** The OAuth token to use in requests */ 251 oauth_token: "", 252 /** The OAuth token secret to use in signing request parameters */ 253 oauth_token_secret: "", 254 /** The OAuth verifier for OAuth version 1.0a */ 255 oauth_verifier: "", 256 /** The access token (OAuth version 2.0) */ 257 access_token: "", 258 /** The expiration time (OAuth version 2.0) */ 259 expires: "" 260 }, 261 /** 262 * Step 1: Get an initial, unauthorized oauth_token and oauth_token_secret. 263 * This is done mostly on pirules.org which contains the consumer token and 264 * secret for various sources and signs the parameters. 265 * pirules.org returns the response from the source, usually of the form: 266 * oauth_token=1234&oauth_token_secret=5678 267 * 268 * @param aSource {string} The source from which the contacts are obtained, 269 * in lowercase, as supported by pirules.org. 270 */ 271 step1: function Import_step1(aSource) { 272 var imp = com.gContactSync.Import, 273 callback = aSource == "facebook" ? imp.step2b : imp.step2a; 274 if (imp.mStarted) { 275 // TODO warn the user and allow him or her to cancel 276 } 277 278 // Reset mOAuth 279 imp.mOAuth.oauth_token = ""; 280 imp.mOAuth.oauth_token_secret = ""; 281 imp.mOAuth.oauth_verifier = ""; 282 imp.mOAuth.access_token = ""; 283 imp.mOAuth.expires = ""; 284 285 com.gContactSync.Overlay.setStatusBarText(com.gContactSync.StringBundle.getStr('startingImport')); 286 imp.mStarted = true; 287 imp.mSource = aSource; 288 // get an oauth_token and oauth_token_secret and give pirules.org some 289 // strings 290 imp.httpReqWrapper("http://www.pirules.org/oauth/index2.php?quiet&silent&step=1&source=" + 291 imp.mSource + 292 "&title=" + 293 encodeURIComponent(com.gContactSync.StringBundle.getStr('importTitle')) + 294 "&instructions_title=" + 295 encodeURIComponent(com.gContactSync.StringBundle.getStr('importInstructionsTitle')) + 296 "&instructions_0=" + 297 encodeURIComponent(com.gContactSync.StringBundle.getStr('importInstructions0')) + 298 "&instructions_1=" + 299 encodeURIComponent(com.gContactSync.StringBundle.getStr('importInstructions1')), 300 callback); 301 }, 302 /** 303 * Step 2a: The first of two substeps where the user is prompted for his or 304 * her credentials on the third-party website. 305 * In this substep, gContactSync gets the login URL from pirules.org with 306 * all it's parameters and the oauth_signature. 307 * This is done in step 1 for OAuth 2.0 (Facebook only at the moment). 308 */ 309 step2a: function Import_step2a(httpReq) { 310 var imp = com.gContactSync.Import, 311 response = httpReq.responseText; 312 if (!response) { 313 com.gContactSync.Overlay.setStatusBarText(com.gContactSync.StringBundle.getStr('importFailed')); 314 com.gContactSync.LOGGER.LOG_ERROR("***Import failed to get the auth tokens"); 315 return; 316 } 317 com.gContactSync.LOGGER.LOG("***IMPORT: Step 1 finished:\nContents:\n" + 318 response); 319 // parse and store the parameters from step 1 (oauth_token & 320 // oauth_token_secret) 321 imp.storeResponse(response.replace("&", "&")); 322 imp.httpReqWrapper("http://www.pirules.org/oauth/index2.php?quiet&silent&step=2&source=" + 323 imp.mSource + 324 "&oauth_token=" + imp.mOAuth.oauth_token + 325 "&oauth_token_secret=" + imp.mOAuth.oauth_token_secret, 326 imp.step2b); 327 }, 328 /** 329 * Step 2b: The second of two substeps where the user is prompted for his or 330 * her credentials on the third-party website. 331 * In this substep, gContactSync opens a browser to the login page for the 332 * particular source. 333 */ 334 step2b: function Import_step2b(httpReq) { 335 var imp = com.gContactSync.Import, 336 response = httpReq.responseText; 337 if (!response) { 338 com.gContactSync.Overlay.setStatusBarText(com.gContactSync.StringBundle.getStr('importFailed')); 339 com.gContactSync.LOGGER.LOG_ERROR("***Import failed to get the login URL"); 340 return; 341 } 342 response = String(response).replace(/\&\;/g, "&"); 343 com.gContactSync.LOGGER.LOG("***IMPORT: Step 2a finished:\nContents:\n" + response); 344 com.gContactSync.Overlay.setStatusBarText(com.gContactSync.StringBundle.getStr('importRequestingCredentials')); 345 imp.openBrowserWindow(response, imp.logStep2b); 346 }, 347 /** 348 * Step 2b: The second of two substeps where the user is prompted for his or 349 * her credentials on the third-party website. 350 * This just logs that step 2b has finished (the login page was opened) 351 */ 352 logStep2b: function Import_logStep2b() { 353 var win = com.gContactSync.Import.mWindow; 354 com.gContactSync.LOGGER.LOG("***IMPORT: Step 2b finished: " + win.location + 355 "Please click Finish Import to continue"); 356 }, 357 /** 358 * Step 3: Gets the new oauth_token then activates the token. 359 * This step must be initiated by the user (for now). 360 * TODO - find a way to automatically start step3 when possible. 361 */ 362 step3: function Import_step3() { 363 var imp = com.gContactSync.Import; 364 if (!imp.mStarted) { 365 com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("importNotStarted")); 366 return; 367 } 368 // Get the new oauth_token from the window. 369 imp.mOAuth.oauth_token = encodeURIComponent(imp.mWindow.document.getElementById('response').innerHTML); 370 // Get the oauth_verifier, if any 371 if (imp.mWindow.document.getElementById("oauth_verifier")) { 372 imp.mOAuth.oauth_verifier = encodeURIComponent(imp.mWindow.document.getElementById('oauth_verifier').innerHTML); 373 } 374 imp.mWindow.close(); 375 if (!imp.mOAuth.oauth_token) { 376 com.gContactSync.alert(com.gContactSync.StringBundle.getStr('importCanceled'), 377 com.gContactSync.StringBundle.getStr('importCanceledTitle'), 378 window); 379 imp.mStarted = false; 380 return; 381 } 382 com.gContactSync.Overlay.setStatusBarText(com.gContactSync.StringBundle.getStr('importActivatingToken')); 383 // activate the token 384 imp.httpReqWrapper("http://www.pirules.org/oauth/index2.php?quiet&silent&step=3&source=" + 385 imp.mSource + 386 "&oauth_token=" + imp.mOAuth.oauth_token + 387 "&oauth_token_secret=" + imp.mOAuth.oauth_token_secret + 388 (imp.mOAuth.oauth_verifier ? "&oauth_verifier=" + imp.mOAuth.oauth_verifier : ""), 389 imp.step4); 390 }, 391 /** 392 * Step 4: Use the token to fetch the user's contacts. 393 * This sends a request and the token/token secret to pirules.org which 394 * signs and sends the request to the source's @me/@friend URL. 395 */ 396 step4: function Import_step4(httpReq) { 397 var imp = com.gContactSync.Import, 398 response = httpReq.responseText; 399 if (!response) { 400 com.gContactSync.LOGGER.LOG("***Import failed on step 3"); 401 return; 402 } 403 com.gContactSync.LOGGER.LOG("***IMPORT: Step 3 finished:\nContents:\n" + response); 404 imp.storeResponse(response.replace("&", "&")); 405 com.gContactSync.Overlay.setStatusBarText(com.gContactSync.StringBundle.getStr('importRetrievingContacts')); 406 // Use the token to fetch the user's contacts 407 // access_token is used instead of the oauth_token in OAuth 2.0 408 if (imp.mOAuth.access_token) { 409 imp.httpReqWrapper("http://www.pirules.org/oauth/index2.php?quiet&silent&step=4&source=" + 410 imp.mSource + 411 "&access_token=" + imp.mOAuth.access_token, 412 imp.finish); 413 } 414 else { 415 imp.httpReqWrapper("http://www.pirules.org/oauth/index2.php?quiet&silent&step=4&source=" + 416 imp.mSource + 417 "&oauth_token=" + imp.mOAuth.oauth_token + 418 "&oauth_token_secret=" + imp.mOAuth.oauth_token_secret, 419 imp.finish); 420 } 421 }, 422 /** 423 * Gets the response from step 4 and calls beginImport to parse the JSON feed 424 * of contacts. 425 */ 426 // Get the contact feed and import it into an AB 427 finish: function Import_finish(httpReq) { 428 var imp = com.gContactSync.Import, 429 response = httpReq.responseText; 430 if (!response) { 431 com.gContactSync.LOGGER.LOG("***Import failed on step 4"); 432 return; 433 } 434 com.gContactSync.LOGGER.LOG("Final response:\n" + response); 435 imp.mStarted = false; 436 com.gContactSync.Overlay.setStatusBarText(com.gContactSync.StringBundle.getStr('importParsingContacts')); 437 // start the import 438 imp.beginImport(response); 439 }, 440 /** 441 * Parses and stores a URL-encoded response in the following format: 442 * param1=value1¶m2=value2¶m3=value3... 443 * The parsed parameters and values are stored (still encoded) in 444 * com.gContactSync.Import.mOAuth[param] = value; 445 * 446 * @param aResponse {string} The encoded response to parse. 447 */ 448 storeResponse: function Import_storeResponse(aResponse) { 449 var imp = com.gContactSync.Import, 450 params = (aResponse).split("&"); 451 for (var i = 0; i < params.length; i++) { 452 var index = params[i].indexOf("="); 453 if (index > 0) { 454 var param = params[i].substr(0, index), 455 value = params[i].substr(index + 1); 456 com.gContactSync.LOGGER.VERBOSE_LOG("***" + param + "=>" + value); 457 imp.mOAuth[param] = value; 458 } 459 } 460 }, 461 /** 462 * Opens a window at the given URL and optionally sets an onbeforeunload 463 * listener. 464 * 465 * @param aUrl {string} The URL to open. 466 * @param aBeforeUnload {function} The function to run before the window is 467 * unloaded. 468 */ 469 openBrowserWindow: function Import_openBrowserWindow(aUrl, aBeforeUnload) { 470 var imp = com.gContactSync.Import; 471 com.gContactSync.LOGGER.LOG("***IMPORT: opening '" + aUrl + "'"); 472 // TODO - find a way to show a location bar, allow context menus, etc. 473 imp.mWindow = window.open(aUrl, 474 "gContactSyncImport" + aUrl, 475 "chrome=yes,location=yes,resizable=yes,height=500,width=500,modal=no"); 476 if (aBeforeUnload) { 477 imp.mWindow.onbeforeunload = aBeforeUnload; 478 } 479 }, 480 /** 481 * Begins the actual import given a JSON feed of contacts. 482 * It promps the user for a name for the destination AB (can be new or old). 483 * 484 * @param aFeed {string} The JSON feed of contacts to parse. 485 */ 486 beginImport: function Import_beginImport(aFeed) { 487 if (!aFeed) { 488 return; 489 } 490 try { 491 com.gContactSync.LOGGER.VERBOSE_LOG(aFeed); 492 var obj = aFeed; 493 // decode the JSON and get the array of cards 494 var nsIJSON = Components.classes["@mozilla.org/dom/json;1"] 495 .createInstance(Components.interfaces.nsIJSON); 496 try { 497 obj = nsIJSON.decode(aFeed); 498 } 499 catch (e) { 500 com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("importFailedMsg")); 501 com.gContactSync.LOGGER.LOG_ERROR("Import failed: ", aFeed); 502 com.gContactSync.Overlay.setStatusBarText(com.gContactSync.StringBundle.getStr('importFailed')); 503 return; 504 } 505 var res = com.gContactSync.prompt(com.gContactSync.StringBundle.getStr("importDestination"), 506 com.gContactSync.StringBundle.getStr("importDestinationTitle"), 507 window); 508 if (!res) { 509 return; 510 } 511 var ab = new com.gContactSync.GAddressBook(com.gContactSync.GAbManager.getAbByName(res), 512 true); 513 var arr = obj.entry || obj.data || obj; 514 515 for (var i in arr) { 516 var contact = arr[i], 517 id = contact.id || contact.guid; 518 if (id || contact.name || contact.displayName) { 519 var newCard = ab.newContact(), 520 attr = ""; 521 // Download FB photos 522 if (this.mSource === "facebook" && id) { 523 var file = com.gContactSync.writePhoto("https://graph.facebook.com/" + id + "/picture?type=large", 524 id + "_" + (new Date()).getTime()); 525 if (file) { 526 com.gContactSync.LOGGER.VERBOSE_LOG("Wrote photo...name: " + file.leafName); 527 newCard.setValue("PhotoName", file.leafName); 528 newCard.setValue("PhotoType", "file"); 529 newCard.setValue("PhotoURI", 530 Components.classes["@mozilla.org/network/io-service;1"] 531 .getService(Components.interfaces.nsIIOService) 532 .newFileURI(file) 533 .spec); 534 } 535 } 536 // Iterate through each attribute in the JSON contact 537 for (var j in contact) { 538 // If there is a map for just this source, check it for the 539 // attribute first, otherwise just use the default map. 540 if (this["mMap" + this.mSource]) 541 attr = this["mMap" + this.mSource][j] || this.mMap[j]; 542 else 543 attr = this.mMap[j]; 544 545 if (attr) { 546 // Download a photo of the user, if available. 547 if (j === "picture" || j === "thumbnailUrl" || j === "photos" || 548 j === "profile_image_url") { 549 var file = com.gContactSync.writePhoto((j === "photos" ? contact[j][0].value : contact[j]), 550 this.mSource + "_" + id, 551 0); 552 if (file) { 553 com.gContactSync.LOGGER.VERBOSE_LOG("Wrote photo...name: " + file.leafName); 554 newCard.setValue("PhotoName", file.leafName); 555 newCard.setValue("PhotoType", "file"); 556 newCard.setValue("PhotoURI", 557 Components.classes["@mozilla.org/network/io-service;1"] 558 .getService(Components.interfaces.nsIIOService) 559 .newFileURI(file) 560 .spec); 561 } 562 } 563 // when contact[j] is an Array things are a bit more 564 // complicated 565 else if (contact[j] instanceof Array) { 566 // emails: [ 567 // {email: somebody@somwhere, type: work}, 568 // {email: somebody2@somwhere, type: work} 569 // ] 570 // contact[j] = emails[] 571 // contact[j][k] = emails[k] 572 for (var k = 0; k < contact[j].length; k++) { 573 // quit if k is too large/shouldn't be stored 574 if (!(k in attr)) { 575 break; 576 } 577 // contact[j][k][l] = sombody@somewhere 578 for (var l in contact[j][k]) { 579 if (l in attr) { 580 var type = contact[j][k].type; 581 // not all arrays can be mapped to TB fields by index 582 // TODO - support using original phone # fields 583 // this would require NOT storing the type... 584 var tbAttribute = String(attr[k] + attr[l]).replace("<type>", type); 585 // Workaround for inconsistent phone number attributes in TB 586 if (attr === "phoneNumbers" && (type === "Cellular" || type === "Pager" || type === "Fax")) { 587 tbAttribute = tbAttribute.replace("Phone", "Number"); 588 } 589 // mMap[j][[k] is the prefix (Primary, Second, etc.) 590 // mMap[j][l] is the suffix (Email) 591 com.gContactSync.LOGGER.VERBOSE_LOG(" - (Array): " + tbAttribute + "=" + contact[j][k][l]); 592 newCard.setValue(tbAttribute, this.decode(contact[j][k][l])); 593 } 594 } 595 596 } 597 } 598 else if (j === "photos") { 599 // TODO download the photo... 600 // possibly implementation-specific 601 } 602 // if it is just a normal property (has a length property => 603 // string) check the map 604 else if (attr.length) { 605 com.gContactSync.LOGGER.VERBOSE_LOG(" - (String): " + attr + "=" + contact[j]) 606 newCard.setValue(attr, this.decode(contact[j])); 607 } 608 // else it is an object with subproperties 609 else { 610 for (var k in contact[j]) { 611 if (k in attr) { 612 com.gContactSync.LOGGER.VERBOSE_LOG(" - (Object): " + attr[k] + "/" + j + "=" + contact[j][k]); 613 newCard.setValue(attr[k], this.decode(contact[j][k])); 614 } 615 } 616 } 617 } 618 } 619 newCard.update(); 620 } 621 } 622 } 623 catch (e) { 624 com.gContactSync.alertError(e); 625 return; 626 } 627 // refresh the ab results pane 628 try { 629 if (SetAbView !== undefined) { 630 SetAbView(GetSelectedDirectory(), false); 631 } 632 633 // select the first card, if any 634 if (gAbView && gAbView.getCardFromRow(0)) 635 SelectFirstCard(); 636 } 637 catch (e) {} 638 com.gContactSync.Overlay.setStatusBarText(com.gContactSync.StringBundle.getStr('importFinished')); 639 com.gContactSync.alert(com.gContactSync.StringBundle.getStr("importComplete"), 640 com.gContactSync.StringBundle.getStr("importCompleteTitle"), 641 window); 642 }, 643 /** 644 * Decodes text returned in a JSON feed. 645 * @param aString {string} The text to decode. 646 * @returns {string} The decoded text. 647 */ 648 decode: function Import_decode(aString) { 649 return aString ? 650 decodeURIComponent(aString).replace(/</g, "<") 651 .replace(/>/g, ">") 652 .replace(/&/g, "&") 653 .replace(/"/g, '"') : 654 ""; 655 }, 656 /** 657 * A wrapper for HttpRequest for use when importing contacts. 658 * @param aURL {string} The URL to send the GET request to. 659 * @param aCallback {function} The callback function if the request succeeds. 660 */ 661 httpReqWrapper: function Import_httpReqWrapper(aURL, aCallback) { 662 var httpReq = new com.gContactSync.HttpRequest(); 663 httpReq.mUrl = aURL; 664 httpReq.mType = "GET"; 665 httpReq.mOnSuccess = aCallback; 666 httpReq.mOnOffline = this.mOfflineFunction; 667 httpReq.mOnError = function import_onError(httpReq) { 668 com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("importFailedMsg")); 669 com.gContactSync.LOGGER.LOG_ERROR("Import failed: ", httpReq.responseText); 670 com.gContactSync.Overlay.setStatusBarText(com.gContactSync.StringBundle.getStr('importFailed')); 671 } 672 httpReq.send(); 673 }, 674 /** 675 * Attempts to import from the Mozilla Labs Contacts add-on. 676 * https://wiki.mozilla.org/Labs/Contacts/ContentAPI 677 */ 678 mozillaLabsContactsImporter: function Import_mozLabsImporter() { 679 if (com.gContactSync.Import.mStarted) { 680 // TODO warn the user and allow him or her to cancel 681 } 682 683 com.gContactSync.Import.mSource = "mozLabsContacts"; 684 try { 685 686 // Import the Mozilla Labs Contacts module that loads the contacts DB 687 Components.utils.import("resource://people/modules/people.js"); 688 689 var nsIJSON = Components.classes["@mozilla.org/dom/json;1"] 690 .createInstance(Components.interfaces.nsIJSON); 691 692 // TODO - this needs to be much more efficient 693 var json = JSON.stringify({data: People.find({})}); 694 var toEncode = {data: []}; 695 var people = []; 696 697 // decode the JSON and get the array of cards 698 try { 699 people = nsIJSON.decode(json); 700 } 701 catch (e) { 702 com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("importFailedMsg")); 703 com.gContactSync.LOGGER.LOG_ERROR("Import failed: ", aFeed); 704 com.gContactSync.Overlay.setStatusBarText(com.gContactSync.StringBundle.getStr('importFailed')); 705 return; 706 } 707 708 // Iterate through each person add add them to the JSON 709 // This loop essentially just converts the people into a portable contacts 710 // format for beginImport() 711 for (var i in people.data) { 712 var person = people.data[i].obj; 713 if (person && person.documents) { 714 var personInfo = {}; 715 716 // People can have the same info in multiple documents, this just 717 // iterates through each document and copies the details over. 718 for (var j in person.documents) { 719 for (var k in person.documents[j]) { 720 for (var l in person.documents[j][k]) 721 personInfo[l] = person.documents[j][k][l]; 722 com.gContactSync.LOGGER.LOG(j + "." + k + "." + l + " - " + person.documents[j][k][l]) 723 } 724 } 725 toEncode.data.push(personInfo); 726 } 727 } 728 com.gContactSync.Import.beginImport(JSON.stringify(toEncode)); 729 } catch (e) { 730 com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("mozLabsContactsImportFailed")); 731 com.gContactSync.LOGGER.LOG_ERROR("Mozilla Labs Contacts Import Failed", e); 732 } 733 } 734 };