Skip to content

Commit

Permalink
Merge pull request #55 from gunet/reliable-storage
Browse files Browse the repository at this point in the history
Make useStorage hooks more reliable (probably)
  • Loading branch information
gkatrakazas committed Sep 22, 2023
2 parents 99f3d4a + 89615e7 commit bcc7816
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 43 deletions.
2 changes: 1 addition & 1 deletion src/components/Notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const Notification = () => {
}, [notification]);

useEffect(() => {
const unregisterMessageListener = onMessageListener()
const unregisterMessageListener = () => onMessageListener()
.then((payload) => {
setNotification({ title: payload?.notification?.title, body: payload?.notification?.body });
})
Expand Down
90 changes: 52 additions & 38 deletions src/components/useStorage.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react';
import { Dispatch, SetStateAction, useCallback, useEffect, useId, useState } from 'react';
import { jsonParseTaggedBinary, jsonStringifyTaggedBinary } from '../util';

type UseStateHandle<T> = [T, Dispatch<SetStateAction<T>>];
type UseGlobalStateHook<T> = (name: string, [value, setValue]: UseStateHandle<T>) => UseStateHandle<T>;

function makeUseGlobalState<T>(): (name: string, [value, setValue]: UseStateHandle<T>) => UseStateHandle<T> {
function makeUseGlobalState<T>(): UseGlobalStateHook<T> {
const setValueHandles = {};
return (name: string, [value, setValue]: UseStateHandle<T>) => {
const handleId = useId();

const setAllValues = useCallback(
(setValueArg: SetStateAction<any>) => {
setValueHandles[name].forEach((setValueHandle: Dispatch<SetStateAction<T>>) => {
Object.values(setValueHandles[name]).forEach((setValueHandle: Dispatch<SetStateAction<T>>) => {
setValueHandle(setValueArg);
});
},
Expand All @@ -18,48 +21,65 @@ function makeUseGlobalState<T>(): (name: string, [value, setValue]: UseStateHand
useEffect(
() => {
if (!setValueHandles[name]) {
setValueHandles[name] = [];
setValueHandles[name] = {};
}
setValueHandles[name].push(setValue);
setValueHandles[name][handleId] = setValue;

return () => {
const i = setValueHandles[name].indexOf(setValue);
setValueHandles[name].splice(i, 1);
delete setValueHandles[name][handleId];
};
},
[name, setValue]
[handleId, name, setValue]
);

return [value, setAllValues];
};
}
const useGlobalState: <T>(name: string, [value, setValue]: UseStateHandle<T>) => UseStateHandle<T> = makeUseGlobalState();

function makeUseStorage<T>(storage: Storage): (name: string, initialValue: T) => UseStateHandle<T> {
return (name: string, initialValue: T) => {
const storedValueStr = storage.getItem(name);
let storedValue = initialValue;
try {
if (storedValueStr !== null) {
storedValue = jsonParseTaggedBinary(storedValueStr);
}
} catch (e) {
// Fall back to initialValue
storage.removeItem(name);
}
const [currentValue, setValue] = useState(storedValue);
function makeUseStorage<T>(
storage: Storage,
description: string,
useGlobalState: UseGlobalStateHook<T>,
): (name: string, initialValue: T) => UseStateHandle<T> {
if (!storage) {
throw new Error(`${description} is not available.`);
}

useEffect(
return (name: string, initialValue: T) => {
const [currentValue, setValue] = useState(
() => {
const storedValueStr = storage.getItem(name);
try {
if (!(currentValue === initialValue && storage.getItem(name) === null)) {
storage.setItem(name, jsonStringifyTaggedBinary(currentValue));
if (storedValueStr !== null) {
return jsonParseTaggedBinary(storedValueStr);
}
} catch (e) {
console.error(`Failed to update session storage "${name}"`, e);
// Fall back to initialValue
storage.removeItem(name);
}
return initialValue;
}
);

// Browser storage is global state, so update all useState hooks with the
// same name whenever one of them changes. The storage event is not fired
// when storage.setItem is called in the same Document.
const [, setAllValues] = useGlobalState(name, [currentValue, setValue]);

const updateValue = useCallback(
(action: SetStateAction<T>): void => {
const newValue =
action instanceof Function
? action(currentValue)
: action;
try {
storage.setItem(name, jsonStringifyTaggedBinary(newValue));
} catch (e) {
console.error(`Failed to update storage "${name}"`, e);
}
setAllValues(newValue);
},
[currentValue, initialValue, name]
[currentValue, name, setAllValues],
);

useEffect(
Expand All @@ -78,21 +98,15 @@ function makeUseStorage<T>(storage: Storage): (name: string, initialValue: T) =>
[name]
);

// Session storage is global state, so update all useState hooks whenever
// one of them changes.
return useGlobalState(name, [currentValue, setValue]);
return [currentValue, updateValue];
};
}

export const useLocalStorage: <T>(name: string, initialValue: T) => UseStateHandle<T> = makeUseStorage(localStorage);
export const useSessionStorage: <T>(name: string, initialValue: T) => UseStateHandle<T> = (
sessionStorage
? makeUseStorage(sessionStorage)
export const useLocalStorage: <T>(name: string, initialValue: T) => UseStateHandle<T> =
makeUseStorage(window.localStorage, "Local storage", makeUseGlobalState());

// Emulate the global state behaviour of session state even when
// sessionStorage is not available.
: (name: string, initialValue: any) => useGlobalState(name, useState(initialValue))
);
export const useSessionStorage: <T>(name: string, initialValue: T) => UseStateHandle<T> =
makeUseStorage(window.sessionStorage, "Session storage", makeUseGlobalState());

export const useClearLocalStorage = () => useCallback(
() => {
Expand Down
16 changes: 12 additions & 4 deletions src/hoc/handleServerMessagesGuard.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default function handleServerMessagesGuard(Component) {
const socket = new WebSocket(REACT_APP_WS_URL);
const keystore = new useLocalStorageKeystore();
const signingRequestHandlerService = SigningRequestHandlerService();
const [isSocketOpen, setIsSocketOpen] = useState(false);


socket.addEventListener('open', (event) => {
Expand All @@ -24,17 +25,20 @@ export default function handleServerMessagesGuard(Component) {
}
console.log("Sending...")
// send handshake request
socket.send(JSON.stringify({ type: "INIT", appToken: appToken }))
socket.send(JSON.stringify({ type: "INIT", appToken: appToken }));
setIsSocketOpen(true); // Set the state to indicate that the connection is open
});

const waitForHandshake = async () => {
return new Promise((resolve, reject) => {
socket.onmessage = event => {
try {
console.log('--->',event.data.toString());
const { type } = JSON.parse(event.data.toString());
if (type == "FIN_INIT") {
console.log("init fin")
setHandshakeEstablished(true);

resolve({});
}
}
Expand All @@ -46,8 +50,11 @@ export default function handleServerMessagesGuard(Component) {
}

useEffect(() => {
waitForHandshake();
}, []);
if (isSocketOpen) {
waitForHandshake();
// You can perform other actions that depend on the socket being open here.
}
}, [isSocketOpen]);

socket.addEventListener('message', async (event) => {
try {
Expand All @@ -64,14 +71,15 @@ export default function handleServerMessagesGuard(Component) {
}
}
catch(e) {
console.log('failed to parse message')
}
})

console.log('->',handshakeEstablished,appToken);
if (handshakeEstablished === true || !appToken) {
return (<Component {...props} />);
}
else {

return (<Spinner />); // loading component
}
}
Expand Down

0 comments on commit bcc7816

Please sign in to comment.