<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: shun</title>
    <description>The latest articles on DEV Community by shun (@shunshun).</description>
    <link>https://dev.to/shunshun</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F118305%2F2a32deb2-5c7c-4e2b-9e6c-992f6acf2898.png</url>
      <title>DEV Community: shun</title>
      <link>https://dev.to/shunshun</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shunshun"/>
    <language>en</language>
    <item>
      <title>On self hosting outline wiki</title>
      <dc:creator>shun</dc:creator>
      <pubDate>Tue, 14 Jul 2026 07:36:35 +0000</pubDate>
      <link>https://dev.to/shunshun/on-self-hosting-outline-wiki-4j33</link>
      <guid>https://dev.to/shunshun/on-self-hosting-outline-wiki-4j33</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;read the original blog here: &lt;a href="https://astonepokes.net/reflections/on-self-hosting/" rel="noopener noreferrer"&gt;https://astonepokes.net/reflections/on-self-hosting/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Before we start
&lt;/h2&gt;

&lt;p&gt;this article serves as a reflection and reminder to myself on what occurred when i tried to self host outline wiki&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;I've an old laptop lying around with these specs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory: 32 gb &lt;/li&gt;
&lt;li&gt;Storage: 1tb sdd&lt;/li&gt;
&lt;li&gt;Cpu: i7-1165G7 (4 cores / 8 threads)&lt;/li&gt;
&lt;li&gt;Graphics: Intel Iris Xe integrated graphics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and I thought: "I could turn this into a homelab for me to tinker and learn self hosting so why not?". originally, I wanted to run local LLMs like gemma 4 on this hardware but I realised the graphics card wasn't up for it.&lt;/p&gt;

&lt;p&gt;thus, i formatted the laptop to use ubuntu 26.04, which seemed to be the most beginner friendly way to start. &lt;/p&gt;

&lt;h2&gt;
  
  
  What I decided on
&lt;/h2&gt;

&lt;p&gt;i took some time to figure out what I want on the server, and settled on self hosting both caddy and outline.&lt;/p&gt;

&lt;p&gt;I've used outline wiki before and I liked it a lot. It was fast, less clunky than confluence and it can be self hosted with &lt;a href="https://docs.getoutline.com/s/hosting/doc/docker-7pfeLP5a8t" rel="noopener noreferrer"&gt;docker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;also, I wanted to learn how to setup a reverse proxy with caddy. out of the box, it provides self signed certificates for SSL which is quite convenient as well because I only want to access everything within my local area network (LAN).&lt;/p&gt;

&lt;p&gt;i thought the process will be quite straightforward, it'd be a one day job and I'd have scratched my itch to learn. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install caddy on bare metal, setup reverse proxy&lt;/li&gt;
&lt;li&gt;setup docker&lt;/li&gt;
&lt;li&gt;get outline wiki docker compose, setup environment variables and start the docker compose&lt;/li&gt;
&lt;li&gt;voila, i'd have my outline wiki ready to access across my lan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, self hosting outline wiki required more work than I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trip up
&lt;/h2&gt;

&lt;p&gt;i assumed outline supported email and password authentication, but they didn't. instead, it offers third party &lt;a href="https://docs.getoutline.com/s/hosting/doc/authentication-7ViKRmRY5o" rel="noopener noreferrer"&gt;authentication&lt;/a&gt; such as Google, Gitlab, Discord and so on but I didn't like the idea of relying on an online service for authentication so I needed to configure &lt;a href="https://docs.getoutline.com/s/hosting/doc/oidc-8CPBm6uC0I" rel="noopener noreferrer"&gt;oidc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;since I decided to use caddy, I couldn't use the recommended &lt;a href="https://hub.docker.com/r/steveltn/https-portal/" rel="noopener noreferrer"&gt;https-portal&lt;/a&gt; which provides a lets encrypt cert out of the box. I had to figure out how to use caddy to issue self signed certificate on the reverse proxied route on outline wiki.&lt;/p&gt;

&lt;p&gt;lastly, I didn't want to pay a dime for a domain which made it that much more harder for myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did
&lt;/h2&gt;

&lt;p&gt;after experimenting, I settled on the following architecture&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz6guhlgerz2ahes6502v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz6guhlgerz2ahes6502v.png" alt=" " width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Technitium - A DNS server to create a DNS zone called &lt;code&gt;marseille.internal&lt;/code&gt; and hostnames like &lt;code&gt;outline.marseille.internal&lt;/code&gt; and &lt;code&gt;authelia.marseille.internal&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;.internal or .home.apra are reserved keywords from ICANN for private use&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Caddy - A reverse proxy to expose outline wiki with a proper hostname like &lt;code&gt;outline.marseille.internal&lt;/code&gt; instead of remembering the ip and port (&lt;code&gt;192.168.1.141:3100&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Authelia - A OIDC provider which supports email and password authentication for outline wiki&lt;/li&gt;
&lt;li&gt;Outline wiki - the application I want to self host&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Rationale
&lt;/h3&gt;

