/ src / lib / utils / orcids / fetch-orcid.ts
fetch-orcid.ts
  1  import { gql } from 'graphql-request';
  2  import { ORCID_PROFILE_FRAGMENT } from '../../../routes/(pages)/app/(app)/orcids/[orcidId]/components/orcid-profile-fragments';
  3  import network from '$lib/stores/wallet/network';
  4  import query from '$lib/graphql/dripsQL';
  5  import { executeRepoDriverReadMethod } from '$lib/utils/sdk/repo-driver/repo-driver';
  6  import { hexlify, toUtf8Bytes } from 'ethers';
  7  import { Forge, type OxString } from '$lib/utils/sdk/sdk-types';
  8  import { PUBLIC_ORCID_API_URL } from '$env/static/public';
  9  import { OrcidApiResponseSchema } from '$lib/utils/orcids/schemas';
 10  import Orcid from '$lib/utils/orcids/entities';
 11  import type {
 12    OrcidByAccountIdQuery,
 13    OrcidByAccountIdQueryVariables,
 14  } from './__generated__/gql.generated';
 15  
 16  const ORCID_SANDBOX_PREFIX = 'sandbox-';
 17  
 18  /**
 19   * Determine if the ORCID API url is pointing to the sandbox environment.
 20   *
 21   * @returns true if the ORCID API url is pointing to the sandbox, false otherwise.
 22   */
 23  export function isSandboxOrcidEnv() {
 24    try {
 25      const url = new URL(PUBLIC_ORCID_API_URL);
 26      return url.host === 'pub.sandbox.orcid.org';
 27    } catch {
 28      return false;
 29    }
 30  }
 31  
 32  /**
 33   * Prefixes an ORCID iD with 'sandbox-' if using the sandbox API. Necessary
 34   * for calls to requestUpdateOwner and calcAccountId.
 35   *
 36   * @param orcidId An ORCID iD
 37   * @returns The ORCID iD prefixed with 'sandbox-' if using the sandbox API or
 38   *  the plain ORCID iD otherwise.
 39   */
 40  export function orcidIdToSandoxOrcidId(orcidId: string) {
 41    if (isSandboxOrcidEnv()) {
 42      return `${ORCID_SANDBOX_PREFIX}${orcidId}`;
 43    }
 44  
 45    return orcidId;
 46  }
 47  
 48  /**
 49   * Return the account ID for an ORCID iD by calling calcAccountId.
 50   *
 51   * @param orcidId An ORCID iD.
 52   * @returns The corresponding account ID.
 53   */
 54  export function orcidIdToAccountId(orcidId: string) {
 55    return executeRepoDriverReadMethod({
 56      functionName: 'calcAccountId',
 57      args: [Forge.orcidId, hexlify(toUtf8Bytes(orcidIdToSandoxOrcidId(orcidId))) as OxString],
 58    });
 59  }
 60  
 61  /**
 62   * Fetch ORCID profile from the ORCID public API. Subject to anonymous usage limits:
 63   * - 12 req/sec
 64   * - 40 burst/sec
 65   * - 25,000 reads/day (Per IP address)
 66   *
 67   * @param orcidId The ORCID iD of the profile to fetch.
 68   * @param fetch A fetch function
 69   * @returns A Orcid instance or null if not found or on error.
 70   */
 71  export async function fetchOrcid(orcidId: string, fetch: typeof global.fetch) {
 72    const orcidResponse = await fetch(`${PUBLIC_ORCID_API_URL}/v3.0/${orcidId}/record`, {
 73      method: 'GET',
 74      headers: {
 75        Accept: 'application/json',
 76      },
 77    });
 78  
 79    if (!orcidResponse.ok) {
 80      // eslint-disable-next-line no-console
 81      console.error('ORCID API returned non-ok response', await orcidResponse.text());
 82      return null;
 83    }
 84  
 85    const responseJson = await orcidResponse.json();
 86    const orcid = OrcidApiResponseSchema.parse(responseJson);
 87  
 88    return new Orcid(orcid);
 89  }
 90  
 91  const getOrcidQuery = gql`
 92    ${ORCID_PROFILE_FRAGMENT}
 93    query OrcidByAccountId($orcid: String!, $chain: SupportedChain!) {
 94      orcidLinkedIdentityByOrcid(orcid: $orcid, chain: $chain) {
 95        ...OrcidProfile
 96      }
 97    }
 98  `;
 99  
100  export async function fetchOrcidAccount(orcidId: string, fetch?: typeof global.fetch) {
101    const result = await query<OrcidByAccountIdQuery, OrcidByAccountIdQueryVariables>(
102      getOrcidQuery,
103      {
104        orcid: orcidIdToSandoxOrcidId(orcidId),
105        chain: network.gqlName,
106      },
107      fetch,
108    );
109  
110    if (result.orcidLinkedIdentityByOrcid) {
111      // For sandboxed ORCID iDs, strip the 'sandbox-' prefix before returning to the caller
112      // for the sake of front-end continuity.
113      result.orcidLinkedIdentityByOrcid.orcid = orcidId;
114    }
115  
116    return result;
117  }