@@ -13,6 +13,7 @@ const connect = require('./lib/connect')
1313const { FIREWALL , BOOTSTRAP_NODES , KNOWN_NODES , COMMANDS } = require ( './lib/constants' )
1414const { hash, createKeyPair } = require ( './lib/crypto' )
1515const { decode } = require ( 'hypercore-id-encoding' )
16+ const { hammingDistance } = require ( '@holepunchto/searchable-record-cache' )
1617const RawStreamSet = require ( './lib/raw-stream-set' )
1718const ConnectionPool = require ( './lib/connection-pool' )
1819const { STREAM_NOT_CONNECTED } = require ( './lib/errors' )
@@ -52,6 +53,7 @@ class HyperDHT extends DHT {
5253 this . _randomPunchInterval = opts . randomPunchInterval || 20000 // min 20s between random punches...
5354 this . _randomPunches = 0
5455 this . _randomPunchLimit = 1 // set to one for extra safety for now
56+ this . _simhash = opts . simhash
5557
5658 this . once ( 'persistent' , ( ) => {
5759 this . _persistent = new Persistent ( this , persistent )
@@ -184,6 +186,68 @@ class HyperDHT extends DHT {
184186 return this . query ( { target, command : COMMANDS . LOOKUP , value : null } , opts )
185187 }
186188
189+ async searchableRecordPut ( tokens , value , opts = { } ) {
190+ if ( ! this . _simhash ) return
191+
192+ const target = this . _simhash . hash ( tokens )
193+ const query = this . query ( { target, command : COMMANDS . SEARCH , value : null } , opts )
194+ await query . finished ( )
195+
196+ for ( const closest of query . closestReplies ) {
197+ await this . request (
198+ {
199+ target,
200+ command : COMMANDS . SEARCHABLE_RECORD_PUT ,
201+ value : c . encode ( m . searchableRecord , { value, key : target } )
202+ } ,
203+ closest . from
204+ )
205+ }
206+
207+ return target
208+ }
209+
210+ async search ( tokens , opts = { } ) {
211+ if ( ! this . _simhash ) return
212+
213+ const target = this . _simhash . hash ( tokens )
214+
215+ const query = this . query (
216+ {
217+ target,
218+ command : COMMANDS . SEARCH ,
219+ value : c . encode ( m . searchOptions , {
220+ closest : opts . closest || 5 ,
221+ values : opts . values || 5
222+ } )
223+ } ,
224+ opts
225+ )
226+
227+ const results = [ ]
228+ const seen = new Set ( )
229+
230+ for await ( const reply of query ) {
231+ if ( reply . value ) {
232+ const res = c . decode ( m . searchResponse , reply . value )
233+
234+ for ( const r of res ) {
235+ const key = r . key . toString ( 'hex' )
236+ if ( seen . has ( key ) ) continue
237+
238+ const distance = hammingDistance ( r . key , target )
239+ seen . add ( key )
240+ results . push ( { ...r , distance, from : reply . from } )
241+ }
242+ }
243+ }
244+
245+ results . sort ( ( a , b ) => a . distance - b . distance )
246+ while ( results . length > 5 ) results . pop ( )
247+
248+ return results
249+ }
250+
187251 lookupAndUnannounce ( target , keyPair , opts = { } ) {
188252 const unannounces = [ ]
189253 const dht = this
@@ -426,6 +490,14 @@ class HyperDHT extends DHT {
426490 this . _persistent . onimmutableget ( req )
427491 return true
428492 }
493+ case COMMANDS . SEARCH : {
494+ this . _persistent . onsearch ( req )
495+ return true
496+ }
497+ case COMMANDS . SEARCHABLE_RECORD_PUT : {
498+ this . _persistent . onsearchablerecordput ( req )
499+ return true
500+ }
429501 }
430502
431503 return false
@@ -604,6 +676,7 @@ function defaultCacheOpts(opts) {
604676 maxSize : ( maxSize / 2 ) | 0 ,
605677 maxAge : opts . maxAge || 48 * 60 * 60 * 1000 // 48 hours
606678 } ,
679+ searchableRecords : { maxSize : Infinity , maxAge : Infinity } ,
607680 bumps : { maxSize, maxAge }
608681 }
609682 }
0 commit comments