Reference

Examples

Whole examples for Garry's Mod, s&box, Unity and Godot.

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.