npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@steven-sg/firefox-sync

v1.2.2

Published

Node.js client for Firefox Sync.

Downloads

2

Readme

Node Firefox Sync npm version

Node.js client for Firefox Sync.

Overview

I actually wanted to inspect my raw Firefox Sync data for some reason that day. I was lazy to put together and run the example code from the blog posts I wrote earlier this year, so I ended up spending a few days to release a Node.js client and CLI instead. Go figure out.

You can authenticate to Firefox Sync through password authentication or through OAuth. With OAuth, the user password is never exposed to the program so I would highly recommend this method for security reasons.

Then you can query all the Firefox Sync collections, as well as the other information endpoints that are available.

The main things that are missing are:

  • OAuth token refresh: while this library will automatically refresh the Firefox Sync token that expires every hour, it doesn't refresh the OAuth token that expires every day.
  • Write methods: it's currently a read-only library and doesn't support creating, updating or deleting entries. That said the encryption method is already implemented so it's just a matter of calling the right endpoint.

Feel free to open a pull request to add those if you need them!

Installation

npm install firefox-sync

Usage

const Sync = require('firefox-sync')
const sync = Sync(options)

The options object can contain:

| Name | Description | |--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------| | credsFile | Manage authentication state in the given file. Useful if you want to keep state between multiple command invocations (e.g. a CLI). | | clientId | OAuth client ID. Defaults to the Android app's one for convenience. | | scope | OAuth scope to access Firefox Sync, you probably don't want to change the default as it's currently the only scope that gives access to Sync data. | | authServerUrl | Only used for password authentication, Firefox Accounts API endpoint, in case you want to use a custom server. | | authorizationUrl | OAuth authorization URL. Default from the OpenID configuration. | | tokenEndpoint | OAuth token endpoint. Default from the OpenID configuration. | | tokenServerUrl | TokenServer URL, in case you want to use a custom server. | | oauthOptions | Extra OAuth parameters. You'll mainly want to use this to pass access_type: 'offline' to get a refresh token. |

Authentication

Email and password

Warning: while this is probably the easiest method to sign in, it gives access to the program to the plaintext password. Even though Mozilla's authentication mechanism never sends the password over the network (on top of being TLS encrypted), it's still going to be stored in RAM and JavaScript doesn't give us a way to reliably wipe it after authenticating. Keep that in mind when evaluating your threat model.

const creds = await sync.auth.password('[email protected]', 'The password goes here!')

const creds = await sync.auth.password('[email protected]', 'The password goes here!', {
  authServerUrl: 'https://your.custom.url/'
})
{
  "oauthToken": {
    "access_token": "32 bytes of hex",
    "token_type": "bearer",
    "scope": "https://identity.mozilla.com/apps/oldsync",
    "expires_in": 86399,
    "auth_at": 1634346661,
    "refresh_token": "32 bytes of hex"
  },
  "syncKeyBundle": {
    "encryptionKey": "32 bytes of Base64",
    "hmacKey": "32 bytes of Base64",
    "kid": "A timestamp and 16 bytes of Base64URL"
  },
  "token": {
    "id": "A bunch of Base64URL",
    "key": "32 bytes of Base64URL",
    "uid": 999999999,
    "api_endpoint": "https://sync-1-us-west1-g.sync.services.mozilla.com/1.5/999999999",
    "duration": 3600,
    "hashalg": "sha256",
    "hashed_fxa_uid": "16 bytes of hex",
    "node_type": "spanner"
  },
  "tokenIssuedAt": 1634346661940
}

OAuth

First, we issue a OAuth challenge that the user needs to open in a browser.

If you don't have your own OAuth client ID with a properly configured redirect URL for your application, you can use a public client ID like the one of the Android app, but you'll need to use web debugging tools to retrieve the OAuth response code, so this will only work for testing purpose.

const challenge = await sync.auth.oauth.challenge()

const challenge = await sync.auth.oauth.challenge({
  oauthOptions: {
    access_type: 'offline'
  }
})
{
  "keyPair": "`KeyPairKeyObjectResult` for internal use",
  "state": "16 bytes of Base64URL",
  "codeVerifier": "32 bytes of Base64URL",
  "url": "https://accounts.firefox.com/authorization?all-the-challenge-params-go-here"
}

