@@ -9,11 +9,16 @@ import 'package:reyveld/scripting/lua.dart';
99import 'package:reyveld/security/authveld.dart' ;
1010import 'package:chalkdart/chalkstrings.dart' ;
1111import 'package:cli_spin/cli_spin.dart' ;
12+ import 'package:reyveld/uuid.dart' ;
1213import 'package:rxdart/rxdart.dart' ;
14+ import 'package:shelf_web_socket/shelf_web_socket.dart' ;
1315import 'package:version/version.dart' ;
1416import 'package:reyveld/extensions.dart' ;
1517import 'package:reyveld/extras.dart' ;
1618import 'package:http/http.dart' as http;
19+ import 'package:shelf_router/shelf_router.dart' ;
20+ import 'package:shelf/shelf.dart' ;
21+ import 'package:shelf/shelf_io.dart' as io;
1722
1823typedef ReyveldSession = (Lua , WebSocket );
1924
@@ -29,9 +34,6 @@ Future<void> runServer() async {
2934 exit (0 );
3035 }
3136
32- /// The reroute version is the version that this Reyveld executable is rerouting to.
33- Version rerouteVersion = await getMostRecentVersion ();
34-
3537 isRunningSpinner.success ("Ready to Start!" .skyBlue);
3638
3739 File lockFile =
@@ -41,185 +43,100 @@ Future<void> runServer() async {
4143 CliSpin (spinner: CliSpinners .bounce).start ("Starting Server..." .skyBlue);
4244
4345 await Reyveld .deleteTempFiles ();
44-
45- final server = await HttpServer .bind (InternetAddress .anyIPv4, 7274 );
46- serverSpinner.success ("Server Started!" .skyBlue);
47-
48- Map <String , ReyveldSession > sessions = {};
4946 Pool luaPool = Pool (int .parse (await Reyveld .getPerformanceOption ("LUAPOOL" )),
5047 timeout: parseDurationFromString (
5148 await Reyveld .getPerformanceOption ("LUAPOOL_TIMEOUT" )));
5249
53- await for (HttpRequest request in server) {
54- try {
55- Version requestedVersion;
50+ HttpServer ? server;
5651
57- try {
58- requestedVersion = Version .parse (request.uri.pathSegments.first);
59- } catch (e) {
60- requestedVersion = rerouteVersion;
61- }
62-
63- /// Check if the requested version is the same as this program's version.
64- if (requestedVersion != Reyveld .version) {
65- /// If not, check if the requested version is running.
66- if (await isRunning (requestedVersion)) {
67- /// The requested version is running.
68- request.response.statusCode = HttpStatus .movedPermanently;
69- await request.response.close ();
70- continue ;
71- } else {
72- /// The requested version is not running.
73- request.response.statusCode = HttpStatus .notFound;
74- await request.response.close ();
75- Reyveld .talker.error (
76- "Version not found. ${request .uri .pathSegments .firstOrNull } not found." );
77- continue ;
78- }
79- }
52+ final app = Router ();
8053
81- // Has the client requested a specific version?
82- bool definedVersion = true ;
54+ app.get ("/authveld" , (Request request) async {
55+ if (! request.url.queryParameters.containsKey ("ticket" )) {
56+ return Response .badRequest (body: "No ticket provided." );
57+ }
58+ return Response .ok (
59+ AuthVeld .authorizePage (request.url.queryParameters["ticket" ]! )
60+ .codeUnits,
61+ headers: {"Content-Type" : "text/html" });
62+ });
63+
64+ app.get ("/heartbeat" , (Request request) async {
65+ Reyveld .talker
66+ .verbose ("Heartbeat checked at ${DateTime .now ().toIso8601String ()}." );
67+ return Response .ok ("OK" .codeUnits);
68+ });
69+
70+ app.get ('/lua' , webSocketHandler ((webSocket, _) {
71+ final id = generateUUID ();
72+ final lua = Lua (socket: webSocket);
73+ Reyveld .printToConsole ("(SID:$id ) Client connected." .skyBlue);
74+ Reyveld .talker.verbose ("(SID:$id ) Client connected." );
75+ // Listen for incoming messages
76+ webSocket.stream.listen ((data) async {
8377 try {
84- Version .parse (request.uri.pathSegments.first);
85- } catch (_) {
86- definedVersion = false ;
87- }
88- final requestUrl =
89- request.uri.pathSegments.sublist (definedVersion ? 1 : 0 ).join ('/' );
90-
91- Reyveld .talker.verbose ("Request: $requestUrl ; ${request .method }" );
92-
93- /// If the requested version is the same as this program's version, continue as normal.
94- if (request.method == "GET" ) {
95- switch (requestUrl) {
96- case "authveld" :
97- request.response.headers.contentType = ContentType .html;
98- if (! request.uri.queryParameters.containsKey ("ticket" )) {
99- request.response.statusCode = HttpStatus .badRequest;
100- await request.response.close ();
101- continue ;
102- }
103- request.response.add (
104- AuthVeld .authorizePage (request.uri.queryParameters["ticket" ]! )
105- .codeUnits);
106- await request.response.close ();
107- case "heartbeat" :
108- request.response.statusCode = HttpStatus .ok;
109- request.response.add ("OK" .codeUnits);
110- Reyveld .talker.verbose (
111- "Heartbeat checked at ${DateTime .now ().toIso8601String ()}." );
112- await request.response.close ();
113- case "lua" :
114-
115- /// We save the client's session ID here, as once the Garbage Collector collects the request, the ID will be gone as well.
116- final id = request.session.id;
117- if (WebSocketTransformer .isUpgradeRequest (request)) {
118- final socket = await WebSocketTransformer .upgrade (request);
119- sessions[id] = (Lua (socket: socket), socket);
120- Reyveld .printToConsole ('(SID:$id ) Client connected.' .skyBlue);
121- Reyveld .talker.info ("(SID:$id ) Client connected." );
122- socket.listen ((data) async {
123- try {
124- /// Run the request and get the result.
125- Reyveld .talker.verbose ("(SID:$id ) Received request." );
126- Reyveld .talker.verbose ("(SID:$id ) Request:\n $data " );
127- final result = await luaPool.withResource (
128- () async => await sessions[id]! .$1.run (data));
129- socket.add (SocketEvent .completed (result.result,
130- pid: result.processId ?? "" )
131- .toString ());
132- Reyveld .talker.verbose (
133- "(SID:$id , PID:${result .processId ?? "" }) Completed request." );
134- } catch (e, st) {
135- Reyveld .printToConsole (
136- "There was a crash on a request, please check the log folder (${Reyveld .appDataPath }/logs) for more information."
137- .red);
138- socket.add (SocketEvent .error (e).toString ());
139- Reyveld .talker.error ("(SID:$id )" , e, st);
140- }
141- }, onDone: () {
142- Reyveld .printToConsole ('(SID:$id ) Client disconnected' .skyBlue);
143- Reyveld .talker.info ("(SID:$id ) Client disconnected." );
144- socket.close ();
145- sessions.remove (id);
146- return ;
147- }, cancelOnError: false );
148- } else {
149- request.response
150- ..statusCode = HttpStatus .forbidden
151- ..close ();
152- }
153- case "permissions/details" :
154- request.response.headers.contentType = ContentType .html;
155- request.response.add (
156- AuthVeld .getDetailsPage (request.uri.queryParameters["ticket" ]! )
157- .codeUnits);
158- await request.response.close ();
159- default :
160- request.response.statusCode = HttpStatus .notFound;
161- await request.response.close ();
162- }
163- } else if (request.method == "POST" ) {
164- switch (requestUrl) {
165- case "authorize" :
166- final origin =
167- request.headers["origin" ] ?? request.headers['referer' ];
168- if (origin == null || origin.isEmpty) {
169- request.response.statusCode = HttpStatus .forbidden;
170- await request.response.close ();
171- continue ;
172- }
173- await AuthVeld .authorize (request.uri.queryParameters["ticket" ]! );
174- request.response.headers.contentType = ContentType .json;
175- request.response.add (jsonEncode ({"allowed" : true }).codeUnits);
176- await request.response.close ();
177- case "deauthorize" :
178- final origin =
179- request.headers["origin" ] ?? request.headers['referer' ];
180- if (origin == null || origin.isEmpty) {
181- request.response.statusCode = HttpStatus .forbidden;
182- await request.response.close ();
183- continue ;
184- }
185- await AuthVeld .deauthorize (request.uri.queryParameters["ticket" ]! );
186- request.response.headers.contentType = ContentType .json;
187- request.response.add (jsonEncode ({"allowed" : false }).codeUnits);
188- await request.response.close ();
189- default :
190- request.response.statusCode = HttpStatus .notFound;
191- await request.response.close ();
192- }
193- } else if (request.method == "OPTIONS" ) {
194- switch (requestUrl) {
195- case "close" :
196- for (final session in sessions.entries) {
197- await session.value.$1.awaitForCompletion ().then ((_) async {
198- await session.value.$2
199- .close (WebSocketStatus .goingAway, "Server closed." );
200- });
201- }
202- request.response.statusCode = HttpStatus .ok;
203- request.response.headers.contentType = ContentType .json;
204- request.response.add (jsonEncode ({}).codeUnits);
205- await request.response.close ();
206- await server.close ();
207- await Reyveld .deleteTempFiles ();
208- exit (0 );
209- default :
210- request.response.statusCode = HttpStatus .notFound;
211- await request.response.close ();
212- }
78+ Reyveld .talker.verbose ("(SID:$id ) Received data:\n $data " );
79+ final result =
80+ await luaPool.withResource (() async => await lua.run (data));
81+ webSocket.sink.add (
82+ SocketEvent .completed (result.result, pid: result.processId ?? "" )
83+ .toString ());
84+ } catch (e, st) {
85+ Reyveld .printToConsole (
86+ "There was a crash on a websocket, please check the log folder (${Reyveld .appDataPath }/logs) for more information."
87+ .red);
88+ webSocket.sink.add (SocketEvent .error (e).toString ());
89+ Reyveld .talker.error ("(SID:$id )" , e, st);
21390 }
214- } catch (e, st ) {
91+ }, onError : (e) {
21592 Reyveld .printToConsole (
21693 "There was a crash on a websocket, please check the log folder (${Reyveld .appDataPath }/logs) for more information."
21794 .red);
218- Reyveld .talker.critical ("Crash Handler" , e, st);
95+ Reyveld .talker.error ("(SID:$id )" , e);
96+ }, onDone: () {
97+ Reyveld .printToConsole ("(SID:$id ) Client disconnected." .orange);
98+ Reyveld .talker.verbose ("(SID:$id ) Client disconnected." );
99+ });
100+ }));
101+
102+ app.get ("/permissions/details" , (Request request) async {
103+ if (! request.url.queryParameters.containsKey ("ticket" )) {
104+ return Response .badRequest (body: "No ticket provided." );
219105 }
220- }
221- await lockFile.delete ();
222- exit (0 );
106+ return Response .ok (
107+ AuthVeld .getDetailsPage (request.url.queryParameters["ticket" ]! )
108+ .codeUnits,
109+ headers: {"Content-Type" : "text/html" });
110+ });
111+
112+ app.post ("/authorize" , (Request request) async {
113+ final origin = request.headers["origin" ] ?? request.headers['referer' ];
114+ if (origin == null || origin.isEmpty) {
115+ return Response .forbidden ("No origin provided in request." );
116+ }
117+ await AuthVeld .authorize (origin);
118+
119+ return Response .ok (jsonEncode ({"allowed" : true }).codeUnits);
120+ });
121+
122+ app.post ("/deauthorize" , (Request request) async {
123+ final origin = request.headers["origin" ] ?? request.headers['referer' ];
124+ if (origin == null || origin.isEmpty) {
125+ return Response .forbidden ("No origin provided in request." );
126+ }
127+ await AuthVeld .deauthorize (origin);
128+
129+ return Response .ok (jsonEncode ({"allowed" : true }).codeUnits);
130+ });
131+
132+ final versionRedirect = createMiddleware (requestHandler: rerouteVersion);
133+
134+ final handler =
135+ const Pipeline ().addMiddleware (versionRedirect).addHandler (app.call);
136+
137+ server = await io.serve (handler, "127.0.0.1" , 7274 );
138+
139+ serverSpinner.success ("Server Started!" .skyBlue);
223140}
224141
225142/// This function checks if the version of this Reyveld executable is already running.
@@ -262,3 +179,44 @@ Future<Version> getMostRecentVersion() async {
262179 }
263180 return currentVersion;
264181}
182+
183+ /// This function reroutes a request to the most recent version, or the requested version if it exists.
184+ ///
185+ /// For example, if there are two versions of Reyveld running, lets say 1.0.0 and 1.0.1, and the request doesn't specify a version;
186+ /// then by default it will reroute to the most recent version, which is 1.0.1.
187+ ///
188+ /// If the request does specify a version, then it attempt to reroute to that version, however it must be running for it to be rerouted.
189+ Future <Response ?> rerouteVersion (Request request) async {
190+ Version requestedVersion;
191+ Version defaultVersion = await getMostRecentVersion ();
192+
193+ // Has the client requested a specific version?
194+ bool definedVersion = true ;
195+ try {
196+ requestedVersion = Version .parse (request.url.pathSegments.first);
197+ } catch (_) {
198+ definedVersion = false ;
199+ requestedVersion = defaultVersion;
200+ }
201+
202+ /// Check if the requested version is the same as this program's version.
203+ if (requestedVersion != Reyveld .version) {
204+ /// If not, check if the requested version is running.
205+ if (await isRunning (requestedVersion)) {
206+ /// The requested version is running.
207+ return Response .movedPermanently (request.url);
208+ } else {
209+ /// The requested version is not running.
210+ Reyveld .talker.error (
211+ "Version not found. ${request .url .pathSegments .firstOrNull } not found." );
212+ return Response .notFound (request.url);
213+ }
214+ }
215+
216+ final requestUrl =
217+ request.url.pathSegments.sublist (definedVersion ? 1 : 0 ).join ('/' );
218+
219+ request.change (path: requestUrl);
220+
221+ return null ;
222+ }
0 commit comments