Skip to content

Commit

Permalink
added overrideLocalStorage; now it is possible to wait for storage …
Browse files Browse the repository at this point in the history
…to be hydrated before rendering the children
  • Loading branch information
wpdas committed Apr 21, 2023
1 parent 3349d5c commit ccaedac
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 17 deletions.
74 changes: 70 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ yarn add near-social-bridge
- [useSyncContentHeight](#usesynccontentheight)
- [Utils](#utils)
- [initRefreshService](#initrefreshservice)
- [overrideLocalStorage](#overridelocalstorage)
- [Preparing a new Widget](#preparing-a-new-widget)
- [Good to know](#good-to-know)
- [Server-Side Rendering](#server-side-rendering)
Expand All @@ -68,14 +69,26 @@ import 'near-social-bridge/near-social-bridge.css'

Then, you need to wrap your app with `NearSocialBridgeProvider` which will start the connection between the React App and the Widget inside Near Social. The connection only occurs when the application is running inside the Widget.

This component accepts a fallback component that's going to be shown until the connection with the Widget is established or the Widget response timeout is reached.. You can set it using the `fallback` prop.
This component accepts a fallback component that's going to be shown until the connection with the Widget is established or the Widget response timeout is reached. You can set it using the `fallback` prop.

If your app is using (or has dependencies using) `localStorage` you'll need to override the `window.localStorage` with the Widget's `Storage` API as `localStorage` is not supported by the VM. You can do it using `overrideLocalStorage` like so:

```tsx
import { NearSocialBridgeProvider } from 'near-social-bridge'
import { Spinner } from 'near-social-bridge'
import { overrideLocalStorage } from 'near-social-bridge/utils'
overrideLocalStorage()
```

When using `overrideLocalStorage`, it's recommended that you set `NearSocialBridgeProvider.waitForStorage` as `true`, so that, the bridge is going to wait for the storage to be hydrated before rendering the children.

```tsx
import { NearSocialBridgeProvider, Spinner, overrideLocalStorage } from 'near-social-bridge'
import 'near-social-bridge/near-social-bridge.css'

overrideLocalStorage()

// ...
return (
<NearSocialBridgeProvider fallback={<Spinner />}>
<NearSocialBridgeProvider waitForStorage fallback={<Spinner />}>
<App />
</NearSocialBridgeProvider>
)
Expand Down Expand Up @@ -536,6 +549,59 @@ useEffect(() => {
// ...
```

### overrideLocalStorage

This is a feature that overrides the `window.localStorage` with the Widget's `Storage`, so that, you can keep using `window.localStorage` but the Widget's `Storage` is going to be the source of data.

**If using CSR:**

```ts
import { overrideLocalStorage } from 'near-social-bridge/utils'

// using `sessionStorage` under the hood
overrideLocalStorage()

localStorage.setItem('name', 'Wenderson')
localStorage.getItem('name') // "Wenderson"
```

**If using SSR:**

```ts
// Page or index.tsx
import { useEffect } from 'react'
import { NearSocialBridgeProvider, overrideLocalStorage } from 'near-social-bridge'
import MyComponent from './MyComponent'

overrideLocalStorage()

const SSRApp = () => {
useEffect(() => {
localStorage.setItem('name', 'Wenderson')
}, [])

return (
<NearSocialBridgeProvider waitForStorage>
<MyComponent />
<MyComponent2 />
</NearSocialBridgeProvider>
)
}

export default SSRApp

// MyComponent
const MyComponent = () => {
console.log(localStorage.getItem('name')) // "Wenderson"
}

// MyComponent2
import { sessionStorage } from 'near-social-bridge'
const MyComponent2 = () => {
console.log(sessionStorage.getItem('name')) // "Wenderson"
}
```

## Preparing a new Widget

Create a new Widget, copy [the content of file **widget-setup.js**](./widget-setup.js) and paste it inside your new Widget. Then set its initial props as you wish:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "near-social-bridge",
"version": "1.3.6",
"version": "1.4.0",
"description": "This library allows you to create a common application using ReactJS and inject it in a controlled way into a Widget on Near Social. Therefore, the Widget talks to the React application and vice versa, making it possible to consume Discovery API resources within the React application.",
"main": "./dist/cjs/index.js",
"module": "./index.js",
Expand Down
40 changes: 37 additions & 3 deletions src/bridge/contexts/NearSocialBridgeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
postMessage as postMessageService,
} from '../../services/bridge-service'
import { GetMessageCallBack, NearSocialBridgeProps } from '../types'
import { sessionStorageUpdateObservable } from '../../session-storage/sessionStorage'

const defaultValue: NearSocialBridgeProps = {
postMessage: () => {
Expand All @@ -26,7 +27,14 @@ export const NearSocialBridgeContext = createContext(defaultValue)

interface Props {
children: React.ReactNode
/**
* Fallback component that's going to be shown until the connection with the Widget is established or the Widget response timeout is reached.
*/
fallback?: React.ReactNode
/**
* Wait for storage to be hydrated before render the children. Fallback component is going to be shown if provided.
*/
waitForStorage?: boolean
}

/**
Expand All @@ -35,11 +43,12 @@ interface Props {
* Fallback component is displayed (if provided) until the connection is established with the Widget
* @returns
*/
const NearSocialBridgeProvider: React.FC<Props> = ({ children, fallback }) => {
const NearSocialBridgeProvider: React.FC<Props> = ({ children, fallback, waitForStorage }) => {
const [_onGetMessage, set_onGetMessage] = useState<{
cb: GetMessageCallBack
}>({ cb: () => {} })
const [isConnected, setIsConnected] = useState(false)
const [isStorageReady, setIsStorageReady] = useState(false)

/**
* Post Message
Expand Down Expand Up @@ -93,8 +102,33 @@ const NearSocialBridgeProvider: React.FC<Props> = ({ children, fallback }) => {
}
}, [])

if (!isConnected && fallback) {
return <>{fallback}</>
// Check if storage is ready
if (waitForStorage) {
const handler = () => {
sessionStorageUpdateObservable.unsubscribe(handler)
setIsStorageReady(true)
}
sessionStorageUpdateObservable.subscribe(handler)
}

// Wait connection
if (!isConnected) {
if (fallback) {
return <>{fallback}</>
}

// SSR - It's necessary. So that, `overrideLocalStorage`, it's going to work without issue
return <div />
}

// Wait storage to be ready (optional)
if (waitForStorage && !isStorageReady) {
if (fallback) {
return <>{fallback}</>
}

// SSR - It's necessary. So that, `overrideLocalStorage`, it's going to work without issue
return <div />
}

return (
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ export * from './hooks'
export * from './components'

// Utils
export { initRefreshService } from './utils/refresh'
export * from './utils'
18 changes: 15 additions & 3 deletions src/session-storage/sessionStorage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { REQUEST_KEYS } from '../constants'
import request from '../request/request'
import { onConnectObservable } from '../services/bridge-service'
import { getConnectionStatus, onConnectObservable } from '../services/bridge-service'
import Observable from '../utils/observable'

/**
Expand All @@ -14,7 +14,11 @@ const setItem = (key: string, value: any) => {
const updatedStorage: any = { ..._storage }
updatedStorage[key] = value
_storage = updatedStorage
hydrateViewer()

// Hydrate only when connection is established
if (getConnectionStatus() === 'connected') {
hydrateViewer()
}
}

const getItem = (key: string) => _storage[key] || null
Expand Down Expand Up @@ -55,9 +59,17 @@ const hydrate = async () => {
forceTryAgain: false,
})

// Check if there're data inside the _storage before the connection, if so, update the viewer
const hasPreviousData = Object.keys(_storage).length > 0

// Hydrate _storage with data stored in the Viewer "sessionStorageClone" state
_storage = view_sessionStorageClone || {}
_storage = { ...view_sessionStorageClone, ..._storage } || {}
sessionStorageUpdateObservable.notify(_storage)

// Update the viewer (only if there're data inside the _storage before the connection)
if (hasPreviousData) {
hydrateViewer()
}
}

// Every time the bridge connection is established, hydrate the storage
Expand Down
3 changes: 2 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { initRefreshService } from './refresh'
import { overrideLocalStorage } from './overrideLocalStorage'

export { initRefreshService }
export { initRefreshService, overrideLocalStorage }
4 changes: 4 additions & 0 deletions src/utils/isBrowser.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
/**
* Is it running in a browser?
* @returns
*/
const isBrowser = () => typeof window !== 'undefined'
export default isBrowser
31 changes: 31 additions & 0 deletions src/utils/overrideLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import sessionStorage from '../session-storage/sessionStorage'
import isBrowser from './isBrowser'

/**
* Overrides the window.localStorage with the bridge's sessionStorage (Widget's Storage)
*/
export const overrideLocalStorage = () => {
class BridgeLocalStorage {
setItem(key: string, value: any) {
sessionStorage.setItem(key, value)
}

getItem(key: string) {
return sessionStorage.getItem(key)
}

removeItem(key: string) {
sessionStorage.removeItem(key)
}
}

const myLocalStorage = new BridgeLocalStorage()

// Assign the newly created instance to localStorage
if (isBrowser()) {
Object.defineProperty(window, 'localStorage', {
value: myLocalStorage,
writable: true,
})
}
}
5 changes: 1 addition & 4 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@lib/*": ["./*"]
}
"baseUrl": "."
}
}

0 comments on commit ccaedac

Please sign in to comment.