{"action":"created","comment":{"url":"https://api.github.com/repos/mozilla/fxa-content-server/pulls/comments/225643520","pull_request_review_id":165287292,"id":225643520,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyNTY0MzUyMA==","diff_hunk":"@@ -0,0 +1,294 @@\n+/* This Source Code Form is subject to the terms of the Mozilla Public\n+ * License, v. 2.0. If a copy of the MPL was not distributed with this\n+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n+\n+import a256gcm from './crypto/a256gcm';\n+import base64url from 'base64url';\n+import ChannelServerClientErrors from './channel-server-client-errors';\n+import { DEVICE_PAIRING_CHANNEL_KEY_BYTES } from './constants';\n+import { Model } from 'backbone';\n+import { pick } from 'underscore';\n+import hkdf from './crypto/hkdf';\n+\n+const CHANNEL_KEY_INFO_BUFFER = Buffer.from('identity.mozilla.com/picl/v1/pair/encryption-key', 'utf8');\n+const CONFIRMATION_CODE_INFO_BUFFER = Buffer.from('identity.mozilla.com/picl/v1/pair/confirmation-code', 'utf8');\n+\n+const CONFIRMATION_CODE_LENGTH = 4;\n+\n+\n+/**\n+ * Connects to the channel server. Expects the following\n+ * attributes to be passed into the constructor:\n+ *  - channelId\n+ *  - channelKey\n+ *  - channelServerUrl\n+ *\n+ * @export\n+ * @class ChannelServerClient\n+ * @extends {Model}\n+ */\n+export default class ChannelServerClient extends Model {\n+  constructor (attrs = {}, options = {}) {\n+    super(attrs, options);\n+\n+    this._notifier = options.notifier;\n+\n+    this.set('isConnected', false);\n+  }\n+\n+  /**\n+   * Open a socket to `channelId`\n+   *\n+   * @param {string} [channelId=this.get('channelId')]\n+   * @returns {Promise} resolves when connected\n+   */\n+  open (channelId = this.get('channelId')) {\n+    return new Promise((resolve, reject) => {\n+      if (this.socket) {\n+        // to avoid opening a duplicate connection, say the client is connected\n+        // if a socket exists but isn't yet connected.\n+        return reject(ChannelServerClientErrors.toError('ALREADY_CONNECTED'));\n+      }\n+\n+      const url = `${this.get('channelServerUrl')}${channelId}`;\n+      this.socket = this._createSocket(url);\n+\n+      this._proxySocketEvents();\n+\n+      /*eslint-disable no-use-before-define*/\n+      const close = () => {\n+        stopListeningToChannelSetupEvents();\n+        reject(ChannelServerClientErrors.toError('COULD_NOT_CONNECT'));\n+      };\n+\n+      const error = () => {\n+        stopListeningToChannelSetupEvents();\n+        reject(ChannelServerClientErrors.toError('COULD_NOT_CONNECT'));\n+      };\n+\n+      const message = (event) => {\n+        stopListeningToChannelSetupEvents();\n+\n+        // The first event is always the channel identifier, we already know what\n+        // that is so the message can be ignored.\n+        this.set('isConnected', true);\n+\n+        this.on('message', (event) => this._encryptedMessageHandler(event));\n+        resolve();\n+      };\n+      /*eslint-enable no-use-before-define*/\n+\n+      const stopListeningToChannelSetupEvents = () => {\n+        this.off('close', close);\n+        this.off('error', error);\n+        this.off('message', message);\n+      };\n+\n+      this.on('close', close);\n+      this.on('error', error);\n+      this.once('message', message);\n+    });\n+  }\n+\n+  /**\n+   * Close the channel.\n+   *\n+   * @returns {Promise} - rejects if no socket, resolves when connection closed\n+   */\n+  close () {\n+    return new Promise((resolve, reject) => {\n+      if (! this.socket) {\n+        return reject(ChannelServerClientErrors.toError('NOT_CONNECTED'));\n+      }\n+\n+      /*eslint-disable no-use-before-define*/\n+      const stopListeningToChannelTeardownEvents = () => {\n+        this.off('close', close);\n+        this.off('error', error);\n+      };\n+\n+      const close = () => {\n+        stopListeningToChannelTeardownEvents();\n+        this.set('isConnected', false);\n+        resolve();\n+      };\n+\n+      const error = (err) => {\n+        stopListeningToChannelTeardownEvents();\n+        reject(err);\n+      };\n+      /*eslint-enable no-use-before-define*/\n+\n+      this.on('close', close);\n+      this.on('error', error);\n+\n+      this.socket.close();\n+    });\n+  }\n+\n+  /**\n+   * Send `message` with `data` to the remote.\n+   *\n+   * @param {String} message name of message to send\n+   * @param {Any} [data={}]\n+   * @returns {Promise} resolves when complete\n+   */\n+  send (message, data = {}) {\n+    if (! this.socket || ! this.get('isConnected')) {\n+      return Promise.reject(ChannelServerClientErrors.toError('NOT_CONNECTED'));\n+    }\n+\n+    const envelope = {\n+      data,\n+      message,\n+    };\n+\n+    return this._encrypt(envelope)\n+      .then(ciphertext => this.socket.send(ciphertext));\n+  }\n+\n+  /**\n+   * Proxy `socket` events as if they were from\n+   * the client itself.\n+   *\n+   * @param {WebSocket} socket\n+   * @private\n+   */\n+  _proxySocketEvents (socket) {\n+    ['close', 'error', 'message', 'open'].forEach(eventName => {\n+      socket.addEventListener(eventName, (event) => this.trigger(eventName, event));\n+    });\n+  }\n+\n+  /**\n+   * Handle an encrypted message. Expects a message with the\n+   * following format:\n+   *\n+   * ```\n+   * {\n+   *   \"message\": <encrypted message>,\n+   *   \"sender\": {\n+   *      \"city\": <sender's city name>,\n+   *      \"country\": <sender's country name>,\n+   *      \"region\": <sender's region>,\n+   *      \"ua\": <sender's user agent string>\n+   *   }\n+   * }\n+   * ```\n+   *\n+   * The encrypted message is expected to have the following format:\n+   *\n+   * ```\n+   * {\n+   *   \"message\": <message name>,\n+   *   \"data\": <any, specific to the message>\n+   * }\n+   *\n+   * @param {Object} event\n+   * @private\n+   */\n+  _encryptedMessageHandler (event) {\n+    const { message: ciphertext, sender } = JSON.parse(event.data);\n+    this._decrypt(ciphertext).then(decrypted => {\n+      const { data, message } = decrypted;\n+\n+      data.channelServerClient = this;\n+      data.remoteMetaData = pick(sender, 'city', 'country', 'region', 'ua');\n+\n+      this.trigger(message, data);\n+      this._notifier.trigger(message, data);\n+    });\n+  }\n+\n+  /**\n+   * Create a WebSocket to `url`\n+   *\n+   * @param {String} url\n+   * @returns {WebSocket}\n+   * @private\n+   */\n+  _createSocket (url) {\n+    return new WebSocket(url);\n+  }\n+\n+  /**\n+   * Decrypt `ciphertext` using the stored JWK\n+   *\n+   * @param {String} ciphertext\n+   * @returns {Promise} resolves to an object when complete\n+   * @private\n+   */\n+  _decrypt (ciphertext) {\n+    return this._getChannelJWK()\n+      .then(keysJwk => a256gcm.decrypt(ciphertext, keysJwk))\n+      .then(result => JSON.parse(result));\n+  }\n+\n+  /**\n+   * Encrypt `envelope` using the stored JWK\n+   *\n+   * @param {Object} envelope\n+   * @returns {Promise} Resolves to a string when complete\n+   * @private\n+   */\n+  _encrypt (envelope) {\n+    return this._getChannelJWK()\n+      .then(keysJwk => a256gcm.encrypt(JSON.stringify(envelope), keysJwk));\n+  }\n+\n+  /**\n+   * Get the encryption/decryption JWK for the channel\n+   *\n+   * @returns {Promise} resolves to the JWK when complete\n+   * @private\n+   */\n+  _getChannelJWK () {\n+    const { channelId, channelJWK, channelKey } = this.toJSON();\n+    if (channelJWK) {\n+      return Promise.resolve(channelJWK);\n+    }\n+\n+    const channelKeyBuffer = Buffer.from(base64url.decode(channelKey), 'utf8');\n+    const channelIdBuffer = Buffer.from(channelId, 'utf8');\n+\n+    return Promise.all([\n+      this._deriveChannelJWK(channelKeyBuffer, channelIdBuffer),\n+      this._deriveConfirmationCode(channelKeyBuffer, channelIdBuffer),\n+    ]).then(([channelJWK, confirmationCode]) => {\n+      this.set({\n+        channelJWK,\n+        confirmationCode,\n+      });\n+      return channelJWK;\n+    });\n+  }\n+\n+  /**\n+   * Use `channelKeyBuffer` and `channelIdBuffer` to derive\n+   * the encryption/decryption JWK for the channel\n+   *\n+   * @param {Buffer} channelKeyBuffer\n+   * @param {Buffer} channelIdBuffer\n+   * @returns {Promise} resolves to the JWK when complete\n+   * @private\n+   */\n+  _deriveChannelJWK(channelKeyBuffer, channelIdBuffer) {\n+    return hkdf(channelKeyBuffer, channelIdBuffer, CHANNEL_KEY_INFO_BUFFER, DEVICE_PAIRING_CHANNEL_KEY_BYTES)","path":"app/scripts/lib/channel-server-client.js","position":276,"original_position":276,"commit_id":"e9ca8929a32aa7c77003f8023dd2858d4b562505","original_commit_id":"e9ca8929a32aa7c77003f8023dd2858d4b562505","user":{"login":"eoger","id":6424575,"node_id":"MDQ6VXNlcjY0MjQ1NzU=","avatar_url":"https://avatars1.githubusercontent.com/u/6424575?v=4","gravatar_id":"","url":"https://api.github.com/users/eoger","html_url":"https://github.com/eoger","followers_url":"https://api.github.com/users/eoger/followers","following_url":"https://api.github.com/users/eoger/following{/other_user}","gists_url":"https://api.github.com/users/eoger/gists{/gist_id}","starred_url":"https://api.github.com/users/eoger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eoger/subscriptions","organizations_url":"https://api.github.com/users/eoger/orgs","repos_url":"https://api.github.com/users/eoger/repos","events_url":"https://api.github.com/users/eoger/events{/privacy}","received_events_url":"https://api.github.com/users/eoger/received_events","type":"User","site_admin":false},"body":"LGTM","created_at":"2018-10-16T17:48:14Z","updated_at":"2018-10-16T17:48:14Z","html_url":"https://github.com/mozilla/fxa-content-server/pull/6641#discussion_r225643520","pull_request_url":"https://api.github.com/repos/mozilla/fxa-content-server/pulls/6641","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/mozilla/fxa-content-server/pulls/comments/225643520"},"html":{"href":"https://github.com/mozilla/fxa-content-server/pull/6641#discussion_r225643520"},"pull_request":{"href":"https://api.github.com/repos/mozilla/fxa-content-server/pulls/6641"}},"in_reply_to_id":225640459},"pull_request":{"url":"https://api.github.com/repos/mozilla/fxa-content-server/pulls/6641","id":223324320,"node_id":"MDExOlB1bGxSZXF1ZXN0MjIzMzI0MzIw","html_url":"https://github.com/mozilla/fxa-content-server/pull/6641","diff_url":"https://github.com/mozilla/fxa-content-server/pull/6641.diff","patch_url":"https://github.com/mozilla/fxa-content-server/pull/6641.patch","issue_url":"https://api.github.com/repos/mozilla/fxa-content-server/issues/6641","number":6641,"state":"open","locked":false,"title":"feat(pairing): The channel server for the pairing flow.","user":{"login":"shane-tomlinson","id":848085,"node_id":"MDQ6VXNlcjg0ODA4NQ==","avatar_url":"https://avatars3.githubusercontent.com/u/848085?v=4","gravatar_id":"","url":"https://api.github.com/users/shane-tomlinson","html_url":"https://github.com/shane-tomlinson","followers_url":"https://api.github.com/users/shane-tomlinson/followers","following_url":"https://api.github.com/users/shane-tomlinson/following{/other_user}","gists_url":"https://api.github.com/users/shane-tomlinson/gists{/gist_id}","starred_url":"https://api.github.com/users/shane-tomlinson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shane-tomlinson/subscriptions","organizations_url":"https://api.github.com/users/shane-tomlinson/orgs","repos_url":"https://api.github.com/users/shane-tomlinson/repos","events_url":"https://api.github.com/users/shane-tomlinson/events{/privacy}","received_events_url":"https://api.github.com/users/shane-tomlinson/received_events","type":"User","site_admin":false},"body":"fixes #6613","created_at":"2018-10-16T16:56:54Z","updated_at":"2018-10-16T17:48:14Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2eab408af799cc81ee90f28cc26782a1bd3d2dd5","assignee":{"login":"shane-tomlinson","id":848085,"node_id":"MDQ6VXNlcjg0ODA4NQ==","avatar_url":"https://avatars3.githubusercontent.com/u/848085?v=4","gravatar_id":"","url":"https://api.github.com/users/shane-tomlinson","html_url":"https://github.com/shane-tomlinson","followers_url":"https://api.github.com/users/shane-tomlinson/followers","following_url":"https://api.github.com/users/shane-tomlinson/following{/other_user}","gists_url":"https://api.github.com/users/shane-tomlinson/gists{/gist_id}","starred_url":"https://api.github.com/users/shane-tomlinson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shane-tomlinson/subscriptions","organizations_url":"https://api.github.com/users/shane-tomlinson/orgs","repos_url":"https://api.github.com/users/shane-tomlinson/repos","events_url":"https://api.github.com/users/shane-tomlinson/events{/privacy}","received_events_url":"https://api.github.com/users/shane-tomlinson/received_events","type":"User","site_admin":false},"assignees":[{"login":"shane-tomlinson","id":848085,"node_id":"MDQ6VXNlcjg0ODA4NQ==","avatar_url":"https://avatars3.githubusercontent.com/u/848085?v=4","gravatar_id":"","url":"https://api.github.com/users/shane-tomlinson","html_url":"https://github.com/shane-tomlinson","followers_url":"https://api.github.com/users/shane-tomlinson/followers","following_url":"https://api.github.com/users/shane-tomlinson/following{/other_user}","gists_url":"https://api.github.com/users/shane-tomlinson/gists{/gist_id}","starred_url":"https://api.github.com/users/shane-tomlinson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shane-tomlinson/subscriptions","organizations_url":"https://api.github.com/users/shane-tomlinson/orgs","repos_url":"https://api.github.com/users/shane-tomlinson/repos","events_url":"https://api.github.com/users/shane-tomlinson/events{/privacy}","received_events_url":"https://api.github.com/users/shane-tomlinson/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[{"name":"fxa-devs","id":2120958,"node_id":"MDQ6VGVhbTIxMjA5NTg=","slug":"fxa-devs","description":"fxa-devs review team","privacy":"closed","url":"https://api.github.com/teams/2120958","members_url":"https://api.github.com/teams/2120958/members{/member}","repositories_url":"https://api.github.com/teams/2120958/repos","permission":"pull"}],"labels":[{"id":346099260,"node_id":"MDU6TGFiZWwzNDYwOTkyNjA=","url":"https://api.github.com/repos/mozilla/fxa-content-server/labels/waffle:active","name":"waffle:active","color":"ededed","default":false}],"milestone":{"url":"https://api.github.com/repos/mozilla/fxa-content-server/milestones/144","html_url":"https://github.com/mozilla/fxa-content-server/milestone/144","labels_url":"https://api.github.com/repos/mozilla/fxa-content-server/milestones/144/labels","id":3532462,"node_id":"MDk6TWlsZXN0b25lMzUzMjQ2Mg==","number":144,"title":"FxA-156: Fenix Pairing flow","description":"https://docs.google.com/document/d/1_n8IQa7YBymnUE87sYORzc7j-zPwHt9EhOP7RQ1aJ5I/edit?ts=5b5a13de\r\nhttps://www.lucidchart.com/documents/edit/7bcd8869-0d90-443c-aa22-81262cef7e4d/0","creator":{"login":"shane-tomlinson","id":848085,"node_id":"MDQ6VXNlcjg0ODA4NQ==","avatar_url":"https://avatars3.githubusercontent.com/u/848085?v=4","gravatar_id":"","url":"https://api.github.com/users/shane-tomlinson","html_url":"https://github.com/shane-tomlinson","followers_url":"https://api.github.com/users/shane-tomlinson/followers","following_url":"https://api.github.com/users/shane-tomlinson/following{/other_user}","gists_url":"https://api.github.com/users/shane-tomlinson/gists{/gist_id}","starred_url":"https://api.github.com/users/shane-tomlinson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shane-tomlinson/subscriptions","organizations_url":"https://api.github.com/users/shane-tomlinson/orgs","repos_url":"https://api.github.com/users/shane-tomlinson/repos","events_url":"https://api.github.com/users/shane-tomlinson/events{/privacy}","received_events_url":"https://api.github.com/users/shane-tomlinson/received_events","type":"User","site_admin":false},"open_issues":10,"closed_issues":16,"state":"open","created_at":"2018-07-30T14:16:33Z","updated_at":"2018-10-16T16:57:02Z","due_on":"2018-09-30T07:00:00Z","closed_at":null},"commits_url":"https://api.github.com/repos/mozilla/fxa-content-server/pulls/6641/commits","review_comments_url":"https://api.github.com/repos/mozilla/fxa-content-server/pulls/6641/comments","review_comment_url":"https://api.github.com/repos/mozilla/fxa-content-server/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mozilla/fxa-content-server/issues/6641/comments","statuses_url":"https://api.github.com/repos/mozilla/fxa-content-server/statuses/e9ca8929a32aa7c77003f8023dd2858d4b562505","head":{"label":"mozilla:pairsona-extract-channel-server-client","ref":"pairsona-extract-channel-server-client","sha":"e9ca8929a32aa7c77003f8023dd2858d4b562505","user":{"login":"mozilla","id":131524,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzMTUyNA==","avatar_url":"https://avatars2.githubusercontent.com/u/131524?v=4","gravatar_id":"","url":"https://api.github.com/users/mozilla","html_url":"https://github.com/mozilla","followers_url":"https://api.github.com/users/mozilla/followers","following_url":"https://api.github.com/users/mozilla/following{/other_user}","gists_url":"https://api.github.com/users/mozilla/gists{/gist_id}","starred_url":"https://api.github.com/users/mozilla/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mozilla/subscriptions","organizations_url":"https://api.github.com/users/mozilla/orgs","repos_url":"https://api.github.com/users/mozilla/repos","events_url":"https://api.github.com/users/mozilla/events{/privacy}","received_events_url":"https://api.github.com/users/mozilla/received_events","type":"Organization","site_admin":false},"repo":{"id":11778024,"node_id":"MDEwOlJlcG9zaXRvcnkxMTc3ODAyNA==","name":"fxa-content-server","full_name":"mozilla/fxa-content-server","private":false,"owner":{"login":"mozilla","id":131524,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzMTUyNA==","avatar_url":"https://avatars2.githubusercontent.com/u/131524?v=4","gravatar_id":"","url":"https://api.github.com/users/mozilla","html_url":"https://github.com/mozilla","followers_url":"https://api.github.com/users/mozilla/followers","following_url":"https://api.github.com/users/mozilla/following{/other_user}","gists_url":"https://api.github.com/users/mozilla/gists{/gist_id}","starred_url":"https://api.github.com/users/mozilla/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mozilla/subscriptions","organizations_url":"https://api.github.com/users/mozilla/orgs","repos_url":"https://api.github.com/users/mozilla/repos","events_url":"https://api.github.com/users/mozilla/events{/privacy}","received_events_url":"https://api.github.com/users/mozilla/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/mozilla/fxa-content-server","description":"Firefox Accounts Content Server","fork":false,"url":"https://api.github.com/repos/mozilla/fxa-content-server","forks_url":"https://api.github.com/repos/mozilla/fxa-content-server/forks","keys_url":"https://api.github.com/repos/mozilla/fxa-content-server/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mozilla/fxa-content-server/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mozilla/fxa-content-server/teams","hooks_url":"https://api.github.com/repos/mozilla/fxa-content-server/hooks","issue_events_url":"https://api.github.com/repos/mozilla/fxa-content-server/issues/events{/number}","events_url":"https://api.github.com/repos/mozilla/fxa-content-server/events","assignees_url":"https://api.github.com/repos/mozilla/fxa-content-server/assignees{/user}","branches_url":"https://api.github.com/repos/mozilla/fxa-content-server/branches{/branch}","tags_url":"https://api.github.com/repos/mozilla/fxa-content-server/tags","blobs_url":"https://api.github.com/repos/mozilla/fxa-content-server/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mozilla/fxa-content-server/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mozilla/fxa-content-server/git/refs{/sha}","trees_url":"https://api.github.com/repos/mozilla/fxa-content-server/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mozilla/fxa-content-server/statuses/{sha}","languages_url":"https://api.github.com/repos/mozilla/fxa-content-server/languages","stargazers_url":"https://api.github.com/repos/mozilla/fxa-content-server/stargazers","contributors_url":"https://api.github.com/repos/mozilla/fxa-content-server/contributors","subscribers_url":"https://api.github.com/repos/mozilla/fxa-content-server/subscribers","subscription_url":"https://api.github.com/repos/mozilla/fxa-content-server/subscription","commits_url":"https://api.github.com/repos/mozilla/fxa-content-server/commits{/sha}","git_commits_url":"https://api.github.com/repos/mozilla/fxa-content-server/git/commits{/sha}","comments_url":"https://api.github.com/repos/mozilla/fxa-content-server/comments{/number}","issue_comment_url":"https://api.github.com/repos/mozilla/fxa-content-server/issues/comments{/number}","contents_url":"https://api.github.com/repos/mozilla/fxa-content-server/contents/{+path}","compare_url":"https://api.github.com/repos/mozilla/fxa-content-server/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mozilla/fxa-content-server/merges","archive_url":"https://api.github.com/repos/mozilla/fxa-content-server/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mozilla/fxa-content-server/downloads","issues_url":"https://api.github.com/repos/mozilla/fxa-content-server/issues{/number}","pulls_url":"https://api.github.com/repos/mozilla/fxa-content-server/pulls{/number}","milestones_url":"https://api.github.com/repos/mozilla/fxa-content-server/milestones{/number}","notifications_url":"https://api.github.com/repos/mozilla/fxa-content-server/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mozilla/fxa-content-server/labels{/name}","releases_url":"https://api.github.com/repos/mozilla/fxa-content-server/releases{/id}","deployments_url":"https://api.github.com/repos/mozilla/fxa-content-server/deployments","created_at":"2013-07-30T23:35:35Z","updated_at":"2018-10-16T16:56:05Z","pushed_at":"2018-10-16T17:36:20Z","git_url":"git://github.com/mozilla/fxa-content-server.git","ssh_url":"git@github.com:mozilla/fxa-content-server.git","clone_url":"https://github.com/mozilla/fxa-content-server.git","svn_url":"https://github.com/mozilla/fxa-content-server","homepage":"","size":22442,"stargazers_count":153,"watchers_count":153,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":126,"mirror_url":null,"archived":false,"open_issues_count":138,"license":{"key":"mpl-2.0","name":"Mozilla Public License 2.0","spdx_id":"MPL-2.0","url":"https://api.github.com/licenses/mpl-2.0","node_id":"MDc6TGljZW5zZTE0"},"forks":126,"open_issues":138,"watchers":153,"default_branch":"master"}},"base":{"label":"mozilla:master","ref":"master","sha":"7ae20e26da6ac109e4fbbbaa2f45591e8e62d035","user":{"login":"mozilla","id":131524,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzMTUyNA==","avatar_url":"https://avatars2.githubusercontent.com/u/131524?v=4","gravatar_id":"","url":"https://api.github.com/users/mozilla","html_url":"https://github.com/mozilla","followers_url":"https://api.github.com/users/mozilla/followers","following_url":"https://api.github.com/users/mozilla/following{/other_user}","gists_url":"https://api.github.com/users/mozilla/gists{/gist_id}","starred_url":"https://api.github.com/users/mozilla/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mozilla/subscriptions","organizations_url":"https://api.github.com/users/mozilla/orgs","repos_url":"https://api.github.com/users/mozilla/repos","events_url":"https://api.github.com/users/mozilla/events{/privacy}","received_events_url":"https://api.github.com/users/mozilla/received_events","type":"Organization","site_admin":false},"repo":{"id":11778024,"node_id":"MDEwOlJlcG9zaXRvcnkxMTc3ODAyNA==","name":"fxa-content-server","full_name":"mozilla/fxa-content-server","private":false,"owner":{"login":"mozilla","id":131524,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzMTUyNA==","avatar_url":"https://avatars2.githubusercontent.com/u/131524?v=4","gravatar_id":"","url":"https://api.github.com/users/mozilla","html_url":"https://github.com/mozilla","followers_url":"https://api.github.com/users/mozilla/followers","following_url":"https://api.github.com/users/mozilla/following{/other_user}","gists_url":"https://api.github.com/users/mozilla/gists{/gist_id}","starred_url":"https://api.github.com/users/mozilla/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mozilla/subscriptions","organizations_url":"https://api.github.com/users/mozilla/orgs","repos_url":"https://api.github.com/users/mozilla/repos","events_url":"https://api.github.com/users/mozilla/events{/privacy}","received_events_url":"https://api.github.com/users/mozilla/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/mozilla/fxa-content-server","description":"Firefox Accounts Content Server","fork":false,"url":"https://api.github.com/repos/mozilla/fxa-content-server","forks_url":"https://api.github.com/repos/mozilla/fxa-content-server/forks","keys_url":"https://api.github.com/repos/mozilla/fxa-content-server/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mozilla/fxa-content-server/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mozilla/fxa-content-server/teams","hooks_url":"https://api.github.com/repos/mozilla/fxa-content-server/hooks","issue_events_url":"https://api.github.com/repos/mozilla/fxa-content-server/issues/events{/number}","events_url":"https://api.github.com/repos/mozilla/fxa-content-server/events","assignees_url":"https://api.github.com/repos/mozilla/fxa-content-server/assignees{/user}","branches_url":"https://api.github.com/repos/mozilla/fxa-content-server/branches{/branch}","tags_url":"https://api.github.com/repos/mozilla/fxa-content-server/tags","blobs_url":"https://api.github.com/repos/mozilla/fxa-content-server/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mozilla/fxa-content-server/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mozilla/fxa-content-server/git/refs{/sha}","trees_url":"https://api.github.com/repos/mozilla/fxa-content-server/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mozilla/fxa-content-server/statuses/{sha}","languages_url":"https://api.github.com/repos/mozilla/fxa-content-server/languages","stargazers_url":"https://api.github.com/repos/mozilla/fxa-content-server/stargazers","contributors_url":"https://api.github.com/repos/mozilla/fxa-content-server/contributors","subscribers_url":"https://api.github.com/repos/mozilla/fxa-content-server/subscribers","subscription_url":"https://api.github.com/repos/mozilla/fxa-content-server/subscription","commits_url":"https://api.github.com/repos/mozilla/fxa-content-server/commits{/sha}","git_commits_url":"https://api.github.com/repos/mozilla/fxa-content-server/git/commits{/sha}","comments_url":"https://api.github.com/repos/mozilla/fxa-content-server/comments{/number}","issue_comment_url":"https://api.github.com/repos/mozilla/fxa-content-server/issues/comments{/number}","contents_url":"https://api.github.com/repos/mozilla/fxa-content-server/contents/{+path}","compare_url":"https://api.github.com/repos/mozilla/fxa-content-server/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mozilla/fxa-content-server/merges","archive_url":"https://api.github.com/repos/mozilla/fxa-content-server/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mozilla/fxa-content-server/downloads","issues_url":"https://api.github.com/repos/mozilla/fxa-content-server/issues{/number}","pulls_url":"https://api.github.com/repos/mozilla/fxa-content-server/pulls{/number}","milestones_url":"https://api.github.com/repos/mozilla/fxa-content-server/milestones{/number}","notifications_url":"https://api.github.com/repos/mozilla/fxa-content-server/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mozilla/fxa-content-server/labels{/name}","releases_url":"https://api.github.com/repos/mozilla/fxa-content-server/releases{/id}","deployments_url":"https://api.github.com/repos/mozilla/fxa-content-server/deployments","created_at":"2013-07-30T23:35:35Z","updated_at":"2018-10-16T16:56:05Z","pushed_at":"2018-10-16T17:36:20Z","git_url":"git://github.com/mozilla/fxa-content-server.git","ssh_url":"git@github.com:mozilla/fxa-content-server.git","clone_url":"https://github.com/mozilla/fxa-content-server.git","svn_url":"https://github.com/mozilla/fxa-content-server","homepage":"","size":22442,"stargazers_count":153,"watchers_count":153,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":126,"mirror_url":null,"archived":false,"open_issues_count":138,"license":{"key":"mpl-2.0","name":"Mozilla Public License 2.0","spdx_id":"MPL-2.0","url":"https://api.github.com/licenses/mpl-2.0","node_id":"MDc6TGljZW5zZTE0"},"forks":126,"open_issues":138,"watchers":153,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/mozilla/fxa-content-server/pulls/6641"},"html":{"href":"https://github.com/mozilla/fxa-content-server/pull/6641"},"issue":{"href":"https://api.github.com/repos/mozilla/fxa-content-server/issues/6641"},"comments":{"href":"https://api.github.com/repos/mozilla/fxa-content-server/issues/6641/comments"},"review_comments":{"href":"https://api.github.com/repos/mozilla/fxa-content-server/pulls/6641/comments"},"review_comment":{"href":"https://api.github.com/repos/mozilla/fxa-content-server/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mozilla/fxa-content-server/pulls/6641/commits"},"statuses":{"href":"https://api.github.com/repos/mozilla/fxa-content-server/statuses/e9ca8929a32aa7c77003f8023dd2858d4b562505"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"eoger"}}