Skip to content

Commit

Permalink
Modularize other elements
Browse files Browse the repository at this point in the history
  • Loading branch information
arkadiuszbachorski committed Jun 14, 2024
1 parent 6077e2c commit bf97a38
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 99 deletions.
51 changes: 0 additions & 51 deletions app/RegisterWorker.ts

This file was deleted.

4 changes: 2 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { getLocale, getMessages } from 'next-intl/server'
import type { ReactNode } from 'react'
import '@stanfordbdhg/design-system/main.css'
import { themeToCSSVariables, lightTheme } from '@stanfordbdhg/design-system'
import { RegisterWorker } from './RegisterWorker'
import './globals.css'
import { AuthProvider } from '../modules/firebase/AuthProvider'

export const metadata: Metadata = {
title: 'ENGAGE-HF Web Frontend',
Expand All @@ -39,7 +39,7 @@ export default async function RootLayout({ children }: RootLayoutProps) {
</style>
</head>
<body>
<RegisterWorker />
<AuthProvider />
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
Expand Down
51 changes: 7 additions & 44 deletions authServiceWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,54 +9,17 @@

declare let self: ServiceWorkerGlobalScope

import { initializeApp } from 'firebase/app'
import { getAuth, getIdToken, type Auth } from 'firebase/auth'
import { getInstallations, getToken } from 'firebase/installations'
import {
getFirebaseConfig,
handleFetchEvent,
} from '@stanfordbdhg/design-system/modules/auth/serviceWorker'

let firebaseConfig: object

/**
* Get Firebase config from query string
* */
const getFirebaseConfig = () => {
const serializedFirebaseConfig = new URL(location.href).searchParams.get(
'firebaseConfig',
)
if (!serializedFirebaseConfig) {
throw new Error(
'Firebase Config object not found in service worker query string.',
)
}
return JSON.parse(serializedFirebaseConfig) as object
}

const getAuthIdToken = async (auth: Auth) => {
await auth.authStateReady()
if (!auth.currentUser) return
return getIdToken(auth.currentUser)
}

const fetchWithFirebaseHeaders = async (request: FetchEvent['request']) => {
const app = initializeApp(firebaseConfig)
const auth = getAuth(app)
const installations = getInstallations(app)
const headers = new Headers(request.headers)
const [authIdToken, installationToken] = await Promise.all([
getAuthIdToken(auth),
getToken(installations),
])
headers.append('Firebase-Instance-ID-Token', installationToken)
if (authIdToken) headers.append('Authorization', `Bearer ${authIdToken}`)
const newRequest = new Request(request, { headers })
return fetch(newRequest)
}

self.addEventListener('install', () => {
firebaseConfig = getFirebaseConfig()
})

self.addEventListener('fetch', (event) => {
const { origin } = new URL(event.request.url)
if (origin !== self.location.origin) return
event.respondWith(fetchWithFirebaseHeaders(event.request))
})
self.addEventListener('fetch', (event) =>
handleFetchEvent(firebaseConfig, event),
)
36 changes: 36 additions & 0 deletions modules/firebase/AuthProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// This source file is part of the Stanford Biodesign Digital Health ENGAGE-HF open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//
'use client'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
import {
useAuthUser,
useRegisterAuthServiceWorker,
} from '@stanfordbdhg/design-system/modules/auth/hooks'
import { auth } from './clientApp'
import { firebaseConfig } from './config'
import { routes } from '../routes'

export const AuthProvider = () => {
const router = useRouter()
const user = useAuthUser(auth)

useRegisterAuthServiceWorker(firebaseConfig)

useEffect(() => {
// TODO: This is not ideal, results with double redirect. To investigate
const isSignIn = window.location.pathname === routes.signIn
if (isSignIn && user) {
window.location.assign(routes.home)
} else if (!isSignIn && user === null) {
window.location.assign(routes.signIn)
}
}, [router, user])

return null
}
6 changes: 4 additions & 2 deletions packages/design-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@
"./components/Label": "./dist/components/Form/Label.js",
"./components/Separator": "./dist/components/Separator/index.js",
"./molecules/AsideBrandLayout": "./dist/molecules/AsideBrandLayout/index.js",
"./modules/auth/SignInForm": "./dist//modules/auth/SignInForm/index.js",
"./modules/auth/serverApp": "./dist//modules/auth/serverApp.js",
"./modules/auth/SignInForm": "./dist/modules/auth/SignInForm/index.js",
"./modules/auth/serverApp": "./dist/modules/auth/serverApp.js",
"./modules/auth/serviceWorker": "./dist/modules/auth/serviceWorker.js",
"./modules/auth/hooks": "./dist/modules/auth/hooks.js",
"./forms/Field": "./dist/forms/Field/index.js",
"./forms/useForm": "./dist/forms/useForm/index.js",
"./main.css": "./dist/main.css"
Expand Down
45 changes: 45 additions & 0 deletions packages/design-system/src/modules/auth/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { User } from '@firebase/auth-types'
import { type FirebaseOptions } from 'firebase/app'
import { type Auth, onAuthStateChanged } from 'firebase/auth'
import { useEffect, useState } from 'react'

/**
* Registers Auth Service Worker
* */
export const useRegisterAuthServiceWorker = (
firebaseOptions: FirebaseOptions,
) => {
useEffect(() => {
if ('serviceWorker' in navigator) {
const serializedFirebaseConfig = encodeURIComponent(
JSON.stringify(firebaseOptions),
)
const serviceWorkerUrl = `/authServiceWorker.js?firebaseConfig=${serializedFirebaseConfig}`

navigator.serviceWorker.register(serviceWorkerUrl).catch(() => {
console.error(
'Registering service worker failed. Make sure public/authServiceWorker.js exists',
)
})
}
}, [firebaseOptions])
}

/**
* Returns currently authenticated user
* null = no user is authenticated
* undefined = initial state
* */
export const useAuthUser = (auth: Auth) => {
const [user, setUser] = useState<User | null>()

useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
// @ts-expect-error Nested methods are not used anyway
setUser(user)
})
return () => unsubscribe()
}, [auth])

