refactor: monorepo+pnpm (removed husky)

This commit is contained in:
Andrew Bastin
2021-09-10 00:28:28 +05:30
parent 917550ff4d
commit b28f82a881
445 changed files with 81301 additions and 63752 deletions

View File

@@ -0,0 +1,107 @@
import {
Analytics,
getAnalytics,
logEvent,
setAnalyticsCollectionEnabled,
setUserId,
setUserProperties,
} from "firebase/analytics"
import { authEvents$ } from "./auth"
import {
HoppAccentColor,
HoppBgColor,
settings$,
settingsStore,
} from "~/newstore/settings"
let analytics: Analytics | null = null
type SettingsCustomDimensions = {
usesProxy: boolean
usesExtension: boolean
syncCollections: boolean
syncEnvironments: boolean
syncHistory: boolean
usesBg: HoppBgColor
usesAccent: HoppAccentColor
usesTelemetry: boolean
}
type HoppRequestEvent =
| {
platform: "rest" | "graphql-query" | "graphql-schema"
strategy: "normal" | "proxy" | "extension"
}
| { platform: "wss" | "sse" | "socketio" | "mqtt" }
export function initAnalytics() {
analytics = getAnalytics()
initLoginListeners()
initSettingsListeners()
}
function initLoginListeners() {
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
})
}
} else if (ev.event === "logout") {
if (settingsStore.value.TELEMETRY_ENABLED && analytics) {
logEvent(analytics, "logout")
}
}
})
}
function initSettingsListeners() {
// Keep track of the telemetry status
let telemetryStatus = settingsStore.value.TELEMETRY_ENABLED
settings$.subscribe((settings) => {
const conf: SettingsCustomDimensions = {
usesProxy: settings.PROXY_ENABLED,
usesExtension: settings.EXTENSIONS_ENABLED,
syncCollections: settings.syncCollections,
syncEnvironments: settings.syncEnvironments,
syncHistory: settings.syncHistory,
usesAccent: settings.THEME_COLOR,
usesBg: settings.BG_COLOR,
usesTelemetry: settings.TELEMETRY_ENABLED,
}
// User toggled telemetry mode to off or to on
if (
((telemetryStatus && !settings.TELEMETRY_ENABLED) ||
settings.TELEMETRY_ENABLED) &&
analytics
) {
setUserProperties(analytics, conf)
}
telemetryStatus = settings.TELEMETRY_ENABLED
if (analytics) setAnalyticsCollectionEnabled(analytics, telemetryStatus)
})
if (analytics) setAnalyticsCollectionEnabled(analytics, telemetryStatus)
}
export function logHoppRequestRunToAnalytics(ev: HoppRequestEvent) {
if (settingsStore.value.TELEMETRY_ENABLED && analytics) {
logEvent(analytics, "hopp-request", ev)
}
}
export function logPageView(pagePath: string) {
if (settingsStore.value.TELEMETRY_ENABLED && analytics) {
logEvent(analytics, "page_view", {
page_path: pagePath,
})
}
}

View File

@@ -0,0 +1,285 @@
import {
User,
getAuth,
onAuthStateChanged,
onIdTokenChanged,
signInWithPopup,
GoogleAuthProvider,
GithubAuthProvider,
signInWithEmailAndPassword as signInWithEmailAndPass,
isSignInWithEmailLink as isSignInWithEmailLinkFB,
fetchSignInMethodsForEmail,
sendSignInLinkToEmail,
signInWithEmailLink as signInWithEmailLinkFB,
ActionCodeSettings,
signOut,
linkWithCredential,
AuthCredential,
UserCredential,
} from "firebase/auth"
import {
onSnapshot,
getFirestore,
setDoc,
doc,
updateDoc,
} from "firebase/firestore"
import {
BehaviorSubject,
distinctUntilChanged,
filter,
map,
Subject,
Subscription,
} from "rxjs"
import { onBeforeUnmount, onMounted } from "@nuxtjs/composition-api"
export type HoppUser = User & {
provider?: string
accessToken?: string
}
type AuthEvents =
| { event: "login"; user: HoppUser }
| { event: "logout" }
| { event: "authTokenUpdate"; user: HoppUser; newToken: string | null }
/**
* 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<AuthEvents>()
/**
* Initializes the firebase authentication related subjects
*/
export function initAuth() {
const auth = getAuth()
const firestore = getFirestore()
let extraSnapshotStop: (() => void) | null = null
onAuthStateChanged(auth, (user) => {
/** Whether the user was logged in before */
const wasLoggedIn = currentUser$.value !== null
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
})
} else {
authIdToken$.next(null)
}
})
}
/**
* 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 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)
}
/**
* 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
}
}
export function getGithubCredentialFromResult(result: UserCredential) {
return GithubAuthProvider.credentialFromResult(result)
}
/**
* A Vue composable function that is called when the auth status
* is being updated to being logged in (fired multiple times),
* this is also called on component mount if the login
* was already resolved before mount.
*/
export function onLoggedIn(exec: (user: HoppUser) => void) {
let sub: Subscription | null = null
onMounted(() => {
sub = currentUser$
.pipe(
map((user) => !!user), // Get a logged in status (true or false)
distinctUntilChanged(), // Don't propagate unless the status updates
filter((x) => x) // Don't propagate unless it is logged in
)
.subscribe(() => {
exec(currentUser$.value!)
})
})
onBeforeUnmount(() => {
sub?.unsubscribe()
})
}

