@crawlee/browser-pool 4.0.0-beta.7 → 4.0.0-beta.70
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/README.md +17 -13
- package/abstract-classes/browser-controller.d.ts +67 -7
- package/abstract-classes/browser-controller.d.ts.map +1 -1
- package/abstract-classes/browser-controller.js +6 -9
- package/abstract-classes/browser-controller.js.map +1 -1
- package/abstract-classes/browser-plugin.d.ts +37 -2
- package/abstract-classes/browser-plugin.d.ts.map +1 -1
- package/abstract-classes/browser-plugin.js +65 -6
- package/abstract-classes/browser-plugin.js.map +1 -1
- package/anonymize-proxy.d.ts +5 -1
- package/anonymize-proxy.d.ts.map +1 -1
- package/anonymize-proxy.js +9 -4
- package/anonymize-proxy.js.map +1 -1
- package/browser-pool.d.ts +88 -13
- package/browser-pool.d.ts.map +1 -1
- package/browser-pool.js +139 -25
- package/browser-pool.js.map +1 -1
- package/fingerprinting/hooks.d.ts.map +1 -1
- package/fingerprinting/hooks.js +24 -8
- package/fingerprinting/hooks.js.map +1 -1
- package/index.d.ts +14 -6
- package/index.d.ts.map +1 -1
- package/index.js +5 -2
- package/index.js.map +1 -1
- package/launch-context.d.ts +19 -2
- package/launch-context.d.ts.map +1 -1
- package/launch-context.js +11 -3
- package/launch-context.js.map +1 -1
- package/package.json +7 -8
- package/playwright/playwright-browser.d.ts.map +1 -1
- package/playwright/playwright-browser.js.map +1 -1
- package/playwright/playwright-controller.d.ts.map +1 -1
- package/playwright/playwright-controller.js +20 -9
- package/playwright/playwright-controller.js.map +1 -1
- package/playwright/playwright-plugin.d.ts +6 -0
- package/playwright/playwright-plugin.d.ts.map +1 -1
- package/playwright/playwright-plugin.js +27 -3
- package/playwright/playwright-plugin.js.map +1 -1
- package/puppeteer/puppeteer-controller.d.ts.map +1 -1
- package/puppeteer/puppeteer-controller.js +11 -8
- package/puppeteer/puppeteer-controller.js.map +1 -1
- package/puppeteer/puppeteer-plugin.d.ts +3 -0
- package/puppeteer/puppeteer-plugin.d.ts.map +1 -1
- package/puppeteer/puppeteer-plugin.js +81 -43
- package/puppeteer/puppeteer-plugin.js.map +1 -1
- package/remote-browser-pool.d.ts +173 -0
- package/remote-browser-pool.d.ts.map +1 -0
- package/remote-browser-pool.js +192 -0
- package/remote-browser-pool.js.map +1 -0
- package/remote-browser-provider.d.ts +84 -0
- package/remote-browser-provider.d.ts.map +1 -0
- package/remote-browser-provider.js +68 -0
- package/remote-browser-provider.js.map +1 -0
- package/utils.d.ts +7 -0
- package/utils.d.ts.map +1 -1
- package/utils.js +19 -0
- package/utils.js.map +1 -1
- package/logger.d.ts +0 -3
- package/logger.d.ts.map +0 -1
- package/logger.js +0 -5
- package/logger.js.map +0 -1
- package/tsconfig.build.tsbuildinfo +0 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base class for remote browser service providers.
|
|
3
|
+
*
|
|
4
|
+
* Implement this class to encapsulate the lifecycle of a remote browser session
|
|
5
|
+
* (creation, connection URL resolution, and cleanup). {@link RemoteBrowserPool}
|
|
6
|
+
* calls {@link connect} once per browser launch and {@link release} when the browser
|
|
7
|
+
* closes, crashes, the pool is destroyed, or the connection fails during launch.
|
|
8
|
+
*
|
|
9
|
+
* Pass the provider instance as the `endpoint` of a {@link RemoteBrowserPool}, then
|
|
10
|
+
* hand the pool to a crawler via its `browserPool` option:
|
|
11
|
+
*
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const browserPool = new RemoteBrowserPool({
|
|
14
|
+
* browserPlugins: [new PlaywrightPlugin(playwright.chromium)],
|
|
15
|
+
* endpoint: new MyProvider(),
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* const crawler = new PlaywrightCrawler({ browserPool });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* **Example — simple static endpoint (e.g. Browserless):**
|
|
22
|
+
* ```typescript
|
|
23
|
+
* class BrowserlessProvider extends RemoteBrowserProvider {
|
|
24
|
+
* maxOpenBrowsers = 2; // respect the service's concurrent session limit
|
|
25
|
+
*
|
|
26
|
+
* async connect() {
|
|
27
|
+
* return { url: `wss://production-sfo.browserless.io?token=${token}` };
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* **Example — session lifecycle with concurrency limit (e.g. Browserbase):**
|
|
33
|
+
* ```typescript
|
|
34
|
+
* class BrowserbaseProvider extends RemoteBrowserProvider<{ id: string }> {
|
|
35
|
+
* maxOpenBrowsers = 2; // respect the service's concurrent session limit
|
|
36
|
+
*
|
|
37
|
+
* async connect({ proxyUrl } = {}) {
|
|
38
|
+
* const session = await createSession(apiKey, projectId, {
|
|
39
|
+
* proxies: proxyUrl ? [{ type: 'external', server: proxyUrl }] : undefined,
|
|
40
|
+
* });
|
|
41
|
+
* return { url: session.connectUrl, context: { id: session.id } };
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* async release(context: { id: string }) {
|
|
45
|
+
* await releaseSession(apiKey, context.id);
|
|
46
|
+
* }
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare abstract class RemoteBrowserProvider<TContext extends Record<string, unknown> = Record<string, unknown>> {
|
|
51
|
+
/**
|
|
52
|
+
* Maximum number of browsers that can be open at the same time.
|
|
53
|
+
* Set this to your remote service's concurrent session limit to avoid 429 errors.
|
|
54
|
+
*/
|
|
55
|
+
maxOpenBrowsers?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Called once per browser launch. Return the WebSocket/CDP endpoint URL
|
|
58
|
+
* and an optional `context` object that will be passed back to {@link release}.
|
|
59
|
+
*
|
|
60
|
+
* @param options.proxyUrl - The proxy URL resolved by Crawlee's proxy configuration
|
|
61
|
+
* for this browser session. Pass it to your remote service's proxy API if supported.
|
|
62
|
+
*/
|
|
63
|
+
abstract connect(options?: {
|
|
64
|
+
proxyUrl?: string;
|
|
65
|
+
}): Promise<{
|
|
66
|
+
url: string;
|
|
67
|
+
context?: TContext;
|
|
68
|
+
}> | {
|
|
69
|
+
url: string;
|
|
70
|
+
context?: TContext;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Called when the browser closes, crashes, the pool is destroyed, or the
|
|
74
|
+
* connection fails right after {@link connect} succeeds.
|
|
75
|
+
* Override this to clean up remote sessions, release API resources, etc.
|
|
76
|
+
*
|
|
77
|
+
* Errors thrown here are caught and logged as warnings — they never crash the crawler.
|
|
78
|
+
* Safe to assume this is called at most once per {@link connect} call.
|
|
79
|
+
*
|
|
80
|
+
* @param _context The same `context` object returned by {@link connect}.
|
|
81
|
+
*/
|
|
82
|
+
release(_context: TContext): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=remote-browser-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-browser-provider.d.ts","sourceRoot":"","sources":["../src/remote-browser-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,8BAAsB,qBAAqB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1G;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,QAAQ,CAAA;KAAE,CAAC,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,QAAQ,CAAA;KAAE;IAEtF;;;;;;;;;OASG;IACG,OAAO,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CACnD"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base class for remote browser service providers.
|
|
3
|
+
*
|
|
4
|
+
* Implement this class to encapsulate the lifecycle of a remote browser session
|
|
5
|
+
* (creation, connection URL resolution, and cleanup). {@link RemoteBrowserPool}
|
|
6
|
+
* calls {@link connect} once per browser launch and {@link release} when the browser
|
|
7
|
+
* closes, crashes, the pool is destroyed, or the connection fails during launch.
|
|
8
|
+
*
|
|
9
|
+
* Pass the provider instance as the `endpoint` of a {@link RemoteBrowserPool}, then
|
|
10
|
+
* hand the pool to a crawler via its `browserPool` option:
|
|
11
|
+
*
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const browserPool = new RemoteBrowserPool({
|
|
14
|
+
* browserPlugins: [new PlaywrightPlugin(playwright.chromium)],
|
|
15
|
+
* endpoint: new MyProvider(),
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* const crawler = new PlaywrightCrawler({ browserPool });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* **Example — simple static endpoint (e.g. Browserless):**
|
|
22
|
+
* ```typescript
|
|
23
|
+
* class BrowserlessProvider extends RemoteBrowserProvider {
|
|
24
|
+
* maxOpenBrowsers = 2; // respect the service's concurrent session limit
|
|
25
|
+
*
|
|
26
|
+
* async connect() {
|
|
27
|
+
* return { url: `wss://production-sfo.browserless.io?token=${token}` };
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* **Example — session lifecycle with concurrency limit (e.g. Browserbase):**
|
|
33
|
+
* ```typescript
|
|
34
|
+
* class BrowserbaseProvider extends RemoteBrowserProvider<{ id: string }> {
|
|
35
|
+
* maxOpenBrowsers = 2; // respect the service's concurrent session limit
|
|
36
|
+
*
|
|
37
|
+
* async connect({ proxyUrl } = {}) {
|
|
38
|
+
* const session = await createSession(apiKey, projectId, {
|
|
39
|
+
* proxies: proxyUrl ? [{ type: 'external', server: proxyUrl }] : undefined,
|
|
40
|
+
* });
|
|
41
|
+
* return { url: session.connectUrl, context: { id: session.id } };
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* async release(context: { id: string }) {
|
|
45
|
+
* await releaseSession(apiKey, context.id);
|
|
46
|
+
* }
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export class RemoteBrowserProvider {
|
|
51
|
+
/**
|
|
52
|
+
* Maximum number of browsers that can be open at the same time.
|
|
53
|
+
* Set this to your remote service's concurrent session limit to avoid 429 errors.
|
|
54
|
+
*/
|
|
55
|
+
maxOpenBrowsers;
|
|
56
|
+
/**
|
|
57
|
+
* Called when the browser closes, crashes, the pool is destroyed, or the
|
|
58
|
+
* connection fails right after {@link connect} succeeds.
|
|
59
|
+
* Override this to clean up remote sessions, release API resources, etc.
|
|
60
|
+
*
|
|
61
|
+
* Errors thrown here are caught and logged as warnings — they never crash the crawler.
|
|
62
|
+
* Safe to assume this is called at most once per {@link connect} call.
|
|
63
|
+
*
|
|
64
|
+
* @param _context The same `context` object returned by {@link connect}.
|
|
65
|
+
*/
|
|
66
|
+
async release(_context) { }
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=remote-browser-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-browser-provider.js","sourceRoot":"","sources":["../src/remote-browser-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,OAAgB,qBAAqB;IACvC;;;OAGG;IACH,eAAe,CAAU;IAazB;;;;;;;;;OASG;IACH,KAAK,CAAC,OAAO,CAAC,QAAkB,IAAkB,CAAC;CACtD"}
|
package/utils.d.ts
CHANGED
|
@@ -3,6 +3,13 @@ import type { PlaywrightPlugin } from './playwright/playwright-plugin.js';
|
|
|
3
3
|
import type { PuppeteerPlugin } from './puppeteer/puppeteer-plugin.js';
|
|
4
4
|
export type UnwrapPromise<T> = T extends PromiseLike<infer R> ? UnwrapPromise<R> : T;
|
|
5
5
|
export declare function noop(..._args: unknown[]): void;
|
|
6
|
+
/**
|
|
7
|
+
* Strips secrets from a URL so it can be safely included in logs and error messages. Removes userinfo
|
|
8
|
+
* credentials and the entire query string and fragment — remote browser services routinely carry tokens
|
|
9
|
+
* there (e.g. Browserless `?token=…`), and we can't tell which params are sensitive. Keeps the
|
|
10
|
+
* protocol, host, port, and path, which are enough to diagnose connection failures.
|
|
11
|
+
*/
|
|
12
|
+
export declare function sanitizeEndpointForLog(endpoint: string): string;
|
|
6
13
|
/**
|
|
7
14
|
* This is required when using optional dependencies.
|
|
8
15
|
* Importing a type gives `any`, but `Parameters<any>` gives `unknown[]` instead of `any`
|
package/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAEvE,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,SAAS,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAErF,wBAAgB,IAAI,CAAC,GAAG,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAAG;AAElD;;;GAGG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,IAAI,OAAO,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;AAEpH,MAAM,MAAM,uBAAuB,CAE/B,KAAK,SAAS,SAAS,OAAO,EAAE,EAEhC,MAAM,SAAS,aAAa,EAAE,GAAG,EAAE,IACnC,KAAK,SAAS,SAAS,CAAC,MAAM,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,GAE1F,UAAU,SAAS,gBAAgB,GAE/B,uBAAuB,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,EAAE,gBAAgB,CAAC,CAAC,GAE5D,UAAU,SAAS,eAAe,GAEhC,uBAAuB,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,EAAE,eAAe,CAAC,CAAC,GAE3D,KAAK,GAEX,KAAK,SAAS,EAAE,GAEd,MAAM,GAEN,KAAK,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAEhC;IAAC,CAAC;CAAC,SAAS,CAAC,eAAe,GAAG,gBAAgB,CAAC,GAE5C,CAAC,EAAE,GAEH,KAAK,GAET,MAAM,CAAC"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAEvE,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,SAAS,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAErF,wBAAgB,IAAI,CAAC,GAAG,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAAG;AAElD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAW/D;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,IAAI,OAAO,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;AAEpH,MAAM,MAAM,uBAAuB,CAE/B,KAAK,SAAS,SAAS,OAAO,EAAE,EAEhC,MAAM,SAAS,aAAa,EAAE,GAAG,EAAE,IACnC,KAAK,SAAS,SAAS,CAAC,MAAM,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,GAE1F,UAAU,SAAS,gBAAgB,GAE/B,uBAAuB,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,EAAE,gBAAgB,CAAC,CAAC,GAE5D,UAAU,SAAS,eAAe,GAEhC,uBAAuB,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,EAAE,eAAe,CAAC,CAAC,GAE3D,KAAK,GAEX,KAAK,SAAS,EAAE,GAEd,MAAM,GAEN,KAAK,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAEhC;IAAC,CAAC;CAAC,SAAS,CAAC,eAAe,GAAG,gBAAgB,CAAC,GAE5C,CAAC,EAAE,GAEH,KAAK,GAET,MAAM,CAAC"}
|
package/utils.js
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
1
1
|
export function noop(..._args) { }
|
|
2
|
+
/**
|
|
3
|
+
* Strips secrets from a URL so it can be safely included in logs and error messages. Removes userinfo
|
|
4
|
+
* credentials and the entire query string and fragment — remote browser services routinely carry tokens
|
|
5
|
+
* there (e.g. Browserless `?token=…`), and we can't tell which params are sensitive. Keeps the
|
|
6
|
+
* protocol, host, port, and path, which are enough to diagnose connection failures.
|
|
7
|
+
*/
|
|
8
|
+
export function sanitizeEndpointForLog(endpoint) {
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(endpoint);
|
|
11
|
+
url.username = '';
|
|
12
|
+
url.password = '';
|
|
13
|
+
url.search = '';
|
|
14
|
+
url.hash = '';
|
|
15
|
+
return url.toString();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return '<invalid URL>';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
2
21
|
//# sourceMappingURL=utils.js.map
|
package/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,IAAI,CAAC,GAAG,KAAgB,IAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,IAAI,CAAC,GAAG,KAAgB,IAAS,CAAC;AAElD;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAgB;IACnD,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC9B,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC;QAClB,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC;QAClB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC;QAChB,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,eAAe,CAAC;IAC3B,CAAC;AACL,CAAC"}
|
package/logger.d.ts
DELETED
package/logger.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,GAAG,0BAEd,CAAC"}
|
package/logger.js
DELETED
package/logger.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,YAAY,CAAC;AAEpC,MAAM,CAAC,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC;IAChC,MAAM,EAAE,aAAa;CACxB,CAAC,CAAC"}
|