diff --git a/include/ircd/m/device.h b/include/ircd/m/device.h index 67b51f5ca..0ff1743b4 100644 --- a/include/ircd/m/device.h +++ b/include/ircd/m/device.h @@ -52,6 +52,7 @@ struct ircd::m::device_keys > { using super_type::tuple; + using super_type::operator=; }; struct ircd::m::device @@ -74,22 +75,23 @@ struct ircd::m::device json::property, /// (s2s) Required. Identity keys for the device. - json::property, + json::property, /// (s2s) Optional display name for the device. json::property > { - using closure = std::function; - using closure_bool = std::function; - using id_closure_bool = std::function; + using closure = std::function; + using closure_bool = std::function; - static bool for_each(const user &, const id_closure_bool &); - static bool for_each(const user &, const closure_bool &); - static bool get(std::nothrow_t, const user &, const string_view &id, const closure &); - static bool get(const user &, const string_view &id, const closure &); + static bool for_each(const user &, const closure_bool &); // each device_id + static bool for_each(const user &, const string_view &id, const closure_bool &); // each property + static bool get(std::nothrow_t, const user &, const string_view &id, const string_view &prop, const closure &); + static bool get(const user &, const string_view &id, const string_view &prop, const closure &); + static bool has(const user &, const string_view &id); static bool del(const user &, const string_view &id); static bool set(const user &, const device &); using super_type::tuple; + using super_type::operator=; }; diff --git a/ircd/m.cc b/ircd/m.cc index 8cd108f51..83452eba8 100644 --- a/ircd/m.cc +++ b/ircd/m.cc @@ -1605,21 +1605,37 @@ ircd::m::device::del(const m::user &user, return function(user, id); } +bool +ircd::m::device::has(const m::user &user, + const string_view &id) +{ + using prototype = bool (const m::user &, const string_view &id); + + static mods::import function + { + "m_device", "ircd::m::device::has" + }; + + return function(user, id); +} + bool ircd::m::device::get(const m::user &user, const string_view &id, + const string_view &prop, const closure &c) { const bool ret { - get(std::nothrow, user, id, c) + get(std::nothrow, user, id, prop, c) }; if(!ret) throw m::NOT_FOUND { - "Device '%s' for user %s not found", + "Property '%s' for device '%s' for user %s not found", id, + prop, string_view{user.user_id} }; @@ -1630,30 +1646,36 @@ bool ircd::m::device::get(std::nothrow_t, const m::user &user, const string_view &id, + const string_view &prop, const closure &c) { - using prototype = bool (std::nothrow_t, const m::user &, const string_view &, const closure &); + using prototype = bool (std::nothrow_t, + const m::user &, + const string_view &, + const string_view &, + const closure &); static mods::import function { "m_device", "ircd::m::device::get" }; - return function(std::nothrow, user, id, c); + return function(std::nothrow, user, id, prop, c); } bool ircd::m::device::for_each(const m::user &user, - const id_closure_bool &c) + const string_view &id, + const closure_bool &c) { - using prototype = bool (const m::user &, const id_closure_bool &); + using prototype = bool (const m::user &, const string_view &id, const closure_bool &); static mods::import function { "m_device", "ircd::m::device::for_each" }; - return function(user, c); + return function(user, id, c); } bool diff --git a/modules/client/devices.cc b/modules/client/devices.cc index b49a95268..00e8592ef 100644 --- a/modules/client/devices.cc +++ b/modules/client/devices.cc @@ -37,6 +37,44 @@ devices_resource__unstable } }; +static void +_get_device(json::stack::object &obj, + const m::user &user, + const string_view &device_id) +{ + json::stack::member + { + obj, "device_id", device_id + }; + + m::device::get(std::nothrow, user, device_id, "display_name", [&obj] + (const string_view &value) + { + json::stack::member + { + obj, "display_name", unquote(value) + }; + }); + + m::device::get(std::nothrow, user, device_id, "last_seen_ip", [&obj] + (const string_view &value) + { + json::stack::member + { + obj, "last_seen_ip", unquote(value) + }; + }); + + m::device::get(std::nothrow, user, device_id, "last_seen_ts", [&obj] + (const string_view &value) + { + json::stack::member + { + obj, "last_seen_ts", value + }; + }); +} + static resource::response get__devices_all(client &client, const resource::request &request, @@ -62,10 +100,11 @@ get__devices_all(client &client, top, "devices" }; - m::device::for_each(request.user_id, [&devices] - (const m::device &device) + m::device::for_each(request.user_id, [&request, &devices] + (const string_view &device_id) { - devices.append(device); + json::stack::object obj{devices}; + _get_device(obj, request.user_id, device_id); return true; }); @@ -86,20 +125,32 @@ get__devices(client &client, m::id::device::buf device_id { - url::decode(device_id, request.parv[1]) + url::decode(device_id, request.parv[0]) }; - std::string buf; - m::device::get(request.user_id, device_id, [&buf] - (const m::device &device) - { - buf = json::strung{device}; - }); + if(!m::device::has(request.user_id, device_id)) + throw m::NOT_FOUND + { + "Device ID '%s' not found", device_id + }; - return resource::response + resource::response::chunked response { - client, http::OK, json::object{buf} + client, http::OK }; + + json::stack out + { + response.buf, response.flusher() + }; + + json::stack::object top + { + out + }; + + _get_device(top, request.user_id, device_id); + return {}; } resource::method @@ -113,7 +164,7 @@ method_get resource::response put__devices(client &client, - const resource::request &request) + const resource::request &request) { if(request.parv.size() < 1) throw m::NEED_MORE_PARAMS @@ -121,29 +172,15 @@ put__devices(client &client, "device_id required" }; - const m::user::room user_room - { - request.user_id - }; - m::id::device::buf device_id { url::decode(device_id, request.parv[1]) }; - user_room.get("ircd.device", device_id, [&] - (const m::event &event) - { - const json::object &content - { - at<"content"_>(event) - }; + m::device data{request.content}; + json::get<"device_id"_>(data) = device_id; - //TODO: where is your Object.update()?? - throw m::UNSUPPORTED{}; - }); - - send(user_room, request.user_id, "ircd.device", device_id, request.content); + m::device::set(request.user_id, data); return resource::response { diff --git a/modules/client/login.cc b/modules/client/login.cc index c0b7d3c8a..eafc0519a 100644 --- a/modules/client/login.cc +++ b/modules/client/login.cc @@ -90,15 +90,13 @@ post__login_password(client &client, { "device_id", device_id }, }); - const m::user::room user_room{user}; - if(!user_room.has("ircd.device", device_id)) - m::send(user_room, user_id, "ircd.device", device_id, json::members - { - { "device_id", device_id }, - { "display_name", initial_device_display_name }, - { "last_seen_ts", ircd::time() }, - { "last_seen_ip", string(remote(client)) }, - }); + m::device::set(user_id, + { + { "device_id", device_id }, + { "display_name", initial_device_display_name }, + { "last_seen_ts", ircd::time() }, + { "last_seen_ip", string(remote(client)) }, + }); // Send response to user return resource::response diff --git a/modules/client/register.cc b/modules/client/register.cc index 67258d948..2359d063c 100644 --- a/modules/client/register.cc +++ b/modules/client/register.cc @@ -318,8 +318,8 @@ register_user(const m::registar &request, { "device", device_id }, }); - if(gen_token && !user_room.has("ircd.device", device_id)) - m::send(user_room, user_id, "ircd.device", device_id, json::members + if(gen_token) + m::device::set(user_id, { { "device_id", device_id }, { "display_name", initial_device_display_name }, diff --git a/modules/console.cc b/modules/console.cc index ee02e2be6..6bafa64d6 100644 --- a/modules/console.cc +++ b/modules/console.cc @@ -8955,10 +8955,17 @@ console_cmd__user__device(opt &out, const string_view &line) return true; } - m::device::get(user_id, device_id, [&out] - (const m::device &device) + m::device::for_each(user_id, device_id, [&out, &user_id, &device_id] + (const string_view &prop) { - out << device << std::endl; + m::device::get(std::nothrow, user_id, device_id, prop, [&out, &prop] + (const json::object &value) + { + out << prop << ": " + << value + << std::endl; + }); + return true; }); diff --git a/modules/federation/user_devices.cc b/modules/federation/user_devices.cc index 7bd7e78f8..64c2d77f2 100644 --- a/modules/federation/user_devices.cc +++ b/modules/federation/user_devices.cc @@ -84,10 +84,39 @@ get__user_devices(client &client, top, "devices" }; - m::device::for_each(user_id, [&devices] - (const m::device &device) + m::device::for_each(user_id, [&devices, &user_id] + (const string_view &device_id) { - devices.append(device); + json::stack::object device + { + devices + }; + + json::stack::member + { + device, "device_id", device_id + }; + + // The property name difference here is on purpose, probably one of + // those so-called spec "thinkos" + m::device::get(std::nothrow, user_id, device_id, "display_name", [&device] + (const string_view &value) + { + json::stack::member + { + device, "device_display_name", unquote(value) + }; + }); + + m::device::get(std::nothrow, user_id, device_id, "keys", [&device] + (const json::object &value) + { + json::stack::member + { + device, "keys", value + }; + }); + return true; }); diff --git a/modules/m_device.cc b/modules/m_device.cc index 5886ed9e2..f0cc0e3ea 100644 --- a/modules/m_device.cc +++ b/modules/m_device.cc @@ -19,54 +19,150 @@ IRCD_MODULE_EXPORT ircd::m::device::set(const m::user &user, const device &device) { + const user::room user_room{user}; + const string_view &device_id + { + at<"device_id"_>(device) + }; + + json::for_each(device, [&user, &user_room, &device_id] + (const auto &prop, auto &&val) + { + if(!json::defined(json::value(val))) + return; + + char buf[m::event::TYPE_MAX_SIZE]; + const string_view type{fmt::sprintf + { + buf, "ircd.device.%s", prop + }}; + + m::send(user_room, user, type, device_id, + { + { "", val } + }); + }); + return true; } +/// To delete a device we iterate the user's room state for all types matching +/// ircd.device.* (and ircd.device) which have a state_key of the device_id. +/// Those events are redacted which removes them from appearing in the state. bool IRCD_MODULE_EXPORT ircd::m::device::del(const m::user &user, const string_view &id) { - const m::user::room user_room{user}; - const m::room::state state{user_room}; - const m::event::idx event_idx + const user::room user_room{user}; + const room::state state{user_room}; + const room::state::type_prefix type { - state.get(std::nothrow, "ircd.device", id) + "ircd.device." }; - if(!event_idx) - return false; - - const m::event::id::buf event_id + state.for_each(type, [&user, &id, &user_room, &state] + (const string_view &type) { - m::event_id(event_idx, std::nothrow) - }; + const auto event_idx + { + state.get(std::nothrow, type, id) + }; + + const auto event_id + { + m::event_id(event_idx, std::nothrow) + }; + + if(event_id) + m::redact(user_room, user, event_id, "deleted"); + + return true; + }); - m::redact(user_room, user, event_id, "deleted"); return true; } +bool +IRCD_MODULE_EXPORT +ircd::m::device::has(const m::user &user, + const string_view &id) +{ + const user::room user_room{user}; + const room::state state{user_room}; + const room::state::type_prefix type + { + "ircd.device." + }; + + bool ret(false); + state.for_each(type, [&state, &id, &ret] + (const string_view &type) + { + ret = state.has(type, id); + return !ret; + }); + + return ret; +} + bool IRCD_MODULE_EXPORT ircd::m::device::get(std::nothrow_t, const m::user &user, const string_view &id, + const string_view &prop, const closure &closure) { + char buf[m::event::TYPE_MAX_SIZE]; + const string_view type{fmt::sprintf + { + buf, "ircd.device.%s", prop + }}; + const m::user::room user_room{user}; const m::room::state state{user_room}; - const m::event::idx event_idx + const auto event_idx { - state.get(std::nothrow, "ircd.device", id) + state.get(std::nothrow, type, id) }; - if(!event_idx) - return false; - return m::get(std::nothrow, event_idx, "content", [&closure] (const json::object &content) { - closure(content); + const string_view &value + { + content.get("") + }; + + closure(value); + }); +} + +bool +IRCD_MODULE_EXPORT +ircd::m::device::for_each(const m::user &user, + const string_view &device_id, + const closure_bool &closure) +{ + const m::user::room user_room{user}; + const m::room::state state{user_room}; + const room::state::type_prefix type + { + "ircd.device." + }; + + return state.for_each(type, [&state, &device_id, &closure] + (const string_view &type) + { + const string_view &prop + { + lstrip(type, "ircd.device.") + }; + + return state.has(type, device_id)? + closure(prop): + true; }); } @@ -74,25 +170,6 @@ bool IRCD_MODULE_EXPORT ircd::m::device::for_each(const m::user &user, const closure_bool &closure) -{ - return for_each(user, id_closure_bool{[&user, &closure] - (const string_view &device_id) - { - bool ret(true); - get(std::nothrow, user, device_id, [&closure, &ret] - (const device &device) - { - ret = closure(device); - }); - - return ret; - }}); -} - -bool -IRCD_MODULE_EXPORT -ircd::m::device::for_each(const m::user &user, - const id_closure_bool &closure) { const m::room::state::keys_bool state_key{[&closure] (const string_view &state_key) @@ -102,5 +179,5 @@ ircd::m::device::for_each(const m::user &user, const m::user::room user_room{user}; const m::room::state state{user_room}; - return state.for_each("ircd.device", state_key); + return state.for_each("ircd.device.device_id", state_key); }