View File

@@ -0,0 +1,154 @@
import {
collection,
doc,
getFirestore,
onSnapshot,
setDoc,
} from "firebase/firestore"
import { currentUser$ } from "./auth"
import {
restCollections$,
graphqlCollections$,
setRESTCollections,
setGraphqlCollections,
translateToNewRESTCollection,
translateToNewGQLCollection,
} from "~/newstore/collections"
import { settingsStore } from "~/newstore/settings"
type CollectionFlags = "collectionsGraphql" | "collections"
/**
* Whether the collections are loaded. If this is set to true
* Updates to the collections store are written into firebase.
*
* If you have want to update the store and not fire the store update
* subscription, set this variable to false, do the update and then
* set it to true
*/
let loadedRESTCollections = false
/**
* Whether the collections are loaded. If this is set to true
* Updates to the collections store are written into firebase.
*
* If you have want to update the store and not fire the store update
* subscription, set this variable to false, do the update and then
* set it to true
*/
let loadedGraphqlCollections = false
export async function writeCollections(
collection: any[],
flag: CollectionFlags
) {
if (currentUser$.value === 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,
collection,
}
try {
await setDoc(
doc(getFirestore(), "users", currentUser$.value.uid, flag, "sync"),
cl
)
} catch (e) {
console.error("error updating", cl, e)
throw e
}
}
export function initCollections() {
restCollections$.subscribe((collections) => {
if (
loadedRESTCollections &&
currentUser$.value &&
settingsStore.value.syncCollections
) {
writeCollections(collections, "collections")
}
})
graphqlCollections$.subscribe((collections) => {
if (
loadedGraphqlCollections &&
currentUser$.value &&
settingsStore.value.syncCollections
) {
writeCollections(collections, "collectionsGraphql")
}
})
let restSnapshotStop: (() => void) | null = null
let graphqlSnapshotStop: (() => void) | null = null
currentUser$.subscribe((user) => {
if (!user) {
if (restSnapshotStop) {
restSnapshotStop()
restSnapshotStop = null
}
if (graphqlSnapshotStop) {
graphqlSnapshotStop()
graphqlSnapshotStop = null
}
} else {
restSnapshotStop = onSnapshot(
collection(getFirestore(), "users", user.uid, "collections"),
(collectionsRef) => {
const collections: any[] = []
collectionsRef.forEach((doc) => {
const collection = doc.data()
collection.id = doc.id
collections.push(collection)
})
// Prevent infinite ping-pong of updates
loadedRESTCollections = false
// TODO: Wth is with collections[0]
if (collections.length > 0) {
setRESTCollections(
(collections[0].collection ?? []).map(
translateToNewRESTCollection
)
)
}
loadedRESTCollections = true
}
)
graphqlSnapshotStop = onSnapshot(
collection(getFirestore(), "users", user.uid, "collectionsGraphql"),
(collectionsRef) => {
const collections: any[] = []
collectionsRef.forEach((doc) => {
const collection = doc.data()
collection.id = doc.id
collections.push(collection)
})
// Prevent infinite ping-pong of updates
loadedGraphqlCollections = false
// TODO: Wth is with collections[0]
if (collections.length > 0) {
setGraphqlCollections(
(collections[0].collection ?? []).map(translateToNewGQLCollection)
)
}
loadedGraphqlCollections = true
}
)
}
})
}

View File

