Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
jribbink committed Oct 28, 2024
1 parent f7cd390 commit 02f1f35
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 139 deletions.
25 changes: 25 additions & 0 deletions packages/fcl-wc/src/request-notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {showNotification} from "./ui/notifications"
import {isMobile} from "./utils"
import mobileIcon from "./ui/assets/mobile.png"

export async function withRequestNotification(
service: any,
user: any,
openDeeplink: (uid: string) => void
) {
const authnService = user?.services?.find((s: any) => s.type === "authn")
const walletProvider = authnService?.provider

const {close: closePrompt} = showNotification({
title: walletProvider?.name || "Mobile Wallet",
message: isMobile()
? "Tap to view request in app"
: "Pending request on your mobile device",
icon: walletProvider?.icon || mobileIcon,
onClick: service.uid
? () => {
openDeeplink(service.uid)
}
: undefined,
})
}
75 changes: 42 additions & 33 deletions packages/fcl-wc/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import {isMobile, openDeeplink} from "./utils"
import {FLOW_METHODS, REQUEST_TYPES} from "./constants"
import {SignClient} from "@walletconnect/sign-client/dist/types/client"
import {createSessionProposal, request} from "./session"
import {config} from "@onflow/config"
import {getNotificationController} from "./ui/notifications"
import {showNotification} from "./ui/notifications"

import mobileIcon from "./ui/assets/mobile.png"

