{"action":"created","comment":{"url":"https://api.github.com/repos/ampproject/amphtml/pulls/comments/206665611","pull_request_review_id":142120735,"id":206665611,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIwNjY2NTYxMQ==","diff_hunk":"@@ -0,0 +1,626 @@\n+/**\n+ * Copyright 2015 The AMP HTML Authors. All Rights Reserved.\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at\n+ *\n+ *      http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS-IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+import {Services} from '../services';\n+import {dev, user} from '../log';\n+import {dict, map} from './object';\n+import {fromIterator} from './array';\n+import {getCorsUrl, getSourceOrigin, getWinOrigin, isProxyOrigin, parseUrlDeprecated} from '../url';\n+import {getMode} from '../mode';\n+import {isArray, isObject} from '../types';\n+import {isFormDataWrapper} from '../form-data-wrapper';\n+import {parseJson} from '../json';\n+import {utf8Encode} from './bytes';\n+\n+/** @private @const {!Array<string>} */\n+const allowedMethods_ = ['GET', 'POST'];\n+\n+/** @private @const {string} */\n+const ALLOW_SOURCE_ORIGIN_HEADER = 'AMP-Access-Control-Allow-Source-Origin';\n+\n+/**\n+ * The \"init\" argument of the Fetch API. Currently, only \"credentials: include\"\n+ * is implemented.  Note ampCors with explicit false indicates that\n+ * __amp_source_origin should not be appended to the URL to allow for\n+ * potential caching or response across pages.\n+ *\n+ * See https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch\n+ *\n+ * @typedef {{\n+ *   body: (!Object|!Array|undefined|string),\n+ *   credentials: (string|undefined),\n+ *   headers: (!JsonObject|undefined),\n+ *   method: (string|undefined),\n+ *   requireAmpResponseSourceOrigin: (boolean|undefined),\n+ *   ampCors: (boolean|undefined)\n+ * }}\n+ */\n+export let FetchInitDef;\n+\n+/**\n+ * Serializes a fetch request so that it can be passed to `postMessage()`,\n+ * i.e., can be cloned using the\n+ * [structured clone algorithm](http://mdn.io/Structured_clone_algorithm).\n+ *\n+ * The request is serialized in the following way:\n+ *\n+ * 1. If the `init.body` is a `FormData`, set content-type header to\n+ * `multipart/form-data` and transform `init.body` into an\n+ * `!Array<!Array<string>>` holding the list of form entries, where each\n+ * element in the array is a form entry (key-value pair) represented as a\n+ * 2-element array.\n+ *\n+ * 2. Return a new object having properties `input` and the transformed\n+ * `init`.\n+ *\n+ * The serialized request is assumed to be de-serialized in the following way:\n+ *\n+ * 1.If content-type header starts with `multipart/form-data`\n+ * (case-insensitive), transform the entry array in `init.body` into a\n+ * `FormData` object.\n+ *\n+ * 2. Pass `input` and transformed `init` to `fetch` (or the constructor of\n+ * `Request`).\n+ *\n+ * Currently only `FormData` used in `init.body` is handled as it's the only\n+ * type being used in AMP runtime that needs serialization. The `Headers` type\n+ * also needs serialization, but callers should not be passing `Headers`\n+ * object in `init`, as that fails `fetchPolyfill` on browsers that don't\n+ * support fetch. Some serialization-needing types for `init.body` such as\n+ * `ArrayBuffer` and `Blob` are already supported by the structured clone\n+ * algorithm. Other serialization-needing types such as `URLSearchParams`\n+ * (which is not supported in IE and Safari) and `FederatedCredentials` are\n+ * not used in AMP runtime.\n+ *\n+ * @param {string} input The URL of the XHR to convert to structured\n+ *     cloneable.\n+ * @param {!FetchInitDef} init The options of the XHR to convert to structured\n+ *     cloneable.\n+ * @return {{input: string, init: !FetchInitDef}} The serialized structurally-\n+ *     cloneable request.\n+ * @private\n+ */\n+export function toStructuredCloneable(input, init) {\n+  const newInit = Object.assign({}, init);\n+  if (isFormDataWrapper(init.body)) {\n+    newInit.headers = newInit.headers || {};\n+    newInit.headers['Content-Type'] = 'multipart/form-data;charset=utf-8';\n+    newInit.body = fromIterator(init.body.entries());\n+  }\n+  return {input, init: newInit};\n+}\n+\n+/**\n+ * De-serializes a fetch response that was made possible to be passed to\n+ * `postMessage()`, i.e., can be cloned using the\n+ * [structured clone algorithm](http://mdn.io/Structured_clone_algorithm).\n+ *\n+ * The response is assumed to be serialized in the following way:\n+ *\n+ * 1. Transform the entries in the headers of the response into an\n+ * `!Array<!Array<string>>` holding the list of header entries, where each\n+ * element in the array is a header entry (key-value pair) represented as a\n+ * 2-element array. The header key is case-insensitive.\n+ *\n+ * 2. Include the header entry list and `status` and `statusText` properties\n+ * of the response in as `headers`, `status` and `statusText` properties of\n+ * `init`.\n+ *\n+ * 3. Include the body of the response serialized as string in `body`.\n+ *\n+ * 4. Return a new object having properties `body` and `init`.\n+ *\n+ * The response is de-serialized in the following way:\n+ *\n+ * 1. If the `Response` type is supported and `responseType` is not\n+ * document, pass `body` and `init` directly to the constructor of `Response`.\n+ *\n+ * 2. Otherwise, populate a fake XHR object to pass to `FetchResponse` as if\n+ * the response is returned by the fetch polyfill.\n+ *\n+ * 3. If `responseType` is `document`, also parse the body and populate\n+ * `responseXML` as a `Document` type.\n+ *\n+ * @param {JsonObject|string|undefined} response The structurally-cloneable\n+ *     response to convert back to a regular Response.\n+ * @param {string|undefined} responseType The original response type used to\n+ *     initiate the XHR.\n+ * @return {!FetchResponse|!Response} The deserialized regular response.\n+ * @private\n+ */\n+export function fromStructuredCloneable(response, responseType) {\n+  user().assert(isObject(response), 'Object expected: %s', response);\n+\n+  const isDocumentType = responseType == 'document';\n+  if (typeof Response === 'function' && !isDocumentType) {\n+    // Use native `Response` type if available for performance. If response\n+    // type is `document`, we must fall back to `FetchResponse` polyfill\n+    // because callers would then rely on the `responseXML` property being\n+    // present, which is not supported by the Response type.\n+    return new Response(response['body'], response['init']);\n+  }\n+\n+  const lowercasedHeaders = map();\n+  const data = {\n+    status: 200,\n+    statusText: 'OK',\n+    responseText: (response['body'] ? String(response['body']) : ''),\n+    /**\n+     * @param {string} name\n+     * @return {string}\n+     */\n+    getResponseHeader(name) {\n+      return lowercasedHeaders[String(name).toLowerCase()] || null;\n+    },\n+  };\n+\n+  if (response['init']) {\n+    const init = response['init'];\n+    if (isArray(init.headers)) {\n+      init.headers.forEach(entry => {\n+        const headerName = entry[0];\n+        const headerValue = entry[1];\n+        lowercasedHeaders[String(headerName).toLowerCase()] =\n+            String(headerValue);\n+      });\n+    }\n+    if (init.status) {\n+      data.status = parseInt(init.status, 10);\n+    }\n+    if (init.statusText) {\n+      data.statusText = String(init.statusText);\n+    }\n+  }\n+\n+  if (isDocumentType) {\n+    // TODO(prateekbh): remove this when integrating document fetcher.\n+    data.responseXML =\n+        new DOMParser().parseFromString(data.responseText, 'text/html');\n+  }\n+\n+  return new FetchResponse(data);\n+}\n+\n+/**\n+ * Intercepts the XHR and proxies it through the viewer if necessary.\n+ *\n+ * XHRs are intercepted if all of the following are true:\n+ * - The AMP doc is in single doc mode\n+ * - The requested resource is not a 1p request.\n+ * - The viewer has the `xhrInterceptor` capability\n+ * - The Viewer is a trusted viewer or AMP is currently in developement mode\n+ * - The AMP doc is opted-in for XHR interception (`<html>` tag has\n+ *   `allow-xhr-interception` attribute)\n+ *\n+ * @param {!Window} win\n+ * @param {?../service/ampdoc-impl.AmpDoc} ampdocSingle\n+ * @param {string} input The URL of the XHR which may get intercepted.\n+ * @param {!FetchInitDef} init The options of the XHR which may get\n+ *     intercepted.\n+ * @return {!Promise<!FetchResponse|!Response|undefined>}\n+ *     A response returned by the interceptor if XHR is intercepted or\n+ *     `Promise<undefined>` otherwise.\n+ * @private\n+ */\n+export function getViewerInterceptResponse(win, ampdocSingle, input, init) {\n+  if (!ampdocSingle) {\n+    return Promise.resolve();\n+  }\n+  const viewer = Services.viewerForDoc(ampdocSingle);\n+  const whenFirstVisible = viewer.whenFirstVisible();\n+  if (isProxyOrigin(input) || !viewer.hasCapability('xhrInterceptor')) {\n+    return whenFirstVisible;\n+  }\n+  const htmlElement = ampdocSingle.getRootNode().documentElement;\n+  const docOptedIn = htmlElement.hasAttribute('allow-xhr-interception');\n+  if (!docOptedIn) {\n+    return whenFirstVisible;\n+  }\n+  return whenFirstVisible.then(() => {\n+    return viewer.isTrustedViewer();\n+  }).then(viewerTrusted => {\n+    const isDevMode = getMode(win).development;\n+    if (!viewerTrusted && !isDevMode) {\n+      return;\n+    }\n+    const messagePayload = dict({\n+      'originalRequest': toStructuredCloneable(input, init),\n+    });\n+    return viewer.sendMessageAwaitResponse('xhr', messagePayload)\n+        .then(response =>\n+          fromStructuredCloneable(response, init.responseType));\n+  });\n+}\n+\n+/**\n+ * Setsup URL based on ampCors\n+ * @param {!Window} win\n+ * @param {string} input\n+ * @param {!FetchInitDef} init The options of the XHR which may get\n+ * intercepted.\n+ */\n+export function setupInput(win, input, init) {\n+  dev().assert(typeof input == 'string', 'Only URL supported: %s', input);\n+  if (init.ampCors !== false) {\n+    input = getCorsUrl(win, input);\n+  }\n+  return input;\n+}\n+\n+/**\n+ * Sets up and normalizes the FetchInitDef\n+ *\n+ * @param {?FetchInitDef=} opt_init Fetch options object.\n+ * @param {string=} opt_accept The HTTP Accept header value.\n+ * @return {!FetchInitDef}\n+ */\n+export function setupInit(opt_init, opt_accept) {\n+  const init = opt_init || {};\n+\n+  // In particular, Firefox does not tolerate `null` values for\n+  // `credentials`.\n+  const creds = init.credentials;\n+  dev().assert(\n+      creds === undefined || creds == 'include' || creds == 'omit',\n+      'Only credentials=include|omit support: %s', creds);\n+\n+  init.method = normalizeMethod_(init.method);\n+  init.headers = init.headers || dict({});\n+  if (opt_accept) {\n+    init.headers['Accept'] = opt_accept;\n+  }\n+\n+  return init;\n+}\n+\n+/**\n+ *\n+ * Sets up AMPSpecific CORS headers.\n+ * @param {!Window} win\n+ * @param {string} input\n+ * @param {?FetchInitDef=} init\n+ * @return {!FetchInitDef}\n+ */\n+export function setupAMPCors(win, input, init) {\n+  // Do not append __amp_source_origin if explicitly disabled.\n+  if (init.ampCors === false) {\n+    init.requireAmpResponseSourceOrigin = false;\n+  }\n+  if (init.requireAmpResponseSourceOrigin === true) {\n+    dev().error('XHR',\n+        'requireAmpResponseSourceOrigin is deprecated, use ampCors instead');\n+  }\n+  if (init.requireAmpResponseSourceOrigin === undefined) {\n+    init.requireAmpResponseSourceOrigin = true;\n+  }\n+  // For some same origin requests, add AMP-Same-Origin: true header to allow\n+  // publishers to validate that this request came from their own origin.\n+  const currentOrigin = getWinOrigin(win);\n+  const targetOrigin = parseUrlDeprecated(input).origin;\n+  if (currentOrigin == targetOrigin) {\n+    init['headers'] = init['headers'] || {};\n+    init['headers']['AMP-Same-Origin'] = 'true';\n+  }\n+  // In edge a `TypeMismatchError` is thrown when body is set to null.\n+  dev().assert(init.body !== null, 'fetch `body` can not be `null`');","path":"src/utils/xhr-utils.js","position":318,"original_position":318,"commit_id":"57ead6665c0af9254df58460876ffbf176d885d1","original_commit_id":"57ead6665c0af9254df58460876ffbf176d885d1","user":{"login":"jridgewell","id":112982,"node_id":"MDQ6VXNlcjExMjk4Mg==","avatar_url":"https://avatars1.githubusercontent.com/u/112982?v=4","gravatar_id":"","url":"https://api.github.com/users/jridgewell","html_url":"https://github.com/jridgewell","followers_url":"https://api.github.com/users/jridgewell/followers","following_url":"https://api.github.com/users/jridgewell/following{/other_user}","gists_url":"https://api.github.com/users/jridgewell/gists{/gist_id}","starred_url":"https://api.github.com/users/jridgewell/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jridgewell/subscriptions","organizations_url":"https://api.github.com/users/jridgewell/orgs","repos_url":"https://api.github.com/users/jridgewell/repos","events_url":"https://api.github.com/users/jridgewell/events{/privacy}","received_events_url":"https://api.github.com/users/jridgewell/received_events","type":"User","site_admin":false},"body":"Nit: seems like we could move this into `setupInit`.","created_at":"2018-07-31T20:05:08Z","updated_at":"2018-07-31T20:08:56Z","html_url":"https://github.com/ampproject/amphtml/pull/17206#discussion_r206665611","pull_request_url":"https://api.github.com/repos/ampproject/amphtml/pulls/17206","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/ampproject/amphtml/pulls/comments/206665611"},"html":{"href":"https://github.com/ampproject/amphtml/pull/17206#discussion_r206665611"},"pull_request":{"href":"https://api.github.com/repos/ampproject/amphtml/pulls/17206"}}},"pull_request":{"url":"https://api.github.com/repos/ampproject/amphtml/pulls/17206","id":204963163,"node_id":"MDExOlB1bGxSZXF1ZXN0MjA0OTYzMTYz","html_url":"https://github.com/ampproject/amphtml/pull/17206","diff_url":"https://github.com/ampproject/amphtml/pull/17206.diff","patch_url":"https://github.com/ampproject/amphtml/pull/17206.patch","issue_url":"https://api.github.com/repos/ampproject/amphtml/issues/17206","number":17206,"state":"open","locked":false,"title":"Document-fetcher using utils functions","user":{"login":"prateekbh","id":5960497,"node_id":"MDQ6VXNlcjU5NjA0OTc=","avatar_url":"https://avatars2.githubusercontent.com/u/5960497?v=4","gravatar_id":"","url":"https://api.github.com/users/prateekbh","html_url":"https://github.com/prateekbh","followers_url":"https://api.github.com/users/prateekbh/followers","following_url":"https://api.github.com/users/prateekbh/following{/other_user}","gists_url":"https://api.github.com/users/prateekbh/gists{/gist_id}","starred_url":"https://api.github.com/users/prateekbh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/prateekbh/subscriptions","organizations_url":"https://api.github.com/users/prateekbh/orgs","repos_url":"https://api.github.com/users/prateekbh/repos","events_url":"https://api.github.com/users/prateekbh/events{/privacy}","received_events_url":"https://api.github.com/users/prateekbh/received_events","type":"User","site_admin":false},"body":"- Implements  common functions (`maybeIntercept_`, `fetchAmpCors_`, `fromStructuredCloneable_`, `toStructuredCloneable_`) as utility functions.\r\n- `xhr-impl` and `document-fetcher` use these utility functions.\r\n- No one uses document-fetcher for now(this is just to keep the files to review count as low as possible).\r\n- Fixes Task #1 in #16707 \r\n\r\nNext PR will shift all uses of `fetchDocument` to `document-fetcher`.","created_at":"2018-07-31T00:08:52Z","updated_at":"2018-07-31T20:08:56Z","closed_at":null,"merged_at":null,"merge_commit_sha":"76cd8357efa2a1f9fb1f320163f76701599f2578","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":596633554,"node_id":"MDU6TGFiZWw1OTY2MzM1NTQ=","url":"https://api.github.com/repos/ampproject/amphtml/labels/cla:%20yes","name":"cla: yes","color":"ededed","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/ampproject/amphtml/pulls/17206/commits","review_comments_url":"https://api.github.com/repos/ampproject/amphtml/pulls/17206/comments","review_comment_url":"https://api.github.com/repos/ampproject/amphtml/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ampproject/amphtml/issues/17206/comments","statuses_url":"https://api.github.com/repos/ampproject/amphtml/statuses/57ead6665c0af9254df58460876ffbf176d885d1","head":{"label":"prateekbh:xhr-utils","ref":"xhr-utils","sha":"57ead6665c0af9254df58460876ffbf176d885d1","user":{"login":"prateekbh","id":5960497,"node_id":"MDQ6VXNlcjU5NjA0OTc=","avatar_url":"https://avatars2.githubusercontent.com/u/5960497?v=4","gravatar_id":"","url":"https://api.github.com/users/prateekbh","html_url":"https://github.com/prateekbh","followers_url":"https://api.github.com/users/prateekbh/followers","following_url":"https://api.github.com/users/prateekbh/following{/other_user}","gists_url":"https://api.github.com/users/prateekbh/gists{/gist_id}","starred_url":"https://api.github.com/users/prateekbh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/prateekbh/subscriptions","organizations_url":"https://api.github.com/users/prateekbh/orgs","repos_url":"https://api.github.com/users/prateekbh/repos","events_url":"https://api.github.com/users/prateekbh/events{/privacy}","received_events_url":"https://api.github.com/users/prateekbh/received_events","type":"User","site_admin":false},"repo":{"id":124931567,"node_id":"MDEwOlJlcG9zaXRvcnkxMjQ5MzE1Njc=","name":"amphtml","full_name":"prateekbh/amphtml","owner":{"login":"prateekbh","id":5960497,"node_id":"MDQ6VXNlcjU5NjA0OTc=","avatar_url":"https://avatars2.githubusercontent.com/u/5960497?v=4","gravatar_id":"","url":"https://api.github.com/users/prateekbh","html_url":"https://github.com/prateekbh","followers_url":"https://api.github.com/users/prateekbh/followers","following_url":"https://api.github.com/users/prateekbh/following{/other_user}","gists_url":"https://api.github.com/users/prateekbh/gists{/gist_id}","starred_url":"https://api.github.com/users/prateekbh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/prateekbh/subscriptions","organizations_url":"https://api.github.com/users/prateekbh/orgs","repos_url":"https://api.github.com/users/prateekbh/repos","events_url":"https://api.github.com/users/prateekbh/events{/privacy}","received_events_url":"https://api.github.com/users/prateekbh/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/prateekbh/amphtml","description":"AMP HTML source code, samples, and documentation.  See below for more info.","fork":true,"url":"https://api.github.com/repos/prateekbh/amphtml","forks_url":"https://api.github.com/repos/prateekbh/amphtml/forks","keys_url":"https://api.github.com/repos/prateekbh/amphtml/keys{/key_id}","collaborators_url":"https://api.github.com/repos/prateekbh/amphtml/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/prateekbh/amphtml/teams","hooks_url":"https://api.github.com/repos/prateekbh/amphtml/hooks","issue_events_url":"https://api.github.com/repos/prateekbh/amphtml/issues/events{/number}","events_url":"https://api.github.com/repos/prateekbh/amphtml/events","assignees_url":"https://api.github.com/repos/prateekbh/amphtml/assignees{/user}","branches_url":"https://api.github.com/repos/prateekbh/amphtml/branches{/branch}","tags_url":"https://api.github.com/repos/prateekbh/amphtml/tags","blobs_url":"https://api.github.com/repos/prateekbh/amphtml/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/prateekbh/amphtml/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/prateekbh/amphtml/git/refs{/sha}","trees_url":"https://api.github.com/repos/prateekbh/amphtml/git/trees{/sha}","statuses_url":"https://api.github.com/repos/prateekbh/amphtml/statuses/{sha}","languages_url":"https://api.github.com/repos/prateekbh/amphtml/languages","stargazers_url":"https://api.github.com/repos/prateekbh/amphtml/stargazers","contributors_url":"https://api.github.com/repos/prateekbh/amphtml/contributors","subscribers_url":"https://api.github.com/repos/prateekbh/amphtml/subscribers","subscription_url":"https://api.github.com/repos/prateekbh/amphtml/subscription","commits_url":"https://api.github.com/repos/prateekbh/amphtml/commits{/sha}","git_commits_url":"https://api.github.com/repos/prateekbh/amphtml/git/commits{/sha}","comments_url":"https://api.github.com/repos/prateekbh/amphtml/comments{/number}","issue_comment_url":"https://api.github.com/repos/prateekbh/amphtml/issues/comments{/number}","contents_url":"https://api.github.com/repos/prateekbh/amphtml/contents/{+path}","compare_url":"https://api.github.com/repos/prateekbh/amphtml/compare/{base}...{head}","merges_url":"https://api.github.com/repos/prateekbh/amphtml/merges","archive_url":"https://api.github.com/repos/prateekbh/amphtml/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/prateekbh/amphtml/downloads","issues_url":"https://api.github.com/repos/prateekbh/amphtml/issues{/number}","pulls_url":"https://api.github.com/repos/prateekbh/amphtml/pulls{/number}","milestones_url":"https://api.github.com/repos/prateekbh/amphtml/milestones{/number}","notifications_url":"https://api.github.com/repos/prateekbh/amphtml/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/prateekbh/amphtml/labels{/name}","releases_url":"https://api.github.com/repos/prateekbh/amphtml/releases{/id}","deployments_url":"https://api.github.com/repos/prateekbh/amphtml/deployments","created_at":"2018-03-12T18:13:36Z","updated_at":"2018-07-30T20:48:12Z","pushed_at":"2018-07-31T19:51:39Z","git_url":"git://github.com/prateekbh/amphtml.git","ssh_url":"git@github.com:prateekbh/amphtml.git","clone_url":"https://github.com/prateekbh/amphtml.git","svn_url":"https://github.com/prateekbh/amphtml","homepage":"https://www.ampproject.org","size":495013,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_projects":true,"has_downloads":false,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":1,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"ampproject:master","ref":"master","sha":"2613175cc890a513d9d359e1d7693489b3eaae4d","user":{"login":"ampproject","id":14114390,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE0MTE0Mzkw","avatar_url":"https://avatars3.githubusercontent.com/u/14114390?v=4","gravatar_id":"","url":"https://api.github.com/users/ampproject","html_url":"https://github.com/ampproject","followers_url":"https://api.github.com/users/ampproject/followers","following_url":"https://api.github.com/users/ampproject/following{/other_user}","gists_url":"https://api.github.com/users/ampproject/gists{/gist_id}","starred_url":"https://api.github.com/users/ampproject/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ampproject/subscriptions","organizations_url":"https://api.github.com/users/ampproject/orgs","repos_url":"https://api.github.com/users/ampproject/repos","events_url":"https://api.github.com/users/ampproject/events{/privacy}","received_events_url":"https://api.github.com/users/ampproject/received_events","type":"Organization","site_admin":false},"repo":{"id":41766002,"node_id":"MDEwOlJlcG9zaXRvcnk0MTc2NjAwMg==","name":"amphtml","full_name":"ampproject/amphtml","owner":{"login":"ampproject","id":14114390,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE0MTE0Mzkw","avatar_url":"https://avatars3.githubusercontent.com/u/14114390?v=4","gravatar_id":"","url":"https://api.github.com/users/ampproject","html_url":"https://github.com/ampproject","followers_url":"https://api.github.com/users/ampproject/followers","following_url":"https://api.github.com/users/ampproject/following{/other_user}","gists_url":"https://api.github.com/users/ampproject/gists{/gist_id}","starred_url":"https://api.github.com/users/ampproject/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ampproject/subscriptions","organizations_url":"https://api.github.com/users/ampproject/orgs","repos_url":"https://api.github.com/users/ampproject/repos","events_url":"https://api.github.com/users/ampproject/events{/privacy}","received_events_url":"https://api.github.com/users/ampproject/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ampproject/amphtml","description":"AMP HTML source code, samples, and documentation.  See below for more info.","fork":false,"url":"https://api.github.com/repos/ampproject/amphtml","forks_url":"https://api.github.com/repos/ampproject/amphtml/forks","keys_url":"https://api.github.com/repos/ampproject/amphtml/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ampproject/amphtml/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ampproject/amphtml/teams","hooks_url":"https://api.github.com/repos/ampproject/amphtml/hooks","issue_events_url":"https://api.github.com/repos/ampproject/amphtml/issues/events{/number}","events_url":"https://api.github.com/repos/ampproject/amphtml/events","assignees_url":"https://api.github.com/repos/ampproject/amphtml/assignees{/user}","branches_url":"https://api.github.com/repos/ampproject/amphtml/branches{/branch}","tags_url":"https://api.github.com/repos/ampproject/amphtml/tags","blobs_url":"https://api.github.com/repos/ampproject/amphtml/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ampproject/amphtml/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ampproject/amphtml/git/refs{/sha}","trees_url":"https://api.github.com/repos/ampproject/amphtml/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ampproject/amphtml/statuses/{sha}","languages_url":"https://api.github.com/repos/ampproject/amphtml/languages","stargazers_url":"https://api.github.com/repos/ampproject/amphtml/stargazers","contributors_url":"https://api.github.com/repos/ampproject/amphtml/contributors","subscribers_url":"https://api.github.com/repos/ampproject/amphtml/subscribers","subscription_url":"https://api.github.com/repos/ampproject/amphtml/subscription","commits_url":"https://api.github.com/repos/ampproject/amphtml/commits{/sha}","git_commits_url":"https://api.github.com/repos/ampproject/amphtml/git/commits{/sha}","comments_url":"https://api.github.com/repos/ampproject/amphtml/comments{/number}","issue_comment_url":"https://api.github.com/repos/ampproject/amphtml/issues/comments{/number}","contents_url":"https://api.github.com/repos/ampproject/amphtml/contents/{+path}","compare_url":"https://api.github.com/repos/ampproject/amphtml/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ampproject/amphtml/merges","archive_url":"https://api.github.com/repos/ampproject/amphtml/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ampproject/amphtml/downloads","issues_url":"https://api.github.com/repos/ampproject/amphtml/issues{/number}","pulls_url":"https://api.github.com/repos/ampproject/amphtml/pulls{/number}","milestones_url":"https://api.github.com/repos/ampproject/amphtml/milestones{/number}","notifications_url":"https://api.github.com/repos/ampproject/amphtml/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ampproject/amphtml/labels{/name}","releases_url":"https://api.github.com/repos/ampproject/amphtml/releases{/id}","deployments_url":"https://api.github.com/repos/ampproject/amphtml/deployments","created_at":"2015-09-01T22:10:53Z","updated_at":"2018-07-31T19:18:22Z","pushed_at":"2018-07-31T20:08:36Z","git_url":"git://github.com/ampproject/amphtml.git","ssh_url":"git@github.com:ampproject/amphtml.git","clone_url":"https://github.com/ampproject/amphtml.git","svn_url":"https://github.com/ampproject/amphtml","homepage":"https://www.ampproject.org","size":493504,"stargazers_count":11813,"watchers_count":11813,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":false,"has_wiki":false,"has_pages":false,"forks_count":2507,"mirror_url":null,"archived":false,"open_issues_count":1335,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":2507,"open_issues":1335,"watchers":11813,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ampproject/amphtml/pulls/17206"},"html":{"href":"https://github.com/ampproject/amphtml/pull/17206"},"issue":{"href":"https://api.github.com/repos/ampproject/amphtml/issues/17206"},"comments":{"href":"https://api.github.com/repos/ampproject/amphtml/issues/17206/comments"},"review_comments":{"href":"https://api.github.com/repos/ampproject/amphtml/pulls/17206/comments"},"review_comment":{"href":"https://api.github.com/repos/ampproject/amphtml/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ampproject/amphtml/pulls/17206/commits"},"statuses":{"href":"https://api.github.com/repos/ampproject/amphtml/statuses/57ead6665c0af9254df58460876ffbf176d885d1"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"jridgewell"}}