Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
feat: failed sse attempt
I don't think I'm smart enough to implement SSE myself. This branch is my failed attempts. I say attempts, because I tried to implement it with KOA and in native LUA. Neither worked. I think the reason is because the request and response variables being passed to the SET_HTTP_HANDLER function are... Weird. All the examples I've found online implementing SSE have access to the request's socket... FiveM doesn't. And for some reason, the headers don't seem to send until the request is done (needs more research). I say this because while using the Node and Lua implementations, CURL handles it fine but, browsers just seem to hang on the request. If the headers were being sent when they're supposed to, this wouldn't happen. The browser should see that it's a "text/event-stream" and handle it as such. Rather than "downloading" the resource.
- Loading branch information
Showing
8 changed files
with
158 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| clientId = 0 | ||
| clients = {} | ||
|
|
||
| function sendUpdate() | ||
| CreateThread(function() | ||
| local t = 100 | ||
| print("doing while") | ||
| while (t > 0) do | ||
| print(t) | ||
| ---print(dump(clients)) | ||
| for id,res in pairs(clients) do | ||
| --print("Sending data to " .. id) | ||
| res.write("data: test" .. t .. "\n\n") | ||
| end | ||
| t = t-1 | ||
| Wait(500) | ||
| end | ||
|
|
||
| for id,res in pairs(clients) do | ||
| --print("Sending data to " .. id) | ||
| res["end"]() | ||
| end | ||
|
|
||
| end) | ||
| end | ||
|
|
||
| SetHttpHandler(function(req, res) | ||
|
|
||
| print("http handler") | ||
| -- Restrict the origin if set, otherwise allow everyone | ||
| res.writeHead(200, { | ||
| ["Access-Control-Allow-Origin"] = GetConvar("livemap_access_control", "*"), | ||
| ["Connection"] = "keep-alive", | ||
| ["Content-Type"] = "text/event-stream", | ||
| ["Cache-Control"] = "no-cache", | ||
| }) | ||
|
|
||
| res.write("\n") | ||
| print(clientId .. " connected") | ||
| clients[clientId] = res | ||
| clientId = clientId +1 | ||
| end) | ||
|
|
||
|
|
||
| SetTimeout(5000, sendUpdate) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| var source = window.source = new EventSource("http://localhost:30120/live_map/sse"); | ||
|
|
||
| source.onmessage = function(e){ | ||
| console.log("TEST EVENT", e); | ||
| }; | ||
|
|
||
| source.onopen = function (e) { | ||
| console.log("EVENTSOURCE OPENED", e); | ||
| }; | ||
|
|
||
| source.onerror = function (e) { | ||
| console.log("error", e); | ||
| }; | ||
|
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,51 +1,45 @@ | ||
| const Koa = require("koa"); | ||
| const koaBody = require("koa-body"); | ||
| const httpCallback = require("@citizenfx/http-wrapper"); | ||
| const Router = require("koa-router"); | ||
| const WS = require("ws"); | ||
| const http = require("http"); | ||
| const logger = require("simple-console-logger"); | ||
|
|
||
| const sse = require("./src/sse"); | ||
|
|
||
| const app = new Koa(); | ||
| const server = http.createServer(app.callback()); | ||
| const router = new Router(); | ||
| const wss = new WS.Server({ server }); | ||
|
|
||
| const debugLevel = GetConvar("livemap_debug_level", "warn"); | ||
| const debugLevel = GetConvar("livemap_debug_level", "[all]"); | ||
| const access = GetConvar("livemap_access_control", "*"); | ||
|
|
||
| logger.configure({ | ||
| level: debugLevel | ||
| }); | ||
| const log = logger.getLogger("LiveMap"); | ||
|
|
||
| router.use(async (ctx, next) => { | ||
| app.use(async (ctx, next) => { | ||
| log.debug("", ctx.res.cfxReq); | ||
| ctx.response.append("Access-Control-Allow-Origin", access); | ||
| next(); | ||
| }); | ||
|
|
||
| const SocketController = require("LivemapSocketController")(access); | ||
| SocketController.hook(wss); | ||
| sse(router); | ||
|
|
||
| //const SocketController = require("LivemapSocketController")(access); | ||
| //SocketController.hook(wss); | ||
|
|
||
| require("LivemapEventsWrapper")(SocketController); | ||
| //require("LivemapEventsWrapper")(SocketController); | ||
|
|
||
| // Passing the SocketController through as we need it to keep blips updated on the client. Plus, I can't seem to just "require" the server in the BlipController. | ||
| const BlipController = require("LivemapBlipController")(SocketController); | ||
| // const BlipController = require("LivemapBlipController")(); | ||
|
|
||
| router.get("/blips", BlipController.getBlips); | ||
| router.get("/blips.json", BlipController.getBlips); | ||
| // router.get("/blips", BlipController.getBlips); | ||
| // router.get("/blips.json", BlipController.getBlips); | ||
|
|
||
| app.use(koaBody( | ||
| { | ||
| patchKoa: true, | ||
| })) | ||
| .use(router.routes()) | ||
| .use(router.allowedMethods()); | ||
| router.get("/test", async (ctx) => { | ||
| ctx.body = `Test ${ctx}`; | ||
| }); | ||
|
|
||
| // Start a server on the socket_port... | ||
| const port = GetConvarInt("socket_port", 30121); | ||
| app.use(router.routes()); | ||
|
|
||
| server.listen(port, function listening() { | ||
| log.info("Listening on %d", port); | ||
| }); | ||
|
|
||
| setInterval(SocketController.SendPlayerData, GetConvarInt("livemap_milliseconds", 500)); // Default = half a second. | ||
| httpCallback.setHttpCallback(app.callback()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| const PassThrough = require("stream").PassThrough; | ||
| const send = ({clientId, type, msg}) => { | ||
| clients[clientId].res.write(`event: ${type || "none"}\ndata: ${msg}\n\n`); | ||
| //clients[clientId].res.end(); | ||
| }; | ||
|
|
||
| const sseLog = logger.getLogger("LiveMap SSE"); | ||
|
|
||
| var clientId = 0; | ||
| var clients = {}; | ||
|
|
||
| module.exports = function(router){ | ||
| router.get("/sse", async (ctx) => { | ||
|
|
||
| try{ | ||
| const stream = new PassThrough(); | ||
|
|
||
| ctx.type = "text/event-stream"; | ||
| ctx.body = stream; | ||
|
|
||
| //ctx.res.statusCode = 200; | ||
| ctx.res.setHeader("Content-Type", "text/event-stream"); | ||
| ctx.res.setHeader("Cache-Control", "no-cache, no-transform"); | ||
| ctx.res.setHeader("Connection", "keep-alive"); | ||
| ctx.res.setHeader("Encoding", "none"); | ||
|
|
||
| sseLog.debug("Writing newline..."); | ||
| //ctx.res.cfxRes.writeOut(":ok"); | ||
| ctx.res.write(":ok\n"); | ||
| //ctx.res.cfxRes.send(); | ||
|
|
||
| (function (clientId) { | ||
| sseLog.debug("Saving client ctx"); | ||
| clients[clientId] = ctx; | ||
|
|
||
| sseLog.debug("Hooking events for clean exiting"); | ||
| ["close", "finish", "error"].forEach(evt => { | ||
| ctx.res.on(evt, () => { | ||
| sseLog.debug(`${evt} for ${clientId}`); | ||
| ctx.res.end(); | ||
| delete clients[clientId]; | ||
| }); | ||
| }); | ||
| setTimeout(() => { | ||
| sseLog.debug("Sending test event"); | ||
| sendEvent("test", { this: "is", some: true, data: 10 }); | ||
| }, 1000); | ||
| })(++clientId); | ||
|
|
||
| //ctx.res.end(); | ||
|
|
||
| }catch(e){ | ||
| sseLog.fatal(e); | ||
| } | ||
| }); | ||
| }; | ||
|
|
||
| module.exports.sendEvent = sendEvent = function(type, data) { | ||
| sseLog.debug(`Clients: ${Object.keys(clients)}`); | ||
|
|
||
| if (typeof(data) !== "string"){ | ||
| data = JSON.stringify(data); | ||
| } | ||
|
|
||
| for (clientId in clients){ | ||
| send({ clientId, type, msg: data }); | ||
| } | ||
| }; |