2024-01-23 21:21:46 +07:00
"use strict" ;
2024-05-10 13:18:05 +07:00
process . title = "Bostr (cluster)" ;
2024-01-03 17:32:10 +07:00
const { version } = require ( "./package.json" ) ;
2023-10-31 14:02:14 +07:00
const WebSocket = require ( "ws" ) ;
2024-06-05 19:57:17 +07:00
const http = require ( "node:http" ) ;
const http2 = require ( "node:http2" ) ;
const fs = require ( "node:fs" ) ;
2023-11-18 16:49:20 +07:00
const bouncer = require ( ` ./bouncer.js ` ) ;
2024-05-13 06:33:56 +07:00
const undici = require ( "undici" ) ;
2023-10-31 14:02:14 +07:00
// For log
const curD = _ => ( new Date ( ) ) . toLocaleString ( "ia" ) ;
const log = _ => console . log ( process . pid , curD ( ) , "-" , _ ) ;
// Server
2024-01-13 18:53:59 +07:00
let server = null ;
2024-02-19 18:37:50 +07:00
let config = require ( process . env . BOSTR _CONFIG _PATH || "./config" ) ;
2024-05-19 23:09:49 +07:00
let connectedHosts = [ ] ;
2024-05-13 16:36:52 +07:00
let wslinkregex = /(?:^- )(wss?:\/\/.*)(?: \(.*\))/gm ;
let loadbalancerUpstreamLinks = [ ] ;
2024-02-19 18:37:50 +07:00
config . server _meta . version = version ;
2024-05-19 22:01:13 +07:00
config . server _meta . limitation = {
2024-05-19 23:33:36 +07:00
auth _required : ( config . server _meta . loadbalancer ? . length || config . authorized _keys ? . length ) ? true : false ,
max _subscription : config . max _client _subs || Infinity
2024-05-19 22:01:13 +07:00
} ;
2024-05-13 06:33:56 +07:00
if ( ! config . relays ? . length ) ( async ( ) => {
console . log ( "Load balancer mode. Fetching relays list from" , config . loadbalancer [ 0 ] . replace ( /^ws/ , "http" ) ) ;
const request = await undici . request ( config . loadbalancer [ 0 ] . replace ( /^ws/ , "http" ) , {
headers : {
2025-01-06 22:27:40 +07:00
"User-Agent" : config . user _agent
2024-05-13 06:33:56 +07:00
}
} ) ;
2024-05-13 07:09:17 +07:00
const text = await request . body . text ( ) ;
2024-05-13 16:36:52 +07:00
let l = null
while ( ( l = wslinkregex . exec ( text ) ) !== null ) {
loadbalancerUpstreamLinks . push ( l [ 1 ] ) ;
}
console . log ( "Got list:" ) ;
console . log ( "- " + loadbalancerUpstreamLinks . join ( "\n- " ) )
2024-05-13 06:33:56 +07:00
} ) ( ) ;
2024-01-13 18:53:59 +07:00
if (
fs . existsSync ( config . https ? . privKey ) &&
fs . existsSync ( config . https ? . certificate )
) {
2024-01-13 06:36:24 -05:00
let http2 _options = {
allowHTTP1 : true ,
key : fs . readFileSync ( config . https ? . privKey ) ,
cert : fs . readFileSync ( config . https ? . certificate ) ,
dhparam : "auto" ,
paddingStrategy : http2 . constants . PADDING _STRATEGY _MAX
}
2024-01-13 18:53:59 +07:00
if ( fs . existsSync ( config . https ? . ticketKey ) )
http2 _options . ticketKeys = fs . readFileSync ( config . https ? . ticketKey ) ;
server = http2 . createSecureServer ( http2 _options ) ;
server . isStandaloneHTTPS = true ;
2024-01-13 06:36:24 -05:00
} else {
2024-05-11 10:30:08 +07:00
server = http . createServer ( )
2024-01-13 18:53:59 +07:00
server . isStandaloneHTTPS = false ;
2024-01-13 06:36:24 -05:00
}
2024-01-13 18:53:59 +07:00
2024-07-29 19:32:37 +07:00
const wss _for _everyone = new WebSocket . WebSocketServer ( {
2024-01-27 19:42:18 +07:00
noServer : true ,
2024-05-16 19:32:53 +07:00
perMessageDeflate : config . perMessageDeflate || false
2024-01-27 19:42:18 +07:00
} ) ;
2023-10-31 14:02:14 +07:00
2024-07-29 19:32:37 +07:00
const wss _for _apple = new WebSocket . WebSocketServer ( {
noServer : true ,
perMessageDeflate : false
} ) ;
2024-01-09 06:58:27 +07:00
const favicon = fs . existsSync ( config . favicon ) ? fs . readFileSync ( config . favicon ) : null ;
2023-10-31 14:02:14 +07:00
server . on ( 'request' , ( req , res ) => {
2024-04-04 21:33:11 +07:00
const globalStat = bouncer . getStat ( "_global" ) ;
2024-05-24 13:35:38 +07:00
const serverAddr = ` ${ req . headers [ "x-forwarded-proto" ] ? . split ( "," ) [ 0 ] ? . replace ( /http/i , "ws" ) || ( server . isStandaloneHTTPS ? "wss" : "ws" ) } :// ${ req . headers . host } ${ req . url } ` ;
2024-01-04 23:12:14 +07:00
log ( ` ${ req . headers [ "x-forwarded-for" ] ? . split ( "," ) [ 0 ] || req . socket . address ( ) ? . address } - ${ req . method } ${ req . url } [ ${ req . headers [ "user-agent" ] || "" } ] ` )
2023-10-31 14:02:14 +07:00
2023-11-13 18:32:29 +07:00
if ( req . headers . accept ? . includes ( "application/nostr+json" ) )
2023-10-31 14:02:14 +07:00
return res . writeHead ( 200 , {
2023-11-13 18:32:29 +07:00
"Content-Type" : "application/json" ,
"Access-Control-Allow-Origin" : "*"
2023-10-31 14:02:14 +07:00
} ) . end ( JSON . stringify ( config . server _meta ) ) ;
if ( req . url === "/" ) {
2023-11-13 18:35:50 +07:00
res . writeHead ( 200 , {
"Content-Type" : "text/plain"
} ) ;
2024-05-19 23:25:29 +07:00
res . write ( "Welcome to Bostr. A nostr relay aggregator for saving mobile bandwidth usage when using nostr." ) ;
2024-05-19 22:44:09 +07:00
res . write ( "\nThis nostr bouncer is bouncing the following relays:\n\n" ) ;
2024-05-13 16:36:52 +07:00
if ( config . relays . length ) {
config . relays . forEach ( _ => {
const { raw _rx , rx , tx , f } = bouncer . getStat ( _ ) ;
res . write ( "- " + _ + ` (raw_rx: ${ raw _rx } ; rx: ${ rx } ; tx: ${ tx } ; fail: ${ f } ) ` + "\n" ) ;
} ) ;
} else if ( loadbalancerUpstreamLinks . length ) {
res . write ( "- " + loadbalancerUpstreamLinks . join ( "\n- " ) + "\n" ) ;
}
2023-10-31 20:01:22 +07:00
2024-08-01 16:19:46 +07:00
res . write ( ` \n I have ${ wss _for _everyone . clients . size + wss _for _apple . clients . size } clients currently connected to this bouncer ${ ( process . env . CLUSTERS || config . clusters ) > 1 ? " on this cluster" : "" } . \n ` ) ;
2024-04-04 21:33:11 +07:00
2024-04-05 10:35:00 +07:00
res . write ( ` \n All bouncer activities in total: ` ) ;
2024-04-06 14:44:16 +07:00
res . write ( ` \n - raw_rx: ${ globalStat . raw _rx } ` ) ;
2024-04-04 21:33:11 +07:00
res . write ( ` \n - rx: ${ globalStat . rx } ` ) ;
res . write ( ` \n - tx: ${ globalStat . tx } ` ) ;
res . write ( ` \n - fail: ${ globalStat . f } ` ) ;
res . write ( ` \n \n Statistics legends: ` ) ;
2024-04-06 14:40:31 +07:00
res . write ( ` \n - raw_rx: received events from upstream relays ` ) ;
res . write ( ` \n - rx: received events from upstream relays that has been forwarded to clients ` ) ;
res . write ( ` \n - tx: succesfully transmitted events that has been forwarded to upstream relays ` ) ;
2024-04-04 21:33:11 +07:00
res . write ( ` \n - fail: failed transmissions or upstream errors \n ` ) ;
2023-11-16 21:53:58 +07:00
if ( config ? . authorized _keys ? . length ) res . write ( "\nNOTE: This relay has configured for personal use only. Only authorized users could use this bostr relay.\n" ) ;
2024-02-23 13:11:00 +07:00
res . write ( ` \n Connect to this bouncer with nostr client: ${ serverAddr } ` ) ;
res . write ( ` \n \n - To make connection that only send whitelisted kind of events, Connect: ` ) ;
res . write ( ` \n ${ serverAddr } ?accept=0,1 ` ) ;
2024-02-18 19:13:41 +07:00
res . write ( ` \n (Will only send events with kind 0, and 1) ` ) ;
2024-02-23 13:11:00 +07:00
res . write ( ` \n \n - To make connection that do not send blacklisted kind of events, Connect: ` ) ;
res . write ( ` \n ${ serverAddr } ?reject=3,6,7 ` ) ;
2024-02-18 12:07:08 +07:00
res . write ( ` \n (Will not send events with kind 3, 6, and 7) ` ) ;
2024-02-23 13:11:00 +07:00
res . write ( ` \n \n - To make connection that override client's REQ limit, Connect: ` ) ;
2024-02-24 12:23:55 +07:00
res . write ( ` \n ${ serverAddr } ?limit=50 or ${ serverAddr } ?accurate=1&limit=50 ` ) ;
2024-02-23 13:11:00 +07:00
res . write ( ` \n (Will override REQ limit from client to 50 if exceeds) ` ) ;
2024-02-25 00:23:09 +07:00
res . write ( ` \n \n - To connect with accurate bouncing mode ${ config . pause _on _limit ? "" : " (Default)" } , Connect: ` ) ;
2024-02-24 12:23:55 +07:00
res . write ( ` \n ${ serverAddr } ?accurate=1 ` ) ;
2024-02-25 00:48:55 +07:00
res . write ( ` \n (May consume lot of bandwidths) ` ) ;
2024-02-25 00:23:09 +07:00
res . write ( ` \n \n - To connect with save mode ${ config . pause _on _limit ? " (Default)" : "" } , Connect: ` ) ;
res . write ( ` \n ${ serverAddr } ?save=1 ` ) ;
res . write ( ` \n (Saves bandwidth usage) ` ) ;
2024-05-19 22:03:24 +07:00
res . write ( ` \n \n Administrator Contact: ${ config . server _meta . contact } ` ) ;
2024-08-29 16:45:55 +07:00
res . end ( ` \n \n --- \n Powered by Bostr ( ${ version } ) - Open source Nostr bouncer \n https://codeberg.org/Yonle/bostr ` ) ;
2024-02-18 11:47:49 +07:00
} else if ( req . url . includes ( "favicon" ) && favicon ) {
2024-01-09 06:58:27 +07:00
res . writeHead ( 200 , { "Content-Type" : "image/" + config . favicon ? . split ( "." ) . pop ( ) } ) ;
res . end ( favicon ) ;
2023-10-31 14:02:14 +07:00
} else {
res . writeHead ( 404 ) . end ( "What are you looking for?" ) ;
}
} ) ;
server . on ( 'upgrade' , ( req , sock , head ) => {
2024-01-04 22:01:08 +07:00
const ip = req . headers [ "x-forwarded-for" ] ? . split ( "," ) [ 0 ] || sock . address ( ) ? . address ;
2024-07-29 19:32:37 +07:00
const ua = req . headers [ "user-agent" ]
const isApple = ua ? . length && ( ua . includes ( "CFNetwork" ) || ua . includes ( "Safari" ) ) && ! ua . includes ( "Chrome" ) && ! ua . includes ( "Firefox" )
2024-02-18 20:07:46 +07:00
if ( config . blocked _hosts && config . blocked _hosts . includes ( ip ) ) return sock . destroy ( ) ;
2024-05-19 23:09:49 +07:00
if ( connectedHosts . filter ( i => i === ip ) . length >= ( config . max _conn _per _ip || Infinity ) ) return sock . destroy ( ) ;
connectedHosts . push ( ip ) ;
2024-01-04 22:01:08 +07:00
2024-07-29 19:32:37 +07:00
let the _wss
if ( isApple ) the _wss = wss _for _apple ;
else the _wss = wss _for _everyone ;
the _wss . handleUpgrade ( req , sock , head , _ => bouncer . handleConnection ( _ , req , _ => delete connectedHosts [ connectedHosts . indexOf ( ip ) ] ) ) ;
2023-10-31 14:02:14 +07:00
} ) ;
const listened = server . listen ( process . env . PORT || config . port , config . address || "0.0.0.0" , _ => {
2024-01-13 18:53:59 +07:00
log ( "Bostr is now listening on " + ` ${ server . isStandaloneHTTPS ? "wss" : "ws" } :// ` + ( config . address || "0.0.0.0" ) + ":" + config . port ) ;
2023-10-31 14:02:14 +07:00
} ) ;