Add after_load() lifecycle docs, fix Rsx_Tabs hash handling, add automated session cleanup
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -14,8 +14,7 @@ use App\RSpade\Core\Task\Task_Instance;
|
||||
* Cleanup Rules:
|
||||
* - Logged-in sessions (login_user_id set): Delete if older than 365 days
|
||||
* - Anonymous sessions (login_user_id null): Delete if older than 14 days
|
||||
*
|
||||
* Runs daily at 3 AM via scheduled task.
|
||||
* - Automated/headless sessions (Playwright, etc.): Delete if older than 1 hour
|
||||
*/
|
||||
class Session_Cleanup_Service extends Rsx_Service_Abstract
|
||||
{
|
||||
@@ -61,4 +60,37 @@ class Session_Cleanup_Service extends Rsx_Service_Abstract
|
||||
'total_deleted' => $total_deleted,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up automated/headless browser sessions
|
||||
*
|
||||
* Expires sessions from Playwright, Puppeteer, and other headless
|
||||
* browsers after 1 hour of inactivity.
|
||||
*
|
||||
* @param Task_Instance $task Task instance for logging
|
||||
* @param array $params Task parameters
|
||||
* @return array Cleanup statistics
|
||||
*/
|
||||
#[Task('Clean up automated/headless sessions (runs hourly)')]
|
||||
#[Schedule('0 * * * *')]
|
||||
public static function cleanup_automated_sessions(Task_Instance $task, array $params = [])
|
||||
{
|
||||
$cutoff = now()->subHour();
|
||||
$deleted = DB::table('_sessions')
|
||||
->where('last_active', '<', $cutoff)
|
||||
->where(function ($query) {
|
||||
$query->where('user_agent', 'LIKE', '%HeadlessChrome%')
|
||||
->orWhere('user_agent', 'LIKE', '%Playwright%')
|
||||
->orWhere('user_agent', 'LIKE', '%Puppeteer%');
|
||||
})
|
||||
->delete();
|
||||
|
||||
if ($deleted > 0) {
|
||||
$task->info("Deleted {$deleted} automated/headless sessions older than 1 hour");
|
||||
}
|
||||
|
||||
return [
|
||||
'automated_deleted' => $deleted,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +159,23 @@ class User_Agent
|
||||
return 'Desktop';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user agent is an automated/headless browser
|
||||
*
|
||||
* Detects Playwright, Puppeteer, headless Chrome/Firefox, and similar.
|
||||
*
|
||||
* @param string|null $user_agent
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_automated(?string $user_agent): bool
|
||||
{
|
||||
if (empty($user_agent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) preg_match('/HeadlessChrome|Headless|Playwright|Puppeteer/i', $user_agent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a short summary suitable for display
|
||||
* e.g., "Chrome on Windows"
|
||||
|
||||
@@ -502,7 +502,7 @@ COMMENTS IN TEMPLATES
|
||||
COMPONENT LIFECYCLE
|
||||
Five-stage deterministic lifecycle:
|
||||
|
||||
on_create → render → on_render → on_load → on_ready
|
||||
on_create → render → on_render → on_load → after_load → on_ready
|
||||
|
||||
1. on_create() (synchronous, runs BEFORE first render)
|
||||
- Setup default state BEFORE template executes
|
||||
@@ -535,7 +535,14 @@ COMPONENT LIFECYCLE
|
||||
- If this.data changes, triggers automatic re-render
|
||||
- Runtime enforces access restrictions with clear errors
|
||||
|
||||
5. on_ready() (bottom-up)
|
||||
5. after_load() (runs on REAL component, not detached proxy)
|
||||
- this.data is frozen (read-only)
|
||||
- this.$, this.state, this.args are accessible
|
||||
- Primary use case: clone this.data to this.state for widgets
|
||||
with complex in-memory manipulations
|
||||
- Runs after on_load() re-render completes
|
||||
|
||||
6. on_ready() (bottom-up)
|
||||
- All children guaranteed ready
|
||||
- Safe for DOM manipulation
|
||||
- Attach event handlers
|
||||
@@ -554,7 +561,7 @@ COMPONENT LIFECYCLE
|
||||
- on_load() runs in parallel for siblings (DOM unpredictable)
|
||||
- Data changes during load trigger automatic re-render
|
||||
- on_create(), on_render(), on_stop() must be synchronous
|
||||
- on_load() and on_ready() can be async
|
||||
- on_load(), after_load(), and on_ready() can be async
|
||||
|
||||
ON_CREATE() USE CASES
|
||||
The on_create() method runs BEFORE the first render, making it perfect
|
||||
@@ -1295,6 +1302,7 @@ SYNCHRONOUS REQUIREMENTS
|
||||
on_render() YES NO
|
||||
on_stop() YES NO
|
||||
on_load() NO (async allowed) YES
|
||||
after_load() NO (async allowed) YES
|
||||
on_ready() NO (async allowed) YES
|
||||
|
||||
Framework needs predictable execution order for lifecycle coordination.
|
||||
|
||||
@@ -598,8 +598,9 @@ class Toggle_Button extends Component {
|
||||
2. **render** → Template executes (top-down: parent before children)
|
||||
3. **on_render()** → Fires after render, BEFORE children ready (top-down, sync)
|
||||
4. **on_load()** → Fetch data into `this.data` (bottom-up, parallel siblings, async)
|
||||
5. **on_ready()** → All children guaranteed ready (bottom-up, async)
|
||||
6. **on_stop()** → Teardown when destroyed (sync)
|
||||
5. **after_load()** → Runs on real component (not proxy). `this.data` frozen, `this.$`/`this.state`/`this.args` accessible. Clone `this.data` → `this.state` for complex in-memory manipulations.
|
||||
6. **on_ready()** → All children guaranteed ready (bottom-up, async)
|
||||
7. **on_stop()** → Teardown when destroyed (sync)
|
||||
|
||||
If `on_load()` modifies `this.data`, component renders twice (defaults → populated). on_ready() fires once after final render.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user