diff --git a/changelogs/client_server/newsfragments/2026.feature b/changelogs/client_server/newsfragments/2026.feature new file mode 100644 index 00000000..f82b9aea --- /dev/null +++ b/changelogs/client_server/newsfragments/2026.feature @@ -0,0 +1 @@ +Add support for sending server notices to clients. diff --git a/event-schemas/examples/m.room.message#m.server_notice b/event-schemas/examples/m.room.message#m.server_notice new file mode 100644 index 00000000..0eb44ea7 --- /dev/null +++ b/event-schemas/examples/m.room.message#m.server_notice @@ -0,0 +1,11 @@ +{ + "$ref": "core/room_event.json", + "type": "m.room.message", + "content": { + "body": "Human-readable message to explain the notice", + "msgtype": "m.server_notice", + "server_notice_type": "m.server_notice.usage_limit_reached", + "admin_contact": "mailto:server.admin@example.org", + "limit_type": "monthly_active_user" + } +} diff --git a/event-schemas/schema/m.room.message#m.server_notice b/event-schemas/schema/m.room.message#m.server_notice new file mode 100644 index 00000000..f1848821 --- /dev/null +++ b/event-schemas/schema/m.room.message#m.server_notice @@ -0,0 +1,39 @@ +--- +allOf: + - $ref: core-event-schema/room_event.yaml +description: Represents a server notice for a user. +properties: + content: + properties: + body: + description: A human-readable description of the notice. + type: string + msgtype: + enum: + - m.server_notice + type: string + server_notice_type: + description: |- + The type of notice being represented. + type: string + admin_contact: + description: |- + A URI giving a contact method for the server administrator. Required if the + notice type is ``m.server_notice.usage_limit_reached``. + type: string + limit_type: + description: |- + The kind of usage limit the server has exceeded. Required if the notice type is + ``m.server_notice.usage_limit_reached``. + type: string + required: + - msgtype + - body + - server_notice_type + type: object + type: + enum: + - m.room.message + type: string +title: ServerNoticeMessage +type: object diff --git a/scripts/templating/matrix_templates/sections.py b/scripts/templating/matrix_templates/sections.py index 5961aa24..92afa5ff 100644 --- a/scripts/templating/matrix_templates/sections.py +++ b/scripts/templating/matrix_templates/sections.py @@ -110,12 +110,13 @@ class MatrixSections(Sections): # Special function: Returning a dict will specify multiple sections where # the key is the section name and the value is the value of the section def render_group_events(self): - # map all event schemata to the form $EVENTTYPE_event with s/./_/g - # e.g. m_room_topic_event + # map all event schemata to the form $EVENTTYPE_event with s/.#/_/g + # e.g. m_room_topic_event or m_room_message_m_text_event schemas = self.units.get("event_schemas") renders = {} for event_type in schemas: - renders[event_type.replace(".", "_") + "_event"] = self._render_events( + underscored_event_type = event_type.replace(".", "_").replace("#", "_") + renders[underscored_event_type + "_event"] = self._render_events( lambda x: x == event_type, sorted ) return renders @@ -141,9 +142,15 @@ class MatrixSections(Sections): "m.room.message#m.notice", "m.room.message#m.image", "m.room.message#m.file" ] + excluded_types = [ + # We exclude server notices from here because we handle them in a + # dedicated module. We do not want to confuse developers this early + # in the spec. + "m.room.message#m.server_notice", + ] other_msgtypes = [ k for k in schemas.keys() if k.startswith("m.room.message#") and - k not in msgtype_order + k not in msgtype_order and k not in excluded_types ] for event_name in (msgtype_order + other_msgtypes): if not event_name.startswith("m.room.message#m."): diff --git a/scripts/templating/matrix_templates/templates/events.tmpl b/scripts/templating/matrix_templates/templates/events.tmpl index 0955cf4c..f55be73f 100644 --- a/scripts/templating/matrix_templates/templates/events.tmpl +++ b/scripts/templating/matrix_templates/templates/events.tmpl @@ -1,7 +1,14 @@ {% import 'tables.tmpl' as tables -%} +{% if (event.type_with_msgtype) %} +``{{event.type_with_msgtype}}`` +{{(4 + event.type_with_msgtype | length) * title_kind}} +{% endif -%} + +{% if (not event.type_with_msgtype) %} ``{{event.type}}`` {{(4 + event.type | length) * title_kind}} +{% endif -%} {% if (event.typeof | length) %} *{{event.typeof}}* diff --git a/scripts/templating/matrix_templates/units.py b/scripts/templating/matrix_templates/units.py index c1755119..466110f8 100644 --- a/scripts/templating/matrix_templates/units.py +++ b/scripts/templating/matrix_templates/units.py @@ -846,6 +846,7 @@ class MatrixUnits(Units): "title": None, "desc": None, "msgtype": None, + "type_with_msgtype": None, # for the template's sake "content_fields": [ # ] @@ -884,6 +885,7 @@ class MatrixUnits(Units): ) if msgtype: schema["msgtype"] = msgtype[0] # enum prop + schema["type_with_msgtype"] = schema["type"] + " (" + msgtype[0] + ")" # link to msgtypes for m.room.message if schema["type"] == "m.room.message" and not msgtype: diff --git a/specification/client_server_api.rst b/specification/client_server_api.rst index 604c2b1c..a8bbfca0 100644 --- a/specification/client_server_api.rst +++ b/specification/client_server_api.rst @@ -219,9 +219,12 @@ Other error codes the client might encounter are: to modify state (eg: sending messages, account data, etc) and not routes which only read state (eg: ``/sync``, get account data, etc). +:``M_CANNOT_LEAVE_SERVER_NOTICE_ROOM``: + The user is unable to reject an invite to join the server notices room. See the + `Server Notices <#server-notices>`_ module for more information. + .. TODO: More error codes (covered by other issues) .. * M_CONSENT_NOT_GIVEN - GDPR: https://github.com/matrix-org/matrix-doc/issues/1512 -.. * M_CANNOT_LEAVE_SERVER_NOTICE_ROOM - GDPR: https://github.com/matrix-org/matrix-doc/issues/1254 .. _sect:txn_ids: diff --git a/specification/modules/server_notices.rst b/specification/modules/server_notices.rst new file mode 100644 index 00000000..63b7bfc5 --- /dev/null +++ b/specification/modules/server_notices.rst @@ -0,0 +1,78 @@ +.. Copyright 2019 The Matrix.org Foundation C.I.C. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Server Notices +============== + +.. _module:server-notices: + +Homeserver hosts often want to send messages to users in an official capacity, +or have resource limits which affect a user's ability to use the homeserver. +For example, the homeserver may be limited to a certain number of active users +per month and has exceeded that limit. To communicate this failure to users, +the homeserver would use the Server Notices room. + +The aesthetics of the room (name, topic, avatar, etc) are left as an implementation +detail. It is recommended that the homeserver decorate the room such that it looks +like an official room to users. + +Events +------ +Notices are sent to the client as normal ``m.room.message`` events with a +``msgtype`` of ``m.server_notice`` in the server notices room. Events with +a ``m.server_notice`` ``msgtype`` outside of the server notice room must +be ignored by clients. + +The specified values for ``server_notice_type`` are: + +:``m.server_notice.usage_limit_reached``: + The server has exceeded some limit which requires the server administrator + to intervene. The ``limit_type`` describes the kind of limit reached. + The specified values for ``limit_type`` are: + + :``monthly_active_user``: + The server's number of active users in the last 30 days has exceeded the + maximum. New connections are being refused by the server. What defines + "active" is left as an implementation detail, however servers are encouraged + to treat syncing users as "active". + + +{{m_room_message_m_server_notice_event}} + +Client behaviour +---------------- +Clients can identify the server notices room by the ``m.server_notice`` tag +on the room. Active notices are represented by the `pinned events <#m-room-pinned-events>`_ +in the server notices room. Server notice events pinned in that room should +be shown to the user through special UI and not through the normal pinned +events interface in the client. For example, clients may show warning banners +or bring up dialogs to get the user's attention. Events which are not server +notice events and are pinned in the server notices room should be shown just +like any other pinned event in a room. + +The client must not expect to be able to reject an invite to join the server +notices room. Attempting to reject the invite must result in a +``M_CANNOT_LEAVE_SERVER_NOTICE_ROOM`` error. Servers should not prevent the user +leaving the room after joining the server notices room, however the same error +code must be used if the server will prevent leaving the room. + +Server behaviour +---------------- +Servers should manage exactly 1 server notices room per user. Servers must +identify this room to clients with the ``m.server_notice`` tag. Servers should +invite the target user rather than automatically join them to the server notice +room. + +How servers send notices to clients, and which user they use to send the events, +is left as an implementation detail for the server. diff --git a/specification/targets.yaml b/specification/targets.yaml index 830449ae..aa53e9c3 100644 --- a/specification/targets.yaml +++ b/specification/targets.yaml @@ -83,6 +83,7 @@ groups: # reusable blobs of files when prefixed with 'group:' - modules/server_acls.rst - modules/mentions.rst - modules/room_upgrades.rst + - modules/server_notices.rst title_styles: ["=", "-", "~", "+", "^", "`", "@", ":"]