type WalletConnectModalType =
typeof import("@walletconnect/modal").WalletConnectModal
Expand Down Expand Up @@ -48,16 +49,18 @@ const makeExec = (
{wcRequestHook, pairingModalOverride}: any,
WalletConnectModal: Promise<WalletConnectModalType>
) => {
const exec = async ({
return async ({
service,
body,
opts,
abortSignal,
user,
}: {
service: any
body: any
opts: any
abortSignal?: AbortSignal
user: any
}) => {
const client = await clientPromise
invariant(!!client, "WalletConnect is not initialized")
Expand Down Expand Up @@ -115,8 +118,15 @@ const makeExec = (
openDeeplink(appLink)
}

// Wrap the request function with a notification
const requestWithNotification = withRequestNotification(request, {
method,
user,
openDeeplink: () => openDeeplink(appLink),
})

// Make request to the WalletConnect client and return the result
return await request({
return await requestWithNotification({
method,
body,
session,
Expand All @@ -135,35 +145,6 @@ const makeExec = (
return uid
}
}

return async ({
service,
body,
opts,
abortSignal: parentSignal,
user,
}: any) => {
const abortController = new AbortController()
parentSignal?.addEventListener("abort", () => {
abortController.abort(parentSignal?.reason)
})

const authnService = user?.services?.find((s: any) => s.type === "authn")

const appTitle: string | undefined = await config().get("app.detail.title")
const appIcon: string | undefined = await config().get("app.detail.icon")

const {close: closePrompt} = getNotificationController().showToast({
walletProvider: authnService?.provider,
})

return await exec({
service,
body,
opts,
abortSignal: abortController.signal,
}).finally(closePrompt)
}
}

// Connect to WalletConnect directly from the browser via deep link or WalletConnectModal
Expand Down Expand Up @@ -261,3 +242,31 @@ function connectWc(WalletConnectModal: Promise<WalletConnectModalType>) {
}
}
}

export function withRequestNotification<
T extends (...args: any[]) => Promise<any>,
>(
fn: T,
params: {
method: string
user: any
openDeeplink?: () => void
}
) {
return async (...args: Parameters<T>) => {
const {method, user, openDeeplink} = params
const authnService = user?.services?.find((s: any) => s.type === "authn")
const walletProvider = authnService?.provider

const {close: dismissNotification} = showNotification({
title: walletProvider?.name || "Mobile Wallet",
message: isMobile()
? "Tap to view request in app"
: "Pending request on your mobile device",
icon: walletProvider?.icon || mobileIcon,
onClick: openDeeplink,
})

return await fn(...args).finally(dismissNotification)
}
}
15 changes: 5 additions & 10 deletions packages/fcl-wc/src/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import {Provider} from "@onflow/typedefs"

export type AppInfo = {
name?: string
icon?: string
}

export type NotificationInfo = {
walletProvider?: Provider
title: string
message: string
icon?: string
onClick?: () => void
onClose?: () => void
}

export type ActiveNotificationInfo = NotificationInfo & {id: number}
52 changes: 20 additions & 32 deletions packages/fcl-wc/src/ui/components/Notification.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,38 @@
import {useState, useRef} from "preact/hooks"
import {useBreakpoint} from "../hooks/useBreakpoint"
import {Breakpoint} from "../constants"
import {isMobile as _isMobile, openDeeplink} from "../../utils"

import mobileIcon from "../assets/mobile.png"
import {ActiveNotificationInfo} from "../../types/types"

export function Notification({walletProvider}: ActiveNotificationInfo) {
const onOpenApp = () => {
openDeeplink((walletProvider as any)?.uid)
}

const isDesktop = useBreakpoint(Breakpoint.MD)
const isMobile = _isMobile()
const [isVisible, setIsVisible] = useState(true)
const timer = useRef<NodeJS.Timeout>()

if (!isVisible) return null
import {NotificationInfo} from "../../types/types"

export function Notification({
title,
message,
icon,
onClick,
onClose,
}: NotificationInfo) {
return (
<div
className="animate-slideUp fixed bottom-3 left-3 right-3 z-[2147483647] flex max-w-sm
overflow-hidden rounded-lg border border-gray-200 bg-white p-4 shadow-lg
md:bottom-4 md:left-auto md:right-4 dark:bg-gray-800 dark:border-gray-700"
role="alert"
onClick={e => {
if (!isDesktop) {
if (onClick) {
e.stopPropagation()
onOpenApp?.()
onClick?.()
}
}}
>
<img
className="h-10 w-10 self-center rounded-md md:self-start"
src={walletProvider?.icon || mobileIcon}
alt={walletProvider?.name || "Wallet"}
/>
{icon && (
<img
className="h-10 w-10 self-center rounded-md md:self-start"
src={icon}
alt={title}
/>
)}
<div className="ml-3 grow">
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">
{walletProvider?.name || "Mobile Wallet"}
{title}
</p>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
{isDesktop
? "Pending request on your mobile device"
: "Tap to view request in app"}
{message}
</p>
</div>

Expand All @@ -55,8 +44,7 @@ export function Notification({walletProvider}: ActiveNotificationInfo) {
dark:hover:text-gray-300 dark:focus:border-transparent"
onClick={e => {
e.stopPropagation()
clearTimeout(timer.current)
setIsVisible(false)
onClose?.()
}}
>
<span className="sr-only">Close</span>
Expand Down
52 changes: 0 additions & 52 deletions packages/fcl-wc/src/ui/components/NotificationProvider.tsx

This file was deleted.

51 changes: 39 additions & 12 deletions packages/fcl-wc/src/ui/notifications.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,48 @@
import {render} from "preact"
import styles from "./styles.css"
import {
NotificationController,
NotificationProvider,
} from "./components/NotificationProvider"
import {Notification} from "./components/Notification"
import {NotificationInfo} from "../types/types"

let notificationController: NotificationController | null = null
let renderRoot: HTMLElement | null = null
let id = 0

export function getNotificationController() {
if (!notificationController) {
notificationController = new NotificationController()
createNotificationProvider(notificationController)
export function showNotification({
title,
message,
icon,
onClick,
onClose,
}: NotificationInfo) {
if (!renderRoot) {
renderRoot = createRenderRoot()
}

return notificationController
render(
<Notification
key={id++}
title={title}
message={message}
icon={icon}
onClick={onClick}
onClose={() => {
close()
onClose?.()
}}
/>,
renderRoot
)

function close() {
if (!renderRoot) return
render(null, renderRoot)
}

return {
close,
}
}

function createNotificationProvider(controller: NotificationController) {
function createRenderRoot() {
const shadowHost = document.createElement("div")
const shadowRoot = shadowHost.attachShadow({mode: "open"})
const container = document.createElement("div")
Expand All @@ -34,6 +60,7 @@ function createNotificationProvider(controller: NotificationController) {
container.classList.toggle("dark", mediaQuery.matches)
}
mediaQuery.addEventListener("change", listener)
listener()

render(<NotificationProvider controller={controller} />, container)
return container
}

0 comments on commit 02f1f35

Please sign in to comment.