flet-web 0.29.0.dev5039__py3-none-any.whl → 0.70.0.dev5066__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. flet_web/__init__.py +8 -1
  2. flet_web/fastapi/__init__.py +2 -0
  3. flet_web/fastapi/app.py +44 -17
  4. flet_web/fastapi/flet_app.py +195 -288
  5. flet_web/fastapi/flet_app_manager.py +41 -64
  6. flet_web/fastapi/flet_oauth.py +2 -1
  7. flet_web/fastapi/flet_static_files.py +54 -50
  8. flet_web/fastapi/serve_fastapi_web_app.py +30 -27
  9. flet_web/patch_index.py +35 -35
  10. flet_web/version.py +1 -1
  11. flet_web/web/assets/NOTICES +1815 -3803
  12. flet_web/web/assets/fonts/roboto.woff2 +0 -0
  13. flet_web/web/assets/packages/media_kit/assets/web/hls1.4.10.js +2 -2
  14. flet_web/web/canvaskit/canvaskit.js +192 -0
  15. flet_web/web/canvaskit/canvaskit.js.symbols +12098 -0
  16. flet_web/web/canvaskit/canvaskit.wasm +0 -0
  17. flet_web/web/canvaskit/chromium/canvaskit.js +192 -0
  18. flet_web/web/canvaskit/chromium/canvaskit.js.symbols +11050 -0
  19. flet_web/web/canvaskit/chromium/canvaskit.wasm +0 -0
  20. flet_web/web/canvaskit/skwasm.js +137 -0
  21. flet_web/web/canvaskit/skwasm.js.symbols +12073 -0
  22. flet_web/web/canvaskit/skwasm.wasm +0 -0
  23. flet_web/web/flutter.js +30 -2
  24. flet_web/web/flutter.js.map +3 -3
  25. flet_web/web/flutter_bootstrap.js +50 -10
  26. flet_web/web/flutter_service_worker.js +23 -21
  27. flet_web/web/index.html +56 -11
  28. flet_web/web/main.dart.js +170400 -167642
  29. flet_web/web/main.dart.mjs +44188 -0
  30. flet_web/web/main.dart.wasm +0 -0
  31. flet_web/web/pyodide/ffi.d.ts +10 -1
  32. flet_web/web/pyodide/micropip-0.8.0-py3-none-any.whl +14 -0
  33. flet_web/web/pyodide/package.json +2 -2
  34. flet_web/web/pyodide/packaging-24.2-py3-none-any.whl +0 -0
  35. flet_web/web/pyodide/pyodide-lock.json +1 -1
  36. flet_web/web/pyodide/pyodide.asm.js +1 -1
  37. flet_web/web/pyodide/pyodide.asm.wasm +0 -0
  38. flet_web/web/pyodide/pyodide.d.ts +15 -3
  39. flet_web/web/pyodide/pyodide.js +1 -1
  40. flet_web/web/pyodide/pyodide.mjs +2 -2
  41. flet_web/web/pyodide/python_stdlib.zip +0 -0
  42. flet_web/web/python-worker.js +75 -24
  43. flet_web/web/python.js +62 -20
  44. {flet_web-0.29.0.dev5039.dist-info → flet_web-0.70.0.dev5066.dist-info}/METADATA +3 -3
  45. flet_web-0.70.0.dev5066.dist-info/RECORD +71 -0
  46. flet_web-0.29.0.dev5039.dist-info/RECORD +0 -57
  47. {flet_web-0.29.0.dev5039.dist-info → flet_web-0.70.0.dev5066.dist-info}/WHEEL +0 -0
  48. {flet_web-0.29.0.dev5039.dist-info → flet_web-0.70.0.dev5066.dist-info}/top_level.txt +0 -0
@@ -1,42 +1,52 @@
1
1
  import asyncio
2
- import copy
3
- import json
2
+ import inspect
4
3
  import logging
5
4
  import os
6
5
  import traceback
6
+ import weakref
7
+ from concurrent.futures import ThreadPoolExecutor
7
8
  from datetime import datetime, timedelta, timezone
8
- from typing import Any, Dict, List, Optional
9
+ from typing import Any, Optional
9
10
 
