diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..4019c92 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,21 @@ +/// +/// +import EventEmitter from "events"; +export default class DHTFlood extends EventEmitter { + private id; + private ttl; + private messageNumber; + private lru; + private swarm; + constructor({ lruSize, ttl, messageNumber, id, swarm }?: { + lruSize?: number | undefined; + ttl?: number | undefined; + messageNumber?: number | undefined; + id?: Buffer | undefined; + swarm?: null | undefined; + }); + private handleMessage; + private setupPeer; + broadcast(data: any, ttl?: number): void; +} +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map new file mode 100644 index 0000000..db417bb --- /dev/null +++ b/dist/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAoBlC,MAAM,CAAC,OAAO,OAAO,QAAS,SAAQ,YAAY;IAC9C,OAAO,CAAC,EAAE,CAAS;IACnB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,KAAK,CAAM;gBAEP,EACI,OAAkB,EAClB,GAAS,EACT,aAAiB,EACjB,EAA2B,EAC3B,KAAY,EACf;;;;;;KAAK;IAkBlB,OAAO,CAAC,aAAa;IAwBrB,OAAO,CAAC,SAAS;IAmCjB,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,SAAW;CActC"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..49eb69b --- /dev/null +++ b/dist/index.js @@ -0,0 +1,105 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const events_1 = __importDefault(require("events")); +const crypto_1 = __importDefault(require("crypto")); +// @ts-ignore +const lru_1 = __importDefault(require("lru")); +const debug_1 = __importDefault(require("debug")); +// @ts-ignore +const protomux_1 = __importDefault(require("protomux")); +const messages_js_1 = require("./messages.js"); +// @ts-ignore +const compact_encoding_1 = __importDefault(require("compact-encoding")); +const b4a_1 = __importDefault(require("b4a")); +const debug = (0, debug_1.default)('dht-flood'); +const LRU_SIZE = 255; +const TTL = 255; +const PROTOCOL = "lumeweb.flood"; +const FLOOD_SYMBOL = Symbol.for(PROTOCOL); +class DHTFlood extends events_1.default { + id; + ttl; + messageNumber; + lru; + swarm; + constructor({ lruSize = LRU_SIZE, ttl = TTL, messageNumber = 0, id = crypto_1.default.randomBytes(32), swarm = null } = {}) { + super(); + this.id = id; + this.ttl = ttl; + this.messageNumber = messageNumber; + this.lru = new lru_1.default(lruSize); + if (!swarm) { + throw new Error('swarm is required'); + } + this.swarm = swarm; + this.swarm.on("connection", (peer) => { + const mux = protomux_1.default.from(peer); + mux.pair({ protocol: PROTOCOL }, () => this.setupPeer(peer)); + }); + } + handleMessage({ originId, messageNumber, ttl, data }, messenger) { + const originIdBuf = b4a_1.default.from(originId); + // Ignore messages from ourselves + if (originIdBuf.equals(this.id)) + return debug('Got message from self', originId, messageNumber); + // Ignore messages we've already seen + const key = originIdBuf.toString('hex') + messageNumber; + if (this.lru.get(key)) + return debug('Got message that was already seen', originId, messageNumber); + this.lru.set(key, true); + this.emit('message', data, originId, messageNumber); + if (ttl <= 0) + return debug('Got message at end of TTL', originId, messageNumber, ttl); + messenger.send({ + originId, + messageNumber, + data, + ttl: ttl - 1 + }); + } + setupPeer(peer) { + const mux = protomux_1.default.from(peer); + let chan; + if (!mux.opened({ protocol: PROTOCOL })) { + chan = mux.createChannel({ + protocol: PROTOCOL, + }); + peer[FLOOD_SYMBOL] = chan; + } + chan = peer[FLOOD_SYMBOL]; + if (!chan) { + throw new Error('could not find channel'); + } + if (!chan.messages.length) { + chan.addMessage({ + encoding: { + preencode: (state, m) => compact_encoding_1.default.raw.preencode(state, messages_js_1.Packet.encode(m).finish()), + encode: (state, m) => compact_encoding_1.default.raw.encode(state, messages_js_1.Packet.encode(m).finish()), + decode: (state) => messages_js_1.Packet.decode(compact_encoding_1.default.raw.decode(state)), + }, + onmessage: (msg) => this.handleMessage(msg, chan.messages[0]), + }); + } + if (!chan.opened) { + chan.open(); + } + return chan.messages[0]; + } + broadcast(data, ttl = this.ttl) { + this.messageNumber++; + const { id, messageNumber } = this; + for (const peer of this.swarm.connections.values()) { + const message = this.setupPeer(peer); + message.send({ + originId: id, + messageNumber, + ttl, + data + }); + } + } +} +exports.default = DHTFlood; diff --git a/dist/messages.d.ts b/dist/messages.d.ts new file mode 100644 index 0000000..b0137be --- /dev/null +++ b/dist/messages.d.ts @@ -0,0 +1,38 @@ +import * as _m0 from "protobufjs/minimal"; +export declare const protobufPackage = ""; +/** type=0 */ +export interface Packet { + originId: Uint8Array; + messageNumber: number; + ttl: number; + data: Uint8Array; +} +export declare const Packet: { + encode(message: Packet, writer?: _m0.Writer): _m0.Writer; + decode(input: _m0.Reader | Uint8Array, length?: number): Packet; + fromJSON(object: any): Packet; + toJSON(message: Packet): unknown; + fromPartial]: never; }>(object: I): Packet; +}; +declare type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; +export declare type DeepPartial = T extends Builtin ? T : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> : T extends {} ? { + [K in keyof T]?: DeepPartial; +} : Partial; +declare type KeysOfUnion = T extends T ? keyof T : never; +export declare type Exact = P extends Builtin ? P : P & { + [K in keyof P]: Exact; +} & { + [K in Exclude>]: never; +}; +export {}; +//# sourceMappingURL=messages.d.ts.map \ No newline at end of file diff --git a/dist/messages.d.ts.map b/dist/messages.d.ts.map new file mode 100644 index 0000000..14d686a --- /dev/null +++ b/dist/messages.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,oBAAoB,CAAC;AAE1C,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,aAAa;AACb,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,UAAU,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,CAAC;CAClB;AAMD,eAAO,MAAM,MAAM;oBACD,MAAM,WAAU,IAAI,MAAM,GAAyB,IAAI,MAAM;kBAgB/D,IAAI,MAAM,GAAG,UAAU,WAAW,MAAM,GAAG,MAAM;qBA2B9C,GAAG,GAAG,MAAM;oBASb,MAAM,GAAG,OAAO;;;;;;;;;;;uEAWiC,MAAM;CAQxE,CAAC;AA8CF,aAAK,OAAO,GAAG,IAAI,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEpF,oBAAY,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,OAAO,GAAG,CAAC,GAC9C,CAAC,SAAS,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GACnH,CAAC,SAAS,EAAE,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GACrD,OAAO,CAAC,CAAC,CAAC,CAAC;AAEf,aAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC;AACpD,oBAAY,KAAK,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,OAAO,GAAG,CAAC,GACrD,CAAC,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG;KAAG,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK;CAAE,CAAC"} \ No newline at end of file diff --git a/dist/messages.js b/dist/messages.js new file mode 100644 index 0000000..c443d84 --- /dev/null +++ b/dist/messages.js @@ -0,0 +1,144 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Packet = exports.protobufPackage = void 0; +/* eslint-disable */ +const _m0 = __importStar(require("protobufjs/minimal")); +exports.protobufPackage = ""; +function createBasePacket() { + return { originId: new Uint8Array(), messageNumber: 0, ttl: 0, data: new Uint8Array() }; +} +exports.Packet = { + encode(message, writer = _m0.Writer.create()) { + if (message.originId.length !== 0) { + writer.uint32(10).bytes(message.originId); + } + if (message.messageNumber !== 0) { + writer.uint32(16).uint32(message.messageNumber); + } + if (message.ttl !== 0) { + writer.uint32(24).uint32(message.ttl); + } + if (message.data.length !== 0) { + writer.uint32(34).bytes(message.data); + } + return writer; + }, + decode(input, length) { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBasePacket(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.originId = reader.bytes(); + break; + case 2: + message.messageNumber = reader.uint32(); + break; + case 3: + message.ttl = reader.uint32(); + break; + case 4: + message.data = reader.bytes(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + fromJSON(object) { + return { + originId: isSet(object.originId) ? bytesFromBase64(object.originId) : new Uint8Array(), + messageNumber: isSet(object.messageNumber) ? Number(object.messageNumber) : 0, + ttl: isSet(object.ttl) ? Number(object.ttl) : 0, + data: isSet(object.data) ? bytesFromBase64(object.data) : new Uint8Array(), + }; + }, + toJSON(message) { + const obj = {}; + message.originId !== undefined && + (obj.originId = base64FromBytes(message.originId !== undefined ? message.originId : new Uint8Array())); + message.messageNumber !== undefined && (obj.messageNumber = Math.round(message.messageNumber)); + message.ttl !== undefined && (obj.ttl = Math.round(message.ttl)); + message.data !== undefined && + (obj.data = base64FromBytes(message.data !== undefined ? message.data : new Uint8Array())); + return obj; + }, + fromPartial(object) { + const message = createBasePacket(); + message.originId = object.originId ?? new Uint8Array(); + message.messageNumber = object.messageNumber ?? 0; + message.ttl = object.ttl ?? 0; + message.data = object.data ?? new Uint8Array(); + return message; + }, +}; +var globalThis = (() => { + if (typeof globalThis !== "undefined") { + return globalThis; + } + if (typeof self !== "undefined") { + return self; + } + if (typeof window !== "undefined") { + return window; + } + if (typeof global !== "undefined") { + return global; + } + throw "Unable to locate global object"; +})(); +function bytesFromBase64(b64) { + if (globalThis.Buffer) { + return Uint8Array.from(globalThis.Buffer.from(b64, "base64")); + } + else { + const bin = globalThis.atob(b64); + const arr = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; ++i) { + arr[i] = bin.charCodeAt(i); + } + return arr; + } +} +function base64FromBytes(arr) { + if (globalThis.Buffer) { + return globalThis.Buffer.from(arr).toString("base64"); + } + else { + const bin = []; + arr.forEach((byte) => { + bin.push(String.fromCharCode(byte)); + }); + return globalThis.btoa(bin.join("")); + } +} +function isSet(value) { + return value !== null && value !== undefined; +}