&lt;p&gt;Technitium, authelia and caddy are installed on bare metal. These applications are going to be used by one or more services, and there isn't a need for scaling, so I skipped docker and the need to deal with an additional layer of networking.&lt;/p&gt;

&lt;p&gt;Outline wiki is on a docker container. It's an application I could switch out any time if I wanted to, and I didn't want any lingering dependencies if I ever removed it. It has its own internal network, saving me the trouble of dealing with port conflicts with commonly used service like redis or postgres.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reminders
&lt;/h3&gt;

&lt;p&gt;These are reminders to myself on what I did to both technitium and authelia. &lt;/p&gt;

&lt;h4&gt;
  
  
  Technitium
&lt;/h4&gt;

&lt;p&gt;Setting up technitium was straightforward, I followed the instructions &lt;a href="https://technitium.com/dns/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftwdyrwsr194qd534c6xp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftwdyrwsr194qd534c6xp.png" alt=" " width="800" height="886"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc176j4kqi43vs5iicrmc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc176j4kqi43vs5iicrmc.png" alt=" " width="799" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fortunately, I didn't run into any issues with port binding as mentioned in: &lt;a href="https://blog.technitium.com/2017/11/running-dns-server-on-ubuntu-linux.html" rel="noopener noreferrer"&gt;Common Issue With Ubuntu&lt;/a&gt;. Once everything was done, I checked the status with &lt;code&gt;sudo systemctl status.service&lt;/code&gt; and visited &lt;code&gt;http://localhost:5380&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj2s23cqbpao6vp7lw0h1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj2s23cqbpao6vp7lw0h1.png" alt=" " width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once in, I created the following A records for authelia and outline. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authelia.marseille.internal -&amp;gt; 192.168.1.141:9091&lt;/li&gt;
&lt;li&gt;outline.marseille.internal -&amp;gt; 192.168.1.141:3100&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Authelia
&lt;/h4&gt;

&lt;p&gt;Setting up authelia was quite unintuitive, the docs didn't give much instructions on how to &lt;strong&gt;start&lt;/strong&gt; post installation. After following the &lt;a href="https://www.authelia.com/integration/deployment/bare-metal/" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;. I expected some docs along the lines of "here are the commands to get you started" or "here's a guide to ubuntu" but there was none. Only &lt;a href="https://www.authelia.com/integration/prologue/get-started/" rel="noopener noreferrer"&gt;Get Started&lt;/a&gt; existed.&lt;/p&gt;

&lt;p&gt;Thankfully, I remembered authelia runs with systemd on bare metal. So I figured that if I ran &lt;code&gt;sudo systemctl status authelia.service&lt;/code&gt; something would pop up and it did!&lt;/p&gt;

&lt;h5&gt;
  
  
  updating configuration
&lt;/h5&gt;

&lt;p&gt;It showed me a bunch of errors regarding an incomplete &lt;code&gt;configuration.yml&lt;/code&gt;, showed me where the file (/etc/authelia/configuration.yml) was and what to fix. In this case its something about the telemetry key, heading to the docs I can find more information about &lt;a href="https://www.authelia.com/configuration/telemetry/metrics/" rel="noopener noreferrer"&gt;telemetry&lt;/a&gt; and configure it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwqvqhc02ga8cbeq583h0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwqvqhc02ga8cbeq583h0.png" alt=" " width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After addressing the issue and saving the configuration file, I just needed to run &lt;code&gt;sudo systemctl restart authelia&lt;/code&gt; and &lt;code&gt;sudo systemctl status authelia --full --no-pager&lt;/code&gt; to ensure that everything works&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4qyq6wqiggahx1c45jmq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4qyq6wqiggahx1c45jmq.png" alt=" " width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Seeding initial users
&lt;/h5&gt;

&lt;p&gt;authelia requires a default user to be created, which is defined within the &lt;code&gt;/etc/authelia/configuration.yml&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyhx7oqd4tnk112r20evq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyhx7oqd4tnk112r20evq.png" alt=" " width="364" height="45"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I populated it like so following the setup &lt;a href="https://www.authelia.com/reference/guides/passwords/" rel="noopener noreferrer"&gt;mentioned&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7rfn1d49fcg2v2g3ysy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7rfn1d49fcg2v2g3ysy.png" alt=" " width="591" height="107"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the user seeded, I visited the &lt;code&gt;https://authelia.marseille.internal&lt;/code&gt; and logged in&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5tdrgm2284ya10qvxdxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5tdrgm2284ya10qvxdxt.png" alt=" " width="685" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fom8wecryinli8szdjcvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fom8wecryinli8szdjcvg.png" alt=" " width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Outline works!
&lt;/h2&gt;