@@ -0,0 +1,157 @@
import {
collection,
doc,
getFirestore,
onSnapshot,
setDoc,
} from "firebase/firestore"
import { currentUser$ } from "./auth"
import {
Environment,
environments$,
globalEnv$,
replaceEnvironments,
setGlobalEnvVariables,
} from "~/newstore/environments"
import { settingsStore } from "~/newstore/settings"
/**
* Used locally to prevent infinite loop when environment sync update
* is applied to the store which then fires the store sync listener.
* When you want to update environments and not want to fire the update listener,
* set this to true and then set it back to false once it is done
*/
let loadedEnvironments = false
/**
* Used locally to prevent infinite loop when global env sync update
* is applied to the store which then fires the store sync listener.
* When you want to update global env and not want to fire the update listener,
* set this to true and then set it back to false once it is done
*/
let loadedGlobals = true
async function writeEnvironments(environment: Environment[]) {
if (currentUser$.value == 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,
environment,
}
try {
await setDoc(
doc(
getFirestore(),
"users",
currentUser$.value.uid,
"envrionments",
"sync"
),
ev
)
} catch (e) {
console.error("error updating", ev, e)
throw e
}
}
async function writeGlobalEnvironment(variables: Environment["variables"]) {
if (currentUser$.value == 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,
variables,
}
try {
await setDoc(
doc(getFirestore(), "users", currentUser$.value.uid, "globalEnv", "sync"),
ev
)
} catch (e) {
console.error("error updating", ev, e)
throw e
}
}
export function initEnvironments() {
environments$.subscribe((envs) => {
if (
currentUser$.value &&
settingsStore.value.syncEnvironments &&
loadedEnvironments
) {
writeEnvironments(envs)
}
})
globalEnv$.subscribe((vars) => {
if (
currentUser$.value &&
settingsStore.value.syncEnvironments &&
loadedGlobals
) {
writeGlobalEnvironment(vars)
}
})
let envSnapshotStop: (() => void) | null = null
let globalsSnapshotStop: (() => void) | null = null
currentUser$.subscribe((user) => {
if (!user) {
// User logged out, clean up snapshot listener
if (envSnapshotStop) {
envSnapshotStop()
envSnapshotStop = null
}
if (globalsSnapshotStop) {
globalsSnapshotStop()
globalsSnapshotStop = null
}
} else if (user) {
envSnapshotStop = onSnapshot(
collection(getFirestore(), "users", user.uid, "environments"),
(environmentsRef) => {
const environments: any[] = []
environmentsRef.forEach((doc) => {
const environment = doc.data()
environment.id = doc.id
environments.push(environment)
})
loadedEnvironments = false
if (environments.length > 0) {
replaceEnvironments(environments[0].environment)
}
loadedEnvironments = true
}
)
globalsSnapshotStop = onSnapshot(
collection(getFirestore(), "users", user.uid, "globalEnv"),
(globalsRef) => {
if (globalsRef.docs.length === 0) {
loadedGlobals = true
return
}
const doc = globalsRef.docs[0].data()
loadedGlobals = false
setGlobalEnvVariables(doc.variables)
loadedGlobals = true
}
)
}
})
}

View File

