Save a login once
Sign in by hand in the live view once. The session saves its cookies and local storage into a profile when it ends, and later sessions start from it already signed in.
When to use it: A site needs a person to sign in once, and later sessions should start signed in.
| Uses | profiles.create, sessions with profile: {id, persist: true} and keepAlive, the live view |
|---|---|
| Needs | a plan with profiles; no model |
| Site | your site (SIGNIN_URL, CHECK_URL). The runner uses its stand-in sign-in (password, then a code) and plays the person |
| Output | output/result.json with the profile's id (use it as PROFILE_ID in "My invoices") |
| Languages | Node and Python |
You take part: It prints the live view link and waits: sign in there, then press Enter.
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.
/** * Save a login once: you sign in by hand in the live view once; the session saves its cookies and local storage into * a profile when it ends, and later sessions start from it already signed in. * * SIGNIN_URL=https://… CHECK_URL=https://… npx tsx node/index.ts (BOXLINE_API_KEY; a plan with profiles) * * CHECK_URL is a page that shows you are signed in (your account page). Writes output/result.json with the saved * login's id: pass it as PROFILE_ID to the "my-invoices" example, or to `profile: {id}` in your own sessions. */import { mkdirSync, writeFileSync } from "node:fs";import { join } from "node:path";import { createInterface } from "node:readline/promises";import { Boxline } from "@boxline/sdk";const out = process.env.OUTPUT_DIR ?? "output";const signin = process.env.SIGNIN_URL;if (!signin) throw new Error("Set SIGNIN_URL to your site's sign-in page (and CHECK_URL to a page that shows you are signed in)");const checkUrl = process.env.CHECK_URL ?? signin;const name = process.env.LOGIN_NAME ?? `${new URL(signin).host} (saved by the example)`;const bx = new Boxline();const saved = await bx.profiles.create({ name });console.log(`Browser profile ${saved.id} ("${name}")`);// 1. A session that writes its browser state into the profile when it ends (persist: true). keepAlive: it keeps// running while nobody is connected (you, in the live view, come and go).const first = await bx.sessions.create({ timeout: 900, keepAlive: true, profile: { id: saved.id, persist: true }, userMetadata: { example: "save-a-login" } });console.log(`Session: ${first.id}`);try { await first.goto(signin); console.log(`\nSign in yourself in the live view (the link works like a password: don't share it):\n ${first.liveUrl}\n`); const rl = createInterface({ input: process.stdin, output: process.stdout }); await rl.question("Sign in in the live view, then press Enter when you are signed in: "); rl.close();} finally { await first.stop(); // ending the session saves the cookies and local storage into the profile}// 2. Any later session started from the profile is signed in already (it does not change the profile).const second = await bx.sessions.create({ timeout: 300, profile: { id: saved.id }, userMetadata: { example: "save-a-login" } });console.log(`Session: ${second.id}`);try { const page = await second.goto(checkUrl); const { content } = await second.content("text"); console.log(`A new session opened ${page.url} ("${page.title}"): ${content.replace(/\s+/g, " ").slice(0, 120)}`); mkdirSync(out, { recursive: true }); writeFileSync( join(out, "result.json"), JSON.stringify({ profileId: saved.id, name, signinUrl: signin, checkUrl, sessions: [first.id, second.id], landedOn: page.url, title: page.title, text: content.slice(0, 1000) }, null, 2), ); console.log(`\nBrowser profile: ${saved.id} (start sessions with profile: {id: "${saved.id}"})`);} finally { await second.stop();}"""Save a login once: you sign in by hand in the live view once; the session saves its cookies and local storage intoa profile when it ends, and later sessions start from it already signed in. SIGNIN_URL=https://… CHECK_URL=https://… python python/main.py (BOXLINE_API_KEY; a plan with profiles)CHECK_URL is a page that shows you are signed in (your account page). Writes output/result.json with the savedlogin's id: pass it as PROFILE_ID to the "my-invoices" example, or as `profile=` to your own sessions."""import jsonimport osfrom pathlib import Pathfrom urllib.parse import urlparsefrom boxline import Boxlineout = Path(os.environ.get("OUTPUT_DIR", "output"))signin = os.environ.get("SIGNIN_URL")if not signin: raise SystemExit("Set SIGNIN_URL to your site's sign-in page (and CHECK_URL to a page that shows you are signed in)")check_url = os.environ.get("CHECK_URL", signin)name = os.environ.get("LOGIN_NAME", f"{urlparse(signin).netloc} (saved by the example)")bx = Boxline()saved = bx.profiles.create(name)print(f'Browser profile {saved["id"]} ("{name}")')# 1. A session that writes its browser state into the profile when it ends (persist_profile=True). keep_alive: it# keeps running while nobody is connected (you, in the live view, come and go). Leaving `with` stops it,# which saves the cookies and local storage into the profile.with bx.sessions.create(timeout=900, keep_alive=True, profile=saved["id"], persist_profile=True, user_metadata={"example": "save-a-login"}) as first: print(f"Session: {first.id}", flush=True) first.goto(signin) print(f"\nSign in yourself in the live view (the link works like a password: don't share it):\n {first.live_url}\n") input("Sign in in the live view, then press Enter when you are signed in: ")# 2. Any later session started from the profile is signed in already (it does not change the profile).with bx.sessions.create(timeout=300, profile=saved["id"], user_metadata={"example": "save-a-login"}) as second: print(f"Session: {second.id}", flush=True) page = second.goto(check_url) text = second.content("text")["content"] print(f'A new session opened {page["url"]} ("{page["title"]}"): {" ".join(text.split())[:120]}') out.mkdir(parents=True, exist_ok=True) result = {"profileId": saved["id"], "name": name, "signinUrl": signin, "checkUrl": check_url, "sessions": [first.id, second.id], "landedOn": page["url"], "title": page["title"], "text": text[:1000]} (out / "result.json").write_text(json.dumps(result, indent=2, ensure_ascii=False)) print(f'\nBrowser profile: {saved["id"]} (start sessions with profile="{saved["id"]}")')Inputs
Environment variables the example reads:
SIGNIN_URL: your sign-in page (required).CHECK_URL: a page that shows you are signed in (for examplehttps://example.com/account).LOGIN_NAME: the profile's name (for exampleMy site).
Run it
Get the examples with git clone https://github.com/Boxline-dev/examples.git and open this one: cd examples/save-a-login. 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 tsxnpx tsx node/index.ts# Python 3.9 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keypip install boxline-sdkpython 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.
On the stand-in, the site saw one sign-in (by the person), and a new session from the profile opened the account page signed in.
Source
github.com/Boxline-dev/examples/save-a-login 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.