diff --git a/src/index.ts b/src/index.ts index baae18f..68ec5d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -160,17 +160,17 @@ const defaultCreateOptions: CreateOptions = { * @param {number} lifetime - lifetime of the record (in milliseconds). * @param {CreateOptions} options - additional create options. */ -export async function create (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, lifetime: number, options?: CreateV2OrV1Options): Promise -export async function create (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, lifetime: number, options: CreateV2Options): Promise -export async function create (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, lifetime: number, options: CreateOptions): Promise -export async function create (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, lifetime: number, options: CreateOptions = defaultCreateOptions): Promise { +export async function create (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, lifetime: number, options?: CreateV2OrV1Options, kv_data? : Object): Promise +export async function create (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, lifetime: number, options: CreateV2Options, kv_data? : Object): Promise +export async function create (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, lifetime: number, options: CreateOptions, kv_data? : Object): Promise +export async function create (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, lifetime: number, options: CreateOptions = defaultCreateOptions, kv_data : Object = {}): Promise { // Validity in ISOString with nanoseconds precision and validity type EOL const expirationDate = new NanoDate(Date.now() + Number(lifetime)) const validityType = IpnsEntry.ValidityType.EOL const [ms, ns] = lifetime.toString().split('.') const lifetimeNs = (BigInt(ms) * BigInt(100000)) + BigInt(ns ?? '0') - return _create(peerId, value, seq, validityType, expirationDate.toString(), lifetimeNs, options) + return _create(peerId, value, seq, validityType, expirationDate.toString(), lifetimeNs, options, kv_data) } /** @@ -202,7 +202,7 @@ export async function createWithExpiration (peerId: PeerId, value: CID | PeerId return _create(peerId, value, seq, validityType, expirationDate.toString(), ttlNs, options) } -const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, validityType: IpnsEntry.ValidityType, validity: string, ttl: bigint, options: CreateOptions = defaultCreateOptions): Promise => { +const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, validityType: IpnsEntry.ValidityType, validity: string, ttl: bigint, options: CreateOptions = defaultCreateOptions, kv_data: object = {}): Promise => { seq = BigInt(seq) const isoValidity = uint8ArrayFromString(validity) const normalizedValue = normalizeValue(value) @@ -213,7 +213,7 @@ const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number } const privateKey = await unmarshalPrivateKey(peerId.privateKey) - const data = createCborData(encodedValue, validityType, isoValidity, seq, ttl) + const data = createCborData(encodedValue, validityType, isoValidity, seq, ttl, kv_data) const sigData = ipnsRecordDataForV2Sig(data) const signatureV2 = await privateKey.sign(sigData) let pubKey: Uint8Array | undefined diff --git a/src/utils.ts b/src/utils.ts index 7df0c78..a29bda1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -164,7 +164,7 @@ export const peerIdFromRoutingKey = (key: Uint8Array): PeerId => { return peerIdFromBytes(key.slice(IPNS_PREFIX.length)) } -export const createCborData = (value: Uint8Array, validityType: IpnsEntry.ValidityType, validity: Uint8Array, sequence: bigint, ttl: bigint): Uint8Array => { +export const createCborData = (value: Uint8Array, validityType: IpnsEntry.ValidityType, validity: Uint8Array, sequence: bigint, ttl: bigint, kv_data : object = {}): Uint8Array => { let ValidityType if (validityType === IpnsEntry.ValidityType.EOL) { @@ -178,7 +178,8 @@ export const createCborData = (value: Uint8Array, validityType: IpnsEntry.Validi Validity: validity, ValidityType, Sequence: sequence, - TTL: ttl + TTL: ttl, + data : kv_data } return cborg.encode(data) diff --git a/test/extensible_data.spec.ts b/test/extensible_data.spec.ts new file mode 100644 index 0000000..9e95355 --- /dev/null +++ b/test/extensible_data.spec.ts @@ -0,0 +1,101 @@ +/* eslint-env mocha */ + +import { generateKeyPair } from '@libp2p/crypto/keys' +import { peerIdFromKeys } from '@libp2p/peer-id' +import { expect } from 'aegir/chai' +import * as ipns from '../src/index.js' +import { IpnsEntry } from '../src/pb/ipns.js' +import type { PeerId } from '@libp2p/interface' +import { parseCborData } from "../src/utils.js" + +const defaultCreateOptions: Object = { + v1Compatible: true +} + +describe('ipns extensible data', function () { + this.timeout(20 * 1000) + + const contentPath = '/ipfs/bafkqae3imvwgy3zamzzg63janjzs22lqnzzqu' + let peerId: PeerId + + before(async () => { + const rsa = await generateKeyPair('RSA', 2048) + peerId = await peerIdFromKeys(rsa.public.bytes, rsa.bytes) + }) + + + + it('should be able add a kv store to IPFS record', async () => { + const sequence = 0 + const validity = 1000000 + + let kv_data = {"KEY" : "VALUE"} + const record = await ipns.create(peerId, contentPath, sequence, validity, defaultCreateOptions, { kv_data : kv_data }) + expect(record.pubKey).to.equalBytes(peerId.publicKey) + + let raw_parsed_cbor_data = parseCborData(record.data) + let parsed_cbor_data = raw_parsed_cbor_data as any; + + + // console.log("\nparsed_cbor_data") + // console.log(raw_parsed_cbor_data) + // console.log("\nrecord.data") + // console.log(record.data) + // console.log("Object.keys(parsed_cbor_data)") + // console.log(Object.keys(raw_parsed_cbor_data)) + // console.log("raw_parsed_cbor_data.data") + // // console.log(parsed_cbor_data) // Produces error, "Property 'data' does not exist on type 'IPNSRecordData'." + // console.log("parsed_cbor_data.data") + // console.log(parsed_cbor_data.data) + // console.log("\n\n") + // console.log(parsed_cbor_data.data) + // console.log({ kv_data : kv_data }) + + + expect(parsed_cbor_data.data.kv_data.KEY).to.equal("VALUE") + + const pb = IpnsEntry.decode(ipns.marshal(record)) + expect(pb.pubKey).to.equalBytes(peerId.publicKey) + }) + + + it('should be able add a raw object as kv store to IPFS record', async () => { + const sequence = 0 + const validity = 1000000 + + let kv_data = { + "KEY" : "VALUE", + "KEY2" : "VALUE2" + } + const record = await ipns.create(peerId, contentPath, sequence, validity, defaultCreateOptions, kv_data) + expect(record.pubKey).to.equalBytes(peerId.publicKey) + + let raw_parsed_cbor_data = parseCborData(record.data) + let parsed_cbor_data = raw_parsed_cbor_data as any; + + + // console.log("\nparsed_cbor_data") + // console.log(raw_parsed_cbor_data) + // console.log("\nrecord.data") + // console.log(record.data) + // console.log("Object.keys(parsed_cbor_data)") + // console.log(Object.keys(raw_parsed_cbor_data)) + // console.log("raw_parsed_cbor_data.data") + // // console.log(parsed_cbor_data) // Produces error, "Property 'data' does not exist on type 'IPNSRecordData'." + // console.log("parsed_cbor_data.data") + // console.log(parsed_cbor_data.data) + // console.log("\n\n") + // console.log(parsed_cbor_data.data) + // console.log({ kv_data : kv_data }) + + + expect(parsed_cbor_data.data.KEY).to.equal("VALUE") + expect(parsed_cbor_data.data.KEY2).to.equal("VALUE2") + + const pb = IpnsEntry.decode(ipns.marshal(record)) + expect(pb.pubKey).to.equalBytes(peerId.publicKey) + }) + + + +}) \ No newline at end of file