Upon successful authentication, the user is redirected to the configured URL that will include a code and state query string parameters that you need to pass to the auth.oauth.complete function.

const result = {
  code: '32 bytes of hex',
  state: '16 bytes of Base64URL (ideally the same as the challenge)'
}

const creds = await sync.auth.oauth.complete(challenge, result)

const creds = await sync.auth.oauth.complete(challenge, result, {
  tokenEndpoint: 'https://your.custom.url/token'
})

Same output as auth.password.

Collections

getCollections

Returns an object mapping collection names associated with the account to the last modified time for each collection.

const collections = await sync.getCollections()
{
  "passwords": 1634346661.94,
  "bookmarks": 1634346661.94,
  "crypto": 1634346661.94,
  "prefs": 1634346661.94,
  "meta": 1634346661.94,
  "addons": 1634346661.94,
  "tabs": 1634346661.94,
  "clients": 1634346661.94,
  "history": 1634346661.94,
  "forms": 1634346661.94
}

getCollection

By default only the BSO IDs are returned, but full objects can be requested using the full parameter. If the collection does not exist, an empty list is returned.

const items = await sync.getCollection('bookmarks')
[
  "foo",
  "bar",
  "baz"
]
const items = await sync.getCollection('bookmarks', { full: true })
const items = await sync.getCollection('bookmarks', { full: true, ids: ['foo', 'bar'] })
[
  {
    "bso": {
      "id": "foo",
      "modified": 1634346661.94,
      "payload": "{\"encrypted\":\"stuff\"}"
    },
    "payload": {
      "decrypted": "stuff"
    }
  }
]

getCollectionItem

Returns the BSO in the collection corresponding to the requested ID.

const item = await sync.getCollectionItem('bookmarks', 'foo')
{
  "bso": {
    "id": "foo",
    "modified": 1634346661.94,
    "payload": "{\"encrypted\":\"stuff\"}"
  },
  "payload": {
    "decrypted": "stuff"
  }
}

Information

There's a number of endpoints that return some information about this Firefox Sync instance.

getQuota

Returns a two-item list giving the user’s current usage and quota (in kB). The second item will be null if the server does not enforce quotas.

const quota = await sync.getQuota()
[
  69.133742,
  null
]

getCollectionUsage

Returns an object mapping collection names associated with the account to the data volume used for each collection (in kB).

const usage = await sync.getCollectionUsage()
{
  "addons": 0.7588336369,
  "crypto": 0.5156744894,
  "forms": 0.3097969336,
  "tabs": 0.2830539361,
  "bookmarks": 0.6618207313,
  "clients": 0.9727294557,
  "prefs": 0.3751385437,
  "meta": 0.6064291011,
  "passwords": 0.7713613800,
  "history": 0.9888805912
}

getCollectionCounts

Returns an object mapping collection names associated with the account to the total number of items in each collection.

const usage = await sync.getCollectionCounts()
{
  "history": 69,
  "addons": 1,
  "forms": 42,
  "meta": 1,
  "bookmarks": 1337,
  "tabs": 1,
  "prefs": 1,
  "crypto": 1,
  "passwords": 420,
  "clients": 1
}

getConfiguration

Provides information about the configuration of this storage server with respect to various protocol and size limits.

const usage = await sync.getConfiguration()
{
  "max_post_bytes": 2097152,
  "max_post_records": 100,
  "max_record_payload_bytes": 2097152,
  "max_request_bytes": 2101248,
  "max_total_bytes": 100000000,
  "max_total_records": 1664,
  "max_quota_limit": 2097152000
}

See also

Firefox Sync CLI, a command line interface to access your Sync data.

The story on how this all started when I tried to access my Lockwise passwords from the CLI:

  1. A journey to scripting Firefox Sync / Lockwise: existing clients
  2. A journey to scripting Firefox Sync / Lockwise: figuring the protocol
  3. A journey to scripting Firefox Sync / Lockwise: understanding BrowserID
  4. A journey to scripting Firefox Sync / Lockwise: hybrid OAuth
  5. A journey to scripting Firefox Sync / Lockwise: complete OAuth