mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 03:35:56 +02:00
feat(mods): add mod.postLog one-way log reporting to a manifest-declared URL
This commit is contained in:
@@ -1094,6 +1094,23 @@ function Loader:_api(mod)
|
||||
return { available = function() return false end,
|
||||
get = refuse, poll = refuse, release = refuse, cancel = refuse }
|
||||
end)(),
|
||||
-- One-way crash-log reporting to the https URL the manifest declares in
|
||||
-- log_url. The destination is reviewed at load, not chosen per call, so
|
||||
-- a mod cannot aim this at arbitrary hosts; the response body is never
|
||||
-- returned, and the worker pool bounds the transfer. Same handle/poll/
|
||||
-- release shape as mod.fetch, so mod.job's sibling patterns carry over.
|
||||
postLog = (function()
|
||||
if mod.manifest.permissionSet.network and mod.manifest.log_url then
|
||||
return function(_, body, opts)
|
||||
return Net.postLog(loader, modId, mod.manifest.log_url, body, opts)
|
||||
end
|
||||
end
|
||||
local function refuse()
|
||||
error(('[%s] mod.postLog needs the "network" permission and a '
|
||||
.. "log_url in manifest.json"):format(modId), 2)
|
||||
end
|
||||
return refuse
|
||||
end)(),
|
||||
-- Background compute, behind the "background" permission. The worker
|
||||
-- rebuilds this mod's sandbox before loading the script, so a job is the
|
||||
-- one thing love.thread is not: off the main thread without a Lua state
|
||||
|
||||
@@ -308,6 +308,24 @@ function Manifest.validate(raw, path)
|
||||
|
||||
local github = Manifest.parseGithub(raw.github)
|
||||
|
||||
-- log_url: the mod's one-way crash-log reporting destination. https-only,
|
||||
-- declared in the manifest so the engine reviews the target at load instead
|
||||
-- of trusting per-call URLs from gameplay code, and gated on the `network`
|
||||
-- permission the mod must also declare. api 1 mods never carry it: it is a
|
||||
-- load violation, not a warning, because a postLog-capable mod that does not
|
||||
-- opt in to networking is a bug in the manifest itself.
|
||||
local logUrl = nil
|
||||
if raw.log_url ~= nil then
|
||||
if strict and not permissionSet.network then
|
||||
violation(strict, raw.id, "log_url requires the network permission")
|
||||
elseif strict and (type(raw.log_url) ~= "string"
|
||||
or not raw.log_url:match("^https://")) then
|
||||
violation(strict, raw.id, "log_url must be an https:// URL")
|
||||
elseif strict then
|
||||
logUrl = raw.log_url
|
||||
end
|
||||
end
|
||||
|
||||
assert(raw.experimental == nil or type(raw.experimental) == "boolean",
|
||||
"experimental must be a boolean")
|
||||
local experimental = raw.experimental == true
|
||||
@@ -414,6 +432,7 @@ function Manifest.validate(raw, path)
|
||||
affects_link = affectsLink,
|
||||
permissions = permissions,
|
||||
permissionSet = permissionSet,
|
||||
log_url = logUrl,
|
||||
options_schema = optionalFile(raw.options_schema, "options_schema"),
|
||||
assets_transforms = optionalFile(raw.assets_transforms, "assets_transforms"),
|
||||
required_imports = requiredImports,
|
||||
|
||||
@@ -32,6 +32,9 @@ local Net = {}
|
||||
Net.MAX_INFLIGHT = 4
|
||||
-- Clamp on the caller's timeout, so a mod cannot pin a worker indefinitely.
|
||||
Net.MAX_SECONDS = 30
|
||||
-- A log body ceiling. Debug logs are kilobytes, and a server operator has no
|
||||
-- reason to accept a mod uploading arbitrary megabytes to its endpoint.
|
||||
Net.MAX_BODY = 65536
|
||||
|
||||
local function fetch()
|
||||
return require("src.net.Fetch")
|
||||
@@ -99,6 +102,62 @@ function Net.get(loader, modId, url, opts)
|
||||
return handle
|
||||
end
|
||||
|
||||
-- The closed list of postLog format switches. Anything outside it is a
|
||||
-- caller bug, rejected before a job is submitted, so the surface stays
|
||||
-- exactly two shapes on the wire.
|
||||
local POST_FORMATS = { text = true, json = true }
|
||||
|
||||
-- A one-way log POST to the mod's manifest-declared log_url (https only,
|
||||
-- validated in Manifest.lua). Same shape as get(): opaque handle, per-mod
|
||||
-- in-flight ceiling, user agent naming the mod. The response body is never
|
||||
-- returned -- a postLog is fire-and-forget reporting, and the engine has no
|
||||
-- reason to hand a mod a server's reply.
|
||||
function Net.postLog(loader, modId, logUrl, body, opts)
|
||||
if type(body) ~= "string" or body == "" then
|
||||
return nil, "log body must be a non-empty string"
|
||||
end
|
||||
if #body > Net.MAX_BODY then
|
||||
return nil, ("log body too large (%d bytes, limit %d)"):format(#body, Net.MAX_BODY)
|
||||
end
|
||||
opts = type(opts) == "table" and opts or {}
|
||||
for key in pairs(opts) do
|
||||
if key ~= "format" then
|
||||
return nil, ("unknown log option %q (format is the only switch)"):format(tostring(key))
|
||||
end
|
||||
end
|
||||
local format = opts.format or "text"
|
||||
if not POST_FORMATS[format] then
|
||||
return nil, ("unknown log format %q (text and json only)"):format(tostring(format))
|
||||
end
|
||||
local denial = Net.urlDenial(logUrl)
|
||||
if denial then return nil, denial end
|
||||
local b = bucket(loader, modId)
|
||||
if inflight(b) >= Net.MAX_INFLIGHT then
|
||||
return nil, ("too many requests in flight (limit %d); poll and release "
|
||||
.. "the ones you have"):format(Net.MAX_INFLIGHT)
|
||||
end
|
||||
local payload = body
|
||||
local contentType = "text/plain"
|
||||
if format == "json" then
|
||||
local Json = require("src.link.Json")
|
||||
payload = Json.encode({
|
||||
ts = os.time(),
|
||||
mod = modId,
|
||||
format = "json",
|
||||
body = body,
|
||||
})
|
||||
contentType = "application/json"
|
||||
end
|
||||
local id = fetch().post(logUrl, payload, {
|
||||
userAgent = "gen1recomp-mod/" .. tostring(modId),
|
||||
contentType = contentType,
|
||||
maxSeconds = Net.MAX_SECONDS,
|
||||
})
|
||||
local handle = {}
|
||||
b[handle] = id
|
||||
return handle
|
||||
end
|
||||
|
||||
-- A copy of the job's state, never the engine's own table. An unknown or
|
||||
-- forged handle reads as an error rather than nil, so a mod that lost track of
|
||||
-- one cannot spin waiting on it forever.
|
||||
|
||||
Reference in New Issue
Block a user