# XMTP - Chrome Extension - Post-mortem ## Background context In november, we set out to build a chrome extension wrapper of the existing xmtp-example-chat app, which is a next.js web app. The project was intended to be a quick POC, which meant we would try not to go too far down technical rabbit holes that could be avoided in place of expediency. A good example of this is our decision not to handle local encryption of the xmtp private key; certainly not production-ready, but good enough for a POC. Originally, I (naively) thought this would take a week or two 🙄. This was based on a few incorrect assumptions: * We could essentially "wrap" the nextjs app in the chrome extension "popup", aka the action. * This quickly proved incorrect due to fundamental differences in a web app context and inherent limitations in the chrome extension action context. Primarily, in the existing codebase, we use the next.js router throughout the code, which relies on various APIs that are not available in a chrome extension action. This means that major pieces of the app needed to be abstracted and/or re-architected for a non-url based environment. * We could "fake" a native push notification by running a background script, similar to how metamask operates. This is possible, but seemed technically beyond the scope of a typical POC when we discussed it. * Metamask doesn’t run a notification server. Instead, it keeps itself running in the background by hacking around the limits of how newer chrome extensions are meant to work. Essentially, it has a keepalive notification that is sent from a webpage every N seconds, which is received by the metamask service worker. This keeps the service worker alive/running in the background (otherwise chrome will kill it after 5 mins max iirc), so MM can periodically query an ETH node to see if there are any new transactions for a linked wallet. If a new transaction occurs linked to one of your wallets, metamask bumps the badge count. * Sometime in december (iirc) Nick shipped a notification server, which integrates with firebase to send notifications. We switched to this strategy, but ## Post Mortem ### What was left out #### Proper metamask extension support Metamask web wallet connect — being able to pop open the metamask chrome extension to sign/authenticate the xmtp client — was one of the major pieces of functionality left out here. The reason for this is because the entire ecosystem of wallet connect/web3modal/[everything in web3 land] relies on accessing metamask in javascript by calling `window.ethereum`. But you don’t have access to chrome extensions on `window` from other chrome extensions action popups 🤦‍♂️ The action popup is an inherently limited environment. There are, however, two ways to do this. One option is to split the app up to run the wallet signing piece as a content script and then pass the private keys to the action popup. A content script is javascript injected into existing browser tabs, which _do have access to _`window.ethereum`. The other _theoretical_ option (hint: not really a viable option) is to use message passing between chrome extensions to communicate between them. Message passing is the officially supported chrome extension way for extensions to communicate. But “message passing” is a very basic standard, individual extensions still have to provide their own api for other extensions to use…Metamask [had support for this] (sigh) but it hasn’t been touched in years and only works for v2 chrome extension manifests 🤦‍♂️ 🤦‍♂️ Manifest v3 has been out for a couple years, so it’s safe to assume Metamask doesn’t care about this. So, this option is not really viable. #### Client-side private key encryption **tl;dr** neither the chrome extension nor xmtp.chat store private keys securely currently. For the chrome extension it’s bc it would’ve massively expanded the scope of this POC. _strap in, encryption stuff ahead_ Background context: the chrome extension is using client-side key encryption using the private key stuff supported already in the xmtp-js client. This lets you tell the client — _hey, i want you to give me the private keys so I can store them in my own **secure location**, otherwise I have to ask my users to sign a wallet transaction every time I create an instance of the client_. If you don’t use the private key init option, you must get a fully formed wallet instance from ethers to initiate the xmtp-js client, which requires that you ask your users to sign a wallet transaction, **every time you reload the page, which in chrome extension land means every time you open/close the action pop** 🤦‍♂️ 🤦‍♂️ 🤦‍♂️ SO, the chrome extension saves private keys locally so we don’t ask the user for a wallet signature every time the user opens the app. That’s good. But we’re not storing the keys in a particularly secure fashion. They’re just stored in the extension’s local storage, and the existing implementation in xmtp.chat just stores them in browser local storage (chrome extension local storage and browser local storage are slightly different). This means that any person or thing with access to the browser (such as a user’s other browser extensions they have installed) can access the xmtp private keys and do whatever they like with them. Needless to say, this is not very secure. So would we go about making it secure? Copy metamask, of course. Thankfully, Metamask has [a library] that handles the hard encryption stuff and creates a local browser keyring, which is basically a secure “vault” encrypted with a user password so you can safely store stuff in browser localstorage (like privatekeys). So basically you would build a little password interface like metamask has and use that user password to interact with the keyring library. But this wouldn’t expanded scope significantly so we decided not to do it. But someone should probably do it before any of this goes to production. Ok, security rant over. _Note: this is a problem you don’t really have on ios or android, because you have a native OS-level keyring that you can securely store stuff in, protected by the user’s device password or biometrics. On the web you have to do all this yourself 🙃_ ### What was done When in the foreground (active), the extension is at feature parity with the example chat react web app at xmtp.chat. It receives messages and allows the user to chat back-and-forth. Some components were duplicated in order to support the chrome extension, but many were abstracted to be able to reuse in a web app and chrome extension context. The only part that doesn’t work is receiving push notifications in the background, which is far as I understand it is a bug with the notification server’s support for the web push protocol. **My understanding from talking with Nick is that this is a bug that is relatively close and once fixed, the chrome extension should receive the background notification correctly.** #### Firebase push notification integration We integrated with firebase to register the device (browser) for push notifications. This generates a token that firebase uses to identify the device to try to deliver notifications to it. The firebase piece works -- if you go send a test notification from Firebase, you'll receive a notification in the chrome extension. But the notification server doesn't seem to be working with web pushes yet, so we're not yet receiving notifications when xmtp messages are sent. #### Wallet connect upgrade When I started working on this project, wallet connect and web3modal sunsetted version 1.x, which the app is currently on. Originally they had their sunset date set to Jan 1, it appears they’ve moved it back to Feb 28. As a result, I upgraded web3modal and wallet connect to v2, thinking wallet connect would be sunsetting the v1 API on Jan 1. v1 and v2 have some key differences and are not backwards compatible. One of the main differences is some things, such as ens address resolution, are access via different APIs. Sometimes this is through wagmi react hooks. This is how the v2 walletconnect instruct to do this. #### URL state moved to app state In a web app, URLs end up inherently capturing some application state. So, when the user is on `/dm/WALLET_ADDRESS`, that means the app should show the conversation thread view. But in the chrome extension we don’t have url-based routing, which means we needed to store some of this global app state somewhere else. There are few different types of state that we store in the zustand state store: * `extensionAppViewState` / `setExtensionAppViewState` - Essentially the state replacement for the URL * `activeRecipient` / `setActiveRecipient` - Used when authoring a new message * `activeConversation` / `setActiveConversation` #### Chrome extension manifest v3 complexity Chrome extension manifest format v2, which is now deprecated, has some key differences from extension format v3 (see [Chrome docs]). One of key differences is how “background” scripts are handled. In v2, it was much easier to run persistent background scripts, making it easy to do things like poll remote APIs to check for new data. In v3, chrome makes this harder, as “background” tasks are implemented via service workers. Their instructions are to use notifications (such as the web push api), timers, and other techniques so chrome can manage the service workers effectively. ### What's left to do The main remaining work is mainly handling the background push notification once the notification server bug is fixed. In addition, there are a few remaining UI/UX bugs: * There are a few minor remaining UI bugs, such as a layout bug on the conversation screen, but the main work is handling notifications from the notification server. * Right now I have a fairly crude "request push notifications" button in the app. It would be better if we just requested push notification permissions automatically from the user, and if granted, gets the token from firebase. This would be a pretty trivial change. When a new notification comes in, a few things should happen: 1. Update the action icon to display a badge. We decided early on not to do a badge count (lots of complexity with getting badge counts correct) and instead just do a red dot to indicate new activity. This can be accomplished either by swapping out the action icon for one with a pre-rendered badge, or by rendering a space in the built-in chrome `setBadgeText` api. 1. When the user clicks on the notification, we need to handle that click and properly route the user to the correct conversation thread. #### Notification server / web push support The notification server that Nick implemented is, as far as I know, quite close to having support for web push. Once that is added, there is very little remaining work to have the chrome extension functional. My understanding of where the notification server sits in the overall architecture is it’s the notification glue between the xmtp backend and firebase. Firebase handles client device tokens and the nuts and bolts of message delivery (e.g. talking to APNs, Android Push, Web Push, etc). So, in theory there shouldn’t be much needed to add support for web push.