instavm 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,986 @@
1
+ "use strict";
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ AuthenticationError: () => AuthenticationError,
35
+ BrowserError: () => BrowserError,
36
+ BrowserInteractionError: () => BrowserInteractionError,
37
+ BrowserManager: () => BrowserManager,
38
+ BrowserNavigationError: () => BrowserNavigationError,
39
+ BrowserSession: () => BrowserSession,
40
+ BrowserSessionError: () => BrowserSessionError,
41
+ BrowserTimeoutError: () => BrowserTimeoutError,
42
+ ElementNotFoundError: () => ElementNotFoundError,
43
+ ExecutionError: () => ExecutionError,
44
+ InstaVM: () => InstaVM,
45
+ InstaVMError: () => InstaVMError,
46
+ NetworkError: () => NetworkError,
47
+ QuotaExceededError: () => QuotaExceededError,
48
+ RateLimitError: () => RateLimitError,
49
+ SessionError: () => SessionError
50
+ });
51
+ module.exports = __toCommonJS(index_exports);
52
+
53
+ // src/client/HTTPClient.ts
54
+ var import_axios = __toESM(require("axios"));
55
+
56
+ // src/errors/BaseError.ts
57
+ var InstaVMError = class extends Error {
58
+ constructor(message, options) {
59
+ super(message);
60
+ this.name = this.constructor.name;
61
+ this.code = options?.code;
62
+ this.statusCode = options?.statusCode;
63
+ this.response = options?.response;
64
+ Object.setPrototypeOf(this, new.target.prototype);
65
+ if (Error.captureStackTrace) {
66
+ Error.captureStackTrace(this, this.constructor);
67
+ }
68
+ }
69
+ };
70
+ var AuthenticationError = class extends InstaVMError {
71
+ constructor(message = "Authentication failed", options) {
72
+ super(message, { ...options, statusCode: 401 });
73
+ }
74
+ };
75
+ var RateLimitError = class extends InstaVMError {
76
+ constructor(message = "Rate limit exceeded", retryAfter, options) {
77
+ super(message, { ...options, statusCode: 429 });
78
+ this.retryAfter = retryAfter;
79
+ }
80
+ };
81
+ var QuotaExceededError = class extends InstaVMError {
82
+ constructor(message = "Usage quota exceeded", options) {
83
+ super(message, { ...options, statusCode: 402 });
84
+ }
85
+ };
86
+ var NetworkError = class extends InstaVMError {
87
+ constructor(message = "Network error", options) {
88
+ super(message, options);
89
+ }
90
+ };
91
+ var ExecutionError = class extends InstaVMError {
92
+ constructor(message, executionOutput, executionTime, options) {
93
+ super(message, options);
94
+ this.executionOutput = executionOutput;
95
+ this.executionTime = executionTime;
96
+ }
97
+ };
98
+ var SessionError = class extends InstaVMError {
99
+ constructor(message = "Session error", options) {
100
+ super(message, options);
101
+ }
102
+ };
103
+ var BrowserError = class extends InstaVMError {
104
+ constructor(message = "Browser error", options) {
105
+ super(message, options);
106
+ }
107
+ };
108
+ var BrowserSessionError = class extends BrowserError {
109
+ constructor(message = "Browser session error", options) {
110
+ super(message, options);
111
+ }
112
+ };
113
+ var BrowserInteractionError = class extends BrowserError {
114
+ constructor(message = "Browser interaction error", options) {
115
+ super(message, options);
116
+ }
117
+ };
118
+ var BrowserTimeoutError = class extends BrowserError {
119
+ constructor(message = "Browser operation timed out", options) {
120
+ super(message, options);
121
+ }
122
+ };
123
+ var BrowserNavigationError = class extends BrowserError {
124
+ constructor(message = "Browser navigation error", options) {
125
+ super(message, options);
126
+ }
127
+ };
128
+ var ElementNotFoundError = class extends BrowserError {
129
+ constructor(message = "Element not found", selector, options) {
130
+ super(message, options);
131
+ this.selector = selector;
132
+ }
133
+ };
134
+
135
+ // src/utils/retry.ts
136
+ function defaultRetryCondition(error) {
137
+ if (error instanceof NetworkError) return true;
138
+ if (error instanceof RateLimitError) return true;
139
+ if (error.response?.status >= 500) return true;
140
+ if (error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") return true;
141
+ if (error.code === "ECONNRESET" || error.code === "ENOTFOUND") return true;
142
+ return false;
143
+ }
144
+ function calculateRetryDelay(attempt, baseDelay, maxDelay = 3e4) {
145
+ const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
146
+ const jitteredDelay = exponentialDelay * (0.5 + Math.random() * 0.5);
147
+ return Math.min(jitteredDelay, maxDelay);
148
+ }
149
+ async function withRetry(fn, options) {
150
+ const {
151
+ retries,
152
+ retryDelay,
153
+ maxRetryDelay = 3e4,
154
+ retryCondition = defaultRetryCondition
155
+ } = options;
156
+ let lastError;
157
+ for (let attempt = 1; attempt <= retries + 1; attempt++) {
158
+ try {
159
+ return await fn();
160
+ } catch (error) {
161
+ lastError = error;
162
+ if (attempt === retries + 1) {
163
+ break;
164
+ }
165
+ if (!retryCondition(error)) {
166
+ break;
167
+ }
168
+ const delay = calculateRetryDelay(attempt, retryDelay, maxRetryDelay);
169
+ await new Promise((resolve) => setTimeout(resolve, delay));
170
+ }
171
+ }
172
+ throw lastError;
173
+ }
174
+
175
+ // src/client/HTTPClient.ts
176
+ var HTTPClient = class {
177
+ get apiKey() {
178
+ return this.config.apiKey;
179
+ }
180
+ constructor(config) {
181
+ this.config = config;
182
+ this.client = import_axios.default.create({
183
+ baseURL: config.baseURL,
184
+ timeout: config.timeout,
185
+ headers: {
186
+ "Content-Type": "application/json",
187
+ "User-Agent": "instavm-js-sdk/0.1.0"
188
+ }
189
+ });
190
+ this.setupInterceptors();
191
+ }
192
+ setupInterceptors() {
193
+ this.client.interceptors.request.use(
194
+ (config) => {
195
+ if (config.url?.includes("/browser/")) {
196
+ config.headers["X-API-Key"] = this.config.apiKey;
197
+ }
198
+ return config;
199
+ },
200
+ (error) => Promise.reject(error)
201
+ );
202
+ this.client.interceptors.response.use(
203
+ (response) => response,
204
+ (error) => {
205
+ const axiosError = error;
206
+ const status = axiosError.response?.status;
207
+ const data = axiosError.response?.data;
208
+ const message = data?.message || data?.error || axiosError.message;
209
+ switch (status) {
210
+ case 401:
211
+ throw new AuthenticationError(message, {
212
+ statusCode: status,
213
+ response: data
214
+ });
215
+ case 402:
216
+ throw new QuotaExceededError(message, {
217
+ statusCode: status,
218
+ response: data
219
+ });
220
+ case 429:
221
+ const retryAfter = parseInt(
222
+ axiosError.response?.headers["retry-after"] || "60"
223
+ );
224
+ throw new RateLimitError(message, retryAfter, {
225
+ statusCode: status,
226
+ response: data
227
+ });
228
+ case 500:
229
+ case 502:
230
+ case 503:
231
+ case 504:
232
+ throw new NetworkError(message, {
233
+ statusCode: status,
234
+ response: data
235
+ });
236
+ default:
237
+ if (axiosError.code === "ECONNABORTED") {
238
+ throw new NetworkError("Request timeout", {
239
+ code: axiosError.code
240
+ });
241
+ }
242
+ throw new InstaVMError(message, {
243
+ statusCode: status,
244
+ response: data,
245
+ code: axiosError.code
246
+ });
247
+ }
248
+ }
249
+ );
250
+ }
251
+ /**
252
+ * Make an HTTP request with retry logic
253
+ */
254
+ async request(requestConfig) {
255
+ const axiosConfig = {
256
+ method: requestConfig.method,
257
+ url: requestConfig.url,
258
+ headers: requestConfig.headers,
259
+ data: requestConfig.data,
260
+ params: requestConfig.params,
261
+ timeout: requestConfig.timeout || this.config.timeout
262
+ };
263
+ const makeRequest = async () => {
264
+ const response = await this.client.request(axiosConfig);
265
+ return response.data;
266
+ };
267
+ return withRetry(makeRequest, {
268
+ retries: this.config.maxRetries,
269
+ retryDelay: this.config.retryDelay
270
+ });
271
+ }
272
+ /**
273
+ * POST request with JSON body
274
+ */
275
+ async post(url, data, headers) {
276
+ return this.request({
277
+ method: "POST",
278
+ url,
279
+ data,
280
+ headers
281
+ });
282
+ }
283
+ /**
284
+ * POST request for code execution (uses X-API-Key header like Python client)
285
+ */
286
+ async postExecution(url, data, headers) {
287
+ const requestHeaders = {
288
+ "X-API-Key": this.config.apiKey,
289
+ ...headers
290
+ };
291
+ return this.request({
292
+ method: "POST",
293
+ url,
294
+ data,
295
+ headers: requestHeaders
296
+ });
297
+ }
298
+ /**
299
+ * POST request with form data (for file uploads)
300
+ */
301
+ async postFormData(url, formData, headers) {
302
+ const requestHeaders = {
303
+ ...formData.getHeaders(),
304
+ ...headers
305
+ };
306
+ return this.request({
307
+ method: "POST",
308
+ url,
309
+ data: formData,
310
+ headers: requestHeaders
311
+ });
312
+ }
313
+ /**
314
+ * GET request
315
+ */
316
+ async get(url, params, headers) {
317
+ return this.request({
318
+ method: "GET",
319
+ url,
320
+ params,
321
+ headers
322
+ });
323
+ }
324
+ /**
325
+ * DELETE request
326
+ */
327
+ async delete(url, headers) {
328
+ return this.request({
329
+ method: "DELETE",
330
+ url,
331
+ headers
332
+ });
333
+ }
334
+ };
335
+
336
+ // src/client/BrowserSession.ts
337
+ var import_eventemitter3 = require("eventemitter3");
338
+ function getErrorMessage(error) {
339
+ return error instanceof Error ? error.message : String(error);
340
+ }
341
+ var BrowserSession = class extends import_eventemitter3.EventEmitter {
342
+ constructor(sessionId, httpClient) {
343
+ super();
344
+ this._isActive = true;
345
+ this.sessionId = sessionId;
346
+ this.httpClient = httpClient;
347
+ }
348
+ /**
349
+ * Navigate to a URL
350
+ */
351
+ async navigate(url, options = {}) {
352
+ this.ensureActive();
353
+ const requestData = {
354
+ url,
355
+ session_id: this.sessionId,
356
+ wait_timeout: options.waitTimeout || 3e4,
357
+ wait_until: options.waitUntil || "load"
358
+ };
359
+ try {
360
+ const response = await this.httpClient.post(
361
+ "/v1/browser/interactions/navigate",
362
+ requestData
363
+ );
364
+ const result = {
365
+ success: response.success !== false,
366
+ url: response.url || url,
367
+ title: response.title,
368
+ status: response.status
369
+ };
370
+ this.emit("navigation", result);
371
+ return result;
372
+ } catch (error) {
373
+ const navigationError = new BrowserNavigationError(
374
+ `Navigation failed: ${getErrorMessage(error)}`,
375
+ { cause: error }
376
+ );
377
+ this.emit("error", navigationError);
378
+ throw navigationError;
379
+ }
380
+ }
381
+ /**
382
+ * Click an element
383
+ */
384
+ async click(selector, options = {}) {
385
+ this.ensureActive();
386
+ const requestData = {
387
+ selector,
388
+ session_id: this.sessionId,
389
+ timeout: options.timeout || 1e4,
390
+ button: options.button || "left",
391
+ click_count: options.clickCount || 1,
392
+ force: options.force || false
393
+ };
394
+ try {
395
+ await this.httpClient.post(
396
+ "/v1/browser/interactions/click",
397
+ requestData
398
+ );
399
+ } catch (error) {
400
+ const errorMessage = getErrorMessage(error);
401
+ if (errorMessage.includes("not found") || errorMessage.includes("selector")) {
402
+ throw new ElementNotFoundError(
403
+ `Element not found: ${selector}`,
404
+ selector,
405
+ { cause: error }
406
+ );
407
+ }
408
+ if (errorMessage.includes("timeout")) {
409
+ throw new BrowserTimeoutError(
410
+ `Click timeout: ${selector}`,
411
+ { cause: error }
412
+ );
413
+ }
414
+ throw new BrowserInteractionError(
415
+ `Click failed: ${errorMessage}`,
416
+ { cause: error }
417
+ );
418
+ }
419
+ }
420
+ /**
421
+ * Type text into an element
422
+ */
423
+ async type(selector, text, options = {}) {
424
+ this.ensureActive();
425
+ const requestData = {
426
+ selector,
427
+ text,
428
+ session_id: this.sessionId,
429
+ timeout: options.timeout || 1e4,
430
+ delay: options.delay || 0,
431
+ clear: options.clear !== false
432
+ };
433
+ try {
434
+ await this.httpClient.post(
435
+ "/v1/browser/interactions/type",
436
+ requestData
437
+ );
438
+ } catch (error) {
439
+ const errorMessage = getErrorMessage(error);
440
+ if (errorMessage.includes("not found") || errorMessage.includes("selector")) {
441
+ throw new ElementNotFoundError(
442
+ `Element not found: ${selector}`,
443
+ selector,
444
+ { cause: error }
445
+ );
446
+ }
447
+ throw new BrowserInteractionError(
448
+ `Type failed: ${errorMessage}`,
449
+ { cause: error }
450
+ );
451
+ }
452
+ }
453
+ /**
454
+ * Fill a form field
455
+ */
456
+ async fill(selector, value, options = {}) {
457
+ this.ensureActive();
458
+ const requestData = {
459
+ selector,
460
+ value,
461
+ session_id: this.sessionId,
462
+ timeout: options.timeout || 1e4,
463
+ force: options.force || false
464
+ };
465
+ try {
466
+ await this.httpClient.post(
467
+ "/v1/browser/interactions/fill",
468
+ requestData
469
+ );
470
+ } catch (error) {
471
+ const errorMessage = getErrorMessage(error);
472
+ if (errorMessage.includes("not found") || errorMessage.includes("selector")) {
473
+ throw new ElementNotFoundError(
474
+ `Element not found: ${selector}`,
475
+ selector,
476
+ { cause: error }
477
+ );
478
+ }
479
+ throw new BrowserInteractionError(
480
+ `Fill failed: ${errorMessage}`,
481
+ { cause: error }
482
+ );
483
+ }
484
+ }
485
+ /**
486
+ * Scroll the page
487
+ */
488
+ async scroll(options = {}) {
489
+ this.ensureActive();
490
+ const requestData = {
491
+ session_id: this.sessionId,
492
+ x: options.x || 0,
493
+ y: options.y || 500,
494
+ behavior: options.behavior || "auto"
495
+ };
496
+ try {
497
+ await this.httpClient.post(
498
+ "/v1/browser/interactions/scroll",
499
+ requestData
500
+ );
501
+ } catch (error) {
502
+ throw new BrowserInteractionError(
503
+ `Scroll failed: ${getErrorMessage(error)}`,
504
+ { cause: error }
505
+ );
506
+ }
507
+ }
508
+ /**
509
+ * Take a screenshot
510
+ */
511
+ async screenshot(options = {}) {
512
+ this.ensureActive();
513
+ const requestData = {
514
+ session_id: this.sessionId,
515
+ full_page: options.fullPage !== false,
516
+ format: options.format || "png",
517
+ quality: options.quality || 90,
518
+ clip: options.clip
519
+ };
520
+ try {
521
+ const response = await this.httpClient.post(
522
+ "/v1/browser/interactions/screenshot",
523
+ requestData
524
+ );
525
+ if (!response.screenshot) {
526
+ throw new BrowserError("Screenshot data not returned");
527
+ }
528
+ return response.screenshot;
529
+ } catch (error) {
530
+ throw new BrowserInteractionError(
531
+ `Screenshot failed: ${getErrorMessage(error)}`,
532
+ { cause: error }
533
+ );
534
+ }
535
+ }
536
+ /**
537
+ * Extract elements from the page
538
+ */
539
+ async extractElements(selector, attributes = ["text"], options = {}) {
540
+ this.ensureActive();
541
+ const requestData = {
542
+ selector,
543
+ attributes,
544
+ session_id: this.sessionId,
545
+ max_results: options.maxResults || 100
546
+ };
547
+ try {
548
+ const response = await this.httpClient.post(
549
+ "/v1/browser/interactions/extract",
550
+ requestData
551
+ );
552
+ return response.elements || [];
553
+ } catch (error) {
554
+ throw new BrowserInteractionError(
555
+ `Element extraction failed: ${getErrorMessage(error)}`,
556
+ { cause: error }
557
+ );
558
+ }
559
+ }
560
+ /**
561
+ * Wait for a condition
562
+ */
563
+ async wait(condition, timeout = 1e4) {
564
+ this.ensureActive();
565
+ let requestData = {
566
+ session_id: this.sessionId,
567
+ timeout
568
+ };
569
+ if (condition.type === "timeout") {
570
+ await new Promise((resolve) => setTimeout(resolve, condition.ms));
571
+ return;
572
+ }
573
+ requestData = {
574
+ ...requestData,
575
+ condition: condition.type,
576
+ selector: "selector" in condition ? condition.selector : void 0
577
+ };
578
+ try {
579
+ await this.httpClient.post(
580
+ "/v1/browser/interactions/wait",
581
+ requestData
582
+ );
583
+ } catch (error) {
584
+ const errorMessage = getErrorMessage(error);
585
+ if (errorMessage.includes("timeout")) {
586
+ throw new BrowserTimeoutError(
587
+ `Wait condition timeout: ${condition.type}`,
588
+ { cause: error }
589
+ );
590
+ }
591
+ throw new BrowserInteractionError(
592
+ `Wait failed: ${errorMessage}`,
593
+ { cause: error }
594
+ );
595
+ }
596
+ }
597
+ /**
598
+ * Close the browser session
599
+ */
600
+ async close() {
601
+ if (!this._isActive) {
602
+ return;
603
+ }
604
+ try {
605
+ await this.httpClient.delete(`/v1/browser/sessions/${this.sessionId}`);
606
+ } catch (error) {
607
+ console.warn(`Failed to close browser session ${this.sessionId}:`, getErrorMessage(error));
608
+ } finally {
609
+ this._isActive = false;
610
+ this.emit("close");
611
+ }
612
+ }
613
+ /**
614
+ * Check if session is active
615
+ */
616
+ get isActive() {
617
+ return this._isActive;
618
+ }
619
+ /**
620
+ * Ensure session is active before operations
621
+ */
622
+ ensureActive() {
623
+ if (!this._isActive) {
624
+ throw new BrowserError("Browser session is not active");
625
+ }
626
+ }
627
+ };
628
+
629
+ // src/client/BrowserManager.ts
630
+ var BrowserManager = class {
631
+ constructor(httpClient) {
632
+ this.activeSessions = /* @__PURE__ */ new Map();
633
+ this.httpClient = httpClient;
634
+ }
635
+ /**
636
+ * Create a new browser session
637
+ */
638
+ async createSession(options = {}) {
639
+ const requestData = {
640
+ viewport_width: options.viewportWidth || 1920,
641
+ viewport_height: options.viewportHeight || 1080,
642
+ user_agent: options.userAgent
643
+ };
644
+ try {
645
+ const response = await this.httpClient.post(
646
+ "/v1/browser/sessions/",
647
+ requestData
648
+ );
649
+ if (!response.session_id) {
650
+ throw new BrowserSessionError("No session ID returned from server");
651
+ }
652
+ const session = new BrowserSession(response.session_id, this.httpClient);
653
+ this.activeSessions.set(response.session_id, session);
654
+ session.on("close", () => {
655
+ this.activeSessions.delete(response.session_id);
656
+ });
657
+ return session;
658
+ } catch (error) {
659
+ const errorMessage = error instanceof Error ? error.message : String(error);
660
+ throw new BrowserSessionError(
661
+ `Failed to create browser session: ${errorMessage}`,
662
+ { cause: error }
663
+ );
664
+ }
665
+ }
666
+ /**
667
+ * Get information about a specific session
668
+ */
669
+ async getSession(sessionId) {
670
+ try {
671
+ const response = await this.httpClient.get(
672
+ `/v1/browser/sessions/${sessionId}`
673
+ );
674
+ return {
675
+ sessionId: response.session_id,
676
+ status: response.status || "active",
677
+ createdAt: response.created_at,
678
+ viewportWidth: response.viewport_width,
679
+ viewportHeight: response.viewport_height,
680
+ userAgent: response.user_agent
681
+ };
682
+ } catch (error) {
683
+ const errorMessage = error instanceof Error ? error.message : String(error);
684
+ throw new BrowserSessionError(
685
+ `Failed to get session info: ${errorMessage}`,
686
+ { cause: error }
687
+ );
688
+ }
689
+ }
690
+ /**
691
+ * List all browser sessions
692
+ */
693
+ async listSessions() {
694
+ try {
695
+ const response = await this.httpClient.get("/v1/browser/sessions/");
696
+ if (!Array.isArray(response.sessions)) {
697
+ return [];
698
+ }
699
+ return response.sessions.map((session) => ({
700
+ sessionId: session.session_id,
701
+ status: session.status || "active",
702
+ createdAt: session.created_at,
703
+ viewportWidth: session.viewport_width,
704
+ viewportHeight: session.viewport_height,
705
+ userAgent: session.user_agent
706
+ }));
707
+ } catch (error) {
708
+ const errorMessage = error instanceof Error ? error.message : String(error);
709
+ throw new BrowserSessionError(
710
+ `Failed to list sessions: ${errorMessage}`,
711
+ { cause: error }
712
+ );
713
+ }
714
+ }
715
+ /**
716
+ * Get a locally tracked session
717
+ */
718
+ getLocalSession(sessionId) {
719
+ return this.activeSessions.get(sessionId);
720
+ }
721
+ /**
722
+ * Get all locally tracked sessions
723
+ */
724
+ getLocalSessions() {
725
+ return Array.from(this.activeSessions.values());
726
+ }
727
+ /**
728
+ * Close all active sessions
729
+ */
730
+ async closeAllSessions() {
731
+ const sessions = Array.from(this.activeSessions.values());
732
+ await Promise.allSettled(
733
+ sessions.map((session) => session.close())
734
+ );
735
+ this.activeSessions.clear();
736
+ }
737
+ /**
738
+ * Clean up resources
739
+ */
740
+ async dispose() {
741
+ await this.closeAllSessions();
742
+ }
743
+ };
744
+
745
+ // src/client/InstaVM.ts
746
+ var import_form_data = __toESM(require("form-data"));
747
+ var InstaVM = class {
748
+ constructor(apiKey, options = {}) {
749
+ this._sessionId = null;
750
+ if (!apiKey) {
751
+ throw new Error("API key is required");
752
+ }
753
+ const config = {
754
+ baseURL: options.baseURL || "https://api.instavm.io",
755
+ timeout: options.timeout || 3e5,
756
+ // 5 minutes
757
+ maxRetries: options.maxRetries || 3,
758
+ retryDelay: options.retryDelay || 1e3,
759
+ apiKey
760
+ };
761
+ this.httpClient = new HTTPClient(config);
762
+ this.browser = new BrowserManager(this.httpClient);
763
+ }
764
+ /**
765
+ * Execute code synchronously
766
+ */
767
+ async execute(command, options = {}) {
768
+ let sessionId = options.sessionId || this._sessionId;
769
+ if (!sessionId) {
770
+ sessionId = await this.createSession();
771
+ }
772
+ const requestData = {
773
+ command,
774
+ language: options.language || "python",
775
+ timeout: options.timeout || 15,
776
+ session_id: sessionId
777
+ };
778
+ try {
779
+ const response = await this.httpClient.postExecution(
780
+ "/execute",
781
+ requestData
782
+ );
783
+ if (response.session_id) {
784
+ this._sessionId = response.session_id;
785
+ }
786
+ return {
787
+ output: response.stdout || response.output || "",
788
+ success: response.success !== false,
789
+ executionTime: response.execution_time || 0,
790
+ sessionId: response.session_id,
791
+ error: response.error
792
+ };
793
+ } catch (error) {
794
+ const errorMessage = error instanceof Error ? error.message : String(error);
795
+ throw new ExecutionError(
796
+ `Code execution failed: ${errorMessage}`,
797
+ void 0,
798
+ void 0,
799
+ { cause: error }
800
+ );
801
+ }
802
+ }
803
+ /**
804
+ * Execute code asynchronously
805
+ */
806
+ async executeAsync(command, options = {}) {
807
+ let sessionId = options.sessionId || this._sessionId;
808
+ if (!sessionId) {
809
+ sessionId = await this.createSession();
810
+ }
811
+ const requestData = {
812
+ command,
813
+ language: options.language || "python",
814
+ timeout: options.timeout || 15,
815
+ session_id: sessionId
816
+ };
817
+ try {
818
+ const response = await this.httpClient.postExecution(
819
+ "/execute_async",
820
+ requestData
821
+ );
822
+ if (response.session_id) {
823
+ this._sessionId = response.session_id;
824
+ }
825
+ return {
826
+ taskId: response.task_id,
827
+ status: response.status || "pending",
828
+ output: response.stdout || response.output,
829
+ success: response.success,
830
+ executionTime: response.execution_time,
831
+ sessionId: response.session_id,
832
+ error: response.error
833
+ };
834
+ } catch (error) {
835
+ const errorMessage = error instanceof Error ? error.message : String(error);
836
+ throw new ExecutionError(
837
+ `Async code execution failed: ${errorMessage}`,
838
+ void 0,
839
+ void 0,
840
+ { cause: error }
841
+ );
842
+ }
843
+ }
844
+ /**
845
+ * Upload files to the execution environment
846
+ */
847
+ async upload(files, options = {}) {
848
+ const formData = new import_form_data.default();
849
+ for (const file of files) {
850
+ if (Buffer.isBuffer(file.content)) {
851
+ formData.append("files", file.content, file.name);
852
+ } else {
853
+ formData.append("files", Buffer.from(file.content), file.name);
854
+ }
855
+ if (file.path) {
856
+ formData.append("paths", file.path);
857
+ }
858
+ }
859
+ if (options.sessionId || this._sessionId) {
860
+ formData.append("session_id", options.sessionId || this._sessionId);
861
+ }
862
+ if (options.remotePath) {
863
+ formData.append("remote_path", options.remotePath);
864
+ }
865
+ if (options.recursive !== void 0) {
866
+ formData.append("recursive", String(options.recursive));
867
+ }
868
+ try {
869
+ const response = await this.httpClient.postFormData(
870
+ "/upload",
871
+ formData
872
+ );
873
+ return {
874
+ success: response.success !== false,
875
+ files: response.files || [],
876
+ message: response.message
877
+ };
878
+ } catch (error) {
879
+ const errorMessage = error instanceof Error ? error.message : String(error);
880
+ throw new SessionError(
881
+ `File upload failed: ${errorMessage}`,
882
+ { cause: error }
883
+ );
884
+ }
885
+ }
886
+ /**
887
+ * Create a new execution session
888
+ */
889
+ async createSession() {
890
+ try {
891
+ const response = await this.httpClient.post("/v1/sessions/session", {
892
+ api_key: this.httpClient.apiKey
893
+ });
894
+ if (response.session_id) {
895
+ this._sessionId = response.session_id;
896
+ return response.session_id;
897
+ }
898
+ throw new SessionError("Session creation failed: No session ID returned");
899
+ } catch (error) {
900
+ const errorMessage = error instanceof Error ? error.message : String(error);
901
+ throw new SessionError(
902
+ `Session creation failed: ${errorMessage}`,
903
+ { cause: error }
904
+ );
905
+ }
906
+ }
907
+ /**
908
+ * Close a session
909
+ */
910
+ async closeSession(sessionId) {
911
+ const targetSessionId = sessionId || this._sessionId;
912
+ if (!targetSessionId) {
913
+ return;
914
+ }
915
+ try {
916
+ await this.httpClient.delete(`/v1/sessions/${targetSessionId}`);
917
+ if (targetSessionId === this._sessionId) {
918
+ this._sessionId = null;
919
+ }
920
+ } catch (error) {
921
+ const errorMessage = error instanceof Error ? error.message : String(error);
922
+ console.warn(`Failed to close session ${targetSessionId}:`, errorMessage);
923
+ }
924
+ }
925
+ /**
926
+ * Get usage statistics for a session
927
+ */
928
+ async getUsage(sessionId) {
929
+ const targetSessionId = sessionId || this._sessionId;
930
+ if (!targetSessionId) {
931
+ throw new SessionError("No active session to get usage for");
932
+ }
933
+ try {
934
+ const response = await this.httpClient.get(
935
+ `/v1/sessions/usage/${targetSessionId}`
936
+ );
937
+ return {
938
+ sessionsUsed: response.sessions_used || 0,
939
+ executionTime: response.execution_time || 0,
940
+ quotaRemaining: response.quota_remaining || 0,
941
+ quotaLimit: response.quota_limit || 0
942
+ };
943
+ } catch (error) {
944
+ const errorMessage = error instanceof Error ? error.message : String(error);
945
+ throw new SessionError(
946
+ `Failed to get usage stats: ${errorMessage}`,
947
+ { cause: error }
948
+ );
949
+ }
950
+ }
951
+ /**
952
+ * Get the current session ID
953
+ */
954
+ get sessionId() {
955
+ return this._sessionId;
956
+ }
957
+ /**
958
+ * Clean up resources
959
+ */
960
+ async dispose() {
961
+ if (this._sessionId) {
962
+ await this.closeSession();
963
+ }
964
+ await this.browser.dispose();
965
+ }
966
+ };
967
+ // Annotate the CommonJS export names for ESM import in node:
968
+ 0 && (module.exports = {
969
+ AuthenticationError,
970
+ BrowserError,
971
+ BrowserInteractionError,
972
+ BrowserManager,
973
+ BrowserNavigationError,
974
+ BrowserSession,
975
+ BrowserSessionError,
976
+ BrowserTimeoutError,
977
+ ElementNotFoundError,
978
+ ExecutionError,
979
+ InstaVM,
980
+ InstaVMError,
981
+ NetworkError,
982
+ QuotaExceededError,
983
+ RateLimitError,
984
+ SessionError
985
+ });
986
+ //# sourceMappingURL=index.js.map