Update a session
One call changes a running session: session.update({ timeout: 600, rotateUrls: true }) gives it a longer life and fresh signed URLs, and the session keeps running. This example creates a session for 5 minutes, updates it to 10, and shows what changed: expiresAt moved by the extra five minutes, the old connectUrl and liveUrl stopped working, and the new ones work.
| Uses | sessions.create, session.update({ timeout, rotateUrls }), Playwright over the connect URL, the live view URL |
|---|---|
| Needs | a plan whose longest session is 10 minutes or more (Free allows 15) |
| Site | none |
| Output | output/result.json (expiresAt and the timeout before and after, what the old and the new URLs do) |
| Languages | Node and Python |
timeout is the session's whole length, counted from its start, so changing 300 to 600 moves expiresAt by 300 seconds (at most the plan's longest session; at least 60 s from now). rotateUrls: true makes every earlier connectUrl, liveUrl and terminalUrl stop working and closes connections made with them; the answer carries the new ones. Another API server may take a few seconds to forget an old URL, so the example tries for up to 20 seconds. Treat these URLs like passwords: rotate them when one leaked, or when a link you shared should stop working.
The session is created with keepAlive: true: the example connects Playwright and disconnects again, and a browser-only session without it stops 5 seconds after its last client leaves.
Settings that are fixed when a session is created (browser, shell, viewport, profile, extensions, env, ...) are refused with NotUpdatableError. rotateProxy: true gets a new proxy IP, captcha, blockAds and proxy apply at once.
The code
The example as it is in its folder. Both versions do the same job and write the same output/, so one result check covers both.
/** * Update a session: a running session gets a longer life and fresh URLs in one call, `session.update({ timeout, rotateUrls })`. * The example shows that `expiresAt` moved by the extra time, and that the old connect and live URLs stopped working while * the new ones work. * * npx tsx node/index.ts (BOXLINE_API_KEY; a plan whose longest session is 10 minutes or more) * * Writes output/result.json. */import { mkdirSync, writeFileSync } from "node:fs";import { join } from "node:path";import { Boxline } from "@boxline/sdk";import { chromium } from "playwright-core";const out = process.env.OUTPUT_DIR ?? "output";const bx = new Boxline();const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));/** Connects Playwright to a connect URL; true when it worked. */async function connects(url: string, timeout: number): Promise<{ ok: true; pages: number } | { ok: false; error: string }> { try { const browser = await chromium.connectOverCDP(url, { timeout }); const pages = browser.contexts()[0]?.pages().length ?? 0; await browser.close(); return { ok: true, pages }; } catch (e) { return { ok: false, error: (e instanceof Error ? e.message : String(e)).split("\n")[0]!.slice(0, 160) }; }}/** Tries until an old URL is refused: another API server may take a few seconds to forget it (at most 20 s). */async function until<T>(attempt: () => Promise<T>, done: (value: T) => boolean) { const deadline = Date.now() + 20_000; for (let attempts = 1; ; attempts++) { const value = await attempt(); if (done(value) || Date.now() > deadline) return { value, attempts }; await sleep(2000); }}// keepAlive: the example connects Playwright and disconnects again; without it a browser-only session stops 5 s after its last client leaves.const session = await bx.sessions.create({ timeout: 300, keepAlive: true, userMetadata: { example: "update-a-session" } });console.log(`Session: ${session.id}`);try { const before = { expiresAt: session.data.expiresAt, timeout: session.data.timeout, connectUrl: session.connectUrl!, liveUrl: session.liveUrl! }; const beforeConnect = await connects(before.connectUrl, 20_000); console.log(`Before: the session lasts ${before.timeout} s, until ${before.expiresAt}; its connect URL ${beforeConnect.ok ? "works" : "does not work"}`); // One call: a longer life and fresh URLs. The session keeps running; nothing else about it changes. await session.update({ timeout: 600, rotateUrls: true }); const added = (Date.parse(session.data.expiresAt) - Date.parse(before.expiresAt)) / 1000; console.log(`After: the session lasts ${session.data.timeout} s, until ${session.data.expiresAt} (${added} s later)`); // The old URLs stop working; the new ones work. const oldLive = await until(() => fetch(before.liveUrl).then((r) => r.status), (status) => status === 401); const newLive = (await fetch(session.liveUrl!)).status; const oldConnect = await until(() => connects(before.connectUrl, 8000), (r) => !r.ok); const newConnect = await connects(session.connectUrl!, 20_000); console.log(`Old live URL: ${oldLive.value} (after ${oldLive.attempts} tries); new live URL: ${newLive}`); console.log(`Old connect URL: ${oldConnect.value.ok ? "still works" : `refused (${oldConnect.value.error})`}; new connect URL: ${newConnect.ok ? "works" : "does not work"}`); mkdirSync(out, { recursive: true }); writeFileSync( join(out, "result.json"), JSON.stringify( { before: { timeout: before.timeout, expiresAt: before.expiresAt, connectWorked: beforeConnect.ok }, after: { timeout: session.data.timeout, expiresAt: session.data.expiresAt }, addedSeconds: added, urlsChanged: { connectUrl: session.connectUrl !== before.connectUrl, liveUrl: session.liveUrl !== before.liveUrl }, oldLive: { status: oldLive.value, tries: oldLive.attempts }, newLive: { status: newLive }, oldConnect: { refused: !oldConnect.value.ok, error: oldConnect.value.ok ? null : oldConnect.value.error, tries: oldConnect.attempts }, newConnect: { connected: newConnect.ok }, }, null, 2, ), );} finally { await session.stop().catch(() => undefined);}"""Update a session: a running session gets a longer life and fresh URLs in one call, ``session.update(timeout=..., rotate_urls=True)``.The example shows that ``expiresAt`` moved by the extra time, and that the old connect and live URLs stopped working whilethe new ones work. python python/main.py (BOXLINE_API_KEY; a plan whose longest session is 10 minutes or more)Writes output/result.json."""import jsonimport osimport timeimport urllib.errorimport urllib.requestfrom datetime import datetimefrom pathlib import Pathfrom typing import Callable, Tuplefrom playwright.sync_api import sync_playwrightfrom boxline import Boxlineout = Path(os.environ.get("OUTPUT_DIR", "output"))bx = Boxline()def seconds(iso: str) -> float: return datetime.fromisoformat(iso.replace("Z", "+00:00")).timestamp()def status_of(url: str) -> int: try: with urllib.request.urlopen(url, timeout=20) as r: return r.status except urllib.error.HTTPError as e: return e.codedef connects(p, url: str, timeout: int) -> dict: """Connects Playwright to a connect URL: ``{"ok": True, "pages": n}`` or ``{"ok": False, "error": ...}``.""" try: browser = p.chromium.connect_over_cdp(url, timeout=timeout) pages = len(browser.contexts[0].pages) if browser.contexts else 0 browser.close() return {"ok": True, "pages": pages} except Exception as e: # Playwright's own error type return {"ok": False, "error": str(e).split("\n")[0][:160]}def until(attempt: Callable[[], object], done: Callable[[object], bool]) -> Tuple[object, int]: """Tries until an old URL is refused: another API server may take a few seconds to forget it (at most 20 s).""" deadline = time.monotonic() + 20 attempts = 0 while True: attempts += 1 value = attempt() if done(value) or time.monotonic() > deadline: return value, attempts time.sleep(2)# keep_alive: the example connects Playwright and disconnects again; without it a browser-only session stops 5 s after its last client leaves.with bx.sessions.create(timeout=300, keep_alive=True, user_metadata={"example": "update-a-session"}) as session, sync_playwright() as p: print(f"Session: {session.id}", flush=True) before = {"expiresAt": session.data["expiresAt"], "timeout": session.data["timeout"], "connectUrl": session.connect_url, "liveUrl": session.live_url} before_connect = connects(p, before["connectUrl"], 20_000) print(f"Before: the session lasts {before['timeout']} s, until {before['expiresAt']}; its connect URL {'works' if before_connect['ok'] else 'does not work'}", flush=True) # One call: a longer life and fresh URLs. The session keeps running; nothing else about it changes. session.update(timeout=600, rotate_urls=True) added = seconds(session.data["expiresAt"]) - seconds(before["expiresAt"]) print(f"After: the session lasts {session.data['timeout']} s, until {session.data['expiresAt']} ({added:g} s later)", flush=True) # The old URLs stop working; the new ones work. old_live, old_live_tries = until(lambda: status_of(before["liveUrl"]), lambda status: status == 401) new_live = status_of(session.live_url) old_connect, old_connect_tries = until(lambda: connects(p, before["connectUrl"], 8000), lambda r: not r["ok"]) new_connect = connects(p, session.connect_url, 20_000) print(f"Old live URL: {old_live} (after {old_live_tries} tries); new live URL: {new_live}", flush=True) print(f"Old connect URL: {'still works' if old_connect['ok'] else 'refused (' + old_connect['error'] + ')'}; new connect URL: {'works' if new_connect['ok'] else 'does not work'}", flush=True) out.mkdir(parents=True, exist_ok=True) result = { "before": {"timeout": before["timeout"], "expiresAt": before["expiresAt"], "connectWorked": before_connect["ok"]}, "after": {"timeout": session.data["timeout"], "expiresAt": session.data["expiresAt"]}, "addedSeconds": added, "urlsChanged": {"connectUrl": session.connect_url != before["connectUrl"], "liveUrl": session.live_url != before["liveUrl"]}, "oldLive": {"status": old_live, "tries": old_live_tries}, "newLive": {"status": new_live}, "oldConnect": {"refused": not old_connect["ok"], "error": None if old_connect["ok"] else old_connect["error"], "tries": old_connect_tries}, "newConnect": {"connected": new_connect["ok"]}, } (out / "result.json").write_text(json.dumps(result, indent=2, ensure_ascii=False))Run it
Get the examples with git clone https://github.com/Boxline-dev/examples.git and open this one: cd examples/update-a-session. Put your API key in BOXLINE_API_KEY (and BOXLINE_API_URL for another API), then run either version from the example’s folder. Both write to output/ and stop their sessions when they finish.
# Node 18 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keynpm install @boxline/sdk playwright-core tsxnpx tsx node/index.ts# Python 3.9 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keypip install boxline-sdk playwrightpython python/main.pyThen check the result, from the same folder: npx tsx check.ts output. It exits with 0 only when the result is right.
What its check proves
check.ts reads what the example wrote and passes only when the result is right, not merely when the example ran.
npx tsx check.ts output exits with 0 only when the timeout went from 300 to 600 and expiresAt moved by 300 seconds (within 5), the connect URL worked before the update, both URLs changed, the old connect URL was refused, the old live URL answered 401, and the new live URL answered 200 and the new connect URL connected.
Source
github.com/Boxline-dev/examples/update-a-session on GitHub, with its README.md, check.ts and .env.example. The examples are open source. See all examples.
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.