From a66a2f5645a38f93ee6a88a7a1d7899622f46168 Mon Sep 17 00:00:00 2001 From: Akash K <57758277+amk-dev@users.noreply.github.com> Date: Wed, 29 Mar 2023 23:59:28 +0530 Subject: [PATCH 1/3] chore: move settings firebase things to platform (#2953) --- .../hoppscotch-common/src/helpers/fb/index.ts | 3 +- .../hoppscotch-common/src/platform/index.ts | 2 + .../src/platform/settings.ts | 3 + packages/hoppscotch-web/src/main.ts | 2 + packages/hoppscotch-web/src/settings.ts | 108 ++++++++++++++++++ 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 packages/hoppscotch-common/src/platform/settings.ts create mode 100644 packages/hoppscotch-web/src/settings.ts diff --git a/packages/hoppscotch-common/src/helpers/fb/index.ts b/packages/hoppscotch-common/src/helpers/fb/index.ts index 9b82c20f5..a944c0a27 100644 --- a/packages/hoppscotch-common/src/helpers/fb/index.ts +++ b/packages/hoppscotch-common/src/helpers/fb/index.ts @@ -2,7 +2,6 @@ import { initializeApp } from "firebase/app" import { platform } from "~/platform" import { initAnalytics } from "./analytics" import { initHistory } from "./history" -import { initSettings } from "./settings" const firebaseConfig = { apiKey: import.meta.env.VITE_API_KEY, @@ -23,7 +22,7 @@ export function initializeFirebase() { initializeApp(firebaseConfig) platform.auth.performAuthInit() - initSettings() + platform.sync.settings.initSettingsSync() platform.sync.collections.initCollectionsSync() initHistory() platform.sync.environments.initEnvironmentsSync() diff --git a/packages/hoppscotch-common/src/platform/index.ts b/packages/hoppscotch-common/src/platform/index.ts index 5845f4065..cd8d39499 100644 --- a/packages/hoppscotch-common/src/platform/index.ts +++ b/packages/hoppscotch-common/src/platform/index.ts @@ -2,6 +2,7 @@ import { AuthPlatformDef } from "./auth" import { UIPlatformDef } from "./ui" import { EnvironmentsPlatformDef } from "./environments" import { CollectionsPlatformDef } from "./collections" +import { SettingsPlatformDef } from "./settings" export type PlatformDef = { ui?: UIPlatformDef @@ -9,6 +10,7 @@ export type PlatformDef = { sync: { environments: EnvironmentsPlatformDef collections: CollectionsPlatformDef + settings: SettingsPlatformDef } } diff --git a/packages/hoppscotch-common/src/platform/settings.ts b/packages/hoppscotch-common/src/platform/settings.ts new file mode 100644 index 000000000..fb79b2a29 --- /dev/null +++ b/packages/hoppscotch-common/src/platform/settings.ts @@ -0,0 +1,3 @@ +export type SettingsPlatformDef = { + initSettingsSync: () => void +} diff --git a/packages/hoppscotch-web/src/main.ts b/packages/hoppscotch-web/src/main.ts index e223e6493..80201251b 100644 --- a/packages/hoppscotch-web/src/main.ts +++ b/packages/hoppscotch-web/src/main.ts @@ -2,11 +2,13 @@ import { createHoppApp } from "@hoppscotch/common" import { def as authDef } from "./firebase/auth" import { def as envDef } from "./environments" import { def as collectionsDef } from "./collections" +import { def as settingsDef } from "./settings" createHoppApp("#app", { auth: authDef, sync: { environments: envDef, collections: collectionsDef, + settings: settingsDef, }, }) diff --git a/packages/hoppscotch-web/src/settings.ts b/packages/hoppscotch-web/src/settings.ts new file mode 100644 index 000000000..5331a0081 --- /dev/null +++ b/packages/hoppscotch-web/src/settings.ts @@ -0,0 +1,108 @@ +import { + collection, + doc, + getFirestore, + onSnapshot, + setDoc, +} from "firebase/firestore" +import { def as platformAuth } from "./firebase/auth" +import { + applySetting, + settingsStore, + SettingsDef, +} from "@hoppscotch/common/newstore/settings" +import { SettingsPlatformDef } from "@hoppscotch/common/platform/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) { + const currentUser = platformAuth.getCurrentUser() + + if (currentUser === null) + throw new Error("Cannot write setting, user not signed in") + + const st = { + updatedOn: new Date(), + author: currentUser.uid, + author_name: currentUser.displayName, + author_image: currentUser.photoURL, + name: setting, + value, + } + + try { + await setDoc( + doc(getFirestore(), "users", currentUser.uid, "settings", setting), + st + ) + } catch (e) { + console.error("error updating", st, e) + throw e + } +} + +export function initSettingsSync() { + const currentUser$ = platformAuth.getCurrentUserStream() + + settingsStore.dispatches$.subscribe((dispatch) => { + const currentUser = platformAuth.getCurrentUser() + + if (currentUser && 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 SettingsDef] + ) + } + } + }) + + 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 + } + ) + } + }) +} + +export const def: SettingsPlatformDef = { + initSettingsSync, +} From cc802b1e9f4fc337d3bba448dc321c36f849112a Mon Sep 17 00:00:00 2001 From: Akash K <57758277+amk-dev@users.noreply.github.com> Date: Thu, 30 Mar 2023 00:09:08 +0530 Subject: [PATCH 2/3] chore: move history firebase things to hoppscotch-web (#2954) --- .../hoppscotch-common/src/helpers/fb/index.ts | 3 +- .../hoppscotch-common/src/platform/history.ts | 3 ++ .../hoppscotch-common/src/platform/index.ts | 2 ++ .../fb => hoppscotch-web/src}/history.ts | 32 ++++++++++++------- packages/hoppscotch-web/src/main.ts | 2 ++ 5 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 packages/hoppscotch-common/src/platform/history.ts rename packages/{hoppscotch-common/src/helpers/fb => hoppscotch-web/src}/history.ts (90%) diff --git a/packages/hoppscotch-common/src/helpers/fb/index.ts b/packages/hoppscotch-common/src/helpers/fb/index.ts index a944c0a27..4c4e15524 100644 --- a/packages/hoppscotch-common/src/helpers/fb/index.ts +++ b/packages/hoppscotch-common/src/helpers/fb/index.ts @@ -1,7 +1,6 @@ import { initializeApp } from "firebase/app" import { platform } from "~/platform" import { initAnalytics } from "./analytics" -import { initHistory } from "./history" const firebaseConfig = { apiKey: import.meta.env.VITE_API_KEY, @@ -24,7 +23,7 @@ export function initializeFirebase() { platform.auth.performAuthInit() platform.sync.settings.initSettingsSync() platform.sync.collections.initCollectionsSync() - initHistory() + platform.sync.history.initHistorySync() platform.sync.environments.initEnvironmentsSync() initAnalytics() diff --git a/packages/hoppscotch-common/src/platform/history.ts b/packages/hoppscotch-common/src/platform/history.ts new file mode 100644 index 000000000..08736099b --- /dev/null +++ b/packages/hoppscotch-common/src/platform/history.ts @@ -0,0 +1,3 @@ +export type HistoryPlatformDef = { + initHistorySync: () => void +} diff --git a/packages/hoppscotch-common/src/platform/index.ts b/packages/hoppscotch-common/src/platform/index.ts index cd8d39499..5894d39c8 100644 --- a/packages/hoppscotch-common/src/platform/index.ts +++ b/packages/hoppscotch-common/src/platform/index.ts @@ -3,6 +3,7 @@ import { UIPlatformDef } from "./ui" import { EnvironmentsPlatformDef } from "./environments" import { CollectionsPlatformDef } from "./collections" import { SettingsPlatformDef } from "./settings" +import { HistoryPlatformDef } from "./history" export type PlatformDef = { ui?: UIPlatformDef @@ -11,6 +12,7 @@ export type PlatformDef = { environments: EnvironmentsPlatformDef collections: CollectionsPlatformDef settings: SettingsPlatformDef + history: HistoryPlatformDef } } diff --git a/packages/hoppscotch-common/src/helpers/fb/history.ts b/packages/hoppscotch-web/src/history.ts similarity index 90% rename from packages/hoppscotch-common/src/helpers/fb/history.ts rename to packages/hoppscotch-web/src/history.ts index f0b1cc7b0..03a254a12 100644 --- a/packages/hoppscotch-common/src/helpers/fb/history.ts +++ b/packages/hoppscotch-web/src/history.ts @@ -12,8 +12,11 @@ import { updateDoc, } from "firebase/firestore" import { FormDataKeyValue } from "@hoppscotch/data" -import { platform } from "~/platform" -import { getSettingSubject, settingsStore } from "~/newstore/settings" +import { def as platformAuth } from "./firebase/auth" +import { + getSettingSubject, + settingsStore, +} from "@hoppscotch/common/newstore/settings" import { GQLHistoryEntry, graphqlHistoryStore, @@ -24,7 +27,8 @@ import { setRESTHistoryEntries, translateToNewGQLHistory, translateToNewRESTHistory, -} from "~/newstore/history" +} from "@hoppscotch/common/newstore/history" +import { HistoryPlatformDef } from "@hoppscotch/common/platform/history" type HistoryFBCollections = "history" | "graphqlHistory" @@ -76,7 +80,7 @@ async function writeHistory( ? purgeFormDataFromRequest(entry as RESTHistoryEntry) : entry - const currentUser = platform.auth.getCurrentUser() + const currentUser = platformAuth.getCurrentUser() if (currentUser === null) throw new Error("User not logged in to sync history") @@ -98,7 +102,7 @@ async function deleteHistory( entry: (RESTHistoryEntry | GQLHistoryEntry) & { id: string }, col: HistoryFBCollections ) { - const currentUser = platform.auth.getCurrentUser() + const currentUser = platformAuth.getCurrentUser() if (currentUser === null) throw new Error("User not logged in to delete history") @@ -114,7 +118,7 @@ async function deleteHistory( } async function clearHistory(col: HistoryFBCollections) { - const currentUser = platform.auth.getCurrentUser() + const currentUser = platformAuth.getCurrentUser() if (currentUser === null) throw new Error("User not logged in to clear history") @@ -130,7 +134,7 @@ async function toggleStar( entry: (RESTHistoryEntry | GQLHistoryEntry) & { id: string }, col: HistoryFBCollections ) { - const currentUser = platform.auth.getCurrentUser() + const currentUser = platformAuth.getCurrentUser() if (currentUser === null) throw new Error("User not logged in to toggle star") @@ -145,11 +149,11 @@ async function toggleStar( } } -export function initHistory() { - const currentUser$ = platform.auth.getCurrentUserStream() +export function initHistorySync() { + const currentUser$ = platformAuth.getCurrentUserStream() const restHistorySub = restHistoryStore.dispatches$.subscribe((dispatch) => { - const currentUser = platform.auth.getCurrentUser() + const currentUser = platformAuth.getCurrentUser() if (loadedRESTHistory && currentUser && settingsStore.value.syncHistory) { if (dispatch.dispatcher === "addEntry") { @@ -166,7 +170,7 @@ export function initHistory() { const gqlHistorySub = graphqlHistoryStore.dispatches$.subscribe( (dispatch) => { - const currentUser = platform.auth.getCurrentUser() + const currentUser = platformAuth.getCurrentUser() if ( loadedGraphqlHistory && @@ -262,7 +266,11 @@ export function initHistory() { gqlHistorySub.unsubscribe() currentUserSub.unsubscribe() - initHistory() + initHistorySync() } }) } + +export const def: HistoryPlatformDef = { + initHistorySync, +} diff --git a/packages/hoppscotch-web/src/main.ts b/packages/hoppscotch-web/src/main.ts index 80201251b..08bfbdb32 100644 --- a/packages/hoppscotch-web/src/main.ts +++ b/packages/hoppscotch-web/src/main.ts @@ -3,6 +3,7 @@ import { def as authDef } from "./firebase/auth" import { def as envDef } from "./environments" import { def as collectionsDef } from "./collections" import { def as settingsDef } from "./settings" +import { def as historyDef } from "./history" createHoppApp("#app", { auth: authDef, @@ -10,5 +11,6 @@ createHoppApp("#app", { environments: envDef, collections: collectionsDef, settings: settingsDef, + history: historyDef, }, }) From dbb45e7253e67fa9538525815a8cc1fe707fbf02 Mon Sep 17 00:00:00 2001 From: Akash K <57758277+amk-dev@users.noreply.github.com> Date: Thu, 30 Mar 2023 15:13:25 +0530 Subject: [PATCH 3/3] chore: add removeDuplicateEntry dispatcher to history store (#2955) --- .../hoppscotch-common/src/newstore/history.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/hoppscotch-common/src/newstore/history.ts b/packages/hoppscotch-common/src/newstore/history.ts index 6943651c1..e4e5d53d9 100644 --- a/packages/hoppscotch-common/src/newstore/history.ts +++ b/packages/hoppscotch-common/src/newstore/history.ts @@ -163,6 +163,20 @@ const RESTHistoryDispatchers = defineDispatchers({ }), } }, + // only used for history.sync.ts to prevent double insertion of history entries from storeSync and Subscriptions + removeDuplicateEntry(currentVal: RESTHistoryType, { id }: { id: string }) { + const entries = currentVal.state.filter((e) => e.id === id) + + if (entries.length == 2) { + const indexToRemove = currentVal.state.findIndex((e) => e.id === id) + + currentVal.state.splice(indexToRemove, 1) + } + + return { + state: currentVal.state, + } + }, }) const GQLHistoryDispatchers = defineDispatchers({ @@ -212,6 +226,20 @@ const GQLHistoryDispatchers = defineDispatchers({ }), } }, + // only used for history.sync.ts to prevent double insertion of history entries from storeSync and Subscriptions + removeDuplicateEntry(currentVal: GraphqlHistoryType, { id }: { id: string }) { + const entries = currentVal.state.filter((e) => e.id === id) + + if (entries.length == 2) { + const indexToRemove = currentVal.state.findIndex((e) => e.id === id) + + currentVal.state.splice(indexToRemove, 1) + } + + return { + state: currentVal.state, + } + }, }) export const restHistoryStore = new DispatchingStore( @@ -297,6 +325,20 @@ export function toggleGraphqlHistoryEntryStar(entry: GQLHistoryEntry) { }) } +export function removeDuplicateRestHistoryEntry(id: string) { + restHistoryStore.dispatch({ + dispatcher: "removeDuplicateEntry", + payload: { id }, + }) +} + +export function removeDuplicateGraphqlHistoryEntry(id: string) { + graphqlHistoryStore.dispatch({ + dispatcher: "removeDuplicateEntry", + payload: { id }, + }) +} + // Listen to completed responses to add to history completedRESTResponse$.subscribe((res) => { if (res !== null) {