&lt;p&gt;With authelia in place, and adding its oidc creds to authelia based on &lt;a href="https://www.authelia.com/integration/openid-connect/clients/outline/" rel="noopener noreferrer"&gt;https://www.authelia.com/integration/openid-connect/clients/outline/&lt;/a&gt; .&lt;br&gt;
I could visit &lt;code&gt;https://outline.marseille.internal&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fegv4fm6u7fntz63x4v5c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fegv4fm6u7fntz63x4v5c.png" alt=" " width="790" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgu6f2sqty6liey1igx0b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgu6f2sqty6liey1igx0b.png" alt=" " width="658" height="793"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fci6q1zmks7zybag0msc2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fci6q1zmks7zybag0msc2.png" alt=" " width="799" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;I never expected self hosting outline wiki to be such a journey, it took five days instead of one! Along the way, it refreshed my memory on so many things such as&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what does my router do again?&lt;/li&gt;
&lt;li&gt;what does a dns server do? and how does technitium help in this case&lt;/li&gt;
&lt;li&gt;reading the docs and figuring out how to setup tools like authelia&lt;/li&gt;
&lt;li&gt;get it through my head that reverse proxy is literally a collection of internal ip address and port to a public domain or subdomain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and so on.&lt;/p&gt;

&lt;p&gt;All these to scratch my itch of self hosting. With this new found knowledge, I realise self hosting isn't as difficult as I thought. Though certain application like outline wiki can give a big trip, but I feel like with the current architecture I have can go a long way.&lt;/p&gt;

&lt;p&gt;So what's my biggest lesson here? I'd say it's remembering the process matters more than the outcome. I know I'll be able to self host outline wiki (outcome) but I tend to procrastinate thinking that it'll be easy, and as it turns out it wasn't and I learned a lot. &lt;/p&gt;

&lt;p&gt;So if I procastinate it means I should probably do it, sounds like a pretty good signal to do stuff.&lt;/p&gt;

</description>
      <category>learning</category>
    </item>
    <item>
      <title>Thoughts on Write yourself a Git!</title>
      <dc:creator>shun</dc:creator>
      <pubDate>Fri, 28 Feb 2025 04:21:59 +0000</pubDate>
      <link>https://dev.to/shunshun/thoughts-on-write-yourself-a-git-2hob</link>
      <guid>https://dev.to/shunshun/thoughts-on-write-yourself-a-git-2hob</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Article in question: &lt;a href="https://wyag.thb.lt/" rel="noopener noreferrer"&gt;https://wyag.thb.lt/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Going through this article provided me with a deeper understanding on how Git works. It's a tool which we use daily but don't really understand how it works.&lt;/p&gt;

&lt;p&gt;I ask myself was there any point of this? I'm not sure really, I won't be asked to build my own Git. But, it was interesting to understand or have an inkling on how Git works at its core. Moreover, there were moments where I find myself saying: 'Oh this is how it works' and feel a tinge of excitement which I've not felt in a while.&lt;/p&gt;

&lt;p&gt;It was cool to consume, verify and implement the content of the article. For example, it mentions that Git objects exists in &lt;code&gt;.git/objects&lt;/code&gt;, I proceeded to verify it and confirm that it actually exists. Additionally, now I understand how the folders and file are structured under it. The first two character of the SHA-1 hash is the folder name and the remaining characters is the binary file name.&lt;/p&gt;

&lt;p&gt;Though I mentally checked out on part 9, I pressed on and went through the article in one pass. There are still a lot of concepts that are still unknown / abstract / confusing to me such as a tree object. In the article it mentions that a tree object is special as it's a binary object but aren't tags and commit binary objects too?&lt;/p&gt;

&lt;p&gt;But I tend to not dwell too much on it, I just note it down, move forward and complete the tutorial to get a bigger picture allowing me to connect the dots backwards.&lt;/p&gt;