@@ -0,0 +1,206 @@
import {
addDoc,
collection,
deleteDoc,
doc,
getDocs,
getFirestore,
limit,
onSnapshot,
orderBy,
query,
updateDoc,
} from "firebase/firestore"
import { currentUser$ } from "./auth"
import { settingsStore } from "~/newstore/settings"
import {
GQLHistoryEntry,
graphqlHistoryStore,
HISTORY_LIMIT,
RESTHistoryEntry,
restHistoryStore,
setGraphqlHistoryEntries,
setRESTHistoryEntries,
translateToNewGQLHistory,
translateToNewRESTHistory,
} from "~/newstore/history"
type HistoryFBCollections = "history" | "graphqlHistory"
/**
* Whether the history are loaded. If this is set to true
* Updates to the history store are written into firebase.
*
* If you have want to update the store and not fire the store update
* subscription, set this variable to false, do the update and then
* set it to true
*/
let loadedRESTHistory = false
/**
* Whether the history are loaded. If this is set to true
* Updates to the history store are written into firebase.
*
* If you have want to update the store and not fire the store update
* subscription, set this variable to false, do the update and then
* set it to true
*/
let loadedGraphqlHistory = false
async function writeHistory(entry: any, col: HistoryFBCollections) {
if (currentUser$.value == null)
throw new Error("User not logged in to sync history")
const hs = {
...entry,
updatedOn: new Date(),
}
try {
await addDoc(
collection(getFirestore(), "users", currentUser$.value.uid, col),
hs
)
} catch (e) {
console.error("error writing to history", hs, e)
throw e
}
}
async function deleteHistory(entry: any, col: HistoryFBCollections) {
if (currentUser$.value == null)
throw new Error("User not logged in to delete history")
try {
await deleteDoc(
doc(getFirestore(), "users", currentUser$.value.uid, col, entry.id)
)
} catch (e) {
console.error("error deleting history", entry, e)
throw e
}
}
async function clearHistory(col: HistoryFBCollections) {
if (currentUser$.value == null)
throw new Error("User not logged in to clear history")
const { docs } = await getDocs(
collection(getFirestore(), "users", currentUser$.value.uid, col)
)
await Promise.all(docs.map((e) => deleteHistory(e, col)))
}
async function toggleStar(entry: any, col: HistoryFBCollections) {
if (currentUser$.value == null)
throw new Error("User not logged in to toggle star")
try {
await updateDoc(
doc(getFirestore(), "users", currentUser$.value.uid, col, entry.id),
{ star: !entry.star }
)
} catch (e) {
console.error("error toggling star", entry, e)
throw e
}
}
export function initHistory() {
restHistoryStore.dispatches$.subscribe((dispatch) => {
if (
loadedRESTHistory &&
currentUser$.value &&
settingsStore.value.syncHistory
) {
if (dispatch.dispatcher === "addEntry") {
writeHistory(dispatch.payload.entry, "history")
} else if (dispatch.dispatcher === "deleteEntry") {
deleteHistory(dispatch.payload.entry, "history")
} else if (dispatch.dispatcher === "clearHistory") {
clearHistory("history")
} else if (dispatch.dispatcher === "toggleStar") {
toggleStar(dispatch.payload.entry, "history")
}
}
})
graphqlHistoryStore.dispatches$.subscribe((dispatch) => {
if (
loadedGraphqlHistory &&
currentUser$.value &&
settingsStore.value.syncHistory
) {
if (dispatch.dispatcher === "addEntry") {
writeHistory(dispatch.payload.entry, "graphqlHistory")
} else if (dispatch.dispatcher === "deleteEntry") {
deleteHistory(dispatch.payload.entry, "graphqlHistory")
} else if (dispatch.dispatcher === "clearHistory") {
clearHistory("graphqlHistory")
} else if (dispatch.dispatcher === "toggleStar") {
toggleStar(dispatch.payload.entry, "graphqlHistory")
}
}
})
let restSnapshotStop: (() => void) | null = null
let graphqlSnapshotStop: (() => void) | null = null
currentUser$.subscribe((user) => {
if (!user) {
// Clear the snapshot listeners when the user logs out
if (restSnapshotStop) {
restSnapshotStop()
restSnapshotStop = null
}
if (graphqlSnapshotStop) {
graphqlSnapshotStop()
graphqlSnapshotStop = null
}
} else {
restSnapshotStop = onSnapshot(
query(
collection(getFirestore(), "users", user.uid, "history"),
orderBy("updatedOn", "desc"),
limit(HISTORY_LIMIT)
),
(historyRef) => {
const history: RESTHistoryEntry[] = []
historyRef.forEach((doc) => {
const entry = doc.data()
entry.id = doc.id
history.push(translateToNewRESTHistory(entry))
})
loadedRESTHistory = false
setRESTHistoryEntries(history)
loadedRESTHistory = true
}
)
graphqlSnapshotStop = onSnapshot(
query(
collection(getFirestore(), "users", user.uid, "graphqlHistory"),
orderBy("updatedOn", "desc"),
limit(HISTORY_LIMIT)
),
(historyRef) => {
const history: GQLHistoryEntry[] = []
historyRef.forEach((doc) => {
const entry = doc.data()
entry.id = doc.id
history.push(translateToNewGQLHistory(entry))
})
loadedGraphqlHistory = false
setGraphqlHistoryEntries(history)
loadedGraphqlHistory = true
}
)
}
})
}

View File

