Skip to content

Commit

Permalink
Merge pull request #1 from joebobmiles/feat/only-one-provider-of-type
Browse files Browse the repository at this point in the history
Providers are unique per document
  • Loading branch information
joebobmiles authored Aug 24, 2021
2 parents e6a310c + 57480f4 commit 7499476
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 25 deletions.
5 changes: 3 additions & 2 deletions rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ export default defineConfig({
],
external: [
'yjs',
'y-websocket',
'y-webrtc',
'y-indexeddb',
'y-protocols',
'y-webrtc',
'y-websocket',
'react'
]
})
27 changes: 24 additions & 3 deletions src/feature/doc/component.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import React from 'react'
import React, { useEffect } from 'react'
import * as Y from 'yjs'

import { Provider } from './type'
import { useDoc } from './hook'

export const DocumentContext = React.createContext<Y.Doc | null>(null)
export const DocumentContext = React.createContext<{
doc: Y.Doc | null
providers: Set<Provider> | null
}>({
doc: null,
providers: null
})

interface DocumentProviderProps {
children: React.ReactNode
Expand All @@ -25,8 +32,22 @@ export const DocumentProvider = ({
superDoc.getMap(folderName ?? '').set(documentName ?? doc.guid, doc)
}

const providers = React.useRef(new Set<Provider>())

useEffect(
() =>
() => providers.current.forEach(
(provider) => provider.destroy()),
[]
)

return (
<DocumentContext.Provider value={doc}>
<DocumentContext.Provider
value={{
doc,
providers: providers.current
}}
>
{children}
</DocumentContext.Provider>
)
Expand Down
19 changes: 16 additions & 3 deletions src/feature/doc/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@ import React from 'react'
import * as Y from 'yjs'

import { DocumentContext } from './component'
import { Provider } from './type'

export const useDoc = (): Y.Doc => {
const value = React.useContext(DocumentContext)
const { doc } = React.useContext(DocumentContext)

if (value !== null) {
return value
if (doc !== null) {
return doc
} else {
throw new Error(
'Could not retrieve a document. Please wrap in a DocumentProvider.'
)
}
}

export const useProviders = (): Set<Provider> => {
const { providers } = React.useContext(DocumentContext)

if (providers !== null) {
return providers
} else {
throw new Error(
'Could not retrieve a set of providers. Please wrap in a DocumentProvider.'
)
}
}
2 changes: 1 addition & 1 deletion src/feature/doc/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { DocumentProvider } from './component'
export { useDoc } from './hook'
export { useDoc, useProviders } from './hook'
8 changes: 8 additions & 0 deletions src/feature/doc/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IndexeddbPersistence } from 'y-indexeddb'
import { WebrtcProvider } from 'y-webrtc'
import { WebsocketProvider } from 'y-websocket'

export type Provider =
| WebrtcProvider
| WebsocketProvider
| IndexeddbPersistence
26 changes: 22 additions & 4 deletions src/feature/provider/hook/useIndexedDb.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import React from 'react'
import { IndexeddbPersistence } from 'y-indexeddb'
import { useDoc } from '../../doc'

import { useDoc, useProviders } from '../../doc'

export const useIndexedDb = (name: string): IndexeddbPersistence => {
const doc = useDoc()
const providers = useProviders()

return React.useMemo(
const existingProvider = React.useMemo(
() =>
new IndexeddbPersistence(name, doc),
[name]
Array.from(providers.values())
.find((provider): provider is IndexeddbPersistence =>
provider instanceof IndexeddbPersistence && provider.db?.name === name
),
[providers, name]
)

if (existingProvider !== undefined) {
return existingProvider
} else {
const provider = React.useMemo(
() => new IndexeddbPersistence(name, doc),
[doc, name]
)

providers.add(provider)

return provider
}
}
17 changes: 17 additions & 0 deletions src/feature/provider/hook/useWebRtc.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,21 @@ describe('useWebRtc', () => {

expect(result.current).toBeInstanceOf(WebrtcProvider)
})

it('Returns the same WebRTC provider under the same document.', () => {
expect(() => renderHook(
() => useWebRtc('room'),
{
wrapper: ({ children }) =>
(
<DocumentProvider>
{children}
{children}
</DocumentProvider>
)
}
)).not.toThrowError(
'Error: A Yjs Doc connected to room "room" already exists!'
)
})
})
26 changes: 22 additions & 4 deletions src/feature/provider/hook/useWebRtc.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import React from 'react'
import { WebrtcProvider } from 'y-webrtc'

import { useDoc } from '../../doc'
import { useDoc, useProviders } from '../../doc'

export const useWebRtc = (room: string): WebrtcProvider => {
const doc = useDoc()
const providers = useProviders()

return React.useMemo(
() => new WebrtcProvider(room, doc),
[room]
const existingProvider = React.useMemo(
() =>
Array.from(providers.values())
.find((provider): provider is WebrtcProvider =>
provider instanceof WebrtcProvider && provider.roomName === room
),
[providers, room]
)

if (existingProvider !== undefined) {
return existingProvider
} else {
const provider = React.useMemo(
() => new WebrtcProvider(room, doc),
[doc, room]
)

providers.add(provider)

return provider
}
}
31 changes: 23 additions & 8 deletions src/feature/provider/hook/useWebSocket.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
import React from 'react'
import { WebsocketProvider } from 'y-websocket'

import { useDoc } from '../../doc'
import { useDoc, useProviders } from '../../doc'

export const useWebSocket = (
url: string,
room: string
): WebsocketProvider => {
const doc = useDoc()
const providers = useProviders()

return React.useMemo(
const existingProvider = React.useMemo(
() =>
new WebsocketProvider(
url,
room,
doc
),
[url, room]
Array.from(providers.values())
.find((provider): provider is WebsocketProvider =>
provider instanceof WebsocketProvider &&
provider.url === url &&
provider.roomname === room
),
[providers, url, room]
)

if (existingProvider !== undefined) {
return existingProvider
} else {
const provider = React.useMemo(
() => new WebsocketProvider(url, room, doc),
[doc, url, room]
)

providers.add(provider)

return provider
}
}

0 comments on commit 7499476

Please sign in to comment.