Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/flet/lib/src/controls/base_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,17 @@ class ConstrainedControl extends StatelessWidget {

@override
Widget build(BuildContext context) {
Widget w = BaseControl(control: control, child: child);

Widget w = _opacity(context, child, control);
w = _tooltip(context, w, control);
w = _directionality(w, control);
w = _sizedControl(w, control);
w = _rotatedControl(context, w, control);
w = _scaledControl(context, w, control);
w = _offsetControl(context, w, control);
w = _aspectRatio(w, control);
w = _positionedControl(context, w, control);
w = _badge(w, Theme.of(context), control);

return w;
return _expandable(w, control);
}
}

Expand Down
36 changes: 23 additions & 13 deletions packages/flet/lib/src/controls/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';

import '../extensions/control.dart';
import '../flet_backend.dart';
import '../models/control.dart';
import '../models/keyboard_event.dart';
Expand Down Expand Up @@ -104,20 +105,29 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {

// page services
var pageServicesControl = widget.control.child("_page_services");
if (_pageServices == null && pageServicesControl != null) {
_pageServices = ServiceRegistry(
control: pageServicesControl,
propertyName: "services",
backend: FletBackend.of(context));
if (pageServicesControl != null) {
if (_pageServices == null ||
(_pageServices != null &&
_pageServices?.control.internals?["uid"] !=
pageServicesControl.internals?["uid"])) {
_pageServices = ServiceRegistry(
control: pageServicesControl,
propertyName: "services",
backend: FletBackend.of(context));
}
}

// user services
var userServicesControl = widget.control.child("_user_services");
if (_userServices == null && userServicesControl != null) {
_userServices = ServiceRegistry(
control: userServicesControl,
propertyName: "services",
backend: FletBackend.of(context));
if (userServicesControl != null) {
if (_userServices == null ||
_userServices?.control.internals?["uid"] !=
userServicesControl.internals?["uid"]) {
_userServices = ServiceRegistry(
control: userServicesControl,
propertyName: "services",
backend: FletBackend.of(context));
}
}

_attachKeyboardListenerIfNeeded();
Expand Down Expand Up @@ -190,7 +200,7 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
}

void _handleAppLifecycleTransition(String state) {
widget.control.triggerEvent("app_lifecycle_state_change", state);
widget.control.triggerEvent("app_lifecycle_state_change", {"state": state});
}

bool _handleKeyDown(KeyEvent e) {
Expand Down Expand Up @@ -247,9 +257,9 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
var assetSrc = backend.getAssetSource(fontUrl);
try {
if (assetSrc.isFile) {
await UserFonts.loadFontFromFile(fontFamily, fontUrl);
await UserFonts.loadFontFromFile(fontFamily, assetSrc.path);
} else {
await UserFonts.loadFontFromUrl(fontFamily, fontUrl);
await UserFonts.loadFontFromUrl(fontFamily, assetSrc.path);
}
} catch (e) {
debugPrint("Error loading font $fontFamily: $e");
Expand Down
4 changes: 2 additions & 2 deletions packages/flet/lib/src/extensions/control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ extension WidgetFromControl on Control {
extension InternalConfiguration on Control {
/// The internal configuration of this control.
/// Represented on Python side by `BaseControl._internals` property.
Map<String, dynamic>? get internals {
return get("_internals") as Map<String, dynamic>?;
Map<dynamic, dynamic>? get internals {
return get("_internals") as Map<dynamic, dynamic>?;
}
}
2 changes: 1 addition & 1 deletion packages/flet/lib/src/flet_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ class FletBackend extends ChangeNotifier {
pageSize = newSize;
var newProps = {"width": newSize.width, "height": newSize.height};
updateControl(page.id, newProps);
triggerControlEvent(page, "resized", newProps);
triggerControlEvent(page, "resize", newProps);

if (isDesktopPlatform()) {
var windowState = await getWindowState();
Expand Down
22 changes: 6 additions & 16 deletions packages/flet/lib/src/models/control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ class Control extends ChangeNotifier {

if (dst[key] is Map && entry.value is Map) {
_mergeMaps(parent, dst[key], entry.value, changes, fullKey);
} else if (dst[key] is Control && entry.value is Map) {
} else if (dst[key] is Control &&
entry.value is Map &&
(dst[key] as Control).id == entry.value["_i"]) {
_mergeMaps(parent, dst[key].properties, entry.value, changes, fullKey);
} else if (dst[key] != entry.value) {
dst[key] = _transformIfControl(entry.value, parent, backend);
Expand Down Expand Up @@ -391,8 +393,7 @@ class Control extends ChangeNotifier {
_invokeMethodListeners.add(listener);

// If someone was waiting for a listener to be added, complete the future
if (_listenerAddedCompleter != null &&
!_listenerAddedCompleter!.isCompleted) {
if (_listenerAddedCompleter?.isCompleted == false) {
_listenerAddedCompleter!.complete();
_listenerAddedCompleter = null;
}
Expand All @@ -408,19 +409,8 @@ class Control extends ChangeNotifier {

// If no listeners, wait until one is added or timeout occurs
if (_invokeMethodListeners.isEmpty) {
_listenerAddedCompleter = Completer<void>();

try {
await Future.any([
_listenerAddedCompleter!.future,
Future.delayed(
timeout,
() => throw TimeoutException(
"No invoke method listeners registered within $timeout")),
]);
} catch (e) {
rethrow;
}
_listenerAddedCompleter ??= Completer<void>();
await _listenerAddedCompleter!.future;
}

if (_invokeMethodListeners.isEmpty) {
Expand Down
2 changes: 1 addition & 1 deletion sdk/python/packages/flet-web/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ requires-python = ">=3.10"
dependencies = [
"flet",
"fastapi >=0.115.12",
"uvicorn[standard] >=0.34.2"
"uvicorn[standard] >=0.35.0"
]

[project.urls]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async def main(page: ft.Page):
icon=ft.icons.ADD, on_click=add_click
)
await page.add_async(
ft.Container(counter, alignment=ft.alignment.center, expand=True)
ft.Container(counter, alignment=ft.Alignment.CENTER, expand=True)
)

app = flet_fastapi.app(main)
Expand Down
44 changes: 34 additions & 10 deletions sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,9 @@ async def handle(self, websocket: WebSocket):
"""
self.__websocket = websocket

self.client_ip = (
self.__websocket.client.host if self.__websocket.client else ""
).split(":")[0]
self.client_ip = self.__websocket.client.host if self.__websocket.client else ""
self.client_user_agent = self.__websocket.headers.get("user-agent", "")
self.__oauth_state_id = self.__websocket.cookies.get("flet_oauth_state")

self.pubsubhub = app_manager.get_pubsubhub(self.__main, loop=self.loop)
self.page_url = str(websocket.url).rsplit("/", 1)[0]
Expand Down Expand Up @@ -165,16 +164,20 @@ async def __on_session_created(self):
f"{self.__session.id}"
)
except BrokenPipeError:
logger.info(f"Session handler terminated: {self.__session.id}")
logger.info(
"Session handler terminated: "
f"{self.__session.id if self.__session else ''}"
)
except Exception as e:
print(
f"Unhandled error processing page session {self.__session.id}:",
"Unhandled error processing page session: "
f"{self.__session.id if self.__session else ''}",
traceback.format_exc(),
)
assert self.__session
self.__session.error(
f"There was an error while processing your request: {e}"
)
if self.__session:
self.__session.error(
f"There was an error while processing your request: {e}"
)

async def __send_loop(self):
assert self.__websocket
Expand Down Expand Up @@ -224,6 +227,14 @@ async def __on_message(self, data: Any):
self.__get_unique_session_id(req.session_id)
)

oauth_state = None
if self.__oauth_state_id:
oauth_state = app_manager.retrieve_state(self.__oauth_state_id)
if oauth_state:
self.__session = await app_manager.get_session(
oauth_state.session_id
)

# re-create session
if self.__session is None:
new_session = True
Expand Down Expand Up @@ -274,6 +285,16 @@ async def __on_message(self, data: Any):
):
self.__session.page.go(self.__session.page.route)

if oauth_state:
await self.__session.page._authorize_callback_async(
{
"state": self.__oauth_state_id,
"code": oauth_state.code,
"error": oauth_state.error,
"error_description": oauth_state.error_description,
}
)

elif action == ClientAction.CONTROL_EVENT:
req = ControlEventBody(**body)
task = asyncio.create_task(
Expand Down Expand Up @@ -329,7 +350,10 @@ def oauth_authorize(self, attrs: dict[str, Any]):
app_manager.store_state(state_id, state)

def __get_unique_session_id(self, session_id: str):
client_hash = sha1(f"{self.client_ip}{self.client_user_agent}")
ip = self.client_ip
if ip in ["127.0.0.1", "::1"]:
ip = ""
client_hash = sha1(f"{ip}{self.client_user_agent}")
return f"{self.page_name}_{session_id}_{client_hash}"

def dispose(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ async def reconnect_session(self, session_id: str, conn: Connection):
if session_id in self.__sessions:
session = self.__sessions[session_id]
await session.connect(conn)
else:
raise Exception(f"Session has expired or not found: {session_id}")

async def disconnect_session(self, session_id: str, session_timeout_seconds: int):
logger.info(f"Session disconnected: {session_id}")
Expand Down
33 changes: 23 additions & 10 deletions sdk/python/packages/flet-web/src/flet_web/fastapi/flet_oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,35 @@ async def handle(self, request: Request):
if not session:
raise HTTPException(status_code=500, detail="Session not found")

await session.page._authorize_callback_async(
{
"state": state_id,
"code": request.query_params.get("code"),
"error": request.query_params.get("error"),
"error_description": request.query_params.get("error_description"),
}
)
state.code = request.query_params.get("code")
state.error = request.query_params.get("error")
state.error_description = request.query_params.get("error_description")

if state.complete_page_url:
return RedirectResponse(state.complete_page_url)
app_manager.store_state(state_id, state)
response = RedirectResponse(state.complete_page_url)
response.set_cookie(
"flet_oauth_state",
state_id,
max_age=300,
httponly=True,
secure=False,
samesite="strict",
)
return response
else:
await session.page._authorize_callback_async(
{
"state": state_id,
"code": state.code,
"error": state.error,
"error_description": state.error_description,
}
)
html_content = (
state.complete_page_html
if state.complete_page_html
else f"""
else """
<!DOCTYPE html>
<html>
<head>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
class OAuthState:
session_id: str
expires_at: datetime
complete_page_url: Optional[str] = dataclasses.field(default=None)
complete_page_html: Optional[str] = dataclasses.field(default=None)
complete_page_url: Optional[str] = None
complete_page_html: Optional[str] = None
code: Optional[str] = None
error: Optional[str] = None
error_description: Optional[str] = None
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ def startup():
no_cdn=no_cdn,
),
)
config = uvicorn.Config(app, host=host, port=port, log_level=log_level)
config = uvicorn.Config(
app, host=host, port=port, log_level=log_level, ws="websockets-sansio"
)
server = uvicorn.Server(config)

if blocking:
Expand Down
5 changes: 0 additions & 5 deletions sdk/python/packages/flet/src/flet/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import os
import signal
import traceback
import warnings
from collections.abc import Awaitable
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
Expand All @@ -30,10 +29,6 @@
ensure_flet_web_package_installed,
)

if os.getenv("FLET_FORCE_WEB_SERVER"):
warnings.filterwarnings("ignore", module=r"websockets")
warnings.filterwarnings("ignore", module=r"uvicorn")

logger = logging.getLogger("flet")

if TYPE_CHECKING:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,10 @@ async def __refresh_token_async(self):
self.__token = self.__convert_token(t)

async def __get_user_async(self):
assert self.token is not None
assert self.__token is not None
assert self.provider.user_endpoint is not None
headers = self.__get_default_headers()
headers["Authorization"] = f"Bearer {self.token.access_token}"
headers["Authorization"] = f"Bearer {self.__token.access_token}"
user_req = httpx.Request("GET", self.provider.user_endpoint, headers=headers)
async with httpx.AsyncClient(follow_redirects=True) as client:
user_resp = await client.send(user_req)
Expand Down
Loading