1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import type {Wakeable} from 'shared/ReactTypes';
    
  11. import type {GitHubIssue} from './githubAPI';
    
  12. 
    
  13. import {unstable_getCacheForType as getCacheForType} from 'react';
    
  14. import {searchGitHubIssues} from './githubAPI';
    
  15. 
    
  16. const API_TIMEOUT = 3000;
    
  17. 
    
  18. const Pending = 0;
    
  19. const Resolved = 1;
    
  20. const Rejected = 2;
    
  21. 
    
  22. type PendingRecord = {
    
  23.   status: 0,
    
  24.   value: Wakeable,
    
  25. };
    
  26. 
    
  27. type ResolvedRecord<T> = {
    
  28.   status: 1,
    
  29.   value: T,
    
  30. };
    
  31. 
    
  32. type RejectedRecord = {
    
  33.   status: 2,
    
  34.   value: null,
    
  35. };
    
  36. 
    
  37. type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
    
  38. 
    
  39. function readRecord<T>(record: Record<T>): ResolvedRecord<T> | RejectedRecord {
    
  40.   if (record.status === Resolved) {
    
  41.     // This is just a type refinement.
    
  42.     return record;
    
  43.   } else if (record.status === Rejected) {
    
  44.     // This is just a type refinement.
    
  45.     return record;
    
  46.   } else {
    
  47.     throw record.value;
    
  48.   }
    
  49. }
    
  50. 
    
  51. type GitHubIssueMap = Map<string, Record<GitHubIssue>>;
    
  52. 
    
  53. function createMap(): GitHubIssueMap {
    
  54.   return new Map();
    
  55. }
    
  56. 
    
  57. function getRecordMap(): Map<string, Record<GitHubIssue>> {
    
  58.   return getCacheForType(createMap);
    
  59. }
    
  60. 
    
  61. export function findGitHubIssue(errorMessage: string): GitHubIssue | null {
    
  62.   errorMessage = normalizeErrorMessage(errorMessage);
    
  63. 
    
  64.   const map = getRecordMap();
    
  65.   let record = map.get(errorMessage);
    
  66. 
    
  67.   if (!record) {
    
  68.     const callbacks = new Set<() => mixed>();
    
  69.     const wakeable: Wakeable = {
    
  70.       then(callback: () => mixed) {
    
  71.         callbacks.add(callback);
    
  72.       },
    
  73. 
    
  74.       // Optional property used by Timeline:
    
  75.       displayName: `Searching GitHub issues for error "${errorMessage}"`,
    
  76.     };
    
  77.     const wake = () => {
    
  78.       // This assumes they won't throw.
    
  79.       callbacks.forEach(callback => callback());
    
  80.       callbacks.clear();
    
  81.     };
    
  82.     const newRecord: Record<GitHubIssue> = (record = {
    
  83.       status: Pending,
    
  84.       value: wakeable,
    
  85.     });
    
  86. 
    
  87.     let didTimeout = false;
    
  88. 
    
  89.     searchGitHubIssues(errorMessage)
    
  90.       .then(maybeItem => {
    
  91.         if (didTimeout) {
    
  92.           return;
    
  93.         }
    
  94. 
    
  95.         if (maybeItem) {
    
  96.           const resolvedRecord =
    
  97.             ((newRecord: any): ResolvedRecord<GitHubIssue>);
    
  98.           resolvedRecord.status = Resolved;
    
  99.           resolvedRecord.value = maybeItem;
    
  100.         } else {
    
  101.           const notFoundRecord = ((newRecord: any): RejectedRecord);
    
  102.           notFoundRecord.status = Rejected;
    
  103.           notFoundRecord.value = null;
    
  104.         }
    
  105. 
    
  106.         wake();
    
  107.       })
    
  108.       .catch(error => {
    
  109.         const thrownRecord = ((newRecord: any): RejectedRecord);
    
  110.         thrownRecord.status = Rejected;
    
  111.         thrownRecord.value = null;
    
  112. 
    
  113.         wake();
    
  114.       });
    
  115. 
    
  116.     // Only wait a little while for GitHub results before showing a fallback.
    
  117.     setTimeout(() => {
    
  118.       didTimeout = true;
    
  119. 
    
  120.       const timedoutRecord = ((newRecord: any): RejectedRecord);
    
  121.       timedoutRecord.status = Rejected;
    
  122.       timedoutRecord.value = null;
    
  123. 
    
  124.       wake();
    
  125.     }, API_TIMEOUT);
    
  126. 
    
  127.     map.set(errorMessage, record);
    
  128.   }
    
  129. 
    
  130.   const response = readRecord(record).value;
    
  131.   return response;
    
  132. }
    
  133. 
    
  134. function normalizeErrorMessage(errorMessage: string): string {
    
  135.   // Remove Fiber IDs from error message (as those will be unique).
    
  136.   errorMessage = errorMessage.replace(/"[0-9]+"/, '');
    
  137.   return errorMessage;
    
  138. }