forked from Shiloh/remnantchat
feat: persist userId
This commit is contained in:
parent
74f11dae2a
commit
b8f8bb5bfd
35
package-lock.json
generated
35
package-lock.json
generated
@ -21,6 +21,7 @@
|
|||||||
"@types/react": "^18.0.17",
|
"@types/react": "^18.0.17",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"fast-memoize": "^2.5.2",
|
"fast-memoize": "^2.5.2",
|
||||||
|
"localforage": "^1.10.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-markdown": "^8.0.3",
|
"react-markdown": "^8.0.3",
|
||||||
@ -15987,6 +15988,22 @@
|
|||||||
"node": ">=8.9.0"
|
"node": ">=8.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/localforage": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
|
||||||
|
"dependencies": {
|
||||||
|
"lie": "3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/localforage/node_modules/lie": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
|
||||||
|
"dependencies": {
|
||||||
|
"immediate": "~3.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
@ -35404,6 +35421,24 @@
|
|||||||
"json5": "^2.1.2"
|
"json5": "^2.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"localforage": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
|
||||||
|
"requires": {
|
||||||
|
"lie": "3.1.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lie": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
|
||||||
|
"requires": {
|
||||||
|
"immediate": "~3.0.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"locate-path": {
|
"locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"@types/react": "^18.0.17",
|
"@types/react": "^18.0.17",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"fast-memoize": "^2.5.2",
|
"fast-memoize": "^2.5.2",
|
||||||
|
"localforage": "^1.10.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-markdown": "^8.0.3",
|
"react-markdown": "^8.0.3",
|
||||||
|
@ -1,15 +1,70 @@
|
|||||||
import React from 'react'
|
import { act, render } from '@testing-library/react'
|
||||||
import { render } from '@testing-library/react'
|
|
||||||
import { MemoryRouter as Router } from 'react-router-dom'
|
import { MemoryRouter as Router } from 'react-router-dom'
|
||||||
|
import localforage from 'localforage'
|
||||||
|
|
||||||
import Bootstrap from './Bootstrap'
|
import { PersistedStorageKeys } from 'models/storage'
|
||||||
|
|
||||||
const StubBootstrap = () => (
|
import Bootstrap, { BootstrapProps } from './Bootstrap'
|
||||||
<Router>
|
|
||||||
<Bootstrap />
|
|
||||||
</Router>
|
|
||||||
)
|
|
||||||
|
|
||||||
test('renders', () => {
|
const mockPersistedStorage =
|
||||||
render(<StubBootstrap />)
|
jest.createMockFromModule<jest.Mock<typeof localforage>>('localforage')
|
||||||
|
|
||||||
|
const mockGetUuid = jest.fn()
|
||||||
|
|
||||||
|
const mockGetItem = jest.fn()
|
||||||
|
const mockSetItem = jest.fn()
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockGetItem.mockImplementation(() => Promise.resolve(null))
|
||||||
|
mockSetItem.mockImplementation((data: any) => Promise.resolve(data))
|
||||||
|
})
|
||||||
|
|
||||||
|
const renderBootstrap = async (overrides: BootstrapProps = {}) => {
|
||||||
|
Object.assign(mockPersistedStorage, {
|
||||||
|
getItem: mockGetItem,
|
||||||
|
setItem: mockSetItem,
|
||||||
|
})
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Router>
|
||||||
|
<Bootstrap
|
||||||
|
persistedStorage={mockPersistedStorage as any as typeof localforage}
|
||||||
|
{...overrides}
|
||||||
|
/>
|
||||||
|
</Router>
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning#an-alternative-waiting-for-the-mocked-promise
|
||||||
|
await act(async () => {
|
||||||
|
await Promise.resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test('renders', async () => {
|
||||||
|
await renderBootstrap()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('checks persistedStorage for user settings', async () => {
|
||||||
|
await renderBootstrap()
|
||||||
|
expect(mockGetItem).toHaveBeenCalledWith(PersistedStorageKeys.USER_SETTINGS)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('persists user settings if none were already persisted', async () => {
|
||||||
|
await renderBootstrap({
|
||||||
|
getUuid: mockGetUuid.mockImplementation(() => 'abc123'),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(mockSetItem).toHaveBeenCalledWith(PersistedStorageKeys.USER_SETTINGS, {
|
||||||
|
userId: 'abc123',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not update user settings if they were already persisted', async () => {
|
||||||
|
mockGetItem.mockImplementation(() => ({
|
||||||
|
userId: 'abc123',
|
||||||
|
}))
|
||||||
|
|
||||||
|
await renderBootstrap()
|
||||||
|
|
||||||
|
expect(mockSetItem).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
@ -1,24 +1,64 @@
|
|||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Routes, Route } from 'react-router-dom'
|
import { Routes, Route } from 'react-router-dom'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
|
import localforage from 'localforage'
|
||||||
|
|
||||||
import { Home } from './pages/Home/'
|
import { Home } from 'pages/Home/'
|
||||||
import { PublicRoom } from './pages/PublicRoom/'
|
import { PublicRoom } from 'pages/PublicRoom/'
|
||||||
|
import { UserSettings } from 'models/settings'
|
||||||
|
import { PersistedStorageKeys } from 'models/storage'
|
||||||
|
|
||||||
function Bootstrap() {
|
export interface BootstrapProps {
|
||||||
const [userId] = useState(uuid())
|
persistedStorage?: typeof localforage
|
||||||
|
getUuid?: typeof uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
function Bootstrap({
|
||||||
|
persistedStorage = localforage.createInstance({
|
||||||
|
name: 'chitchatter',
|
||||||
|
description: 'Persisted settings data for chitchatter',
|
||||||
|
}),
|
||||||
|
getUuid = uuid,
|
||||||
|
}: BootstrapProps) {
|
||||||
|
const [hasLoadedSettings, setHasLoadedSettings] = useState(false)
|
||||||
|
const [settings, setSettings] = useState({ userId: getUuid() })
|
||||||
|
const { userId } = settings
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
;(async () => {
|
||||||
|
if (hasLoadedSettings) return
|
||||||
|
|
||||||
|
const persistedUserSettings =
|
||||||
|
await persistedStorage.getItem<UserSettings>(
|
||||||
|
PersistedStorageKeys.USER_SETTINGS
|
||||||
|
)
|
||||||
|
|
||||||
|
if (persistedUserSettings) {
|
||||||
|
setSettings(persistedUserSettings)
|
||||||
|
} else {
|
||||||
|
await persistedStorage.setItem(
|
||||||
|
PersistedStorageKeys.USER_SETTINGS,
|
||||||
|
settings
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setHasLoadedSettings(true)
|
||||||
|
})()
|
||||||
|
}, [hasLoadedSettings, persistedStorage, settings, userId])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Chitchatter">
|
<div className="Chitchatter">
|
||||||
<Routes>
|
{hasLoadedSettings ? (
|
||||||
{['/', '/index.html'].map(path => (
|
<Routes>
|
||||||
<Route key={path} path={path} element={<Home />} />
|
{['/', '/index.html'].map(path => (
|
||||||
))}
|
<Route key={path} path={path} element={<Home />} />
|
||||||
<Route
|
))}
|
||||||
path="/public/:roomId"
|
<Route
|
||||||
element={<PublicRoom userId={userId} />}
|
path="/public/:roomId"
|
||||||
/>
|
element={<PublicRoom userId={userId} />}
|
||||||
</Routes>
|
/>
|
||||||
|
</Routes>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
3
src/models/settings.ts
Normal file
3
src/models/settings.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface UserSettings {
|
||||||
|
userId: string
|
||||||
|
}
|
3
src/models/storage.ts
Normal file
3
src/models/storage.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export enum PersistedStorageKeys {
|
||||||
|
USER_SETTINGS = 'userSettings',
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user