return user
}
64 changes: 64 additions & 0 deletions packages/design-system/src/modules/auth/serviceWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// This source file is part of the Stanford Biodesign Digital Health ENGAGE-HF open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//
/// <reference lib="webworker" />

import { type FirebaseOptions, initializeApp } from 'firebase/app'
import { getAuth, getIdToken, type Auth } from 'firebase/auth'
import { getInstallations, getToken } from 'firebase/installations'

/**
* Get Firebase config from query string
* */
export const getFirebaseConfig = () => {
const serializedFirebaseConfig = new URL(location.href).searchParams.get(
'firebaseConfig',
)
if (!serializedFirebaseConfig) {
throw new Error(
'Firebase Config object not found in service worker query string.',
)
}
return JSON.parse(serializedFirebaseConfig) as FirebaseOptions
}

export const getAuthIdToken = async (auth: Auth) => {
await auth.authStateReady()
if (!auth.currentUser) return
return getIdToken(auth.currentUser)
}

export const fetchWithFirebaseHeaders = async (
firebaseConfig: FirebaseOptions,
request: FetchEvent['request'],
) => {
const app = initializeApp(firebaseConfig)
const auth = getAuth(app)
const installations = getInstallations(app)
const headers = new Headers(request.headers)
const [authIdToken, installationToken] = await Promise.all([
getAuthIdToken(auth),
getToken(installations),
])
headers.append('Firebase-Instance-ID-Token', installationToken)
if (authIdToken) headers.append('Authorization', `Bearer ${authIdToken}`)
const newRequest = new Request(request, { headers })
return fetch(newRequest)
}

/**
* Appends Bearer token to every fetch request
* This allows RSC and API endpoints to identify user
* */
export const handleFetchEvent = (
firebaseConfig: FirebaseOptions,
event: FetchEvent,
) => {
const { origin } = new URL(event.request.url)
if (origin !== self.location.origin) return
event.respondWith(fetchWithFirebaseHeaders(firebaseConfig, event.request))
}

0 comments on commit bf97a38

Please sign in to comment.