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.
- flet_web/__init__.py +8 -1
- flet_web/fastapi/__init__.py +2 -0
- flet_web/fastapi/app.py +44 -17
- flet_web/fastapi/flet_app.py +195 -288
- flet_web/fastapi/flet_app_manager.py +41 -64
- flet_web/fastapi/flet_oauth.py +2 -1
- flet_web/fastapi/flet_static_files.py +54 -50
- flet_web/fastapi/serve_fastapi_web_app.py +30 -27
- flet_web/patch_index.py +35 -35
- flet_web/version.py +1 -1
- flet_web/web/assets/NOTICES +1815 -3803
- flet_web/web/assets/fonts/roboto.woff2 +0 -0
- flet_web/web/assets/packages/media_kit/assets/web/hls1.4.10.js +2 -2
- flet_web/web/canvaskit/canvaskit.js +192 -0
- flet_web/web/canvaskit/canvaskit.js.symbols +12098 -0
- flet_web/web/canvaskit/canvaskit.wasm +0 -0
- flet_web/web/canvaskit/chromium/canvaskit.js +192 -0
- flet_web/web/canvaskit/chromium/canvaskit.js.symbols +11050 -0
- flet_web/web/canvaskit/chromium/canvaskit.wasm +0 -0
- flet_web/web/canvaskit/skwasm.js +137 -0
- flet_web/web/canvaskit/skwasm.js.symbols +12073 -0
- flet_web/web/canvaskit/skwasm.wasm +0 -0
- flet_web/web/flutter.js +30 -2
- flet_web/web/flutter.js.map +3 -3
- flet_web/web/flutter_bootstrap.js +50 -10
- flet_web/web/flutter_service_worker.js +23 -21
- flet_web/web/index.html +56 -11
- flet_web/web/main.dart.js +170400 -167642
- flet_web/web/main.dart.mjs +44188 -0
- flet_web/web/main.dart.wasm +0 -0
- flet_web/web/pyodide/ffi.d.ts +10 -1
- flet_web/web/pyodide/micropip-0.8.0-py3-none-any.whl +14 -0
- flet_web/web/pyodide/package.json +2 -2
- flet_web/web/pyodide/packaging-24.2-py3-none-any.whl +0 -0
- flet_web/web/pyodide/pyodide-lock.json +1 -1
- flet_web/web/pyodide/pyodide.asm.js +1 -1
- flet_web/web/pyodide/pyodide.asm.wasm +0 -0
- flet_web/web/pyodide/pyodide.d.ts +15 -3
- flet_web/web/pyodide/pyodide.js +1 -1
- flet_web/web/pyodide/pyodide.mjs +2 -2
- flet_web/web/pyodide/python_stdlib.zip +0 -0
- flet_web/web/python-worker.js +75 -24
- flet_web/web/python.js +62 -20
- {flet_web-0.29.0.dev5039.dist-info → flet_web-0.70.0.dev5066.dist-info}/METADATA +3 -3
- flet_web-0.70.0.dev5066.dist-info/RECORD +71 -0
- flet_web-0.29.0.dev5039.dist-info/RECORD +0 -57
- {flet_web-0.29.0.dev5039.dist-info → flet_web-0.70.0.dev5066.dist-info}/WHEEL +0 -0
- {flet_web-0.29.0.dev5039.dist-info → flet_web-0.70.0.dev5066.dist-info}/top_level.txt +0 -0
flet_web/fastapi/flet_app.py
CHANGED
|
@@ -1,42 +1,52 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import
|
|
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,
|
|
9
|
+
from typing import Any, Optional
|
|
9
10
|
|
|
10
|
-
import
|
|
11
|
+
import msgpack
|
|
11
12
|
from fastapi import WebSocket, WebSocketDisconnect
|
|
12
|
-
from flet.
|
|
13
|
-
from flet.
|
|
14
|
-
from flet.
|
|
15
|
-
from flet.
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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(
|
|
43
|
+
class FletApp(Connection):
|
|
36
44
|
def __init__(
|
|
37
45
|
self,
|
|
38
46
|
loop: asyncio.AbstractEventLoop,
|
|
39
|
-
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
* `
|
|
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.
|
|
61
|
-
self.
|
|
62
|
-
self.
|
|
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
|
-
|
|
127
|
+
send_loop_task = asyncio.create_task(self.__send_loop())
|
|
110
128
|
await self.__receive_loop()
|
|
111
|
-
|
|
129
|
+
await send_loop_task
|
|
112
130
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
self.__get_unique_session_id(
|
|
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
|
|
133
|
-
|
|
134
|
-
|
|
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.
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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: {
|
|
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 {
|
|
171
|
+
f"Unhandled error processing page session {self.__session.id}:",
|
|
153
172
|
traceback.format_exc(),
|
|
154
173
|
)
|
|
155
|
-
assert self.
|
|
156
|
-
self.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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.
|
|
215
|
-
self.
|
|
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
|
-
|
|
223
|
-
|
|
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.
|
|
268
|
+
self.__get_unique_session_id(self.__session.id), self
|
|
276
269
|
)
|
|
277
270
|
|
|
278
|
-
if
|
|
279
|
-
self.
|
|
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
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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 "{
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
|
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.
|
|
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"
|
|
318
|
-
complete_page_url=attrs.get("completePageUrl"
|
|
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.
|
|
337
|
+
self.__session = None
|