All files / src data-root.ts

0% Statements 0/40
0% Branches 0/15
0% Functions 0/9
0% Lines 0/36

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154                                                                                                                                                                                                                                                                                                                   
import * as check from './fs/types/check'
import * as debug from './common/debug'
import * as did from './did'
import * as dns from './dns'
import * as ucan from './ucan'
import { CID } from './ipfs'
import { api } from './common'
import { Ucan } from './ucan'
import { setup } from './setup/internal'
 
/**
 * CID representing an empty string. We use to to speed up DNS propagation
 * However, we treat that as a null value in the code
 */
const EMPTY_CID = 'Qmc5m94Gu7z62RC8waSKkZUrCCBJPyHbkpmGzEePxy2oXJ'
 
/**
 * Get the CID of a user's data root.
 * First check Fission server, then check DNS
 *
 * @param username The username of the user that we want to get the data root of.
 */
export async function lookup(
  username: string
): Promise<CID | null> {
  const maybeRoot = await lookupOnFisson(username)
  if(maybeRoot === EMPTY_CID) return null
  if(maybeRoot !== null) return maybeRoot
 
  try {
    const cid = await dns.lookupDnsLink(username + '.files.' + setup.endpoints.user)
    return cid === EMPTY_CID ? null : cid
  } catch(err) {
    console.error(err)
    throw new Error('Could not locate user root in dns')
  }
}
 
/**
 * Get the CID of a user's data root from the Fission server.
 *
 * @param username The username of the user that we want to get the data root of.
 */
export async function lookupOnFisson(
  username: string
): Promise<CID | null> {
  try {
    const resp = await fetch(
      `${setup.endpoints.api}/user/data/${username}`,
      { cache: 'reload' } // don't use cache
    )
    const cid = await resp.json()
    if (!check.isCID(cid)) {
      throw new Error("Did not receive a CID")
    }
    return cid
 
  } catch(err) {
    debug.log('Could not locate user root on Fission server: ', err.toString())
    return null
 
  }
}
 
/**
 * Update a user's data root.
 *
 * @param cid The CID of the data root.
 * @param proof The proof to use in the UCAN sent to the API.
 */
export async function update(
  cid: CID | string,
  proof: Ucan
): Promise<{ success: boolean }> {
  const apiEndpoint = setup.endpoints.api
 
  // Debug
  debug.log("🌊 Updating your DNSLink:", cid)
 
  // Make API call
  return await fetchWithRetry(`${apiEndpoint}/user/data/${cid}`, {
    headers: async () => {
      const jwt = ucan.encode(await ucan.build({
        audience: await api.did(),
        issuer: await did.ucan(),
        potency: "APPEND",
        proof,
 
        // TODO: Waiting on API change.
        //       Should be `username.fission.name/*`
        resource: proof.payload.rsc
      }))
 
      return { 'authorization': `Bearer ${jwt}` }
    },
    retries: 100,
    retryDelay: 5000,
    retryOn: [ 502, 503, 504 ],
 
  }, {
    method: 'PATCH'
 
  }).then((response: Response) => {
    if (response.status < 300) debug.log("🪴 DNSLink updated:", cid)
    else debug.log("🔥 Failed to update DNSLink for:", cid)
    return { success: response.status < 300 }
 
  }).catch(err => {
    debug.log("🔥 Failed to update DNSLink for:", cid)
    console.error(err)
    return { success: false }
 
  })
}
 
 
 
// ㊙️
 
 
type RetryOptions = {
  headers: () => Promise<{ [_: string]: string }>
  retries: number
  retryDelay: number
  retryOn: Array<number>
}
 
 
async function fetchWithRetry(
  url: string,
  retryOptions: RetryOptions,
  fetchOptions: RequestInit,
  retry: number = 0
): Promise<Response> {
  const headers = await retryOptions.headers()
  const response = await fetch(url, {
    ...fetchOptions,
    headers: { ...fetchOptions.headers, ...headers }
  })
 
  if (retryOptions.retryOn.includes(response.status)) {
    if (retry < retryOptions.retries) {
      return await new Promise((resolve, reject) => setTimeout(
        () => fetchWithRetry(url, retryOptions, fetchOptions, retry + 1).then(resolve, reject),
        retryOptions.retryDelay
      ))
    } else {
      throw new Error("Too many retries for fetch")
    }
  }
 
  return response
}