Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a6fd000
Removed unused code
FeodorFitsner Dec 9, 2022
8658b4d
Added websockets dependency
FeodorFitsner Dec 9, 2022
232141d
SyncConnection added
FeodorFitsner Dec 12, 2022
ff5b04b
Merge branch 'main' into async-experiment
FeodorFitsner Dec 12, 2022
e742f44
Refactored Connection
FeodorFitsner Dec 12, 2022
f081c40
Simplify event handlers
FeodorFitsner Dec 12, 2022
b83640d
macOS client is closed by a real PID
FeodorFitsner Dec 13, 2022
69e7822
flet.py -> sync/async
FeodorFitsner Dec 14, 2022
19494e0
close_flet_view for both sync/async
FeodorFitsner Dec 15, 2022
38050be
a working AsyncConnection
FeodorFitsner Dec 15, 2022
bbc0e80
AsyncConnection complete
FeodorFitsner Dec 15, 2022
ca77418
Async session started
FeodorFitsner Dec 15, 2022
171ecfc
Update control.py
FeodorFitsner Dec 15, 2022
dd7833c
Merge branch 'main' into async-experiment
FeodorFitsner Dec 15, 2022
278d532
some clean() methods deprecated
FeodorFitsner Dec 15, 2022
8546312
mount/unmount refactored
FeodorFitsner Dec 15, 2022
11ab4e5
Async Page methods
FeodorFitsner Dec 15, 2022
6240f84
Run CLI to work with a new flet view method
FeodorFitsner Dec 15, 2022
29248ef
Async control events
FeodorFitsner Dec 16, 2022
6d59682
httpx added as dependency
FeodorFitsner Dec 16, 2022
9418f3d
Update github_oauth_provider.py
FeodorFitsner Dec 18, 2022
11934e9
Merge branch 'main' into async-experiment
FeodorFitsner Jan 4, 2023
473368f
Fix flet.py
FeodorFitsner Jan 4, 2023
8922cec
Update pdm.lock
FeodorFitsner Jan 4, 2023
ce23f58
GitHub OAuth provider to async
FeodorFitsner Jan 4, 2023
281c749
Authorization async
FeodorFitsner Jan 5, 2023
88ee2e2
remove "requests" dependency
FeodorFitsner Jan 5, 2023
f017450
Page class - most of the methods async
FeodorFitsner Jan 5, 2023
0a6c4f3
All methods async
FeodorFitsner Jan 5, 2023
8df3071
sync/async asserts
FeodorFitsner Jan 5, 2023
102481d
focus_async
FeodorFitsner Jan 5, 2023
870a76a
PubSub async
FeodorFitsner Jan 6, 2023
3bd5a6e
ClientStorage async
FeodorFitsner Jan 6, 2023
8514aed
FilePicker async
FeodorFitsner Jan 6, 2023
8bf419c
Fix OAuth authorization flow
FeodorFitsner Jan 6, 2023
0f0e645
Add cryptography to dev dependencies
FeodorFitsner Jan 6, 2023
6ab424c
clean() method is back!
FeodorFitsner Jan 6, 2023
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
4 changes: 4 additions & 0 deletions client/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ void main([List<String>? args]) async {
throw Exception('Page URL must be provided as a first argument.');
}
pageUrl = args[0];
if (args.length > 1) {
var pidFile = await File(args[1]).create();
await pidFile.writeAsString("$pid");
}
}

