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 }