@@ -0,0 +1,40 @@
import { initializeApp } from "firebase/app"
import { initAnalytics } from "./analytics"
import { initAuth } from "./auth"
import { initCollections } from "./collections"
import { initEnvironments } from "./environments"
import { initHistory } from "./history"
import { initSettings } from "./settings"
const firebaseConfig = {
apiKey: process.env.API_KEY,
authDomain: process.env.AUTH_DOMAIN,
databaseURL: process.env.DATABASE_URL,
projectId: process.env.PROJECT_ID,
storageBucket: process.env.STORAGE_BUCKET,
messagingSenderId: process.env.MESSAGING_SENDER_ID,
appId: process.env.APP_ID,
measurementId: process.env.MEASUREMENT_ID,
}
let initialized = false
export function initializeFirebase() {
if (!initialized) {
try {
initializeApp(firebaseConfig)
initAuth()
initSettings()
initCollections()
initHistory()
initEnvironments()
initAnalytics()
initialized = true
} catch (e) {
// initializeApp throws exception if we reinitialize
initialized = true
}
}
}

View File

@@ -0,0 +1,74 @@
import {
audit,
combineLatest,
distinctUntilChanged,
EMPTY,
from,
map,
Subscription,
} from "rxjs"
import { doc, getDoc, getFirestore, setDoc } from "firebase/firestore"
import {
HoppRESTRequest,
translateToNewRequest,
} from "../types/HoppRESTRequest"
import { currentUser$, HoppUser } from "./auth"
import { restRequest$ } from "~/newstore/RESTSession"
/**
* Writes a request to a user's firestore sync
*
* @param user The user to write to
* @param request The request to write to the request sync
*/
function writeCurrentRequest(user: HoppUser, request: HoppRESTRequest) {
return setDoc(
doc(getFirestore(), "users", user.uid, "requests", "rest"),
request
)
}
/**
* Loads the synced request from the firestore sync
*
* @returns Fetched request object if exists else null
*/
export async function loadRequestFromSync(): Promise<HoppRESTRequest | null> {
const currentUser = currentUser$.value
if (!currentUser)
throw new Error("Cannot load request from sync without login")
const fbDoc = await getDoc(
doc(getFirestore(), "users", currentUser.uid, "requests", "rest")
)
const data = fbDoc.data()
if (!data) return null
else return translateToNewRequest(data)
}
/**
* Performs sync of the REST Request session with Firestore.
*
* @returns A subscription to the sync observable stream.
* Unsubscribe to stop syncing.
*/
export function startRequestSync(): Subscription {
const sub = combineLatest([
currentUser$,
restRequest$.pipe(distinctUntilChanged()),
])
.pipe(
map(([user, request]) =>
user ? from(writeCurrentRequest(user, request)) : EMPTY
),
audit((x) => x)
)
.subscribe(() => {
// NOTE: This subscription should be kept
})
return sub
}

View File

@@ -0,0 +1,93 @@
import {
collection,
doc,
getFirestore,
onSnapshot,
setDoc,
} from "@firebase/firestore"
import { currentUser$ } from "./auth"
import { applySetting, settingsStore, SettingsType } from "~/newstore/settings"
/**
* Used locally to prevent infinite loop when settings sync update
* is applied to the store which then fires the store sync listener.
* When you want to update settings and not want to fire the update listener,
* set this to true and then set it back to false once it is done
*/
let loadedSettings = false
/**
* Write Transform
*/
async function writeSettings(setting: string, value: any) {
if (currentUser$.value === 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,
name: setting,
value,
}
try {
await setDoc(
doc(getFirestore(), "users", currentUser$.value.uid, "settings", setting),
st
)
} catch (e) {
console.error("error updating", st, e)
throw e
}
}
export function initSettings() {
settingsStore.dispatches$.subscribe((dispatch) => {
if (currentUser$.value && loadedSettings) {
if (dispatch.dispatcher === "bulkApplySettings") {
Object.keys(dispatch.payload).forEach((key) => {
writeSettings(key, dispatch.payload[key])
})
} else {
writeSettings(
dispatch.payload.settingKey,
settingsStore.value[dispatch.payload.settingKey as keyof SettingsType]
)
}
}
})
let snapshotStop: (() => void) | null = null
// Subscribe and unsubscribe event listeners
currentUser$.subscribe((user) => {
if (!user && snapshotStop) {
// User logged out
snapshotStop()
snapshotStop = null
} else if (user) {
snapshotStop = onSnapshot(
collection(getFirestore(), "users", user.uid, "settings"),
(settingsRef) => {
const settings: any[] = []
settingsRef.forEach((doc) => {
const setting = doc.data()
setting.id = doc.id
settings.push(setting)
})
loadedSettings = false
settings.forEach((e) => {
if (e && e.name && e.value != null) {
applySetting(e.name, e.value)
}
})
loadedSettings = true
}
)
}
})
}