@@ -4,6 +4,7 @@ import * as cborg from 'cborg'
44import errCode from 'err-code'
55import { Key } from 'interface-datastore/key'
66import { base32upper } from 'multiformats/bases/base32'
7+ import { CID } from 'multiformats/cid'
78import * as Digest from 'multiformats/hashes/digest'
89import { identity } from 'multiformats/hashes/identity'
910import NanoDate from 'timestamp-nano'
@@ -24,7 +25,7 @@ export const namespaceLength = namespace.length
2425
2526export class IPNSRecord {
2627 readonly pb : IpnsEntry
27- private readonly data : any
28+ readonly data : any
2829
2930 constructor ( pb : IpnsEntry ) {
3031 this . pb = pb
@@ -37,7 +38,7 @@ export class IPNSRecord {
3738 }
3839
3940 value ( ) : string {
40- return uint8ArrayToString ( this . data . Value )
41+ return normalizeValue ( this . data . Value )
4142 }
4243
4344 validityType ( ) : IpnsEntry . ValidityType {
@@ -93,7 +94,7 @@ const defaultCreateOptions: CreateOptions = {
9394 * @param {number } lifetime - lifetime of the record (in milliseconds).
9495 * @param {CreateOptions } options - additional create options.
9596 */
96- export const create = async ( peerId : PeerId , value : string , seq : number | bigint , lifetime : number , options : CreateOptions = defaultCreateOptions ) : Promise < IPNSRecord > => {
97+ export const create = async ( peerId : PeerId , value : string | Uint8Array , seq : number | bigint , lifetime : number , options : CreateOptions = defaultCreateOptions ) : Promise < IPNSRecord > => {
9798 // Validity in ISOString with nanoseconds precision and validity type EOL
9899 const expirationDate = new NanoDate ( Date . now ( ) + Number ( lifetime ) )
99100 const validityType = IpnsEntry . ValidityType . EOL
@@ -113,7 +114,7 @@ export const create = async (peerId: PeerId, value: string, seq: number | bigint
113114 * @param {string } expiration - expiration datetime for record in the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision.
114115 * @param {CreateOptions } options - additional creation options.
115116 */
116- export const createWithExpiration = async ( peerId : PeerId , value : string , seq : number | bigint , expiration : string , options : CreateOptions = defaultCreateOptions ) : Promise < IPNSRecord > => {
117+ export const createWithExpiration = async ( peerId : PeerId , value : string | Uint8Array , seq : number | bigint , expiration : string , options : CreateOptions = defaultCreateOptions ) : Promise < IPNSRecord > => {
117118 const expirationDate = NanoDate . fromString ( expiration )
118119 const validityType = IpnsEntry . ValidityType . EOL
119120
@@ -123,10 +124,10 @@ export const createWithExpiration = async (peerId: PeerId, value: string, seq: n
123124 return _create ( peerId , value , seq , validityType , expirationDate , ttlNs , options )
124125}
125126
126- const _create = async ( peerId : PeerId , value : string , seq : number | bigint , validityType : IpnsEntry . ValidityType , expirationDate : NanoDate , ttl : bigint , options : CreateOptions = defaultCreateOptions ) : Promise < IPNSRecord > => {
127+ const _create = async ( peerId : PeerId , value : string | Uint8Array , seq : number | bigint , validityType : IpnsEntry . ValidityType , expirationDate : NanoDate , ttl : bigint , options : CreateOptions = defaultCreateOptions ) : Promise < IPNSRecord > => {
127128 seq = BigInt ( seq )
128129 const isoValidity = uint8ArrayFromString ( expirationDate . toString ( ) )
129- const encodedValue = uint8ArrayFromString ( value )
130+ const encodedValue = uint8ArrayFromString ( normalizeValue ( value ) )
130131
131132 if ( peerId . privateKey == null ) {
132133 throw errCode ( new Error ( 'Missing private key' ) , ERRORS . ERR_MISSING_PRIVATE_KEY )
@@ -198,3 +199,22 @@ const signLegacyV1 = async (privateKey: PrivateKey, value: Uint8Array, validityT
198199 throw errCode ( new Error ( 'record signature creation failed' ) , ERRORS . ERR_SIGNATURE_CREATION )
199200 }
200201}
202+
203+ /**
204+ * Normalizes the given record value. It ensures it is a string starting with '/'.
205+ * If the given value is a cid, the returned path will be '/ipfs/{cid}'.
206+ */
207+ const normalizeValue = ( value : string | Uint8Array ) : string => {
208+ const str = typeof value === 'string' ? value : uint8ArrayToString ( value )
209+
210+ if ( str . startsWith ( '/' ) ) {
211+ return str
212+ }
213+
214+ try {
215+ const cid = CID . parse ( str )
216+ return '/ipfs/' + cid . toV1 ( ) . toString ( )
217+ } catch ( _ ) {
218+ throw errCode ( new Error ( 'Value must be a valid content path starting with /' ) , ERRORS . ERR_INVALID_VALUE )
219+ }
220+ }
0 commit comments