<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://jimmy.warting.se/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jimmy.warting.se/" rel="alternate" type="text/html" /><updated>2026-05-23T11:01:58+00:00</updated><id>https://jimmy.warting.se/feed.xml</id><title type="html">Endless</title><subtitle>I code, I like p2p, anything that has to do with IPFS, WebTorrent and WebRTC. decentralizing the web is grate. Serverless can also be cool.
</subtitle><author><name>Jimmy Wärting</name><email>jimmy@warting.se</email></author><entry><title type="html">Establish WebRTC connection without a signaling server using nothing but Web Push</title><link href="https://jimmy.warting.se/2021/02/16/p2p-signal-with-webpush.html" rel="alternate" type="text/html" title="Establish WebRTC connection without a signaling server using nothing but Web Push" /><published>2021-02-16T00:00:00+00:00</published><updated>2021-02-16T00:00:00+00:00</updated><id>https://jimmy.warting.se/2021/02/16/p2p-signal-with-webpush</id><content type="html" xml:base="https://jimmy.warting.se/2021/02/16/p2p-signal-with-webpush.html"><![CDATA[<p>Serverless technologies are great. And when you need to chat/talk to one or another, then you can do so directly with WebRTC. But what sucks is that you have to signal offer &amp; answers back and forth using a server.</p>

<p>Imagine if you didn’t have to use a server or some external third party WebSocket service? This is what I have attempted to solve using nothing but Web Push as a signaling technique.</p>

<details>
  <summary>What I will go through</summary>
  <blockquote><ul>
    <li>How you can use Web Push as a kind of "phone number" that you can "call" too</li>
    <li>Setting up Service Worker to listen to push messages and broadcast them to all open tabs</li>
    <li>Generating a VAPID public/private key</li>
    <li>Using the keys to subscribe and encrypt payloads</li>
  </ul></blockquote>
</details>

<details>
  <summary>What I won't be cover</summary>
  <blockquote><ul>
    <li>How to best request for notification permission and dealing with rejection.</li>
    <li>How to get the other person's subscription</li>
    <li>Having an "answer" or "hang up" option to choose from.</li>
    <li>Showing any desktop notification and reacting to click event. (Yes, we are going to send silent push)</li>
    <li>Or how to forward the push payload to the correct tab that is setting up the WebRTC peer connection</li>
    <li>In this case it will be best that you only have one tab open on two browsers.</li>
  </ul>
  
  This will mostly just only focus on just establish a p2p connection with WebRTC
  </blockquote>
</details>

<p><br /></p>

<p>First thing first. You are going to need a service worker that can handle push events<br />
Create a service worker and add the required event.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// service-worker.js</span>
<span class="nx">globalThis</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">push</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="nx">evt</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// Read the push message that was sent</span>
  <span class="kd">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="nx">evt</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">json</span><span class="p">()</span>

  <span class="c1">// Broadcast the message to all tabs</span>
  <span class="nx">globalThis</span><span class="p">.</span><span class="nx">clients</span><span class="p">.</span><span class="nx">matchAll</span><span class="p">().</span><span class="nx">then</span><span class="p">(</span><span class="nx">clients</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">clients</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">client</span> <span class="o">=&gt;</span> <span class="nx">client</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">(</span><span class="nx">payload</span><span class="p">))</span>
  <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Then in our main thread we will need two libraries to help bootstrap the application</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// main.js</span>
<span class="k">import</span> <span class="nx">Peer</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">https://jimmy.warting.se/packages/webrtc/perfect-negotiation.js</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">EncryptionHelperAES128GCM</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">https://jimmy.warting.se/packages/webpush/encryption-aes128gcm.js</span><span class="dl">'</span>

<span class="cm">/*
  We are going to need some public/private keys to subscribe and encrypt messages
  you can generate your own with this:

  import('https://jimmy.warting.se/packages/webpush/encryption-aes128gcm.js')
    .then(m =&gt; m.default.generateB64ServerKeys())
    .then(JSON.stringify)
    .then(prompt.bind(null, 'Here is your keys'))
*/</span>

<span class="kd">const</span> <span class="nx">APPLICATION_KEYS</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">publicKey</span><span class="p">:</span> <span class="dl">'</span><span class="s1">BLZJC...</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">privateKey</span><span class="p">:</span> <span class="dl">'</span><span class="s1">AhGCH...</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}</span>

<span class="p">...</span>
</code></pre></div></div>
<p>Then we are going to</p>
<ul>
  <li>Initiate the encryption helper with our keys</li>
  <li>Set up the local peer</li>
  <li>And install the service worker</li>
</ul>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span>

<span class="kd">const</span> <span class="nx">encryptionHelper</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">EncryptionHelperAES128GCM</span><span class="p">({</span>
  <span class="na">vapidKeys</span><span class="p">:</span> <span class="nx">APPLICATION_KEYS</span><span class="p">,</span>
  <span class="c1">// contact information for push service to contact you</span>
  <span class="c1">// in case of problem. It's either a mailto: or https: link</span>
  <span class="na">subject</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://jimmy.warting.se</span><span class="dl">'</span>
<span class="p">})</span>

