FIX - stabilize CEF media capture and uploads

This commit is contained in:
DerEchteAlec
2026-08-12 18:59:14 +02:00
parent c3b7a0871a
commit ff7e00596b
15 changed files with 912 additions and 179 deletions
+55 -22
View File
@@ -3,14 +3,32 @@ SkyPhoneMedia = {}
local pending_uploads = {}
local pending_deletes = {}
local allowed_remote_mimes = {
photo = {
["image/jpeg"] = true,
["image/png"] = true,
["image/webp"] = true,
},
video = {
["video/mp4"] = true,
["video/webm"] = true,
},
}
local function media_config()
return Config.Media.FiveManage
end
local function media_api_key()
local convar = media_config().ApiKeyConvar
if type(convar) ~= "string" or convar == "" then
return ""
end
return GetConvar(convar, "")
end
local function api_configured()
local api_key = media_config().ApiKey
return type(api_key) == "string" and api_key ~= "" and api_key ~= "YOUR_API_TOKEN"
return media_api_key() ~= ""
end
local function http_request(url, method, body, headers, timeout_ms)
@@ -63,7 +81,7 @@ local function request_presigned_url()
tostring(config.BaseUrl):gsub("/+$", "") .. "/presigned-url",
"GET",
"",
{ ["Authorization"] = config.ApiKey },
{ ["Authorization"] = media_api_key() },
tonumber(config.RequestTimeoutMs) or 10000
)
local data, response_error = decode_response(response)
@@ -86,7 +104,7 @@ local function get_remote_file(remote_id)
("%s/%s"):format(tostring(config.BaseUrl):gsub("/+$", ""), remote_id),
"GET",
"",
{ ["Authorization"] = config.ApiKey },
{ ["Authorization"] = media_api_key() },
tonumber(config.RequestTimeoutMs) or 10000
)
return decode_response(response)
@@ -101,7 +119,7 @@ local function delete_remote_file(remote_id)
("%s/%s"):format(tostring(config.BaseUrl):gsub("/+$", ""), remote_id),
"DELETE",
"",
{ ["Authorization"] = config.ApiKey },
{ ["Authorization"] = media_api_key() },
tonumber(config.RequestTimeoutMs) or 10000
)
if response.status < 200 or response.status >= 300 then
@@ -153,7 +171,7 @@ function SkyPhoneMedia.ResolveOwnedMedia(source, media_id, media_type)
params[#params + 1] = value
end
local rows = Bridge.Database.Query(([[
SELECT `url`, `media_type` FROM `sky_phone_media`
SELECT `url`, `media_type`, `mime_type` FROM `sky_phone_media`
WHERE `id` = ? AND %s
LIMIT 1
]]):format(condition), params)
@@ -172,7 +190,7 @@ function SkyPhoneMedia.ResolveOwnedMedia(source, media_id, media_type)
)
return nil, "invalid_attachment"
end
return media.url
return media.url, nil, media.mime_type
end
local function upload_result(source, correlation_id, success, error_code, media)
@@ -224,18 +242,29 @@ local function verify_remote_upload(state, remote_id, uploaded_url)
if remote.url ~= uploaded_url and remote.originalUrl ~= uploaded_url then
return nil, "invalid_upload"
end
local remote_type = tostring(remote.type or remote.mimeType or ""):lower()
local remote_mime = tostring(remote.mimeType or ""):lower():match("^%s*([^;%s]+)") or ""
local remote_type = tostring(remote.type or ""):lower():match("^%s*([^;%s]+)") or ""
if remote_mime == "" and allowed_remote_mimes[state.media_type][remote_type] then
remote_mime = remote_type
end
if remote_type == "" then
remote_type = remote_mime
end
if state.media_type == "photo" and remote_type ~= "" and not remote_type:find("image", 1, true) then
return nil, "invalid_media_type"
end
if state.media_type == "video" and remote_type ~= "" and not remote_type:find("video", 1, true) then
return nil, "invalid_media_type"
end
if remote_mime ~= "" and not allowed_remote_mimes[state.media_type][remote_mime] then
return nil, "invalid_media_type"
end
local metadata = parse_metadata(remote.metadata)
if not metadata or metadata.captureToken ~= state.capture_token then
return nil, "invalid_upload_token"
end
return {
mime_type = allowed_remote_mimes[state.media_type][remote_mime] and remote_mime or state.mime_type,
remote_id = remote_id,
url = remote.url or uploaded_url,
}
@@ -255,7 +284,7 @@ Bridge.Callbacks.Register("sky_phone:gallery:list", function(source, data)
if not owner then
return error_response
end
data = data or {}
data = type(data) == "table" and data or {}
local limit = math.max(1, math.min(math.floor(tonumber(data.limit) or Config.Media.PageSize), 100))
local offset = math.max(0, math.floor(tonumber(data.offset) or 0))
local media_type = data.mediaType
@@ -342,7 +371,7 @@ Bridge.Callbacks.Register("sky_phone:messages:gifs", function(source, data)
if #query > 60 or offset < 0 or offset > 500 then
return { success = false, error = "invalid_request" }
end
local api_key = Config.Media.GiphyApiKey
local api_key = GetConvar(Config.Media.GiphyApiKeyConvar, "")
if api_key == "" then
return { success = false, error = "gif_provider_unconfigured" }
end
@@ -420,7 +449,7 @@ end)
RegisterNetEvent("sky_phone:media:request-upload", function(data)
local src = source
data = data or {}
data = type(data) == "table" and data or {}
local correlation_id = data.correlationId
local media_type = data.mediaType
if type(correlation_id) ~= "string" or #correlation_id > 80
@@ -454,6 +483,9 @@ RegisterNetEvent("sky_phone:media:request-upload", function(data)
capture_token = capture_token,
correlation_id = correlation_id,
media_type = media_type,
mime_type = media_type == "video" and "video/webm"
or ({ png = "image/png", webp = "image/webp" })[tostring(Config.Media.Photo.Encoding):lower()]
or "image/jpeg",
owner = owner,
source = src,
}
@@ -474,7 +506,7 @@ end)
RegisterNetEvent("sky_phone:media:complete-upload", function(data)
local src = source
data = data or {}
data = type(data) == "table" and data or {}
local request_id = data.requestId
local state = type(request_id) == "string" and pending_uploads[request_id] or nil
if not state or state.source ~= src or state.completing then
@@ -496,14 +528,14 @@ RegisterNetEvent("sky_phone:media:complete-upload", function(data)
local result
if owner.account_id then
result = Bridge.Database.Query([[
INSERT INTO `sky_phone_media` (`account_id`, `device_imei`, `url`, `remote_id`, `media_type`)
VALUES (?, NULL, ?, ?, ?)
]], { owner.account_id, verified.url, verified.remote_id, state.media_type })
INSERT INTO `sky_phone_media` (`account_id`, `device_imei`, `url`, `remote_id`, `media_type`, `mime_type`)
VALUES (?, NULL, ?, ?, ?, ?)
]], { owner.account_id, verified.url, verified.remote_id, state.media_type, verified.mime_type })
else
result = Bridge.Database.Query([[
INSERT INTO `sky_phone_media` (`account_id`, `device_imei`, `url`, `remote_id`, `media_type`)
VALUES (NULL, ?, ?, ?, ?)
]], { owner.imei, verified.url, verified.remote_id, state.media_type })
INSERT INTO `sky_phone_media` (`account_id`, `device_imei`, `url`, `remote_id`, `media_type`, `mime_type`)
VALUES (NULL, ?, ?, ?, ?, ?)
]], { owner.imei, verified.url, verified.remote_id, state.media_type, verified.mime_type })
end
pending_uploads[request_id] = nil
local media_id = type(result) == "number" and result or (type(result) == "table" and tonumber(result.insertId))
@@ -522,7 +554,7 @@ end)
RegisterNetEvent("sky_phone:media:cancel-upload", function(data)
local src = source
local request_id = data and data.requestId
local request_id = type(data) == "table" and data.requestId or nil
local state = type(request_id) == "string" and pending_uploads[request_id] or nil
if state and state.source == src and not state.completing then
pending_uploads[request_id] = nil
@@ -532,7 +564,7 @@ end)
RegisterNetEvent("sky_phone:media:fail-upload", function(data)
local src = source
local request_id = data and data.requestId
local request_id = type(data) == "table" and data.requestId or nil
local state = type(request_id) == "string" and pending_uploads[request_id] or nil
if not state or state.source ~= src or state.completing then
return
@@ -550,7 +582,7 @@ end)
RegisterNetEvent("sky_phone:media:delete", function(data)
local src = source
data = data or {}
data = type(data) == "table" and data or {}
local correlation_id = data.correlationId
local media_id = tonumber(data.id)
if type(correlation_id) ~= "string" or #correlation_id > 80 or not media_id then
@@ -646,6 +678,7 @@ AddEventHandler("playerDropped", function()
end)
if not api_configured() then
print("^3[sky_phone] Camera and Gallery uploads are disabled until Config.Media.FiveManage.ApiKey is set in config/media.lua.^7")
print(("^3[sky_phone] Camera and Gallery uploads are disabled until the %s server convar is set.^7")
:format(tostring(media_config().ApiKeyConvar)))
end
end)