{ "type": "entry", "published": "2018-11-15T20:41:02+0000", "url": "https://seblog.nl/2018/11/15/1/enhancing-the-micropub-experience", "category": [ "indieweb", "micropub" ], "syndication": [ "https://news.indieweb.org/en/seblog.nl/2018/11/15/1/enhancing-the-micropub-experience" ], "name": "Enhancing the Micropub experience with services", "content": { "text": "At IndieWebCamp Berlin this year, at the session about Workflow, we came up with an idea, how to enhance your blogposts with an external service using Micropub. I\u2019ve thought of a few variants, and in spirit of the IndieWeb I should first build them and then show it, but I haven\u2019t got around it yet.\nSo y\u2019all will have to do with just a description. I might implement it at some point, if I have a real use case for it. I don\u2019t actually want weather on my posts. \nBut let\u2019s start at an idea I first had at IndieWebCamp N\u00fcrnberg.\nThe Syndication Button Hack\nMicropub is an open API standard that allows clients to post to servers. In the spec, there is a mechanism for clients to show buttons for syndication targets. The client asks the server what targets there are, and the server responds with a list of names and UIDs. The client then shows the names on buttons (or near checkboxes) and if the user selects one, the UID is set as the mp-syndicate-to field of the post. The server is then responsible for syndicating the post to, say, Twitter or Facebook.\nThis mechanism is widely supported among clients. And since the client does not have to do any work actually related to the syndication, it can also be used for other things.\nImagine the server implementing private posts. The support for private posts in Micropub clients is not really existing at the moment of writing. But we can get a button to toggle the state of the post created, quite easily:\nGET /micropub?q=syndicate-to\nAuthorization: Bearer xxxxxxxxx\nAccept: application/json\n\nHTTP/1.1 200 OK\nContent-type: application/json\n\n{\n \"syndicate-to\": [\n {\n \"uid\": \"private-post\",\n \"name\": \"Private post\"\n }\n ]\n}\nSince it\u2019s up to the server to syndicate to private-post, it can decide not to syndicate it, but to mark it private. There are a number of possibilities with this: toggle audiences, mark the post as draft. All these things could have their own queries at some point, but until then, this will work in almost all the clients.\nAlso notice the Bearer token. The server can know which client is asking, so it could show a different set of buttons, depending on the client. Quill supports draft posts? Don\u2019t show that button in Quill.\nEnter the Weather Service\nBack to the idea of Berlin, which takes this one step further. If we have the Syndication Button Hack in place, we can also hook up external services to enhance our blog posts.\nSay I display a location with every entry I post. I could have a button that says: \u2018Weather Service\u2019. Activating that button would instruct my server to ping the Weather Service about the existance of this new post. This could be done by WebSub or some other mechanism.\nBack when I signed up for the Weather Service, I gave it access to my Micropub endpoint as well. The Weather Service waits for new posts to arrive, reads their location, fetches the weather for that location, and sends a Micropub update request.\nThe only new part this requires, is the button and the ping to the Weather Service. All the other parts exist in clients and servers. Ah, and someone will need to build that Weather Service.\nExternal services in general\nThe nice thing about this model, is that the heavy lifting is on neither the Micropub client nor the server. It\u2019s on the external service. And it\u2019s not that heavy of a lifting, because the external service does only one thing and does one thing well. It can give superpowers to both Wordpress blogs and static generated sites.\nThe external service could provide information about the weather, but think of Aaron\u2019s Overland and Compass: it could also provide the location of the post given a point in time. There might be more. Expanding venue info?\nOne thing to watch out for, is concurrent processing of these Micropub requests. This might not be a problem for you, but I store my posts as flat files. If two services send an update request for the same post, one might start, and the other might overwrite the first one. (I really need to check how my blog handles this case.)\nWhen you are using a database like MySQL, you should be safe for this kind of stuff, but it still depends on the implementation of your Micropub endpoint.\nOther ways of doing it\nPeter did not like this first approach, because his post would have multiple visible states (first a few seconds without weather, then with it).\nAnother appreach would be a sort of Russian doll Micropub request, where you sign in to an external service which signs in to your Micropub endpoint. This would mean that quill.p3k.io posts to weather.example/micropub which intercepts the request, and sends the same request with weather info added to seblog.nl/micropub. \nI don\u2019t like that approach either, because now I have to trust the Weather Service with my tokens. In the first approach, every service gets their own scoped token, which is safer.\nSince the server knows how many services it has asked to enhance the post, it could also keep it in draft until the last update request comes in. This would require more work on the server\u2019s side of things, and there has to be a timeout on it, but it could be a way to mitigate Peter\u2019s problem.\nAs always: feel free to steal or improve, but please let me know.", "html": "<p>At IndieWebCamp Berlin this year, at the session about Workflow, we came up with an idea, how to enhance your blogposts with an external service using Micropub. I\u2019ve thought of a few variants, and in spirit of the IndieWeb I should first build them and then show it, but I haven\u2019t got around it yet.</p>\n<p>So y\u2019all will have to do with just a description. I might implement it at some point, if I have a real use case for it. I don\u2019t actually want weather on my posts. </p>\n<p>But let\u2019s start at an idea I first had at IndieWebCamp N\u00fcrnberg.</p>\n<h2>The Syndication Button Hack</h2>\n<p>Micropub is an open API standard that allows clients to post to servers. In <a href=\"https://www.w3.org/TR/micropub/#syndication-targets\">the spec</a>, there is a mechanism for clients to show buttons for syndication targets. The client asks the server what targets there are, and the server responds with a list of names and UIDs. The client then shows the names on buttons (or near checkboxes) and if the user selects one, the UID is set as the <code>mp-syndicate-to</code> field of the post. The server is then responsible for syndicating the post to, say, Twitter or Facebook.</p>\n<p>This mechanism is widely supported among clients. And since the client does not have to do any work actually related to the syndication, it can also be used for other things.</p>\n<p>Imagine the server implementing private posts. The support for private posts in Micropub clients is not really existing at the moment of writing. But we can get a button to toggle the state of the post created, quite easily:</p>\n<pre><code>GET /micropub?q=syndicate-to\nAuthorization: Bearer xxxxxxxxx\nAccept: application/json\n\nHTTP/1.1 200 OK\nContent-type: application/json\n\n{\n \"syndicate-to\": [\n {\n \"uid\": \"private-post\",\n \"name\": \"Private post\"\n }\n ]\n}</code></pre>\n<p>Since it\u2019s up to the server to syndicate to <code>private-post</code>, it can decide not to syndicate it, but to mark it private. There are a number of possibilities with this: toggle audiences, mark the post as draft. All these things could have their own queries at some point, but until then, this will work in almost all the clients.</p>\n<p>Also notice the <code>Bearer</code> token. The server can know which client is asking, so it could show a different set of buttons, depending on the client. Quill supports draft posts? Don\u2019t show that button in Quill.</p>\n<h2>Enter the Weather Service</h2>\n<p>Back to the idea of Berlin, which takes this one step further. If we have the Syndication Button Hack in place, we can also hook up external services to enhance our blog posts.</p>\n<p>Say I display a location with every entry I post. I could have a button that says: \u2018Weather Service\u2019. Activating that button would instruct my server to ping the Weather Service about the existance of this new post. This could be done by WebSub or some other mechanism.</p>\n<p>Back when I signed up for the Weather Service, I gave it access to my Micropub endpoint as well. The Weather Service waits for new posts to arrive, reads their location, fetches the weather for that location, and sends a Micropub update request.</p>\n<p>The only new part this requires, is the button and the ping to the Weather Service. All the other parts exist in clients and servers. Ah, and someone will need to build that Weather Service.</p>\n<h2>External services in general</h2>\n<p>The nice thing about this model, is that the <a href=\"https://chat.indieweb.org/dev/2018-11-15#t1542309128941000\">heavy lifting</a> is on neither the Micropub client nor the server. It\u2019s on the external service. And it\u2019s not that heavy of a lifting, because the external service does only one thing and does one thing well. It can give superpowers to both Wordpress blogs and static generated sites.</p>\n<p>The external service could provide information about the weather, but think of <a href=\"https://aaronpk.com\">Aaron</a>\u2019s <a href=\"https://overland.p3k.app/\">Overland</a> and <a href=\"https://github.com/aaronpk/Compass\">Compass</a>: it could also provide the location of the post given a point in time. There might be more. Expanding venue info?</p>\n<p>One thing to watch out for, is concurrent processing of these Micropub requests. This might not be a problem for you, but I store my posts as flat files. If two services send an update request for the same post, one might start, and the other might overwrite the first one. (I really need to check how my blog handles this case.)</p>\n<p>When you are using a database like MySQL, you should be safe for this kind of stuff, but it still depends on the implementation of your Micropub endpoint.</p>\n<h2>Other ways of doing it</h2>\n<p><a href=\"https://petermolnar.net\">Peter</a> did not like this first approach, because his post would have multiple visible states (first a few seconds without weather, then with it).</p>\n<p>Another appreach would be a sort of Russian doll Micropub request, where you sign in to an external service which signs in to your Micropub endpoint. This would mean that <code>quill.p3k.io</code> posts to <code>weather.example/micropub</code> which intercepts the request, and sends the same request with weather info added to <code>seblog.nl/micropub</code>. </p>\n<p>I don\u2019t like that approach either, because now I have to trust the Weather Service with my tokens. In the first approach, every service gets their own scoped token, which is safer.</p>\n<p>Since the server knows how many services it has asked to enhance the post, it could also keep it in draft until the last update request comes in. This would require more work on the server\u2019s side of things, and there has to be a timeout on it, but it could be a way to mitigate Peter\u2019s problem.</p>\n<p>As always: feel free to steal or improve, but please let me know.</p>" }, "author": { "type": "card", "name": "Sebastiaan Andeweg", "url": "https://seblog.nl/", "photo": "https://aperture-proxy.p3k.io/10e8aeca31d1cd146999fcacc07a8eb9ad47c813/68747470733a2f2f7365626c6f672e6e6c2f70686f746f2e6a7067" }, "post-type": "article", "_id": "1426067", "_source": "1366", "_is_read": true }
{ "type": "entry", "published": "2018-11-15T04:50:53-05:00", "url": "https://eddiehinkle.com/2018/11/15/10/note/", "category": [ "indieweb-goals" ], "syndication": [ "https://micro.blog/EddieHinkle", "https://news.indieweb.org/en", "https://twitter.com/eddiehinkle" ], "content": { "text": "I posted my #newwwyear 2019 #indieweb goals, it\u2019s quite ambitious. But if I complete all of them, my bonus goal is getting some movement on tracking my books and reading in my website. https://eddiehinkle.com/2018/11/15/5/article/", "html": "I posted my #newwwyear 2019 #indieweb goals, it\u2019s quite ambitious. But if I complete all of them, my bonus goal is getting some movement on tracking my books and reading in my website. <a href=\"https://eddiehinkle.com/2018/11/15/5/article/\">https://eddiehinkle.com/2018/11/15/5/article/</a>" }, "post-type": "note", "_id": "1421613", "_source": "226", "_is_read": true }
{ "type": "entry", "published": "2018-11-14 14:42-0800", "url": "http://tantek.com/2018/318/t1/2019-indieweb-commitment", "category": [ "indieweb" ], "content": { "text": "My 2019-001 #indieweb commitment: build on my @IndieWebCamp Berlin coding, improve archive pages, more nav & pagination, e.g. home stream to prev day(s).\nDetails: https://indieweb.org/Falcon#archive_pages\n\nWhat\u2019s your 2019-01-01 personal site commitment?\nhttps://indieweb.org/2019-01-01-commitments\n\nPreviously: \n* http://tantek.com/2018/308/t2/indiewebcamp-archive-navigation-day-archives\n* http://tantek.com/2016/330/b1/2017-01-01-indieweb-commitment-own-my-rsvps\n\nMore on archives, navigation, pagination:\n* https://indieweb.org/archive\n* https://indieweb.org/navigation\n* https://indieweb.org/archive_navigation\n* https://indieweb.org/pagination", "html": "My 2019-001 #<span class=\"p-category\">indieweb</span> commitment: build on my <a class=\"h-cassis-username\" href=\"https://twitter.com/IndieWebCamp\">@IndieWebCamp</a> Berlin coding, improve archive pages, more nav & pagination, e.g. home stream to prev day(s).<br />Details: <a href=\"https://indieweb.org/Falcon#archive_pages\">https://indieweb.org/Falcon#archive_pages</a><br /><br />What\u2019s your 2019-01-01 personal site commitment?<br /><a href=\"https://indieweb.org/2019-01-01-commitments\">https://indieweb.org/2019-01-01-commitments</a><br /><br />Previously: <br />* <a href=\"http://tantek.com/2018/308/t2/indiewebcamp-archive-navigation-day-archives\">http://tantek.com/2018/308/t2/indiewebcamp-archive-navigation-day-archives</a><br />* <a href=\"http://tantek.com/2016/330/b1/2017-01-01-indieweb-commitment-own-my-rsvps\">http://tantek.com/2016/330/b1/2017-01-01-indieweb-commitment-own-my-rsvps</a><br /><br />More on archives, navigation, pagination:<br />* <a href=\"https://indieweb.org/archive\">https://indieweb.org/archive</a><br />* <a href=\"https://indieweb.org/navigation\">https://indieweb.org/navigation</a><br />* <a href=\"https://indieweb.org/archive_navigation\">https://indieweb.org/archive_navigation</a><br />* <a href=\"https://indieweb.org/pagination\">https://indieweb.org/pagination</a>" }, "author": { "type": "card", "name": "Tantek \u00c7elik", "url": "http://tantek.com/", "photo": "https://aperture-media.p3k.io/tantek.com/acfddd7d8b2c8cf8aa163651432cc1ec7eb8ec2f881942dca963d305eeaaa6b8.jpg" }, "post-type": "note", "_id": "1420707", "_source": "1", "_is_read": true }
{ "type": "entry", "published": "2018-11-13T20:05:34-05:00", "url": "https://martymcgui.re/2018/11/13/200534/", "featured": "https://martymcgui.re/imageproxy/960,fit,s3XR-DoqI1vPJFsKdNSfC3Db47FnlmmtoOFLnv-RkeaI=/https://media.martymcgui.re/5c/8c/e6/23/fbc1f119c93947469e36dd7578cd5527b7a030102570f65162780c7a.jpg", "category": [ "HWC", "IndieWeb", "Baltimore", "wrap-up" ], "name": "HWC Baltimore 2018-11-13 Wrap-Up", "content": { "text": "Baltimore's first Homebrew Website Club of November met at the Digital Harbor Foundation Tech Center on November 13th.\nHere are some notes from the \"broadcast\" portion of the meetup:\n\n jonathanprozzi.net \u2014 Ran into an issue with his homepage. Some nginx rule was falling back to a default site. Got it fixed, though. Been doing lots of web dev for work, but none for his personal site.\n \n\n\n martymcgui.re \u2014 Been traveling a lot! Went to IWC Berlin and Accessibility Club and had a great time! Grateful to catch up with and meet new people in the IndieWeb community. Worked on month/year archive navigation for the new Hugo version of his site, which is very close to being feature-complete compared to his Jekyll site.\n \n\nOther discussion:\nIWC Berlin! Marty went and there were many good sessions.\n Changes in organizing HWCs (wiki stuff) and related events (travel sponsorships for IWCs, peripheral events that could bring people into the IndieWeb community).\n \nReplacing Flickr. Flickr dumping free content beyond the latest 1000 images. DHF relies heavily on Flickr albums, including using them for the homepage of their site. Will probably get a Flickr Pro account now, request an export to make sure their photos are safe.\n Web accessibility! So many fresh new JavaScript-based things refuse to use semantic markup, or otherwise break navigation or readability, making them unusable. Accessibility Club had a lot of good lessons. Hopefully the videos of the talks will go up soon!\n \n WordPress accessibility issues around the new Gutenberg editor, the problems with Automattic pushing it through without listening to community. Yikes.\n \n\nLeft-to-right: martymcgui.re, jonathanprozzi.netThanks to everyone who came out! We look forward to seeing you at our next meetup on Tuesday, November 27th at 7:30pm!", "html": "<p>Baltimore's first <a href=\"https://indieweb.org/events/2018-11-13-homebrew-website-club-baltimore\">Homebrew Website Club of November</a> met at the <a href=\"https://digitialharbor.org/\">Digital Harbor Foundation Tech Center</a> on November 13th.</p>\n<p>Here are some notes from the \"broadcast\" portion of the meetup:</p>\n<p>\n jonathanprozzi.net \u2014 Ran into an issue with his homepage. Some nginx rule was falling back to a default site. Got it fixed, though. Been doing lots of web dev for work, but none for his personal site.\n <br /></p>\n<p>\n martymcgui.re \u2014 Been traveling a lot! Went to <a href=\"https://indieweb.org/2018/Berlin\">IWC Berlin</a> and <a href=\"https://accessibility-club.org/\">Accessibility Club</a> and had a great time! Grateful to catch up with and meet new people in the IndieWeb community. Worked on month/year archive navigation for the new Hugo version of his site, which is very close to being feature-complete compared to his Jekyll site.\n <br /></p>\n<p>Other discussion:</p>\n<ul><li>IWC Berlin! Marty went and there were many good sessions.</li>\n <li>Changes in organizing HWCs (wiki stuff) and related events (travel sponsorships for IWCs, peripheral events that could bring people into the IndieWeb community).</li>\n <li>\n<a href=\"https://indieweb.org/2018/Berlin/photos\">Replacing Flickr</a>. Flickr dumping free content beyond the latest 1000 images. DHF relies heavily on Flickr albums, including using them for the homepage of their site. Will probably get a Flickr Pro account now, request an export to make sure their photos are safe.</li>\n <li>Web accessibility! So many fresh new JavaScript-based things refuse to use semantic markup, or otherwise break navigation or readability, making them unusable. Accessibility Club had a lot of good lessons. Hopefully the videos of the talks will go up soon!</li>\n <li>\n WordPress accessibility issues around the new Gutenberg editor, the problems with Automattic pushing it through without listening to community. Yikes.\n <br /></li>\n</ul><img class=\"u-featured\" src=\"https://martymcgui.re/imageproxy/960,fit,s3XR-DoqI1vPJFsKdNSfC3Db47FnlmmtoOFLnv-RkeaI=/https://media.martymcgui.re/5c/8c/e6/23/fbc1f119c93947469e36dd7578cd5527b7a030102570f65162780c7a.jpg\" alt=\"fbc1f119c93947469e36dd7578cd5527b7a030102570f65162780c7a.jpg\" />Left-to-right: martymcgui.re, jonathanprozzi.net<p>Thanks to everyone who came out! We look forward to seeing you at our next meetup on Tuesday, November 27th at 7:30pm!</p>" }, "author": { "type": "card", "name": "Marty McGuire", "url": false, "photo": "https://aperture-proxy.p3k.io/8275f85e3a389bd0ae69f209683436fc53d8bad9/68747470733a2f2f6d617274796d636775692e72652f696d616765732f6c6f676f2e6a7067" }, "post-type": "article", "_id": "1413346", "_source": "175", "_is_read": true }
Rolled out several fixes today for receiving Webmentions, parsing different kinds of usernames, and notifying Mastodon instances. Hopefully all these cross-site interactions will be a little bit more seamless now.
{ "type": "entry", "author": { "name": "Manton Reece", "url": "https://www.manton.org/", "photo": "https://aperture-proxy.p3k.io/907926e361383204bd1bc913c143c23e70ae69bb/68747470733a2f2f6d6963726f2e626c6f672f6d616e746f6e2f6176617461722e6a7067" }, "url": "https://www.manton.org/2018/11/13/rolled-out-several.html", "content": { "html": "<p>Rolled out several fixes today for receiving Webmentions, parsing different kinds of usernames, and notifying Mastodon instances. Hopefully all these cross-site interactions will be a little bit more seamless now.</p>", "text": "Rolled out several fixes today for receiving Webmentions, parsing different kinds of usernames, and notifying Mastodon instances. Hopefully all these cross-site interactions will be a little bit more seamless now." }, "published": "2018-11-13T17:16:31-06:00", "post-type": "note", "_id": "1412826", "_source": "12", "_is_read": true }
{ "type": "entry", "published": "2018-11-13T21:25:01+0000", "url": "https://seblog.nl/2018/11/13/3/pushapi-without-notifications", "category": [ "indieweb", "PushAPI", "notifications" ], "syndication": [ "https://news.indieweb.org/en/seblog.nl/2018/11/13/3/pushapi-without-notifications" ], "name": "PushAPI without Notifications", "content": { "text": "Recently I\u2019ve been to IndieWebCamp Berlin, where I spend the Hack\u00a0Day on abusing the PushAPI to update ServiceWorker caches.\nI would like to start with a small section on what and why, but while I was procrastinating on writing this blog post (the pressure is high), no one less than Jeremy Keith wrote a blog post about it. Since that\u2019s a perfect what and why, there are just two things to do for me here: demo and how.\nDemo\nI did a demo in Berlin, but the demo-gods where unforgiving. It did not work at all, but when I got back to my seat, it started working again. What happened? My Mac tried to be nice and turned off notifications while I was presenting.\nBut, as to make up for it, the new macOS Mojave shipped with a screen capturing tool. So here is a retry of the demo in under 5 minutes:\nThe how\nThis might not be the most interesting part of it, but it\u2019s nice to share work. It\u2019s not a full comprehensive guide on how to do this stuff, because that would just take way too long. See it as a quick guide behind the different API\u2019s involved. \nI googled it all anyway. You can google along.\nOh and if you want to skip ahead: there are some use cases at the end.\nShowing a local notification\nLike with any Javascript, you should check support before you ask something. There is a list of things to ask in the below code example: we want Notifications, it should not be denied, there has to be ServiceWorker support, and for the part later on, there should be a PushManager too. \nOnce we prompted the user and got permission, it\u2019s as simple as getting our ServiceWorker registration and ask it to show a notification. As you can see: this involves the ServiceWorker, but it does not involve any other servers.\nfunction activateNotifications() {\n Notification.requestPermission()\n .then(status => this.status = status)\n},\n\nfunction supportsNotifications() {\n return ('Notification' in window) && (this.status !== 'denied') &&\n ('serviceWorker' in navigator) && ('PushManager' in window)\n}\n\nasync function sendTestNotification() {\n const reg = await navigator.serviceWorker.getRegistration()\n\n return reg.showNotification('Hallo, test!')\n}\nNote: the demo code is using Vue, which I leave out in this blog post to simplify things. But that\u2019s where this points to: a collection of variables on the Vue instance.\nSubscribing for the PushAPI\nOnce the user clicks the button \u2018Subscribe\u2019, the following function gets triggered. In here, we again get the ServiceWorker registration, and then access the PushManager on it, which we tell to subscribe.\nasync function subscribe() {\n const reg = await navigator.serviceWorker.getRegistration()\n const sub = await reg.pushManager.subscribe({\n userVisibleOnly: true, // required for Chrome\n applicationServerKey: urlB64ToUint8Array(this.publicVapidKey)\n })\n\n this.notifications = true\n const key = sub.getKey('p256dh')\n const token = sub.getKey('auth')\n\n await this.$http.post('/subscriptions', {\n endpoint: sub.endpoint,\n key: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : null,\n token: token ? btoa(String.fromCharCode.apply(null, new Uint8Array(token))) : null\n })\n\n this.subscribed = sub !== null\n},\nSome browsers have their own way of doing authentication, but the most universal is with a Vapid key pair. The package I use for the backend came with a way of creating them. We give the public key to the PushManager, which will give us a Subscription object.\nIn the end, we send the Subscription\u2019s key, token and endpoint to the server via a POST request.\nNote: my HTTP library of choice is axios and the urlB64ToUint8Array() function can be found here\nStoring the Subscription\nFor the backend, I\u2019m using a Laravel package for WebPush, which allows me to save the endpoint with very minimal code:\npublic function update(Request $request)\n{\n $this->validate($request, ['endpoint' => 'required']);\n $request->user()->updatePushSubscription(\n $request->endpoint,\n $request->key,\n $request->token\n );\n return response()->json(null, 201);\n}\nAs you can see, it is using the user to associate the data with. (I fake the auth in the demo, which I do not recommend.) It ends up in a database, with four main columns: user_id, endpoint, public_key, auth_token.\nIn theory, you can go without users, but you will need to store the other parts. The token and key look like random strings, but the endpoint is an actual URL, on a subdomain of either Mozilla or Google, depending on the browser. (No support on Safari yet, mind you.)\nThese endpoints and tokens can expire, so you will need to keep an eye on the table.\nSending the notification\nI can be short about this part: I have no idea. The following code is all it takes to trigger it:\nNotification::send(\n User::all(), \n new NewBlogPostCreated($content, $notify)\n);\n... where $content is the content of the post, and $notify a boolean, telling my ServiceWorker whether or not to show a notification (we\u2019ll get to that).\nThe NewBlogPostCreated class extends Laravel\u2019s build-in Notification class and has these two methods:\npublic function via($notifiable)\n{\n return [WebPushChannel::class];\n}\n\npublic function toWebPush($notifiable, $notification)\n{\n return (new WebPushMessage)\n ->title($this->notify ? 'notify' : 'update-cache')\n ->body($this->content);\n}\nThere is a lot of magic behind the scenes here. I have no idea. In the end, they send a POST request to the endpoints of those users, after signing the right things with the right keys.\nReceiving the notification and then don\u2019t\nNext, we\u2019re back in Javascript-land, however, this is the ServiceWorker-province. The ServiceWorker, once installed, is a script, written in Javascript, but completely decoupled from any window. It lives in your browser and represents not one page, but your whole website. \nIt\u2019s quite hard to wrap your head around at first, but, I think the PushAPI makes it easier: there is no window involved with a push message, and there is no page involved with a push message. There is only your ServiceWorker, which acts for your whole website.\nThe ServiceWorker script itself consists of a series of callbacks, that are executed whenever things happen. In the case of a push message, the 'push' event is triggered:\n(function() {\n 'use strict';\n\n self.addEventListener('push', function (e) {\n const data = e.data.json()\n\n self.caches.open('manual')\n .then(cache => cache.put('hello', new Response(data.body)))\n\n if (data.title == 'notify') {\n e.waitUntil(\n self.registration.showNotification(\n 'New content!', \n {body: data.body}\n )\n );\n }\n });\n\n})();\nThat\u2019s all I need for receiving push notifications. I first retrieve the data from the message. Then I open the cache named \u2018manual\u2019 and I put the body of the message in that cache as the content of a URL (in this case \u2018offline.test/hello\u2019). It is made for pages, but I use it as a key-value store here.\nThen I check the title field, which I have abused for this purpose. If it is set to the magic string \u2018notify\u2019, I will trigger the notification. If it\u2019s something else I will do nothing.\nThis shows that I don\u2019t have to: I can leave the notification out, but I still get a ServiceWorker activation and I can do whatever I want with it.\nUse cases\nI think this can be used for creepy things (can I occasionally ping my ServiceWorkers and ask for data like \u2018how many windows are open?\u2019 and phone that home?), but I also think there are nice uses for this as well.\nAs Jeremy wrote: this can be used for magazines, podcasts and blogs to push new content to my phone, to read on a plane or in the subway when I\u2019m offline. I see a nice feature for a web-based IndieWeb Reader too: it can push me copies of posts it collected.\nI think the Reader is a nice place to use this. With great power comes great responsibility. Do I want to grand that great power to that weird magazine, that dubious podcast, that blog I visit once or twice a month? I might know you well, I might not. Do I trust you, pushing megabytes on my phone without me noticing?\nWeb apps like a Reader are easier to bond with. Plus: once I know my Reader supports reading offline, I might visit it in the subway. Will I remember the magazine? \nThe last bonus of the IndieWeb Reader specifically: it can send me posts from any magazine or podcast or blog, whether they support offline reading or not. But that\u2019s more specific to the Reader than it is to Push.\nI\u2019m also very curious to know how things will evolve if ServiceWorkers get even more superpowers. How well will those pair with a free ServiceWorker activation? Lot\u2019s of exploring to do!", "html": "<p>Recently I\u2019ve been to IndieWebCamp Berlin, where I spend the Hack\u00a0Day on abusing the PushAPI to update ServiceWorker caches.</p>\n<p>I would like to start with a small section on what and why, but while I was procrastinating on writing this blog post (the pressure is high), no one less than Jeremy Keith wrote <a href=\"https://adactio.com/journal/14511\">a blog post about it</a>. Since that\u2019s a perfect what and why, there are just two things to do for me here: demo and how.</p>\n<h2>Demo</h2>\n<p>I did a demo in Berlin, but the demo-gods where unforgiving. It did not work at all, but when I got back to my seat, it started working again. What happened? My Mac tried to be nice and turned off notifications while I was presenting.</p>\n<p>But, as to make up for it, the new macOS Mojave shipped with a screen capturing tool. So here is a retry of the demo in under 5 minutes:</p>\n<h2>The how</h2>\n<p>This might not be the most interesting part of it, but it\u2019s nice to share work. It\u2019s not a full comprehensive guide on how to do this stuff, because that would just take way too long. See it as a quick guide behind the different API\u2019s involved. </p>\n<p>I googled it all anyway. You can google along.</p>\n<p><em>Oh and if you want to skip ahead: there are some use cases at the end.</em></p>\n<h3>Showing a local notification</h3>\n<p>Like with any Javascript, you should check support before you ask something. There is a list of things to ask in the below code example: we want Notifications, it should not be denied, there has to be ServiceWorker support, and for the part later on, there should be a PushManager too. </p>\n<p>Once we prompted the user and got permission, it\u2019s as simple as getting our ServiceWorker registration and ask it to show a notification. As you can see: this involves the ServiceWorker, but it does not involve any other servers.</p>\n<pre><code>function activateNotifications() {\n Notification.requestPermission()\n .then(status => this.status = status)\n},\n\nfunction supportsNotifications() {\n return ('Notification' in window) && (this.status !== 'denied') &&\n ('serviceWorker' in navigator) && ('PushManager' in window)\n}\n\nasync function sendTestNotification() {\n const reg = await navigator.serviceWorker.getRegistration()\n\n return reg.showNotification('Hallo, test!')\n}</code></pre>\n<p><em>Note: the demo code is using <a href=\"https://vuejs.org\">Vue</a>, which I leave out in this blog post to simplify things. But that\u2019s where <code>this</code> points to: a collection of variables on the Vue instance.</em></p>\n<h3>Subscribing for the PushAPI</h3>\n<p>Once the user clicks the button \u2018Subscribe\u2019, the following function gets triggered. In here, we again get the ServiceWorker registration, and then access the PushManager on it, which we tell to subscribe.</p>\n<pre><code>async function subscribe() {\n const reg = await navigator.serviceWorker.getRegistration()\n const sub = await reg.pushManager.subscribe({\n userVisibleOnly: true, // required for Chrome\n applicationServerKey: urlB64ToUint8Array(this.publicVapidKey)\n })\n\n this.notifications = true\n const key = sub.getKey('p256dh')\n const token = sub.getKey('auth')\n\n await this.$http.post('/subscriptions', {\n endpoint: sub.endpoint,\n key: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : null,\n token: token ? btoa(String.fromCharCode.apply(null, new Uint8Array(token))) : null\n })\n\n this.subscribed = sub !== null\n},</code></pre>\n<p>Some browsers have their own way of doing authentication, but the most universal is with a <a href=\"https://blog.mozilla.org/services/2016/08/23/sending-vapid-identified-webpush-notifications-via-mozillas-push-service/\">Vapid</a> key pair. The package I use for the backend came with a way of creating them. We give the public key to the PushManager, which will give us a <code>Subscription</code> object.</p>\n<p>In the end, we send the <code>Subscription</code>\u2019s key, token and endpoint to the server via a POST request.</p>\n<p><em>Note: my HTTP library of choice is <a href=\"https://github.com/axios/axios\">axios</a> and the <code>urlB64ToUint8Array()</code> function can be found <a href=\"https://github.com/gbhasha/base64-to-uint8array/blob/master/index.js\">here</a></em></p>\n<h3>Storing the Subscription</h3>\n<p>For the backend, I\u2019m using a <a href=\"http://laravel-notification-channels.com/webpush/\">Laravel package for WebPush</a>, which allows me to save the endpoint with very minimal code:</p>\n<pre><code>public function update(Request $request)\n{\n $this->validate($request, ['endpoint' => 'required']);\n $request->user()->updatePushSubscription(\n $request->endpoint,\n $request->key,\n $request->token\n );\n return response()->json(null, 201);\n}</code></pre>\n<p>As you can see, it is using the user to associate the data with. (I fake the auth in the demo, which I do not recommend.) It ends up in a database, with four main columns: <code>user_id</code>, <code>endpoint</code>, <code>public_key</code>, <code>auth_token</code>.</p>\n<p>In theory, you can go without users, but you will need to store the other parts. The token and key look like random strings, but the endpoint is an actual URL, on a subdomain of either Mozilla or Google, depending on the browser. (No support on Safari yet, mind you.)</p>\n<p>These endpoints and tokens can expire, so you will need to keep an eye on the table.</p>\n<h3>Sending the notification</h3>\n<p>I can be short about this part: I have no idea. The following code is all it takes to trigger it:</p>\n<pre><code>Notification::send(\n User::all(), \n new NewBlogPostCreated($content, $notify)\n);</code></pre>\n<p>... where <code>$content</code> is the content of the post, and <code>$notify</code> a boolean, telling my ServiceWorker whether or not to show a notification (we\u2019ll get to that).</p>\n<p>The <code>NewBlogPostCreated</code> class extends Laravel\u2019s build-in <code>Notification</code> class and has these two methods:</p>\n<pre><code>public function via($notifiable)\n{\n return [WebPushChannel::class];\n}\n\npublic function toWebPush($notifiable, $notification)\n{\n return (new WebPushMessage)\n ->title($this->notify ? 'notify' : 'update-cache')\n ->body($this->content);\n}</code></pre>\n<p>There is a lot of magic behind the scenes here. I have no idea. In the end, they send a POST request to the endpoints of those users, after signing the right things with the right keys.</p>\n<h3>Receiving the notification and then don\u2019t</h3>\n<p>Next, we\u2019re back in Javascript-land, however, this is the ServiceWorker-province. The ServiceWorker, once installed, is a script, written in Javascript, but completely decoupled from any window. It lives in your browser and represents not one page, but your whole website. </p>\n<p>It\u2019s quite hard to wrap your head around at first, but, I think the PushAPI makes it easier: there is no window involved with a push message, and there is no page involved with a push message. There is only your ServiceWorker, which acts for your whole website.</p>\n<p>The ServiceWorker script itself consists of a series of callbacks, that are executed whenever things happen. In the case of a push message, the <code>'push'</code> event is triggered:</p>\n<pre><code>(function() {\n 'use strict';\n\n self.addEventListener('push', function (e) {\n const data = e.data.json()\n\n self.caches.open('manual')\n .then(cache => cache.put('hello', new Response(data.body)))\n\n if (data.title == 'notify') {\n e.waitUntil(\n self.registration.showNotification(\n 'New content!', \n {body: data.body}\n )\n );\n }\n });\n\n})();</code></pre>\n<p>That\u2019s all I need for receiving push notifications. I first retrieve the data from the message. Then I open the cache named \u2018manual\u2019 and I put the body of the message in that cache as the content of a URL (in this case \u2018offline.test/hello\u2019). It is made for pages, but I use it as a key-value store here.</p>\n<p>Then I check the title field, which I have abused for this purpose. If it is set to the magic string \u2018notify\u2019, I will trigger the notification. If it\u2019s something else I will do nothing.</p>\n<p>This shows that I don\u2019t have to: I can leave the notification out, but I still get a ServiceWorker activation and I can do whatever I want with it.</p>\n<h2>Use cases</h2>\n<p>I think this can be used for creepy things (can I occasionally ping my ServiceWorkers and ask for data like \u2018how many windows are open?\u2019 and phone that home?), but I also think there are nice uses for this as well.</p>\n<p>As Jeremy wrote: this can be used for magazines, podcasts and blogs to push new content to my phone, to read on a plane or in the subway when I\u2019m offline. I see a nice feature for a web-based IndieWeb <a href=\"https://indieweb.org/reader\">Reader</a> too: it can push me copies of posts it collected.</p>\n<p>I think the Reader is a nice place to use this. With great power comes great responsibility. Do I want to grand that great power to that weird magazine, that dubious podcast, that blog I visit once or twice a month? I might know you well, I might not. Do I trust you, pushing megabytes on my phone without me noticing?</p>\n<p>Web apps like a Reader are easier to bond with. Plus: once I know my Reader supports reading offline, I might visit it in the subway. Will I remember the magazine? </p>\n<p>The last bonus of the IndieWeb Reader specifically: it can send me posts from any magazine or podcast or blog, whether they support offline reading or not. But that\u2019s more specific to the Reader than it is to Push.</p>\n<p>I\u2019m also very curious to know how things will evolve if ServiceWorkers get even more superpowers. How well will those pair with a free ServiceWorker activation? Lot\u2019s of exploring to do!</p>" }, "author": { "type": "card", "name": "Sebastiaan Andeweg", "url": "https://seblog.nl/", "photo": "https://aperture-proxy.p3k.io/10e8aeca31d1cd146999fcacc07a8eb9ad47c813/68747470733a2f2f7365626c6f672e6e6c2f70686f746f2e6a7067" }, "post-type": "article", "_id": "1411685", "_source": "1366", "_is_read": true }
{ "type": "entry", "published": "2018-11-13T07:17:11-05:00", "url": "https://david.shanske.com/2018/11/13/an-indieweb-podcast-episode-11-four-iwcs-later/", "name": "Episode 11 - Four IWCs Later", "content": { "text": "https://david.shanske.com/wp-content/uploads/2018/11/Indieweb11.mp3In the latest episode of An Indieweb Podcast, Chris Aldrich and I get together to talk about what we\u2019ve been up to since the last episode in September.\n\u00a0", "html": "<a href=\"https://david.shanske.com/wp-content/uploads/2018/11/Indieweb11.mp3\">https://david.shanske.com/wp-content/uploads/2018/11/Indieweb11.mp3</a><p>In the latest episode of An Indieweb Podcast, Chris Aldrich and I get together to talk about what we\u2019ve been up to since the last episode in September.</p>\n<p>\u00a0</p>" }, "author": { "type": "card", "name": "David Shanske", "url": "https://david.shanske.com/", "photo": "https://david.shanske.com/wp-content/uploads/avatar-privacy/cache/gravatar/2/c/2cb1f8afd9c8d3b646b4071c5ed887c970d81d625eeed87e447706940e2c403d-125.png" }, "post-type": "article", "_id": "1408734", "_source": "5", "_is_read": true }
{ "type": "entry", "author": { "name": "Duncan Stephen", "url": "https://duncanstephen.co.uk/", "photo": null }, "url": "https://duncanstephen.co.uk/shifting-focus/", "published": "2018-11-12T20:05:33+00:00", "content": { "html": "<img src=\"https://aperture-proxy.p3k.io/82083408d23bf3edfa0bd7aa3bb921f4d5340a12/68747470733a2f2f64756e63616e7374657068656e2e636f2e756b2f77702d636f6e74656e742f75706c6f6164732f323031382f31312f74696c742d73686966742d333630783138392e6a7067\" alt=\"Tilt shift photo of a wall\" /><p><a href=\"https://duncanstephen.co.uk/why-its-time-to-reclaim-our-digital-lives/\">Just over a year ago, I started blogging again</a>. Shortly afterwards, I committed myself to publishing a blog post a day. Most of these would be \u201c<a href=\"https://duncanstephen.co.uk/type/link/\">links that made me think</a>\u201d. I saw this as quite an easy way of publishing something daily.</p>\n<p>This seemed to work pretty well at first. But gradually, I began to realise something disturbing. It was taking me far too long to push these link posts out.</p>\n<p>Here is my blogging workflow. Whenever I read an interesting article I think is worth linking to, I add it to a backlog. When I have some spare time for blogging, I go through this backlog, re-read those articles, consider if I still find them interesting, then figure out if I have something interesting to say about them.</p>\n<p>I would often schedule these posts way in advance. This worked really well if I was going on holiday. I could have two weeks\u2019 worth of posts lined up and ready to go. But that brought other challenges. I had to avoid really timely topics. Although since it took me so long to get through my backlog, timely topics usually fell by the wayside anyway.</p>\n<p>Sometimes people would say to me in person, \u201cyou posted an interesting article today\u201d. But I couldn\u2019t even remember what I had written and scheduled for that day.</p>\n<p>If I was feeling really lazy, I would simply post a link, just with a quote from the link, without adding anything of my own. After a while, I stopped myself from doing that.</p>\n<p>I ought to add value, by adding my own commentary, or some kind of response to the article. Otherwise there is no real point in my posting a link.</p>\n<p>Also, most of the links I published didn\u2019t actually attract much traffic. People seem to be more interested in my longer articles, when I write about myself, or what I think about something.</p>\n<p>In a way, that makes sense. Why would anyone go to the Duncan Stephen blog to follow external links? As much as I might like to be, I\u2019m not a curator or an aggregator. The point of a personal blog is that it\u2019s <em>personal</em> \u2014 about me.</p>\n<p>Most absurdly of all, I have a large backlog of links that I have <em>loads</em> to say about, but I <em>haven\u2019t</em>. Because I perceive that I don\u2019t have the time to. Because I thought I needed to write about less interesting articles once a day.</p>\n<p>I expected August to be the toughest time of year to keep up the daily blogging routine, because it\u2019s such a busy time of year for me and I was going on holiday. But things actually ground to a halt in October.</p>\n<p>I found myself staring at a backlog of articles that were either slightly dull, or I didn\u2019t have anything to say about. Meanwhile, further down the list, there were a set of articles and topics I\u2019ve put off for another day when I\u2019ve got more time.</p>\n<p>The tail was wagging the dog. This was completely the opposite of the situation I wanted to be in when I started blogging again. I had wanted to be in control of what I published. But I let a self-imposed routine take control of me.</p>\n<p>So about a month ago I loosened up my rules. Gone are the daily link posts. Gone, possibly, is the commitment to post daily at all.</p>\n<p>Then the floodgates opened. Suddenly, I was writing more freely. I found myself tapping out more longer articles every week \u2014 sometimes a handful of them a one day.</p>\n<p>I had gone from agonising over what to say about a link, to genuinely writing about my own thoughts and feelings. It feels much better.</p>\n<h3>A technical note</h3>\n<p>I have changed the way different post types are organised. I had tried two previous out-of-the-box options \u2014 <a href=\"https://indieweb.org/Post_Kinds_Plugin\">IndieWeb Post Kinds</a> and <a href=\"https://codex.wordpress.org/Post_Formats\">WordPress\u2019s own Post Formats</a>. Neither of them worked quite right for me.</p>\n<p>So now I have implemented my own custom taxonomy. Quite why I hadn\u2019t done that in the first place is a mystery to me now, since custom taxonomies aren\u2019t actually that difficult to set up in WordPress.</p>\n<p>This does mean that I have to go through and add all of my archive posts to the new custom taxonomy. I\u2019m about a quarter of the way through. So some archives posts will look a bit odd for the time being.</p>", "text": "Just over a year ago, I started blogging again. Shortly afterwards, I committed myself to publishing a blog post a day. Most of these would be \u201clinks that made me think\u201d. I saw this as quite an easy way of publishing something daily.\nThis seemed to work pretty well at first. But gradually, I began to realise something disturbing. It was taking me far too long to push these link posts out.\nHere is my blogging workflow. Whenever I read an interesting article I think is worth linking to, I add it to a backlog. When I have some spare time for blogging, I go through this backlog, re-read those articles, consider if I still find them interesting, then figure out if I have something interesting to say about them.\nI would often schedule these posts way in advance. This worked really well if I was going on holiday. I could have two weeks\u2019 worth of posts lined up and ready to go. But that brought other challenges. I had to avoid really timely topics. Although since it took me so long to get through my backlog, timely topics usually fell by the wayside anyway.\nSometimes people would say to me in person, \u201cyou posted an interesting article today\u201d. But I couldn\u2019t even remember what I had written and scheduled for that day.\nIf I was feeling really lazy, I would simply post a link, just with a quote from the link, without adding anything of my own. After a while, I stopped myself from doing that.\nI ought to add value, by adding my own commentary, or some kind of response to the article. Otherwise there is no real point in my posting a link.\nAlso, most of the links I published didn\u2019t actually attract much traffic. People seem to be more interested in my longer articles, when I write about myself, or what I think about something.\nIn a way, that makes sense. Why would anyone go to the Duncan Stephen blog to follow external links? As much as I might like to be, I\u2019m not a curator or an aggregator. The point of a personal blog is that it\u2019s personal \u2014 about me.\nMost absurdly of all, I have a large backlog of links that I have loads to say about, but I haven\u2019t. Because I perceive that I don\u2019t have the time to. Because I thought I needed to write about less interesting articles once a day.\nI expected August to be the toughest time of year to keep up the daily blogging routine, because it\u2019s such a busy time of year for me and I was going on holiday. But things actually ground to a halt in October.\nI found myself staring at a backlog of articles that were either slightly dull, or I didn\u2019t have anything to say about. Meanwhile, further down the list, there were a set of articles and topics I\u2019ve put off for another day when I\u2019ve got more time.\nThe tail was wagging the dog. This was completely the opposite of the situation I wanted to be in when I started blogging again. I had wanted to be in control of what I published. But I let a self-imposed routine take control of me.\nSo about a month ago I loosened up my rules. Gone are the daily link posts. Gone, possibly, is the commitment to post daily at all.\nThen the floodgates opened. Suddenly, I was writing more freely. I found myself tapping out more longer articles every week \u2014 sometimes a handful of them a one day.\nI had gone from agonising over what to say about a link, to genuinely writing about my own thoughts and feelings. It feels much better.\nA technical note\nI have changed the way different post types are organised. I had tried two previous out-of-the-box options \u2014 IndieWeb Post Kinds and WordPress\u2019s own Post Formats. Neither of them worked quite right for me.\nSo now I have implemented my own custom taxonomy. Quite why I hadn\u2019t done that in the first place is a mystery to me now, since custom taxonomies aren\u2019t actually that difficult to set up in WordPress.\nThis does mean that I have to go through and add all of my archive posts to the new custom taxonomy. I\u2019m about a quarter of the way through. So some archives posts will look a bit odd for the time being." }, "name": "Shifting focus", "post-type": "article", "_id": "1404806", "_source": "239", "_is_read": true }
{ "type": "event", "name": "Homebrew Website Club SF!", "summary": "17:30: Optional writing hour and quiet socializing\n18:30: IndieWeb demos and hack night!\n\nHomebrew Website Club retro 1980s-style logo\nTopics for this week: Recently: IndieWebCamp Berlin! Year-end hack projects Demos of personal website breakthroughs Create or update your personal web site!\nJoin a community with like-minded interests. Bring friends that want a personal site, or are interested in a healthy, independent web!\nAny questions? Ask in #indieweb Slack or IRC\nMore information: IndieWeb Wiki Event Page\nRSVP: post an indie RSVP on your own site!", "published": "2018-11-12 10:15-0800", "start": "2018-11-14 17:30-0800", "end": "2018-11-14 19:30-0800", "url": "http://tantek.com/2018/318/e1/homebrew-website-club-sf", "location": [ "https://wiki.mozilla.org/SF" ], "content": { "text": "When: 2018-11-14 17:30\u202619:30\nWhere: Mozilla San Francisco\n\nHost: Tantek \u00c7elik\n\n\n\n17:30: Optional writing hour and quiet socializing\n\n18:30: IndieWeb demos and hack night!\n\n\nTopics for this week:\nRecently: IndieWebCamp Berlin!\nYear-end hack projects\nDemos of personal website breakthroughs\nCreate or update your personal web site!\n\nJoin a community with like-minded interests. Bring friends that want a personal site, or are interested in a healthy, independent web!\n\n\nAny questions? Ask in \n#indieweb Slack or IRC\n\n\nMore information: \nIndieWeb Wiki Event Page\n\n\nRSVP: post an indie RSVP on your own site!", "html": "<p>\nWhen: <time class=\"dt-start\">2018-11-14 17:30</time>\u2026<time class=\"dt-end\">19:30</time><span>\nWhere: <a class=\"u-location h-card\" href=\"https://wiki.mozilla.org/SF\">Mozilla San Francisco</a>\n</span>\nHost: <a class=\"u-organizer h-card\" href=\"http://tantek.com/\">Tantek \u00c7elik</a>\n</p>\n\n<p>\n17:30: Optional writing hour and quiet socializing<br />\n18:30: IndieWeb demos and hack night!<br /></p>\n<p><img class=\"u-featured\" style=\"height:300px;\" src=\"https://aperture-media.p3k.io/indieweb.org/c24f7b1e711955ef818bde12e2a3e79708ecc9b106d95b460a9fefe93b0be723.jpg\" alt=\"Homebrew Website Club retro 1980s-style logo\" /></p>\n<p>Topics for this week:</p>\n<ul><li>Recently: <a href=\"https://indieweb.org/2018/Berlin\">IndieWebCamp Berlin</a>!</li>\n<li><a href=\"https://indieweb.org/2019-01-01-commitments\">Year-end hack projects</a></li>\n<li>Demos of personal website breakthroughs</li>\n<li>Create or update your personal web site!</li>\n</ul><p>\nJoin a community with like-minded interests. Bring friends that want a personal site, or are interested in a healthy, independent web!\n</p>\n<p>\nAny questions? Ask in \n<a href=\"https://indieweb.org/discuss\">#indieweb Slack or IRC</a>\n</p>\n<p>\nMore information: \n<a class=\"u-url\" href=\"https://indieweb.org/events/2018-11-14-homebrew-website-club-sf\">IndieWeb Wiki Event Page</a>\n</p>\n<p>\nRSVP: post an <a href=\"https://indieweb.org/rsvp\">indie RSVP</a> on your own site!\n</p>" }, "post-type": "event", "refs": { "https://wiki.mozilla.org/SF": { "type": "card", "name": "Mozilla San Francisco", "url": "https://wiki.mozilla.org/SF", "photo": null } }, "_id": "1404766", "_source": "1", "_is_read": true }
{ "type": "entry", "url": "https://davidjohnmead.com/blog/2018/11/11/am-i-secure-now/", "syndication": [ "https://twitter.com/davidmead/status/1061737385216610304" ], "content": { "text": "I think I\u2019ve just successfully set up an SSL Certificate for my domain. Now lets see if any of my #indieweb stuff breaks.", "html": "I <em>think</em> I\u2019ve just successfully set up an <abbr title=\"Secure Sockets Layer\">SSL</abbr> Certificate for my domain. Now lets see if any of my #indieweb stuff breaks." }, "post-type": "note", "_id": "1403105", "_source": "194", "_is_read": true }
{ "type": "entry", "author": { "name": "Manton Reece", "url": "https://www.manton.org/", "photo": "https://aperture-proxy.p3k.io/907926e361383204bd1bc913c143c23e70ae69bb/68747470733a2f2f6d6963726f2e626c6f672f6d616e746f6e2f6176617461722e6a7067" }, "url": "https://www.manton.org/2018/11/12/timetable-migrated-to.html", "name": "Timetable migrated to Micro.blog", "content": { "html": "<p>Earlier this year I migrated 15 years of posts on manton.org to Micro.blog blog hosting. Today I finished moving over 100 episodes of <a href=\"https://timetable.manton.org/\">my podcast Timetable</a> to Micro.blog podcast hosting. I had gotten out of the routine of recording Timetable, and I think moving it to Micro.blog will simplify the setup and make me publish the podcast more often.</p>\n\n<p>Timetable had been hosted on WordPress using the Seriously Simple Podcasting plugin. There were at least a few ways I could\u2019ve moved the episodes, but ultimately I decided on the following:</p>\n\n<ol><li>Downloaded all the MP3s to my Mac.</li>\n <li>Adjusted the WordPress settings to show 120 recent posts in the feeds, then saved the resulting JSON Feed of all episodes. This let me parse the JSON much more easily than working with the WordPress API or XML.</li>\n <li>Wrote <a href=\"https://gist.github.com/manton/d840af1ea9048215de40985b22668cca\">a Ruby script</a> that uses the Micropub API to upload each MP3, then create a new post with an <code>audio</code> tag, preserving the original post content and date.</li>\n <li>Pointed timetable.manton.org to use Micro.blog.</li>\n</ol><p>Even if you don\u2019t know Ruby, hopefully you can see in the script how easy is it to work with Micropub, which Micro.blog uses as its native interface to create posts and upload files. I just call out to the <code>curl</code> command-line tool to do the work.</p>\n\n<p>If you\u2019re new to Timetable, each episode is only about 5 minutes. Some of my recent favorites include <a href=\"http://timetable.manton.org/2017/12/21/episode-lose-yourself.html\">episode 91</a> (Lose yourself), <a href=\"http://timetable.manton.org/2018/01/12/episode-good-better.html\">episode 92</a> (Good, better, best), and <a href=\"http://timetable.manton.org/2018/05/04/episode-one-year.html\">episode 100</a> (One year).</p>", "text": "Earlier this year I migrated 15 years of posts on manton.org to Micro.blog blog hosting. Today I finished moving over 100 episodes of my podcast Timetable to Micro.blog podcast hosting. I had gotten out of the routine of recording Timetable, and I think moving it to Micro.blog will simplify the setup and make me publish the podcast more often.\n\nTimetable had been hosted on WordPress using the Seriously Simple Podcasting plugin. There were at least a few ways I could\u2019ve moved the episodes, but ultimately I decided on the following:\n\nDownloaded all the MP3s to my Mac.\n Adjusted the WordPress settings to show 120 recent posts in the feeds, then saved the resulting JSON Feed of all episodes. This let me parse the JSON much more easily than working with the WordPress API or XML.\n Wrote a Ruby script that uses the Micropub API to upload each MP3, then create a new post with an audio tag, preserving the original post content and date.\n Pointed timetable.manton.org to use Micro.blog.\nEven if you don\u2019t know Ruby, hopefully you can see in the script how easy is it to work with Micropub, which Micro.blog uses as its native interface to create posts and upload files. I just call out to the curl command-line tool to do the work.\n\nIf you\u2019re new to Timetable, each episode is only about 5 minutes. Some of my recent favorites include episode 91 (Lose yourself), episode 92 (Good, better, best), and episode 100 (One year)." }, "published": "2018-11-12T09:40:44-06:00", "post-type": "article", "_id": "1402463", "_source": "12", "_is_read": true }
{ "type": "entry", "published": "2018-11-11T23:41:41Z", "url": "https://adactio.com/journal/14511", "category": [ "push", "notifications", "serviceworkers", "offline", "caching", "caches", "background", "sync", "frontend", "development", "permissions", "dialogue", "security", "indiewebcamp", "berlin", "browsers", "standards" ], "syndication": [ "https://medium.com/@adactio/e5dfb24f7189" ], "name": "Push without notifications", "content": { "text": "On the first day of Indie Web Camp Berlin, I led a session on going offline with service workers. This covered all the usual use-cases: pre-caching; custom offline pages; saving pages for offline reading.\n\nBut on the second day, Sebastiaan spent a fair bit of time investigating a more complex use of service workers with the Push API.\n\nThe Push API is what makes push notifications possible on the web. There are a lot of moving parts\u2014browser, server, service worker\u2014and, frankly, it\u2019s way over my head. But I\u2019m familiar with the general gist of how it works. Here\u2019s a typical flow:\n\nA website prompts the user for permission to send push notifications.\nThe user grants permission.\nA whole lot of complicated stuff happens behinds the scenes.\nNext time the website publishes something relevant, it fires a push message containing the details of the new URL.\nThe user\u2019s service worker receives the push message (even if the site isn\u2019t open).\nThe service worker creates a notification linking to the URL, interrupting the user, and generally adding to the weight of information overload.\nHere\u2019s what Sebastiaan wanted to investigate: what if that last step weren\u2019t so intrusive? Here\u2019s the alternate flow he wanted to test:\n\nA website prompts the user for permission to send push notifications.\nThe user grants permission.\nA whole lot of complicated stuff happens behinds the scenes.\nNext time the website publishes something relevant, it fires a push message containing the details of the new URL.\nThe user\u2019s service worker receives the push message (even if the site isn\u2019t open).\nThe service worker fetches the contents of the URL provided in the push message and caches the page. Silently.\nIt worked.\n\nI think this could be a real game-changer. I don\u2019t know about you, but I\u2019m very, very wary of granting websites the ability to send me push notifications. In fact, I don\u2019t think I\u2019ve ever given a website permission to interrupt me with push notifications. \n\nYou\u2019ve seen the annoying permission dialogues, right?\n\nIn Firefox, it looks like this:\n\n\n Will you allow name-of-website to send notifications?\n \n [Not Now] [Allow Notifications]\n\n\nIn Chrome, it\u2019s:\n\n\n name-of-website wants to\n \n Show notifications\n \n [Block] [Allow]\n\n\nBut in actual fact, these dialogues are asking for permission to do two things:\n\nReceive messages pushed from the server.\nDisplay notifications based on those messages.\nThere\u2019s no way to ask for permission just to do the first part. That\u2019s a shame. While I\u2019m very unwilling to grant permission to be interrupted by intrusive notifications, I\u2019d be more than willing to grant permission to allow a website to silently cache timely content in the background. It would be a more calm technology.\n\nThink of the use cases:\n\nI grant push permission to a magazine. When the magazine publishes a new article, it\u2019s cached on my device.\nI grant push permission to a podcast. Whenever a new episode is published, it\u2019s cached on my device.\nI grant push permission to a blog. When there\u2019s a new blog post, it\u2019s cached on my device.\nThen when I\u2019m on a plane, or in the subway, or in any other situation without a network connection, I could still visit these websites and get content that\u2019s fresh to me. It\u2019s kind of like background sync in reverse.\n\nThere\u2019s plenty of opportunity for abuse\u2014the cache could get filled with content. But websites can already do that, and they don\u2019t need to be granted any permissions to do so; just by visiting a website, it can add multiple files to a cache.\n\nSo it seems that the reason for the permissions dialogue is all about displaying notifications \u2026not so much about receiving push messages from the server.\n\nI wish there were a way to implement this background-caching pattern without requiring the user to grant permission to a dialogue that contains the word \u201cnotification.\u201d\n\nI wonder if the act of adding a site to the home screen could implicitly grant permission to allow use of the Push API without notifications?\n\nIn the meantime, the proposal for periodic synchronisation (using background sync) could achieve similar results, but in a less elegant way; periodically polling for new content instead of receiving a push message when new content is published. Also, it requires permission. But at least in this case, the permission dialogue should be more specific, and wouldn\u2019t include the word \u201cnotification\u201d anywhere.", "html": "<p>On the first day of <a href=\"https://indieweb.org/2018/Berlin\">Indie Web Camp Berlin</a>, I led a session on <a href=\"https://abookapart.com/products/going-offline\">going offline with service workers</a>. This covered all the usual use-cases: pre-caching; custom offline pages; saving pages for offline reading.</p>\n\n<p>But on the second day, <a href=\"https://seblog.nl/\">Sebastiaan</a> spent a fair bit of time investigating a more complex use of service workers with <a href=\"https://www.w3.org/TR/push-api/\">the Push API</a>.</p>\n\n<p>The Push API is what makes push notifications possible on the web. There are a <em>lot</em> of moving parts\u2014browser, server, service worker\u2014and, frankly, it\u2019s way over my head. But I\u2019m familiar with the general gist of how it works. Here\u2019s a typical flow:</p>\n\n<ol><li>A website prompts the user for permission to send push notifications.</li>\n<li>The user grants permission.</li>\n<li>A whole lot of complicated stuff happens behinds the scenes.</li>\n<li>Next time the website publishes something relevant, it fires a push message containing the details of the new URL.</li>\n<li>The user\u2019s service worker receives the push message (even if the site isn\u2019t open).</li>\n<li>The service worker creates a notification linking to the URL, interrupting the user, and generally adding to the weight of information overload.</li>\n</ol><p>Here\u2019s what Sebastiaan wanted to investigate: what if that last step weren\u2019t so intrusive? Here\u2019s the alternate flow he wanted to test:</p>\n\n<ol><li>A website prompts the user for permission to send push notifications.</li>\n<li>The user grants permission.</li>\n<li>A whole lot of complicated stuff happens behinds the scenes.</li>\n<li>Next time the website publishes something relevant, it fires a push message containing the details of the new URL.</li>\n<li>The user\u2019s service worker receives the push message (even if the site isn\u2019t open).</li>\n<li>The service worker fetches the contents of the URL provided in the push message and caches the page. Silently.</li>\n</ol><p>It worked.</p>\n\n<p>I think this could be a real game-changer. I don\u2019t know about you, but I\u2019m very, <em>very</em> wary of granting websites the ability to send me push notifications. In fact, I don\u2019t think I\u2019ve ever given a website permission to interrupt me with push notifications. </p>\n\n<p>You\u2019ve seen the annoying permission dialogues, right?</p>\n\n<p>In Firefox, it looks like this:</p>\n\n<blockquote>\n <p>Will you allow name-of-website to send notifications?</p>\n \n <p>[Not Now] [Allow Notifications]</p>\n</blockquote>\n\n<p>In Chrome, it\u2019s:</p>\n\n<blockquote>\n <p>name-of-website wants to</p>\n \n <p>Show notifications</p>\n \n <p>[Block] [Allow]</p>\n</blockquote>\n\n<p>But in actual fact, these dialogues are asking for permission to do <strong>two</strong> things:</p>\n\n<ol><li>Receive messages pushed from the server.</li>\n<li>Display notifications based on those messages.</li>\n</ol><p>There\u2019s no way to ask for permission just to do the first part. That\u2019s a shame. While I\u2019m very unwilling to grant permission to be interrupted by intrusive notifications, I\u2019d be more than willing to grant permission to allow a website to silently cache timely content in the background. It would be a more <a href=\"https://calmtech.com/\">calm technology</a>.</p>\n\n<p>Think of the use cases:</p>\n\n<ul><li>I grant push permission to <a href=\"https://www.1843magazine.com/\">a magazine</a>. When the magazine publishes a new article, it\u2019s cached on my device.</li>\n<li>I grant push permission to <a href=\"http://revisionisthistory.com/\">a podcast</a>. Whenever a new episode is published, it\u2019s cached on my device.</li>\n<li>I grant push permission to <a href=\"https://www.centauri-dreams.org/\">a blog</a>. When there\u2019s a new blog post, it\u2019s cached on my device.</li>\n</ul><p>Then when I\u2019m on a plane, or in the subway, or in any other situation without a network connection, I could still visit these websites and get content that\u2019s fresh to me. It\u2019s kind of like <a href=\"https://developers.google.com/web/updates/2015/12/background-sync\">background sync</a> in reverse.</p>\n\n<p>There\u2019s plenty of opportunity for abuse\u2014the cache could get filled with content. But websites can already do that, and they don\u2019t need to be granted any permissions to do so; <a href=\"https://adactio.com/journal/11730\">just by visiting a website</a>, it can add multiple files to a cache.</p>\n\n<p>So it seems that the reason for the permissions dialogue is all about <em>displaying notifications</em> \u2026not so much about receiving push messages from the server.</p>\n\n<p>I wish there were a way to implement this background-caching pattern without requiring the user to grant permission to a dialogue that contains the word \u201cnotification.\u201d</p>\n\n<p>I wonder if the act of <a href=\"https://adactio.com/journal/13061\">adding a site to the home screen</a> could implicitly grant permission to allow use of the Push API <em>without</em> notifications?</p>\n\n<p>In the meantime, the proposal for <a href=\"https://github.com/WICG/BackgroundSync/blob/master/explainer.md#periodic-synchronization-in-design\">periodic synchronisation</a> (using background sync) could achieve similar results, but in a less elegant way; periodically polling for new content instead of receiving a push message when new content is published. Also, it requires permission. But at least in this case, the permission dialogue should be more specific, and wouldn\u2019t include the word \u201cnotification\u201d anywhere.</p>" }, "author": { "type": "card", "name": "Jeremy Keith", "url": "https://adactio.com/", "photo": "https://aperture-proxy.p3k.io/bbbacdf0a064621004f2ce9026a1202a5f3433e0/68747470733a2f2f6164616374696f2e636f6d2f696d616765732f70686f746f2d3135302e6a7067" }, "post-type": "article", "_id": "1399637", "_source": "2", "_is_read": true }
{ "type": "entry", "url": "http://davidjohnmead.com/blog/2018/11/11/am-i-secure-now/", "content": { "text": "I think I\u2019ve just successfully set up an SSL Certificate for my domain. Now lets see if any of my #indieweb stuff breaks.", "html": "I <em>think</em> I\u2019ve just successfully set up an <abbr title=\"Secure Sockets Layer\">SSL</abbr> Certificate for my domain. Now lets see if any of my #indieweb stuff breaks." }, "post-type": "note", "_id": "1398818", "_source": "194", "_is_read": true }
{ "type": "entry", "published": "2018-11-10T12:53:17Z", "url": "https://adactio.com/journal/14509", "category": [ "indieweb", "webmentions", "publishing", "privacy", "ethics", "indiewebcamp", "berlin", "h-entry", "microformats", "location", "mapping", "demos" ], "syndication": [ "https://medium.com/@adactio/38a5680bea11" ], "name": "Webmentions at Indie Web Camp Berlin", "content": { "text": "I was in Berlin for most of last week, and every day was packed with activity:\n\n\nIndie Web Camp on Saturday and Sunday,\n\nAccessibility Club on Monday,\n\nBeyond Tellerrand on Tuesday and Wednesday.\nBy the time I got back to Brighton, my brain was full \u2026just in time for FF Conf.\n\nAll of the events were very different, but equally enjoyable. It was also quite nice to just attend events without speaking at them.\n\nIndie Web Camp Berlin was terrific. There was an excellent turnout, and once again, I found that the format was just right: a day of discussions (BarCamp style) followed by a day of doing (coding, designing, hacking). I got very inspired on the first day, so I was raring to go on the second.\n\nWhat I like to do on the second day is try to complete two tasks; one that\u2019s fairly straightforward, and one that\u2019s a bit tougher. That way, when it comes time to demo at the end of the day, even if I haven\u2019t managed to complete the tougher one, I\u2019ll still be able to demo the simpler one.\n\nIn this case, the tougher one was also tricky to demo. It involved a lot of invisible behind-the-scenes plumbing. I was tweaking my webmention endpoint (stop sniggering\u2014tweaking your endpoint is no laughing matter).\n\nUp until now, I could handle straightforward webmentions, and I could handle updates (if I receive more than one webmention from the same link, I check it each time). But I needed to also handle deletions.\n\nThe spec is quite clear on this. A 404 isn\u2019t enough to trigger a deletion\u2014that might be a temporary state. But a status of 410 Gone indicates that a resource was once here but has since been deliberately removed. In that situation, any stored webmentions for that link should also be removed.\n\nAnyway, I think I got it working, but it\u2019s tricky to test and even trickier to demo. \u201cNot to worry\u201d, I thought, \u201cI\u2019ve always got my simpler task.\u201d\n\nFor that, I chose to add a little map to my homepage showing the last location I published something from. I\u2019ve been geotagging all my content for years (journal entries, notes, links, articles), but not really doing anything with that data. This is a first step to doing something interesting with many years of location data.\n\nI\u2019ve got it working now, but the demo gods really weren\u2019t with me at Indie Web Camp. Both of my demos failed. The webmention demo failed quite embarrassingly.\n\nAs well as handling deletions, I also wanted to handle updates where a URL that once linked to a post of mine no longer does. Just to be clear, the URL still exists\u2014it\u2019s not 404 or 410\u2014but it has been updated to remove the original link back to one of my posts. I know this sounds like another very theoretical situation, but I\u2019ve actually got an example of it on my very first webmention test post from five years ago. Believe it or not, there\u2019s an escort agency in Nottingham that\u2019s using webmention as a vector for spam. They post something that does link to my test post, send a webmention, and then remove the link to my test post. I almost admire their dedication.\n\nStill, I wanted to foil this particular situation so I thought I had updated my code to handle it. Alas, when it came time to demo this, I was using someone else\u2019s computer, and in my attempt to right-click and copy the URL of the spam link \u2026I accidentally triggered it. In front of a room full of people. It was midly NSFW, but more worryingly, a potential Code Of Conduct violation. I\u2019m very sorry about that.\n\nApart from the humiliating demo, I thoroughly enjoyed Indie Web Camp, and I\u2019m going to keep adjusting my webmention endpoint. There was a terrific discussion around the ethical implications of storing webmentions, led by Sebastian, based on his epic post from earlier this year.\n\nWe established early in the discussion that we weren\u2019t going to try to solve legal questions\u2014like GDPR \u201ccompliance\u201d, which varies depending on which lawyer you talk to\u2014but rather try to figure out what the right thing to do is.\n\nEarlier that day, during the introductions, I quite happily showed webmentions in action on my site. I pointed out that my last blog post had received a response from another site, and because that response was marked up as an h-entry, I displayed it in full on my site. I thought this was all hunky-dory, but now this discussion around privacy made me question some inferences I was making:\n\nBy receiving a webention in the first place, I was inferring a willingness for the link to be made public. That\u2019s not necessarily true, as someone pointed out: a CMS could be automatically sending webmentions, which the author might be unaware of.\nIf the linking post is marked up in h-entry, I was inferring a willingness for the content to be republished. Again, not necessarily true.\nThat second inferrence of mine\u2014that publishing in a particular format somehow grants permissions\u2014actually has an interesting precedent: Google AMP. Simply by including the Google AMP script on a web page, you are implicitly giving Google permission to store a complete copy of that page and serve it from their servers instead of sending people to your site. No terms and conditions. No checkbox ticked. No \u201cI agree\u201d button pressed.\n\nJust sayin\u2019.\n\nAnyway, when it comes to my own processing of webmentions, I\u2019m going to take some of the suggestions from the discussion on board. There are certain signals I could be looking for in the linking post:\n\nDoes it include a link to a licence?\nIs there a restrictive robots.txt file?\nAre there meta declarations that say noindex?\nEach one of these could help to infer whether or not I should be publishing a webmention or not. I quickly realised that what we\u2019re talking about here is an algorithm.\n\nDespite its current usage to mean \u201cmagic\u201d, an algorithm is a recipe. It\u2019s a series of steps that contribute to a decision point. The problem is that, in the case of silos like Facebook or Instagram, the algorithms are secret (which probably contributes to their aura of magical thinking). If I\u2019m going to write an algorithm that handles other people\u2019s information, I don\u2019t want to make that mistake. Whatever steps I end up codifying in my webmention endpoint, I\u2019ll be sure to document them publicly.", "html": "<p>I was in Berlin for most of last week, and every day was packed with activity:</p>\n\n<ul><li>\n<a href=\"https://indieweb.org/2018/Berlin\">Indie Web Camp</a> on Saturday and Sunday,</li>\n<li>\n<a href=\"https://accessibility-club.org/\">Accessibility Club</a> on Monday,</li>\n<li>\n<a href=\"https://beyondtellerrand.com/events/berlin-2018/speakers\">Beyond Tellerrand</a> on Tuesday and Wednesday.</li>\n</ul><p>By the time I got back to Brighton, my brain was full \u2026just in time for <a href=\"https://2018.ffconf.org/\">FF Conf</a>.</p>\n\n<p>All of the events were very different, but equally enjoyable. It was also quite nice to just <em>attend</em> events without speaking at them.</p>\n\n<p><a href=\"https://indieweb.org/2018/Berlin\">Indie Web Camp Berlin</a> was terrific. There was an excellent turnout, and once again, I found that the format was just right: a day of discussions (BarCamp style) followed by a day of doing (coding, designing, hacking). I got very inspired on the first day, so I was raring to go on the second.</p>\n\n<p>What I like to do on the second day is try to complete two tasks; one that\u2019s fairly straightforward, and one that\u2019s a bit tougher. That way, when it comes time to demo at the end of the day, even if I haven\u2019t managed to complete the tougher one, I\u2019ll still be able to demo the simpler one.</p>\n\n<p>In this case, the tougher one was also tricky to demo. It involved a lot of invisible behind-the-scenes plumbing. I was tweaking my <a href=\"https://webmention.net/\">webmention</a> endpoint (stop sniggering\u2014tweaking your endpoint is no laughing matter).</p>\n\n<p>Up until now, I could handle straightforward webmentions, and I could handle updates (if I receive more than one webmention from the same link, I check it each time). But I needed to also handle deletions.</p>\n\n<p><a href=\"https://www.w3.org/TR/webmention/#sending-webmentions-for-deleted-posts\">The spec is quite clear on this</a>. A 404 isn\u2019t enough to trigger a deletion\u2014that might be a temporary state. But a status of <a href=\"https://tools.ietf.org/html/rfc7231#section-6.5.9\">410 Gone</a> indicates that a resource was once here but has since been deliberately removed. In that situation, any stored webmentions for that link should also be removed.</p>\n\n<p>Anyway, I <em>think</em> I got it working, but it\u2019s tricky to test and even trickier to demo. \u201cNot to worry\u201d, I thought, \u201cI\u2019ve always got my simpler task.\u201d</p>\n\n<p>For that, I chose to add a little map to my homepage showing the last location I published something from. I\u2019ve been geotagging all my content for years (journal entries, notes, links, articles), but not really doing anything with that data. This is a first step to doing something interesting with many years of location data.</p>\n\n<p>I\u2019ve got it working <em>now</em>, but the demo gods really weren\u2019t with me at Indie Web Camp. Both of my demos failed. The webmention demo failed quite embarrassingly.</p>\n\n<p>As well as handling deletions, I also wanted to handle updates where a URL that once linked to a post of mine no longer does. Just to be clear, the URL still exists\u2014it\u2019s not 404 or 410\u2014but it has been updated to remove the original link back to one of my posts. I know this sounds like another very theoretical situation, but I\u2019ve actually got an example of it on <a href=\"https://adactio.com/journal/6469\">my very first webmention test post</a> from five years ago. Believe it or not, there\u2019s an escort agency in Nottingham that\u2019s using webmention as a vector for spam. They post something that <em>does</em> link to my test post, send a webmention, and then remove the link to my test post. I almost admire their dedication.</p>\n\n<p>Still, I wanted to foil this particular situation so I thought I had updated my code to handle it. Alas, when it came time to demo this, I was using someone else\u2019s computer, and in my attempt to right-click and copy the URL of the spam link \u2026I accidentally triggered it. In front of a room full of people. It was midly <abbr title=\"Not Safe For Work\">NSFW</abbr>, but more worryingly, a potential <a href=\"https://indieweb.org/code-of-conduct\">Code Of Conduct</a> violation. I\u2019m very sorry about that.</p>\n\n<p>Apart from the humiliating demo, I thoroughly enjoyed Indie Web Camp, and I\u2019m going to keep adjusting my webmention endpoint. There was a terrific <a href=\"https://etherpad.indieweb.org/ethics\">discussion</a> around the ethical implications of storing webmentions, led by <a href=\"https://sebastiangreger.net/\">Sebastian</a>, based on <a href=\"https://sebastiangreger.net/2018/05/indieweb-privacy-challenge-webmentions-backfeeds-gdpr/\">his epic post from earlier this year</a>.</p>\n\n<p>We established early in the discussion that we weren\u2019t going to try to solve legal questions\u2014like GDPR \u201ccompliance\u201d, which varies depending on which lawyer you talk to\u2014but rather try to figure out what the right thing to do is.</p>\n\n<p>Earlier that day, during the introductions, I quite happily showed webmentions in action on my site. I pointed out that <a href=\"https://adactio.com/journal/14452\">my last blog post</a> had received <a href=\"https://ruk.ca/content/phil-nash-and-jeremy-keith-save-safari-video-playback-day\">a response from another site</a>, and because that response was marked up as an <a href=\"http://microformats.org/wiki/h-entry\">h-entry</a>, I <a href=\"https://adactio.com/journal/14452#comment64243\">displayed it in full on my site</a>. I thought this was all hunky-dory, but now this discussion around privacy made me question some inferences I was making:</p>\n\n<ol><li>By receiving a webention in the first place, I was inferring a willingness for the link to be made public. That\u2019s not necessarily true, as someone pointed out: a CMS could be automatically sending webmentions, which the author might be unaware of.</li>\n<li>If the linking post is marked up in h-entry, I was inferring a willingness for the content to be republished. Again, not necessarily true.</li>\n</ol><p>That second inferrence of mine\u2014that publishing in a particular format somehow grants permissions\u2014actually has an interesting precedent: <a href=\"https://www.ampproject.org/\">Google AMP</a>. Simply by including the Google AMP script on a web page, <a href=\"https://www.ampproject.org/support/faqs/overview#42_AMP_content_5\">you are implicitly giving Google permission</a> to store a complete copy of that page <strong>and</strong> serve it from their servers instead of sending people to your site. No terms and conditions. No checkbox ticked. No \u201cI agree\u201d button pressed.</p>\n\n<p>Just sayin\u2019.</p>\n\n<p>Anyway, when it comes to my own processing of webmentions, I\u2019m going to take some of the suggestions from the discussion on board. There are certain signals I could be looking for in the linking post:</p>\n\n<ul><li>Does it include a link to a licence?</li>\n<li>Is there a restrictive <code>robots.txt</code> file?</li>\n<li>Are there <code>meta</code> declarations that say <code>noindex</code>?</li>\n</ul><p>Each one of these could help to infer whether or not I should be publishing a webmention or not. I quickly realised that what we\u2019re talking about here is an algorithm.</p>\n\n<p>Despite its current usage to mean \u201cmagic\u201d, an algorithm is a recipe. It\u2019s a series of steps that contribute to a decision point. The problem is that, in the case of silos like Facebook or Instagram, the algorithms are secret (which probably contributes to their aura of magical thinking). If I\u2019m going to write an algorithm that handles other people\u2019s information, I don\u2019t want to make that mistake. Whatever steps I end up codifying in my webmention endpoint, I\u2019ll be sure to document them publicly.</p>" }, "author": { "type": "card", "name": "Jeremy Keith", "url": "https://adactio.com/", "photo": "https://aperture-proxy.p3k.io/bbbacdf0a064621004f2ce9026a1202a5f3433e0/68747470733a2f2f6164616374696f2e636f6d2f696d616765732f70686f746f2d3135302e6a7067" }, "post-type": "article", "_id": "1391009", "_source": "2", "_is_read": true }
{ "type": "entry", "author": { "name": null, "url": "https://www.manton.org/", "photo": null }, "url": "https://www.manton.org/2018/11/09/dates-set-for.html", "name": "Dates set for IndieWebCamp Austin", "content": { "html": "<p>Last year we held the first IndieWebCamp in Austin. <a href=\"https://www.manton.org/2017/12/12/indiewebcamp-austin-wrapup.html\">I blogged about</a> how the event went, including sessions and topics covered, and what I learned from it:</p>\n\n<blockquote>\n <p>I\u2019m really happy with the way the event came together. I learned a lot in helping plan it, made a few mistakes that we can improve next time, but overall came away as inspired as ever to keep improving Micro.blog so that it\u2019s a standout platform of the IndieWeb movement.</p>\n</blockquote>\n\n<p>We\u2019re going to do it again early next year: February 23-24 at Capital Factory. If you\u2019ll be in Austin, mark that weekend on your calendar. I\u2019ll make another announcement when registration is open.</p>\n\n<p>Last year there was a lot of work and last-minute stress deciding on a venue, so this year we\u2019re going to keep a bunch of things the same that seemed to work pretty well. By nailing down the details early, we\u2019ll have much more time for promotion. I\u2019d love to see us double attendance from last year. Hope to see you there!</p>", "text": "Last year we held the first IndieWebCamp in Austin. I blogged about how the event went, including sessions and topics covered, and what I learned from it:\n\n\n I\u2019m really happy with the way the event came together. I learned a lot in helping plan it, made a few mistakes that we can improve next time, but overall came away as inspired as ever to keep improving Micro.blog so that it\u2019s a standout platform of the IndieWeb movement.\n\n\nWe\u2019re going to do it again early next year: February 23-24 at Capital Factory. If you\u2019ll be in Austin, mark that weekend on your calendar. I\u2019ll make another announcement when registration is open.\n\nLast year there was a lot of work and last-minute stress deciding on a venue, so this year we\u2019re going to keep a bunch of things the same that seemed to work pretty well. By nailing down the details early, we\u2019ll have much more time for promotion. I\u2019d love to see us double attendance from last year. Hope to see you there!" }, "published": "2018-11-09T10:30:24-06:00", "post-type": "article", "_id": "1385747", "_source": "12", "_is_read": true }
{ "type": "entry", "published": "2018-11-08 23:13-0800", "url": "https://gregorlove.com/2018/11/recap-of-an-introduction-to-microformats/", "name": "Recap of An Introduction to Microformats", "content": { "text": "I gave a talk on microformats last night at the San Diego PHP Meetup group. This was my first time giving a formal talk on the topic. I appreciate SDPHP giving me the opportunity!\n\nResources\n\n\nAn Introduction to Microformats slides. I added microformats to them because, of course. :]\n\t\nmicroformats.io: A brief introduction to microformats and links to several parsers. Includes web versions so you can try parsing microformats by direct HTML input or by entering a URL.\n\t\nmicroformats.org wiki: the microformats specifications and the parsing algorithm. I touched on some specific pages: principles, h-card, h-event, microformats2\n\n\t\nmf2-to-iCalendar: a small PHP library that converts h-event microformats to iCalendar. This is what I use on my own calendar page.\n\t\nWebmention: a specification I mentioned that enables cross-site commenting. Many receivers of webmention will parse h-card and h-entry microformats to display responses.\n\t\nMicropub: a specification for publishing using third-party clients. It uses the microformats vocabulary.\n\t\nphp-mf2: PHP parser library for microformats2\n\t\nGranary: a library and web service to convert between multiple formats, including microformats2\n\t\nXRay: a library and web service to parse structured content, including microformats2\nSome people were really interested in webmention and we ended up talking about it more after the event. I recommended Aaron's tutorial: Sending your First Webmention from Scratch. Schema.org also came up in that conversation and I mentioned Aaron's other post that documents the history of Google's recommended formats. They've changed a lot over the years, but they still parse microformats1 hReview.\n\nNotes\n\nThese are mostly notes to myself, or other potential presenters.\n\nI forgot to demo one of my favorite uses for microformats. I use the Granary web service to parse the h-feed on my notes page and generate an Atom feed: https://gregorlove.com/notes.atom\n\nI think it would help to discuss use-cases earlier in the talk. Through the questions and some discussion after the talk, I realized that wasn't fully clicking for people earlier in the talk.\n\nThe live demo of the parser went okay, though I should have planned out some more examples for the direct HTML input instead of trying to come up with it off the top of my head. It would also be good to have some examples that emphasize the flexibility of microformats placement. We discussed that point, but a visual example would be better.\n\nBe prepared to discuss schema.org, alternatives, pros and cons. I think I did okay with this. I chose to focus on the interesting consuming use-cases for microformats.", "html": "<p>I gave a talk on microformats last night at the <a href=\"http://sdphp.org\">San Diego PHP Meetup group</a>. This was my first time giving a formal talk on the topic. I appreciate SDPHP giving me the opportunity!</p>\n\n<h2>Resources</h2>\n\n<ul><li>\n<i><a href=\"https://gregorlove.com/presentations/an-introduction-to-microformats/\">An Introduction to Microformats</a></i> slides. I added microformats to them because, of course. :]</li>\n\t<li>\n<a href=\"https://microformats.io\">microformats.io</a>: A brief introduction to microformats and links to several parsers. Includes web versions so you can try parsing microformats by direct HTML input or by entering a URL.</li>\n\t<li>\n<a href=\"http://microformats.org/wiki/\">microformats.org wiki</a>: the microformats specifications and the parsing algorithm. I touched on some specific pages: <a href=\"http://microformats.org/wiki/principles\">principles</a>, <a href=\"http://microformats.org/wiki/h-card\">h-card</a>, <a href=\"http://microformats.org/wiki/h-event\">h-event</a>, <a href=\"http://microformats.org/wiki/microformats2\">microformats2</a>\n</li>\n\t<li>\n<a href=\"https://github.com/gRegorLove/mf2-to-iCalendar\">mf2-to-iCalendar</a>: a small PHP library that converts h-event microformats to iCalendar. This is what I use on my own <a href=\"https://gregorlove.com/calendar/\">calendar</a> page.</li>\n\t<li>\n<a href=\"https://webmention.net\">Webmention</a>: a specification I mentioned that enables cross-site commenting. Many receivers of webmention will parse h-card and h-entry microformats to display responses.</li>\n\t<li>\n<a href=\"https://micropub.net\">Micropub</a>: a specification for publishing using third-party clients. It uses the microformats vocabulary.</li>\n\t<li>\n<a href=\"https://github.com/microformats/php-mf2\">php-mf2</a>: PHP parser library for microformats2</li>\n\t<li>\n<a href=\"https://granary.io\">Granary</a>: a library and web service to convert between multiple formats, including microformats2</li>\n\t<li>\n<a href=\"https://xray.p3k.app\">XRay</a>: a library and web service to parse structured content, including microformats2</li>\n</ul><p>Some people were really interested in webmention and we ended up talking about it more after the event. I recommended Aaron's tutorial: <i><a href=\"https://aaronparecki.com/2018/06/30/11/your-first-webmention\">Sending your First Webmention from Scratch</a></i>. <a href=\"https://schema.org\">Schema.org</a> also came up in that conversation and I mentioned Aaron's <a href=\"https://aaronparecki.com/2016/12/17/8/owning-my-reviews\">other post</a> that documents the history of Google's recommended formats. They've changed a lot over the years, but they still parse microformats1 <a href=\"http://microformats.org/wiki/hreview\">hReview</a>.</p>\n\n<h2>Notes</h2>\n\n<p>These are mostly notes to myself, or other potential presenters.</p>\n\n<p>I forgot to demo one of my favorite uses for microformats. I use the Granary web service to parse the h-feed on my <a href=\"https://gregorlove.com/notes/\">notes</a> page and generate an Atom feed: <a href=\"https://gregorlove.com/notes.atom\">https://gregorlove.com/notes.atom</a></p>\n\n<p>I think it would help to discuss use-cases earlier in the talk. Through the questions and some discussion after the talk, I realized that wasn't fully clicking for people earlier in the talk.</p>\n\n<p>The live demo of the parser went okay, though I should have planned out some more examples for the direct HTML input instead of trying to come up with it off the top of my head. It would also be good to have some examples that emphasize the flexibility of microformats placement. We discussed that point, but a visual example would be better.</p>\n\n<p>Be prepared to discuss schema.org, alternatives, pros and cons. I think I did okay with this. I chose to focus on the interesting consuming use-cases for microformats.</p>" }, "post-type": "article", "_id": "1382681", "_source": "179", "_is_read": true }
{ "type": "entry", "published": "2018-11-08T14:38:50-08:00", "url": "https://aaronparecki.com/2018/11/08/11/", "category": [ "https://indiewebcat.com/" ], "photo": [ "https://aperture-media.p3k.io/aaronparecki.com/ecef22a0a4f7891c4445813f307d0937d31b4661bfbc30c4496887d6e0309bdd.jpg" ], "video": [ "https://aperture-media.p3k.io/aaronparecki.com/da5aa6fcbe8c973178f77fc187827c8c6d6d4305361759eafdf018d20207c4c3.mp4" ], "syndication": [ "https://www.instagram.com/p/Bp77Zo-gm6h/" ], "content": { "text": "Look at @indiewebcat mostly enjoying the ride to the vet \ud83d\ude3b\ud83d\udc89", "html": "Look at <a href=\"https://twitter.com/indiewebcat\">@indiewebcat</a> mostly enjoying the ride to the vet <a href=\"https://aaronparecki.com/emoji/%F0%9F%98%BB\">\ud83d\ude3b</a><a href=\"https://aaronparecki.com/emoji/%F0%9F%92%89\">\ud83d\udc89</a>" }, "author": { "type": "card", "name": "Aaron Parecki", "url": "https://aaronparecki.com/", "photo": "https://aperture-media.p3k.io/aaronparecki.com/2b8e1668dcd9cfa6a170b3724df740695f73a15c2a825962fd0a0967ec11ecdc.jpg" }, "post-type": "video", "_id": "1380320", "_source": "16", "_is_read": true }
#IndieWebSummit party time
{ "type": "entry", "published": "2018-06-26T00:10:02.185Z", "url": "https://grant.codes/2018/06/26/5b31845ac1f2f0162199a45d-1", "photo": [ "https://aperture-proxy.p3k.io/fa46e496d59866a2dc5253737ebe411d94008c86/68747470733a2f2f6772616e742e636f6465732f" ], "syndication": [ "https://twitter.com/grantcodes/status/1011401165551087616", "https://www.instagram.com/p/Bkd53a7l0Lf/", "https://www.facebook.com/2307321132619023" ], "content": { "text": "#IndieWebSummit party time", "html": "<p>#IndieWebSummit party time</p>" }, "author": { "type": "card", "name": "Grant Richmond", "url": "https://grant.codes", "photo": "https://aperture-proxy.p3k.io/be31049af9884a65289b2d14300adafc0e4030c6/68747470733a2f2f6772616e742e636f6465732f696d672f6d652e6a7067" }, "post-type": "photo", "_id": "1380142", "_source": "11", "_is_read": true }
{ "type": "entry", "author": { "name": null, "url": "https://www.manton.org/", "photo": null }, "url": "https://www.manton.org/2018/11/08/usernames-on-microblog.html", "name": "Usernames on Micro.blog", "content": { "html": "<p><a href=\"https://micro.blog/\">Micro.blog</a> now has 3 distinct styles of usernames to make the platform more compatible with other services:</p>\n\n<ul><li>Micro.blog usernames, e.g. <strong>@you</strong>. These are simple usernames for @-mentioning someone else in the Micro.blog community.</li>\n <li>Mastodon usernames, e.g. <strong>@you@yourdomain.com</strong>. When you search Micro.blog for these usernames, Micro.blog will look for the user in another federated Mastodon instance so that you can follow them.</li>\n <li>IndieWeb-friendly domain names, e.g. <strong>@yourdomain.com</strong>. This is where I always thought we\u2019d go for more distributed \u201cthe web is the social network\u201d interactions. Replying to one of these usernames will send a Webmention to that user\u2019s external web site.</li>\n</ul><p>I\u2019m <a href=\"https://micro.blog/manton\">@manton</a> on Micro.blog, my blog is <a href=\"https://manton.org/\">manton.org</a>, and because <a href=\"https://manton.org/2018/11/07/microblog-mastodon.html\">Micro.blog-hosted blogs support the ActivityPub API</a>, you can follow me from Mastodon by using @manton@manton.org.</p>\n\n<p>I just rolled out some ActivityPub-related reply improvements. I\u2019ll continue to improve the Micro.blog timeline so that these usernames can all coexist nicely. We\u2019ll also be updating the native apps to better support these usernames, for highlighting and auto-completion.</p>", "text": "Micro.blog now has 3 distinct styles of usernames to make the platform more compatible with other services:\n\nMicro.blog usernames, e.g. @you. These are simple usernames for @-mentioning someone else in the Micro.blog community.\n Mastodon usernames, e.g. @you@yourdomain.com. When you search Micro.blog for these usernames, Micro.blog will look for the user in another federated Mastodon instance so that you can follow them.\n IndieWeb-friendly domain names, e.g. @yourdomain.com. This is where I always thought we\u2019d go for more distributed \u201cthe web is the social network\u201d interactions. Replying to one of these usernames will send a Webmention to that user\u2019s external web site.\nI\u2019m @manton on Micro.blog, my blog is manton.org, and because Micro.blog-hosted blogs support the ActivityPub API, you can follow me from Mastodon by using @manton@manton.org.\n\nI just rolled out some ActivityPub-related reply improvements. I\u2019ll continue to improve the Micro.blog timeline so that these usernames can all coexist nicely. We\u2019ll also be updating the native apps to better support these usernames, for highlighting and auto-completion." }, "published": "2018-11-08T15:32:39-06:00", "post-type": "article", "_id": "1379552", "_source": "12", "_is_read": true }
Publishing on the web really is quite marvellous:
…an endless thrill, a sort of everlasting, punk-rock feeling and I hope it will never really go away.
{ "type": "entry", "published": "2018-11-08T08:26:07Z", "url": "https://adactio.com/links/14498", "category": [ "writing", "sharing", "publishing", "indieweb", "communication", "connection", "text" ], "bookmark-of": [ "https://robinrendle.com/notes/what-do-you-want-to-do-when-you-grow-up-kid/" ], "content": { "text": "What do you want to do when you grow up, kid? \u2022 Robin Rendle\n\n\n\nPublishing on the web really is quite marvellous:\n\n\n \u2026an endless thrill, a sort of everlasting, punk-rock feeling and I hope it will never really go away.", "html": "<h3>\n<a class=\"p-name u-bookmark-of\" href=\"https://robinrendle.com/notes/what-do-you-want-to-do-when-you-grow-up-kid/\">\nWhat do you want to do when you grow up, kid? \u2022 Robin Rendle\n</a>\n</h3>\n\n<p>Publishing on the web really is quite marvellous:</p>\n\n<blockquote>\n <p>\u2026an endless thrill, a sort of everlasting, punk-rock feeling and I hope it will never really go away.</p>\n</blockquote>" }, "post-type": "bookmark", "_id": "1373429", "_source": "2", "_is_read": true }