<span class="cm">/**
 * @params {PushSubsription|object} subscription
 * @params {string} text
 */</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">sendPushMessage</span> <span class="p">(</span><span class="nx">subscription</span><span class="p">,</span> <span class="nx">text</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">uint8</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TextEncoder</span><span class="p">().</span><span class="nx">encode</span><span class="p">(</span><span class="nx">text</span><span class="p">)</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">'</span><span class="s1">Sending push message</span><span class="dl">'</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">text</span><span class="p">))</span>

  <span class="c1">// Returns an array that can be passed to fetch as arguments</span>
  <span class="c1">// Or reconstructed to work with xhr, axios, or whatever</span>
  <span class="c1">// Can also be used for sending the request through a CORS proxy</span>
  <span class="kd">const</span> <span class="nx">request</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">encryptionHelper</span><span class="p">.</span><span class="nx">getRequestDetails</span><span class="p">(</span>
    <span class="nx">subscription</span><span class="p">,</span>
    <span class="nx">uint8</span>
  <span class="p">)</span>

  <span class="k">return</span> <span class="nx">fetch</span><span class="p">(...</span><span class="nx">request</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// we can create a WebRTC peer early so it have some time</span>
<span class="c1">// to gather ice candidates</span>
<span class="kd">const</span> <span class="nx">peer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Peer</span><span class="p">({</span>
  <span class="c1">// We want to send as few push messages as needed</span>
  <span class="c1">// since push messages is costly and have a quota</span>
  <span class="c1">// on how many push messages you can send</span>
  <span class="c1">// (specially when sending silent push)</span>
  <span class="na">trickle</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>

  <span class="c1">// Both are going to start out as polite but first person to</span>
  <span class="c1">// receive a push message isn't going to be polite</span>
  <span class="na">polite</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">})</span>

<span class="c1">// Install the service worker</span>
<span class="nb">navigator</span><span class="p">.</span><span class="nx">serviceWorker</span><span class="p">.</span><span class="nx">register</span><span class="p">(</span><span class="dl">'</span><span class="s1">/service-worker.js</span><span class="dl">'</span><span class="p">)</span>

<span class="c1">// Listen for when a service worker is broadcasting a push event</span>
<span class="nb">navigator</span><span class="p">.</span><span class="nx">serviceWorker</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">message</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="nx">evt</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="nx">evt</span><span class="p">.</span><span class="nx">data</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="na">subscription</span><span class="p">:</span> <span class="nx">caller</span><span class="p">,</span> <span class="p">...</span><span class="nx">sdp</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">payload</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">'</span><span class="s1">Received a push payload</span><span class="dl">'</span><span class="p">,</span> <span class="nx">payload</span><span class="p">)</span>

  <span class="c1">// The first signal (push) message is going to have push subscription</span>
  <span class="c1">// from the sender that we (the receiver) can use when sending back messages</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">caller</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">peer</span><span class="p">.</span><span class="nx">polite</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="c1">// Start listening for offer/answer signals</span>
    <span class="nx">peer</span><span class="p">.</span><span class="nx">signalingPort</span><span class="p">.</span><span class="nx">onmessage</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">data</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nx">sendPushMessage</span><span class="p">(</span><span class="nx">caller</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="c1">// Send the remaining sdp signal to the peer</span>
  <span class="nx">peer</span><span class="p">.</span><span class="nx">signalingPort</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">(</span><span class="nx">sdp</span><span class="p">)</span>
<span class="p">})</span>

<span class="p">...</span>
</code></pre></div></div>
<p>Now when the service worker, peer and encryption helper is set up all and done
We will need to set up call to action</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span>

<span class="nx">button</span><span class="p">.</span><span class="nx">onclick</span> <span class="o">=</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">Notification</span><span class="p">.</span><span class="nx">permission</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">granted</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nx">Notification</span><span class="p">.</span><span class="nx">requestPermission</span><span class="p">()</span>
  <span class="p">}</span>

  <span class="kd">const</span> <span class="nx">friendsSubscription</span> <span class="o">=</span> <span class="nx">prompt</span><span class="p">(</span>
    <span class="dl">'</span><span class="s1">Who would you like to call?</span><span class="dl">'</span><span class="p">,</span>
    <span class="nx">sessionStorage</span><span class="p">.</span><span class="nx">lastSubscription</span>
  <span class="p">)</span>

  <span class="nx">sessionStorage</span><span class="p">.</span><span class="nx">lastSubscription</span> <span class="o">=</span> <span class="nx">friendsSubscription</span>

  <span class="c1">// We are going to need our own subscription also</span>
  <span class="c1">// So we can tell our friend who is calling him and reply back</span>
  <span class="kd">let</span> <span class="nx">registration</span> <span class="o">=</span> <span class="k">await</span> <span class="nb">navigator</span><span class="p">.</span><span class="nx">serviceWorker</span><span class="p">.</span><span class="nx">ready</span>
  <span class="kd">let</span> <span class="nx">subscription</span> <span class="o">=</span>
    <span class="k">await</span> <span class="nx">registration</span><span class="p">.</span><span class="nx">pushManager</span><span class="p">.</span><span class="nx">getSubscription</span><span class="p">()</span> <span class="o">||</span>
    <span class="k">await</span> <span class="nx">registration</span><span class="p">.</span><span class="nx">pushManager</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">({</span>
      <span class="na">userVisibleOnly</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// a chrome requirement...</span>
      <span class="na">applicationServerKey</span><span class="p">:</span> <span class="nx">APPLICATION_KEYS</span><span class="p">.</span><span class="nx">publicKey</span>
    <span class="p">})</span>

  <span class="c1">// Now that we have two subscription we can begin talking to each other</span>
  <span class="nx">peer</span><span class="p">.</span><span class="nx">signalingPort</span><span class="p">.</span><span class="nx">onmessage</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">data</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="c1">// Embed our own push subscription into the first signal message</span>
    <span class="c1">// so that he or she can respond back</span>
    <span class="nx">data</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">subscription</span><span class="p">,</span> <span class="p">...</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">})</span>

    <span class="c1">// Now send it via web push</span>
    <span class="nx">sendPushMessage</span><span class="p">(</span>
      <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">friendsSubscription</span><span class="p">),</span>
      <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span>
    <span class="p">)</span>

    <span class="c1">// It could be useful to always send my own subscription.</span>
    <span class="c1">// or some other form of identification as long the receiver</span>
    <span class="c1">// have a id&lt;-&gt;subscription mapping but for the sake of simplicity</span>
    <span class="c1">// we will only deal with one p2p connection at the time</span>
    <span class="c1">// + a subscription can be quite lengthy and a push payload is farley</span>
    <span class="c1">// limited i guess. but i don't think the size is an issue.</span>
    <span class="nx">subscription</span> <span class="o">=</span> <span class="kc">undefined</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="demo-time">Demo Time</h3>

