UNPKG

w3name

Version:

The JavaScript API client for w3name

1 lines 15.6 kB
{"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["/**\n * A client library for the w3name - IPNS over HTTP API. It provides a\n * convenient interface for creating names, making revisions to name records,\n * and publishing and resolving them via the HTTP API.\n *\n * @example\n * ```js\n * import * as Name from 'w3name'\n *\n * const name = await Name.create()\n *\n * console.log('Name:', name.toString())\n * // e.g. k51qzi5uqu5di9agapykyjh3tqrf7i14a7fjq46oo0f6dxiimj62knq13059lt\n *\n * // The value to publish\n * const value = '/ipfs/bafkreiem4twkqzsq2aj4shbycd4yvoj2cx72vezicletlhi7dijjciqpui'\n * const revision = await Name.v0(name, value)\n *\n * // Publish the revision\n * await Name.publish(revision, name.key)\n *\n * // Resolve the latest value\n * await Name.resolve(name)\n * ```\n * @module\n */\n\nimport { generateKeyPair, publicKeyFromProtobuf, privateKeyFromProtobuf, privateKeyFromRaw } from '@libp2p/crypto/keys'\nimport { PrivateKey, PublicKey } from '@libp2p/interface'\nimport { Link } from 'multiformats'\nimport { base36 } from 'multiformats/bases/base36'\nimport { base64pad } from 'multiformats/bases/base64'\nimport { CID } from 'multiformats/cid'\nimport * as Digest from 'multiformats/hashes/digest'\nimport * as ipns from 'ipns'\nimport { validate } from 'ipns/validator'\nimport * as cbor from 'cborg'\nimport W3NameService from './service.js'\n\nconst libp2pKeyCode = 0x72\nconst ONE_YEAR = 1000 * 60 * 60 * 24 * 365\n\nconst defaultValidity = (): RFC3339DateString => new Date(Date.now() + ONE_YEAR).toISOString()\nconst defaultTTL = 5n * 60n * 1000n * 1000000n // 5 minutes in nanoseconds\nconst defaultService = new W3NameService()\n\n/**\n * Name is an IPNS key ID.\n *\n * Names can be used to retrieve the latest published value from the W3name service\n * using the {@link resolve} function.\n *\n * Note that `Name` contains only the public verification key and does not allow publishing\n * or updating records. To create or update a record, use the {@link WritableName} subclass.\n *\n * To convert from a string representation of a name to a `Name` object use the {@link parse} function.\n */\nexport class Name {\n /** @internal */\n _pubKey: PublicKey\n\n /** @internal */\n _cid: Link\n\n constructor (pubKey: PublicKey) {\n /**\n * @private\n */\n this._pubKey = pubKey\n this._cid = pubKey.toCID()\n }\n\n /**\n * A binary representation of the IPNS verification key.\n */\n get bytes (): Uint8Array {\n return this._cid.bytes\n }\n\n /**\n * @returns the string representation of the IPNS verification key (e.g. `k51qzi5uqu5di9agapykyjh3tqrf7i14a7fjq46oo0f6dxiimj62knq13059lt`)\n */\n toString (): string {\n return this._cid.toString(base36)\n }\n}\n\n/**\n * WritableName is a {@link Name} that has a signing key associated with it such that\n * new IPNS record {@link Revision}s can be created and signed for it.\n *\n * New `WritableName`s can be generated using the {@link create} function.\n *\n * To load a `WritableName` from a saved binary representation, see {@link from}.\n */\nexport class WritableName extends Name {\n /** @internal */\n _privKey: PrivateKey\n\n constructor (privKey: PrivateKey) {\n super(privKey.publicKey)\n /**\n * @private\n */\n this._privKey = privKey\n }\n\n /**\n * The private signing key, as a libp2p `PrivateKey` object.\n *\n * To save a key for later loading with {@link from}, write the\n * contents of `key.raw` somewhere safe.\n */\n get key (): PrivateKey {\n return this._privKey\n }\n}\n\n/**\n * Create a new name with associated signing key that can be used to create and\n * publish IPNS record revisions.\n */\nexport async function create (): Promise<WritableName> {\n const privKey = await generateKeyPair('Ed25519', 2048)\n return new WritableName(privKey)\n}\n\n/**\n * Parses a string-encoded {@link Name} to a {@link Name} object.\n *\n * Note that this returns a read-only {@link Name}, which can be used to {@link resolve} values\n * but cannot {@link publish} them.\n */\nexport function parse (name: string): Name {\n const keyCid = CID.parse(name, base36)\n if (keyCid.code !== libp2pKeyCode) {\n throw new Error(`Invalid key, expected ${libp2pKeyCode} codec code but got ${keyCid.code}`)\n }\n const pubKey = publicKeyFromProtobuf(Digest.decode(keyCid.multihash.bytes).bytes)\n return new Name(pubKey)\n}\n\n/**\n * Creates a {@link WritableName} from an existing signing key (private key).\n *\n * Expects the given `Uint8Array` to contain a binary representation of a\n * private signing key. Note that this is **not** the same as the output of\n * {@link Name#bytes | Name.bytes}, which always returns an encoding of the _public_ key,\n * even when the name in question is a {@link WritableName}.\n *\n * To save the key for a {@link WritableName} so that it can be used with this\n * function, use `key.raw`, for example:\n *\n * @example\n * ```js\n * import * as Name from 'w3name'\n * import fs from 'fs'\n *\n * async function example() {\n * const myName = await Name.create()\n *\n * // myName.key.raw can now be written to disk / database, etc.\n * await fs.promises.writeFile('myName.key', myName.key.raw)\n *\n * // let's pretend some time has passed and we want to load the\n * // key from disk:\n * const loadedBytes = await fs.promises.readFile('myName.key')\n * const myName2 = await Name.from(loadedBytes)\n *\n * // myName and myName2 can now be used interchangeably\n * }\n * ```\n *\n */\nexport async function from (key: Uint8Array): Promise<WritableName> {\n let privKey: PrivateKey\n try {\n privKey = privateKeyFromRaw(key)\n } catch {\n // legacy keys where key.bytes used to contain the PB encoded private key\n privKey = privateKeyFromProtobuf(key)\n }\n return new WritableName(privKey)\n}\n\n/**\n * Create an initial version of the IPNS record for the passed {@link Name}, set to the\n * passed value.\n *\n * Note that the returned {@link Revision} object must be {@link publish}ed before it\n * can be {@link resolve}d using the service.\n */\nexport async function v0 (name: Name, value: string): Promise<Revision> {\n return new Revision(name, value, 0n, defaultValidity())\n}\n\n/**\n * Create a {@link Revision} of the passed IPNS record by incrementing the sequence\n * number and changing the value.\n *\n * This returns a new {@link Revision} and does not alter the original `revision` argument.\n */\nexport async function increment (revision: Revision, value: string): Promise<Revision> {\n const seqno = revision.sequence + 1n\n return new Revision(revision.name, value, seqno, defaultValidity())\n}\n\nexport type RFC3339DateString = string\n\nexport interface RevisionOptions {\n /** TTL in nanoseconds, Default: 5m */\n ttl?: bigint\n}\n\n/**\n * A representation of a IPNS record that may be initial or revised.\n */\nexport class Revision {\n /** @internal */\n _name: Name\n\n /** @internal */\n _value: string\n\n /** @internal */\n _sequence: bigint\n\n /** @internal */\n _validity: string\n\n /** @internal */\n _ttl: bigint | undefined\n\n constructor (name: Name, value: string, sequence: bigint, validity: RFC3339DateString, options?: RevisionOptions) {\n this._name = name\n\n if (typeof value !== 'string') {\n throw new Error('invalid value')\n }\n this._value = value\n\n if (typeof sequence !== 'bigint') {\n throw new Error('invalid sequence number')\n }\n this._sequence = sequence\n\n if (typeof validity !== 'string' || Number.isNaN(new Date(validity).getTime())) {\n throw new Error('invalid validity')\n }\n this._validity = validity\n\n const ttl = options?.ttl ?? defaultTTL\n if (typeof ttl !== 'bigint') {\n throw new Error('invalid TTL')\n }\n this._ttl = ttl\n }\n\n get name (): Name {\n return this._name\n }\n\n get value (): string {\n return this._value\n }\n\n get sequence (): bigint {\n return this._sequence\n }\n\n /**\n * RFC3339 date string.\n */\n get validity (): RFC3339DateString {\n return this._validity\n }\n\n /** TTL in nanoseconds */\n get ttl (): bigint | undefined {\n return this._ttl\n }\n\n /**\n * Encodes a `Revision` to a binary representation and returns it as a `Uint8Array`.\n *\n * Note: if `revision.name` is a `WritableName` then signing key data will be\n * lost. i.e. the private key is not encoded.\n */\n static encode (revision: Revision): Uint8Array {\n return cbor.encode({\n name: revision._name.toString(),\n value: revision._value,\n sequence: revision._sequence,\n validity: revision._validity,\n ...(revision._ttl != null ? { ttl: revision._ttl } : {})\n })\n }\n\n /**\n * Decodes a `Revision` from a binary representation.\n *\n * @param bytes - a `Uint8Array` containing a binary encoding of a `Revision`, as produced by {@link #encode}.\n * @returns a {@link Revision} object\n * @throws if `bytes` does not contain a valid encoded `Revision`\n */\n static decode (bytes: Uint8Array): Revision {\n const raw = cbor.decode(bytes)\n const name = parse(raw.name)\n return new Revision(name, raw.value, BigInt(raw.sequence), raw.validity, { ttl: raw.ttl != null ? BigInt(raw.ttl) : undefined })\n }\n}\n\n/**\n * Publish a name {@link Revision} to W3name.\n *\n * Names should be {@link resolve}-able immediately via the w3name service, and will be\n * provided to the IPFS DHT network.\n *\n * Note that it may take a few seconds for the record to propagate and become available via\n * the IPFS DHT network and IPFS <-> HTTP gateways.\n */\nexport async function publish (revision: Revision, key: PrivateKey, service: W3NameService = defaultService): Promise<void> {\n const url = new URL(`name/${revision.name.toString()}`, service.endpoint)\n const entry = await ipns.createIPNSRecord(\n key,\n revision.value,\n revision.sequence,\n new Date(revision.validity).getTime() - Date.now(),\n { ttlNs: revision.ttl }\n )\n await service.waitForRateLimit()\n\n await maybeHandleError(fetch(url.toString(), {\n method: 'POST',\n body: base64pad.baseEncode(ipns.marshalIPNSRecord(entry))\n }))\n}\n\n/**\n * Resolve the current IPNS record revision for the passed name.\n *\n * Note that this will only resolve names published using the W3name service. Names published by\n * other IPNS implementations should be resolved using a DHT-backed implementation (e.g. kubo, js-ipfs, etc).\n */\nexport async function resolve (name: Name, service: W3NameService = defaultService): Promise<Revision> {\n const url = new URL(`name/${name.toString()}`, service.endpoint)\n await service.waitForRateLimit()\n\n const res: globalThis.Response = await maybeHandleError(fetch(url.toString()))\n const { record } = await res.json()\n\n const bytes = base64pad.baseDecode(record)\n const entry = ipns.unmarshalIPNSRecord(bytes)\n const keyCid = CID.decode(name.bytes)\n const pubKey = publicKeyFromProtobuf(Digest.decode(keyCid.multihash.bytes).bytes)\n\n await validate(pubKey, bytes)\n\n return new Revision(\n name,\n entry.value,\n entry.sequence,\n entry.validity,\n { ttl: entry.ttl }\n )\n}\n\nasync function maybeHandleError (resPromise: Promise<globalThis.Response>): Promise<globalThis.Response> {\n const res = await resPromise\n if (res.ok) return res\n let error\n try {\n const parsedError = await res.json()\n error = new Error(parsedError.message)\n } catch (jsonParseError) {\n error = new Error(`unexpected response from API, cannot parse error response. Received status: ${res.status}`)\n }\n throw error\n}\n"],"names":["defaultValidity","Date","now","toISOString","defaultTTL","defaultService","W3NameService","Name","_pubKey","_cid","constructor","pubKey","this","toCID","bytes","toString","base36","WritableName","_privKey","privKey","super","publicKey","key","parse","name","keyCid","CID","code","Error","publicKeyFromProtobuf","Digest","decode","multihash","Revision","_name","_value","_sequence","_validity","_ttl","value","sequence","validity","options","Number","isNaN","getTime","ttl","static","revision","cbor","encode","raw","BigInt","undefined","async","maybeHandleError","resPromise","res","ok","error","parsedError","json","message","jsonParseError","status","generateKeyPair","privateKeyFromRaw","privateKeyFromProtobuf","seqno","service","url","URL","endpoint","entry","ipns","createIPNSRecord","ttlNs","waitForRateLimit","fetch","method","body","base64pad","baseEncode","marshalIPNSRecord","record","baseDecode","unmarshalIPNSRecord","validate"],"mappings":"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"}