Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/static_src/shared/federated.js b/static_src/shared/federated.js
new file mode 100644
index 0000000..e5b7567
--- /dev/null
+++ b/static_src/shared/federated.js
@@ -0,0 +1,194 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Logic for dealing with federated issue references.
+ */
+
+import loadGapi, {fetchGapiEmail} from './gapi-loader.js';
+
+const GOOGLE_ISSUE_TRACKER_REGEX = /^b\/\d+$/;
+
+const GOOGLE_ISSUE_TRACKER_API_ROOT = 'https://issuetracker.corp.googleapis.com';
+const GOOGLE_ISSUE_TRACKER_DISCOVERY_PATH = '/$discovery/rest';
+const GOOGLE_ISSUE_TRACKER_API_VERSION = 'v3';
+
+// Returns if shortlink is valid for any federated tracker.
+export function isShortlinkValid(shortlink) {
+  return FEDERATED_TRACKERS.some((TrackerClass) => {
+    try {
+      return new TrackerClass(shortlink);
+    } catch (e) {
+      if (e instanceof FederatedIssueError) {
+        return false;
+      } else {
+        throw e;
+      }
+    }
+  });
+}
+
+// Returns a issue instance for the first matching tracker.
+export function fromShortlink(shortlink) {
+  for (const key in FEDERATED_TRACKERS) {
+    if (FEDERATED_TRACKERS.hasOwnProperty(key)) {
+      const TrackerClass = FEDERATED_TRACKERS[key];
+      try {
+        return new TrackerClass(shortlink);
+      } catch (e) {
+        if (e instanceof FederatedIssueError) {
+          continue;
+        } else {
+          throw e;
+        }
+      }
+    }
+  }
+  return null;
+}
+
+// FederatedIssue is an abstract class for representing one federated issue.
+// Each supported tracker should subclass this class.
+class FederatedIssue {
+  constructor(shortlink) {
+    if (!this.isShortlinkValid(shortlink)) {
+      throw new FederatedIssueError(`Invalid tracker shortlink: ${shortlink}`);
+    }
+    this.shortlink = shortlink;
+  }
+
+  // isShortlinkValid returns whether a given shortlink is valid.
+  isShortlinkValid(shortlink) {
+    if (!(typeof shortlink === 'string')) {
+      throw new FederatedIssueError('shortlink argument must be a string.');
+    }
+    return Boolean(shortlink.match(this.shortlinkRe()));
+  }
+
+  // shortlinkRe returns the regex used to validate shortlinks.
+  shortlinkRe() {
+    throw new Error('Not implemented.');
+  }
+
+  // toURL returns the URL to this issue.
+  toURL() {
+    throw new Error('Not implemented.');
+  }
+
+  // toIssueRef converts the FedRef's information into an object having the
+  // IssueRef format everywhere on the front-end expects.
+  toIssueRef() {
+    return {
+      extIdentifier: this.shortlink,
+    };
+  }
+
+  // trackerName should return the name of the bug tracker.
+  get trackerName() {
+    throw new Error('Not implemented.');
+  }
+
+  // isOpen returns a Promise that resolves either true or false.
+  async isOpen() {
+    throw new Error('Not implemented.');
+  }
+}
+
+// Class for Google Issue Tracker (Buganizer) logic.
+export class GoogleIssueTrackerIssue extends FederatedIssue {
+  constructor(shortlink) {
+    super(shortlink);
+    this.issueID = Number(shortlink.substr(2));
+    this._federatedDetails = null;
+  }
+
+  shortlinkRe() {
+    return GOOGLE_ISSUE_TRACKER_REGEX;
+  }
+
+  toURL() {
+    return `https://issuetracker.google.com/issues/${this.issueID}`;
+  }
+
+  get trackerName() {
+    return 'Buganizer';
+  }
+
+  async getFederatedDetails() {
+    // Prevent fetching details more than once.
+    if (this._federatedDetails) {
+      return this._federatedDetails;
+    }
+
+    await loadGapi();
+    const email = await fetchGapiEmail();
+    if (!email) {
+      // Fail open.
+      return true;
+    }
+    const res = await this._loadGoogleIssueTrackerIssue(this.issueID);
+    if (!res || !res.result) {
+      // Fail open.
+      return null;
+    }
+
+    this._federatedDetails = res.result;
+    return this._federatedDetails;
+  }
+
+  // isOpen assumes getFederatedDetails has already been called, otherwise
+  // it will fail open (returning that the issue is open).
+  get isOpen() {
+    if (!this._federatedDetails) {
+      // Fail open.
+      return true;
+    }
+
+    // Open issues will not have a `resolvedTime`.
+    return !Boolean(this._federatedDetails.resolvedTime);
+  }
+
+  // summary assumes getFederatedDetails has already been called.
+  get summary() {
+    if (this._federatedDetails &&
+        this._federatedDetails.issueState &&
+        this._federatedDetails.issueState.title) {
+      return this._federatedDetails.issueState.title;
+    }
+    return null;
+  }
+
+  toIssueRef() {
+    return {
+      extIdentifier: this.shortlink,
+      summary: this.summary,
+      statusRef: {meansOpen: this.isOpen},
+    };
+  }
+
+  get _APIURL() {
+    return GOOGLE_ISSUE_TRACKER_API_ROOT + GOOGLE_ISSUE_TRACKER_DISCOVERY_PATH;
+  }
+
+  _loadGoogleIssueTrackerIssue(bugID) {
+    return new Promise((resolve, reject) => {
+      const version = GOOGLE_ISSUE_TRACKER_API_VERSION;
+      gapi.client.load(this._APIURL, version, () => {
+        const request = gapi.client.corp_issuetracker.issues.get({
+          'issueId': bugID,
+        });
+        request.execute((response) => {
+          resolve(response);
+        });
+      });
+    });
+  }
+}
+
+class FederatedIssueError extends Error {}
+
+// A list of supported tracker classes.
+const FEDERATED_TRACKERS = [
+  GoogleIssueTrackerIssue,
+];