<pre id="yourSubscription"></pre>
<p><button hidden="" id="showMySubscription">Show my push subscription</button>
<button id="callButton">Call a friend</button>
<script type="module" src="/demos/webrtc-and-webpush/main.js"></script></p>

<h3 id="summary">summary</h3>

<p>Knowing the other persons subscription requires some form of syncing but it only need to happen once. After that you can push messages to the other person how often you want. You can store this subscription in localStorage or however you like. I store the other persons subscription in SessionsStorage, so it’s cleared when you close your browser. This is like a HTTP websocket upgrade request where you go from web push to start talking over WebRTC instead.</p>

<p>The bad parts is you will need the user consent to have this working. Most ppl don’t like to enable desktop notification if you don’t have a good UX/context of why it should be enabled. Remember don’t ask for permission on page load, do it on any user interaction.</p>

<p>The 2nd part is that not all push services have enabled CORS support on there own push service. Only Mozilla do, <a href="https://github.com/w3c/push-api/issues/303">CORS is a reasonable SHOULD requirement</a> to enable this kind of end-to-end p2p to start talking to each other without any involvement of server. not even your own! The push payload is also encrypted so there is no way for the push service to know what you are sending to the other person. I’m also using a CORS proxy service to send out push to the rest of browsers.</p>]]></content><author><name>Jimmy Wärting</name><email>jimmy@warting.se</email></author><category term="p2p" /><category term="WebRTC" /><category term="WebPush" /><summary type="html"><![CDATA[Serverless technologies are great. And when you need to chat/talk to one or another, then you can do so directly with WebRTC. But what sucks is that you have to signal offer &amp; answers back and forth using a server.]]></summary></entry><entry><title type="html">Welcome</title><link href="https://jimmy.warting.se/2021/02/15/welcome.html" rel="alternate" type="text/html" title="Welcome" /><published>2021-02-15T00:00:00+00:00</published><updated>2021-02-15T00:00:00+00:00</updated><id>https://jimmy.warting.se/2021/02/15/welcome</id><content type="html" xml:base="https://jimmy.warting.se/2021/02/15/welcome.html"><![CDATA[<p>Going to try and get something up eventually i hope</p>]]></content><author><name>Jimmy Wärting</name><email>jimmy@warting.se</email></author><summary type="html"><![CDATA[Going to try and get something up eventually i hope]]></summary></entry></feed>