web-agent-bridge 2.5.0 → 2.6.0

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.
@@ -0,0 +1,293 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Session Engine
5
+ *
6
+ * Manages browser execution sessions with cookies, tokens,
7
+ * and state persistence across navigation.
8
+ */
9
+
10
+ const crypto = require('crypto');
11
+ const { bus } = require('../runtime/event-bus');
12
+
13
+ class SessionEngine {
14
+ constructor() {
15
+ this._sessions = new Map(); // sessionId → BrowserSession
16
+ this._cookieJars = new Map(); // sessionId → CookieJar
17
+ this._maxSessions = 500;
18
+ this._defaultTTL = 3600_000; // 1 hour
19
+ }
20
+
21
+ /**
22
+ * Create a new browser execution session
23
+ */
24
+ create(config = {}) {
25
+ const sessionId = `sess_${crypto.randomBytes(16).toString('hex')}`;
26
+
27
+ const session = {
28
+ id: sessionId,
29
+ agentId: config.agentId || null,
30
+ siteId: config.siteId || null,
31
+ state: 'active',
32
+ viewport: config.viewport || { width: 1920, height: 1080 },
33
+ userAgent: config.userAgent || null,
34
+ proxy: config.proxy || null,
35
+ localStorage: {},
36
+ sessionStorage: {},
37
+ variables: {}, // Arbitrary key-value store for the agent
38
+ history: [],
39
+ createdAt: Date.now(),
40
+ expiresAt: Date.now() + (config.ttl || this._defaultTTL),
41
+ lastActivity: Date.now(),
42
+ };
43
+
44
+ this._sessions.set(sessionId, session);
45
+ this._cookieJars.set(sessionId, new CookieJar());
46
+ this._evict();
47
+
48
+ bus.emit('session.created', { sessionId, agentId: session.agentId, siteId: session.siteId });
49
+ return session;
50
+ }
51
+
52
+ /**
53
+ * Get session
54
+ */
55
+ get(sessionId) {
56
+ const session = this._sessions.get(sessionId);
57
+ if (!session) return null;
58
+ if (session.expiresAt < Date.now()) {
59
+ this.destroy(sessionId);
60
+ return null;
61
+ }
62
+ return session;
63
+ }
64
+
65
+ /**
66
+ * Update session last activity
67
+ */
68
+ touch(sessionId) {
69
+ const session = this._sessions.get(sessionId);
70
+ if (session) session.lastActivity = Date.now();
71
+ }
72
+
73
+ /**
74
+ * Set cookies for a session
75
+ */
76
+ setCookies(sessionId, cookies) {
77
+ const jar = this._cookieJars.get(sessionId);
78
+ if (!jar) return;
79
+ for (const cookie of cookies) {
80
+ jar.set(cookie);
81
+ }
82
+ this.touch(sessionId);
83
+ }
84
+
85
+ /**
86
+ * Get cookies for a session/domain
87
+ */
88
+ getCookies(sessionId, domain = null) {
89
+ const jar = this._cookieJars.get(sessionId);
90
+ if (!jar) return [];
91
+ return jar.getAll(domain);
92
+ }
93
+
94
+ /**
95
+ * Set localStorage value
96
+ */
97
+ setStorage(sessionId, key, value, type = 'local') {
98
+ const session = this._sessions.get(sessionId);
99
+ if (!session) return;
100
+ const store = type === 'session' ? session.sessionStorage : session.localStorage;
101
+ store[key] = value;
102
+ this.touch(sessionId);
103
+ }
104
+
105
+ /**
106
+ * Get localStorage value
107
+ */
108
+ getStorage(sessionId, key, type = 'local') {
109
+ const session = this._sessions.get(sessionId);
110
+ if (!session) return null;
111
+ const store = type === 'session' ? session.sessionStorage : session.localStorage;
112
+ return store[key] || null;
113
+ }
114
+
115
+ /**
116
+ * Set variable in session
117
+ */
118
+ setVariable(sessionId, key, value) {
119
+ const session = this._sessions.get(sessionId);
120
+ if (session) {
121
+ session.variables[key] = value;
122
+ this.touch(sessionId);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Get variable from session
128
+ */
129
+ getVariable(sessionId, key) {
130
+ const session = this._sessions.get(sessionId);
131
+ return session?.variables[key] || null;
132
+ }
133
+
134
+ /**
135
+ * Record navigation in session history
136
+ */
137
+ recordNavigation(sessionId, url, title = '') {
138
+ const session = this._sessions.get(sessionId);
139
+ if (!session) return;
140
+ session.history.push({
141
+ url,
142
+ title,
143
+ timestamp: Date.now(),
144
+ index: session.history.length,
145
+ });
146
+ this.touch(sessionId);
147
+ }
148
+
149
+ /**
150
+ * Export session state (for transfer/persistence)
151
+ */
152
+ export(sessionId) {
153
+ const session = this._sessions.get(sessionId);
154
+ if (!session) return null;
155
+ return {
156
+ id: session.id,
157
+ agentId: session.agentId,
158
+ siteId: session.siteId,
159
+ viewport: session.viewport,
160
+ userAgent: session.userAgent,
161
+ localStorage: { ...session.localStorage },
162
+ sessionStorage: { ...session.sessionStorage },
163
+ variables: { ...session.variables },
164
+ cookies: this.getCookies(sessionId),
165
+ history: [...session.history],
166
+ createdAt: session.createdAt,
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Import session state (restore from exported data)
172
+ */
173
+ import(data) {
174
+ const session = this.create({
175
+ agentId: data.agentId,
176
+ siteId: data.siteId,
177
+ viewport: data.viewport,
178
+ userAgent: data.userAgent,
179
+ });
180
+
181
+ session.localStorage = data.localStorage || {};
182
+ session.sessionStorage = data.sessionStorage || {};
183
+ session.variables = data.variables || {};
184
+ session.history = data.history || [];
185
+
186
+ if (data.cookies) {
187
+ this.setCookies(session.id, data.cookies);
188
+ }
189
+
190
+ return session;
191
+ }
192
+
193
+ /**
194
+ * Destroy session
195
+ */
196
+ destroy(sessionId) {
197
+ this._sessions.delete(sessionId);
198
+ this._cookieJars.delete(sessionId);
199
+ bus.emit('session.destroyed', { sessionId });
200
+ }
201
+
202
+ /**
203
+ * List sessions
204
+ */
205
+ list(filters = {}, limit = 50) {
206
+ const now = Date.now();
207
+ let sessions = Array.from(this._sessions.values()).filter(s => s.expiresAt >= now);
208
+
209
+ if (filters.agentId) sessions = sessions.filter(s => s.agentId === filters.agentId);
210
+ if (filters.siteId) sessions = sessions.filter(s => s.siteId === filters.siteId);
211
+ if (filters.state) sessions = sessions.filter(s => s.state === filters.state);
212
+
213
+ return sessions.slice(0, limit).map(s => ({
214
+ id: s.id,
215
+ agentId: s.agentId,
216
+ siteId: s.siteId,
217
+ state: s.state,
218
+ historyLength: s.history.length,
219
+ createdAt: s.createdAt,
220
+ lastActivity: s.lastActivity,
221
+ }));
222
+ }
223
+
224
+ getStats() {
225
+ return {
226
+ activeSessions: this._sessions.size,
227
+ maxSessions: this._maxSessions,
228
+ };
229
+ }
230
+
231
+ _evict() {
232
+ const now = Date.now();
233
+ for (const [id, session] of this._sessions) {
234
+ if (session.expiresAt < now) this.destroy(id);
235
+ }
236
+ if (this._sessions.size > this._maxSessions) {
237
+ const sorted = Array.from(this._sessions.entries())
238
+ .sort((a, b) => a[1].lastActivity - b[1].lastActivity);
239
+ const toRemove = sorted.slice(0, sorted.length - this._maxSessions);
240
+ for (const [id] of toRemove) this.destroy(id);
241
+ }
242
+ }
243
+ }
244
+
245
+ // ─── Cookie Jar ─────────────────────────────────────────────────────────────
246
+
247
+ class CookieJar {
248
+ constructor() {
249
+ this._cookies = new Map(); // domain:name → cookie
250
+ }
251
+
252
+ set(cookie) {
253
+ const key = `${cookie.domain || '*'}:${cookie.name}`;
254
+ this._cookies.set(key, {
255
+ name: cookie.name,
256
+ value: cookie.value,
257
+ domain: cookie.domain || '*',
258
+ path: cookie.path || '/',
259
+ expires: cookie.expires || null,
260
+ httpOnly: cookie.httpOnly || false,
261
+ secure: cookie.secure || false,
262
+ sameSite: cookie.sameSite || 'Lax',
263
+ setAt: Date.now(),
264
+ });
265
+ }
266
+
267
+ get(name, domain = '*') {
268
+ return this._cookies.get(`${domain}:${name}`) || null;
269
+ }
270
+
271
+ getAll(domain = null) {
272
+ const now = Date.now();
273
+ const result = [];
274
+ for (const cookie of this._cookies.values()) {
275
+ if (cookie.expires && cookie.expires < now) continue;
276
+ if (domain && cookie.domain !== '*' && cookie.domain !== domain) continue;
277
+ result.push({ ...cookie });
278
+ }
279
+ return result;
280
+ }
281
+
282
+ delete(name, domain = '*') {
283
+ this._cookies.delete(`${domain}:${name}`);
284
+ }
285
+
286
+ clear() {
287
+ this._cookies.clear();
288
+ }
289
+ }
290
+
291
+ const sessionEngine = new SessionEngine();
292
+
293
+ module.exports = { SessionEngine, CookieJar, sessionEngine };
@@ -184,6 +184,19 @@ class AgentIdentity {
184
184
  }
185
185
  }
186
186
 
187
+ /**
188
+ * Validate a session token and return session data
189
+ */
190
+ validateSession(token) {
191
+ const session = this._sessions.get(token);
192
+ if (!session) return null;
193
+ if (Date.now() > session.expiresAt) {
194
+ this._sessions.delete(token);
195
+ return null;
196
+ }
197
+ return session;
198
+ }
199
+
187
200
  /**
188
201
  * Cleanup expired sessions
189
202
  */