debugPrint("Page URL: $pageUrl");
Expand Down
7 changes: 3 additions & 4 deletions client/macos/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
/* Begin PBXFileReference section */
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* flet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = flet.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10ED2044A3C60003C045 /* Flet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Flet.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
Expand Down Expand Up @@ -112,7 +112,7 @@
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* flet.app */,
33CC10ED2044A3C60003C045 /* Flet.app */,
);
name = Products;
sourceTree = "<group>";
Expand Down Expand Up @@ -159,7 +159,6 @@
EDAD244E5F1A9F15957004F8 /* Pods-Runner.release.xcconfig */,
3BFA430DEADD5855D5604A50 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
Expand Down Expand Up @@ -193,7 +192,7 @@
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* flet.app */;
productReference = 33CC10ED2044A3C60003C045 /* Flet.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
Expand Down
2 changes: 1 addition & 1 deletion client/macos/Runner/DebugProfile.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<false/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
Expand Down
2 changes: 1 addition & 1 deletion client/macos/Runner/Release.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<false/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
Expand Down
2 changes: 1 addition & 1 deletion package/lib/src/controls/audio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ class _AudioControlState extends State<AudioControl> {
sendResult(Object? result, String? error) {
ws.pageEventFromWeb(
eventTarget: widget.control.id,
eventName: "result",
eventName: "method_result",
eventData: json.encode({
"i": i,
"r": result != null ? json.encode(result) : null,
Expand Down
182 changes: 182 additions & 0 deletions sdk/python/flet/async_connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import asyncio
import json
import logging
from typing import List, Optional
import uuid
from flet import constants
from flet.connection import Connection
import websockets.client as ws_client
from websockets.client import WebSocketClientProtocol
from flet.protocol import *
from asyncio.queues import Queue


class AsyncConnection(Connection):
__CONNECT_TIMEOUT = 0.2
__CONNECT_ATTEMPTS = 50

def __init__(
self,
server_address: str,
page_name: str,
auth_token: Optional[str],
on_event=None,
on_session_created=None,
):
super().__init__()
self.__send_queue = Queue(1)
self.page_name = page_name
self.__server_address = server_address
self.__is_reconnecting = False
self.__host_client_id: Optional[str] = None
self.__auth_token = auth_token
self.__ws_callbacks = {}
self.__on_event = on_event
self.__on_session_created = on_session_created

async def connect(self):
ws_url = self._get_ws_url(self.__server_address)
logging.debug(f"Connecting via WebSockets to {ws_url}...")

attempt = self.__CONNECT_ATTEMPTS
while True:
try:
self.__ws: WebSocketClientProtocol = await ws_client.connect(ws_url)
break
except Exception as e:
logging.debug(f"Error connecting to Flet server: {e}")
if attempt == 0 and not self.__is_reconnecting:
raise Exception(
f"Failed to connect Flet server in {self.__CONNECT_ATTEMPTS} attempts."
)
attempt -= 1
await asyncio.sleep(self.__CONNECT_TIMEOUT)
logging.debug(f"Connected to Flet server {self.__server_address}")
self.__is_reconnecting = True

# start send/receive loops
asyncio.get_event_loop().create_task(self.__start_loops())

await self.__register_host_client()

async def __register_host_client(self):
payload = RegisterHostClientRequestPayload(
hostClientID=self.__host_client_id,
pageName=self.page_name,
isApp=True,
update=False,
authToken=self.__auth_token,
permissions=None,
)
response = await self._send_message_with_result(
Actions.REGISTER_HOST_CLIENT, payload
)
register_result = RegisterHostClientResponsePayload(**response)
self.__host_client_id = register_result.hostClientID
self.page_name = register_result.pageName
self.page_url = self.__server_address.rstrip("/")
if self.page_name != constants.INDEX_PAGE:
self.page_url += f"/{self.page_name}"

async def __start_loops(self):
self.__receive_loop_task = asyncio.create_task(self.__receive_looop())
self.__send_loop_task = asyncio.create_task(self.__send_loop())
done, pending = await asyncio.wait(
[self.__receive_loop_task, self.__send_loop_task],
return_when=asyncio.FIRST_COMPLETED,
)
failed = False
for task in done:
name = task.get_name()
exception = task.exception()
if isinstance(exception, Exception):
logging.error(f"{name} threw {exception}")
failed = True
for task in pending:
task.cancel()

# re-connect if one of tasks failed
if failed:
logging.debug(f"Re-connecting to Flet server in 1 second")
await asyncio.sleep(self.__CONNECT_TIMEOUT)
await self.connect()

async def __on_ws_message(self, data):
logging.debug(f"_on_message: {data}")
msg_dict = json.loads(data)
msg = Message(**msg_dict)
if msg.id:
# callback
evt = self.__ws_callbacks[msg.id][0]
self.__ws_callbacks[msg.id] = (None, msg.payload)
evt.set()
elif msg.action == Actions.PAGE_EVENT_TO_HOST:
if self.__on_event is not None:
asyncio.create_task(self.__on_event(PageEventPayload(**msg.payload)))
elif msg.action == Actions.SESSION_CREATED:
if self.__on_session_created is not None:
asyncio.create_task(
self.__on_session_created(PageSessionCreatedPayload(**msg.payload))
)
else:
# it's something else
print(msg.payload)

async def __receive_looop(self):
async for message in self.__ws:
await self.__on_ws_message(message)

async def __send_loop(self):
while True:
message = await self.__send_queue.get()
try:
await self.__ws.send(message)
except:
# re-enqueue the message to repeat it when re-connected
self.__send_queue.put_nowait(message)
raise

async def send_command_async(self, session_id: str, command: Command):
assert self.page_name is not None
payload = PageCommandRequestPayload(self.page_name, session_id, command)
response = await self._send_message_with_result(
Actions.PAGE_COMMAND_FROM_HOST, payload
)
result = PageCommandResponsePayload(**response)
if result.error:
raise Exception(result.error)
return result

async def send_commands_async(self, session_id: str, commands: List[Command]):
assert self.page_name is not None
payload = PageCommandsBatchRequestPayload(self.page_name, session_id, commands)
response = await self._send_message_with_result(
Actions.PAGE_COMMANDS_BATCH_FROM_HOST, payload
)
result = PageCommandsBatchResponsePayload(**response)
if result.error:
raise Exception(result.error)
return result

async def _send_message_with_result(self, action_name, payload):
msg_id = uuid.uuid4().hex
msg = Message(msg_id, action_name, payload)
j = json.dumps(msg, cls=CommandEncoder, separators=(",", ":"))
logging.debug(f"_send_message_with_result: {j}")
evt = asyncio.Event()
self.__ws_callbacks[msg_id] = (evt, None)
await self.__send_queue.put(j)
await evt.wait()
return self.__ws_callbacks.pop(msg_id)[1]

async def close(self):
logging.debug("Closing WebSockets connection...")
if self.__receive_loop_task:
self.__receive_loop_task.cancel()
if self.__send_loop_task:
self.__send_loop_task.cancel()
if self.__ws:
try:
await self.__ws.close()
except:
pass # do nothing
Loading