10
- import flet_web.fastapi as flet_fastapi
11
+ import msgpack
11
12
  from fastapi import WebSocket, WebSocketDisconnect
12
- from flet.core.event import Event
13
- from flet.core.local_connection import LocalConnection
14
- from flet.core.page import Page, PageDisconnectedException
15
- from flet.core.protocol import (
16
- ClientActions,
13
+ from flet.controls.base_control import BaseControl
14
+ from flet.controls.page import PageDisconnectedException, _session_page
15
+ from flet.controls.update_behavior import UpdateBehavior
16
+ from flet.messaging.connection import Connection
17
+ from flet.messaging.protocol import (
18
+ ClientAction,
17
19
  ClientMessage,
18
- Command,
19
- CommandEncoder,
20
- PageCommandResponsePayload,
21
- PageCommandsBatchResponsePayload,
22
- RegisterWebClientRequestPayload,
20
+ ControlEventBody,
21
+ InvokeMethodResponseBody,
22
+ RegisterClientRequestBody,
23
+ RegisterClientResponseBody,
24
+ UpdateControlPropsBody,
25
+ configure_encode_object_for_msgpack,
26
+ decode_ext_from_msgpack,
23
27
  )
28
+ from flet.messaging.session import Session
24
29
  from flet.utils import random_string, sha1
30
+
31
+ import flet_web.fastapi as flet_fastapi
25
32
  from flet_web.fastapi.flet_app_manager import app_manager
26
33
  from flet_web.fastapi.oauth_state import OAuthState
27
34
  from flet_web.uploads import build_upload_url
28
35
 
29
36
  logger = logging.getLogger(flet_fastapi.__name__)
37
+ transport_log = logging.getLogger("flet_transport")
30
38
 
31
39
  DEFAULT_FLET_SESSION_TIMEOUT = 3600
32
40
  DEFAULT_FLET_OAUTH_STATE_TIMEOUT = 600
33
41
 
34
42
 
35
- class FletApp(LocalConnection):
43
+ class FletApp(Connection):
36
44
  def __init__(
37
45
  self,
38
46
  loop: asyncio.AbstractEventLoop,
39
- session_handler,
47
+ executor: ThreadPoolExecutor,
48
+ main,
49
+ before_main,
40
50
  session_timeout_seconds: int = DEFAULT_FLET_SESSION_TIMEOUT,
41
51
  oauth_state_timeout_seconds: int = DEFAULT_FLET_OAUTH_STATE_TIMEOUT,
42
52
  upload_endpoint_path: Optional[str] = None,
@@ -47,21 +57,30 @@ class FletApp(LocalConnection):
47
57
 
48
58
  Parameters:
49
59
 
50
- * `session_handler` (Coroutine) - application entry point - an async method called for newly connected user. Handler coroutine must have 1 parameter: `page` - `Page` instance.
51
- * `session_timeout_seconds` (int, optional) - session lifetime, in seconds, after user disconnected.
52
- * `oauth_state_timeout_seconds` (int, optional) - OAuth state lifetime, in seconds, which is a maximum allowed time between starting OAuth flow and redirecting to OAuth callback URL.
53
- * `upload_endpoint_path` (str, optional) - absolute URL of upload endpoint, e.g. `/upload`.
60
+ * `session_handler` (Coroutine) - application entry point - an async method
61
+ called for newly connected user. Handler coroutine must have
62
+ 1 parameter: `page` - `Page` instance.
63
+ * `session_timeout_seconds` (int, optional) - session lifetime, in seconds,
64
+ after user disconnected.
65
+ * `oauth_state_timeout_seconds` (int, optional) - OAuth state lifetime,
66
+ in seconds, which is a maximum allowed time between starting OAuth flow
67
+ and redirecting to OAuth callback URL.
68
+ * `upload_endpoint_path` (str, optional) - absolute URL of upload endpoint,
69
+ e.g. `/upload`.
54
70
  * `secret_key` (str, optional) - secret key to sign upload requests.
55
71
  """
56
72
  super().__init__()
57
73
  self.__id = random_string(8)
58
74
  logger.info(f"New FletApp: {self.__id}")
59
75
 
60
- self.__page = None
61
- self.__loop = loop
62
- self.__session_handler = session_handler
76
+ self.__session = None
77
+ self.loop = loop
78
+ self.executor = executor
79
+ self.__main = main
80
+ self.__before_main = before_main
63
81
  self.__session_timeout_seconds = session_timeout_seconds
64
82
  self.__oauth_state_timeout_seconds = oauth_state_timeout_seconds
83
+ self.__running_tasks = set()
65
84
 
66
85
  env_session_timeout_seconds = os.getenv("FLET_SESSION_TIMEOUT")
67
86
  if env_session_timeout_seconds:
@@ -74,6 +93,11 @@ class FletApp(LocalConnection):
74
93
  self.__upload_endpoint_path = upload_endpoint_path
75
94
  self.__secret_key = secret_key
76
95
 
96
+ app_id = self.__id
97
+ weakref.finalize(
98
+ self, lambda: logger.debug(f"FletApp was garbage collected: {app_id}")
99
+ )
100
+
77
101
  async def handle(self, websocket: WebSocket):
78
102
  """
79
103
  Handle WebSocket connection.
@@ -87,15 +111,9 @@ class FletApp(LocalConnection):
87
111
  self.client_ip = (
88
112
  self.__websocket.client.host if self.__websocket.client else ""
89
113
  ).split(":")[0]
90
- self.client_user_agent = (
91
- self.__websocket.headers["user-agent"]
92
- if "user-agent" in self.__websocket.headers
93
- else ""
94
- )
114
+ self.client_user_agent = self.__websocket.headers.get("user-agent", "")
95
115
 
96
- self.pubsubhub = app_manager.get_pubsubhub(
97
- self.__session_handler, loop=self.__loop
98
- )
116
+ self.pubsubhub = app_manager.get_pubsubhub(self.__main, loop=self.loop)
99
117
  self.page_url = str(websocket.url).rsplit("/", 1)[0]
100
118
  self.page_name = websocket.url.path.rsplit("/", 1)[0].lstrip("/")
101
119
 
@@ -106,320 +124,209 @@ class FletApp(LocalConnection):
106
124
 
107
125
  await self.__websocket.accept()
108
126
  self.__send_queue = asyncio.Queue()
109
- st = asyncio.create_task(self.__send_loop())
127
+ send_loop_task = asyncio.create_task(self.__send_loop())
110
128
  await self.__receive_loop()
111
- st.cancel()
129
+ await send_loop_task
112
130
 
113
- async def __on_event(self, e):
114
- session = await app_manager.get_session(
115
- self.__get_unique_session_id(e.sessionID)
131
+ # disconnect this connection from a session
132
+ await app_manager.disconnect_session(
133
+ self.__get_unique_session_id(self.__session.id),
134
+ self.__session_timeout_seconds,
116
135
  )
117
- if session is not None:
118
- try:
119
- await session.on_event_async(
120
- Event(e.eventTarget, e.eventName, e.eventData)
121
- )
122
- except PageDisconnectedException:
123
- logger.debug(
124
- f"Event handler attempted to update disconnected page: {e.sessionID}"
125
- )
126
- if e.eventTarget == "page" and e.eventName == "close":
127
- logger.info(f"Session closed: {e.sessionID}")
128
- await app_manager.delete_session(
129
- self.__get_unique_session_id(e.sessionID)
130
- )
131
136
 
132
- async def __on_session_created(self, session_data):
133
- logger.info(f"Start session: {session_data.sessionID}")
134
- session_id = session_data.sessionID
137
+ async def __on_session_created(self):
138
+ assert self.__session
139
+ logger.info(f"Start session: {self.__session.id}")
135
140
  try:
136
- assert self.__session_handler is not None
137
- if asyncio.iscoroutinefunction(self.__session_handler):
138
- await self.__session_handler(self.__page)
141
+ assert self.__main is not None
142
+ _session_page.set(self.__session.page)
143
+ UpdateBehavior.reset()
144
+
145
+ if asyncio.iscoroutinefunction(self.__main):
146
+ await self.__main(self.__session.page)
147
+
148
+ elif inspect.isasyncgenfunction(self.__main):
149
+ async for _ in self.__main(self.__session.page):
150
+ if UpdateBehavior.auto_update_enabled():
151
+ await self.__session.auto_update(self.__session.page)
152
+
153
+ elif inspect.isgeneratorfunction(self.__main):
154
+ for _ in self.__main(self.__session.page):
155
+ if UpdateBehavior.auto_update_enabled():
156
+ await self.__session.auto_update(self.__session.page)
139
157
  else:
140
- # run in thread pool
141
- await asyncio.get_running_loop().run_in_executor(
142
- app_manager.executor, self.__session_handler, self.__page
143
- )
158
+ self.__main(self.__session.page)
159
+
160
+ if UpdateBehavior.auto_update_enabled():
161
+ await self.__session.auto_update(self.__session.page)
144
162
  except PageDisconnectedException:
145
163
  logger.debug(
146
- f"Session handler attempted to update disconnected page: {session_id}"
164
+ "Session handler attempted to update disconnected page: "
165
+ f"{self.__session.id}"
147
166
  )
148
167
  except BrokenPipeError:
149
- logger.info(f"Session handler terminated: {session_id}")
168
+ logger.info(f"Session handler terminated: {self.__session.id}")
150
169
  except Exception as e:
151
170
  print(
152
- f"Unhandled error processing page session {session_id}:",
171
+ f"Unhandled error processing page session {self.__session.id}:",
153
172
  traceback.format_exc(),
154
173
  )
155
- assert self.__page
156
- self.__page.error(f"There was an error while processing your request: {e}")
174
+ assert self.__session
175
+ self.__session.error(
176
+ f"There was an error while processing your request: {e}"
177
+ )
157
178
 
158
179
  async def __send_loop(self):
159
180
  assert self.__websocket
160
181
  assert self.__send_queue
161
182
  while True:
162
183
  message = await self.__send_queue.get()
184
+ if message is None:
185
+ break
186
+
163
187
  try:
164
- await self.__websocket.send_text(message)
188
+ await self.__websocket.send_bytes(message)
165
189
  except Exception:
166
190
  # re-enqueue the message to repeat it when re-connected
167
- self.__send_queue.put_nowait(message)
191
+ # self.__send_queue.put_nowait(message)
168
192
  raise
193
+ self.__websocket = None
194
+ self.__send_queue = None
169
195
 
170
196
  async def __receive_loop(self):
171
197
  assert self.__websocket
172
198
  try:
173
199
  while True:
174
- await self.__on_message(await self.__websocket.receive_text())
200
+ data = await self.__websocket.receive_bytes()
201
+ await self.__on_message(
202
+ msgpack.unpackb(data, ext_hook=decode_ext_from_msgpack)
203
+ )
175
204
  except Exception as e:
176
205
  if not isinstance(e, WebSocketDisconnect):
177
- logger.warning(f"Receive loop error: {e}")
178
- if self.__page:
179
- await app_manager.disconnect_session(
180
- self.__get_unique_session_id(self.__page.session_id),
181
- self.__session_timeout_seconds,
206
+ logger.warning(f"Receive loop error: {e}", exc_info=True)
207
+ if self.__session:
208
+ # terminate __send_loop
209
+ await self.__send_queue.put(None)
210
+
211
+ async def __on_message(self, data: Any):
212
+ action = ClientAction(data[0])
213
+ body = data[1]
214
+ transport_log.debug(f"_on_message: {action} {body}")
215
+ task = None
216
+ if action == ClientAction.REGISTER_CLIENT:
217
+ req = RegisterClientRequestBody(**body)
218
+
219
+ new_session = False
220
+
221
+ # try to retrieve existing session
222
+ if req.session_id:
223
+ self.__session = await app_manager.get_session(
224
+ self.__get_unique_session_id(req.session_id)
182
225
  )
183
- self.__websocket = None
184
- self.__send_queue = None
185
226
 
186
- async def __on_message(self, data: str):
187
- logger.debug(f"_on_message: {data}")
188
- msg_dict = json.loads(data)
189
- msg = ClientMessage(**msg_dict)
190
- if msg.action == ClientActions.REGISTER_WEB_CLIENT:
191
- self._client_details = RegisterWebClientRequestPayload(**msg.payload)
192
-
193
- new_session = True
194
- if (
195
- not self._client_details.sessionId
196
- or await app_manager.get_session(
197
- self.__get_unique_session_id(self._client_details.sessionId)
198
- )
199
- is None
200
- ):
201
- # generate session ID
202
- self._client_details.sessionId = random_string(16)
203
-
204
- # create new Page object
205
- self.__page = Page(
206
- self,
207
- self._client_details.sessionId,
208
- executor=app_manager.executor,
209
- loop=asyncio.get_running_loop(),
210
- )
227
+ # re-create session
228
+ if self.__session is None:
229
+ new_session = True
230
+
231
+ # create new session
232
+ self.__session = Session(self)
211
233
 
212
234
  # register session
213
235
  await app_manager.add_session(
214
- self.__get_unique_session_id(self._client_details.sessionId),
215
- self.__page,
216
- )
217
- else:
218
- # existing session
219
- logger.info(
220
- f"Existing session requested: {self._client_details.sessionId}"
236
+ self.__get_unique_session_id(self.__session.id),
237
+ self.__session,
221
238
  )
222
- self.__page = await app_manager.get_session(
223
- self.__get_unique_session_id(self._client_details.sessionId)
239
+
240
+ original_route = self.__session.page.route
241
+
242
+ # apply page patch
243
+ self.__session.apply_page_patch(req.page)
244
+
245
+ if new_session:
246
+ if asyncio.iscoroutinefunction(self.__before_main):
247
+ await self.__before_main(self.__session.page)
248
+ elif callable(self.__before_main):
249
+ self.__before_main(self.__session.page)
250
+
251
+ # register response
252
+ self.send_message(
253
+ ClientMessage(
254
+ ClientAction.REGISTER_CLIENT,
255
+ RegisterClientResponseBody(
256
+ session_id=self.__session.id,
257
+ page_patch=self.__session.get_page_patch(),
258
+ error="",
259
+ ),
224
260
  )
225
- new_session = False
226
-
227
- # update page props
228
- assert self.__page
229
- original_route = self.__page.route
230
- self.__page._set_attr("route", self._client_details.pageRoute, False)
231
- self.__page._set_attr("pwa", self._client_details.isPWA, False)
232
- self.__page._set_attr("web", self._client_details.isWeb, False)
233
- self.__page._set_attr("debug", self._client_details.isDebug, False)
234
- self.__page._set_attr("platform", self._client_details.platform, False)
235
- self.__page._set_attr(
236
- "platformBrightness", self._client_details.platformBrightness, False
237
- )
238
- self.__page._set_attr("media", self._client_details.media, False)
239
- self.__page._set_attr("width", self._client_details.pageWidth, False)
240
- self.__page._set_attr("height", self._client_details.pageHeight, False)
241
- self.__page._set_attr(
242
- "windowWidth", self._client_details.windowWidth, False
243
- )
244
- self.__page._set_attr(
245
- "windowHeight", self._client_details.windowHeight, False
246
- )
247
- self.__page._set_attr("windowTop", self._client_details.windowTop, False)
248
- self.__page._set_attr("windowLeft", self._client_details.windowLeft, False)
249
- self.__page._set_attr("clientIP", self.client_ip, False)
250
- self.__page._set_attr("clientUserAgent", self.client_user_agent, False)
251
-
252
- p = self.__page.snapshot.get("page")
253
- if not p:
254
- p = {
255
- "i": "page",
256
- "t": "page",
257
- "p": "",
258
- "c": [],
259
- }
260
- self.__page.snapshot["page"] = p
261
- self.__page.copy_attrs(p)
262
-
263
- # send register response
264
- self.__send(
265
- self._create_register_web_client_response(controls=self.__page.snapshot)
266
261
  )
267
262
 
268
263
  # start session
269
264
  if new_session:
270
- asyncio.create_task(
271
- self.__on_session_created(self._create_session_handler_arg())
272
- )
265
+ asyncio.create_task(self.__on_session_created())
273
266
  else:
274
267
  await app_manager.reconnect_session(
275
- self.__get_unique_session_id(self._client_details.sessionId), self
268
+ self.__get_unique_session_id(self.__session.id), self
276
269
  )
277
270
 
278
- if original_route != self.__page.route:
279
- self.__page.go(self.__page.route)
271
+ if (
272
+ self.__session.page.route
273
+ and self.__session.page.route != original_route
274
+ ):
275
+ self.__session.page.go(self.__session.page.route)
280
276
 
281
- elif msg.action == ClientActions.PAGE_EVENT_FROM_WEB:
282
- if self.__on_event is not None:
283
- asyncio.create_task(
284
- self.__on_event(self._create_page_event_handler_arg(msg))
285
- )
277
+ elif action == ClientAction.CONTROL_EVENT:
278
+ req = ControlEventBody(**body)
279
+ task = asyncio.create_task(
280
+ self.__session.dispatch_event(req.target, req.name, req.data)
281
+ )
282
+
283
+ elif action == ClientAction.UPDATE_CONTROL_PROPS:
284
+ req = UpdateControlPropsBody(**body)
285
+ self.__session.apply_patch(req.id, req.props)
286
+
287
+ elif action == ClientAction.INVOKE_METHOD:
288
+ req = InvokeMethodResponseBody(**body)
289
+ self.__session.handle_invoke_method_results(
290
+ req.control_id, req.call_id, req.result, req.error
291
+ )
286
292
 
287
- elif msg.action == ClientActions.UPDATE_CONTROL_PROPS:
288
- if self.__on_event is not None:
289
- asyncio.create_task(
290
- self.__on_event(self._create_update_control_props_handler_arg(msg))
291
- )
292
293
  else:
293
294
  # it's something else
294
- raise Exception(f'Unknown message "{msg.action}": {msg.payload}')
295
-
296
- def _process_get_upload_url_command(self, attrs):
297
- assert len(attrs) == 2, '"getUploadUrl" command has wrong number of attrs'
298
- assert (
299
- self.__upload_endpoint_path
300
- ), "upload_path should be specified to enable uploads"
301
- return (
302
- build_upload_url(
303
- self.__upload_endpoint_path,
304
- attrs["file"],
305
- int(attrs["expires"]),
306
- self.__secret_key,
307
- ),
308
- None,
295
+ raise Exception(f'Unknown message "{action}": {body}')
296
+
297
+ if task:
298
+ self.__running_tasks.add(task)
299
+ task.add_done_callback(self.__running_tasks.discard)
300
+
301
+ def send_message(self, message: ClientMessage):
302
+ transport_log.debug(f"send_message: {message}")
303
+ m = msgpack.packb(
304
+ [message.action, message.body],
305
+ default=configure_encode_object_for_msgpack(BaseControl),
306
+ )
307
+ self.__send_queue.put_nowait(m)
308
+
309
+ def get_upload_url(self, file_name: str, expires: int) -> str:
310
+ assert self.__upload_endpoint_path, (
311
+ "upload_path should be specified to enable uploads"
312
+ )
313
+ return build_upload_url(
314
+ self.__upload_endpoint_path,
315
+ file_name,
316
+ expires,
317
+ self.__secret_key,
309
318
  )
310
319
 
311
- def __process_oauth_authorize_command(self, attrs: Dict[str, Any]):
320
+ def oauth_authorize(self, attrs: dict[str, Any]):
312
321
  state_id = attrs["state"]
313
322
  state = OAuthState(
314
- session_id=self.__get_unique_session_id(self._client_details.sessionId),
323
+ session_id=self.__get_unique_session_id(self.__session.id),
315
324
  expires_at=datetime.now(timezone.utc)
316
325
  + timedelta(seconds=self.__oauth_state_timeout_seconds),
317
- complete_page_html=attrs.get("completePageHtml", None),
318
- complete_page_url=attrs.get("completePageUrl", None),
326
+ complete_page_html=attrs.get("completePageHtml"),
327
+ complete_page_url=attrs.get("completePageUrl"),
319
328
  )
320
329
  app_manager.store_state(state_id, state)
321
- return (
322
- "",
323
- None,
324
- )
325
-
326
- def _process_add_command(self, command: Command):
327
- assert self.__page
328
- result, message = super()._process_add_command(command)
329
- if message:
330
- for oc in message.payload.controls:
331
- control = copy.deepcopy(oc)
332
- id = control["i"]
333
- pid = control["p"]
334
- parent = self.__page.snapshot[pid]
335
- assert parent, f"parent control not found: {pid}"
336
- if id not in parent["c"]:
337
- if "at" in control:
338
- parent["c"].insert(int(control["at"]), id)
339
- else:
340
- parent["c"].append(id)
341
- self.__page.snapshot[id] = control
342
- return result, message
343
-
344
- def _process_set_command(self, values, attrs):
345
- assert self.__page
346
- result, message = super()._process_set_command(values, attrs)
347
- control = self.__page.snapshot.get(values[0])
348
- if control:
349
- for k, v in attrs.items():
350
- control[k] = v
351
- return result, message
352
-
353
- def _process_remove_command(self, values):
354
- assert self.__page
355
- result, message = super()._process_remove_command(values)
356
- for id in values:
357
- control = self.__page.snapshot.get(id)
358
- assert (
359
- control is not None
360
- ), f"_process_remove_command: control with ID '{id}' not found."
361
- for cid in self.__get_all_descendant_ids(id):
362
- self.__page.snapshot.pop(cid, None)
363
- # delete control itself
364
- self.__page.snapshot.pop(id, None)
365
- # remove id from parent
366
- parent = self.__page.snapshot.get(control["p"])
367
- if parent:
368
- parent["c"].remove(id)
369
- return result, message
370
-
371
- def _process_clean_command(self, values):
372
- assert self.__page
373
- result, message = super()._process_clean_command(values)
374
- for id in values:
375
- for cid in self.__get_all_descendant_ids(id):
376
- self.__page.snapshot.pop(cid, None)
377
- return result, message
378
-
379
- def __get_all_descendant_ids(self, id):
380
- assert self.__page
381
- ids = []
382
- control = self.__page.snapshot.get(id)
383
- if control:
384
- for cid in control["c"]:
385
- ids.append(cid)
386
- ids.extend(self.__get_all_descendant_ids(cid))
387
- return ids
388
-
389
- def send_command(self, session_id: str, command: Command):
390
- if command.name == "oauthAuthorize":
391
- result, message = self.__process_oauth_authorize_command(command.attrs)
392
- else:
393
- result, message = self._process_command(command)
394
- if message:
395
- self.__send(message)
396
- return PageCommandResponsePayload(result=result, error="")
397
-
398
- def send_commands(self, session_id: str, commands: List[Command]):
399
- results = []
400
- messages = []
401
- for command in commands:
402
- if command.name == "oauthAuthorize":
403
- result, message = self.__process_oauth_authorize_command(command.attrs)
404
- else:
405
- result, message = self._process_command(command)
406
- if command.name in ["add", "get"]:
407
- results.append(result)
408
- if message:
409
- messages.append(message)
410
- if len(messages) > 0:
411
- self.__send(ClientMessage(ClientActions.PAGE_CONTROLS_BATCH, messages))
412
- return PageCommandsBatchResponsePayload(results=results, error="")
413
-
414
- def __send(self, message: ClientMessage):
415
- m = json.dumps(message, cls=CommandEncoder, separators=(",", ":"))
416
- logger.debug(f"__send: {m}")
417
- if self.__send_queue:
418
- self.__loop.call_soon_threadsafe(self.__send_queue.put_nowait, m)
419
-
420
- def _get_next_control_id(self):
421
- assert self.__page
422
- return self.__page.get_next_control_id()
423
330
 
424
331
  def __get_unique_session_id(self, session_id: str):
425
332
  client_hash = sha1(f"{self.client_ip}{self.client_user_agent}")
@@ -427,4 +334,4 @@ class FletApp(LocalConnection):
427
334
 
428
335
  def dispose(self):
429
336
  logger.info(f"Disposing FletApp: {self.__id}")
430
- self.__page = None
337
+ self.__session = None