&lt;p&gt;Other than noting down parts I'm not sure on, I wrote down a question and answer deck along the way so that I can insert into Anki for revision. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is a git object? Git objects are binary file that represent files within a repository. These objects usually lives in '.git/object' and it's identified by a SHA-1 hash which is derived from the contents of the file. Thus git doesn't actually store files&lt;/li&gt;
&lt;li&gt;What happens when a file in git is modified? Instead of updating a file, a new file is created in a different location&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lastly, I'm going through this content a second ~ nth time to reinforce the concept taught, modifying the code to reflect my understanding and reduce cognitive load. For example, I'm intending to split up the different git commands to separate files for easy reading and introduce typings to the parameters to know what it should expect. But this is only possible if I've went through it the first time round. Essentially, I'm replicating first before iterating the content with my own twist&lt;/p&gt;




&lt;p&gt;For those interested in the code I've written up it's here: &lt;a href="https://github.com/tanshunyuan/build-my-own-x/tree/main/wyagit" rel="noopener noreferrer"&gt;https://github.com/tanshunyuan/build-my-own-x/tree/main/wyagit&lt;/a&gt; &lt;/p&gt;

</description>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>Preventing account sharing with Clerk</title>
      <dc:creator>shun</dc:creator>
      <pubDate>Mon, 03 Jun 2024 14:40:28 +0000</pubDate>
      <link>https://dev.to/shunshun/preventing-account-sharing-with-clerk-47pm</link>
      <guid>https://dev.to/shunshun/preventing-account-sharing-with-clerk-47pm</guid>
      <description>&lt;h1&gt;
  
  
  Scenario
&lt;/h1&gt;

&lt;p&gt;Imagine the following scenario:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When User B logs in using User A's credentials from a different device, the platform invalidates User A's active session, logs them out, and allows User B to access the account to prevent account sharing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  The Problem
&lt;/h1&gt;

&lt;p&gt;While Clerk provides a way to invalidate a user session, there aren't any docs or guides on achieving the said scenario. Thus, I've written this guide to illustrate the process.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Guide
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Before we continue
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Disclaimer
&lt;/h3&gt;

&lt;p&gt;This walkthrough will not cover how Clerk was set up with TRPC &amp;amp; NextJS 14 as seen in the example repo. It'll only go through the core logic/implementation of preventing account sharing. &lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;p&gt;This guide uses the following tech stack:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/t3-oss/create-t3-app" rel="noopener noreferrer"&gt;Create T3 APP&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;NextJS 14 app directory&lt;/li&gt;
&lt;li&gt;TRPC 11&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pusher Channels

&lt;ul&gt;
&lt;li&gt;We'll be using the free tier which allows for 200k messages per day &amp;amp; 100 concurrent connection.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Clerk &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While it's using bleeding edge tech, I believe the concept described in the guide can be used on a traditional client-server architecture as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Folder Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;src
├── app
│   ├── (auth)
│   │   ├── layout.tsx
│   │   └── sign-in
│   │       └── [[...sign-in]]
│   │           └── page.tsx
│   ├── (internal)
│   │   └── app
│   │       ├── dashboard
│   │       │   └── page.tsx
│   │       └── layout.tsx
│   ├── api
│   │   └── pusher
│   │      └── auth
│   │          └── route.ts
│   └── layout.tsx
├── hooks
│   └── use-setup-session-management.ts
├── lib
│   └── pusher
│       ├── client.ts
│       └── server.ts
├── server
│   └── api
│       └── routers
│          └── user.ts
└── utils
    ├── app-provider.tsx
    └── route-paths.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;The full code is right &lt;a href="https://github.com/tanshunyuan/blog-session-invalidation/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring a web socket service on your application
&lt;/h3&gt;

&lt;p&gt;With a pusher account, follow it's JS SDK &lt;a href="https://pusher.com/docs/channels/getting_started/javascript/" rel="noopener noreferrer"&gt;walkthrough&lt;/a&gt;to retrieve the environment variables from your account.&lt;/p&gt;

&lt;p&gt;Now we need to setup a client and server pusher instance as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/pusher/client.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PusherClient&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pusher-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/env.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PUSHER_AUTH_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/pusher/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pusherClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PusherClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_PUSHER_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_PUSHER_CLUSTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PUSHER_AUTH_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PUSHER_AUTH_ENDPOINT&lt;/code&gt; is an authentication endpoint that will be called on the client side to authenticate the outgoing request to the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Server&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/pusher/server.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PusherServer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pusher&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/env.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pusherInstance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PusherServer&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getPusherInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;pusherInstance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pusherInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PusherServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PUSHER_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_PUSHER_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PUSHER_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_PUSHER_CLUSTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;useTLS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pusherInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Authenticating Client Pusher Request on the server&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/api/pusher/auth/route.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getPusherInstance&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/lib/pusher/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pusherServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getPusherInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// see https://pusher.com/docs/channels/server_api/authenticating-users&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;socket_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channel_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// use JWTs here to authenticate users before continuing&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pusherServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorizeChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socket_id&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channel_name&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pusher/auth.handler.catch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Establish a client web socket connection
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Client: Creating a hook&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/hooks/use-setup-session-management.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pusherClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/lib/pusher/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;didInit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useSubscribeToSessionChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;didInit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pusherClient&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;private-session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s2"&gt;`evt::revoke-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;session-revoked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// handle session removal&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;didInit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unbind&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;didInit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleSessionRemoval&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;didInit&lt;/code&gt; 

