Garry's Mod (Lua)
local API = "https://www.dasmaffin.com/api/mgds/v1/projects/YOUR-PROJECT-ID"
local function post(path, body, done)
HTTP({
url = API .. path, method = "POST",
body = util.TableToJSON(body or {}), type = "application/json",
success = function(code, text) done(code, util.JSONToTable(text or "") or {}) end,
failed = function(err) print("MGDS failed: " .. err) end,
})
end
-- Anonymous: reuse the device key if there is one.
local key = file.Read("mgds_device.txt", "DATA")
post("/auth/anonymous", { deviceKey = key }, function(code, res)
if res.deviceKey then file.Write("mgds_device.txt", res.deviceKey) end
print("Signed in as player " .. tostring(res.playerId))
end)
s&box (C#)
const string Api = "https://www.dasmaffin.com/api/mgds/v1/projects/YOUR-PROJECT-ID";
// A sketch: check the names against the current s&box API reference.
async Task<string> SignInAsync()
{
var token = await Sandbox.Services.Auth.GetToken( "mgds" );
var steamId = Game.SteamId.ToString(); // the local player's SteamID64
var answer = await Http.RequestJsonAsync<Session>( Api + "/auth/sbox", "POST",
Http.CreateJsonContent( new { steamId, token } ) );
return answer.sessionToken; // keep it, and send it as Authorization: Bearer
}
record Session( string playerId, string sessionToken, string displayName, bool created );
Unity (C#)
const string Api = "https://www.dasmaffin.com/api/mgds/v1/projects/YOUR-PROJECT-ID";
IEnumerator SignInAnonymously()
{
string key = PlayerPrefs.GetString("mgds_device", null);
string json = key == null ? "{}" : $"{{\"deviceKey\":\"{key}\"}}";
using var req = new UnityWebRequest(Api + "/auth/anonymous", "POST");
req.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(json));
req.downloadHandler = new DownloadHandlerBuffer();
req.SetRequestHeader("Content-Type", "application/json");
yield return req.SendWebRequest();
var res = JsonUtility.FromJson<Session>(req.downloadHandler.text);
if (!string.IsNullOrEmpty(res.deviceKey)) PlayerPrefs.SetString("mgds_device", res.deviceKey);
}
[System.Serializable]
class Session { public string playerId, sessionToken, expiresAt, displayName, deviceKey; public bool created; }
Push messages in Unity (C#)
// Reads the stream as it arrives, line by line.
class PushStream : DownloadHandlerScript
{
readonly System.Text.StringBuilder line = new();
string name, data;
public long LastId;
public System.Action<string, string> OnEvent; // event name (the trigger), data JSON
public PushStream() : base(new byte[4096]) { }
protected override bool ReceiveData(byte[] bytes, int length)
{
foreach (char c in System.Text.Encoding.UTF8.GetString(bytes, 0, length))
{
if (c != '\n') { line.Append(c); continue; }
string l = line.ToString().TrimEnd('\r'); line.Clear();
if (l.Length == 0) { if (data != null) OnEvent?.Invoke(name ?? "message", data); name = data = null; }
else if (l.StartsWith("event: ")) name = l[7..];
else if (l.StartsWith("data: ")) data = l[6..];
else if (l.StartsWith("id: ")) LastId = long.Parse(l[4..]);
}
return true;
}
}
IEnumerator Listen(string token)
{
var stream = new PushStream { OnEvent = (trigger, json) => Debug.Log($"{trigger}: {json}") };
while (true)
{
using var req = new UnityWebRequest(Api + "/push/stream", "GET") { downloadHandler = stream, disposeDownloadHandlerOnDispose = false };
req.SetRequestHeader("Accept", "text/event-stream");
if (token != null) req.SetRequestHeader("Authorization", "Bearer " + token);
if (stream.LastId > 0) req.SetRequestHeader("Last-Event-ID", stream.LastId.ToString());
yield return req.SendWebRequest();
yield return new WaitForSeconds(3); // ended or dropped: connect again, from the last id
}
}
On WebGL, UnityWebRequest does not stream; use the browser's EventSource through a .jslib.
Push messages in Lua
With lua-http:
local request = require "http.request"
-- Listens until the stream ends; returns the last id, to call it again with.
local function listen(api, last_id, on_event)
local req = request.new_from_uri(api .. "/push/stream")
req.headers:upsert("accept", "text/event-stream")
if last_id then req.headers:upsert("last-event-id", tostring(last_id)) end
local headers, stream = assert(req:go())
local name, data
while true do
local line = stream:get_body_until("\n", true, false)
if not line then return last_id end
line = line:gsub("\r$", "")
if line == "" then
if data then on_event(name or "message", data) end
name, data = nil, nil
elseif line:sub(1, 7) == "event: " then name = line:sub(8)
elseif line:sub(1, 6) == "data: " then data = line:sub(7)
elseif line:sub(1, 4) == "id: " then last_id = tonumber(line:sub(5))
end
end
end
Godot (GDScript)
const API = "https://www.dasmaffin.com/api/mgds/v1/projects/YOUR-PROJECT-ID"
func start_dasmaffin_sign_in() -> void:
var http := HTTPRequest.new()
add_child(http)
http.request(API + "/auth/dasmaffin", ["Content-Type: application/json"], HTTPClient.METHOD_POST, "{}")
var result = await http.request_completed
var started = JSON.parse_string(result[3].get_string_from_utf8())
OS.shell_open(started.url)
# Then POST /auth/dasmaffin/poll every 2 seconds with requestId and pollToken until it answers 200 or 201.