Loop/Architecture/Address Book
Introduction
The main means by which users are expected to initiate Loop calls on desktop is via the "Address Book" sidebar, which will contain a list of the user's contacts. For the initial versions of Loop, contacts will can be populated from several sources, via a one-way synchronization (that is, the data will be imported from a remote authority and updated from that remote authority, but cannot be changed by the Loop client). A necessary implication of this setup is that all contacts will need to track which data source they belong to, and be updated only from those sources.
Format
To allow for future integration with the Contacts API and/or potential integration with contact synchronization across devices (including Firefox OS devices), we will use objects with properties having the same structure as those used by mozContact. In theory, to store these in the Contacts API database, all that would be required would be passing in the object as the argument to the mozContact constructor, and then storing it via navigator.mozContacts.save().
An example of this format is as follows; note that many of the fields are arrays of values rather than single values. Even though our importation will typically only populate these with a single value, we will be maintaining them as arrays to retain the properties described above.
{ "id": "23598", "published": "2014-07-02T15:25:32.000Z", "updated": "2014-07-09T19:46:28.000Z", "bday": "1972-07-19T05:00:00.000Z", "adr": [ { "countryName": "USA", "locality": "Dallas", "postalCode": "75201", "pref": true, "region": "TX", "streetAddress": "1234 Main St.", "type": [ "home" ] } ], "email": [ { "pref": true, "type": [ "work" ], "value": "adam@example.com" } ], "tel": [ { "pref": true, "type": [ "work" ], "value": "+12145551234" } ], "name": [ "Adam Roach" ], "honorificPrefix": [ "Mr." ], "givenName": [ "Adam" ], "additionalName": [ "Boyd" ], "familyName": [ "Roach" ], "honorificSuffix": [ "Jr." ], "category": [ "gmail" ], "org": [ "Mozilla" ], "jobTitle": [ "Principal Engineer" ], "note": [ "Likes Mexican food" ] }
We will be using the "category" field to keep track of the source from which the contact was imported. The remaining fields are derived from information retrieved during the importation process.
For the time being, we will not be using the following mozContacts fields:
- anniversary
- sex
- genderIdentity
- url
- impp
- phoneticGivenName
- phoneticGamilyName
- nickname
- key
Importing
In order to avoid running into cross-origin issues in retrieving contacts, the retrieval will be performed in privileged code. This means that the window.mozLoop API will need new functions added to it in order to initiate contact import.
The function for starting an import will be of the form:
function startImport(importId, serviceParameters, newContactCallback, errorCallback)
- importId: A unique ID passed back to the caller in callbacks. This allows the caller to disambiguate between import operations in the case that there are multiple operations going on at the same time.
- serviceParameters: An object defining the service to import from. This must contain a "service" parameter. Certain services may require additional parameters.
- newContactCallback: A callback of the form function(importId, contact, progress, total), called whenever a new contact is imported. Note that this contact may already exist in the database (that is, it may already have an entry with a matching "id" field), in which case the new contact should replace the existing entry.
- importId: The import ID passed in when startImport was called.
- contact: The contact in the format described above
- progress: The number of contacts that have been imported from this operation
- total: The total number of contacts in this current import operation. Set to '-1' if unknown.
- errorCallback: A callback of the form function(importId, error)
- importId: The import ID passed in when startImport was called.
- error: An Error object with information about the failure.
window.mozLoop.startImport({service: "gmail"}, addContactToDatabase, handleImportError)
Gmail
The current plan is to use the Google Contacts API, v3 to retrieve contacts from users' gmail accounts. The behavior described here is modeled on the Gaia Gmail Connector code.
Functionally, the steps involved here are:
- Retrieve OAUTH2 token for user (see "Authorizing requests to the Google Contacts API service" in the Google Contacts API)
- Retrieve the group id for the "Contacts" group from https://www.google.com/m8/feeds/groups/default/full/ (see "Retreiving all Contact Groups").
- Retrieve the list of users in the "Contacts" group from https://www.google.com/m8/feeds/contacts/default/full/?max-results=maxResults&group=groupId, where groupId is the value retrieved in the previous step. The value of maxResults should be large enough to accommodate users' address books -- the mobile implementation uses 10,000.
- For each contact in the result:
- Translate the result into a contact object according the the table below. Note that some data may require manipulation to convert between the gmail format and the field described in mozContacts.
- Fire a callback to allow the UI to update. The callback should include the number of total contacts being imported, the number of contacts already imported (from this import), and the "id" field of the contact that was just imported.
destination | source (see Google Data Elements) |
---|---|
id | id |
published | published |
updated | updated |
name[0] | name/fullName |
honorificPrefix[0] | name/namePrefix |
givenName[0] | name/givenName |
additionalName[0] | name/additionalName |
familyName[0] | name/familyName |
honorificSuffix[0] | name/nameSuffix |
email[x].type[0] | email @rel |
email[x].pref | email @primary |
email[x].value | email @email |
adr[x].type[0] | structuredPostalAddress @rel |
adr[x].pref | structuredPostalAddress @primary |
adr[x].streetAddress | structuredPostalAddress/street (or structuredPostalAddress/pobox) |
adr[x].locality | structuredPostalAddress/city |
adr[x].region | structuredPostalAddress/region |
adr[x].postalCode | structuredPostalAddress/postcode |
adr[x].countryName | structuredPostalAddress/country |
tel[x].type[0] | phoneNumber @rel |
tel[x].pref | phoneNumber @primary |
tel[x].value | phoneNumber @uri (or text value) |
org[0] | organization/orgName |
jobTitle[0] | organization/orgTitle |
bday | birthday/when |
note[0] | content |
photo[0] | link[rel='http://schemas.google.com/contacts/2008/rel#photo'] -- Note; need to retrieve and convert to blob |
category[0] | hardcoded to "gmail" |