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
60 changes: 39 additions & 21 deletions pkg/templates/python/cua/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,28 +111,46 @@ async def start(self) -> SessionInfo:
return self.info

async def stop(self) -> SessionInfo:
info = self.info
session_id = self._session_id
if not session_id:
# Already stopped — return a snapshot of the (cleared) state without
# touching `self.info`, whose `session_id` property raises on None.
return SessionInfo(
session_id="",
live_view_url=self._live_view_url or "",
replay_id=self._replay_id,
replay_view_url=self._replay_view_url,
viewport_width=self.opts.viewport_width,
viewport_height=self.opts.viewport_height,
)

info = SessionInfo(
session_id=session_id,
live_view_url=self._live_view_url or "",
replay_id=self._replay_id,
replay_view_url=self._replay_view_url,
viewport_width=self.opts.viewport_width,
viewport_height=self.opts.viewport_height,
)

if self._session_id:
session_id = self._session_id
try:
if self.opts.record_replay and self._replay_id:
if self.opts.replay_grace_period > 0:
await asyncio.sleep(self.opts.replay_grace_period)
await self._stop_replay()
info.replay_view_url = self._replay_view_url
finally:
# Reset state up front so that if browser deletion or a thrown replay
# error propagates, a follow-up stop() call from the caller's error path
# is a no-op instead of attempting to delete the same session twice.
self._session_id = None
self._live_view_url = None
self._replay_id = None
self._replay_view_url = None
print(f"Destroying browser session: {session_id}")
await asyncio.to_thread(
self.kernel.browsers.delete_by_id, session_id,
)
try:
if self.opts.record_replay and self._replay_id:
if self.opts.replay_grace_period > 0:
await asyncio.sleep(self.opts.replay_grace_period)
await self._stop_replay()
info.replay_view_url = self._replay_view_url
finally:
# Reset state up front so that if browser deletion or a thrown replay
# error propagates, a follow-up stop() call from the caller's error path
# is a no-op instead of attempting to delete the same session twice.
self._session_id = None
self._live_view_url = None
self._replay_id = None
self._replay_view_url = None
print(f"Destroying browser session: {session_id}")
await asyncio.to_thread(
self.kernel.browsers.delete_by_id, session_id,
)

return info

Expand Down
58 changes: 38 additions & 20 deletions pkg/templates/typescript/cua/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,29 +103,47 @@ export class KernelBrowserSession {
}

async stop(): Promise<SessionInfo> {
const info = this.info;
const sessionId = this._sessionId;
if (!sessionId) {
// Already stopped — return a snapshot of the (cleared) state without
// touching `this.info`, whose `sessionId` getter throws on null.
return {
sessionId: '',
liveViewUrl: this._liveViewUrl ?? '',
replayId: this._replayId ?? undefined,
replayViewUrl: this._replayViewUrl ?? undefined,
viewportWidth: this.opts.viewportWidth,
viewportHeight: this.opts.viewportHeight,
};
}

if (this._sessionId) {
const sessionId = this._sessionId;
try {
if (this.opts.recordReplay && this._replayId) {
if (this.opts.replayGracePeriod > 0) {
await sleep(this.opts.replayGracePeriod * 1000);
}
await this.stopReplay();
info.replayViewUrl = this._replayViewUrl || undefined;
const info: SessionInfo = {
sessionId,
liveViewUrl: this._liveViewUrl ?? '',
replayId: this._replayId ?? undefined,
replayViewUrl: this._replayViewUrl ?? undefined,
viewportWidth: this.opts.viewportWidth,
viewportHeight: this.opts.viewportHeight,
};

try {
if (this.opts.recordReplay && this._replayId) {
if (this.opts.replayGracePeriod > 0) {
await sleep(this.opts.replayGracePeriod * 1000);
}
} finally {
// Reset state up front so that if browser deletion or a thrown replay error
// propagates, a follow-up stop() call from the caller's error path is a no-op
// instead of attempting to delete the same session twice.
this._sessionId = null;
this._liveViewUrl = null;
this._replayId = null;
this._replayViewUrl = null;
console.log(`Destroying browser session: ${sessionId}`);
await this.kernel.browsers.deleteByID(sessionId);
await this.stopReplay();
info.replayViewUrl = this._replayViewUrl || undefined;
}
} finally {
// Reset state up front so that if browser deletion or a thrown replay error
// propagates, a follow-up stop() call from the caller's error path is a no-op
// instead of attempting to delete the same session twice.
this._sessionId = null;
this._liveViewUrl = null;
this._replayId = null;
this._replayViewUrl = null;
console.log(`Destroying browser session: ${sessionId}`);
await this.kernel.browsers.deleteByID(sessionId);
}

return info;
Expand Down