Skip to content

Commit

Permalink
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
TGRHavoc committed Apr 14, 2020
1 parent 42083c2 commit 735df7b
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 37 deletions.
12 changes: 7 additions & 5 deletions __resource.lua → fxmanifest.lua
@@ -1,8 +1,9 @@
resource_manifest_version "44febabe-d386-4d18-afbe-5e627f4af937"
fx_version "bodacious"
game "gta5"

dependency "yarn"
dependency "webpack"
webpack_config "webpack.config.js"
author "TGR_Havoc"
description ""
version "1.0.0"

client_scripts{
"client/client.lua",
Expand All @@ -21,6 +22,7 @@ exports {
}

server_scripts{
"dist/livemap.js",
--"src/server.js",
"server/sse.lua",
"server/update_check.lua"
}
7 changes: 4 additions & 3 deletions package.json
Expand Up @@ -2,7 +2,7 @@
"name": "fivem-live_map",
"version": "2.3.4",
"description": "The FiveM resource to get Havoc's LiveMap working#",
"main": "index.js",
"main": "src/server.js",
"scripts": {
"lint": "eslint ./src/**/*",
"test": "echo \"Error: no test specified\" && exit 1"
Expand All @@ -18,9 +18,10 @@
},
"homepage": "https://github.com/TGRHavoc/live_map#readme",
"dependencies": {
"koa": "^2.7.0",
"koa-body": "^4.1.0",
"@citizenfx/http-wrapper": "^0.2.2",
"koa": "^2.11.0",
"koa-router": "^7.4.0",
"koa-sse-stream": "^0.2.0",
"simple-console-logger": "^2.0.2",
"socket.io": "^2.2.0",
"webpack": "^4.33.0",
Expand Down
45 changes: 45 additions & 0 deletions server/sse.lua
@@ -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)
1 change: 0 additions & 1 deletion server/update_check.lua
Expand Up @@ -5,7 +5,6 @@
You should have received a copy of the GNU General Public License
along with this program in the file "LICENSE". If not, see <http://www.gnu.org/licenses/>.
]]

local url = "https://raw.githubusercontent.com/TGRHavoc/live_map/master/version.json"
local version = "2.1.7"
local latest = true
Expand Down
4 changes: 1 addition & 3 deletions src/blips.js
@@ -1,14 +1,12 @@
const log = require("simple-console-logger").getLogger("LiveMap Blips");
const fs = require("fs");
const path = require("path");

const BlipController = (SocketController) => {
let playerWhoGeneratedBlips = null;
let blips = null;
const blipFile = GetConvar("blip_file", "server/blips.json");

// Middleware to send the blips
const getBlips = (ctx) => {
const getBlips = async (ctx) => {
if (blips === null){
ctx.body = JSON.stringify({
error: "blip cache is empty"
Expand Down
14 changes: 14 additions & 0 deletions src/example.js
@@ -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);
};

44 changes: 19 additions & 25 deletions src/server.js
@@ -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());
68 changes: 68 additions & 0 deletions src/sse.js
@@ -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 });
}
};

0 comments on commit 735df7b

Please sign in to comment.