UNPKG

@authereum/resolution

Version:
147 lines (129 loc) 4.21 kB
import { ResolutionMethod, Provider, ResolutionResponse, SourceDefinition, NamingServiceName, } from '.'; import BaseConnection from './BaseConnection'; import ConfigurationError, { ConfigurationErrorCode, } from './errors/configurationError'; import ResolutionError, { ResolutionErrorCode } from './errors/resolutionError'; import FetchProvider from './FetchProvider'; import { nodeHash } from './types'; import { CryptoRecords } from './publicTypes'; export default abstract class NamingService extends BaseConnection { readonly name: ResolutionMethod; readonly network: number; readonly url: string | undefined; readonly registryAddress?: string; protected provider: Provider; abstract isSupportedDomain(domain: string): boolean; abstract isSupportedNetwork(): boolean; abstract owner(domain: string): Promise<string | null>; abstract records(domain: string, keys: string[]): Promise<CryptoRecords>; abstract resolve(domain: string): Promise<ResolutionResponse | null>; abstract resolver(domain: string): Promise<string>; abstract twitter(domain: string): Promise<string>; abstract childhash( parent: nodeHash, label: string, ): nodeHash; abstract allRecords(domain: string): Promise<CryptoRecords>; constructor(source: SourceDefinition, name: ResolutionMethod) { super(); this.name = name; source = this.normalizeSource(source); this.ensureConfigured(source); this.url = source.url; this.provider = source.provider || new FetchProvider(this.name, this.url || ''); this.network = source.network as number; this.registryAddress = source.registry; } serviceName(domain: string): NamingServiceName { return this.name as NamingServiceName; } namehash(domain: string): string { this.ensureSupportedDomain(domain); const parent = '0000000000000000000000000000000000000000000000000000000000000000'; return '0x' + [parent] .concat( domain .split('.') .reverse() .filter(label => label), ) .reduce((parent, label) => this.childhash(parent, label), ); } async record(domain: string, key: string): Promise<string> { const records = await this.records(domain, [key]); return NamingService.ensureRecordPresence(domain, key, records[key]); } protected abstract normalizeSource( source: SourceDefinition | undefined, ): SourceDefinition; protected ensureSupportedDomain(domain: string): void { if (!this.isSupportedDomain(domain)) { throw new ResolutionError(ResolutionErrorCode.UnsupportedDomain, { domain, }); } } protected async ignoreResolutionErrors<T>( codes: ResolutionErrorCode[], promise: Promise<T>, ): Promise<T | undefined> { try { return await promise; } catch (error) { if (codes.some(code => this.isResolutionError(error, code))) { return undefined; } else { throw error; } } } protected isResolutionError(error: any, code?: ResolutionErrorCode): boolean { return error instanceof ResolutionError && (!code || error.code === code); } public static ensureRecordPresence( domain: string, key: string, value: string | undefined | null, ): string { if (value) { return value; } throw new ResolutionError(ResolutionErrorCode.RecordNotFound, { recordName: key, domain: domain, }); } protected ensureConfigured(source: SourceDefinition): void { if (!source.network) { throw new ConfigurationError(ConfigurationErrorCode.UnspecifiedNetwork, { method: this.name, }); } if (!source.url && !source.provider) { throw new ConfigurationError(ConfigurationErrorCode.UnspecifiedUrl, { method: this.name, }); } } protected constructRecords( keys: string[], values: undefined | (string | undefined)[] | CryptoRecords, ): CryptoRecords { const records: CryptoRecords = {}; keys.forEach((key, index) => { const value = (values instanceof Array ? values[index] : values?.[key]) || ''; if (value) records[key] = value; }); return records; } }