gologin-agent-browser-cli 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2 -2
- package/dist/commands/close.js +10 -0
- package/dist/commands/sessions.js +19 -1
- package/dist/daemon/server.js +9 -0
- package/dist/daemon/sessionManager.d.ts +10 -2
- package/dist/daemon/sessionManager.js +93 -24
- package/dist/lib/types.d.ts +12 -0
- package/dist/lib/utils.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -80,8 +80,8 @@ const commandUsage = {
|
|
|
80
80
|
upload: "upload <target> <file...> [--session <sessionId>]",
|
|
81
81
|
pdf: "pdf <path> [--session <sessionId>]",
|
|
82
82
|
screenshot: "screenshot <path> [--annotate] [--press-escape] [--session <sessionId>]",
|
|
83
|
-
close: "close [--session <sessionId>] (aliases: quit, exit)",
|
|
84
|
-
sessions: "sessions",
|
|
83
|
+
close: "close [--session <sessionId>] [--all] (aliases: quit, exit)",
|
|
84
|
+
sessions: "sessions [--prune] [--older-than-ms <ms>]",
|
|
85
85
|
current: "current"
|
|
86
86
|
};
|
|
87
87
|
function printUsage() {
|
package/dist/commands/close.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runCloseCommand = runCloseCommand;
|
|
4
|
+
const errors_1 = require("../lib/errors");
|
|
4
5
|
const utils_1 = require("../lib/utils");
|
|
5
6
|
async function runCloseCommand(context, argv) {
|
|
6
7
|
const parsed = (0, utils_1.parseArgs)(argv);
|
|
8
|
+
const closeAll = (0, utils_1.getFlagBoolean)(parsed, "all");
|
|
7
9
|
const sessionId = (0, utils_1.getFlagString)(parsed, "session");
|
|
10
|
+
if (closeAll) {
|
|
11
|
+
if (sessionId) {
|
|
12
|
+
throw new errors_1.AppError("BAD_REQUEST", "--all cannot be combined with --session", 400);
|
|
13
|
+
}
|
|
14
|
+
const response = await context.client.request("POST", "/sessions/close-all");
|
|
15
|
+
context.stdout.write(`closed ${response.closed} session(s)\n`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
8
18
|
const resolvedSessionId = sessionId ??
|
|
9
19
|
(await context.client.request("GET", "/sessions/current")).sessionId;
|
|
10
20
|
const response = await context.client.request("POST", `/sessions/${resolvedSessionId}/close`);
|
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runSessionsCommand = runSessionsCommand;
|
|
4
|
+
const errors_1 = require("../lib/errors");
|
|
4
5
|
const utils_1 = require("../lib/utils");
|
|
6
|
+
function parseOlderThanMs(value) {
|
|
7
|
+
if (!value) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const parsed = Number(value);
|
|
11
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
12
|
+
throw new errors_1.AppError("BAD_REQUEST", "--older-than-ms must be a non-negative integer", 400);
|
|
13
|
+
}
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
5
16
|
async function runSessionsCommand(context, argv) {
|
|
6
|
-
(0, utils_1.parseArgs)(argv);
|
|
17
|
+
const parsed = (0, utils_1.parseArgs)(argv);
|
|
18
|
+
if ((0, utils_1.getFlagBoolean)(parsed, "prune")) {
|
|
19
|
+
const olderThanMs = parseOlderThanMs((0, utils_1.getFlagString)(parsed, "older-than-ms"));
|
|
20
|
+
const prune = await context.client.request("POST", "/sessions/prune", {
|
|
21
|
+
maxIdleMs: olderThanMs,
|
|
22
|
+
});
|
|
23
|
+
context.stderr.write(`pruned ${prune.closed} session(s) idle for at least ${prune.maxIdleMs}ms\n`);
|
|
24
|
+
}
|
|
7
25
|
const response = await context.client.request("GET", "/sessions");
|
|
8
26
|
if (response.sessions.length === 0) {
|
|
9
27
|
context.stdout.write("no sessions\n");
|
package/dist/daemon/server.js
CHANGED
|
@@ -46,6 +46,15 @@ async function handleRequest(request, response) {
|
|
|
46
46
|
(0, utils_1.writeJsonResponse)(response, 200, await sessionManager.currentSession());
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
+
if (method === "POST" && pathname === "/sessions/close-all") {
|
|
50
|
+
(0, utils_1.writeJsonResponse)(response, 200, await sessionManager.closeAll());
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (method === "POST" && pathname === "/sessions/prune") {
|
|
54
|
+
const body = (await (0, utils_1.readJsonBody)(request));
|
|
55
|
+
(0, utils_1.writeJsonResponse)(response, 200, await sessionManager.pruneSessions(body?.maxIdleMs));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
49
58
|
if (method === "POST" && pathname === "/sessions/open") {
|
|
50
59
|
const body = (await (0, utils_1.readJsonBody)(request));
|
|
51
60
|
(0, utils_1.writeJsonResponse)(response, 200, await sessionManager.open(body));
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { ActionResponse, AgentConfig, BrowserCookie, CheckResponse, ClickResponse, CloseSessionResponse, CookiesClearResponse, CookiesImportResponse, CookiesResponse, DoubleClickResponse, EvalResponse, FillResponse, FindRequest, FindResponse, FocusResponse, GetKind, GetResponse, HoverResponse, OpenSessionRequest, OpenSessionResponse, PdfResponse, PressResponse, ScrollDirection, ScrollIntoViewResponse, ScrollResponse, ScreenshotResponse, SelectResponse, SessionSummary, SessionsResponse, SnapshotResponse, StorageClearResponse, StorageExportResponse, StorageImportResponse, StorageScope, StorageState, TabCloseResponse, TabFocusResponse, TabOpenResponse, TabsResponse, TypeResponse, UncheckResponse, UploadResponse, WaitResponse } from "../lib/types";
|
|
1
|
+
import type { ActionResponse, AgentConfig, BrowserCookie, CheckResponse, ClickResponse, CloseSessionResponse, CloseAllSessionsResponse, CookiesClearResponse, CookiesImportResponse, CookiesResponse, DoubleClickResponse, EvalResponse, FillResponse, FindRequest, FindResponse, FocusResponse, GetKind, GetResponse, HoverResponse, OpenSessionRequest, OpenSessionResponse, PdfResponse, PressResponse, PruneSessionsResponse, ScrollDirection, ScrollIntoViewResponse, ScrollResponse, ScreenshotResponse, SelectResponse, SessionSummary, SessionsResponse, SnapshotResponse, StorageClearResponse, StorageExportResponse, StorageImportResponse, StorageScope, StorageState, TabCloseResponse, TabFocusResponse, TabOpenResponse, TabsResponse, TypeResponse, UncheckResponse, UploadResponse, WaitResponse } from "../lib/types";
|
|
2
2
|
export declare class SessionManager {
|
|
3
3
|
private readonly config;
|
|
4
|
+
private static readonly DEFAULT_PRUNE_IDLE_MS;
|
|
5
|
+
private static readonly CLOUD_SLOT_RELEASE_WAIT_MS;
|
|
4
6
|
private readonly sessions;
|
|
5
7
|
private activeSessionId?;
|
|
6
8
|
private readonly refStore;
|
|
@@ -8,6 +10,10 @@ export declare class SessionManager {
|
|
|
8
10
|
private nowIso;
|
|
9
11
|
private requireToken;
|
|
10
12
|
private sessionExpired;
|
|
13
|
+
private sessionIdleMs;
|
|
14
|
+
private isCloudSlotLimitError;
|
|
15
|
+
private pruneInactiveSessions;
|
|
16
|
+
private waitForCloudSlotRelease;
|
|
11
17
|
private destroySession;
|
|
12
18
|
private getSessionOrThrow;
|
|
13
19
|
private evictExpiredSessions;
|
|
@@ -17,6 +23,7 @@ export declare class SessionManager {
|
|
|
17
23
|
private resetSnapshotState;
|
|
18
24
|
private activatePage;
|
|
19
25
|
private validateIdleTimeout;
|
|
26
|
+
private createSessionRecord;
|
|
20
27
|
private resolveTargetLocator;
|
|
21
28
|
private runTargetAction;
|
|
22
29
|
open(request: OpenSessionRequest): Promise<OpenSessionResponse>;
|
|
@@ -62,5 +69,6 @@ export declare class SessionManager {
|
|
|
62
69
|
close(sessionId?: string): Promise<CloseSessionResponse>;
|
|
63
70
|
listSessions(): Promise<SessionsResponse>;
|
|
64
71
|
currentSession(): Promise<SessionSummary>;
|
|
65
|
-
|
|
72
|
+
pruneSessions(maxIdleMs?: number): Promise<PruneSessionsResponse>;
|
|
73
|
+
closeAll(): Promise<CloseAllSessionsResponse>;
|
|
66
74
|
}
|
|
@@ -13,6 +13,8 @@ const refStore_1 = require("./refStore");
|
|
|
13
13
|
const snapshot_1 = require("./snapshot");
|
|
14
14
|
class SessionManager {
|
|
15
15
|
config;
|
|
16
|
+
static DEFAULT_PRUNE_IDLE_MS = 10 * 60 * 1000;
|
|
17
|
+
static CLOUD_SLOT_RELEASE_WAIT_MS = 3_000;
|
|
16
18
|
sessions = new Map();
|
|
17
19
|
activeSessionId;
|
|
18
20
|
refStore = new refStore_1.RefStore();
|
|
@@ -38,6 +40,32 @@ class SessionManager {
|
|
|
38
40
|
}
|
|
39
41
|
return Date.now() - lastActivityAt > session.idleTimeoutMs;
|
|
40
42
|
}
|
|
43
|
+
sessionIdleMs(session) {
|
|
44
|
+
const lastActivityAt = Date.parse(session.lastActivityAt);
|
|
45
|
+
if (Number.isNaN(lastActivityAt)) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
return Math.max(0, Date.now() - lastActivityAt);
|
|
49
|
+
}
|
|
50
|
+
isCloudSlotLimitError(error) {
|
|
51
|
+
return (error instanceof errors_1.AppError &&
|
|
52
|
+
error.code === "BROWSER_CONNECTION_FAILED" &&
|
|
53
|
+
/max parallel cloud launches limit/i.test(error.message));
|
|
54
|
+
}
|
|
55
|
+
async pruneInactiveSessions(maxIdleMs = SessionManager.DEFAULT_PRUNE_IDLE_MS) {
|
|
56
|
+
const closedSessionIds = [];
|
|
57
|
+
for (const session of Array.from(this.sessions.values())) {
|
|
58
|
+
if (this.sessionIdleMs(session) < maxIdleMs) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
closedSessionIds.push(session.sessionId);
|
|
62
|
+
await this.destroySession(session);
|
|
63
|
+
}
|
|
64
|
+
return closedSessionIds;
|
|
65
|
+
}
|
|
66
|
+
async waitForCloudSlotRelease() {
|
|
67
|
+
await new Promise((resolve) => setTimeout(resolve, SessionManager.CLOUD_SLOT_RELEASE_WAIT_MS));
|
|
68
|
+
}
|
|
41
69
|
async destroySession(session) {
|
|
42
70
|
await (0, browser_1.closeSessionHandles)(session).catch(() => undefined);
|
|
43
71
|
this.sessions.delete(session.sessionId);
|
|
@@ -116,6 +144,30 @@ class SessionManager {
|
|
|
116
144
|
throw new errors_1.AppError("BAD_REQUEST", "--idle-timeout-ms must be a positive integer", 400);
|
|
117
145
|
}
|
|
118
146
|
}
|
|
147
|
+
async createSessionRecord(token, sessionId, profileId, request, createdAt, resolvedProxy, autoCreatedProfile) {
|
|
148
|
+
const connection = await (0, browser_1.connectToBrowser)(this.config, token, profileId);
|
|
149
|
+
const currentUrl = await (0, browser_1.navigatePage)(connection.page, request.url, this.config.navigationTimeoutMs);
|
|
150
|
+
const lastActivityAt = this.nowIso();
|
|
151
|
+
if (!resolvedProxy && profileId) {
|
|
152
|
+
resolvedProxy = await (0, browser_1.getCloudProfileProxy)(token, profileId).catch(() => undefined);
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
sessionId,
|
|
156
|
+
profileId,
|
|
157
|
+
autoCreatedProfile,
|
|
158
|
+
connectUrl: connection.connectUrl,
|
|
159
|
+
browser: connection.browser,
|
|
160
|
+
context: connection.context,
|
|
161
|
+
page: connection.page,
|
|
162
|
+
currentUrl,
|
|
163
|
+
hasSnapshot: false,
|
|
164
|
+
staleSnapshot: false,
|
|
165
|
+
proxy: resolvedProxy,
|
|
166
|
+
createdAt,
|
|
167
|
+
lastActivityAt,
|
|
168
|
+
idleTimeoutMs: request.idleTimeoutMs
|
|
169
|
+
};
|
|
170
|
+
}
|
|
119
171
|
async resolveTargetLocator(session, target) {
|
|
120
172
|
if ((0, utils_1.isRefTarget)(target)) {
|
|
121
173
|
const descriptor = this.refStore.get(session.sessionId, target);
|
|
@@ -142,6 +194,7 @@ class SessionManager {
|
|
|
142
194
|
async open(request) {
|
|
143
195
|
const token = this.requireToken();
|
|
144
196
|
this.validateIdleTimeout(request.idleTimeoutMs);
|
|
197
|
+
await this.pruneInactiveSessions();
|
|
145
198
|
if (request.profileId && request.proxy) {
|
|
146
199
|
throw new errors_1.AppError("BAD_REQUEST", "proxy flags cannot be combined with --profile", 400);
|
|
147
200
|
}
|
|
@@ -180,35 +233,36 @@ class SessionManager {
|
|
|
180
233
|
autoCreatedProfile = true;
|
|
181
234
|
}
|
|
182
235
|
try {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
236
|
+
let session;
|
|
237
|
+
try {
|
|
238
|
+
session = await this.createSessionRecord(token, sessionId, profileId, request, createdAt, resolvedProxy, autoCreatedProfile);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (!this.isCloudSlotLimitError(error)) {
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
if (this.sessions.size === 0) {
|
|
245
|
+
throw new errors_1.AppError("BROWSER_CONNECTION_FAILED", `${error.message}. No tracked local sessions were available to close. Wait for cloud slots to free up or close stale sessions from another daemon, then retry.`, error.status, error.details);
|
|
246
|
+
}
|
|
247
|
+
const closedSessionIds = (await this.closeAll()).closedSessionIds;
|
|
248
|
+
await this.waitForCloudSlotRelease();
|
|
249
|
+
try {
|
|
250
|
+
session = await this.createSessionRecord(token, sessionId, profileId, request, createdAt, resolvedProxy, autoCreatedProfile);
|
|
251
|
+
}
|
|
252
|
+
catch (retryError) {
|
|
253
|
+
if (retryError instanceof errors_1.AppError && retryError.code === "BROWSER_CONNECTION_FAILED") {
|
|
254
|
+
throw new errors_1.AppError(retryError.code, `${retryError.message}. Closed tracked sessions (${closedSessionIds.join(", ")}) and retried once, but the cloud slot was still unavailable.`, retryError.status, retryError.details);
|
|
255
|
+
}
|
|
256
|
+
throw retryError;
|
|
257
|
+
}
|
|
188
258
|
}
|
|
189
|
-
const session = {
|
|
190
|
-
sessionId,
|
|
191
|
-
profileId,
|
|
192
|
-
autoCreatedProfile,
|
|
193
|
-
connectUrl: connection.connectUrl,
|
|
194
|
-
browser: connection.browser,
|
|
195
|
-
context: connection.context,
|
|
196
|
-
page: connection.page,
|
|
197
|
-
currentUrl,
|
|
198
|
-
hasSnapshot: false,
|
|
199
|
-
staleSnapshot: false,
|
|
200
|
-
proxy: resolvedProxy,
|
|
201
|
-
createdAt,
|
|
202
|
-
lastActivityAt,
|
|
203
|
-
idleTimeoutMs: request.idleTimeoutMs
|
|
204
|
-
};
|
|
205
259
|
this.sessions.set(sessionId, session);
|
|
206
260
|
this.activeSessionId = sessionId;
|
|
207
261
|
this.refStore.clear(sessionId);
|
|
208
262
|
return {
|
|
209
263
|
sessionId,
|
|
210
264
|
profileId,
|
|
211
|
-
url: currentUrl,
|
|
265
|
+
url: session.currentUrl,
|
|
212
266
|
proxy: session.proxy,
|
|
213
267
|
idleTimeoutMs: session.idleTimeoutMs
|
|
214
268
|
};
|
|
@@ -436,10 +490,11 @@ class SessionManager {
|
|
|
436
490
|
value
|
|
437
491
|
};
|
|
438
492
|
}
|
|
439
|
-
|
|
493
|
+
const resolvedTarget = target ?? (kind === "text" || kind === "html" ? "body" : undefined);
|
|
494
|
+
if (!resolvedTarget) {
|
|
440
495
|
throw new errors_1.AppError("BAD_REQUEST", `get ${kind} requires a target`, 400);
|
|
441
496
|
}
|
|
442
|
-
const locator = await this.resolveTargetLocator(session,
|
|
497
|
+
const locator = await this.resolveTargetLocator(session, resolvedTarget);
|
|
443
498
|
const value = await (0, browser_1.readLocatorValue)(locator, kind, this.config.actionTimeoutMs);
|
|
444
499
|
this.markSessionState(session);
|
|
445
500
|
return {
|
|
@@ -673,12 +728,26 @@ class SessionManager {
|
|
|
673
728
|
async currentSession() {
|
|
674
729
|
return this.toSummary(await this.getSessionOrThrow());
|
|
675
730
|
}
|
|
731
|
+
async pruneSessions(maxIdleMs = SessionManager.DEFAULT_PRUNE_IDLE_MS) {
|
|
732
|
+
const closedSessionIds = await this.pruneInactiveSessions(maxIdleMs);
|
|
733
|
+
return {
|
|
734
|
+
closed: closedSessionIds.length,
|
|
735
|
+
closedSessionIds,
|
|
736
|
+
maxIdleMs,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
676
739
|
async closeAll() {
|
|
740
|
+
const closedSessionIds = [];
|
|
677
741
|
for (const session of Array.from(this.sessions.values())) {
|
|
742
|
+
closedSessionIds.push(session.sessionId);
|
|
678
743
|
await this.destroySession(session);
|
|
679
744
|
}
|
|
680
745
|
this.sessions.clear();
|
|
681
746
|
this.activeSessionId = undefined;
|
|
747
|
+
return {
|
|
748
|
+
closed: closedSessionIds.length,
|
|
749
|
+
closedSessionIds,
|
|
750
|
+
};
|
|
682
751
|
}
|
|
683
752
|
}
|
|
684
753
|
exports.SessionManager = SessionManager;
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -206,10 +206,22 @@ export interface CloseSessionResponse {
|
|
|
206
206
|
sessionId: string;
|
|
207
207
|
closed: true;
|
|
208
208
|
}
|
|
209
|
+
export interface CloseAllSessionsResponse {
|
|
210
|
+
closed: number;
|
|
211
|
+
closedSessionIds: string[];
|
|
212
|
+
}
|
|
209
213
|
export interface SessionsResponse {
|
|
210
214
|
activeSessionId?: string;
|
|
211
215
|
sessions: SessionSummary[];
|
|
212
216
|
}
|
|
217
|
+
export interface PruneSessionsRequest {
|
|
218
|
+
maxIdleMs?: number;
|
|
219
|
+
}
|
|
220
|
+
export interface PruneSessionsResponse {
|
|
221
|
+
closed: number;
|
|
222
|
+
closedSessionIds: string[];
|
|
223
|
+
maxIdleMs: number;
|
|
224
|
+
}
|
|
213
225
|
export interface TabSummary {
|
|
214
226
|
index: number;
|
|
215
227
|
url: string;
|
package/dist/lib/utils.js
CHANGED
|
@@ -26,7 +26,7 @@ const errors_1 = require("./errors");
|
|
|
26
26
|
function parseArgs(argv) {
|
|
27
27
|
const positional = [];
|
|
28
28
|
const flags = {};
|
|
29
|
-
const booleanFlags = new Set(["interactive", "exact", "annotate", "press-escape", "json", "clear"]);
|
|
29
|
+
const booleanFlags = new Set(["interactive", "exact", "annotate", "press-escape", "json", "clear", "all", "prune"]);
|
|
30
30
|
for (let index = 0; index < argv.length; index += 1) {
|
|
31
31
|
const token = argv[index];
|
|
32
32
|
if (token === "-i") {
|