<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Push notifications on backd</title><link>https://fernandezvara.github.io/backd/docs/push/</link><description>Recent content in Push notifications on backd</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://fernandezvara.github.io/backd/docs/push/index.xml" rel="self" type="application/rss+xml"/><item><title>Devices</title><link>https://fernandezvara.github.io/backd/docs/push/devices/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/push/devices/</guid><description>&lt;p&gt;A &lt;strong&gt;device&lt;/strong&gt; is one app installation (or one browser) that can receive pushes for a user. Apps register it with &lt;code&gt;POST /_auth/devices&lt;/code&gt;; backd owns the registry, so it can move tokens between users, drop them when a session ends and clean up after erasure, which an app&amp;rsquo;s own collection can&amp;rsquo;t.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -X POST &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$BACKD&lt;/span&gt;&lt;span class="s2"&gt;/v1/acme/_auth/devices&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s2"&gt;&amp;#34;Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -H &lt;span class="s1"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;platform&amp;#34;: &amp;#34;android&amp;#34;, &amp;#34;token&amp;#34;: &amp;#34;&amp;lt;FCM registration token&amp;gt;&amp;#34;, &amp;#34;name&amp;#34;: &amp;#34;Ana&amp;#39;&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#39;&amp;#34;&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s Pixel&amp;#34;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;d3c9ljp8hc2g00b6s1m0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;platform&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;android&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Ana&amp;#39;s Pixel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;created_at&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;…&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;last_seen&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;…&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-endpoints"&gt;The endpoints&lt;a class="hd-anchor" href="#the-endpoints" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;All of these need a &lt;strong&gt;user session&lt;/strong&gt;: an API key (or &lt;code&gt;X-Backd-On-Behalf-Of&lt;/code&gt;) can&amp;rsquo;t register devices.&lt;/p&gt;</description></item><item><title>Sending pushes</title><link>https://fernandezvara.github.io/backd/docs/push/sending/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/push/sending/</guid><description>&lt;p&gt;A function that declares &lt;a href="../../functions/reference/"&gt;&lt;code&gt;push&lt;/code&gt;&lt;/a&gt; in its &lt;code&gt;function.yaml&lt;/code&gt; has &lt;code&gt;ctx.push.send&lt;/code&gt;. The call answers at once; backd&amp;rsquo;s workers deliver.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;user&lt;/span&gt;: &lt;span class="kt"&gt;order.owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or users: [...], or audience (below)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;order-shipped&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// a template of the realm, or title + body (raw)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;order_no&lt;/span&gt;: &lt;span class="kt"&gt;order.no&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eta&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tomorrow&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`order-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;high&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// r = { send_id: &amp;#34;ps_…&amp;#34;, users: 1, jobs: 1, queued: 1, limited: 0, no_devices: 0 }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# function.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;push: true # or push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kinds: [order-shipped], audience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="who"&gt;Who&lt;a class="hd-anchor" href="#who" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Give exactly one of:&lt;/p&gt;</description></item><item><title>Records, test send and metrics</title><link>https://fernandezvara.github.io/backd/docs/push/records/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/push/records/</guid><description>&lt;h2 id="the-record-of-a-send"&gt;The record of a send&lt;a class="hd-anchor" href="#the-record-of-a-send" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Every send leaves one record, kept for &lt;code&gt;push.send_retention&lt;/code&gt; (30 days by default). It holds &lt;strong&gt;counts, never content&lt;/strong&gt;: no title, body, data or token, and no user ids (those live only in the send&amp;rsquo;s jobs, which follow the realm&amp;rsquo;s &lt;a href="../../functions/jobs/"&gt;job retention&lt;/a&gt;).&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Field&lt;/th&gt;
 &lt;th&gt;Meaning&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;kind&lt;/code&gt;, &lt;code&gt;sender&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;The send, its template kind (&lt;code&gt;raw&lt;/code&gt; for a message the function wrote), who sent it (&lt;code&gt;function:&amp;lt;database&amp;gt;/&amp;lt;name&amp;gt;&lt;/code&gt; or &lt;code&gt;admin:&amp;lt;actor&amp;gt;&lt;/code&gt;) and when&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;users&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;How many users were named; &lt;code&gt;null&lt;/code&gt; for an audience&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;queued&lt;/code&gt;, &lt;code&gt;limited&lt;/code&gt;, &lt;code&gt;no_devices&lt;/code&gt;, &lt;code&gt;no_consent&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Users handed to delivery, users over their limit, users with no device to reach (a silent push skips browsers), users who don&amp;rsquo;t hold the &lt;a href="../../auth/consents/"&gt;consent&lt;/a&gt; the send requires&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;sent&lt;/code&gt;, &lt;code&gt;invalid&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Devices the provider accepted, devices removed as dead, devices not delivered to&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;planned&lt;/code&gt;, &lt;code&gt;batches&lt;/code&gt;, &lt;code&gt;batches_done&lt;/code&gt;, &lt;code&gt;units&lt;/code&gt;, &lt;code&gt;units_done&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;How far the work is&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;completed&lt;/code&gt;, &lt;code&gt;completed_at&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Everything is done&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;backd push sends --realm acme &lt;span class="c1"&gt;# the latest, newest first&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;backd push sends --realm acme --kind order-shipped
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;backd push sends --realm acme --function main/notify --limit &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;backd push sends --realm acme --id ps_d3c9ljp8hc2g00b6s1m0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The admin interface shows the same on its &lt;strong&gt;Push&lt;/strong&gt; page (&lt;a href="../../auth/admin-ui/#push-notifications"&gt;Admin interface&lt;/a&gt;). The admin API: &lt;code&gt;GET /_admin/push&lt;/code&gt; (the configuration state and the secrets still missing), &lt;code&gt;GET /_admin/push/sends&lt;/code&gt; (filters &lt;code&gt;kind&lt;/code&gt;, &lt;code&gt;function&lt;/code&gt;, &lt;code&gt;since&lt;/code&gt;, &lt;code&gt;until&lt;/code&gt;, &lt;code&gt;limit&lt;/code&gt;, &lt;code&gt;skip&lt;/code&gt;) and &lt;code&gt;GET /_admin/push/sends/{id}&lt;/code&gt;. Both need the &lt;code&gt;functions&lt;/code&gt; area, and read-only administrators may use them.&lt;/p&gt;</description></item><item><title>Local development</title><link>https://fernandezvara.github.io/backd/docs/push/local/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/push/local/</guid><description>&lt;p&gt;You don&amp;rsquo;t need a device or a provider key to develop with push.&lt;/p&gt;
&lt;h2 id="the-capture-provider"&gt;The capture provider&lt;a class="hd-anchor" href="#the-capture-provider" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Give a platform &lt;code&gt;provider: capture&lt;/code&gt;: the worker then &lt;strong&gt;stores each delivery&lt;/strong&gt; in a &lt;code&gt;push_outbox&lt;/code&gt; collection of the realm&amp;rsquo;s system database instead of calling a provider, with the message as it would have gone out and the outcome the device was given.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;android&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;capture }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;capture&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It needs no credentials and no secrets, but it stores message content, so &lt;strong&gt;startup refuses it unless &lt;code&gt;BACKD_DEV=true&lt;/code&gt;&lt;/strong&gt;. Captured deliveries are kept for 24 hours.&lt;/p&gt;</description></item><item><title>FCM: Android, and iOS apps on the FCM SDK</title><link>https://fernandezvara.github.io/backd/docs/push/fcm/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/push/fcm/</guid><description>&lt;p&gt;FCM is the provider for &lt;strong&gt;Android&lt;/strong&gt;, and for &lt;strong&gt;iOS apps that use the FCM SDK&lt;/strong&gt; (&lt;code&gt;ios.provider: fcm&lt;/code&gt;). backd sends through FCM&amp;rsquo;s HTTP v1 API from the worker, with an OAuth2 access token it mints from a &lt;strong&gt;service account&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="1-a-firebase-project"&gt;1. A Firebase project&lt;a class="hd-anchor" href="#1-a-firebase-project" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://console.firebase.google.com"&gt;Firebase console&lt;/a&gt; and create a project, or pick the one your app uses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Android:&lt;/strong&gt; add an Android app with your application id (the package name). Download &lt;code&gt;google-services.json&lt;/code&gt; for the app&amp;rsquo;s build.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;iOS over FCM:&lt;/strong&gt; add an iOS app with the bundle id, download &lt;code&gt;GoogleService-Info.plist&lt;/code&gt;, and upload an &lt;strong&gt;APNs authentication key&lt;/strong&gt; under &lt;em&gt;Project settings → Cloud Messaging → Apple app configuration&lt;/em&gt; (see &lt;a href="../apns/#1-the-key"&gt;APNs&lt;/a&gt; for where it comes from). That key lives in Firebase: backd never sees it.&lt;/li&gt;
&lt;li&gt;In the linked Google Cloud project, enable the &lt;strong&gt;Firebase Cloud Messaging API (V1)&lt;/strong&gt; (&lt;em&gt;APIs &amp;amp; Services → Library&lt;/em&gt;). The legacy API is not used.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-a-service-account"&gt;2. A service account&lt;a class="hd-anchor" href="#2-a-service-account" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Project settings → Service accounts → Generate new private key.&lt;/em&gt; A JSON file downloads: it holds &lt;code&gt;project_id&lt;/code&gt;, &lt;code&gt;client_email&lt;/code&gt; and &lt;code&gt;private_key&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Store it as a realm secret, from the file:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;backd secret &lt;span class="nb"&gt;set&lt;/span&gt; --realm acme --name FCM_SERVICE_ACCOUNT &amp;lt; service-account.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The value is read from standard input, newlines included, and encrypted with the instance&amp;rsquo;s &lt;code&gt;BACKD_SECRETS_KEY&lt;/code&gt;. Delete the downloaded file afterwards.&lt;/p&gt;</description></item><item><title>APNs: iOS</title><link>https://fernandezvara.github.io/backd/docs/push/apns/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/push/apns/</guid><description>&lt;p&gt;&lt;code&gt;ios.provider: apns&lt;/code&gt; sends to iPhones and iPads directly through Apple&amp;rsquo;s push service: HTTP/2, authenticated with a &lt;strong&gt;token&lt;/strong&gt; signed by a key you create in your Apple Developer account. (Apps on the FCM SDK use &lt;a href="../fcm/"&gt;FCM&lt;/a&gt; instead.)&lt;/p&gt;
&lt;h2 id="1-the-key"&gt;1. The key&lt;a class="hd-anchor" href="#1-the-key" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;In the &lt;a href="https://developer.apple.com/account"&gt;Apple Developer account&lt;/a&gt;: &lt;em&gt;Certificates, Identifiers &amp;amp; Profiles → Keys → +&lt;/em&gt;. Name it, tick &lt;strong&gt;Apple Push Notifications service (APNs)&lt;/strong&gt;, and continue.&lt;/li&gt;
&lt;li&gt;Download the &lt;code&gt;.p8&lt;/code&gt; file: &lt;strong&gt;it can be downloaded once&lt;/strong&gt;. Note the 10-character &lt;strong&gt;Key ID&lt;/strong&gt; shown.&lt;/li&gt;
&lt;li&gt;Note your &lt;strong&gt;Team ID&lt;/strong&gt;: the 10 characters at the top right of the account&amp;rsquo;s &lt;em&gt;Membership details&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;topic&lt;/strong&gt; is your app&amp;rsquo;s bundle id (for example &lt;code&gt;com.acme.app&lt;/code&gt;). The app&amp;rsquo;s &lt;em&gt;App ID&lt;/em&gt; must have the &lt;strong&gt;Push Notifications&lt;/strong&gt; capability enabled.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-the-secret"&gt;2. The secret&lt;a class="hd-anchor" href="#2-the-secret" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;backd secret &lt;span class="nb"&gt;set&lt;/span&gt; --realm acme --name APNS_KEY &amp;lt; AuthKey_AB12CD34EF.p8
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The file is read as it is, newlines included.&lt;/p&gt;</description></item><item><title>Web Push: browsers</title><link>https://fernandezvara.github.io/backd/docs/push/web-push/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/push/web-push/</guid><description>&lt;p&gt;&lt;code&gt;web.provider: webpush&lt;/code&gt; sends to browsers with the standard Web Push protocol: no account with a vendor, only a &lt;strong&gt;VAPID key pair&lt;/strong&gt; that identifies your realm to the browsers&amp;rsquo; push services (Google, Mozilla, Apple, Microsoft).&lt;/p&gt;
&lt;h2 id="1-the-key-pair"&gt;1. The key pair&lt;a class="hd-anchor" href="#1-the-key-pair" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;backd push web-keys --realm acme
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It generates a P-256 key pair, stores the &lt;strong&gt;private&lt;/strong&gt; half as the realm secret &lt;code&gt;VAPID_PRIVATE_KEY&lt;/code&gt;, and prints the &lt;strong&gt;public&lt;/strong&gt; half with the &lt;code&gt;realm.yaml&lt;/code&gt; snippet:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;push:
 web:
 provider: webpush
 public_key: BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U
 private_key: secret:VAPID_PRIVATE_KEY
 subject: secret:VAPID_SUBJECT
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="2-the-subject"&gt;2. The subject&lt;a class="hd-anchor" href="#2-the-subject" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Browsers&amp;rsquo; push services use a contact URI to reach you when something goes wrong with your pushes:&lt;/p&gt;</description></item><item><title>From apps: the JavaScript client</title><link>https://fernandezvara.github.io/backd/docs/push/client/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/push/client/</guid><description>&lt;p&gt;The &lt;a href="../../clients/js/"&gt;JavaScript client&lt;/a&gt; has &lt;code&gt;client.push&lt;/code&gt;, a small layer over the &lt;a href="../devices/"&gt;device endpoints&lt;/a&gt;. It needs a &lt;strong&gt;user session&lt;/strong&gt;: an API key can&amp;rsquo;t register devices.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;backd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;android&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fcmToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Ana&amp;#39;s Pixel&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;backd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// [{ id, platform, name, created_at, last_seen }] (never a token)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;backd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unregister&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// the device this client registered; or unregister(id)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Call&lt;/th&gt;
 &lt;th&gt;What it does&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;register({ platform, token, name? })&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;POST /_auth/devices&lt;/code&gt;. Registers or refreshes; keeps the returned id (&lt;code&gt;client.push.deviceId&lt;/code&gt;) for &lt;code&gt;unregister()&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;devices()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;GET /_auth/devices&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;unregister(id?)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;DELETE /_auth/devices/{id}&lt;/code&gt;; without an id, the device this client registered (&lt;code&gt;false&lt;/code&gt; when there is none)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;registerOnLaunch(getToken, { onError })&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Registers &lt;strong&gt;now&lt;/strong&gt; if signed in and again &lt;strong&gt;after every sign-in&lt;/strong&gt;. &lt;code&gt;getToken&lt;/code&gt; returns what to register, or &lt;code&gt;null&lt;/code&gt; while the app has no token. Returns a function that stops it&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;subscribeWeb({ registration, name?, requestPermission? })&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Subscribes a browser and registers the subscription (below)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="keeping-a-device-registered"&gt;Keeping a device registered&lt;a class="hd-anchor" href="#keeping-a-device-registered" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;A device is removed when its session ends, when it is unseen for &lt;code&gt;push.devices.stale_after&lt;/code&gt;, and when its realm&amp;rsquo;s web key is rotated. So an app registers &lt;strong&gt;at every launch and after every sign-in&lt;/strong&gt;, which is what &lt;code&gt;registerOnLaunch&lt;/code&gt; does:&lt;/p&gt;</description></item></channel></rss>