{"@attributes":{"version":"2.0"},"channel":{"title":"Patrick Cloke","link":"https:\/\/patrick.cloke.us\/","description":{},"lastBuildDate":"Fri, 23 Feb 2024 16:01:00 -0500","item":[{"title":"Joining the Matrix Spec Core\u00a0Team","link":"https:\/\/patrick.cloke.us\/posts\/2024\/02\/23\/joining-the-matrix-spec-core-team\/","description":"<p>I was recently invited to join the Matrix &#8220;Spec Core Team&#8221;, the group who\nsteward the Matrix protocol, from their <a class=\"reference external\" href=\"https:\/\/matrix.org\/about\/\">own documentation<\/a>:<\/p>\n<blockquote>\nThe contents and direction of the Matrix Spec is governed by the Spec Core Team;\na set of experts from across the whole Matrix community, representing all aspects\nof the Matrix ecosystem. The Spec Core Team acts as a subcommittee of the Foundation.<\/blockquote>\n<p>This was the <a class=\"reference external\" href=\"https:\/\/matrix.org\/blog\/2024\/02\/09\/this-week-in-matrix-2024-02-09\/#dept-of-status-of-matrix-face-with-th\">announced a couple of weeks ago<\/a> and I&#8217;m just starting to get my feet\nwet! You can see an interview between myself, Tulir (another new member of the Spec\nCore Team), and Matthew (the Spec Core Team lead) in today&#8217;s <a class=\"reference external\" href=\"https:\/\/matrix.org\/blog\/2024\/02\/23\/this-week-in-matrix-2024-02-23\/\">This Week in Matrix<\/a>.\nWe cover a range of topics including <a class=\"reference external\" href=\"https:\/\/www.thunderbird.net\/\">Thunderbird<\/a> (and <a class=\"reference external\" href=\"http:\/\/instantbird.com\/\">Instantbird<\/a>), some\nimprovements I hope to make and&nbsp;more.<\/p>\n<div class=\"youtube youtube-16x9\"><iframe src=\"https:\/\/www.youtube-nocookie.com\/embed\/8vjjwxx7k1w\" allowfullscreen seamless frameBorder=\"0\"><\/iframe><\/div>","pubDate":"Fri, 23 Feb 2024 16:01:00 -0500","guid":"tag:patrick.cloke.us,2024-02-23:\/posts\/2024\/02\/23\/joining-the-matrix-spec-core-team\/","category":["articles","matrix","mozilla","thunderbird","instantbird"]},{"title":"Synapse URL\u00a0Previews","link":"https:\/\/patrick.cloke.us\/posts\/2024\/02\/23\/synapse-url-previews\/","description":"<p>Matrix includes the ability for a client to request that the server\n<a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.8\/client-server-api\/#get_matrixmediav3preview_url\">generate a &#8220;preview&#8221; for a <span class=\"caps\">URL<\/span><\/a>. The client provides a <span class=\"caps\">URL<\/span> to the server which\nreturns <a class=\"reference external\" href=\"https:\/\/ogp.me\/\">Open Graph<\/a> data as a <span class=\"caps\">JSON<\/span> response. This leaks any URLs detected in\nthe message content to the server, but protects the end user&#8217;s <span class=\"caps\">IP<\/span> address, etc.\nfrom the <span class=\"caps\">URL<\/span> being previewed. <a class=\"footnote-reference\" href=\"#footnote-1\" id=\"footnote-reference-1\">[1]<\/a> (Note that clients generally disable <span class=\"caps\">URL<\/span> previews\nfor encrypted rooms, but it can be&nbsp;enabled.)<\/p>\n<div class=\"section\" id=\"improvements\">\n<h2>Improvements<\/h2>\n<p>Synapse implements the <span class=\"caps\">URL<\/span> preview endpoint, but it was a bit neglected. I was\none of the few main developers running with <span class=\"caps\">URL<\/span> previews enabled and sunk a bit of\ntime into improving <span class=\"caps\">URL<\/span> previews for my on sake. Some highlights of the improvements\nmade include (in addition to lots and lots of&nbsp;refactoring):<\/p>\n<ul class=\"simple\">\n<li>Support <a class=\"reference external\" href=\"https:\/\/oembed.com\/\">oEmbed<\/a> for <span class=\"caps\">URL<\/span> previews:\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/7920\">#7920<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/10714\">#10714<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/10759\">#10759<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/10814\">#10814<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/10819\">#10819<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/10822\">#10822<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/11065\">#11065<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/11669\">#11669<\/a> (combine with <span class=\"caps\">HTML<\/span> results),\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/14089\">#14089<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/14781\">#14781<\/a>.<\/li>\n<li>Reduction of 500 errors:\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/8883\">#8883<\/a> (empty media),\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/9333\">#9333<\/a> (unable to parse),\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/11061\">#11061<\/a> (oEmbed&nbsp;errors).<\/li>\n<li>Improved support for document encodings:\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/9164\">#9164<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/9333\">#9333<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/11077\">#11077<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/11089\">#11089<\/a>.<\/li>\n<li>Support previewing <span class=\"caps\">XML<\/span> documents (<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/11196\">#11196<\/a>)\nand <tt class=\"docutils literal\">data:<\/tt> URIs (<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/11767\">#11767<\/a>).<\/li>\n<li>Return partial information if images or oEmbed can&#8217;t be fetched:\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/12950\">#12950<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/15092\">#15092<\/a>.<\/li>\n<li>Skipping empty Open Graph (<tt class=\"docutils literal\">og<\/tt>) or <tt class=\"docutils literal\">meta<\/tt> tags:\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/12951\">#12951<\/a>.<\/li>\n<li>Support previewing from <a class=\"reference external\" href=\"https:\/\/developer.twitter.com\/en\/docs\/twitter-for-websites\/cards\/guides\/getting-started\">Twitter card information<\/a>:\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/13056\">#13056<\/a>.<\/li>\n<li>Fallback to favicon if no images found:\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/12951\">#12951<\/a>.<\/li>\n<li>Ignore navgiation tags: <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/12951\">#12951<\/a>.<\/li>\n<li>Document how Synapse <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/blob\/be65a8ec0195955c15fdb179c9158b187638e39a\/synapse\/media\/url_previewer.py#L101-L154\">generates <span class=\"caps\">URL<\/span> previews<\/a>:\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/10753\">#10753<\/a>,\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/13261\">#13261<\/a>.<\/li>\n<\/ul>\n<p>I also helped review many changes by&nbsp;others:<\/p>\n<ul class=\"simple\">\n<li>Improved support for encodings: <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/10410\">#10410<\/a>.<\/li>\n<li>Safer content-type support: <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/11936\">#11936<\/a>.<\/li>\n<li>Attempts to fix Twitter previews: <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/11985\">#11985<\/a>.<\/li>\n<li>Remove useless elements from previews: <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/12887\">#12887<\/a>.<\/li>\n<li>Avoid crashes due to unbounded recursion:\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/security\/advisories\/GHSA-22p3-qrh9-cx32\"><span class=\"caps\">GHSA<\/span>-22p3-qrh9-cx32<\/a>.<\/li>\n<\/ul>\n<p>And also fixed some security&nbsp;issues:<\/p>\n<ul class=\"simple\">\n<li>Apply <tt class=\"docutils literal\">url_preview_url_blacklist<\/tt> to oEmbed and pre-cached images:\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/15601\">#15601<\/a>.<\/li>\n<\/ul>\n<\/div>\n<div class=\"section\" id=\"results\">\n<h2>Results<\/h2>\n<p>Overall, there was an improved result (from my point of view). A summary of some\nof the improvements. I tested 26 URLs (based on ones that had previously been\nreported or found to give issues). See the table below for testing at a few versions.\nThe error reason was also broken out into whether JavaScript was required or some\nother error occurred. <a class=\"footnote-reference\" href=\"#footnote-2\" id=\"footnote-reference-2\">[2]<\/a><\/p>\n<table border=\"1\" class=\"docutils\">\n<colgroup>\n<col width=\"9%\" \/>\n<col width=\"14%\" \/>\n<col width=\"20%\" \/>\n<col width=\"28%\" \/>\n<col width=\"29%\" \/>\n<\/colgroup>\n<thead valign=\"bottom\">\n<tr><th class=\"head\">Version<\/th>\n<th class=\"head\">Release date<\/th>\n<th class=\"head\">Successful preview<\/th>\n<th class=\"head\">JavaScript required error<\/th>\n<th class=\"head\">Found image <span class=\"amp\">&amp;<\/span> description?<\/th>\n<\/tr>\n<\/thead>\n<tbody valign=\"top\">\n<tr><td>1.0.0<\/td>\n<td>2019-06-11<\/td>\n<td>15<\/td>\n<td>4<\/td>\n<td>14<\/td>\n<\/tr>\n<tr><td>1.12.0<\/td>\n<td>2020-03-23<\/td>\n<td>18<\/td>\n<td>4<\/td>\n<td>17<\/td>\n<\/tr>\n<tr><td>1.24.0<\/td>\n<td>2020-12-09<\/td>\n<td>20<\/td>\n<td>1<\/td>\n<td>16<\/td>\n<\/tr>\n<tr><td>1.36.0<\/td>\n<td>2021-06-15<\/td>\n<td>20<\/td>\n<td>1<\/td>\n<td>16<\/td>\n<\/tr>\n<tr><td>1.48.0<\/td>\n<td>2021-11-30<\/td>\n<td>20<\/td>\n<td>1<\/td>\n<td>11<\/td>\n<\/tr>\n<tr><td>1.60.0<\/td>\n<td>2022-05-31<\/td>\n<td>21<\/td>\n<td>0<\/td>\n<td>21<\/td>\n<\/tr>\n<tr><td>1.72.0<\/td>\n<td>2022-11-22<\/td>\n<td>22<\/td>\n<td>0<\/td>\n<td>21<\/td>\n<\/tr>\n<tr><td>1.84.0<\/td>\n<td>2023-05-23<\/td>\n<td>22<\/td>\n<td>0<\/td>\n<td>21<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<div class=\"section\" id=\"future-improvements\">\n<h2>Future&nbsp;improvements<\/h2>\n<p>I am no longer working on Synapse, but some of the ideas I had for additional improvements&nbsp;included:<\/p>\n<ul class=\"simple\">\n<li>Use <a class=\"reference external\" href=\"https:\/\/www.crummy.com\/software\/BeautifulSoup\/\">BeautifulSoup<\/a> instead of a custom parser to handle some edge cases in <span class=\"caps\">HTML<\/span>\ndocuments better (<span class=\"caps\">WIP<\/span> &#64; <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/tree\/clokep\/bs4\"><tt class=\"docutils literal\">clokep\/bs4<\/tt><\/a>).<\/li>\n<li>Always request both oEmbed and <span class=\"caps\">HTML<\/span> (<span class=\"caps\">WIP<\/span> &#64; <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/tree\/clokep\/oembed-and-html\"><tt class=\"docutils literal\"><span class=\"pre\">clokep\/oembed-and-html<\/span><\/tt><\/a>).<\/li>\n<li>Structured data support (<a class=\"reference external\" href=\"https:\/\/json-ld.org\/\"><span class=\"caps\">JSON<\/span>-<span class=\"caps\">LD<\/span><\/a>, <a class=\"reference external\" href=\"https:\/\/html.spec.whatwg.org\/multipage\/\">Microdata<\/a>, <a class=\"reference external\" href=\"https:\/\/rdfa.info\/\">RDFa<\/a>) (<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/issues\/11540\">#11540<\/a>).<\/li>\n<li>Some minimal JavaScript support (<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/issues\/14118\">#14118<\/a>).<\/li>\n<li>Fixing any of the other issues with particular URLs (see this <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3AA-URL-Preview+\">GitHub search<\/a>).<\/li>\n<li>Thumbnailing of <span class=\"caps\">SVG<\/span> images (which sites tend to use for favicons) (<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/issues\/1309\">#1309<\/a>).<\/li>\n<\/ul>\n<p>There&#8217;s also a ton more that could be done here if you wanted, e.g. handling more\ndata types (text and <span class=\"caps\">PDF<\/span> are the ones I have frequently come across that would be\nhelpful to preview). I&#8217;m sure there are also many other URLs that don&#8217;t work right\nnow for some reason. Hopefully the <span class=\"caps\">URL<\/span> preview code continues to&nbsp;improve!<\/p>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-1\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-1\">[1]<\/a><\/td><td>See some <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec\/blob\/main\/attic\/drafts\/url_previews.md\">ancient documentation<\/a> on the tradeoffs and design of <span class=\"caps\">URL<\/span> previews.\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/4095\"><span class=\"caps\">MSC4095<\/span><\/a> was recently written to bundle the <span class=\"caps\">URL<\/span> preview information into\nevens.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-2\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-2\">[2]<\/a><\/td><td>This was done by instantiating different Synapse versions via Docker and\nasking them to preview URLs. (See <a class=\"reference external\" href=\"https:\/\/github.com\/clokep\/test-matrix-url-previews\/tree\/e0e20154ec348fc25d203546ddede0c881b9772a\/docker\">the code<\/a>.) This is not a super realistic\ntest since it assumes that URLs are static over time. In particular some\nsites (e.g. Twitter) like to change what they allow you to access without\nbeing authenticated.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","pubDate":"Fri, 23 Feb 2024 15:35:00 -0500","guid":"tag:patrick.cloke.us,2024-02-23:\/posts\/2024\/02\/23\/synapse-url-previews\/","category":["articles","matrix"]},{"title":"Matrix Intentional Mentions\u00a0explained","link":"https:\/\/patrick.cloke.us\/posts\/2023\/12\/15\/matrix-intentional-mentions-explained\/","description":"<p>Previously I have written about how <a class=\"reference external\" href=\"https:\/\/patrick.cloke.us\/posts\/2023\/05\/08\/matrix-push-rules-notifications\/\">push rules generate notifications<\/a> and how\n<a class=\"reference external\" href=\"https:\/\/patrick.cloke.us\/posts\/2023\/01\/05\/matrix-read-receipts-and-notifications\/\">read receipts mark notificiations as read<\/a> in the Matrix protocol. This article\nis about a change that I instigated to improve <em>when<\/em> a &#8220;mention&#8221; (or &#8220;ping&#8221;)\nnotification is created. (This is a &#8220;highlight&#8221; notification in the Matrix&nbsp;specification.)<\/p>\n<p>This was part of the work I did at Element to reduce <a class=\"reference external\" href=\"https:\/\/github.com\/vector-im\/element-meta\/issues\/886\">unintentional pings<\/a>. I\npreferred thinking of it in the positive &#8212; that we should only generate a mention\non purpose, hence &#8220;intentional&#8221; mentions. <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/3952\"><span class=\"caps\">MSC3952<\/span><\/a> details the technical protocol\nchanges, but this serves as a bit of a higher-level overview (some of this content\nis copied from the <span class=\"caps\">MSC<\/span>).<\/p>\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p class=\"last\">This blog post assumes that default push rules are enabled, these can be heavily\nmodified, disabled, etc. but that is ignored in this&nbsp;post.<\/p>\n<\/div>\n<div class=\"section\" id=\"legacy-mentions\">\n<h2>Legacy&nbsp;mentions<\/h2>\n<p>The legacy mention system searches for the current user&#8217;s display name or the\nlocalpart of the Matrix <span class=\"caps\">ID<\/span> <a class=\"footnote-reference\" href=\"#footnote-1\" id=\"footnote-reference-1\">[1]<\/a> in the text content of an event. For example, an\nevent like the following would generate a mention for&nbsp;me:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"c1\">\/\/ Additional fields ignored.<\/span>\n<span class=\"w\">  <\/span><span class=\"s2\">&quot;content&quot;<\/span><span class=\"o\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"s2\">&quot;body&quot;<\/span><span class=\"o\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Hello @clokep:matrix.org!&quot;<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>A <tt class=\"docutils literal\">body<\/tt> content field <a class=\"footnote-reference\" href=\"#footnote-2\" id=\"footnote-reference-2\">[2]<\/a> containing <tt class=\"docutils literal\">clokep<\/tt> or <tt class=\"docutils literal\">Patrick Cloke<\/tt>\nwould cause a &#8220;highlight&#8221; notification (displayed as red in Element). This isn&#8217;t uncommon\nin chat protocols and is how <span class=\"caps\">IRC<\/span> and <span class=\"caps\">XMPP<\/span>.<\/p>\n<p>Some of the issues with this&nbsp;are:<\/p>\n<ul class=\"simple\">\n<li>Replying to a message will re-issue pings from the initial message due to\n<a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.5\/client-server-api\/#fallbacks-for-rich-replies\">fallback replies<\/a>.<\/li>\n<li>Each time a message is edited the new version will be re-evaluated for&nbsp;mentions.<\/li>\n<li>Mentions occurring <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec\/issues\/16\">in spoiler contents<\/a> or <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec\/issues\/15\">code blocks<\/a> are&nbsp;evaluated.<\/li>\n<li>If the <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/issues\/3011\">localpart of your Matrix <span class=\"caps\">ID<\/span> is a common word<\/a> then spurious notifications\nmight occur (e.g. Travis <span class=\"caps\">CI<\/span> matching if your Matrix <span class=\"caps\">ID<\/span> is <tt class=\"docutils literal\">&#64;travis:example.org<\/tt>).<\/li>\n<li>If the <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/issues\/2735\">localpart or display name of your Matrix <span class=\"caps\">ID<\/span> matches the hostname<\/a>\n(e.g. <tt class=\"docutils literal\">&#64;example:example.org<\/tt> receives notifications whenever <tt class=\"docutils literal\">&#64;foo:example.org<\/tt>\nis replied&nbsp;to).<\/li>\n<\/ul>\n<p>There were some <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/blob\/main\/proposals\/3952-intentional-mentions.md#prior-proposals\">prior attempts<\/a> to fix this, but I would summarize them as attempting\nto reduce edge-cases instead of attempting to rethink how mentions are&nbsp;done.<\/p>\n<\/div>\n<div class=\"section\" id=\"intentional-mentions\">\n<h2>Intentional&nbsp;mentions<\/h2>\n<p>I chose to call this &#8220;intentional&#8221; mentions since the protocol now requires\nexplicitly referring to the Matrix IDs to mention in a dedicated field, instead\nof implicit references in the text&nbsp;content.<\/p>\n<p>The overall change is simple: include a list of mentioned users in a new\ncontent field,&nbsp;e.g.:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"p\">{<\/span>\n<span class=\"w\">  <\/span><span class=\"c1\">\/\/ Additional fields ignored.<\/span>\n<span class=\"w\">  <\/span><span class=\"s2\">&quot;content&quot;<\/span><span class=\"o\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"s2\">&quot;body&quot;<\/span><span class=\"o\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Hello @clokep:matrix.org!&quot;<\/span>\n<span class=\"w\">    <\/span><span class=\"s2\">&quot;m.mentions&quot;<\/span><span class=\"o\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">      <\/span><span class=\"s2\">&quot;user_ids&quot;<\/span><span class=\"o\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">[<\/span><span class=\"s2\">&quot;@clokep:matrix.org&quot;<\/span><span class=\"p\">]<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">  <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/pre><\/div>\n<p>Only the <tt class=\"docutils literal\">m.mentions<\/tt> field is used to generate mentions, the <tt class=\"docutils literal\">body<\/tt> field is\nno longer involved. Not only does this remove a whole class of potential bugs,\nbut also allows for &#8220;hidden&#8221; mentions and paves the way for mentions in extensible\nevents (see <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/4053\"><span class=\"caps\">MSC4053<\/span><\/a>).<\/p>\n<p>That&#8217;s the gist of the change, although the <span class=\"caps\">MSC<\/span> goes deeper into backwards\ncompatibility, and interacting with replies or&nbsp;edits.<\/p>\n<\/div>\n<div class=\"section\" id=\"comparison-to-other-protocols\">\n<h2>Comparison to other&nbsp;protocols<\/h2>\n<p>The <tt class=\"docutils literal\">m.mentions<\/tt> field is similar to how <a class=\"reference external\" href=\"https:\/\/developer.twitter.com\/en\/docs\/twitter-api\/data-dictionary\/object-model\/tweet\">Twitter<\/a>, <a class=\"reference external\" href=\"https:\/\/docs.joinmastodon.org\/entities\/Status\/#Mention\">Mastodon<\/a>, <a class=\"reference external\" href=\"https:\/\/discord.com\/developers\/docs\/resources\/channel#message-object\">Discord<\/a>,\nand <a class=\"reference external\" href=\"https:\/\/learn.microsoft.com\/en-us\/graph\/api\/resources\/chatmessagemention?view=graph-rest-1.0\">Microsoft Teams<\/a> handle mentioning users. The main downside of this approach\nis that it is not obvious <em>where<\/em> in the text the user&#8217;s mention is (and allows\nfor hidden&nbsp;mentions).<\/p>\n<p>The other seriously considered approach was searching for &#8220;pills&#8221; in the <span class=\"caps\">HTML<\/span>\ncontent of the event. This is similar to how <a class=\"reference external\" href=\"https:\/\/api.slack.com\/reference\/surfaces\/formatting#mentioning-users\">Slack<\/a> handles mentions, where the\nuser <span class=\"caps\">ID<\/span> is encoded with some markup <a class=\"footnote-reference\" href=\"#footnote-3\" id=\"footnote-reference-3\">[3]<\/a>. This has a major downside of requiring <span class=\"caps\">HTML<\/span>\nparsing on a hotpath of processing notifications (and it is unclear how this would\nwork for non-<span class=\"caps\">HTML<\/span>&nbsp;clients).<\/p>\n<\/div>\n<div class=\"section\" id=\"can-i-use-this\">\n<h2>Can I use&nbsp;this?<\/h2>\n<p>You can! The <span class=\"caps\">MSC<\/span> was approved and <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.9\/client-server-api\/#user-and-room-mentions\">included in Matrix 1.7<\/a>, Synapase has had\nsupport since v1.86.0; it is pretty much up to clients to implement&nbsp;it!<\/p>\n<p>Element Web has handled (and sent intentional mentions) since v1.11.37, although\nI&#8217;m not aware of other clients which do (Element X might now). Hopefully it will\nbecome used throughout the ecosystem since many of the above issues are still\ncommon complaints I see with&nbsp;Matrix.<\/p>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-1\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-1\">[1]<\/a><\/td><td>This post ignores room-mentions, but they&#8217;re handled very similarly.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-2\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-2\">[2]<\/a><\/td><td>Note that the plaintext content of the event is searched <em>not<\/em> the &#8220;formatted&#8221;\ncontent (which is <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.9\/client-server-api\/#mroommessage-msgtypes\">usually <span class=\"caps\">HTML<\/span><\/a>).<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-3\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-3\">[3]<\/a><\/td><td>This solution should also reduce the number of unintentional mentions, but\ndoesn&#8217;t allow for hidden mentions.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","pubDate":"Fri, 15 Dec 2023 15:41:00 -0500","guid":"tag:patrick.cloke.us,2023-12-15:\/posts\/2023\/12\/15\/matrix-intentional-mentions-explained\/","category":["articles","matrix","notes"]},{"title":"Matrix\u00a0Presence","link":"https:\/\/patrick.cloke.us\/posts\/2023\/12\/15\/matrix-presence\/","description":"<p>I put together some notes on presence when implementing <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/16066\">multi-device support for presence<\/a>\nin Synapse, maybe this is helpful to others! This is a combination of information\nfrom the specification, as well as some information about how Synapse&nbsp;works.<\/p>\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p class=\"last\">These notes are true as of the v1.9 of the Matrix spec and also cover some\nMatrix spec changes which may or may not have been merged&nbsp;since.<\/p>\n<\/div>\n<div class=\"section\" id=\"presence-in-matrix\">\n<h2>Presence in&nbsp;Matrix<\/h2>\n<p>Matrix includes basic presence support, which is explained decently from <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.7\/client-server-api\/#presence\">the specification<\/a>:<\/p>\n<blockquote>\n<p>Each user has the concept of presence information. This&nbsp;encodes:<\/p>\n<ul class=\"simple\">\n<li>Whether the user is currently&nbsp;online<\/li>\n<li>How recently the user was last active (as seen by the&nbsp;server)<\/li>\n<li>Whether a given client considers the user to be currently&nbsp;idle<\/li>\n<li>Arbitrary information about the user\u2019s current status (e.g. \u201cin a&nbsp;meeting\u201d).<\/li>\n<\/ul>\n<p>This information is collated from both per-device (<tt class=\"docutils literal\">online<\/tt>, <tt class=\"docutils literal\">idle<\/tt>, <tt class=\"docutils literal\">last_active<\/tt>)\nand per-user (status) data, aggregated by the user\u2019s homeserver and transmitted\nas an <tt class=\"docutils literal\">m.presence<\/tt> event. Presence events are sent to interested parties where\nusers share a room&nbsp;membership.<\/p>\n<p>User\u2019s presence state is represented by the presence key, which is an enum of\none of the&nbsp;following:<\/p>\n<ul class=\"simple\">\n<li><tt class=\"docutils literal\">online<\/tt> : The default state when the user is connected to an event&nbsp;stream.<\/li>\n<li><tt class=\"docutils literal\">unavailable<\/tt> : The user is not reachable at this time e.g. they are idle. <a class=\"footnote-reference\" href=\"#footnote-1\" id=\"footnote-reference-1\">[1]<\/a><\/li>\n<li><tt class=\"docutils literal\">offline<\/tt> : The user is not connected to an event stream or is explicitly suppressing their profile information from being&nbsp;sent.<\/li>\n<\/ul>\n<\/blockquote>\n<p><a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/3026\"><span class=\"caps\">MSC3026<\/span><\/a> defines a <tt class=\"docutils literal\">busy<\/tt> presence&nbsp;state:<\/p>\n<blockquote>\nthe user is online and active but is performing an activity that would prevent\nthem from giving their full attention to an external solicitation, i.e. the user\nis online and active but not available.<\/blockquote>\n<p>Presence information is returned to clients in the <tt class=\"docutils literal\">presence<\/tt> key of the\n<a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.7\/client-server-api\/#_matrixclientv3sync_presence\">sync response<\/a> as a <tt class=\"docutils literal\">m.presence<\/tt> <span class=\"caps\">EDU<\/span> which&nbsp;contains:<\/p>\n<ul class=\"simple\">\n<li><tt class=\"docutils literal\">currently_active<\/tt>: Whether the user is currently active&nbsp;(boolean)<\/li>\n<li><tt class=\"docutils literal\">last_active_ago<\/tt>: The last time since this used performed some action, in&nbsp;milliseconds.<\/li>\n<li><tt class=\"docutils literal\">presence<\/tt>: <tt class=\"docutils literal\">online<\/tt>, <tt class=\"docutils literal\">unavailable<\/tt>, or <tt class=\"docutils literal\">offline<\/tt> (or <tt class=\"docutils literal\">busy<\/tt>)<\/li>\n<li><tt class=\"docutils literal\">status_msg<\/tt>: An optional description to accompany the&nbsp;presence.<\/li>\n<\/ul>\n<div class=\"section\" id=\"updating-presence\">\n<h3>Updating&nbsp;presence<\/h3>\n<p>Clients can call <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.8\/client-server-api\/#put_matrixclientv3presenceuseridstatus\"><tt class=\"docutils literal\"><span class=\"caps\">PUT<\/span> <span class=\"pre\">\/_matrix\/client\/v3\/presence\/{userId}\/status<\/span><\/tt><\/a> to update the presence state <span class=\"amp\">&amp;<\/span> status message or\ncan set the presence state via <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.8\/client-server-api\/#get_matrixclientv3sync\">the <tt class=\"docutils literal\">set_presence<\/tt> parameter on <tt class=\"docutils literal\">\/sync<\/tt> request<\/a>.<\/p>\n<p>Note that when using the <tt class=\"docutils literal\">set_presence<\/tt> parameter, <tt class=\"docutils literal\">offline<\/tt> is equivalent to\n&#8220;do not make a&nbsp;change&#8221;.<\/p>\n<\/div>\n<div class=\"section\" id=\"user-activity\">\n<h3>User&nbsp;activity<\/h3>\n<p>From the <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.7\/client-server-api\/#last-active-ago\">Matrix spec on last active ago<\/a>:<\/p>\n<blockquote>\nThe server maintains a timestamp of the last time it saw a pro-active event from\nthe user. A pro-active event may be sending a message to a room or changing presence\nstate to <tt class=\"docutils literal\">online<\/tt>. This timestamp is presented via a key called <tt class=\"docutils literal\">last_active_ago<\/tt>\nwhich gives the relative number of milliseconds since the pro-active event.<\/blockquote>\n<p>If the presence is set to <tt class=\"docutils literal\">online<\/tt> then <tt class=\"docutils literal\">last_active_ago<\/tt> is not part of the\n<tt class=\"docutils literal\">\/sync<\/tt> response and <tt class=\"docutils literal\">currently_active<\/tt> is returned&nbsp;instead.<\/p>\n<\/div>\n<div class=\"section\" id=\"idle-timeout\">\n<h3>Idle&nbsp;timeout<\/h3>\n<p>From the <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.7\/client-server-api\/#idle-timeout\">Matrix spec on automatically idling users<\/a>:<\/p>\n<blockquote>\nThe server will automatically set a user\u2019s presence to <tt class=\"docutils literal\">unavailable<\/tt> if their\nlast active time was over a threshold value (e.g. 5 minutes). Clients can manually\nset a user\u2019s presence to <tt class=\"docutils literal\">unavailable<\/tt>. Any activity that bumps the last active\ntime on any of the user\u2019s clients will cause the server to automatically set their\npresence to <tt class=\"docutils literal\">online<\/tt>.<\/blockquote>\n<p><a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/3026\"><span class=\"caps\">MSC3026<\/span><\/a> also&nbsp;recommends:<\/p>\n<blockquote>\nIf a user&#8217;s presence is set to <tt class=\"docutils literal\">busy<\/tt>, it is strongly recommended for implementations\nto not implement a timer that would trigger an update to the <tt class=\"docutils literal\">unavailable<\/tt> state\n(like most implementations do when the user is in the <tt class=\"docutils literal\">online<\/tt> state).<\/blockquote>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"presence-in-synapse\">\n<h2>Presence in&nbsp;Synapse<\/h2>\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p>This describes Synapse&#8217;s behavior <em>after<\/em> v1.93.0. Before that version Synapse\ndid not account for multiple devices, essentially meaning that the latest device\nupdate&nbsp;won.<\/p>\n<p class=\"last\">This also only applies to <em>local<\/em> users; per-device information for remote users\nis not available, only the combined per-user&nbsp;state.<\/p>\n<\/div>\n<p>User&#8217;s devices can set a device&#8217;s presence state and a user&#8217;s status message.\nA user&#8217;s device knows better than the server whether they&#8217;re online and should\nsend that state as part of <tt class=\"docutils literal\">\/sync<\/tt> calls (e.g. sending <tt class=\"docutils literal\">online<\/tt> or <tt class=\"docutils literal\">unavailable<\/tt>\nor <tt class=\"docutils literal\">offline<\/tt>).<\/p>\n<p>Thus a device is only ever able to set the &#8220;minimum&#8221; presence state for the user.\nPresence states are coalesced across devices as\n<tt class=\"docutils literal\">busy<\/tt> &gt; <tt class=\"docutils literal\">online<\/tt> &gt; <tt class=\"docutils literal\">unavailable<\/tt> &gt; <tt class=\"docutils literal\">offline<\/tt>. You can build simple\ntruth tables of how these combine with multiple&nbsp;devices:<\/p>\n<table border=\"1\" class=\"docutils\">\n<colgroup>\n<col width=\"33%\" \/>\n<col width=\"33%\" \/>\n<col width=\"33%\" \/>\n<\/colgroup>\n<thead valign=\"bottom\">\n<tr><th class=\"head\">Device 1<\/th>\n<th class=\"head\">Device 2<\/th>\n<th class=\"head\">User state<\/th>\n<\/tr>\n<\/thead>\n<tbody valign=\"top\">\n<tr><td><tt class=\"docutils literal\">online<\/tt><\/td>\n<td><tt class=\"docutils literal\">unavailable<\/tt><\/td>\n<td><tt class=\"docutils literal\">online<\/tt><\/td>\n<\/tr>\n<tr><td><tt class=\"docutils literal\">busy<\/tt><\/td>\n<td><tt class=\"docutils literal\">online<\/tt><\/td>\n<td><tt class=\"docutils literal\">busy<\/tt><\/td>\n<\/tr>\n<tr><td><tt class=\"docutils literal\">unavailable<\/tt><\/td>\n<td><tt class=\"docutils literal\">offline<\/tt><\/td>\n<td><tt class=\"docutils literal\">unavailable<\/tt><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Additionally, users expect to see the latest activity time across all devices.\n(And therefore if any device is online and the latest activity is recent then\nthe user is currently&nbsp;active).<\/p>\n<p>The status message is global and setting it should always override any previous\nstate (and never be cleared&nbsp;automatically).<\/p>\n<div class=\"section\" id=\"automatic-state-transitions\">\n<h3>Automatic state&nbsp;transitions<\/h3>\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p class=\"last\">Note that the below only describes the logic for <em>local<\/em> users. Data received\nover federation is handled&nbsp;differently.<\/p>\n<\/div>\n<p>If a device is <tt class=\"docutils literal\">unavailable<\/tt> or <tt class=\"docutils literal\">offline<\/tt> it should transition to <tt class=\"docutils literal\">online<\/tt>\nif a &#8220;pro-active event&#8221; occurs. This includes sending a receipt or event, or syncing\nwithout <tt class=\"docutils literal\">set_presence<\/tt> or <tt class=\"docutils literal\">set_presence=online<\/tt>.<\/p>\n<p>If a device is <tt class=\"docutils literal\">offline<\/tt> it should transition to <tt class=\"docutils literal\">unavailable<\/tt> if it is syncing\nwith <tt class=\"docutils literal\">set_presence=unavailable<\/tt>.<\/p>\n<p>If a device is <tt class=\"docutils literal\">online<\/tt> (either directly or implicitly via user actions) it should\ntransition to <tt class=\"docutils literal\">unavailable<\/tt> (idle) after a period of time <a class=\"footnote-reference\" href=\"#footnote-2\" id=\"footnote-reference-2\">[2]<\/a> if the device is\ncontinuing to sync. (Note that this implies the sync is occurring with\n<tt class=\"docutils literal\">set_presence=unavailable<\/tt> as otherwise the device is continuing to report as\n<tt class=\"docutils literal\">online<\/tt>). <a class=\"footnote-reference\" href=\"#footnote-3\" id=\"footnote-reference-3\">[3]<\/a><\/p>\n<p>If a device is <tt class=\"docutils literal\">online<\/tt> or <tt class=\"docutils literal\">unavailable<\/tt> it should transition to  <tt class=\"docutils literal\">offline<\/tt>\nafter a period of time if it is not syncing and not making other actions which\nwould transition the device to <cite>online<\/cite>. <a class=\"footnote-reference\" href=\"#footnote-4\" id=\"footnote-reference-4\">[4]<\/a><\/p>\n<p>Note if a device is <tt class=\"docutils literal\">busy<\/tt> it should not transition to other states. <a class=\"footnote-reference\" href=\"#footnote-5\" id=\"footnote-reference-5\">[5]<\/a><\/p>\n<p>There&#8217;s a <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/blob\/be65a8ec0195955c15fdb179c9158b187638e39a\/tests\/handlers\/test_presence.py#L971-L1106\">huge testcase<\/a> which checks all these&nbsp;transitions.<\/p>\n<div class=\"section\" id=\"examples\">\n<h4>Examples<\/h4>\n<ol class=\"arabic simple\">\n<li>Two devices continually syncing, one <tt class=\"docutils literal\">online<\/tt> and one <tt class=\"docutils literal\">unavailable<\/tt>. The\nend result should be <cite>online<\/cite>. <a class=\"footnote-reference\" href=\"#footnote-6\" id=\"footnote-reference-6\">[6]<\/a><\/li>\n<li>One device syncing with <tt class=\"docutils literal\">set_presence=unavailable<\/tt> but had a &#8220;pro-active&#8221;\naction, after a period of time the user should be <tt class=\"docutils literal\">unavailable<\/tt> if no additional\n&#8220;pro-active&#8221; actions&nbsp;occurred.<\/li>\n<li>One device that stops syncing (and no other &#8220;pro-active&#8221; actions&#8221; are occurring),\nafter a period of time the user should be <tt class=\"docutils literal\">offline<\/tt>.<\/li>\n<li>Two devices continually syncing, one <tt class=\"docutils literal\">online<\/tt> and one <tt class=\"docutils literal\">unavailable<\/tt>. The\n<tt class=\"docutils literal\">online<\/tt> device stops syncing, after a period of time the user should be\n<tt class=\"docutils literal\">unavailable<\/tt>.<\/li>\n<\/ol>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-1\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-1\">[1]<\/a><\/td><td>This should be called <tt class=\"docutils literal\">idle<\/tt>.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-2\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-2\">[2]<\/a><\/td><td>The period of time is implementation specific.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-3\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-3\">[3]<\/a><\/td><td>Note that syncing with <tt class=\"docutils literal\">set_presence=offline<\/tt> does not transition to offline,\nit is equivalent to not syncing. (It is mostly for mobile applications to\nprocess push notifications.)<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-4\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-4\">[4]<\/a><\/td><td>The spec doesn&#8217;t seem to ever say that devices can transition to offline.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-5\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-5\">[5]<\/a><\/td><td>See the <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/3026\/files#r1287453423\">open thread on the <span class=\"caps\">MSC3026<\/span><\/a>.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-6\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-6\">[6]<\/a><\/td><td>This is essentially the <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/issues\/16057\">bug illustrated by the change in Element Web&#8217;s behavior<\/a>.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/div>\n","pubDate":"Fri, 15 Dec 2023 11:24:00 -0500","guid":"tag:patrick.cloke.us,2023-12-15:\/posts\/2023\/12\/15\/matrix-presence\/","category":["articles","matrix","notes"]},{"title":"Handling GitHub\u00a0Notifications","link":"https:\/\/patrick.cloke.us\/posts\/2023\/10\/06\/handling-github-notifications\/","description":"<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p class=\"last\">This was originally written for some coworkers and assumes a mostly GitHub-based\nworkflow. It has been lightly edited to be more readable, but if your organization\ndoesn&#8217;t use GitHub like we do then it might not apply&nbsp;great.<\/p>\n<\/div>\n<p><a class=\"reference external\" href=\"https:\/\/github.com\">GitHub<\/a> can generate a lot of notifications which can be difficult to follow,\nthis documents some of my process for keeping up with it! For reference, I\nsubscribe&nbsp;to:<\/p>\n<ol class=\"arabic simple\">\n<li>All notifications for the repositories I work in somewhat&nbsp;frequently;<\/li>\n<li>Only releases and security alerts for repositories which might affect me (e.g.\nupstream&nbsp;repositories);<\/li>\n<li>Other issues that might be related to the project I&#8217;m working on (e.g. bugs\nin upstream&nbsp;projects).<\/li>\n<\/ol>\n<p>I also watch a bunch of open source projects and have some of my own projects.\n(These are mostly Twisted or Celery&nbsp;related.)<\/p>\n<p>I generally enjoy having some idea of &#8220;everything&#8221; going on in my team (in enough\ndetail to know what people are generally working&nbsp;on).<\/p>\n<p>To avoid being overwhelmed by notifications I only subscribe to specific issues\nfor repositories from other teams or projects. These are&nbsp;usually:<\/p>\n<ul class=\"simple\">\n<li>Things that personally annoy me (and I want to see&nbsp;fixed);<\/li>\n<li>Things that are directly related to or blocking my&nbsp;work;<\/li>\n<\/ul>\n<p>For reference, I currently <a class=\"reference external\" href=\"https:\/\/github.com\/watching\">watch 321 repositories<\/a>, although most of my\nnotifications probably come from &lt; 20 repositories. I also have 32 repositories\nwith custom notification rules &#8212; those are set to only releases <span class=\"amp\">&amp;<\/span> Security alerts.\n(And I have 1 muted repository.) <a class=\"footnote-reference\" href=\"#footnote-1\" id=\"footnote-reference-1\">[1]<\/a><\/p>\n<div class=\"section\" id=\"when-how\">\n<h2>When \/&nbsp;how<\/h2>\n<p>I tend to do the following&nbsp;daily:<\/p>\n<ul class=\"simple\">\n<li>Catch-up on notifications in the morning (takes ~15 - 45 minutes for GitHub,\nchat, e-mail,&nbsp;etc.).<\/li>\n<li>Check notifications a few times during the day (between meetings, after lunch,\nwhile tests runs,&nbsp;etc.).<\/li>\n<\/ul>\n<p>Each time I check notifications I quickly triage each notification by skimming\nthe title to see if I&#8217;m interested (sometimes the title is enough info!). From\nthis I do one of several&nbsp;things:<\/p>\n<ul class=\"simple\">\n<li>Open any issue in a separate tab to come back to if I need to read more (or\npotentially take action). I usually skim the update, leaving it open if I need\nto respond, closing the tab if I&nbsp;don&#8217;t.<\/li>\n<li><span class=\"dquo\">&#8220;<\/span>Mark as read&#8221; if I know it does not require anything from me:<ul>\n<li>A review someone else is handling (unless it is a bit of code I&#8217;m keen to\nunderstand, or know is tricky and feel some ownership&nbsp;over).<\/li>\n<li>The title contains enough information I don&#8217;t need to read the issue (e.g.\na colleague following a follow-up&nbsp;issue).<\/li>\n<li>Obvious support requests, unless I&#8217;m the maintainer. <a class=\"footnote-reference\" href=\"#footnote-2\" id=\"footnote-reference-2\">[2]<\/a><\/li>\n<li>Random MSCs \/ matrix-doc issues that I don&#8217;t care&nbsp;about.<\/li>\n<\/ul>\n<\/li>\n<li><strong>Unsubscribing<\/strong> if I&#8217;m not interested in following the issue (e.g. an open\nsource project is re-doing their <span class=\"caps\">CI<\/span>). This was key for me watching other\nprojects that I only somewhat care&nbsp;about.<\/li>\n<\/ul>\n<p>I use both Thunderbird and the GitHub website (specifically the\n<a class=\"reference external\" href=\"https:\/\/github.com\/notifications?query=is%3Aunread\">unread notifications view<\/a>) to go through notifications. Note that the website\nhas quick buttons on the right which I use frequently: &#8220;Done&#8221; and &#8220;Unsubscribe&#8221;\n(there is also &#8220;Save&#8221; &#8212; which I do not use, I mark as unread if I need to come back).\nIt can also be useful to &#8220;Mark as done&#8221; an entire repository for projects I\nfollow out of vague interest, but don&#8217;t have time to read at the&nbsp;moment.<\/p>\n<p><span class=\"dquo\">&#8220;<\/span>Open unread&#8221; is useful to get everything into separate tabs for later processing\n(and to avoid waiting for GitHub to load). I usually use it when I have &lt; 10\nnotifications left for a particular&nbsp;repository.<\/p>\n<p>I usually attempt to go through notifications that I know I won&#8217;t have to respond\nto first, as they can be quickly processed and reduce the overwhelming number of&nbsp;notifications.<\/p>\n<\/div>\n<div class=\"section\" id=\"setup\">\n<h2>Setup<\/h2>\n<p>This workflow refers to using GitHub with <a class=\"reference external\" href=\"https:\/\/www.thunderbird.net\/\">Mozilla Thunderbird<\/a> (via <a class=\"reference external\" href=\"https:\/\/www.fastmail.com\/\">Fastmail<\/a>)\nand <a class=\"reference external\" href=\"https:\/\/getfirefox.net\">Mozilla Firefox<\/a>, none of it is particular to those applications and can be\nadapted to&nbsp;others.<\/p>\n<div class=\"section\" id=\"github\">\n<h3>GitHub<\/h3>\n<p>If you use GitHub for both work and other personal \/ open source projects it can\nbe helpful to route your work notifications to a separate email address. (This is\na good idea regardless for security <span class=\"amp\">&amp;<\/span> intellectual property&nbsp;concerns.)<\/p>\n<p>Your default email can be configured on the <a class=\"reference external\" href=\"https:\/\/github.com\/settings\/notifications\">Notifications page<\/a> and\nseparation by organization can be configured on the <a class=\"reference external\" href=\"https:\/\/github.com\/settings\/notifications\/custom_routing\">Custom routing page<\/a>.\nUnder &#8220;Subscriptions&#8221; on the Notification page, I have both &#8220;Watching&#8221; and\n&#8220;Participating, &#64;mentions and custom&#8221; set to notify on both GitHub <span class=\"amp\">&amp;<\/span>&nbsp;email.<\/p>\n<p>You may also want to tweak your &#8220;Customize email preferences&#8221;. I have the\nfollowing&nbsp;enabled:<\/p>\n<ul class=\"simple\">\n<li><span class=\"dquo\">&#8220;<\/span>Pull Request&nbsp;reviews&#8221;<\/li>\n<li><span class=\"dquo\">&#8220;<\/span>Comments on Issues and Pull&nbsp;Requests&#8221;<\/li>\n<li><span class=\"dquo\">&#8220;<\/span>Include your own updates&#8221; &#8212; this sounds weird, but you only need to lose a\nmassive comment on GitHub once to want a copy of it in your inbox. (I\nautomatically mark them as read, see&nbsp;below.)<\/li>\n<\/ul>\n<p>I disable &#8220;Pull Request pushes&#8221; because I don&#8217;t find it useful, although you will\nstill get these via the&nbsp;website.<\/p>\n<\/div>\n<div class=\"section\" id=\"fastmail\">\n<h3>Fastmail<\/h3>\n<p>I have two mail rules setup in Fastmail to move all GitHub email to a separate\nfolder and to mark my own emails as read: <a class=\"footnote-reference\" href=\"#footnote-3\" id=\"footnote-reference-3\">[3]<\/a><\/p>\n<ol class=\"arabic simple\">\n<li>From: <tt class=\"docutils literal\">Patrick Cloke &lt;notifications&#64;github.com&gt;<\/tt>:\n1. Mark as read\n2. Move to&nbsp;&#8220;GitHub&#8221;<\/li>\n<li>From email address: <tt class=\"docutils literal\">notifications&#64;github.com<\/tt>:\n1. Move to&nbsp;&#8220;GitHub&#8221;<\/li>\n<\/ol>\n<p>Similar filters can be setup on other mail services, e.g. Google&nbsp;Mail:<\/p>\n<ol class=\"arabic simple\">\n<li>Matches: <tt class=\"docutils literal\"><span class=\"pre\">from:(Patrick<\/span> Cloke &lt;notifications&#64;github.com&gt;)<\/tt>\n1. Skip Inbox\n2. Mark as read\n3. Apply label:&nbsp;&#8220;GitHub&#8221;<\/li>\n<li>Matches: <tt class=\"docutils literal\"><span class=\"pre\">from:(notifications&#64;github.com)<\/span><\/tt>\n1. Skip Inbox\n2. Apply label:&nbsp;&#8220;GitHub&#8221;<\/li>\n<\/ol>\n<p>You can also check for <a class=\"reference external\" href=\"https:\/\/docs.github.com\/en\/account-and-profile\/managing-subscriptions-and-notifications-on-github\/setting-up-notifications\/configuring-notifications#filtering-email-notifications\">more ways to filter GitHub emails<\/a>.<\/p>\n<\/div>\n<div class=\"section\" id=\"mozilla-thunderbird\">\n<h3>Mozilla&nbsp;Thunderbird<\/h3>\n<p>For all of my folders I use threads (View &gt; Sort By &gt; Threaded) and only view\nthreads which have unread messages (View &gt; Threads &gt; Threads with&nbsp;Unread).<\/p>\n<p>Other things that are&nbsp;useful:<\/p>\n<ul class=\"simple\">\n<li>Enable &#8220;Automatically mark messages as read&#8221;, but with a short delay (I have\n&#8220;After displaying for&#8221; set to 1 second).  (This lets you move through messages\nquickly using the keyboard or shortcuts without marking them all by&nbsp;mistake.)<\/li>\n<li>Add GitHub to the exceptions list in under &#8220;Allow remote content in messages&#8221;\nfor either <cite>notifications&#64;github.com<\/cite> or the <cite>https:\/\/github.com<\/cite>: this can\nbe added when viewing an email from GitHub. (This will\n<a class=\"reference external\" href=\"https:\/\/docs.github.com\/en\/account-and-profile\/managing-subscriptions-and-notifications-on-github\/setting-up-notifications\/configuring-notifications#notification-delivery-options\">mark the notification as read<\/a> the GitHub website&nbsp;automatically.)<\/li>\n<\/ul>\n<p>I sort my threads by date, oldest first so I can just click the &#8220;n&#8221; hotkey to\nmove through messages quickly. I also use the message pane to have some context\non remaining unread messages per thread, but it should work fine without that.\nIf you decide you don&#8217;t care about the rest of the thread &#8220;r&#8221; marks it as read.\nNote that reading any messages in a thread will mark the entire issue or pull\nrequest as done on the website. I find this extremely efficient for going through\na small number of notifications&nbsp;quickly.<\/p>\n<p>I very much wish there was a way to sync back the read status of notifications from\nGitHub back to Thunderbird. Lacking that I tend to mark the entire folder as read\n(Shift+C) if I&#8217;ve caught up on the website. <a class=\"footnote-reference\" href=\"#footnote-4\" id=\"footnote-reference-4\">[4]<\/a><\/p>\n<\/div>\n<div class=\"section\" id=\"mozilla-firefox\">\n<h3>Mozilla&nbsp;Firefox<\/h3>\n<p>I  use a few GitHub related extensions which can&nbsp;help:<\/p>\n<ul class=\"simple\">\n<li><a class=\"reference external\" href=\"https:\/\/github.com\/sindresorhus\/refined-github\/\">Refined GitHub<\/a>: includes lots of small tweaks to make GitHub&nbsp;&#8220;better&#8221;.<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/github.com\/fregante\/github-issue-link-status\">GitHub Issue Link Status<\/a>: colors the issue \/ <span class=\"caps\">PR<\/span> links with whether it is\nopen \/ closed \/ etc and marks it as an issue \/ <span class=\"caps\">PR<\/span>.<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/github.com\/tanmayrajani\/notifications-preview-github\">Notifications Preview for GitHub<\/a>: makes the notification button a dropdown\nfor quick&nbsp;processing.<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/github.com\/homerchen19\/github-file-icons\">File Icons for GitHub and GitLab<\/a>: adds file icons per file type for&nbsp;GitHub<\/li>\n<li><a class=\"reference external\" href=\"https:\/\/github.com\/freaktechnik\/advanced-github-notifier\">Advanced GitHub Notifier<\/a>: adds a Firefox toolbar button with easy access to\nyour notifications (including a count of unread&nbsp;notifications)<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"conclusion\">\n<h2>Conclusion<\/h2>\n<p>Hopefully some of this is helpful, please let me know if you have any questions\nor&nbsp;thoughts!<\/p>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-1\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-1\">[1]<\/a><\/td><td>In August 2021 I was watching 263 repositories and had 18 repositories with\ncustom notification settings.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-2\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-2\">[2]<\/a><\/td><td>My team rotates through who is the first-line of contacts for incoming\ncommunity requests, releases, etc.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-3\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-3\">[3]<\/a><\/td><td>I have similar filters setup for <a class=\"reference external\" href=\"https:\/\/gitlab.com\">GitLab<\/a>, <a class=\"reference external\" href=\"https:\/\/sentry.io\">Sentry<\/a>, etc.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-4\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-4\">[4]<\/a><\/td><td>You could probably do this with an Thunderbird extension, but I&#8217;ve failed to\nfind time to look into it.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","pubDate":"Fri, 06 Oct 2023 07:55:00 -0400","guid":"tag:patrick.cloke.us,2023-10-06:\/posts\/2023\/10\/06\/handling-github-notifications\/","category":["articles","thunderbird"]},{"title":"Matrix Live demo on Linearized\u00a0Matrix","link":"https:\/\/patrick.cloke.us\/posts\/2023\/10\/04\/matrix-live-demo-on-linearized-matrix\/","description":"<p>I demoed some of my work at <a class=\"reference external\" href=\"https:\/\/element.io\">Element<\/a> on Matrix Live back on August 4th&#8217;s\n<a class=\"reference external\" href=\"https:\/\/matrix.org\/blog\/2023\/08\/04\/this-week-in-matrix-2023-08-04\/\">This Week in Matrix<\/a> (and failed to mention it here). I talked a bit about\nwhat <a class=\"reference external\" href=\"https:\/\/turt2live.github.io\/ietf-mimi-linearized-matrix\/draft-ralston-mimi-linearized-matrix.html\">Linearized Matrix<\/a>, Element&#8217;s effort for the <span class=\"caps\">IETF<\/span>&#8217;s\n<a class=\"reference external\" href=\"https:\/\/datatracker.ietf.org\/group\/mimi\/about\/\">&#8220;More Instant Messaging Interoperability&#8221; (<span class=\"caps\">MIMI<\/span>)<\/a> working&nbsp;group.<\/p>\n<p>I demoed two <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\">Synapse<\/a> instances as a dual-stack Matrix\/Linearised Matrix\nhomeserver communicating over federation with two <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/eigen-server\">eigen-server<\/a> instances, an\nexample greenfield Linearized Matrix server. THis work server as a proof of\nconcept for interoperability between Matrix and Linearized&nbsp;Matrix.<\/p>\n<p>You can find a bit more about the approach (and the code) on <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/issues\/15954\">GitHub<\/a>.<\/p>\n<p>Check out the video below (my bit starts around the 5:27 mark) and let me know\nif you have any&nbsp;questions!<\/p>\n<div class=\"youtube youtube-16x9\"><iframe src=\"https:\/\/www.youtube-nocookie.com\/embed\/1LxkbTku0XQ?start=327\" allowfullscreen seamless frameBorder=\"0\"><\/iframe><\/div>","pubDate":"Wed, 04 Oct 2023 09:55:00 -0400","guid":"tag:patrick.cloke.us,2023-10-04:\/posts\/2023\/10\/04\/matrix-live-demo-on-linearized-matrix\/","category":["articles","matrix"]},{"title":"Celery architecture\u00a0breakdown","link":"https:\/\/patrick.cloke.us\/posts\/2023\/09\/15\/celery-architecture-breakdown\/","description":"\n<p>The <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/\">Celery project<\/a>, which is often used Python library to run \u201cbackground tasks\u201d\nfor synchronous web frameworks, describes itself\u00a0as:<\/p>\n<blockquote>\n<p>Celery is a simple, flexible, and reliable distributed system to process vast\namounts of messages , while providing operations with the tools required to\nmaintain such a\u00a0system.<\/p>\n<p>It\u2019s a task queue with focus on real-time processing, while also supporting\ntask\u00a0scheduling.<\/p>\n<\/blockquote>\n<p>The documentation goes into great detail about how to configure Celery with\nits plethora of options, but it does not focus much on the <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/getting-started\/introduction.html\">high level architecture<\/a>\nor how messages pass between the components. Celery is <em>extremely<\/em> flexible (almost\nevery component can be easily replaced!) but this can make it hard to understand.\nI attempt to break it down to the best of my understanding below. <a class=\"footnote-reference\" href=\"#footnote-1\" id=\"footnote-reference-1\">[1]<\/a><\/p>\n<div class=\"section\" id=\"high-level-architecture\">\n<h2><a class=\"toc-backref\" href=\"#toc-entry-1\">High Level\u00a0Architecture<\/a><\/h2>\n<p>Celery has a few main components <a class=\"footnote-reference\" href=\"#footnote-2\" id=\"footnote-reference-2\">[2]<\/a>:<\/p>\n<ol class=\"arabic simple\">\n<li>Your application code, including any <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/userguide\/tasks.html\"><tt class=\"docutils literal\">Task<\/tt><\/a> objects you\u2019ve defined. (Usually\ncalled the \u201cclient\u201d in Celery\u2019s\u00a0documentation.)<\/li>\n<li>A <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/getting-started\/backends-and-brokers\/index.html\">broker<\/a> or message\u00a0transport.<\/li>\n<li>One or more Celery <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/internals\/worker.html\">workers<\/a>.<\/li>\n<li>A (results) <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/getting-started\/backends-and-brokers\/index.html\">backend<\/a>.<\/li>\n<\/ol>\n<blockquote class=\"text-center\">\n<a class=\"reference external image-reference\" href=\"https:\/\/patrick.cloke.us\/images\/celery-architecture\/celery-overview.png\"><img alt=\"Celery overview\" src=\"https:\/\/patrick.cloke.us\/images\/celery-architecture\/celery-overview.png\" style=\"width: 50%;\"\/><\/a>\n<p>A simplified view of Celery\u00a0components.<\/p>\n<\/blockquote>\n<p>In order to use Celery you need\u00a0to:<\/p>\n<ol class=\"arabic simple\">\n<li>Instantiate a Celery <tt class=\"docutils literal\">application<\/tt> (which includes configuration, such as\nwhich broker and backend to use and how to connect to them) and define one or\nmore <tt class=\"docutils literal\">Task<\/tt> definitions.<\/li>\n<li>Run a\u00a0broker.<\/li>\n<li>Run one or more Celery\u00a0workers.<\/li>\n<li>(Maybe) run a\u00a0backend.<\/li>\n<\/ol>\n<p>If you\u2019re unfamiliar with Celery, below is an example. It declares a simple\n<tt class=\"docutils literal\">add<\/tt> task using the <tt class=\"docutils literal\">@task<\/tt> decorator and will request the task to be executed\nin the background twice (<tt class=\"docutils literal\"><span class=\"pre\">add.delay(...)<\/span><\/tt>). <a class=\"footnote-reference\" href=\"#footnote-3\" id=\"footnote-reference-3\">[3]<\/a> The results are then fetched\n(<tt class=\"docutils literal\">asyncresult_1.get()<\/tt>) and printed. Place this in a file named <tt class=\"docutils literal\">my_app.py<\/tt>:<\/p>\n<pre class=\"code python highlight literal-block\">\n<span class=\"kn\">from<\/span> <span class=\"nn\">celery<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">Celery<\/span><span class=\"w\">\n\n<\/span><span class=\"n\">app<\/span> <span class=\"o\">=<\/span> <span class=\"n\">Celery<\/span><span class=\"p\">(<\/span><span class=\"w\">\n<\/span>    <span class=\"s2\">\"my_app\"<\/span><span class=\"p\">,<\/span><span class=\"w\">\n<\/span>    <span class=\"n\">backend<\/span><span class=\"o\">=<\/span><span class=\"s2\">\"rpc:\/\/\"<\/span><span class=\"p\">,<\/span><span class=\"w\">\n<\/span>    <span class=\"n\">broker<\/span><span class=\"o\">=<\/span><span class=\"s2\">\"amqp:\/\/guest@localhost\/\/\"<\/span><span class=\"p\">,<\/span><span class=\"w\">\n<\/span><span class=\"p\">)<\/span><span class=\"w\">\n\n\n<\/span><span class=\"nd\">@app<\/span><span class=\"o\">.<\/span><span class=\"n\">task<\/span><span class=\"p\">()<\/span><span class=\"w\">\n<\/span><span class=\"k\">def<\/span> <span class=\"nf\">add<\/span><span class=\"p\">(<\/span><span class=\"n\">a<\/span><span class=\"p\">:<\/span> <span class=\"nb\">int<\/span><span class=\"p\">,<\/span> <span class=\"n\">b<\/span><span class=\"p\">:<\/span> <span class=\"nb\">int<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"nb\">int<\/span><span class=\"p\">:<\/span><span class=\"w\">\n<\/span>    <span class=\"k\">return<\/span> <span class=\"n\">a<\/span> <span class=\"o\">+<\/span> <span class=\"n\">b<\/span><span class=\"w\">\n\n\n<\/span><span class=\"k\">if<\/span> <span class=\"vm\">__name__<\/span> <span class=\"o\">==<\/span> <span class=\"s2\">\"__main__\"<\/span><span class=\"p\">:<\/span><span class=\"w\">\n<\/span>    <span class=\"c1\"># Request that the tasks run and capture their async results.<\/span><span class=\"w\">\n<\/span>    <span class=\"n\">asyncresult_1<\/span> <span class=\"o\">=<\/span> <span class=\"n\">add<\/span><span class=\"o\">.<\/span><span class=\"n\">delay<\/span><span class=\"p\">(<\/span><span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"mi\">2<\/span><span class=\"p\">)<\/span><span class=\"w\">\n<\/span>    <span class=\"n\">asyncresult_2<\/span> <span class=\"o\">=<\/span> <span class=\"n\">add<\/span><span class=\"o\">.<\/span><span class=\"n\">delay<\/span><span class=\"p\">(<\/span><span class=\"mi\">3<\/span><span class=\"p\">,<\/span> <span class=\"mi\">4<\/span><span class=\"p\">)<\/span><span class=\"w\">\n\n<\/span>    <span class=\"n\">result_1<\/span> <span class=\"o\">=<\/span> <span class=\"n\">asyncresult_1<\/span><span class=\"o\">.<\/span><span class=\"n\">get<\/span><span class=\"p\">()<\/span><span class=\"w\">\n<\/span>    <span class=\"n\">result_2<\/span> <span class=\"o\">=<\/span> <span class=\"n\">asyncresult_2<\/span><span class=\"o\">.<\/span><span class=\"n\">get<\/span><span class=\"p\">()<\/span><span class=\"w\">\n<\/span>    <span class=\"c1\"># Should result in 3, 7.<\/span><span class=\"w\">\n<\/span>    <span class=\"nb\">print<\/span><span class=\"p\">(<\/span><span class=\"sa\">f<\/span><span class=\"s2\">\"Results: <\/span><span class=\"si\">{<\/span><span class=\"n\">result_1<\/span><span class=\"si\">}<\/span><span class=\"s2\">, <\/span><span class=\"si\">{<\/span><span class=\"n\">result_2<\/span><span class=\"si\">}<\/span><span class=\"s2\">\"<\/span><span class=\"p\">)<\/span>\n<\/pre>\n<p>Usually you don\u2019t care where (which worker) the task runs on it, or how it gets\nthere but sometimes you need! We can break down the components more to reveal more\u00a0detail:<\/p>\n<blockquote class=\"text-center\">\n<a class=\"reference external image-reference\" href=\"https:\/\/patrick.cloke.us\/images\/celery-architecture\/celery-components.png\"><img alt=\"Celery components\" src=\"https:\/\/patrick.cloke.us\/images\/celery-architecture\/celery-components.png\" style=\"width: 50%;\"\/><\/a>\n<p>The Celery components broken into\u00a0sub-components.<\/p>\n<\/blockquote>\n<div class=\"section\" id=\"broker\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-2\">Broker<\/a><\/h3>\n<p>The message broker is a piece of off-the-shelf software which takes task requests\nand queues them until a worker is ready to process them. Common options include\n<a class=\"reference external\" href=\"https:\/\/www.rabbitmq.com\/\">RabbitMQ<\/a>, or <a class=\"reference external\" href=\"https:\/\/redis.io\">Redis<\/a>, although your cloud provider might have a custom\u00a0one.<\/p>\n<p>The broker may have some sub-components, including an exchange and one or more\nqueues. (Note that Celery tends to use <span class=\"caps\">AMQP<\/span> terminology and sometimes emulates\nfeatures which do not exist on other\u00a0brokers.)<\/p>\n<p>Configuring your broker is beyond the scope of this article (and depends heavily\non workload). The Celery <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/userguide\/routing.html\">routing documentation<\/a> has more information on how and\nwhy you might route tasks to different\u00a0queues.<\/p>\n<\/div>\n<div class=\"section\" id=\"workers\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-3\">Workers<\/a><\/h3>\n<p>Celery workers fetch queued tasks from the broker and then run the code defined in\nyour <tt class=\"docutils literal\">task<\/tt>, they can optionally return a value via the results\u00a0backend.<\/p>\n<p>Celery workers have a \u201cconsumer\u201d which fetches tasks from the broker: by default\nit requests many tasks at once, equivalent to \u201c<a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/userguide\/configuration.html#std-setting-worker_prefetch_multiplier\">prefetch multiplier<\/a> x <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/userguide\/configuration.html#std-setting-worker_concurrency\">concurrency<\/a>\u201c.\n(If your prefetch multiplier is 5 and your concurrency is 4, it attempts to\nfetch up to 20 queued tasks from the broker.) Once fetched it places them into\nan in-memory buffer. The task pool then runs each task via its <tt class=\"docutils literal\">Strategy<\/tt> \u2014\nfor a normal Celery <tt class=\"docutils literal\">Task<\/tt> the task pool essentially executes tasks from the\nconsumer\u2019s\u00a0buffer.<\/p>\n<p>The worker also handles scheduling tasks to run in future (by queueing them\nin-memory), but we will not go deeper into that\u00a0here.<\/p>\n<p>Using the \u201cprefork\u201d pool, the consumer and task pool are separate processes, while\nthe \u201cgevent\u201d\/\u201deventlet\u201d pool uses coroutines, and the \u201cthreads\u201d pool uses threads.\nThere\u2019s also a \u201csolo\u201d pool which can be useful for testing (everything is run in\nthe same process: a single task runs at a time and blocks the consumer from\nfetching more\u00a0tasks.)<\/p>\n<\/div>\n<div class=\"section\" id=\"backend\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-4\">Backend<\/a><\/h3>\n<p>The backend is another piece of off-the-shelf software which is used to store the\nresults of your task. It provides a key-value store and is commonly <a class=\"reference external\" href=\"https:\/\/redis.io\">Redis<\/a>,\nalthough there are many options depending on how durable and large your results\nare. The results backend can be queried by using the <tt class=\"docutils literal\">AsyncResult<\/tt> object which\nis returned to your application code. <a class=\"footnote-reference\" href=\"#footnote-4\" id=\"footnote-reference-4\">[4]<\/a><\/p>\n<p>Much like for brokers, how you configure results backends is beyond the scope of\nthis\u00a0article.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"dataflow\">\n<h2><a class=\"toc-backref\" href=\"#toc-entry-5\">Dataflow<\/a><\/h2>\n<p>You might have observed that the above components discussed at least several\ndifferent processes (client, broker, worker, worker pool, backend) which may also\nexist on different computers. How does this all work to pass the task between\nthem? Usually this level of detail isn\u2019t necessary to understand what it means\nto \u201crun a task in the background\u201d, but it can be useful for diagnosing performance\nor configuring brokers and\u00a0backends.<\/p>\n<p>The main thing to understand is that there\u2019s lots of serialization happening across\neach process\u00a0boundary:<\/p>\n<blockquote class=\"text-center\">\n<a class=\"reference external image-reference\" href=\"https:\/\/patrick.cloke.us\/images\/celery-architecture\/celery-dataflow.png\"><img alt=\"Celery dataflow\" src=\"https:\/\/patrick.cloke.us\/images\/celery-architecture\/celery-dataflow.png\" style=\"width: 50%;\"\/><\/a>\n<p>A task message traversing from application code to the broker to a worker,\nand a result traversing from a worker to a backend to application\u00a0code.<\/p>\n<\/blockquote>\n<div class=\"section\" id=\"request-serialization\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-6\">Request\u00a0Serialization<\/a><\/h3>\n<p>When a client requests for a task to be run the information needs to be passed to\nthe broker in a form it understands. The necessary data\u00a0includes:<\/p>\n<ul class=\"simple\">\n<li>The task identifier (e.g. <tt class=\"docutils literal\">my_app.add<\/tt>).<\/li>\n<li>Any arguments (e.g. <tt class=\"docutils literal\">(1, 2)<\/tt>) and keyword\u00a0arguments.<\/li>\n<li>A request <span class=\"caps\">ID<\/span>.<\/li>\n<li>Routing\u00a0information.<\/li>\n<li>\u2026and a bunch of other\u00a0metadata.<\/li>\n<\/ul>\n<p>Exactly what is included is defined by the <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/internals\/protocol.html#message-protocol-task-v2\">message protocol<\/a> (of which Celery\nhas two, although they\u2019re fairly\u00a0similar).<\/p>\n<p>Most of the metadata gets placed in the headers while the task arguments, which\nmight be any Python class, need to be serialized into the body. Celery supports\n<a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/userguide\/calling.html#calling-serializers\">many serializers<\/a> and uses <a class=\"reference external\" href=\"https:\/\/docs.python.org\/3\/library\/json.html\"><span class=\"caps\">JSON<\/span><\/a> by default (<a class=\"reference external\" href=\"https:\/\/docs.python.org\/dev\/library\/pickle.html#module-pickle\">pickle<\/a>, <a class=\"reference external\" href=\"http:\/\/yaml.org\/\"><span class=\"caps\">YAML<\/span><\/a>, and <a class=\"reference external\" href=\"http:\/\/msgpack.org\/\">msgpack<\/a>,\nas well as custom schemes can be used as\u00a0well).<\/p>\n<p>After serialization, Celery also supports <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/userguide\/calling.html#compression\">compressing the message<\/a> or\n<a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/userguide\/security.html#message-signing\">signing the message<\/a> for additional\u00a0security.<\/p>\n<p>An example <span class=\"caps\">AMQP<\/span> message containing the details of a task request (from RabbitMQ\u2019s\n<a class=\"reference external\" href=\"https:\/\/www.rabbitmq.com\/management.html\">management interface<\/a>) is shown\u00a0below:<\/p>\n<blockquote class=\"text-center\">\n<a class=\"reference external image-reference\" href=\"https:\/\/patrick.cloke.us\/images\/celery-architecture\/rabbitmq-task-message.png\"><img alt=\"Celery task wrapped in a RabbitMQ message\" src=\"https:\/\/patrick.cloke.us\/images\/celery-architecture\/rabbitmq-task-message.png\" style=\"width: 50%;\"\/><\/a>\n<p>The example Celery task wrapped in a RabbitMQ\u00a0message<\/p>\n<\/blockquote>\n<p>When a worker fetches a task from the broker it deserializes it into a <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/userguide\/tasks.html#task-request\">Request<\/a>\nand executes it (as discussed above). In the case of a \u201cprefork\u201d worker pool the\n<tt class=\"docutils literal\">Request<\/tt> is serialized <em>again<\/em> using pickle when passed to task pool <a class=\"footnote-reference\" href=\"#footnote-5\" id=\"footnote-reference-5\">[5]<\/a>.<\/p>\n<p>The worker pool then unpickles the request, loads the task code, and executes\nit with the requested arguments. Finally your task code is running! Note that the\ntask code itself is not contained in the serialized request, that is loaded\nseparately by the\u00a0worker.<\/p>\n<\/div>\n<div class=\"section\" id=\"result-serialization\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-7\">Result\u00a0Serialization<\/a><\/h3>\n<p>When a task returns a value it gets stored in the results backend with enough\ninformation for the original client to find\u00a0it:<\/p>\n<ul class=\"simple\">\n<li>The result <span class=\"caps\">ID<\/span>.<\/li>\n<li>The\u00a0result.<\/li>\n<li>\u2026and some other\u00a0metadata.<\/li>\n<\/ul>\n<p>Similarly to tasks this information must be serialized before being placed in the\nresults backend (and gets split between the headers and body). Celery provides\nconfiguration options to customize this serialization. <a class=\"footnote-reference\" href=\"#footnote-6\" id=\"footnote-reference-6\">[6]<\/a><\/p>\n<p>An example <span class=\"caps\">AMQP<\/span> message containing the details of a result is shown\u00a0below:<\/p>\n<blockquote class=\"text-center\">\n<a class=\"reference external image-reference\" href=\"https:\/\/patrick.cloke.us\/images\/celery-architecture\/rabbitmq-result-message.png\"><img alt=\"Celery result wrapped in a RabbitMQ message\" src=\"https:\/\/patrick.cloke.us\/images\/celery-architecture\/rabbitmq-result-message.png\" style=\"width: 50%;\"\/><\/a>\n<p>The example Celery result wrapped in a RabbitMQ\u00a0message<\/p>\n<\/blockquote>\n<p>Once the result is fetched by the client it can deserialized the true (Python)\nreturn value and provide it to the application\u00a0code.<\/p>\n<\/div>\n<div class=\"section\" id=\"final-thoughts\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-8\">Final\u00a0thoughts<\/a><\/h3>\n<p>Since the Celery protocol is a public, documented <span class=\"caps\">API<\/span> it allows you to create\ntask requests externally to Celery! As long as you can interface to the Celery broker\n(and have some shared configuration) you can use a different application (or programming\nlanguage) to publish and\/or consume tasks. This is exactly what others have\u00a0done:<\/p>\n<ul class=\"simple\">\n<li>JavaScript \/ TypeScript<ul>\n<li><a class=\"reference external\" href=\"https:\/\/github.com\/mher\/node-celery\">node-celery<\/a><\/li>\n<li><a class=\"reference external\" href=\"https:\/\/github.com\/node-celery-ts\/node-celery-ts\">node-celery-ts<\/a><\/li>\n<\/ul>\n<\/li>\n<li><span class=\"caps\">PHP<\/span>:<ul>\n<li><a class=\"reference external\" href=\"https:\/\/github.com\/gjedeer\/celery-php\">celery-php<\/a><\/li>\n<\/ul>\n<\/li>\n<li>Rust<ul>\n<li><a class=\"reference external\" href=\"https:\/\/github.com\/rusty-celery\/rusty-celery\">rusty-celery<\/a><\/li>\n<\/ul>\n<\/li>\n<li>Go<ul>\n<li><a class=\"reference external\" href=\"https:\/\/github.com\/gocelery\/gocelery\">gocelery<\/a><\/li>\n<li><a class=\"reference external\" href=\"https:\/\/github.com\/marselester\/gopher-celery\">gopher-celery<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Note that I haven\u2019t used any of the above projects (and can\u2019t vouch for\u00a0them).<\/p>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-1\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-1\">[1]<\/a><\/td><td>Part of this started out as an <a class=\"reference external\" href=\"https:\/\/github.com\/clokep\/celery-batches\/issues\/69#issuecomment-1181855643\">explanation of how celery-batches works<\/a>.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-2\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-2\">[2]<\/a><\/td><td><a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/userguide\/periodic-tasks.html\">Celery beat<\/a> is another common component used to run scheduled or periodic\ntasks. Architecture wise it takes the same place as your application code,\ni.e. it runs forever and requests for tasks to be executed based on the time.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-3\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-3\">[3]<\/a><\/td><td>There\u2019s a <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/userguide\/calling.html\">bunch of ways<\/a> to do this, <tt class=\"docutils literal\">apply_async<\/tt> and <tt class=\"docutils literal\">delay<\/tt> are the\nmost common, but don\u2019t impact the contents of this article.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-4\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-4\">[4]<\/a><\/td><td>As a quick aside \u2014 <tt class=\"docutils literal\">AsyncResult<\/tt> does not refer to async\/await in Python.\n<tt class=\"docutils literal\">AsyncResult.get()<\/tt> is <strong>synchronous<\/strong>. A <a class=\"reference external\" href=\"https:\/\/patrick.cloke.us\/posts\/2018\/10\/23\/calling-celery-from-twisted\/\">previous article<\/a> has some more\ninformation on this.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-5\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-5\">[5]<\/a><\/td><td>This is not configurable. The Celery <a class=\"reference external\" href=\"https:\/\/docs.celeryq.dev\/en\/v5.2.7\/userguide\/security.html#serializers\">security guide<\/a> recommends not using\npickle for serializers (and it is <a class=\"reference external\" href=\"https:\/\/docs.python.org\/dev\/library\/pickle.html\">well known<\/a> that pickle can be a security\nflaw), but it does not seem documented anywhere that pickle will be used with\nthe prefork pool. If you are using <span class=\"caps\">JSON<\/span> to initially serialize to the broker\nthen your task should only be left with \u201csimple\u201d types (strings, integers,\nfloats, null, lists, and dictionaries) so this should not be an issue.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-6\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-6\">[6]<\/a><\/td><td>Tasks and results can be configured to have different serializers (or different\ncompression settings) via the <tt class=\"docutils literal\">task_<\/tt> vs. <tt class=\"docutils literal\">result_<\/tt> configuration options.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n","pubDate":"Fri, 15 Sep 2023 15:28:00 -0400","guid":"tag:patrick.cloke.us,2023-09-15:\/posts\/2023\/09\/15\/celery-architecture-breakdown\/","category":["articles","celery"]},{"title":"Matrix Push Rules &\u00a0Notifications","link":"https:\/\/patrick.cloke.us\/posts\/2023\/05\/08\/matrix-push-rules-notifications\/","description":"<p>In a previous post about <a class=\"reference external\" href=\"https:\/\/patrick.cloke.us\/posts\/2023\/01\/05\/matrix-read-receipts-and-notifications\/\">read receipts <span class=\"amp\">&amp;<\/span> notifications in Matrix<\/a> I briefly\nmentioned that push rules generate notifications, but with little detail. After\ncompleting a rather large project to improve notifications in Matrix I want to\nfill in some of those blanks. <a class=\"footnote-reference\" href=\"#footnote-1\" id=\"footnote-reference-1\">[1]<\/a><\/p>\n<!-- comment:\n\nAdapted from https:\/\/docs.google.com\/presentation\/d\/1odrbD5wMwGz_qUtG5U1pFb7p3sFwLApDaYtyHpdI-Oo\/edit -->\n\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p class=\"last\">These notes are true as of the v1.6 of the Matrix spec and also cover some\nMatrix spec changes which may or may not have been merged\u00a0since.<\/p>\n<\/div>\n<div class=\"section\" id=\"push-notifications-in-matrix\">\n<h2><a class=\"toc-backref\" href=\"#toc-entry-1\">Push notifications in\u00a0Matrix<\/a><\/h2>\n<p>Matrix includes a <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.6\/client-server-api\/#push-notifications\">push notifications module<\/a> which defines when Matrix events\nare considered an unread <strong>notification<\/strong> or <strong>highlight notification<\/strong> <a class=\"footnote-reference\" href=\"#footnote-2\" id=\"footnote-reference-2\">[2]<\/a>\nand <em>how<\/em> those events are <strong>sent to third-party push notification services<\/strong>.<\/p>\n<p><strong>Push rules<\/strong> are a set of <em>ordered<\/em> rules which clients upload to the homeserver.\nThese are shared by all device and are evaluated per event by the homeserver (and\nalso by clients). <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.6\/client-server-api\/#predefined-rules\">Default push rules<\/a> are defined in the Matrix spec. Push rules\npower the unread (and highlight) counts for each room, push notifications, and the\nnotifications <span class=\"caps\">API<\/span>.<\/p>\n<p>Each rule defines <strong>conditions<\/strong> which must be met for the rule to match and\n<strong>actions<\/strong> to take if the rule\u00a0matches.<\/p>\n<p>Processing of push rules occur until a rule matches or all rules have been\u00a0evaluated.<\/p>\n<div class=\"section\" id=\"getting-notifications\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-2\">Getting\u00a0notifications<\/a><\/h3>\n<p>As some background, clients receive notifications in one of two ways, via polling\n<tt class=\"docutils literal\">\/sync<\/tt> and\/or via push\u00a0notifications.<\/p>\n<p>Web-based clients often receive events via\u00a0polling:<\/p>\n<blockquote class=\"text-center\">\n<a class=\"reference external image-reference\" href=\"https:\/\/patrick.cloke.us\/images\/matrix-push-rules-and-notifications\/web-push-flow.png\"><img alt=\"Notification flow for web applications.\" src=\"\/thumbnails\/matrix-push-rules-and-notifications\/web-push-flow_medium.png\"\/><\/a>\n<\/blockquote>\n<p>The <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.6\/client-server-api\/#_matrixclientv3sync_unread-notification-counts\">sync response<\/a> (both initial and incremental) include the count of unread\nnotifications and unread highlight notifications per\u00a0room.<\/p>\n<p>Mobile applications often <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.6\/push-gateway-api\/#overview\">receive events via push<\/a> <a class=\"footnote-reference\" href=\"#footnote-3\" id=\"footnote-reference-3\">[3]<\/a>:<\/p>\n<blockquote class=\"text-center\">\n<a class=\"reference external image-reference\" href=\"https:\/\/patrick.cloke.us\/images\/matrix-push-rules-and-notifications\/mobile-push-flow.png\"><img alt=\"Notification flow for mobile applications.\" src=\"\/thumbnails\/matrix-push-rules-and-notifications\/mobile-push-flow_medium.png\"\/><\/a>\n<\/blockquote>\n<p>Push notifications include the event information (or just the event <span class=\"caps\">ID<\/span>) and\nwhether the event was a highlight notification. (The event being pushed implies\nit increased the notification\u00a0count.)<\/p>\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p class=\"last\">The deployment of the push gateway must be paired with the application (the\npush keys must be paired). I.e. if you make your own application (or even\nyour own build of Element iOS \/ Android) you cannot re-use the deployment at\nmatrix.org and must have your own\u00a0deployment.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"getting-events-which-generated-notifications\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-3\">Getting events which generated\u00a0notifications<\/a><\/h3>\n<p>There\u2019s <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.6\/client-server-api\/#listing-notifications\">an <span class=\"caps\">API<\/span> to retrieve a list of events<\/a> which the user has been notified\nabout. This powers the \u201cnotification panel\u201d on Element Web and is meant to help\nusers catch-up on missed\u00a0notifications.<\/p>\n<p>It is fairly underspecified and the Synapse implementation has\u00a0limitations:<\/p>\n<ul class=\"simple\">\n<li>Highlight notifications are only kept for 30\u00a0days<\/li>\n<li>Non-highlight notifications are only kept for 72\u00a0hours<\/li>\n<\/ul>\n<p>Additionally it <a class=\"reference external\" href=\"https:\/\/github.com\/vector-im\/element-web\/issues\/6874\">works poorly for encrypted rooms<\/a>.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"push-rules-background\">\n<h2><a class=\"toc-backref\" href=\"#toc-entry-4\">Push rules\u00a0background<\/a><\/h2>\n<div class=\"section\" id=\"getting-the-configured-push-rules\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-5\">Getting the configured push\u00a0rules?<\/a><\/h3>\n<p>There\u2019s a <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.6\/client-server-api\/#push-rules-api\">set of APIs to fetch or modify push rules<\/a>, they let\u00a0you:<\/p>\n<ul class=\"simple\">\n<li>Fetch all push\u00a0rules<\/li>\n<li>Create or delete an individual push\u00a0rule<\/li>\n<li>Fetch or update an individual push rule\u2019s\u00a0actions<\/li>\n<li>Fetch or enable\/disable an individual push\u00a0rule<\/li>\n<\/ul>\n<p>An initial sync includes all of a user\u2019s push rules under the user\u2019s account\u00a0data.<\/p>\n<p>Any changes to push rules are included in incremental syncs. <em>Except<\/em> for newly\nadded rules to the specification (this is likely a homeserver\u00a0bug).<\/p>\n<p>Note that you cannot use <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.6\/client-server-api\/#client-config\">the account data APIs<\/a> to configure push rules. <a class=\"footnote-reference\" href=\"#footnote-4\" id=\"footnote-reference-4\">[4]<\/a><\/p>\n<\/div>\n<div class=\"section\" id=\"what-makes-up-a-push-rule\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-6\">What makes up a push\u00a0rule?<\/a><\/h3>\n<p>A push rule is a <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.6\/client-server-api\/#_matrixclientv3pushrules_pushrule\"><span class=\"caps\">JSON<\/span> object with the following fields<\/a>:<\/p>\n<ul class=\"simple\">\n<li><tt class=\"docutils literal\">rule_id<\/tt>: Unique (per-user) <span class=\"caps\">ID<\/span> for the rule.<ul>\n<li>The <tt class=\"docutils literal\">rule_id<\/tt> for default rules have a special form (they start with a\ndot: <tt class=\"docutils literal\">.<\/tt>).<\/li>\n<\/ul>\n<\/li>\n<li><tt class=\"docutils literal\">default<\/tt>: Whether the rule is part of the predefined set of\u00a0rules.<\/li>\n<li><tt class=\"docutils literal\">enabled<\/tt>: Whether the rule is\u00a0enabled.<\/li>\n<li><tt class=\"docutils literal\">conditions<\/tt>: an array of 0 or more conditions to\u00a0match.<\/li>\n<li><tt class=\"docutils literal\">actions<\/tt>: 0 or more actions to take if the rule\u00a0matches.<\/li>\n<\/ul>\n<p>All conditions must match for a push rule to match. If there are no conditions,\nthen the push rule always matches. Possible conditions\u00a0include:<\/p>\n<ul class=\"simple\">\n<li>Check event properties against patterns or exact values<ul>\n<li>Strings can be compared via globbing or exact\u00a0values.<\/li>\n<li>The globbing behavior changes if you\u2019re checking the <tt class=\"docutils literal\">body<\/tt> property or\u00a0not.<\/li>\n<\/ul>\n<\/li>\n<li>Check against the number of room members<ul>\n<li>Used to (incorrectly) check if a room is a direct\u00a0message.<\/li>\n<\/ul>\n<\/li>\n<li>Check if a user can <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.6\/client-server-api\/#mroompower_levels\">perform an action<\/a> via power rules<ul>\n<li>The only defined option is whether a user can send\u00a0@room.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Push rule actions define <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.6\/client-server-api\/#actions\">what to do once a push rule<\/a> matches an\u00a0event.<\/p>\n<ul class=\"simple\">\n<li><tt class=\"docutils literal\">notify<\/tt>: increment the notification count and send a push notification. Uses\n\u201ctweaks\u201d to optionally:<ul>\n<li>Play a\u00a0sound.<\/li>\n<li>Create a highlight notification, this causes the highlight count to be\nincremented (in addition to the notification\u00a0count).<\/li>\n<\/ul>\n<\/li>\n<li>Can be an empty list to do\u00a0nothing.<\/li>\n<\/ul>\n<p>There are other undefined or no-op actions (<tt class=\"docutils literal\">dont_notify<\/tt>, <tt class=\"docutils literal\">coalesce<\/tt>) which will be\nremoved in the next version of the spec. <a class=\"footnote-reference\" href=\"#footnote-5\" id=\"footnote-reference-5\">[5]<\/a><\/p>\n<\/div>\n<div class=\"section\" id=\"types-of-push-rules\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-7\">Types of push\u00a0rules<\/a><\/h3>\n<p>Push rules have a type associated with them, these are executed in\u00a0order:<\/p>\n<ul class=\"simple\">\n<li>Override: generic high priority\u00a0rules<\/li>\n<li>Content-specific: applies to messages which have a <tt class=\"docutils literal\">body<\/tt> that matches a <tt class=\"docutils literal\">pattern<\/tt><\/li>\n<li>Room-specific: applies to messages of a\u00a0room<\/li>\n<li>Sender-specific: applies to messages from a\u00a0sender<\/li>\n<li>Underride: generic low priority\u00a0rules<\/li>\n<\/ul>\n<p>The previously discussed shape of push rules is not the full story! There are\nspecial cases which do not accept conditions, but can be mapped to\u00a0them.<\/p>\n<ul class=\"simple\">\n<li>Content-specific: has a <tt class=\"docutils literal\">pattern<\/tt> field which maps to a pattern against the\n<tt class=\"docutils literal\">body<\/tt> property.<\/li>\n<li>Room-specific: the <tt class=\"docutils literal\">rule_id<\/tt> is re-used to match against the room <span class=\"caps\">ID<\/span>.<\/li>\n<li>Sender-specific: the <tt class=\"docutils literal\">rule_id<\/tt> is re-used to match against the event <tt class=\"docutils literal\">sender<\/tt>.<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"why-do-clients-care-doesnt-the-homeserver-do-this-all-for-me\">\n<h2><a class=\"toc-backref\" href=\"#toc-entry-8\">Why do clients care? Doesn\u2019t the homeserver do this all for\u00a0me?<\/a><\/h2>\n<p>Encryption ruins everything! Some of the push rules require the decrypted event\ncontent to be properly processed. The enable this, the default rules declare\n<strong>all encrypted events as notifications<\/strong>. Clients are expected to\n<strong>re-run push rules on the decrypted content<\/strong>. <a class=\"footnote-reference\" href=\"#footnote-6\" id=\"footnote-reference-6\">[6]<\/a><\/p>\n<p>This can result in one of the following: <a class=\"footnote-reference\" href=\"#footnote-7\" id=\"footnote-reference-7\">[7]<\/a><\/p>\n<ul class=\"simple\">\n<li>Increment the highlight count (the decrypted event results in a\u00a0highlight)<\/li>\n<li>No change (the decrypted event results in a\u00a0notification)<\/li>\n<li>Decrement notification counts (the decrypted event results in no\u00a0notification)<\/li>\n<\/ul>\n<p>Due to gappy syncs clients frequently can only make a best estimate of the true\nunread \/ highlight count of events in encrypted\u00a0rooms.<\/p>\n<div class=\"admonition warning\">\n<p class=\"first admonition-title\">Warning<\/p>\n<p class=\"last\">Element iOS \/ Android get encrypted events pushed to them, but do not properly\nimplement mentions <span class=\"amp\">&amp;<\/span>\u00a0keywords.<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"what-happens-by-default\">\n<h2><a class=\"toc-backref\" href=\"#toc-entry-9\">What happens by\u00a0default?<\/a><\/h2>\n<p>The <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.6\/client-server-api\/#predefined-rules\">default rules are in the Matrix spec<\/a> and\u00a0include:<\/p>\n<ul class=\"simple\">\n<li>Highlight:<ul>\n<li>Tombstones<\/li>\n<li>Room <span class=\"amp\">&amp;<\/span> user\u00a0mentions<\/li>\n<\/ul>\n<\/li>\n<li>Do nothing:<ul>\n<li>Notice\u00a0messages<\/li>\n<li>Other room member\u00a0events<\/li>\n<li>Server <span class=\"caps\">ACL<\/span>\u00a0updates<\/li>\n<\/ul>\n<\/li>\n<li>Notification:<ul>\n<li>Invites to\u00a0me<\/li>\n<li>Messages and encrypted events in\u00a0non-DMs<\/li>\n<\/ul>\n<\/li>\n<li>Notification with sound:<ul>\n<li>Incoming\u00a0calls<\/li>\n<li>Messages and encrypted events in\u00a0DMs<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Default rules can be disabled or have their actions modified on a per-user basis.\nSome of the above features are handled by multiple push\u00a0rules.<\/p>\n<div class=\"section\" id=\"other-standard-rules\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-10\">Other \u201cstandard\u201d\u00a0rules<\/a><\/h3>\n<p>Element creates custom push rules based on a known form. <a class=\"footnote-reference\" href=\"#footnote-8\" id=\"footnote-reference-8\">[8]<\/a><\/p>\n<ul class=\"simple\">\n<li>Keywords (implemented as a content-specific rule with a\u00a0pattern)<\/li>\n<li>Per-room overrides:<ul>\n<li>All messages (implemented as a room-specific rule with a notify\u00a0action)<\/li>\n<li>Mentions <span class=\"amp\">&amp;<\/span> keywords (implemented as a room-specific rule with no\u00a0actions)<\/li>\n<li>Mute (implemented as an override rule to match the room <span class=\"caps\">ID<\/span> with no\u00a0actions)<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Matrix also allows defining arbitrary rules (e.g. to change behavior for particular\nrooms, senders, message types,\u00a0etc.)<\/p>\n<\/div>\n<div class=\"section\" id=\"what-about-unread-rooms\">\n<h3><a class=\"toc-backref\" href=\"#toc-entry-11\">What about unread\u00a0rooms?<\/a><\/h3>\n<p>The unread (\u201cbold\u201d) rooms logic in Element Web is completely custom and outside\nof the Matrix\u00a0specification.<\/p>\n<ul class=\"simple\">\n<li>Will the <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-react-sdk\/blob\/d33e416fc75369d3fec1c1f27ef9d5b2ea0b3703\/src\/shouldHideEvent.ts#L58-L82\">event be shown<\/a>\u00a0?<\/li>\n<li>Is it <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-react-sdk\/blob\/d33e416fc75369d3fec1c1f27ef9d5b2ea0b3703\/src\/Unread.ts#L41-L48\">not an ignored event type<\/a>\u00a0?<\/li>\n<li>Is it <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-react-sdk\/blob\/d33e416fc75369d3fec1c1f27ef9d5b2ea0b3703\/src\/Unread.ts#L52\">not redacted<\/a>\u00a0?<\/li>\n<li>Does a <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-react-sdk\/blob\/d33e416fc75369d3fec1c1f27ef9d5b2ea0b3703\/src\/Unread.ts#L53\">renderer exist for the event<\/a>\u00a0?<\/li>\n<\/ul>\n<p>Note that if you enable hidden events (or tweak other options to show events)\nthen the behavior\u00a0changes!<\/p>\n<\/div>\n<\/div>\n<div class=\"section\" id=\"putting-it-altogether\">\n<h2><a class=\"toc-backref\" href=\"#toc-entry-12\">Putting it\u00a0altogether\u2026<\/a><\/h2>\n<p>\u2026it gets complicated trying to figure out whether a message will generate a\nnotification or\u00a0not.<\/p>\n<blockquote class=\"text-center\">\n<a class=\"reference external image-reference\" href=\"https:\/\/patrick.cloke.us\/images\/matrix-push-rules-and-notifications\/default-push-rules.png\"><img alt=\"Flow chart of the default Matrix push rules when using Element.\" src=\"\/thumbnails\/matrix-push-rules-and-notifications\/default-push-rules_medium.png\"\/><\/a>\n<p>The default Matrix push rules (also showing the options available within\u00a0Element).<\/p>\n<\/blockquote>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-1\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-1\">[1]<\/a><\/td><td>Improving unintentional mentions (<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/3952\"><span class=\"caps\">MSC3952<\/span><\/a>) is the main feature we were\nworking on, but this was powered by <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/3758\"><span class=\"caps\">MSC3758<\/span><\/a> (from <a class=\"reference external\" href=\"https:\/\/www.beeper.com\/\">Beeper<\/a>),\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/3873\"><span class=\"caps\">MSC3873<\/span><\/a> (from a coworker), and <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/3966\"><span class=\"caps\">MSC3966<\/span><\/a>. <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/3980\"><span class=\"caps\">MSC3980<\/span><\/a> was also a\nfollow-up for consistency.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-2\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-2\">[2]<\/a><\/td><td><p class=\"first\">Notification count (the grey badge with count in Element Web) is the number\nof unread messages in a room. Highlight count (the red badge with count in\nElement Web) is the number of unread mentions in a\u00a0room.<\/p>\n<div class=\"admonition warning\">\n<p class=\"first admonition-title\">Warning<\/p>\n<p class=\"last\">The unread (\u201cbold\u201d) rooms feature in Element Web, which represents a room\nwith unread messages (but no notification count) is not powered by push\nrules (and is not\u00a0specced).<\/p>\n<\/div>\n<p class=\"last\">See the <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-react-sdk\/blob\/develop\/docs\/room-list-store.md#list-ordering-algorithm-importance\">Element Web docs on the room list<\/a>.<\/p>\n<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-3\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-3\">[3]<\/a><\/td><td>This post generally defines \u201cpush notifications\u201d as a notification which\nis sent via a push provider to an application. Push providers include Apple,\nGoogle, Microsoft, or Mozilla.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-4\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-4\">[4]<\/a><\/td><td><a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/4010\"><span class=\"caps\">MSC4010<\/span><\/a> aims to make this explicit.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-5\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-5\">[5]<\/a><\/td><td>See <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/3987\"><span class=\"caps\">MSC3987<\/span><\/a>.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-6\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-6\">[6]<\/a><\/td><td>It was not clear how clients should handle encrypted events <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec\/pull\/1461\">until recently<\/a><\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-7\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-7\">[7]<\/a><\/td><td>Adapted from a <a class=\"reference external\" href=\"https:\/\/gist.github.com\/Half-Shot\/f9501916363894761a1659250aa25181\">Gist from Half-Shot<\/a>.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-8\" rules=\"none\">\n<colgroup><col class=\"label\"\/><col\/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-8\">[8]<\/a><\/td><td>These don\u2019t seem to be specced, I\u2019m unsure if other clients create similar\nrules or understand these rules.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","pubDate":"Mon, 08 May 2023 14:56:00 -0400","guid":"tag:patrick.cloke.us,2023-05-08:\/posts\/2023\/05\/08\/matrix-push-rules-notifications\/","category":["articles","matrix","notes"]},{"title":"Python str Collection\u00a0Gotchas","link":"https:\/\/patrick.cloke.us\/posts\/2023\/02\/24\/python-str-collection-gotchas\/","description":"<p>We have been slowly adding Python type hints <a class=\"footnote-reference\" href=\"#footnote-1\" id=\"footnote-reference-1\">[1]<\/a> to <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\">Synapse<\/a> and have made\ngreat progress (see <a class=\"reference external\" href=\"https:\/\/matrix.org\/blog\/2021\/12\/03\/type-coverage-for-sydent-motivation\">some of our motivation<\/a>). Through this process we have\nlearned a lot about Python and type hints. One bit that was unexpected is that\nmany of the <a class=\"reference external\" href=\"https:\/\/docs.python.org\/3\/library\/collections.abc.html#collections-abstract-base-classes\">abstract base classes<\/a> representing groups of <tt class=\"docutils literal\">str<\/tt> instances\nalso match an individual <tt class=\"docutils literal\">str<\/tt> instance. This has resulted in more than one\n<em>real<\/em> bug for us <a class=\"footnote-reference\" href=\"#footnote-2\" id=\"footnote-reference-2\">[2]<\/a>: a function which has parameter of type <tt class=\"docutils literal\">Collection[str]<\/tt>\nwas called with a <tt class=\"docutils literal\">str<\/tt>, for example <a class=\"footnote-reference\" href=\"#footnote-3\" id=\"footnote-reference-3\">[3]<\/a>:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"k\">def<\/span> <span class=\"nf\">send<\/span><span class=\"p\">(<\/span><span class=\"n\">event<\/span><span class=\"p\">:<\/span> <span class=\"n\">Event<\/span><span class=\"p\">,<\/span> <span class=\"n\">destinations<\/span><span class=\"p\">:<\/span> <span class=\"n\">Collection<\/span><span class=\"p\">[<\/span><span class=\"nb\">str<\/span><span class=\"p\">])<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"kc\">None<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"sd\">&quot;&quot;&quot;Send an event to a set of destinations.&quot;&quot;&quot;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"n\">destination<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">destinations<\/span><span class=\"p\">:<\/span>\n        <span class=\"c1\"># Do some HTTP.<\/span>\n        <span class=\"o\">...<\/span>\n\n<span class=\"k\">def<\/span> <span class=\"nf\">create_event<\/span><span class=\"p\">(<\/span><span class=\"n\">sender<\/span><span class=\"p\">:<\/span> <span class=\"nb\">str<\/span><span class=\"p\">,<\/span> <span class=\"n\">content<\/span><span class=\"p\">:<\/span> <span class=\"nb\">str<\/span><span class=\"p\">,<\/span> <span class=\"n\">room<\/span><span class=\"p\">:<\/span> <span class=\"n\">Room<\/span><span class=\"p\">)<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"kc\">None<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"sd\">&quot;&quot;&quot;Create &amp; send an event.&quot;&quot;&quot;<\/span>\n    <span class=\"n\">event<\/span> <span class=\"o\">=<\/span> <span class=\"n\">Event<\/span><span class=\"p\">(<\/span><span class=\"n\">sender<\/span><span class=\"p\">,<\/span> <span class=\"n\">content<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">send<\/span><span class=\"p\">(<\/span><span class=\"n\">event<\/span><span class=\"p\">,<\/span> <span class=\"s2\">&quot;matrix.org&quot;<\/span><span class=\"p\">)<\/span>\n<\/pre><\/div>\n<p>The correct version should call <tt class=\"docutils literal\">send<\/tt> with a <tt class=\"docutils literal\">list<\/tt> of destinations instead\nof a single one. The &#8220;s&#8221; at the end of &#8220;destinations&#8221; takes on quite a bit of\nimportance! See the&nbsp;fix:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"gu\">@@ -7,5 +7,5 @@<\/span>\n<span class=\"w\"> <\/span>  def create_event(sender: str, content: str, room: Room) -&gt; None:\n<span class=\"w\"> <\/span>      &quot;&quot;&quot;Create &amp; send an event.&quot;&quot;&quot;\n<span class=\"w\"> <\/span>      event = Event(sender, content)\n<span class=\"gd\">-      send(event, &quot;matrix.org&quot;)<\/span>\n<span class=\"gi\">+      send(event, [&quot;matrix.org&quot;])<\/span>\n<\/pre><\/div>\n<p>A possible solution is redefine the <tt class=\"docutils literal\">destinations<\/tt> parameter as a <tt class=\"docutils literal\">List[str]<\/tt>,\nbut this forces the caller to convert a <tt class=\"docutils literal\">set<\/tt> or <tt class=\"docutils literal\">tuple<\/tt> to a <tt class=\"docutils literal\">list<\/tt>\n(meaning iterating, allocate memory, etc.) or maybe using a <tt class=\"docutils literal\"><span class=\"pre\">cast(...)<\/span><\/tt> (and\nthus losing some of the protections from type hints). As a team we have a desire\nto keep the type hints of function parameters as broad as&nbsp;possible.<\/p>\n<p>Put another way, <tt class=\"docutils literal\">str<\/tt> is an instance of <tt class=\"docutils literal\">Collection[str]<\/tt>, <tt class=\"docutils literal\">Container[str]<\/tt>,\n<tt class=\"docutils literal\">Iterable[str]<\/tt>, and <tt class=\"docutils literal\">Sequence[str]<\/tt>. <a class=\"footnote-reference\" href=\"#footnote-4\" id=\"footnote-reference-4\">[4]<\/a> <a class=\"footnote-reference\" href=\"#footnote-5\" id=\"footnote-reference-5\">[5]<\/a><\/p>\n<p>Since our type hints are only used internally we do not need to worry too much\nabout accepting exotic types and eventually came up with <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/blob\/335f52d595c2c32e4b512b97e2851bc98b819ca7\/synapse\/types\/__init__.py#L84-L86\"><tt class=\"docutils literal\">StrCollection<\/tt><\/a>:<\/p>\n<div class=\"highlight\"><pre><span><\/span><span class=\"c1\"># Collection[str] that does not include str itself; str being a Sequence[str]<\/span>\n<span class=\"c1\"># is very misleading and results in bugs.<\/span>\n<span class=\"n\">StrCollection<\/span> <span class=\"o\">=<\/span> <span class=\"n\">Union<\/span><span class=\"p\">[<\/span><span class=\"n\">Tuple<\/span><span class=\"p\">[<\/span><span class=\"nb\">str<\/span><span class=\"p\">,<\/span> <span class=\"o\">...<\/span><span class=\"p\">],<\/span> <span class=\"n\">List<\/span><span class=\"p\">[<\/span><span class=\"nb\">str<\/span><span class=\"p\">],<\/span> <span class=\"n\">AbstractSet<\/span><span class=\"p\">[<\/span><span class=\"nb\">str<\/span><span class=\"p\">]]<\/span>\n<\/pre><\/div>\n<p>This covers lists, tuples, sets, and frozen sets of <tt class=\"docutils literal\">str<\/tt>, the one case it does\nnot seem to cover well is if you are using a dictionary as an <tt class=\"docutils literal\">Iterable[str]<\/tt>,\nthe easy workaround there is to call <tt class=\"docutils literal\">keys()<\/tt> on it to explicitly convert to a\n<a class=\"reference external\" href=\"https:\/\/docs.python.org\/3\/library\/collections.abc.html#collections.abc.KeysView\"><tt class=\"docutils literal\">KeysView<\/tt><\/a>, which inherits from <tt class=\"docutils literal\">Set<\/tt>.<\/p>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-1\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-1\">[1]<\/a><\/td><td>Looking at the <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/commits\/develop\/mypy.ini\">commits<\/a> to <tt class=\"docutils literal\">mypy.ini<\/tt> is probably the best way to see progress.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-2\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-2\">[2]<\/a><\/td><td><a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/issues\/14809\">matrix-org\/synapse#14809<\/a> is our tracking issue for fixing this, although\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\/pull\/14880\/files#diff-0b449f6f95672437cf04f0b5512572b4a6a729d2759c438b7c206ea249619885R1161\">matrix-org\/synapse#14880<\/a> shows a concrete bug fix which occurred.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-3\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-3\">[3]<\/a><\/td><td>This is heavily simplified, but hopefully illustrates the bug!<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-4\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-4\">[4]<\/a><\/td><td>And <tt class=\"docutils literal\">Reversible[str]<\/tt>, but I don&#8217;t think I have ever seen that used and\nI think it less likely to introduce a bug.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-5\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-5\">[5]<\/a><\/td><td><tt class=\"docutils literal\">bytes<\/tt> doesn&#8217;t have quite the same issue, but it might be surprising\nthat <tt class=\"docutils literal\">bytes<\/tt> is an instance of <tt class=\"docutils literal\">Collection[int]<\/tt>, <tt class=\"docutils literal\">Container[int]<\/tt>,\n<tt class=\"docutils literal\">Iterable[int]<\/tt>, and <tt class=\"docutils literal\">Sequence[int]<\/tt>. I find this less likely to\nintroduce a bug.<\/td><\/tr>\n<\/tbody>\n<\/table>\n","pubDate":"Fri, 24 Feb 2023 19:42:00 -0500","guid":"tag:patrick.cloke.us,2023-02-24:\/posts\/2023\/02\/24\/python-str-collection-gotchas\/","category":["articles","python"]},{"title":"Researching for a Matrix Spec\u00a0Change","link":"https:\/\/patrick.cloke.us\/posts\/2023\/01\/12\/researching-for-a-matrix-spec-change\/","description":"<p>The <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/\">Matrix protocol<\/a> is modified via <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/proposals\/\">Matrix Spec Changes<\/a> (frequently abbreviated\nto &#8220;MSCs&#8221;). These are short documents describing any technical changes and why they\nare worth making (see <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pull\/2457\">an example<\/a>). I&#8217;ve <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/pulls?q=is%3Apr+author%3Aclokep+label%3Aproposal\">written a bunch<\/a> and wanted to\ndocument my research process. <a class=\"footnote-reference\" href=\"#footnote-1\" id=\"footnote-reference-1\">[1]<\/a><\/p>\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note<\/p>\n<p class=\"last\">I treat my research as a <em>living document<\/em>, not an <em>artifact<\/em>. Thus, I don&#8217;t worry\nmuch about the format. The important part is to start writing everything down\nto have a single source of truth that can be shared with&nbsp;others.<\/p>\n<\/div>\n<p>First, I write out a <strong>problem statement<\/strong>: what am I trying to solve? (This step\nmight seem obvious, but it is useful to frame the technical changes in why\nthey matter. Many proposals seem to skip this step.) Most of my work tends to be\nfrom the point of view of an end-user, but some changes are purely technical. Regardless,\nthere is benefit from a shared written context of the issue to be&nbsp;solved.<\/p>\n<p>From the above and prior knowledge, I list any <strong>open questions<\/strong> (which I update\nthrough this process). I&#8217;ll augment the questions with answers I find in my research,\nwrite new ones about things I don&#8217;t understand, or remove them as they become&nbsp;irrelevant.<\/p>\n<p>Next, I begin collecting any previous work done in this area,&nbsp;including:<\/p>\n<ul>\n<li><p class=\"first\">What is the <strong>current specification<\/strong> related to this? I usually pull out blurbs\n(with links back to the source) from <a class=\"reference external\" href=\"https:\/\/spec.matrix.org\/v1.5\/client-server-api\/\">the latest specification<\/a>.<\/p>\n<\/li>\n<li><p class=\"first\">Are there any <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec\/issues\"><strong>related known issues<\/strong><\/a>? It is also worth checking the issue\ntrackers of projects: I start with the <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/synapse\">Synapse<\/a>, <a class=\"reference external\" href=\"https:\/\/github.com\/vector-im\/element-meta\">Element Meta<\/a>, and\n<a class=\"reference external\" href=\"https:\/\/github.com\/vector-im\/element-web\">Element Web<\/a>&nbsp;repositories.<\/p>\n<\/li>\n<li><p class=\"first\">Are there <strong>related outstanding MSCs<\/strong> or <strong>previous research<\/strong>? I search the\n<a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/\">matrix-spec-proposals<\/a> repository for keywords, open anything that looks\nvaguely related and then crawl those for mentions of other MSCs. I&#8217;ll document\nthe related ones with links and a brief description of the proposed&nbsp;change.<\/p>\n<p>I include both proposed and closed MSCs to check for previously rejected&nbsp;ideas.<\/p>\n<\/li>\n<li><p class=\"first\">Are others interested in this? Have others had conversation about it? I roughly\nfollow the <a class=\"reference external\" href=\"https:\/\/matrix.to\/#\/#matrix-spec:matrix.org\">#matrix-spec<\/a> room or search for rooms that might be interested in\nthe topic. I would recommend joining the <a class=\"reference external\" href=\"https:\/\/matrix.to\/#\/#matrix-spec:matrix.org\">#matrix-spec<\/a> room for brainstorming\nand&nbsp;searching.<\/p>\n<p>This step can help uncover any missed known issues and MSCs. I will also ask\nothers with a longer history in the project if I am missing&nbsp;anything.<\/p>\n<\/li>\n<li><p class=\"first\">A brief <strong>competitive analysis<\/strong> is performed. Information can be gleaned from\ntechnical blog posts and <span class=\"caps\">API<\/span> documentation. I consider not just competing\n<em>products<\/em>, but also investigate if others have solved similar <em>technical<\/em>\nproblems. Other protocols are worth checking (e.g. <span class=\"caps\">IRC<\/span>, <span class=\"caps\">XMPP<\/span>, <span class=\"caps\">IMAP<\/span>).<\/p>\n<\/li>\n<\/ul>\n<p>You can see an example of my research on <a class=\"reference external\" href=\"https:\/\/patrick.cloke.us\/posts\/2023\/01\/05\/matrix-read-receipts-and-notifications\/\">Matrix read receipts <span class=\"amp\">&amp;<\/span> notifications<\/a>.<\/p>\n<p>Once I have compiled the above information, I jump into the <strong>current implementation<\/strong>\nto ensure it roughly matches the specification. <a class=\"footnote-reference\" href=\"#footnote-2\" id=\"footnote-reference-2\">[2]<\/a> I start considering what\nprotocol changes would solve the problem and are reasonable to implement. I find\nit useful to write down all of my ideas, not just the one I think is best. <a class=\"footnote-reference\" href=\"#footnote-3\" id=\"footnote-reference-3\">[3]<\/a><\/p>\n<p>At this point I&nbsp;have:<\/p>\n<ul class=\"simple\">\n<li>A problem&nbsp;statement<\/li>\n<li>A bunch of background about the current protocol, other proposed solutions,&nbsp;etc.<\/li>\n<li>A list of open&nbsp;questions<\/li>\n<li>Rough ideas for proposed&nbsp;solutions<\/li>\n<\/ul>\n<p>The next step is to iterate with my colleagues: answer any open questions, check\nthat our product goals will be met, and seek agreement that we are designing a\nbuildable solution. <a class=\"footnote-reference\" href=\"#footnote-4\" id=\"footnote-reference-4\">[4]<\/a><\/p>\n<p><em>Finally<\/em>, I take the above and formalize it in into one or more Matrix Spec Changes.\nAt this point I&#8217;ll think about error conditions \/ responses, backwards compatibility,\nsecurity concerns, and any other parts of the full <span class=\"caps\">MSC<\/span>. Once it is documented, I\nmake a pull request with the proposal and self-review it for loose ends and clarity.\nI leave comments for any parts I am unsure about or want to open discussion&nbsp;on.<\/p>\n<p>Then I ask me colleagues to read through it and wait for feedback from both them and\nany interested community members. It can be useful to be in the <a class=\"reference external\" href=\"https:\/\/matrix.to\/#\/#matrix-spec:matrix.org\">#matrix-spec<\/a> room\nas folks might want to discuss the&nbsp;proposal.<\/p>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-1\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-1\">[1]<\/a><\/td><td>There&#8217;s a useful <a class=\"reference external\" href=\"https:\/\/github.com\/matrix-org\/matrix-spec-proposals\/blob\/9b3f01b0193caa1e1c563cfc861ab021bcddcb2c\/proposals\/0000-proposal-template.md\">proposal template<\/a> that I eventually use, but I do much\nof this process before constraining myself by that.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-2\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-2\">[2]<\/a><\/td><td>This consists of looking through code as well as just trying it out by\nmanually making <span class=\"caps\">API<\/span> calls or understanding how APIs power product features.<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-3\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-3\">[3]<\/a><\/td><td>Part of the <span class=\"caps\">MSC<\/span> proposal is documenting alternatives (and why you didn&#8217;t\nchoose one of those). It is useful to brainstorm early before you&#8217;re set\nin a decision!<\/td><\/tr>\n<\/tbody>\n<\/table>\n<table class=\"docutils footnote\" frame=\"void\" id=\"footnote-4\" rules=\"none\">\n<colgroup><col class=\"label\" \/><col \/><\/colgroup>\n<tbody valign=\"top\">\n<tr><td class=\"label\"><a class=\"fn-backref\" href=\"#footnote-reference-4\">[4]<\/a><\/td><td>I usually do work with Matrix homeservers and am not as experienced with\nclients. It is useful to bounce ideas off a client developer to understand\ntheir needs.<\/td><\/tr>\n<\/tbody>\n<\/table>\n","pubDate":"Thu, 12 Jan 2023 15:24:00 -0500","guid":"tag:patrick.cloke.us,2023-01-12:\/posts\/2023\/01\/12\/researching-for-a-matrix-spec-change\/","category":["articles","matrix"]}]}}