From 36842306320cf443a4e965c4eb7e2de5f19a109a Mon Sep 17 00:00:00 2001 From: Bruno Bernardino Date: Tue, 4 Jan 2022 16:07:53 +0000 Subject: [PATCH] Explore end-to-end encryption for data (v2, userbase) This enables end-to-end encryption via Userbase. Everything works and is fast (unlike Etebase), though it still lacks billing control. All dependencies were updated, as the starting point was #1 --- .env.sample | 2 +- README.md | 10 +- components/BudgetModal.tsx | 8 +- components/ExpenseModal.tsx | 8 +- components/ImportExportModal.tsx | 9 +- components/Panels/AddExpense.tsx | 6 +- components/Panels/All.tsx | 37 +- components/Panels/Budgets.tsx | 2 - components/Panels/Expenses.tsx | 2 - components/Panels/Settings.tsx | 13 +- lib/data-utils.ts | 751 +++++++++++++------------------ lib/types.ts | 4 - lib/utils.ts | 19 +- modules/auth/LoginButton.tsx | 64 ++- next.config.js | 2 +- package-lock.json | 383 ++++------------ package.json | 5 +- pages/index.tsx | 7 +- 18 files changed, 474 insertions(+), 858 deletions(-) diff --git a/.env.sample b/.env.sample index db2d2d9..efb69c7 100644 --- a/.env.sample +++ b/.env.sample @@ -1,4 +1,4 @@ NODE_PATH=/ AWS_ACCESS_KEY_ID=accesskey AWS_SECRET_ACCESS_KEY=sshhh -NEXT_PUBLIC_ETEBASE_SERVER_URL=https://api.etebase.com/example +NEXT_PUBLIC_USERBASE_APP_ID=get-from-userbase.com diff --git a/README.md b/README.md index 0c4bfd5..030e5e8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is the web app for the [Budget Zen app](https://budgetzen.net), built with Next.js and deployed to AWS with Serverless. -It's v2, meaning it is [end-to-end encrypted via etebase](https://etebase.com), and requires an email + [token](https://budgetzen.net/get-sync-token) to work. +It's v2, meaning it is [end-to-end encrypted via userbase](https://userbase.com), and requires an email + password to work. It also means it's not compatible with Budget Zen v1, which you can still get locally from [this commit](https://github.com/BrunoBernardino/budgetzen-web/tree/397d625469b7dfd8d1968c847b32e607ee7c8ee9). @@ -20,11 +20,7 @@ make deploy # deploys to v2.budgetzen.net (requires `serverless` to be installe ## TODOs -- [ ] Implement billing in signup - -## TODOs for later - -- [ ] Implement encrypted session (always ask for a pin)? +- [ ] Implement billing: https://userbase.com/docs/sdk/purchase-subscription/ +- [ ] Implement changing/forgetting password: https://userbase.com/docs/sdk/forgot-password/ && https://userbase.com/docs/sdk/update-user/ - [ ] Improve UI/UX in general - [ ] Improve dark/light mode -- [ ] Implement [Signed Pages](https://github.com/tasn/webext-signed-pages)? diff --git a/components/BudgetModal.tsx b/components/BudgetModal.tsx index a3b694a..73b9acf 100644 --- a/components/BudgetModal.tsx +++ b/components/BudgetModal.tsx @@ -2,7 +2,6 @@ import React, { useState, useCallback } from 'react'; import styled from 'styled-components'; import Rodal from 'rodal'; import Swal from 'sweetalert2'; -import * as Etebase from 'etebase'; import Button from 'components/Button'; import { showNotification } from 'lib/utils'; @@ -18,7 +17,6 @@ interface BudgetModalProps { month: string; value: number; reloadData: () => Promise; - etebase: Etebase.Account; } const Container = styled.section` @@ -65,7 +63,7 @@ const BudgetModal = (props: BudgetModalProps) => { const [month, setMonth] = useState(`${props.month}-01`); const [value, setValue] = useState(props.value.toString()); - const { id, isOpen, reloadData, etebase } = props; + const { id, isOpen, reloadData } = props; const onClose = useCallback(() => { const { onClose: closeModal } = props; @@ -90,7 +88,7 @@ const BudgetModal = (props: BudgetModalProps) => { month: month ? month.substring(0, 7) : '', }; - const success = await saveBudget(etebase, parsedBudget); + const success = await saveBudget(parsedBudget); setIsSubmitting(false); @@ -124,7 +122,7 @@ const BudgetModal = (props: BudgetModalProps) => { setIsSubmitting(true); - const success = await deleteBudget(etebase, id); + const success = await deleteBudget(id); setIsSubmitting(false); diff --git a/components/ExpenseModal.tsx b/components/ExpenseModal.tsx index 0394f31..c825cf0 100644 --- a/components/ExpenseModal.tsx +++ b/components/ExpenseModal.tsx @@ -2,7 +2,6 @@ import React, { useState, useCallback } from 'react'; import styled from 'styled-components'; import Rodal from 'rodal'; import Swal from 'sweetalert2'; -import * as Etebase from 'etebase'; import Button from 'components/Button'; import { showNotification } from 'lib/utils'; @@ -20,7 +19,6 @@ interface ExpenseModalProps { date: string; budgets: T.Budget[]; reloadData: () => Promise; - etebase: Etebase.Account; } const Container = styled.section` @@ -89,7 +87,7 @@ const ExpenseModal = (props: ExpenseModalProps) => { const [budget, setBudget] = useState(props.budget); const [date, setDate] = useState(props.date); - const { id, isOpen, budgets, reloadData, etebase } = props; + const { id, isOpen, budgets, reloadData } = props; const onClose = useCallback(() => { const { onClose: closeModal } = props; @@ -116,7 +114,7 @@ const ExpenseModal = (props: ExpenseModalProps) => { date, }; - const success = await saveExpense(etebase, parsedExpense); + const success = await saveExpense(parsedExpense); setIsSubmitting(false); @@ -150,7 +148,7 @@ const ExpenseModal = (props: ExpenseModalProps) => { setIsSubmitting(true); - const success = await deleteExpense(etebase, id); + const success = await deleteExpense(id); setIsSubmitting(false); diff --git a/components/ImportExportModal.tsx b/components/ImportExportModal.tsx index 47c8f76..e3fbc27 100644 --- a/components/ImportExportModal.tsx +++ b/components/ImportExportModal.tsx @@ -2,7 +2,6 @@ import React, { useState } from 'react'; import styled from 'styled-components'; import Rodal from 'rodal'; import Swal from 'sweetalert2'; -import * as Etebase from 'etebase'; import Button from 'components/Button'; import { showNotification } from 'lib/utils'; @@ -16,8 +15,6 @@ type ImportedFileData = { }; interface ImportExportModalProps { - etebase: Etebase.Account; - session: string; isOpen: boolean; onClose: () => void; setIsLoading: (isLoading: boolean) => void; @@ -50,7 +47,7 @@ const Note = styled.span` const ImportExportModal = (props: ImportExportModalProps) => { const [isSubmitting, setIsSubmitting] = useState(false); - const { isOpen, onClose, etebase, session, setIsLoading } = props; + const { isOpen, onClose, setIsLoading } = props; const onRequestImport = async () => { if (isSubmitting) { @@ -117,8 +114,6 @@ const ImportExportModal = (props: ImportExportModalProps) => { setIsLoading(true); const success = await importData( - etebase, - session, mergeOrReplaceDialogResult.isDenied, budgets, expenses, @@ -149,7 +144,7 @@ const ImportExportModal = (props: ImportExportModalProps) => { .substring(0, 19) .replace(/:/g, '-')}.json`; - const exportData = await exportAllData(etebase); + const exportData = await exportAllData(); const exportContents = JSON.stringify(exportData, null, 2); diff --git a/components/Panels/AddExpense.tsx b/components/Panels/AddExpense.tsx index e7e2924..a073fa0 100644 --- a/components/Panels/AddExpense.tsx +++ b/components/Panels/AddExpense.tsx @@ -1,6 +1,5 @@ import React, { useState, useCallback } from 'react'; import styled from 'styled-components'; -import * as Etebase from 'etebase'; import Button from 'components/Button'; import { colors, fontSizes } from 'lib/constants'; @@ -11,7 +10,6 @@ import * as T from 'lib/types'; interface AddExpenseProps { budgets: T.Budget[]; reloadData: () => Promise; - etebase: Etebase.Account; } const Container = styled.section` @@ -89,7 +87,7 @@ const Select = styled.select` } `; -const AddExpense = ({ budgets, reloadData, etebase }: AddExpenseProps) => { +const AddExpense = ({ budgets, reloadData }: AddExpenseProps) => { const [isSubmitting, setIsSubmitting] = useState(false); const [description, setDescription] = useState(''); const [cost, setCost] = useState(''); @@ -112,7 +110,7 @@ const AddExpense = ({ budgets, reloadData, etebase }: AddExpenseProps) => { date, }; - const success = await saveExpense(etebase, parsedExpense); + const success = await saveExpense(parsedExpense); setIsSubmitting(false); diff --git a/components/Panels/All.tsx b/components/Panels/All.tsx index f17baf2..7b1b895 100644 --- a/components/Panels/All.tsx +++ b/components/Panels/All.tsx @@ -1,8 +1,7 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import moment from 'moment'; import styled from 'styled-components'; import { useAsync } from 'react-use'; -import * as Etebase from 'etebase'; import LogoutLink from 'modules/auth/LogoutLink'; import { Loading } from 'components'; @@ -46,11 +45,9 @@ const All = () => { const [isLoading, setIsLoading] = useState(true); const [monthInView, setMonthInView] = useState(moment().format('YYYY-MM')); const [currency, setCurrency] = useState('USD'); - const [session, setSession] = useState(''); const [theme, setTheme] = useState('light'); const [budgets, setBudgets] = useState([]); const [expenses, setExpenses] = useState([]); - const etebase = useRef(null); type ReloadData = (options?: { monthToLoad?: string; @@ -62,16 +59,10 @@ const All = () => { } = {}) => { setIsLoading(true); - const fetchedBudgets = await fetchBudgets( - etebase.current, - monthToLoad || monthInView, - ); + const fetchedBudgets = await fetchBudgets(monthToLoad || monthInView); setBudgets(fetchedBudgets); - const fetchedExpenses = await fetchExpenses( - etebase.current, - monthToLoad || monthInView, - ); + const fetchedExpenses = await fetchExpenses(monthToLoad || monthInView); setExpenses(fetchedExpenses); // If this is for the current or next month and there are no budgets, create budgets based on the previous/current month. @@ -84,7 +75,7 @@ const All = () => { (monthToLoad && monthToLoad === nextMonth) || (!monthToLoad && monthInView === nextMonth) ) { - await copyBudgets(etebase.current, currentMonth, nextMonth); + await copyBudgets(currentMonth, nextMonth); await reloadData({ monthToLoad, isComingFromEmptyState: true }); return; } @@ -93,7 +84,7 @@ const All = () => { (monthToLoad && monthToLoad === currentMonth) || (!monthToLoad && monthInView === currentMonth) ) { - await copyBudgets(etebase.current, previousMonth, currentMonth); + await copyBudgets(previousMonth, currentMonth); await reloadData({ monthToLoad, isComingFromEmptyState: true }); return; } @@ -120,16 +111,10 @@ const All = () => { const userInfo = getUserInfo(); setCurrency(userInfo.currency); setTheme(userInfo.theme || 'light'); - setSession(userInfo.session); - const initializedDb = await initializeDb(userInfo.session); - etebase.current = initializedDb; + await initializeDb(); await reloadData(); - - showNotification( - 'Data is continuously synchronizing in the background. Navigate between months to see the latest data.', - ); } }, []); @@ -155,7 +140,6 @@ const All = () => { budgets={budgets} expenses={expenses} reloadData={reloadData} - etebase={etebase.current} /> { budgets={budgets} expenses={expenses} reloadData={reloadData} - etebase={etebase.current} /> - + { const [isBudgetModalOpen, setIsBudgetModalOpen] = useState(false); const [chosenBudget, setChosenBudget] = useState({ @@ -148,7 +147,6 @@ const Budgets = ({ isOpen={isBudgetModalOpen} onClose={() => closeBudgetModal()} reloadData={reloadData} - etebase={etebase} {...chosenBudget} /> diff --git a/components/Panels/Expenses.tsx b/components/Panels/Expenses.tsx index f559352..7c8febf 100644 --- a/components/Panels/Expenses.tsx +++ b/components/Panels/Expenses.tsx @@ -104,7 +104,6 @@ const Expenses = ({ budgets, currency, reloadData, - etebase, }: ExpensesProps) => { const [filterExpenseDescription, setFilterExpenseDescription] = useState(''); const [filterBudgets, setFilterBudgets] = useState>(new Set()); @@ -181,7 +180,6 @@ const Expenses = ({ onClose={() => closeExpenseModal()} budgets={budgets} reloadData={reloadData} - etebase={etebase} {...chosenExpense} /> void; currentTheme: T.Theme; updateTheme: (theme: T.Theme) => void; - session: string; - etebase: Etebase.Account; setIsLoading: (isLoading: boolean) => void; reloadData: () => Promise; } @@ -80,8 +77,6 @@ const Settings = ({ updateCurrency, currentTheme, updateTheme, - session, - etebase, setIsLoading, reloadData, }: SettingsProps) => { @@ -107,7 +102,7 @@ const Settings = ({ setIsSubmitting(true); - const success = doLogin(session, newCurrency, currentTheme); + const success = updatePreferences(newCurrency, currentTheme); if (success) { updateCurrency(newCurrency); @@ -127,7 +122,7 @@ const Settings = ({ setIsSubmitting(true); - const success = doLogin(session, currentCurrency, newTheme); + const success = updatePreferences(currentCurrency, newTheme); if (success) { updateTheme(newTheme); @@ -208,8 +203,6 @@ const Settings = ({ { setIsImportExportModalOpen(false); diff --git a/lib/data-utils.ts b/lib/data-utils.ts index a24f683..ece58cf 100644 --- a/lib/data-utils.ts +++ b/lib/data-utils.ts @@ -1,53 +1,37 @@ -import * as Etebase from 'etebase'; +import userbase from 'userbase-js'; import Swal from 'sweetalert2'; import moment from 'moment'; import { sortByName, sortByDate, + splitArrayInChunks, showNotification, - doLogin, - uniqBy, } from './utils'; import * as T from './types'; -const ETEBASE_SERVER_URL = process.env.NEXT_PUBLIC_ETEBASE_SERVER_URL; +const USERBASE_APP_ID = process.env.NEXT_PUBLIC_USERBASE_APP_ID; -const collectionTypes = { - budget: 'budgetzen.budget', - expense: 'budgetzen.expense', -}; - -const collectionUids = { - budgets: '', - expenses: '', -}; - -// While this defeats the purpose of e2ee a little bit, the app is just too slow without it. const cachedData: { budgets: T.Budget[]; expenses: T.Expense[] } = { budgets: [], expenses: [], }; -const hasStartedLoading = { - budgets: false, - expenses: false, -}; - const hasFinishedLoading = { budgets: false, expenses: false, }; -export const validateLogin = async (email: string, syncToken: string) => { +const sessionLengthInHours = 90 * 24; + +export const validateLogin = async (email: string, password: string) => { try { - const etebase = await Etebase.Account.login( - email, - syncToken, - ETEBASE_SERVER_URL, - ); - const savedSession = await etebase.save(); - doLogin(savedSession); + await userbase.signIn({ + username: email, + password, + sessionLength: sessionLengthInHours, + rememberMe: 'local', + }); return { success: true }; } catch (error) { console.log(error); @@ -55,182 +39,104 @@ export const validateLogin = async (email: string, syncToken: string) => { } }; -export const createAccount = async (email: string, syncToken: string) => { - try { - const etebase = await Etebase.Account.signup( - { - username: Etebase.toBase64(Etebase.randomBytes(24)), - email, - }, - syncToken, - ETEBASE_SERVER_URL, - ); - const savedSession = await etebase.save(); - doLogin(savedSession); - return true; - } catch (error) { - console.log(error); - return false; - } -}; - -export const initializeDb = async (session: string) => { - if (!session) { - return null; - } - - const etebase = await Etebase.Account.restore(session); - - const collectionManager = etebase.getCollectionManager(); +export const createAccount = async (email: string, password: string) => { try { - const collections = await collectionManager.list([ - collectionTypes.budget, - collectionTypes.expense, - ]); - - if (collections.data.length === 0) { - const budgetsCollection = await collectionManager.create( - collectionTypes.budget, - { - name: 'Budgets', - description: 'BudgetZen Budgets', - color: '#7fa780ff', - }, - '', // Empty content - ); - const expensesCollection = await collectionManager.create( - collectionTypes.expense, - { - name: 'Expenses', - description: 'BudgetZen Expenses', - color: '#84848aff', - }, - '', // Empty content - ); - - await collectionManager.transaction(budgetsCollection); - await collectionManager.transaction(expensesCollection); - - collectionUids.budgets = budgetsCollection.uid; - collectionUids.expenses = expensesCollection.uid; - } else { - for (const collection of collections.data) { - const collectionType = collection.getCollectionType(); - if (collectionType === collectionTypes.budget) { - collectionUids.budgets = collection.uid; - } else if (collectionType === collectionTypes.expense) { - collectionUids.expenses = collection.uid; - } - } - } + await userbase.signUp({ + username: email, + password, + sessionLength: sessionLengthInHours, + rememberMe: 'local', + email, + }); + return { success: true }; } catch (error) { console.log(error); - showNotification(error, 'error'); - return null; + return { success: false, error }; } - - return etebase; }; -const getBudgetFromItem = async (item: Etebase.Item) => { +const getBudgetFromItem = (item: userbase.Item) => { try { - const data: T.BudgetContent = JSON.parse( - await item.getContent(Etebase.OutputFormat.String), - ); - return { - id: item.uid, - name: data.name, - month: data.month, - value: data.value, + id: item.itemId, + name: item.item.name, + month: item.item.month, + value: item.item.value, } as T.Budget; } catch (error) { return null; } }; -const getExpenseFromItem = async (item: Etebase.Item) => { +const getExpenseFromItem = (item: userbase.Item) => { try { - const data: T.ExpenseContent = JSON.parse( - await item.getContent(Etebase.OutputFormat.String), - ); - return { - id: item.uid, - cost: data.cost, - description: data.description, - budget: data.budget, - date: data.date, + id: item.itemId, + cost: item.item.cost, + description: item.item.description, + budget: item.item.budget, + date: item.item.date, } as T.Expense; } catch (error) { return null; } }; -const loadItemsAsync = async ( - type: 'budgets' | 'expenses', - itemManager: Etebase.ItemManager, - stoken?: string, -) => { - if (hasFinishedLoading[type] || (hasStartedLoading[type] && !stoken)) { - return cachedData[type]; - } - - const items = await itemManager.list({ limit: 500, stoken }); - - // TODO: Remove this - console.log('======== data-utils.loadItemsAsync -- items'); - console.log(items); - - if (type === 'budgets') { - hasStartedLoading.budgets = true; - - const budgets = ( - await Promise.all(items.data.map(getBudgetFromItem)) - ).filter((budget) => Boolean(budget)); +const loadItemsAsync = async () => { + await userbase.openDatabase({ + databaseName: 'budgets', + changeHandler: async (items) => { + // TODO: Remove this + console.log('======== data-utils.loadItemsAsync.budgets'); + console.log(items.length); + const budgets = items + .map(getBudgetFromItem) + .filter((budget) => Boolean(budget)); - if (!items.done) { - // eslint-disable-next-line - loadItemsAsync(type, itemManager, items.stoken); - } else { hasFinishedLoading.budgets = true; - } - - cachedData.budgets = uniqBy([...cachedData.budgets, ...budgets], 'id'); - return cachedData.budgets; - } + cachedData.budgets = budgets; + }, + }); - hasStartedLoading.expenses = true; + await userbase.openDatabase({ + databaseName: 'expenses', + changeHandler: (items) => { + // TODO: Remove this + console.log('======== data-utils.loadItemsAsync.expenses'); + console.log(items.length); + const expenses = items + .map(getExpenseFromItem) + .filter((expense) => Boolean(expense)); - const expenses = ( - await Promise.all(items.data.map(getExpenseFromItem)) - ).filter((expense) => Boolean(expense)); + hasFinishedLoading.expenses = true; - if (!items.done) { - // eslint-disable-next-line - loadItemsAsync(type, itemManager, items.stoken); - } else { - hasFinishedLoading.expenses = true; - } + cachedData.expenses = expenses; + }, + }); +}; - cachedData.expenses = uniqBy([...cachedData.expenses, ...expenses], 'id'); +export const initializeDb = async () => { + try { + await userbase.init({ appId: USERBASE_APP_ID }); - return cachedData.expenses; + await loadItemsAsync(); + } catch (error) { + console.log(error); + showNotification(error, 'error'); + } }; -export const fetchBudgets = async ( - etebase: Etebase.Account, - month?: string, -) => { +export const fetchBudgets = async (month?: string) => { try { - const collectionManager = etebase.getCollectionManager(); - const collection = await collectionManager.fetch(collectionUids.budgets); - const itemManager = collectionManager.getItemManager(collection); + // Very ugly, but... works. + while (!hasFinishedLoading.budgets) { + await new Promise((resolve) => { + setTimeout(resolve, 100); + }); + } - const budgets: T.Budget[] = ( - (await loadItemsAsync('budgets', itemManager)) as T.Budget[] - ) + const budgets = cachedData.budgets .filter((budget) => { if (!month) { return true; @@ -257,18 +163,16 @@ export const fetchBudgets = async ( return []; }; -export const fetchExpenses = async ( - etebase: Etebase.Account, - month?: string, -) => { +export const fetchExpenses = async (month?: string) => { try { - const collectionManager = etebase.getCollectionManager(); - const collection = await collectionManager.fetch(collectionUids.expenses); - const itemManager = collectionManager.getItemManager(collection); + // Very ugly, but... works. + while (!hasFinishedLoading.expenses) { + await new Promise((resolve) => { + setTimeout(resolve, 100); + }); + } - const expenses = ( - (await loadItemsAsync('expenses', itemManager)) as T.Expense[] - ) + const expenses = cachedData.expenses .filter((expense) => { if (!month) { return true; @@ -296,10 +200,7 @@ export const fetchExpenses = async ( return []; }; -export const saveBudget = async ( - etebase: Etebase.Account, - budget: T.Budget, -) => { +export const saveBudget = async (budget: T.Budget) => { try { if (budget.name === 'Total') { showNotification('Cannot create budget named "Total".', 'error'); @@ -321,7 +222,7 @@ export const saveBudget = async ( } // Check if the name is unique for the given month - const existingBudgetsInMonth = await fetchBudgets(etebase, budget.month); + const existingBudgetsInMonth = await fetchBudgets(budget.month); const duplicateBudget = existingBudgetsInMonth.find( (existingBudget) => existingBudget.name === budget.name && existingBudget.id !== budget.id, @@ -335,86 +236,78 @@ export const saveBudget = async ( return false; } - const collectionManager = etebase.getCollectionManager(); - const collection = await collectionManager.fetch(collectionUids.budgets); - const itemManager = collectionManager.getItemManager(collection); - if (budget.id === 'newBudget') { - const item = await itemManager.create( - { - type: 'budget', - mtime: new Date().getTime(), - }, - JSON.stringify({ + budget.id = `${Date.now().toString()}:${Math.random()}`; + + await userbase.insertItem({ + databaseName: 'budgets', + item: { name: budget.name, value: budget.value, month: budget.month, - } as T.BudgetContent), - ); - budget.id = item.uid; - - await itemManager.batch([item]); - - cachedData.budgets.push({ - id: budget.id, - name: budget.name, - value: budget.value, - month: budget.month, + } as T.BudgetContent, + itemId: budget.id, }); } else { - const existingBudgetItem = await itemManager.fetch(budget.id); - const existingBudget = await getBudgetFromItem(existingBudgetItem); + const existingBudget = cachedData.budgets.find( + (_budget) => _budget.id === budget.id, + ); const oldName = existingBudget.name; const newName = budget.name; - await existingBudgetItem.setContent( - JSON.stringify({ + await userbase.updateItem({ + databaseName: 'budgets', + item: { name: budget.name, value: budget.value, month: existingBudget.month, // Don't allow changing a budget's month - } as T.BudgetContent), - ); - await itemManager.batch([existingBudgetItem]); + } as T.BudgetContent, + itemId: budget.id, + }); - const cachedBudgetItemIndex = cachedData.budgets.findIndex( + const cachedBudgetIndex = cachedData.budgets.findIndex( (_budget) => _budget.id === budget.id, ); - if (cachedBudgetItemIndex !== -1) { - cachedData.budgets[cachedBudgetItemIndex].name = budget.name; - cachedData.budgets[cachedBudgetItemIndex].value = budget.value; + if (cachedBudgetIndex !== -1) { + cachedData.budgets[cachedBudgetIndex].name = budget.name; + cachedData.budgets[cachedBudgetIndex].value = budget.value; } // Update all expenses with the previous budget name to the new one, if it changed if (oldName !== newName) { const matchingExpenses = ( - await fetchExpenses(etebase, existingBudget.month) + await fetchExpenses(existingBudget.month) ).filter((expense) => expense.budget === oldName); - const expensesCollection = await collectionManager.fetch( - collectionUids.expenses, - ); - const expensesItemManager = - collectionManager.getItemManager(expensesCollection); - const expenseItemsToUpdate = []; - for (const expense of matchingExpenses) { - const expenseItem = await expensesItemManager.fetch(expense.id); - await expenseItem.setContent( - JSON.stringify({ - cost: expense.cost, - description: expense.description, - budget: newName, - date: expense.date, - } as T.ExpenseContent), - ); - expenseItemsToUpdate.push(expenseItem); - const cachedItemIndex = cachedData.expenses.findIndex( + for (const expense of matchingExpenses) { + const cachedExpenseIndex = cachedData.expenses.findIndex( (_expense) => _expense.id === expense.id, ); - if (cachedItemIndex !== -1) { - cachedData.expenses[cachedItemIndex].budget = newName; + if (cachedExpenseIndex !== -1) { + cachedData.expenses[cachedExpenseIndex].budget = newName; } } - await expensesItemManager.batch(expenseItemsToUpdate); + + const updateChunks: T.Expense[][] = splitArrayInChunks( + matchingExpenses, + 10, + ); + + for (const machingExpensesChunk of updateChunks) { + await userbase.putTransaction({ + databaseName: 'expenses', + operations: machingExpensesChunk.map((expense) => ({ + command: 'Update', + item: { + cost: expense.cost, + description: expense.description, + budget: newName, + date: expense.date, + } as T.ExpenseContent, + itemId: expense.id, + })), + }); + } } } @@ -431,28 +324,22 @@ export const saveBudget = async ( return false; }; -export const deleteBudget = async ( - etebase: Etebase.Account, - budgetId: string, -) => { +export const deleteBudget = async (budgetId: string) => { try { - const collectionManager = etebase.getCollectionManager(); - const collection = await collectionManager.fetch(collectionUids.budgets); - const itemManager = collectionManager.getItemManager(collection); - const item = await itemManager.fetch(budgetId); - - const existingBudget = await getBudgetFromItem(item); + const existingBudget = cachedData.budgets.find( + (budget) => budget.id === budgetId, + ); // Check if the budget has no expenses, if so, don't delete - const matchingExpenses = ( - await fetchExpenses(etebase, existingBudget.month) - ).filter((expense) => expense.budget === existingBudget.name); + const matchingExpenses = (await fetchExpenses(existingBudget.month)).filter( + (expense) => expense.budget === existingBudget.name, + ); if (matchingExpenses.length > 0) { // Check if there are duplicate budgets (can happen on slow sync) - const matchingBudgets = ( - await fetchBudgets(etebase, existingBudget.month) - ).filter((budget) => budget.name === existingBudget.name); + const matchingBudgets = (await fetchBudgets(existingBudget.month)).filter( + (budget) => budget.name === existingBudget.name, + ); if (matchingBudgets.length === 1) { showNotification( @@ -463,8 +350,10 @@ export const deleteBudget = async ( } } - item.delete(); - await itemManager.batch([item]); + await userbase.deleteItem({ + databaseName: 'budgets', + itemId: budgetId, + }); const cachedItemIndex = cachedData.budgets.findIndex( (budget) => budget.id === budgetId, @@ -486,10 +375,7 @@ export const deleteBudget = async ( return false; }; -export const saveExpense = async ( - etebase: Etebase.Account, - expense: T.Expense, -) => { +export const saveExpense = async (expense: T.Expense) => { try { if (!expense.cost || typeof expense.cost !== 'number') { showNotification('Cost missing or invalid', 'error'); @@ -510,7 +396,7 @@ export const saveExpense = async ( (!expense.budget || expense.budget === 'Misc') && expense.id === 'newExpense' ) { - const matchingExpense = (await fetchExpenses(etebase)).find( + const matchingExpense = (await fetchExpenses()).find( (_expense) => _expense.description === expense.description, ); @@ -525,77 +411,47 @@ export const saveExpense = async ( // Check if the budget exists for the expense in that given month, otherwise create one const existingBudget = ( - await fetchBudgets(etebase, expense.date.substring(0, 7)) + await fetchBudgets(expense.date.substring(0, 7)) ).find((budget) => budget.name === expense.budget); if (!existingBudget) { - const collectionManager = etebase.getCollectionManager(); - const collection = await collectionManager.fetch(collectionUids.budgets); - const itemManager = collectionManager.getItemManager(collection); - - const item = await itemManager.create( - { - type: 'budget', - mtime: new Date().getTime(), - }, - JSON.stringify({ + const newBudgetId = `${Date.now().toString()}:${Math.random()}`; + + await userbase.insertItem({ + databaseName: 'budgets', + item: { name: expense.budget, month: expense.date.substring(0, 7), value: 100, - } as T.BudgetContent), - ); - - await itemManager.batch([item]); - - cachedData.budgets.push({ - id: item.uid, - name: expense.budget, - value: 100, - month: expense.date.substring(0, 7), + } as T.BudgetContent, + itemId: newBudgetId, }); } - const collectionManager = etebase.getCollectionManager(); - const collection = await collectionManager.fetch(collectionUids.expenses); - const itemManager = collectionManager.getItemManager(collection); - if (expense.id === 'newExpense') { - const item = await itemManager.create( - { - type: 'expense', - mtime: new Date().getTime(), - }, - JSON.stringify({ + expense.id = `${Date.now().toString()}:${Math.random()}`; + + await userbase.insertItem({ + databaseName: 'expenses', + item: { cost: expense.cost, budget: expense.budget, description: expense.description, date: expense.date, - } as T.ExpenseContent), - ); - - await itemManager.batch([item]); - - expense.id = item.uid; - - cachedData.expenses.push({ - id: expense.id, - cost: expense.cost, - budget: expense.budget, - description: expense.description, - date: expense.date, + } as T.ExpenseContent, + itemId: expense.id, }); } else { - const existingExpenseItem = await itemManager.fetch(expense.id); - - await existingExpenseItem.setContent( - JSON.stringify({ + await userbase.updateItem({ + databaseName: 'expenses', + item: { cost: expense.cost, description: expense.description, budget: expense.budget, date: expense.date, - } as T.ExpenseContent), - ); - await itemManager.batch([existingExpenseItem]); + } as T.ExpenseContent, + itemId: expense.id, + }); const cachedItemIndex = cachedData.expenses.findIndex( (_expense) => _expense.id === expense.id, @@ -621,18 +477,12 @@ export const saveExpense = async ( return false; }; -export const deleteExpense = async ( - etebase: Etebase.Account, - expenseId: string, -) => { +export const deleteExpense = async (expenseId: string) => { try { - const collectionManager = etebase.getCollectionManager(); - const collection = await collectionManager.fetch(collectionUids.expenses); - const itemManager = collectionManager.getItemManager(collection); - const item = await itemManager.fetch(expenseId); - - item.delete(); - await itemManager.batch([item]); + await userbase.deleteItem({ + databaseName: 'expenses', + itemId: expenseId, + }); const cachedItemIndex = cachedData.expenses.findIndex( (expense) => expense.id === expenseId, @@ -654,68 +504,63 @@ export const deleteExpense = async ( return false; }; -export const deleteAllData = async (etebase: Etebase.Account) => { - const collectionManager = etebase.getCollectionManager(); - const budgetsCollection = await collectionManager.fetch( - collectionUids.budgets, - ); - const budgetsItemManager = - collectionManager.getItemManager(budgetsCollection); - - const budgets = await fetchBudgets(etebase); - const budgetItems: Etebase.Item[] = []; - for (const budget of budgets) { - const item = await budgetsItemManager.fetch(budget.id); - item.delete(); - budgetItems.push(item); - } - await budgetsItemManager.batch(budgetItems); - - const expensesCollection = await collectionManager.fetch( - collectionUids.expenses, - ); - const expensesItemManager = - collectionManager.getItemManager(expensesCollection); - - const expenses = await fetchExpenses(etebase); - const expenseItems: Etebase.Item[] = []; - for (const expense of expenses) { - const item = await expensesItemManager.fetch(expense.id); - item.delete(); - expenseItems.push(item); +export const deleteAllData = async () => { + const budgets = await fetchBudgets(); + const expenses = await fetchExpenses(); + + const deleteBudgetChunks: T.Budget[][] = splitArrayInChunks(budgets, 10); + const deleteExpenseChunks: T.Expense[][] = splitArrayInChunks(expenses, 10); + + for (const budgetsToDelete of deleteBudgetChunks) { + await userbase.putTransaction({ + databaseName: 'budgets', + operations: budgetsToDelete.map((budget) => ({ + command: 'Delete', + itemId: budget.id, + })), + }); + + // Wait a second, to avoid hitting rate limits + await new Promise((resolve) => { + setTimeout(resolve, 1000); + }); } - await expensesItemManager.batch(expenseItems); - budgetsCollection.delete(); - await collectionManager.upload(budgetsCollection); + for (const expensesToDelete of deleteExpenseChunks) { + await userbase.putTransaction({ + databaseName: 'expenses', + operations: expensesToDelete.map((expense) => ({ + command: 'Delete', + itemId: expense.id, + })), + }); - expensesCollection.delete(); - await collectionManager.upload(expensesCollection); + // Wait a second, to avoid hitting rate limits + await new Promise((resolve) => { + setTimeout(resolve, 1000); + }); + } cachedData.budgets.length = 0; cachedData.expenses.length = 0; - hasStartedLoading.budgets = false; - hasStartedLoading.expenses = false; hasFinishedLoading.budgets = false; hasFinishedLoading.expenses = false; }; -type ExportAllData = (etebase: Etebase.Account) => Promise<{ +type ExportAllData = () => Promise<{ budgets?: T.Budget[]; expenses?: T.Expense[]; }>; -export const exportAllData: ExportAllData = async ( - etebase: Etebase.Account, -) => { +export const exportAllData: ExportAllData = async () => { // Don't import anything until we're done with the first full load if (!hasFinishedLoading.budgets || !hasFinishedLoading.expenses) { return {}; } try { - const budgets = (await fetchBudgets(etebase)).sort(sortByName); - const expenses = (await fetchExpenses(etebase)).sort(sortByDate); + const budgets = (await fetchBudgets()).sort(sortByName); + const expenses = (await fetchExpenses()).sort(sortByDate); return { budgets, expenses }; } catch (error) { @@ -731,8 +576,6 @@ export const exportAllData: ExportAllData = async ( }; export const importData = async ( - etebase: Etebase.Account, - session: string, replaceData: boolean, budgets: T.Budget[], expenses: T.Expense[], @@ -744,77 +587,97 @@ export const importData = async ( try { if (replaceData) { - await deleteAllData(etebase); + await deleteAllData(); + + await initializeDb(); - await initializeDb(session); + // Very ugly, but... works. + while (!hasFinishedLoading.budgets || !hasFinishedLoading.expenses) { + await new Promise((resolve) => { + setTimeout(resolve, 100); + }); + } } - const collectionManager = etebase.getCollectionManager(); - const budgetsCollection = await collectionManager.fetch( - collectionUids.budgets, - ); - const budgetsItemManager = - collectionManager.getItemManager(budgetsCollection); - const budgetItems: Etebase.Item[] = []; + const finalBudgetsToAdd: T.Budget[] = []; for (const budget of budgets) { - const item = await budgetsItemManager.create( - { - type: 'budget', - mtime: new Date().getTime(), - }, - JSON.stringify({ - name: budget.name, - value: budget.value, - month: budget.month, - } as T.BudgetContent), - ); - - budgetItems.push(item); - - cachedData.budgets.push({ - id: item.uid, + const newBudgetId = `${Date.now().toString()}:${Math.random()}`; + const newBudget: T.Budget = { + id: newBudgetId, name: budget.name, value: budget.value, month: budget.month, - }); - } + }; - await budgetsItemManager.batch(budgetItems); + finalBudgetsToAdd.push(newBudget); + } - const expensesCollection = await collectionManager.fetch( - collectionUids.expenses, + const addBudgetChunks: T.Budget[][] = splitArrayInChunks( + finalBudgetsToAdd, + 10, ); - const expensesItemManager = - collectionManager.getItemManager(expensesCollection); - const expenseItems: Etebase.Item[] = []; - for (const expense of expenses) { - const item = await expensesItemManager.create( - { - type: 'expense', - mtime: new Date().getTime(), - }, - JSON.stringify({ - cost: expense.cost, - budget: expense.budget, - description: expense.description, - date: expense.date, - } as T.ExpenseContent), - ); + for (const budgetsToAdd of addBudgetChunks) { + await userbase.putTransaction({ + databaseName: 'budgets', + operations: budgetsToAdd.map((budget) => ({ + command: 'Insert', + item: { + name: budget.name, + value: budget.value, + month: budget.month, + } as T.BudgetContent, + itemId: budget.id, + })), + }); + + // Wait a second, to avoid hitting rate limits + await new Promise((resolve) => { + setTimeout(resolve, 1000); + }); + } - expenseItems.push(item); + const finalExpensesToAdd: T.Expense[] = []; - cachedData.expenses.push({ - id: item.uid, + for (const expense of expenses) { + const newExpenseId = `${Date.now().toString()}:${Math.random()}`; + const newExpense: T.Expense = { + id: newExpenseId, cost: expense.cost, budget: expense.budget, description: expense.description, date: expense.date, - }); + }; + + finalExpensesToAdd.push(newExpense); } - await expensesItemManager.batch(expenseItems); + const addExpenseChunks: T.Expense[][] = splitArrayInChunks( + finalExpensesToAdd, + 10, + ); + + for (const expensesToAdd of addExpenseChunks) { + await userbase.putTransaction({ + databaseName: 'expenses', + operations: expensesToAdd.map((expense) => ({ + command: 'Insert', + item: { + cost: expense.cost, + budget: expense.budget, + description: expense.description, + date: expense.date, + } as T.ExpenseContent, + itemId: expense.id, + })), + }); + + // Wait a second, to avoid hitting rate limits + await new Promise((resolve) => { + setTimeout(resolve, 1000); + }); + } return true; } catch (error) { @@ -830,7 +693,6 @@ export const importData = async ( }; export const copyBudgets = async ( - etebase: Etebase.Account, originalMonth: string, destinationMonth: string, ) => { @@ -839,7 +701,7 @@ export const copyBudgets = async ( return; } - const originalBudgets = await fetchBudgets(etebase, originalMonth); + const originalBudgets = await fetchBudgets(originalMonth); const destinationBudgets = originalBudgets.map((budget) => { const newBudget: T.Budget = { ...budget }; newBudget.id = `${Date.now().toString()}:${Math.random()}`; @@ -848,35 +710,38 @@ export const copyBudgets = async ( }); if (destinationBudgets.length > 0) { try { - const collectionManager = etebase.getCollectionManager(); - const collection = await collectionManager.fetch(collectionUids.budgets); - const itemManager = collectionManager.getItemManager(collection); - const items: Etebase.Item[] = []; + const finalBudgetsToAdd: T.Budget[] = []; for (const budget of destinationBudgets) { - const item = await itemManager.create( - { - type: 'budget', - mtime: new Date().getTime(), - }, - JSON.stringify({ - name: budget.name, - value: budget.value, - month: budget.month, - } as T.BudgetContent), - ); - - items.push(item); - - cachedData.budgets.push({ - id: item.uid, + const newBudget: T.Budget = { + id: budget.id, name: budget.name, value: budget.value, month: budget.month, - }); + }; + + finalBudgetsToAdd.push(newBudget); } - await itemManager.batch(items); + const addBudgetChunks: T.Budget[][] = splitArrayInChunks( + finalBudgetsToAdd, + 10, + ); + + for (const budgetsToAdd of addBudgetChunks) { + await userbase.putTransaction({ + databaseName: 'budgets', + operations: budgetsToAdd.map((budget) => ({ + command: 'Insert', + item: { + name: budget.name, + value: budget.value, + month: budget.month, + } as T.BudgetContent, + itemId: budget.id, + })), + }); + } } catch (error) { Swal.fire({ title: 'Uh-oh', diff --git a/lib/types.ts b/lib/types.ts index c64f898..0c07eca 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,5 +1,3 @@ -import * as Etebase from 'etebase'; - export interface PlainObject { [key: string]: any; } @@ -9,7 +7,6 @@ export type Currency = 'USD' | 'EUR' | 'GBP'; export type Theme = 'dark' | 'light'; export interface AuthToken { - session: string; currency: Currency; theme?: Theme; } @@ -41,5 +38,4 @@ export interface PanelProps { budgets: Budget[]; expenses: Expense[]; reloadData: () => Promise; - etebase: Etebase.Account; } diff --git a/lib/utils.ts b/lib/utils.ts index bbd9029..e90dae2 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,8 +1,11 @@ +import userbase from 'userbase-js'; import Swal from 'sweetalert2'; import { sessionNamespace } from 'lib/constants'; import { AuthToken, Currency, Theme } from 'lib/types'; +const USERBASE_APP_ID = process.env.NEXT_PUBLIC_USERBASE_APP_ID; + export const formatNumber = (currency: Currency, number: number) => new Intl.NumberFormat('en-US', { style: 'currency', @@ -108,13 +111,11 @@ export const showNotification = ( }); }; -export const doLogin = ( - session: string, +export const updatePreferences = ( currency: Currency = 'USD', theme: Theme = 'light', ) => { const authToken: AuthToken = { - session, currency, theme, }; @@ -136,9 +137,10 @@ export const doLogin = ( return false; }; -export const doLogout = () => { +export const doLogout = async () => { try { localStorage.removeItem(`${sessionNamespace}:userInfo`); + await userbase.signOut(); return true; } catch (error) { Swal.fire( @@ -166,16 +168,17 @@ export const getUserInfo: GetUserInfo = () => { } return { - session: null, currency: 'USD', theme: 'light', }; }; -export const isLoggedIn = () => { +export const isLoggedIn = async () => { try { - const userInfo = getUserInfo(); - return Boolean(userInfo.session); + const session = await userbase.init({ appId: USERBASE_APP_ID }); + if (session.user) { + return true; + } } catch (error) { // Do nothing } diff --git a/modules/auth/LoginButton.tsx b/modules/auth/LoginButton.tsx index 2a76b56..f38a604 100644 --- a/modules/auth/LoginButton.tsx +++ b/modules/auth/LoginButton.tsx @@ -4,21 +4,22 @@ import Swal from 'sweetalert2'; import Loading from 'components/Loading'; import Button from 'components/Button'; import TextInput from 'components/TextInput'; +import Paragraph from 'components/Paragraph'; import { validateLogin, createAccount } from 'lib/data-utils'; import { showNotification } from 'lib/utils'; const LoginButton = () => { const [isSubmitting, setIsSubmitting] = useState(false); const [email, setEmail] = useState(''); - const [syncToken, setSyncToken] = useState(''); + const [password, setPassword] = useState(''); const handleLogin = useCallback(async () => { - if (!email || !syncToken) { + if (!email || !password) { return; } setIsSubmitting(true); - const { success, error } = await validateLogin(email, syncToken); + const { success, error } = await validateLogin(email, password); if (success) { Swal.fire( @@ -30,21 +31,38 @@ const LoginButton = () => { window.location.reload(); } else { if (error) { - const errorMessage = error.toString(); - if (errorMessage.includes('User not found')) { - showNotification( - 'User does not exist. Will create an account and login.', - 'error', - ); - await createAccount(email, syncToken); - } else { - showNotification(error, 'error'); - } + showNotification(error, 'error'); } setIsSubmitting(false); } - }, [isSubmitting, email, syncToken]); + }, [isSubmitting, email, password]); + + const handleSignup = useCallback(async () => { + if (!email || !password) { + return; + } + + setIsSubmitting(true); + + const { success, error } = await createAccount(email, password); + + if (success) { + Swal.fire( + 'Alright!', + "That looks alright. Let's get on with it!", + 'success', + ); + + window.location.reload(); + } else { + if (error) { + showNotification(error, 'error'); + } + + setIsSubmitting(false); + } + }, [isSubmitting, email, password]); return ( <> @@ -65,11 +83,11 @@ const LoginButton = () => { /> ) => - setSyncToken(event.target.value) + setPassword(event.target.value) } onKeyDown={(event: React.KeyboardEvent) => { if (event.key === 'Enter') { @@ -80,10 +98,20 @@ const LoginButton = () => { + or + ); }; diff --git a/next.config.js b/next.config.js index 9e3509b..9c288f9 100644 --- a/next.config.js +++ b/next.config.js @@ -9,7 +9,7 @@ const securityHeaders = [ { key: 'Content-Security-Policy', value: - "default-src 'self' https://api.etebase.com data: blob:; child-src 'self' data: blob:; img-src 'self' https://*.usefathom.com data: blob:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.usefathom.com", + "default-src 'self' https://*.userbase.com wss://*.userbase.com data: blob:; child-src 'self' data: blob:; img-src 'self' https://*.usefathom.com data: blob:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.usefathom.com", }, { key: 'Strict-Transport-Security', diff --git a/package-lock.json b/package-lock.json index 30cb42a..3aef38e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,12 +13,10 @@ "@types/react": "17.0.38", "@types/react-dom": "17.0.11", "@types/styled-components": "5.1.19", - "etebase": "0.43.1", "ky": "0.28.7", "moment": "2.29.1", "next": "12.0.7", "next-pwa": "5.4.4", - "node-fetch": "3.1.0", "react": "17.0.2", "react-dom": "17.0.2", "react-helmet": "6.1.0", @@ -29,7 +27,8 @@ "sass": "1.45.2", "styled-components": "5.3.3", "sweetalert2": "11.3.3", - "typescript": "4.5.4" + "typescript": "4.5.4", + "userbase-js": "2.8.0" }, "devDependencies": { "@types/enzyme": "3.10.11", @@ -2539,14 +2538,6 @@ "node": ">=4.2" } }, - "node_modules/@msgpack/msgpack": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-1.12.2.tgz", - "integrity": "sha512-Vwhc3ObxmDZmA5hY8mfsau2rJ4vGPvzbj20QSZ2/E1GDPF61QVyjLfNHak9xmel6pW4heRt3v1fHa6np9Ehfeg==", - "engines": { - "node": ">= 10" - } - }, "node_modules/@napi-rs/triples": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.0.3.tgz", @@ -3290,11 +3281,6 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, - "node_modules/@types/urijs": { - "version": "1.19.17", - "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.17.tgz", - "integrity": "sha512-ShIlp+8iNGo/yVVfYFoNRqUiaE9wMCzsSl85qTg2/C5l56BTJokU7QeMgVBQ9xhcyhWQP0zGXPBZPPvEG/sRmQ==" - }, "node_modules/@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -3835,19 +3821,6 @@ "node": ">= 8" } }, - "node_modules/argon2-browser": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz", - "integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw==" - }, - "node_modules/argon2-webworker": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/argon2-webworker/-/argon2-webworker-0.1.1.tgz", - "integrity": "sha512-OABEgMfYD533Z4E3x+53tZ+69O82xWfv587Zfwef6VL2EkPIcSiiqkYSaw5gG8Dwpvu+8EZJ/C189Ev9wEJ9GA==", - "dependencies": { - "argon2-browser": "^1.15.3" - } - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -4542,6 +4515,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-arraybuffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", + "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -6782,52 +6763,6 @@ "node": ">= 0.6" } }, - "node_modules/etebase": { - "version": "0.43.1", - "resolved": "https://registry.npmjs.org/etebase/-/etebase-0.43.1.tgz", - "integrity": "sha512-JMsiGCpw126RAMKe2b1Vrl0/LTQL4B7G+bRSOimkYZHKrErDZ3IkbeaFxdZ9n/DHjehxv+LmNmGFwAE02Uj/+g==", - "dependencies": { - "@msgpack/msgpack": "^1.12.2", - "@types/urijs": "^1.15.38", - "argon2-webworker": "^0.1.1", - "isomorphic-ws": "^4.0.1", - "libsodium-wrappers": "0.7.6", - "node-fetch": "^2.6.0", - "reconnecting-websocket": "^4.4.0", - "urijs": "^1.19.1", - "ws": "^7.4.2" - } - }, - "node_modules/etebase/node_modules/node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/etebase/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "node_modules/etebase/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "node_modules/etebase/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -6992,27 +6927,6 @@ "bser": "2.1.1" } }, - "node_modules/fetch-blob": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.3.tgz", - "integrity": "sha512-ax1Y5I9w+9+JiM+wdHkhBoxew+zG4AJ2SvAD1v1szpddUIiPERVGBxrMcB2ZqW0Y3PP8bOWYv2zqQq1Jp2kqUQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -7121,17 +7035,6 @@ "node": ">= 6" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -8178,14 +8081,6 @@ "node": ">=0.10.0" } }, - "node_modules/isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "peerDependencies": { - "ws": "*" - } - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -10144,19 +10039,6 @@ "node": ">= 0.8.0" } }, - "node_modules/libsodium": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.6.tgz", - "integrity": "sha512-hPb/04sEuLcTRdWDtd+xH3RXBihpmbPCsKW/Jtf4PsvdyKh+D6z2D2gvp/5BfoxseP+0FCOg66kE+0oGUE/loQ==" - }, - "node_modules/libsodium-wrappers": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.6.tgz", - "integrity": "sha512-OUO2CWW5bHdLr6hkKLHIKI4raEkZrf3QHkhXsJ1yCh6MZ3JDA7jFD3kCATNquuGSG6MjjPHQIQms0y0gBDzjQg==", - "dependencies": { - "libsodium": "0.7.6" - } - }, "node_modules/loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -10752,31 +10634,6 @@ "node": ">=4" } }, - "node_modules/node-fetch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.1.0.tgz", - "integrity": "sha512-QU0WbIfMUjd5+MUzQOYhenAazakV7Irh1SGkWCsRzBwvm4fAhzEUaHMJ6QLP7gWT6WO9/oH2zhKMMGMuIrDyKw==", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.2", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/node-fetch/node_modules/data-uri-to-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", - "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", - "engines": { - "node": ">= 12" - } - }, "node_modules/node-html-parser": { "version": "1.4.9", "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.4.9.tgz", @@ -11814,11 +11671,6 @@ "node": ">=8.10.0" } }, - "node_modules/reconnecting-websocket": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz", - "integrity": "sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==" - }, "node_modules/reflect.ownkeys": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", @@ -12227,6 +12079,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + }, "node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -12345,6 +12202,11 @@ "node": ">=0.10.0" } }, + "node_modules/sorted-array": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sorted-array/-/sorted-array-2.0.4.tgz", + "integrity": "sha512-58INzrX0rL6ttCfsGoFmOuQY5AjR6A5E/MmGKJ5JvWHOey6gOEOC6vO8K6C0Y2bQR6KJ8o8aFwHjp/mJ/HcYsQ==" + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -13452,11 +13314,6 @@ "punycode": "^2.1.0" } }, - "node_modules/urijs": { - "version": "1.19.7", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz", - "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA==" - }, "node_modules/use-subscription": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz", @@ -13468,6 +13325,19 @@ "react": "^16.8.0 || ^17.0.0" } }, + "node_modules/userbase-js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/userbase-js/-/userbase-js-2.8.0.tgz", + "integrity": "sha512-fjGgfpI018T+HY3k9XW1bkUyppHDa1bLzfJJ+Virq+58YzzcN74c5AF8W/cww76syyyscndbCvzLduvwiVKlwA==", + "dependencies": { + "@babel/runtime": "^7.9.2", + "base64-arraybuffer": "^0.2.0", + "diffie-hellman": "^5.0.3", + "scrypt-js": "^3.0.0", + "sorted-array": "^2.0.4", + "uuid": "^3.4.0" + } + }, "node_modules/util": { "version": "0.12.4", "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", @@ -13486,6 +13356,15 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -13562,14 +13441,6 @@ "node": ">=10.13.0" } }, - "node_modules/web-streams-polyfill": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", - "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==", - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", @@ -14124,6 +13995,7 @@ "version": "7.5.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "dev": true, "engines": { "node": ">=8.3.0" }, @@ -15980,11 +15852,6 @@ "resolved": "https://registry.npmjs.org/@jsbits/get-package-version/-/get-package-version-1.0.3.tgz", "integrity": "sha512-IJy1jRL01x7p6UEpgKa1lVLstMUx8EiIR8pPoS5sBfsHEoeLkzYiNpAfxPx8zLDUJyS1yBbChJjcWdPqyH285w==" }, - "@msgpack/msgpack": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-1.12.2.tgz", - "integrity": "sha512-Vwhc3ObxmDZmA5hY8mfsau2rJ4vGPvzbj20QSZ2/E1GDPF61QVyjLfNHak9xmel6pW4heRt3v1fHa6np9Ehfeg==" - }, "@napi-rs/triples": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.0.3.tgz", @@ -16541,11 +16408,6 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, - "@types/urijs": { - "version": "1.19.17", - "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.17.tgz", - "integrity": "sha512-ShIlp+8iNGo/yVVfYFoNRqUiaE9wMCzsSl85qTg2/C5l56BTJokU7QeMgVBQ9xhcyhWQP0zGXPBZPPvEG/sRmQ==" - }, "@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -16956,19 +16818,6 @@ "picomatch": "^2.0.4" } }, - "argon2-browser": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz", - "integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw==" - }, - "argon2-webworker": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/argon2-webworker/-/argon2-webworker-0.1.1.tgz", - "integrity": "sha512-OABEgMfYD533Z4E3x+53tZ+69O82xWfv587Zfwef6VL2EkPIcSiiqkYSaw5gG8Dwpvu+8EZJ/C189Ev9wEJ9GA==", - "requires": { - "argon2-browser": "^1.15.3" - } - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -17545,6 +17394,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "base64-arraybuffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", + "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==" + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -19287,51 +19141,6 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, - "etebase": { - "version": "0.43.1", - "resolved": "https://registry.npmjs.org/etebase/-/etebase-0.43.1.tgz", - "integrity": "sha512-JMsiGCpw126RAMKe2b1Vrl0/LTQL4B7G+bRSOimkYZHKrErDZ3IkbeaFxdZ9n/DHjehxv+LmNmGFwAE02Uj/+g==", - "requires": { - "@msgpack/msgpack": "^1.12.2", - "@types/urijs": "^1.15.38", - "argon2-webworker": "^0.1.1", - "isomorphic-ws": "^4.0.1", - "libsodium-wrappers": "0.7.6", - "node-fetch": "^2.6.0", - "reconnecting-websocket": "^4.4.0", - "urijs": "^1.19.1", - "ws": "^7.4.2" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -19467,14 +19276,6 @@ "bser": "2.1.1" } }, - "fetch-blob": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.3.tgz", - "integrity": "sha512-ax1Y5I9w+9+JiM+wdHkhBoxew+zG4AJ2SvAD1v1szpddUIiPERVGBxrMcB2ZqW0Y3PP8bOWYv2zqQq1Jp2kqUQ==", - "requires": { - "web-streams-polyfill": "^3.0.3" - } - }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -19561,14 +19362,6 @@ "mime-types": "^2.1.12" } }, - "formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "requires": { - "fetch-blob": "^3.1.2" - } - }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -20301,12 +20094,6 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, - "isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "requires": {} - }, "istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -21790,19 +21577,6 @@ "type-check": "~0.4.0" } }, - "libsodium": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.6.tgz", - "integrity": "sha512-hPb/04sEuLcTRdWDtd+xH3RXBihpmbPCsKW/Jtf4PsvdyKh+D6z2D2gvp/5BfoxseP+0FCOg66kE+0oGUE/loQ==" - }, - "libsodium-wrappers": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.6.tgz", - "integrity": "sha512-OUO2CWW5bHdLr6hkKLHIKI4raEkZrf3QHkhXsJ1yCh6MZ3JDA7jFD3kCATNquuGSG6MjjPHQIQms0y0gBDzjQg==", - "requires": { - "libsodium": "0.7.6" - } - }, "loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -22264,23 +22038,6 @@ "workbox-window": "^6.4.2" } }, - "node-fetch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.1.0.tgz", - "integrity": "sha512-QU0WbIfMUjd5+MUzQOYhenAazakV7Irh1SGkWCsRzBwvm4fAhzEUaHMJ6QLP7gWT6WO9/oH2zhKMMGMuIrDyKw==", - "requires": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.2", - "formdata-polyfill": "^4.0.10" - }, - "dependencies": { - "data-uri-to-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", - "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==" - } - } - }, "node-html-parser": { "version": "1.4.9", "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.4.9.tgz", @@ -23071,11 +22828,6 @@ "picomatch": "^2.2.1" } }, - "reconnecting-websocket": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz", - "integrity": "sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==" - }, "reflect.ownkeys": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", @@ -23375,6 +23127,11 @@ "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==" }, + "scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -23469,6 +23226,11 @@ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, + "sorted-array": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sorted-array/-/sorted-array-2.0.4.tgz", + "integrity": "sha512-58INzrX0rL6ttCfsGoFmOuQY5AjR6A5E/MmGKJ5JvWHOey6gOEOC6vO8K6C0Y2bQR6KJ8o8aFwHjp/mJ/HcYsQ==" + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -24297,11 +24059,6 @@ "punycode": "^2.1.0" } }, - "urijs": { - "version": "1.19.7", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz", - "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA==" - }, "use-subscription": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz", @@ -24310,6 +24067,19 @@ "object-assign": "^4.1.1" } }, + "userbase-js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/userbase-js/-/userbase-js-2.8.0.tgz", + "integrity": "sha512-fjGgfpI018T+HY3k9XW1bkUyppHDa1bLzfJJ+Virq+58YzzcN74c5AF8W/cww76syyyscndbCvzLduvwiVKlwA==", + "requires": { + "@babel/runtime": "^7.9.2", + "base64-arraybuffer": "^0.2.0", + "diffie-hellman": "^5.0.3", + "scrypt-js": "^3.0.0", + "sorted-array": "^2.0.4", + "uuid": "^3.4.0" + } + }, "util": { "version": "0.12.4", "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", @@ -24328,6 +24098,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -24394,11 +24169,6 @@ "graceful-fs": "^4.1.2" } }, - "web-streams-polyfill": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", - "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==" - }, "webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", @@ -24868,6 +24638,7 @@ "version": "7.5.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "dev": true, "requires": {} }, "xml-name-validator": { diff --git a/package.json b/package.json index e0d3a9d..c027e59 100644 --- a/package.json +++ b/package.json @@ -26,12 +26,10 @@ "@types/react": "17.0.38", "@types/react-dom": "17.0.11", "@types/styled-components": "5.1.19", - "etebase": "0.43.1", "ky": "0.28.7", "moment": "2.29.1", "next": "12.0.7", "next-pwa": "5.4.4", - "node-fetch": "3.1.0", "react": "17.0.2", "react-dom": "17.0.2", "react-helmet": "6.1.0", @@ -42,7 +40,8 @@ "sass": "1.45.2", "styled-components": "5.3.3", "sweetalert2": "11.3.3", - "typescript": "4.5.4" + "typescript": "4.5.4", + "userbase-js": "2.8.0" }, "devDependencies": { "@types/enzyme": "3.10.11", diff --git a/pages/index.tsx b/pages/index.tsx index 47c0d51..ed7a647 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -14,9 +14,12 @@ const IndexPage = () => { const [hasValidSession, setHasValidSession] = useState(false); useEffect(() => { - const isUserLoggedIn = isLoggedIn(); + const checkSession = async () => { + const isUserLoggedIn = await isLoggedIn(); + setHasValidSession(isUserLoggedIn); + }; - setHasValidSession(isUserLoggedIn); + checkSession(); }, []); return (