&lt;ul&gt;
&lt;li&gt;Ensures that the hook only run once when mounted. More &lt;a href="https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application" rel="noopener noreferrer"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;pusherClient.subscribe('private-session')&lt;/code&gt; 

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;private-session&lt;/code&gt; is the channel name&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;pusherClient.subscribe('private-session').bind('evt::revoke-${user?.id})&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;evt::revoke-${user?.id}&lt;/code&gt; is the event name to be triggered within the channel. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Client: Mounting it in the protected directory provider&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/utils/app-provider.tsx&lt;/span&gt;

&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSubscribeToSessionChannel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/hooks/use-setup-session-management&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BaseChildrenProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/types/common&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AppProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BaseChildrenProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;

  &lt;span class="nf"&gt;useSubscribeToSessionChannel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By putting it in a provider it means that every time the user visits the main application. This connection will be setup
## Query for extra session on the server side&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Server&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/server/api/routers/user.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;clerkClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createTRPCRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;protectedProcedure&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../trpc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getPusherInstance&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~/lib/pusher/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pusherServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getPusherInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userRouter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTRPCRouter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;getExcessSessions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;protectedProcedure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentSessionId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&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;auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;activeSessions&lt;/span&gt; &lt;span class="p"&gt;}&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;clerkClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSessionList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeSessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;excessSessionsIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activeSessions&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;currentSessionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;revokeSessionsPromises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;excessSessionsIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;clerkClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;revokeSessionsPromises&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pusherServer&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;private-session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`evt::revoke-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;session-revoked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;excessSessionsIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure both &lt;code&gt;channelName&lt;/code&gt; &amp;amp; &lt;code&gt;eventName&lt;/code&gt; for pusher is the same as the one on the client side&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Client&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/utils/app-provider.tsx &lt;/span&gt;

&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSubscribeToSessionChannel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/hooks/use-setup-session-management&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/trpc/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BaseChildrenProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/types/common&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AppProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BaseChildrenProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;

  &lt;span class="nf"&gt;useSubscribeToSessionChannel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;excessSessionQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getExcessSessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;excessSessionQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sign user out based on extra sessions
&lt;/h2&gt;

&lt;p&gt;We're going to handle the information from the callback in the web socket connection&lt;/p&gt;

&lt;p&gt;We'd need to define how to handle the session removal when the web socket receives an item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/hooks/use-setup-session-management.ts&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useHandleSignOut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signOut&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useClerk&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;currentSessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signOut&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ROUTE_PATHS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SIGNIN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?forcedRedirect=true`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentSessionId&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useHandleSessionRemoval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useClerk&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSignOut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useHandleSignOut&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;excessSessionIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasExcess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;excessSessionIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isCurrentSessionExcess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;hasExcess&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;currentSession&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;excessSessionIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isCurrentSessionExcess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handleSignOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error removing session:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;use&lt;/code&gt; as a keyword to prevent typescript from complaining&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Refer to &lt;a href="https://github.com/tanshunyuan/blog-prevent-account-sharing/blob/main/src/hooks/use-setup-session-management.ts" rel="noopener noreferrer"&gt;here&lt;/a&gt; for the full code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Touching up the UI
&lt;/h2&gt;

&lt;p&gt;After the user is signed out, they'll be redirected to the sign in page. At this point we'd want to notify the user that they've been logged out.&lt;/p&gt;

&lt;p&gt;Recall this is how we redirected the user&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ROUTE_PATHS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SIGNIN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?forcedRedirect=true`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the query parameter of &lt;code&gt;forcedRedirect=true&lt;/code&gt;, this is used to trigger a toast to indicate the user they've been logged out on the sign in page.&lt;/p&gt;

&lt;p&gt;Here's how:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/(auth)/sign-in/[[...sign-in]]/page.tsx&lt;/span&gt;

&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SignIn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSearchParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ROUTE_PATHS&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/utils/route-paths&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-hot-toast&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SignInPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSearchParams&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;forcedRedirect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;forcedRedirect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forcedRedirect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Detected additional sessions, kicking you out&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ROUTE_PATHS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SIGNIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;forcedRedirect&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SignIn&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We detect if there's a searchParam called &lt;code&gt;forcedRedirect&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If there is we display a toast telling the user that they've been logged out&lt;/li&gt;
&lt;li&gt;We also perform a &lt;code&gt;router.replace&lt;/code&gt; to remove the query param on the URL to hide the search query.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;By the end of this guide, you should have grasp on how to leverage Clerk's session management to prevent account sharing. &lt;/p&gt;

&lt;p&gt;If there's any questions or remarks, feel free to leave it in the comments!&lt;/p&gt;

&lt;p&gt;The full code to this guide is &lt;a href="https://github.com/tanshunyuan/blog-prevent-account-sharing/tree/main" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>clerk</category>
    </item>
    <item>
      <title>Consuming Video Resources</title>
      <dc:creator>shun</dc:creator>
      <pubDate>Sun, 13 Dec 2020 04:14:09 +0000</pubDate>
      <link>https://dev.to/shunshun/consuming-video-resources-53md</link>
      <guid>https://dev.to/shunshun/consuming-video-resources-53md</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;The learning process of a self-taught developer is time-consuming, from reading documentation to watching videos on how to build applications or a developer conference that usually lasts about 30 minutes or more. I find myself watching conference videos because they offer insights about a framework or optimizations that I would not figure out by reading through documentation.&lt;/p&gt;

&lt;p&gt;While watching video provides resources, there is also a caveat of spending a large chunk of time on it. Additionally, viewing such videos makes me zone out after a while due to it being dry or too content-heavy. I needed a way to combat the time taken and zoning out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Faster
&lt;/h2&gt;

&lt;p&gt;After a while, I concluded that watching videos with an increased playback speed is the solution. Initially, this thought is filled with self-criticism such as not being able to comprehend what the speaker is saying however it is a worthy attempt.&lt;/p&gt;

&lt;p&gt;Watching videos at 2x speed takes time to adjust to but once I passed the adjusting phase, I never looked back. I find myself not zoning out much due to the need of focusing on what the speaker is saying at a high playback speed. Additionally, being focused increase my comprehension of the videos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Watching videos on a higher playback speed might not be for everyone, but it is worth a shot! Saving time and increasing comprehension is the main takeaway. With the extra time generated, I can devote it to doing other tasks or taking a longer break.&lt;/p&gt;

&lt;p&gt;Give it a shot! Hope you enjoyed this post.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>discuss</category>
      <category>career</category>
    </item>
    <item>
      <title>Squeezing time out of weekends</title>
      <dc:creator>shun</dc:creator>
      <pubDate>Sat, 05 Dec 2020 03:37:24 +0000</pubDate>
      <link>https://dev.to/shunshun/squeezing-time-out-of-weekends-5cfh</link>
      <guid>https://dev.to/shunshun/squeezing-time-out-of-weekends-5cfh</guid>
      <description>&lt;h2&gt;
  
  
  In the beginning
&lt;/h2&gt;

&lt;p&gt;Having only weekends to work on side projects, I was strapped for time. In an attempt to create more time during the weekends, I started waking up early in the weekends around 6 to 7 am. The temptation of lazing in bed is strong but the drive to generate time was stronger so I dragged myself out of bed and went on with the day.&lt;/p&gt;

&lt;p&gt;Waking up early does not get easier even after a month, but I pressed on and got work done. However, being awake for long hours made me drained and prone to distraction. I scrolling through social media applications on my phone or watching youtube videos excessively, procrastinating at the task on hand. At the end of the day, I am left with little time to complete my work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking Shape
&lt;/h2&gt;

&lt;p&gt;Many weekends passed with me procrastinating, I decided that it needs to stop. Thus I wrote a guideline on what items that needs to be completed. Using Trello, I started to list down the tasks that need to be done and whenever I finished a task I would mark it as completed and move on to the next one. Having a guideline gave shape to my day but I was still distracted when dealing with daunting tasks such as debugging or planning out features which takes up a huge amount of time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experimenting
&lt;/h2&gt;

&lt;p&gt;To prevent me from getting distracted while completing tasks, I began allocating 2 to 3 hours time frame for completing the task only to break when the task was finished. Initially, I can complete my tasks but overtime it increasingly got more difficult to focus.&lt;/p&gt;

&lt;p&gt;When a large time frame did not work, I shifted to a smaller time frame of one-hour intervals but it was still difficult to focus. While desperately searching for a solution, I came across the Pomodoro technique which indicates that one will work for 25 minutes and rest for 5 minutes. The ideal amount of cycle to hit is 8 with break time incrementing up to 15 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pomodoro Technique
&lt;/h2&gt;

&lt;p&gt;Initially, I was skeptical about the time frame given to focus as it was only 25 minutes. After all writing code is all about immersing yourself in the lines and getting lost with time right? Well, I was never wrong more. The technique worked miracles, allowing myself to work for 25 minutes meant that I can have a laser focus on the task at hand and the short breather of 5 minutes allowed me to plan what I was going to do next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Overall, I am very happy with the results that I have achieved with the Pomodoro technique. It has allowed me to complete my work at consistent pacing, speeding up the development process. Moreover, with break time I can reduce the fatigue that I faced previously while coding for long periods.&lt;/p&gt;

&lt;p&gt;Additionally, at the end of the day, I will do a review of what I have done and written it down. This is to prove to myself that I have indeed done something during the day and did not waste it.&lt;/p&gt;

&lt;p&gt;Hope you have enjoyed it!&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>Use Angular CLI to serve https locally</title>
      <dc:creator>shun</dc:creator>
      <pubDate>Wed, 25 Nov 2020 06:20:10 +0000</pubDate>
      <link>https://dev.to/shunshun/use-angular-cli-to-serve-https-locally-3n2o</link>
      <guid>https://dev.to/shunshun/use-angular-cli-to-serve-https-locally-3n2o</guid>
      <description>&lt;p&gt;Developing an angular application with HTTPS is useful, this is a guide outlining how to do it in a Linux environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Perquisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;angular-cli&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/FiloSottile/mkcert" rel="noopener noreferrer"&gt;mkcert&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;Initialize an angular application by running &lt;code&gt;ng new https-dev&lt;/code&gt; in your terminal. Once the application is generated, change directory into it and create a folder called &lt;code&gt;ssl&lt;/code&gt; . This will contain the certificates needed. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F68lhm8znf2yzisa88hbj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F68lhm8znf2yzisa88hbj.png" alt="Untitled(1)" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Optionally you can edit &lt;code&gt;.gitignore&lt;/code&gt; to ignore this folder, preventing it from being committed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using mkcert
&lt;/h3&gt;

&lt;p&gt;There are instructions on the page to guide you through installing &lt;code&gt;mkcert&lt;/code&gt; regardless of your OS. With mkcert installed, run &lt;code&gt;mkcert -install&lt;/code&gt; to generate a local Certificate Authority (CA) and restart your browser to make sure it registers the newly generated CA. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firefox&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Head to preferences and type in &lt;code&gt;certificates&lt;/code&gt; in the search bar. Click on &lt;code&gt;View Certificates&lt;/code&gt; and head to &lt;code&gt;Authorities&lt;/code&gt; and locate &lt;code&gt;mkcert development CA&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fl4sf6ibnghr2itslbke8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fl4sf6ibnghr2itslbke8.jpg" alt="firefox-mkcert(1)" width="652" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chrome&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Head to settings and type in &lt;code&gt;certificates&lt;/code&gt; in the search bar. Scroll down to &lt;code&gt;Manage certificates&lt;/code&gt; and head to &lt;code&gt;Authorities&lt;/code&gt; and locate &lt;code&gt;org-mkcert development CA&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Foyjg1qf7b8pblpu035hp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Foyjg1qf7b8pblpu035hp.jpg" alt="chromemkcerts(1)" width="714" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating certificates
&lt;/h3&gt;

&lt;p&gt;At the root of your project, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mkcert &lt;span class="nt"&gt;-cert-file&lt;/span&gt; &amp;lt;folder/filename.pem&amp;gt; &lt;span class="nt"&gt;-key-file&lt;/span&gt; &amp;lt;folder/filename.pem&amp;gt; &amp;lt;space delimeted domain&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;mkcert &lt;span class="nt"&gt;-cert-file&lt;/span&gt; ssl/local-cert.pem &lt;span class="nt"&gt;-key-file&lt;/span&gt; ssl/local-key.pem localhost 127.0.0.1 192.168.1.109 172.18.0.1 ::1 

Created a new certificate valid &lt;span class="k"&gt;for &lt;/span&gt;the following names 📜
 - &lt;span class="s2"&gt;"localhost"&lt;/span&gt;
 - &lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;
 - &lt;span class="s2"&gt;"192.168.1.109"&lt;/span&gt;
 - &lt;span class="s2"&gt;"localhost"&lt;/span&gt;
 - &lt;span class="s2"&gt;"172.18.0.1"&lt;/span&gt;
 - &lt;span class="s2"&gt;"::1"&lt;/span&gt;

The certificate is at &lt;span class="s2"&gt;"./ssl/local-cert.pem"&lt;/span&gt; and the key at &lt;span class="s2"&gt;".ssl/local-key.pem"&lt;/span&gt; ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fh8atnu7r102ex21agzif.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fh8atnu7r102ex21agzif.jpg" alt="localssleedy(1)" width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Development with https
&lt;/h3&gt;

&lt;p&gt;To use the certificate we generated, use the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ng serve &lt;span class="nt"&gt;--ssl&lt;/span&gt; &amp;lt;boolean&amp;gt; &lt;span class="nt"&gt;--ssl-cert&lt;/span&gt; &amp;lt;path-to-cert&amp;gt; &lt;span class="nt"&gt;--ssl-key&lt;/span&gt; &amp;lt;path-to-key&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;ng serve &lt;span class="nt"&gt;--ssl&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--ssl-cert&lt;/span&gt; ./ssl/local-cert.pem &lt;span class="nt"&gt;--ssl-key&lt;/span&gt; ./ssl/local-key.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the application is being served, check your address bar for the padlock. Indicating that &lt;a href="http://localhost" rel="noopener noreferrer"&gt;localhost&lt;/a&gt; has https&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2lt2bb3vez4v8u45eas3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2lt2bb3vez4v8u45eas3.png" alt="Untitled(2)" width="226" height="30"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for reading!!&lt;/p&gt;

</description>
      <category>angular</category>
      <category>linux</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Most of my time is not spent on writing code</title>
      <dc:creator>shun</dc:creator>
      <pubDate>Tue, 02 Jun 2020 02:01:01 +0000</pubDate>
      <link>https://dev.to/shunshun/most-of-my-time-is-not-spent-on-writing-code-31id</link>
      <guid>https://dev.to/shunshun/most-of-my-time-is-not-spent-on-writing-code-31id</guid>
      <description>&lt;p&gt;As some geek that likes to code, the development life cycle is vicious when there isn’t a clear goal or established plan. In an attempt to fix bugs or implement something from documentation, I find myself blindly fixing bugs or just copy-pasting code off the documentation hoping that it will work. Which doesn’t, leading me to an endless loop of trying to find the error and fix it. Blindly coding was what I used to spend most of my time on.&lt;/p&gt;

&lt;p&gt;Until I stumbled upon something simple and yet effective, that is planning. With planning, this delicious pie represents how I spend my time when developing a website or a side project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff4rmvln69hj30ojgaxji.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff4rmvln69hj30ojgaxji.png" alt="Alt Text" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Planning?
&lt;/h2&gt;

&lt;p&gt;For me, it is simply taking a piece of paper and writing out what I am going to develop, how it looks like, and the possible issues that I might face. It is like an artist who sketches on a canvas before drawing the image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Planning?
&lt;/h2&gt;

&lt;p&gt;Because it is essential to me, being able to write down my thoughts, brainstorming ideas, and have a visual guideline of what I am going to develop forces me to think. Think of the possible issues that I might run into and solutions for it. Also, it brings forward most of the debugging work that I will encounter. All these, are now written down with a pen and paper.&lt;/p&gt;

&lt;p&gt;Aim for progress and not perfection. This is what I have learned while having a plan helps you outline what needs to be done it is not perfect. When writing a plan, it does not cover all the issues that we might encounter, which is why we have to adapt the plan as it is being written.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Plan?
&lt;/h2&gt;

&lt;p&gt;Planning is like a puzzle. To gather the pieces, I have to brainstorm and write out all the ideas and possible implementations that are in my head. With the pieces in hand, I will begin to put them together, refining my plans by shifting the pieces around until I feel satisfied. Finally, it is time to assemble, I will start by trying to map out a flow and see if it makes sense.&lt;/p&gt;

&lt;p&gt;If the final plan does not make sense, I will refine it until I feel good about it and most of the doubts I have are cleared. This ensures a close to smooth sailing experience when developing, as it potentially irons out most of the errors that might occur. Be it a logic flaw or a data structure model that just doesn’t make sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;At the end of the day, we can’t escape from debugging or reading documentation as it is part of the development journey. But I believe that with a plan we can find ways to remove potential obstacles, reducing the time wasted on blindly coding.&lt;/p&gt;

&lt;p&gt;What do we do with the time saved? For me, I spend that time debugging some random bug, refactor my code or knock off early and start doing some other things. For you, take that time to relax, learn a new hobby, or just have do something outside of work. Work != life.&lt;/p&gt;

</description>
      <category>writing</category>
      <category>tips</category>
    </item>
  </channel>
</rss>
