Skip to content

Commit aa5aa65

Browse files
committed
Implementing access to sessionStorage
1 parent 6eda41d commit aa5aa65

File tree

8 files changed

+135
-31
lines changed

8 files changed

+135
-31
lines changed

src/js/packages/@reactpy/client/src/components.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ export function Layout(props: { client: ReactPyClient }): JSX.Element {
5252
})
5353
)
5454

55+
useEffect(
56+
() =>
57+
props.client.onMessage("sync-session-storage", ({ type, storage}) => {
58+
for (let itemKey in storage) {
59+
window.sessionStorage.setItem(
60+
itemKey,
61+
storage[itemKey]
62+
)
63+
}
64+
})
65+
)
66+
5567
return (
5668
<ClientContext.Provider value={props.client}>
5769
<Element model={currentModel} />

src/js/packages/@reactpy/client/src/messages.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export type LocalStorageUpdateMessage = {
1717
storage: any;
1818
}
1919

20-
export type IncomingMessage = LayoutUpdateMessage | LocalStorageUpdateMessage;
21-
export type OutgoingMessage = LayoutEventMessage | LocalStorageUpdateMessage;
20+
export type SessionStorageUpdateMessage = {
21+
type: "sync-session-storage",
22+
storage: any;
23+
}
24+
25+
export type IncomingMessage = LayoutUpdateMessage | LocalStorageUpdateMessage | SessionStorageUpdateMessage;
26+
export type OutgoingMessage = LayoutEventMessage | LocalStorageUpdateMessage | SessionStorageUpdateMessage;
2227
export type Message = IncomingMessage | OutgoingMessage;

src/js/packages/@reactpy/client/src/reactpy-client.ts

+25-9
Original file line numberDiff line numberDiff line change
@@ -205,21 +205,37 @@ function createReconnectingWebSocket(
205205
everConnected = true;
206206
logger.log("client connected");
207207

208-
let storage : any = {}
208+
let _localStorage : any = {}
209209
for (let i = 0; i <= window.localStorage.length; i++) {
210210
let storage_key = window.localStorage.key(i)
211211
if (storage_key){
212-
storage[storage_key] = window.localStorage.getItem(storage_key)
212+
_localStorage[storage_key] = window.localStorage.getItem(storage_key)
213213
}
214-
socket.current?.send(
215-
JSON.stringify(
216-
{
217-
"type": "sync-local-storage",
218-
"storage": storage
219-
}
220-
)
214+
}
215+
socket.current?.send(
216+
JSON.stringify(
217+
{
218+
"type": "sync-local-storage",
219+
"storage": _localStorage
220+
}
221221
)
222+
)
223+
224+
let _sessionStorage : any = {}
225+
for (let i = 0; i <= window.sessionStorage.length; i++) {
226+
let storage_key = window.sessionStorage.key(i)
227+
if (storage_key){
228+
_sessionStorage[storage_key] = window.sessionStorage.getItem(storage_key)
229+
}
222230
}
231+
socket.current?.send(
232+
JSON.stringify(
233+
{
234+
"type": "sync-local-storage",
235+
"storage": _sessionStorage
236+
}
237+
)
238+
)
223239

224240
interval = startInterval;
225241
retries = 0;

src/py/reactpy/reactpy/backend/hooks.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from reactpy.backend.types import Connection, Location
77
from reactpy.core.hooks import Context, create_context, use_context
8-
from reactpy.core.types import LocalStorage
8+
from reactpy.core.types import LocalStorage, SessionStorage
99

1010
# backend implementations should establish this context at the root of an app
1111
ConnectionContext: Context[Connection[Any] | None] = create_context(None)
@@ -30,5 +30,9 @@ def use_location() -> Location:
3030
return use_connection().location
3131

3232
def use_local_storage() -> LocalStorage:
33-
"""Get the storage object for the connection"""
34-
return use_connection().storage
33+
"""Get the localStorage object for the connection"""
34+
return use_connection().local_storage
35+
36+
def use_session_storage() -> SessionStorage:
37+
"""Get the sessionStorage object for the connection"""
38+
return use_connection().session_storage

src/py/reactpy/reactpy/backend/starlette.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from reactpy.config import REACTPY_WEB_MODULES_DIR
3030
from reactpy.core.layout import Layout
3131
from reactpy.core.serve import RecvCoroutine, SendCoroutine, serve_layout
32-
from reactpy.core.types import RootComponentConstructor, LocalStorage
32+
from reactpy.core.types import RootComponentConstructor, LocalStorage, SessionStorage
3333

3434
logger = logging.getLogger(__name__)
3535

@@ -79,7 +79,7 @@ def use_connection() -> Connection[WebSocket]:
7979
if not isinstance(conn.carrier, WebSocket): # nocov
8080
msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?"
8181
raise TypeError(msg)
82-
return
82+
return conn
8383

8484

8585
@dataclass
@@ -140,7 +140,8 @@ async def model_stream(socket: WebSocket) -> None:
140140
pathname = "/" + socket.scope["path_params"].get("path", "")
141141
pathname = pathname[len(options.url_prefix) :] or "/"
142142
search = socket.scope["query_string"].decode()
143-
storage_obj = LocalStorage(sock=socket)
143+
local_storage_obj = LocalStorage(sock=socket)
144+
session_storage_obj = SessionStorage(sock=socket)
144145

145146
try:
146147
await serve_layout(
@@ -150,12 +151,14 @@ async def model_stream(socket: WebSocket) -> None:
150151
value=Connection(
151152
scope=socket.scope,
152153
location=Location(pathname, f"?{search}" if search else ""),
153-
storage=storage_obj,
154+
local_storage=local_storage_obj,
155+
session_storage=session_storage_obj,
154156
carrier=socket,
155157
),
156158
)
157159
),
158-
storage_obj,
160+
local_storage_obj,
161+
session_storage_obj,
159162
send,
160163
recv,
161164
)

src/py/reactpy/reactpy/backend/types.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from dataclasses import dataclass
66
from typing import Any, Callable, Generic, Protocol, TypeVar, runtime_checkable
77

8-
from reactpy.core.types import RootComponentConstructor, LocalStorage
8+
from reactpy.core.types import RootComponentConstructor, LocalStorage, SessionStorage
99

1010
_App = TypeVar("_App")
1111

@@ -51,7 +51,11 @@ class Connection(Generic[_Carrier]):
5151
location: Location
5252
"""The current location (URL)"""
5353

54-
storage: LocalStorage
54+
local_storage: LocalStorage
55+
"""An object to obtain client localStorage"""
56+
57+
session_storage: SessionStorage
58+
"""An object to obtain client sessionStorage"""
5559

5660
carrier: _Carrier
5761
"""How the connection is mediated. For example, a request or websocket.

src/py/reactpy/reactpy/core/serve.py

+22-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@
88
from anyio.abc import TaskGroup
99

1010
from reactpy.config import REACTPY_DEBUG_MODE
11-
from reactpy.core.types import StorageEventMessage, LocalStorage, LayoutEventMessage, LayoutType, LayoutUpdateMessage
11+
from reactpy.core.types import (
12+
LocalStorageEventMessage,
13+
SessionStorageEventMessage,
14+
LocalStorage,
15+
SessionStorage,
16+
LayoutEventMessage,
17+
LayoutType,
18+
LayoutUpdateMessage
19+
)
1220
from reactpy.core.layout import Layout
1321
logger = getLogger(__name__)
1422

@@ -33,7 +41,8 @@ class Stop(BaseException):
3341

3442
async def serve_layout(
3543
layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage],
36-
storage: LocalStorage,
44+
local_storage: LocalStorage,
45+
session_storage: SessionStorage,
3746
send: SendCoroutine,
3847
recv: RecvCoroutine,
3948
) -> None:
@@ -42,7 +51,7 @@ async def serve_layout(
4251
try:
4352
async with create_task_group() as task_group:
4453
task_group.start_soon(_single_outgoing_loop, layout, send)
45-
task_group.start_soon(_single_incoming_loop, task_group, layout, storage, recv)
54+
task_group.start_soon(_single_incoming_loop, task_group, layout, local_storage, session_storage,recv)
4655
except Stop:
4756
logger.info(f"Stopped serving {layout}")
4857

@@ -65,21 +74,25 @@ async def _single_outgoing_loop(
6574

6675
async def incoming_router(
6776
layout: Layout,
68-
storage: LocalStorage,
69-
event: LayoutEventMessage or StorageEventMessage,
77+
local_storage: LocalStorage,
78+
session_storage: SessionStorage,
79+
event: LayoutEventMessage or LocalStorageEventMessage or SessionStorageEventMessage,
7080
):
7181
if event["type"] == "sync-local-storage":
72-
storage._sync(event["storage"])
73-
else:
82+
local_storage._sync(event["storage"])
83+
elif event["type"] == "sync-session-storage":
84+
session_storage._sync(event["storage"])
85+
elif event["type"] == "layout-event":
7486
await layout.deliver(event)
7587

7688
async def _single_incoming_loop(
7789
task_group: TaskGroup,
7890
layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage],
79-
storage: LocalStorage,
91+
local_storage: LocalStorage,
92+
session_storage: SessionStorage,
8093
recv: RecvCoroutine,
8194
) -> None:
8295
while True:
8396
# We need to fire and forget here so that we avoid waiting on the completion
8497
# of this event handler before receiving and running the next one.
85-
task_group.start_soon(incoming_router, layout, storage, await recv())
98+
task_group.start_soon(incoming_router, layout, local_storage, session_storage, await recv())

src/py/reactpy/reactpy/core/types.py

+48-1
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,21 @@ class LayoutEventMessage(TypedDict):
235235
data: Sequence[Any]
236236
"""A list of event data passed to the event handler."""
237237

238-
class StorageEventMessage(TypedDict):
238+
class LocalStorageEventMessage(TypedDict):
239+
"""Message describing an event containing localStorage"""
240+
239241
type: Literal["sync-local-storage"]
242+
"""The type of message"""
243+
storage: dict
244+
"""A dictionary containing localStorage items"""
245+
246+
class SessionStorageEventMessage(TypedDict):
247+
"""Message describing an event containing sessionStorage"""
248+
249+
type: Literal["sync-session-storage"]
250+
"""Message describing an event containing localStorage"""
240251
storage: dict
252+
"""A dictionary containing localStorage items"""
241253

242254
class LocalStorage():
243255
_socket: WebSocket
@@ -266,6 +278,41 @@ def get_item(
266278
):
267279
return self.storage.get(key)
268280

281+
async def set_item(
282+
self,
283+
key: str,
284+
value: str
285+
):
286+
self.storage[key] = value
287+
await self._sync_client()
288+
289+
class SessionStorage():
290+
_socket: WebSocket
291+
storage: dict
292+
293+
def __init__(self, sock):
294+
self._socket = sock
295+
self.storage = {}
296+
297+
def _sync(self, sto):
298+
self.storage = sto
299+
300+
async def _sync_client(self):
301+
await self._socket.send_text(
302+
json.dumps(
303+
{
304+
"type": "sync-session-storage",
305+
"storage": self.storage
306+
}
307+
)
308+
)
309+
310+
def get_item(
311+
self,
312+
key: str
313+
):
314+
return self.storage.get(key)
315+
269316
async def set_item(
270317
self,
271318
key: str,

0 commit comments

Comments
 (0)