chore: reintroduce updated auth mechanism

This commit is contained in:
Andrew Bastin
2023-02-07 19:21:06 +05:30
parent cd72851289
commit ce0898956d
47 changed files with 1081 additions and 823 deletions

View File

@@ -12,6 +12,7 @@ import {
CombinedError,
Operation,
OperationResult,
Client,
} from "@urql/core"
import { authExchange } from "@urql/exchange-auth"
import { devtoolsExchange } from "@urql/devtools"
@@ -21,12 +22,7 @@ import * as TE from "fp-ts/TaskEither"
import { pipe, constVoid, flow } from "fp-ts/function"
import { subscribe, pipe as wonkaPipe } from "wonka"
import { filter, map, Subject } from "rxjs"
import {
authIdToken$,
getAuthIDToken,
probableUser$,
waitProbableLoginToConfirm,
} from "~/helpers/fb/auth"
import { platform } from "~/platform"
// TODO: Implement caching
@@ -57,11 +53,7 @@ export const gqlClientError$ = new Subject<GQLClientErrorEvent>()
const createSubscriptionClient = () => {
return new SubscriptionClient(BACKEND_WS_URL, {
reconnect: true,
connectionParams: () => {
return {
authorization: `Bearer ${authIdToken$.value}`,
}
},
connectionParams: () => platform.auth.getBackendHeaders(),
connectionCallback(error) {
if (error?.length > 0) {
gqlClientError$.next({
@@ -79,7 +71,7 @@ const createHoppClient = () => {
dedupExchange,
authExchange({
addAuthToOperation({ authState, operation }) {
if (!authState || !authState.authToken) {
if (!authState) {
return operation
}
@@ -88,28 +80,29 @@ const createHoppClient = () => {
? operation.context.fetchOptions()
: operation.context.fetchOptions || {}
const authHeaders = platform.auth.getBackendHeaders()
return makeOperation(operation.kind, operation, {
...operation.context,
fetchOptions: {
...fetchOptions,
headers: {
...fetchOptions.headers,
Authorization: `Bearer ${authState.authToken}`,
...authHeaders,
},
},
})
},
willAuthError({ authState }) {
return !authState || !authState.authToken
willAuthError() {
return platform.auth.willBackendHaveAuthError()
},
getAuth: async () => {
if (!probableUser$.value) return { authToken: null }
const probableUser = platform.auth.getProbableUser()
await waitProbableLoginToConfirm()
if (probableUser !== null)
await platform.auth.waitProbableLoginToConfirm()
return {
authToken: getAuthIDToken(),
}
return {}
},
}),
fetchExchange,
@@ -137,31 +130,40 @@ const createHoppClient = () => {
return createClient({
url: BACKEND_GQL_URL,
exchanges,
...(platform.auth.getGQLClientOptions
? platform.auth.getGQLClientOptions()
: {}),
})
}
let subscriptionClient: SubscriptionClient | null
export const client = ref(createHoppClient())
authIdToken$.subscribe((idToken) => {
// triggering reconnect by closing the websocket client
if (idToken && subscriptionClient) {
subscriptionClient?.client?.close()
}
// creating new subscription
if (idToken && !subscriptionClient) {
subscriptionClient = createSubscriptionClient()
}
// closing existing subscription client.
if (!idToken && subscriptionClient) {
subscriptionClient.close()
subscriptionClient = null
}
export const client = ref<Client>()
export function initBackendGQLClient() {
client.value = createHoppClient()
})
platform.auth.onBackendGQLClientShouldReconnect(() => {
const currentUser = platform.auth.getCurrentUser()
// triggering reconnect by closing the websocket client
if (currentUser && subscriptionClient) {
subscriptionClient?.client?.close()
}
// creating new subscription
if (currentUser && !subscriptionClient) {
subscriptionClient = createSubscriptionClient()
}
// closing existing subscription client.
if (!currentUser && subscriptionClient) {
subscriptionClient.close()
subscriptionClient = null
}
client.value = createHoppClient()
})
}
type RunQueryOptions<T = any, V = object> = {
query: TypedDocumentNode<T, V>
@@ -185,7 +187,7 @@ export const runGQLQuery = <DocType, DocVarType, DocErrorType extends string>(
args: RunQueryOptions<DocType, DocVarType>
): Promise<E.Either<GQLError<DocErrorType>, DocType>> => {
const request = createRequest<DocType, DocVarType>(args.query, args.variables)
const source = client.value.executeQuery(request, {
const source = client.value!.executeQuery(request, {
requestPolicy: "network-only",
})
@@ -250,7 +252,7 @@ export const runGQLSubscription = <
) => {
const result$ = new Subject<E.Either<GQLError<DocErrorType>, DocType>>()
const source = client.value.executeSubscription(
const source = client.value!.executeSubscription(
createRequest(args.query, args.variables)
)
@@ -342,8 +344,8 @@ export const runMutation = <
pipe(
TE.tryCatch(
() =>
client.value
.mutation(mutation, variables, {
client
.value!.mutation(mutation, variables, {
requestPolicy: "cache-and-network",
...additionalConfig,
})

View File

@@ -6,7 +6,7 @@ import {
setUserId,
setUserProperties,
} from "firebase/analytics"
import { authEvents$ } from "./auth"
import { platform } from "~/platform"
import {
HoppAccentColor,
HoppBgColor,
@@ -42,13 +42,15 @@ export function initAnalytics() {
}
function initLoginListeners() {
const authEvents$ = platform.auth.getAuthEventsStream()
authEvents$.subscribe((ev) => {
if (ev.event === "login") {
if (settingsStore.value.TELEMETRY_ENABLED && analytics) {
setUserId(analytics, ev.user.uid)
logEvent(analytics, "login", {
method: ev.user.providerData[0]?.providerId, // Assume the first provider is the login provider
method: ev.user.provider, // Assume the first provider is the login provider
})
}
} else if (ev.event === "logout") {

View File

@@ -1,434 +0,0 @@
import {
User,
getAuth,
onAuthStateChanged,
onIdTokenChanged,
signInWithPopup,
GoogleAuthProvider,
GithubAuthProvider,
OAuthProvider,
signInWithEmailAndPassword as signInWithEmailAndPass,
isSignInWithEmailLink as isSignInWithEmailLinkFB,
fetchSignInMethodsForEmail,
sendSignInLinkToEmail,
signInWithEmailLink as signInWithEmailLinkFB,
ActionCodeSettings,
signOut,
linkWithCredential,
AuthCredential,
AuthError,
UserCredential,
updateProfile,
updateEmail,
sendEmailVerification,
reauthenticateWithCredential,
} from "firebase/auth"
import {
onSnapshot,
getFirestore,
setDoc,
doc,
updateDoc,
} from "firebase/firestore"
import { BehaviorSubject, filter, Subject, Subscription } from "rxjs"
import {
setLocalConfig,
getLocalConfig,
removeLocalConfig,
} from "~/newstore/localpersistence"
export type HoppUser = User & {
provider?: string
accessToken?: string
}
export type AuthEvent =
| { event: "probable_login"; user: HoppUser } // We have previous login state, but the app is waiting for authentication
| { event: "login"; user: HoppUser } // We are authenticated
| { event: "logout" } // No authentication and we have no previous state
| { event: "authTokenUpdate"; user: HoppUser; newToken: string | null } // Token has been updated
/**
* A BehaviorSubject emitting the currently logged in user (or null if not logged in)
*/
export const currentUser$ = new BehaviorSubject<HoppUser | null>(null)
/**
* A BehaviorSubject emitting the current idToken
*/
export const authIdToken$ = new BehaviorSubject<string | null>(null)
/**
* A subject that emits events related to authentication flows
*/
export const authEvents$ = new Subject<AuthEvent>()
/**
* Like currentUser$ but also gives probable user value
*/
export const probableUser$ = new BehaviorSubject<HoppUser | null>(null)
/**
* Resolves when the probable login resolves into proper login
*/
export const waitProbableLoginToConfirm = () =>
new Promise<void>((resolve, reject) => {
if (authIdToken$.value) resolve()
if (!probableUser$.value) reject(new Error("no_probable_user"))
let sub: Subscription | null = null
sub = authIdToken$.pipe(filter((token) => !!token)).subscribe(() => {
sub?.unsubscribe()
resolve()
})
})
/**
* Initializes the firebase authentication related subjects
*/
export function initAuth() {
const auth = getAuth()
const firestore = getFirestore()
let extraSnapshotStop: (() => void) | null = null
probableUser$.next(JSON.parse(getLocalConfig("login_state") ?? "null"))
onAuthStateChanged(auth, (user) => {
/** Whether the user was logged in before */
const wasLoggedIn = currentUser$.value !== null
if (user) {
probableUser$.next(user)
} else {
probableUser$.next(null)
removeLocalConfig("login_state")
}
if (!user && extraSnapshotStop) {
extraSnapshotStop()
extraSnapshotStop = null
} else if (user) {
// Merge all the user info from all the authenticated providers
user.providerData.forEach((profile) => {
if (!profile) return
const us = {
updatedOn: new Date(),
provider: profile.providerId,
name: profile.displayName,
email: profile.email,
photoUrl: profile.photoURL,
uid: profile.uid,
}
setDoc(doc(firestore, "users", user.uid), us, { merge: true }).catch(
(e) => console.error("error updating", us, e)
)
})
extraSnapshotStop = onSnapshot(
doc(firestore, "users", user.uid),
(doc) => {
const data = doc.data()
const userUpdate: HoppUser = user
if (data) {
// Write extra provider data
userUpdate.provider = data.provider
userUpdate.accessToken = data.accessToken
}
currentUser$.next(userUpdate)
}
)
}
currentUser$.next(user)
// User wasn't found before, but now is there (login happened)
if (!wasLoggedIn && user) {
authEvents$.next({
event: "login",
user: currentUser$.value!,
})
} else if (wasLoggedIn && !user) {
// User was found before, but now is not there (logout happened)
authEvents$.next({
event: "logout",
})
}
})
onIdTokenChanged(auth, async (user) => {
if (user) {
authIdToken$.next(await user.getIdToken())
authEvents$.next({
event: "authTokenUpdate",
newToken: authIdToken$.value,
user: currentUser$.value!, // Force not-null because user is defined
})
setLocalConfig("login_state", JSON.stringify(user))
} else {
authIdToken$.next(null)
}
})
}
export function getAuthIDToken(): string | null {
return authIdToken$.getValue()
}
/**
* Sign user in with a popup using Google
*/
export async function signInUserWithGoogle() {
return await signInWithPopup(getAuth(), new GoogleAuthProvider())
}
/**
* Sign user in with a popup using Github
*/
export async function signInUserWithGithub() {
return await signInWithPopup(
getAuth(),
new GithubAuthProvider().addScope("gist")
)
}
/**
* Sign user in with a popup using Microsoft
*/
export async function signInUserWithMicrosoft() {
return await signInWithPopup(getAuth(), new OAuthProvider("microsoft.com"))
}
/**
* Sign user in with email and password
*/
export async function signInWithEmailAndPassword(
email: string,
password: string
) {
return await signInWithEmailAndPass(getAuth(), email, password)
}
/**
* Gets the sign in methods for a given email address
*
* @param email - Email to get the methods of
*
* @returns Promise for string array of the auth provider methods accessible
*/
export async function getSignInMethodsForEmail(email: string) {
return await fetchSignInMethodsForEmail(getAuth(), email)
}
export async function linkWithFBCredential(
user: User,
credential: AuthCredential
) {
return await linkWithCredential(user, credential)
}
/**
* Links account with another account given in a auth/account-exists-with-different-credential error
*
* @param error - Error caught after trying to login
*
* @returns Promise of UserCredential
*/
export async function linkWithFBCredentialFromAuthError(error: unknown) {
// credential is not null since this function is called after an auth/account-exists-with-different-credential error, ie credentials actually exist
const credentials = OAuthProvider.credentialFromError(error as AuthError)!
const otherLinkedProviders = (
await getSignInMethodsForEmail((error as AuthError).customData.email!)
).filter((providerId) => credentials.providerId !== providerId)
let user: User | null = null
if (otherLinkedProviders.indexOf("google.com") >= -1) {
user = (await signInUserWithGoogle()).user
} else if (otherLinkedProviders.indexOf("github.com") >= -1) {
user = (await signInUserWithGithub()).user
} else if (otherLinkedProviders.indexOf("microsoft.com") >= -1) {
user = (await signInUserWithMicrosoft()).user
}
// user is not null since going through each provider will return a user
return await linkWithCredential(user!, credentials)
}
/**
* Sends an email with the signin link to the user
*
* @param email - Email to send the email to
* @param actionCodeSettings - The settings to apply to the link
*/
export async function signInWithEmail(
email: string,
actionCodeSettings: ActionCodeSettings
) {
return await sendSignInLinkToEmail(getAuth(), email, actionCodeSettings)
}
/**
* Checks and returns whether the sign in link is an email link
*
* @param url - The URL to look in
*/
export function isSignInWithEmailLink(url: string) {
return isSignInWithEmailLinkFB(getAuth(), url)
}
/**
* Sends an email with sign in with email link
*
* @param email - Email to log in to
* @param url - The action URL which is used to validate login
*/
export async function signInWithEmailLink(email: string, url: string) {
return await signInWithEmailLinkFB(getAuth(), email, url)
}
/**
* Signs out the user
*/
export async function signOutUser() {
if (!currentUser$.value) throw new Error("No user has logged in")
await signOut(getAuth())
}
/**
* Sets the provider id and relevant provider auth token
* as user metadata
*
* @param id - The provider ID
* @param token - The relevant auth token for the given provider
*/
export async function setProviderInfo(id: string, token: string) {
if (!currentUser$.value) throw new Error("No user has logged in")
const us = {
updatedOn: new Date(),
provider: id,
accessToken: token,
}
try {
await updateDoc(
doc(getFirestore(), "users", currentUser$.value.uid),
us
).catch((e) => console.error("error updating", us, e))
} catch (e) {
console.error("error updating", e)
throw e
}
}
/**
* Sets the user's display name
*
* @param name - The new display name
*/
export async function setDisplayName(name: string) {
if (!currentUser$.value) throw new Error("No user has logged in")
const us = {
displayName: name,
}
try {
await updateProfile(currentUser$.value, us)
} catch (e) {
console.error("error updating", e)
throw e
}
}
/**
* Send user's email address verification mail
*/
export async function verifyEmailAddress() {
if (!currentUser$.value) throw new Error("No user has logged in")
try {
await sendEmailVerification(currentUser$.value)
} catch (e) {
console.error("error updating", e)
throw e
}
}
/**
* Sets the user's email address
*
* @param email - The new email address
*/
export async function setEmailAddress(email: string) {
if (!currentUser$.value) throw new Error("No user has logged in")
try {
await updateEmail(currentUser$.value, email)
} catch (e) {
await reauthenticateUser()
console.error("error updating", e)
throw e
}
}
/**
* Reauthenticate the user with the given credential
*/
async function reauthenticateUser() {
if (!currentUser$.value) throw new Error("No user has logged in")
const currentAuthMethod = currentUser$.value.provider
let credential
if (currentAuthMethod === "google.com") {
const result = await signInUserWithGithub()
credential = GithubAuthProvider.credentialFromResult(result)
} else if (currentAuthMethod === "github.com") {
const result = await signInUserWithGoogle()
credential = GoogleAuthProvider.credentialFromResult(result)
} else if (currentAuthMethod === "microsoft.com") {
const result = await signInUserWithMicrosoft()
credential = OAuthProvider.credentialFromResult(result)
} else if (currentAuthMethod === "password") {
const email = prompt(
"Reauthenticate your account using your current email:"
)
const actionCodeSettings = {
url: `${process.env.BASE_URL}/enter`,
handleCodeInApp: true,
}
await signInWithEmail(email as string, actionCodeSettings)
.then(() =>
alert(
`Check your inbox - we sent an email to ${email}. It contains a magic link that will reauthenticate your account.`
)
)
.catch((e) => {
alert(`Error: ${e.message}`)
console.error(e)
})
return
}
try {
await reauthenticateWithCredential(
currentUser$.value,
credential as AuthCredential
)
} catch (e) {
console.error("error updating", e)
throw e
}
}
export function getGithubCredentialFromResult(result: UserCredential) {
return GithubAuthProvider.credentialFromResult(result)
}

View File

@@ -9,7 +9,7 @@ import {
translateToNewRESTCollection,
translateToNewGQLCollection,
} from "@hoppscotch/data"
import { currentUser$ } from "./auth"
import { platform } from "~/platform"
import {
restCollections$,
graphqlCollections$,
@@ -44,20 +44,22 @@ export async function writeCollections(
collection: any[],
flag: CollectionFlags
) {
if (currentUser$.value === null)
const currentUser = platform.auth.getCurrentUser()
if (currentUser === null)
throw new Error("User not logged in to write collections")
const cl = {
updatedOn: new Date(),
author: currentUser$.value.uid,
author_name: currentUser$.value.displayName,
author_image: currentUser$.value.photoURL,
author: currentUser.uid,
author_name: currentUser.displayName,
author_image: currentUser.photoURL,
collection,
}
try {
await setDoc(
doc(getFirestore(), "users", currentUser$.value.uid, flag, "sync"),
doc(getFirestore(), "users", currentUser.uid, flag, "sync"),
cl
)
} catch (e) {
@@ -67,10 +69,14 @@ export async function writeCollections(
}
export function initCollections() {
const currentUser$ = platform.auth.getCurrentUserStream()
const restCollSub = restCollections$.subscribe((collections) => {
const currentUser = platform.auth.getCurrentUser()
if (
loadedRESTCollections &&
currentUser$.value &&
currentUser &&
settingsStore.value.syncCollections
) {
writeCollections(collections, "collections")
@@ -80,7 +86,7 @@ export function initCollections() {
const gqlCollSub = graphqlCollections$.subscribe((collections) => {
if (
loadedGraphqlCollections &&
currentUser$.value &&
currentUser &&
settingsStore.value.syncCollections
) {
writeCollections(collections, "collectionsGraphql")

View File

@@ -6,7 +6,7 @@ import {
onSnapshot,
setDoc,
} from "firebase/firestore"
import { currentUser$ } from "./auth"
import { platform } from "~/platform"
import {
environments$,
globalEnv$,
@@ -32,26 +32,22 @@ let loadedEnvironments = false
let loadedGlobals = true
async function writeEnvironments(environment: Environment[]) {
if (currentUser$.value == null)
const currentUser = platform.auth.getCurrentUser()
if (currentUser === null)
throw new Error("Cannot write environments when signed out")
const ev = {
updatedOn: new Date(),
author: currentUser$.value.uid,
author_name: currentUser$.value.displayName,
author_image: currentUser$.value.photoURL,
author: currentUser.uid,
author_name: currentUser.displayName,
author_image: currentUser.photoURL,
environment,
}
try {
await setDoc(
doc(
getFirestore(),
"users",
currentUser$.value.uid,
"environments",
"sync"
),
doc(getFirestore(), "users", currentUser.uid, "environments", "sync"),
ev
)
} catch (e) {
@@ -61,20 +57,22 @@ async function writeEnvironments(environment: Environment[]) {
}
async function writeGlobalEnvironment(variables: Environment["variables"]) {
if (currentUser$.value == null)
const currentUser = platform.auth.getCurrentUser()
if (currentUser === null)
throw new Error("Cannot write global environment when signed out")
const ev = {
updatedOn: new Date(),
author: currentUser$.value.uid,
author_name: currentUser$.value.displayName,
author_image: currentUser$.value.photoURL,
author: currentUser.uid,
author_name: currentUser.displayName,
author_image: currentUser.photoURL,
variables,
}
try {
await setDoc(
doc(getFirestore(), "users", currentUser$.value.uid, "globalEnv", "sync"),
doc(getFirestore(), "users", currentUser.uid, "globalEnv", "sync"),
ev
)
} catch (e) {
@@ -84,9 +82,13 @@ async function writeGlobalEnvironment(variables: Environment["variables"]) {
}
export function initEnvironments() {
const currentUser$ = platform.auth.getCurrentUserStream()
const envListenSub = environments$.subscribe((envs) => {
const currentUser = platform.auth.getCurrentUser()
if (
currentUser$.value &&
currentUser &&
settingsStore.value.syncEnvironments &&
loadedEnvironments
) {
@@ -95,11 +97,9 @@ export function initEnvironments() {
})
const globalListenSub = globalEnv$.subscribe((vars) => {
if (
currentUser$.value &&
settingsStore.value.syncEnvironments &&
loadedGlobals
) {
const currentUser = platform.auth.getCurrentUser()
if (currentUser && settingsStore.value.syncEnvironments && loadedGlobals) {
writeGlobalEnvironment(vars)
}
})

View File

@@ -12,7 +12,7 @@ import {
updateDoc,
} from "firebase/firestore"
import { FormDataKeyValue } from "@hoppscotch/data"
import { currentUser$ } from "./auth"
import { platform } from "~/platform"
import { getSettingSubject, settingsStore } from "~/newstore/settings"
import {
GQLHistoryEntry,
@@ -76,7 +76,9 @@ async function writeHistory(
? purgeFormDataFromRequest(entry as RESTHistoryEntry)
: entry
if (currentUser$.value == null)
const currentUser = platform.auth.getCurrentUser()
if (currentUser === null)
throw new Error("User not logged in to sync history")
const hs = {
@@ -85,10 +87,7 @@ async function writeHistory(
}
try {
await addDoc(
collection(getFirestore(), "users", currentUser$.value.uid, col),
hs
)
await addDoc(collection(getFirestore(), "users", currentUser.uid, col), hs)
} catch (e) {
console.error("error writing to history", hs, e)
throw e
@@ -99,12 +98,14 @@ async function deleteHistory(
entry: (RESTHistoryEntry | GQLHistoryEntry) & { id: string },
col: HistoryFBCollections
) {
if (currentUser$.value == null)
const currentUser = platform.auth.getCurrentUser()
if (currentUser === null)
throw new Error("User not logged in to delete history")
try {
await deleteDoc(
doc(getFirestore(), "users", currentUser$.value.uid, col, entry.id)
doc(getFirestore(), "users", currentUser.uid, col, entry.id)
)
} catch (e) {
console.error("error deleting history", entry, e)
@@ -113,11 +114,13 @@ async function deleteHistory(
}
async function clearHistory(col: HistoryFBCollections) {
if (currentUser$.value == null)
const currentUser = platform.auth.getCurrentUser()
if (currentUser === null)
throw new Error("User not logged in to clear history")
const { docs } = await getDocs(
collection(getFirestore(), "users", currentUser$.value.uid, col)
collection(getFirestore(), "users", currentUser.uid, col)
)
await Promise.all(docs.map((e) => deleteHistory(e as any, col)))
@@ -127,12 +130,13 @@ async function toggleStar(
entry: (RESTHistoryEntry | GQLHistoryEntry) & { id: string },
col: HistoryFBCollections
) {
if (currentUser$.value == null)
throw new Error("User not logged in to toggle star")
const currentUser = platform.auth.getCurrentUser()
if (currentUser === null) throw new Error("User not logged in to toggle star")
try {
await updateDoc(
doc(getFirestore(), "users", currentUser$.value.uid, col, entry.id),
doc(getFirestore(), "users", currentUser.uid, col, entry.id),
{ star: !entry.star }
)
} catch (e) {
@@ -142,12 +146,12 @@ async function toggleStar(
}
export function initHistory() {
const currentUser$ = platform.auth.getCurrentUserStream()
const restHistorySub = restHistoryStore.dispatches$.subscribe((dispatch) => {
if (
loadedRESTHistory &&
currentUser$.value &&
settingsStore.value.syncHistory
) {
const currentUser = platform.auth.getCurrentUser()
if (loadedRESTHistory && currentUser && settingsStore.value.syncHistory) {
if (dispatch.dispatcher === "addEntry") {
writeHistory(dispatch.payload.entry, "history")
} else if (dispatch.dispatcher === "deleteEntry") {
@@ -162,9 +166,11 @@ export function initHistory() {
const gqlHistorySub = graphqlHistoryStore.dispatches$.subscribe(
(dispatch) => {
const currentUser = platform.auth.getCurrentUser()
if (
loadedGraphqlHistory &&
currentUser$.value &&
currentUser &&
settingsStore.value.syncHistory
) {
if (dispatch.dispatcher === "addEntry") {

View File

@@ -1,6 +1,6 @@
import { initializeApp } from "firebase/app"
import { platform } from "~/platform"
import { initAnalytics } from "./analytics"
import { initAuth } from "./auth"
import { initCollections } from "./collections"
import { initEnvironments } from "./environments"
import { initHistory } from "./history"
@@ -24,7 +24,7 @@ export function initializeFirebase() {
try {
initializeApp(firebaseConfig)
initAuth()
platform.auth.performAuthInit()
initSettings()
initCollections()
initHistory()

View File

@@ -10,7 +10,8 @@ import {
import { doc, getDoc, getFirestore, setDoc } from "firebase/firestore"
import { cloneDeep } from "lodash-es"
import { HoppRESTRequest, translateToNewRequest } from "@hoppscotch/data"
import { currentUser$, HoppUser } from "./auth"
import { platform } from "~/platform"
import { HoppUser } from "~/platform/auth"
import { restRequest$ } from "~/newstore/RESTSession"
/**
@@ -44,7 +45,7 @@ function writeCurrentRequest(user: HoppUser, request: HoppRESTRequest) {
* @returns Fetched request object if exists else null
*/
export async function loadRequestFromSync(): Promise<HoppRESTRequest | null> {
const currentUser = currentUser$.value
const currentUser = platform.auth.getCurrentUser()
if (!currentUser)
throw new Error("Cannot load request from sync without login")
@@ -66,6 +67,8 @@ export async function loadRequestFromSync(): Promise<HoppRESTRequest | null> {
* Unsubscribe to stop syncing.
*/
export function startRequestSync(): Subscription {
const currentUser$ = platform.auth.getCurrentUserStream()
const sub = combineLatest([
currentUser$,
restRequest$.pipe(distinctUntilChanged()),

View File

@@ -5,7 +5,7 @@ import {
onSnapshot,
setDoc,
} from "firebase/firestore"
import { currentUser$ } from "./auth"
import { platform } from "~/platform"
import { applySetting, settingsStore, SettingsType } from "~/newstore/settings"
/**
@@ -20,21 +20,23 @@ let loadedSettings = false
* Write Transform
*/
async function writeSettings(setting: string, value: any) {
if (currentUser$.value === null)
const currentUser = platform.auth.getCurrentUser()
if (currentUser === null)
throw new Error("Cannot write setting, user not signed in")
const st = {
updatedOn: new Date(),
author: currentUser$.value.uid,
author_name: currentUser$.value.displayName,
author_image: currentUser$.value.photoURL,
author: currentUser.uid,
author_name: currentUser.displayName,
author_image: currentUser.photoURL,
name: setting,
value,
}
try {
await setDoc(
doc(getFirestore(), "users", currentUser$.value.uid, "settings", setting),
doc(getFirestore(), "users", currentUser.uid, "settings", setting),
st
)
} catch (e) {
@@ -44,8 +46,12 @@ async function writeSettings(setting: string, value: any) {
}
export function initSettings() {
const currentUser$ = platform.auth.getCurrentUserStream()
settingsStore.dispatches$.subscribe((dispatch) => {
if (currentUser$.value && loadedSettings) {
const currentUser = platform.auth.getCurrentUser()
if (currentUser && loadedSettings) {
if (dispatch.dispatcher === "bulkApplySettings") {
Object.keys(dispatch.payload).forEach((key) => {
writeSettings(key, dispatch.payload[key])

View File

@@ -1,75 +0,0 @@
import { pipe } from "fp-ts/function"
import * as E from "fp-ts/Either"
import { BehaviorSubject } from "rxjs"
import { authIdToken$ } from "../fb/auth"
import { runGQLQuery } from "../backend/GQLClient"
import { GetUserInfoDocument } from "../backend/graphql"
/*
* This file deals with interfacing data provided by the
* Hoppscotch Backend server
*/
/**
* Defines the information provided about a user
*/
export interface UserInfo {
/**
* UID of the user
*/
uid: string
/**
* Displayable name of the user (or null if none available)
*/
displayName: string | null
/**
* Email of the user (or null if none available)
*/
email: string | null
/**
* URL to the profile photo of the user (or null if none available)
*/
photoURL: string | null
}
/**
* An observable subject onto the currently logged in user info (is null if not logged in)
*/
export const currentUserInfo$ = new BehaviorSubject<UserInfo | null>(null)
/**
* Initializes the currenUserInfo$ view and sets up its update mechanism
*/
export function initUserInfo() {
authIdToken$.subscribe((token) => {
if (token) {
updateUserInfo()
} else {
currentUserInfo$.next(null)
}
})
}
/**
* Runs the actual user info fetching
*/
async function updateUserInfo() {
const result = await runGQLQuery({
query: GetUserInfoDocument,
})
currentUserInfo$.next(
pipe(
result,
E.matchW(
() => null,
(x) => ({
uid: x.me.uid,
displayName: x.me.displayName ?? null,
email: x.me.email ?? null,
photoURL: x.me.photoURL ?? null,
})
)
)
)
}

View File

@@ -2,7 +2,7 @@ import * as E from "fp-ts/Either"
import { BehaviorSubject } from "rxjs"
import { GQLError, runGQLQuery } from "../backend/GQLClient"
import { GetMyTeamsDocument, GetMyTeamsQuery } from "../backend/graphql"
import { authIdToken$ } from "~/helpers/fb/auth"
import { platform } from "~/platform"
const BACKEND_PAGE_SIZE = 10
const POLL_DURATION = 10000
@@ -47,8 +47,10 @@ export default class TeamListAdapter {
}
async fetchList() {
const currentUser = platform.auth.getCurrentUser()
// if the authIdToken is not present, don't fetch the teams list, as it will fail anyway
if (!authIdToken$.value) return
if (!currentUser) return
this.loading$.next(true)