{"version":3,"file":"hocuspocus-throttle.cjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Extension, onConnectPayload } from \"@hocuspocus/server\";\n\nexport interface ThrottleConfiguration {\n\tthrottle: number | null | false; // how many requests within `consideredSeconds` until we're rejecting requests (setting this to 15 means the 16th request will be rejected)\n\tconsideredSeconds: number; // how many seconds to consider (default is last 60 seconds from the current connection attempt)\n\tbanTime: number; // for how long to ban after receiving too many requests (in minutes!)\n\tcleanupInterval: number; // how often to clean up the records of IPs (this won't delete ips that are still blocked or recent enough by `consideredSeconds`)\n}\n\nexport class Throttle implements Extension {\n\tconfiguration: ThrottleConfiguration = {\n\t\tthrottle: 15,\n\t\tbanTime: 5,\n\t\tconsideredSeconds: 60,\n\t\tcleanupInterval: 90,\n\t};\n\n\tconnectionsByIp: Map<string, Array<number>> = new Map();\n\n\tbannedIps: Map<string, number> = new Map();\n\n\tcleanupInterval?: NodeJS.Timeout;\n\n\t/**\n\t * Constructor\n\t */\n\tconstructor(configuration?: Partial<ThrottleConfiguration>) {\n\t\tthis.configuration = {\n\t\t\t...this.configuration,\n\t\t\t...configuration,\n\t\t};\n\n\t\tthis.cleanupInterval = setInterval(\n\t\t\tthis.clearMaps.bind(this),\n\t\t\tthis.configuration.cleanupInterval * 1000,\n\t\t);\n\t}\n\n\tonDestroy() {\n\t\tif (this.cleanupInterval) {\n\t\t\tclearInterval(this.cleanupInterval);\n\t\t}\n\n\t\treturn Promise.resolve();\n\t}\n\n\tpublic clearMaps() {\n\t\tthis.connectionsByIp.forEach((value, key) => {\n\t\t\tconst filteredValue = value.filter(\n\t\t\t\t(timestamp) =>\n\t\t\t\t\ttimestamp + this.configuration.consideredSeconds * 1000 > Date.now(),\n\t\t\t);\n\n\t\t\tif (filteredValue.length) {\n\t\t\t\tthis.connectionsByIp.set(key, filteredValue);\n\t\t\t} else {\n\t\t\t\tthis.connectionsByIp.delete(key);\n\t\t\t}\n\t\t});\n\n\t\tthis.bannedIps.forEach((value, key) => {\n\t\t\tif (!this.isBanned(key)) {\n\t\t\t\tthis.bannedIps.delete(key);\n\t\t\t}\n\t\t});\n\t}\n\n\tisBanned(ip: string) {\n\t\tconst bannedAt = this.bannedIps.get(ip) || 0;\n\t\treturn Date.now() < bannedAt + this.configuration.banTime * 60 * 1000;\n\t}\n\n\t/**\n\t * Throttle requests\n\t * @private\n\t */\n\tprivate throttle(ip: string): boolean {\n\t\tif (!this.configuration.throttle) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (this.isBanned(ip)) return true;\n\n\t\tthis.bannedIps.delete(ip);\n\n\t\t// add this connection try to the list of previous connections\n\t\tconst previousConnections = this.connectionsByIp.get(ip) || [];\n\t\tpreviousConnections.push(Date.now());\n\n\t\t// calculate the previous connections in the last considered time interval\n\t\tconst previousConnectionsInTheConsideredInterval =\n\t\t\tpreviousConnections.filter(\n\t\t\t\t(timestamp) =>\n\t\t\t\t\ttimestamp + this.configuration.consideredSeconds * 1000 > Date.now(),\n\t\t\t);\n\n\t\tthis.connectionsByIp.set(ip, previousConnectionsInTheConsideredInterval);\n\n\t\tif (\n\t\t\tpreviousConnectionsInTheConsideredInterval.length >\n\t\t\tthis.configuration.throttle\n\t\t) {\n\t\t\tthis.bannedIps.set(ip, Date.now());\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * onConnect hook\n\t * @param data\n\t */\n\tonConnect(data: onConnectPayload): Promise<any> {\n\t\tconst { request } = data;\n\n\t\t// get the remote ip address from headers (web standard Request doesn't have socket)\n\t\tconst ip =\n\t\t\trequest.headers.get(\"x-real-ip\") ||\n\t\t\trequest.headers.get(\"x-forwarded-for\") ||\n\t\t\t\"\";\n\n\t\t// throttle the connection\n\t\treturn this.throttle(<string>ip) ? Promise.reject() : Promise.resolve();\n\t}\n}\n"],"mappings":";;;AASA,IAAa,WAAb,MAA2C;;;;CAiB1C,YAAY,eAAgD;uBAhBrB;GACtC,UAAU;GACV,SAAS;GACT,mBAAmB;GACnB,iBAAiB;GACjB;yCAE6C,IAAI,KAAK;mCAEtB,IAAI,KAAK;AAQzC,OAAK,gBAAgB;GACpB,GAAG,KAAK;GACR,GAAG;GACH;AAED,OAAK,kBAAkB,YACtB,KAAK,UAAU,KAAK,KAAK,EACzB,KAAK,cAAc,kBAAkB,IACrC;;CAGF,YAAY;AACX,MAAI,KAAK,gBACR,eAAc,KAAK,gBAAgB;AAGpC,SAAO,QAAQ,SAAS;;CAGzB,AAAO,YAAY;AAClB,OAAK,gBAAgB,SAAS,OAAO,QAAQ;GAC5C,MAAM,gBAAgB,MAAM,QAC1B,cACA,YAAY,KAAK,cAAc,oBAAoB,MAAO,KAAK,KAAK,CACrE;AAED,OAAI,cAAc,OACjB,MAAK,gBAAgB,IAAI,KAAK,cAAc;OAE5C,MAAK,gBAAgB,OAAO,IAAI;IAEhC;AAEF,OAAK,UAAU,SAAS,OAAO,QAAQ;AACtC,OAAI,CAAC,KAAK,SAAS,IAAI,CACtB,MAAK,UAAU,OAAO,IAAI;IAE1B;;CAGH,SAAS,IAAY;EACpB,MAAM,WAAW,KAAK,UAAU,IAAI,GAAG,IAAI;AAC3C,SAAO,KAAK,KAAK,GAAG,WAAW,KAAK,cAAc,UAAU,KAAK;;;;;;CAOlE,AAAQ,SAAS,IAAqB;AACrC,MAAI,CAAC,KAAK,cAAc,SACvB,QAAO;AAGR,MAAI,KAAK,SAAS,GAAG,CAAE,QAAO;AAE9B,OAAK,UAAU,OAAO,GAAG;EAGzB,MAAM,sBAAsB,KAAK,gBAAgB,IAAI,GAAG,IAAI,EAAE;AAC9D,sBAAoB,KAAK,KAAK,KAAK,CAAC;EAGpC,MAAM,6CACL,oBAAoB,QAClB,cACA,YAAY,KAAK,cAAc,oBAAoB,MAAO,KAAK,KAAK,CACrE;AAEF,OAAK,gBAAgB,IAAI,IAAI,2CAA2C;AAExE,MACC,2CAA2C,SAC3C,KAAK,cAAc,UAClB;AACD,QAAK,UAAU,IAAI,IAAI,KAAK,KAAK,CAAC;AAClC,UAAO;;AAGR,SAAO;;;;;;CAOR,UAAU,MAAsC;EAC/C,MAAM,EAAE,YAAY;EAGpB,MAAM,KACL,QAAQ,QAAQ,IAAI,YAAY,IAChC,QAAQ,QAAQ,IAAI,kBAAkB,IACtC;AAGD,SAAO,KAAK,SAAiB,GAAG,GAAG,QAAQ,QAAQ,GAAG,QAAQ,SAAS"}