From fd92cfb2cf15a90f8cab5d913d73c7064d2d38e3 Mon Sep 17 00:00:00 2001
From: achingbrain <alex@achingbrain.net>
Date: Sun, 3 Nov 2024 21:09:32 +0000
Subject: [PATCH] feat: add cancelReprovide function to routing

Exposes the cancel reprovide functionality of the libp2p routers.
---
 packages/interface/src/routing.ts              | 13 +++++++++++++
 packages/routers/src/delegated-http-routing.ts | 16 ++++++++++------
 packages/routers/src/libp2p-routing.ts         |  4 ++++
 packages/routers/test/libp2p-routing.spec.ts   |  9 +++++++++
 packages/utils/src/routing.ts                  |  9 +++++++++
 5 files changed, 45 insertions(+), 6 deletions(-)

diff --git a/packages/interface/src/routing.ts b/packages/interface/src/routing.ts
index 7263e4f1d..1ab5ed5f2 100644
--- a/packages/interface/src/routing.ts
+++ b/packages/interface/src/routing.ts
@@ -64,6 +64,19 @@ export interface Routing {
    */
   provide(cid: CID, options?: RoutingOptions): Promise<void>
 
+  /**
+   * Helia will periodically re-provide every previously provided CID. Use this
+   * method to no longer re-provide the passed CID.
+   *
+   * @example
+   *
+   * ```js
+   * // ...
+   * await contentRouting.cancelReprovide(cid)
+   * ```
+   */
+  cancelReprovide(key: CID, options?: AbortOptions): Promise<void>
+
   /**
    * Find the providers of the passed CID.
    *
diff --git a/packages/routers/src/delegated-http-routing.ts b/packages/routers/src/delegated-http-routing.ts
index 6cb30e9a5..d9e9da9bf 100644
--- a/packages/routers/src/delegated-http-routing.ts
+++ b/packages/routers/src/delegated-http-routing.ts
@@ -25,11 +25,15 @@ class DelegatedHTTPRouter implements Routing {
     this.client = createDelegatedRoutingV1HttpApiClient(url, init)
   }
 
-  async provide (cid: CID, options?: RoutingOptions | undefined): Promise<void> {
+  async provide (cid: CID, options?: RoutingOptions): Promise<void> {
     // noop
   }
 
-  async * findProviders (cid: CID<unknown, number, number, Version>, options?: RoutingOptions | undefined): AsyncIterable<Provider> {
+  async cancelReprovide (cid?: CID, options?: RoutingOptions): Promise<void> {
+    // noop
+  }
+
+  async * findProviders (cid: CID<unknown, number, number, Version>, options?: RoutingOptions): AsyncIterable<Provider> {
     yield * map(this.client.getProviders(cid, options), (record) => {
       return {
         id: record.ID,
@@ -39,7 +43,7 @@ class DelegatedHTTPRouter implements Routing {
     })
   }
 
-  async put (key: Uint8Array, value: Uint8Array, options?: RoutingOptions | undefined): Promise<void> {
+  async put (key: Uint8Array, value: Uint8Array, options?: RoutingOptions): Promise<void> {
     if (!isIPNSKey(key)) {
       return
     }
@@ -51,7 +55,7 @@ class DelegatedHTTPRouter implements Routing {
     await this.client.putIPNS(cid, record, options)
   }
 
-  async get (key: Uint8Array, options?: RoutingOptions | undefined): Promise<Uint8Array> {
+  async get (key: Uint8Array, options?: RoutingOptions): Promise<Uint8Array> {
     if (!isIPNSKey(key)) {
       throw new NotFoundError('Not found')
     }
@@ -74,7 +78,7 @@ class DelegatedHTTPRouter implements Routing {
     }
   }
 
-  async findPeer (peerId: PeerId, options?: RoutingOptions | undefined): Promise<PeerInfo> {
+  async findPeer (peerId: PeerId, options?: RoutingOptions): Promise<PeerInfo> {
     const peer = await first(this.client.getPeers(peerId, options))
 
     if (peer != null) {
@@ -87,7 +91,7 @@ class DelegatedHTTPRouter implements Routing {
     throw new NotFoundError('Not found')
   }
 
-  async * getClosestPeers (key: Uint8Array, options?: RoutingOptions | undefined): AsyncIterable<PeerInfo> {
+  async * getClosestPeers (key: Uint8Array, options?: RoutingOptions): AsyncIterable<PeerInfo> {
     // noop
   }
 }
diff --git a/packages/routers/src/libp2p-routing.ts b/packages/routers/src/libp2p-routing.ts
index 935c86d47..e71cf7d19 100644
--- a/packages/routers/src/libp2p-routing.ts
+++ b/packages/routers/src/libp2p-routing.ts
@@ -13,6 +13,10 @@ class Libp2pRouter implements Routing {
     await this.libp2p.contentRouting.provide(cid, options)
   }
 
+  async cancelReprovide (key: CID, options?: RoutingOptions): Promise<void> {
+    await this.libp2p.contentRouting.cancelReprovide(key, options)
+  }
+
   async * findProviders (cid: CID, options?: RoutingOptions): AsyncIterable<Provider> {
     yield * this.libp2p.contentRouting.findProviders(cid, options)
   }
diff --git a/packages/routers/test/libp2p-routing.spec.ts b/packages/routers/test/libp2p-routing.spec.ts
index d7c916d87..5309e858b 100644
--- a/packages/routers/test/libp2p-routing.spec.ts
+++ b/packages/routers/test/libp2p-routing.spec.ts
@@ -33,6 +33,15 @@ describe('libp2p-routing', () => {
     expect(contentRouting.provide.calledWith(cid, options)).to.be.true()
   })
 
+  it('should call through to contentRouting.cancelReprovide', async () => {
+    const cid = CID.parse('bafyreidykglsfhoixmivffc5uwhcgshx4j465xwqntbmu43nb2dzqwfvae')
+    const options = {}
+
+    await router.cancelReprovide(cid, options)
+
+    expect(contentRouting.cancelReprovide.calledWith(cid, options)).to.be.true()
+  })
+
   it('should call through to contentRouting.findProviders', async () => {
     contentRouting.findProviders.returns(async function * () {}())
 
diff --git a/packages/utils/src/routing.ts b/packages/utils/src/routing.ts
index 11db18f62..47c33248d 100644
--- a/packages/utils/src/routing.ts
+++ b/packages/utils/src/routing.ts
@@ -115,6 +115,15 @@ export class Routing implements RoutingInterface, Startable {
     )
   }
 
+  async cancelReprovide (key: CID, options: AbortOptions = {}): Promise<void> {
+    await Promise.all(
+      supports(this.routers, 'cancelReprovide')
+        .map(async (router) => {
+          await router.cancelReprovide(key, options)
+        })
+    )
+  }
+
   /**
    * Store the given key/value pair in the available content routings
    */