@forestadmin/agent 1.79.5 → 1.80.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.
@@ -6,9 +6,13 @@ import BaseRoute from '../base-route';
6
6
  export default class WorkflowExecutorProxyRoute extends BaseRoute {
7
7
  readonly type = RouteType.PrivateRoute;
8
8
  private readonly executorUrl;
9
+ protected readonly requestTimeoutMs: number;
9
10
  constructor(services: ForestAdminHttpDriverServices, options: AgentOptionsWithDefaults);
10
11
  setupRoutes(router: KoaRouter): void;
11
12
  private handleProxy;
13
+ private buildTargetUrl;
14
+ private executorPath;
15
+ private forwardedHeaders;
12
16
  private forwardRequest;
13
17
  }
14
18
  //# sourceMappingURL=workflow-executor-proxy.d.ts.map
@@ -3,46 +3,94 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const datasource_toolkit_1 = require("@forestadmin/datasource-toolkit");
6
7
  const http_1 = require("http");
7
8
  const https_1 = require("https");
8
9
  const types_1 = require("../../types");
9
10
  const base_route_1 = __importDefault(require("../base-route"));
11
+ const AGENT_PREFIX = '/_internal/workflow-executions';
12
+ const EXECUTOR_PREFIX = '/runs';
13
+ // Never forwarded: hop-by-hop headers, Host (Node derives the executor's from the target URL),
14
+ // and length/encoding the HTTP client recomputes.
15
+ const SKIPPED_HEADERS = new Set([
16
+ 'connection',
17
+ 'keep-alive',
18
+ 'transfer-encoding',
19
+ 'upgrade',
20
+ 'te',
21
+ 'trailer',
22
+ 'proxy-authenticate',
23
+ 'proxy-authorization',
24
+ 'host',
25
+ 'content-length',
26
+ ]);
27
+ // Substrings that could let the wildcard escape EXECUTOR_PREFIX (traversal, encoded dots,
28
+ // backslash, null byte).
29
+ const UNSAFE_PATH_FRAGMENTS = ['..', '%2e', '%2E', '\\', '\0'];
30
+ const REQUEST_TIMEOUT_MS = 120000;
10
31
  class WorkflowExecutorProxyRoute extends base_route_1.default {
11
32
  constructor(services, options) {
12
33
  super(services, options);
13
34
  this.type = types_1.RouteType.PrivateRoute;
35
+ // Overridable so tests can exercise the timeout branch without waiting the full default.
36
+ this.requestTimeoutMs = REQUEST_TIMEOUT_MS;
14
37
  // Remove trailing slash for clean URL joining
15
38
  this.executorUrl = new URL(options.workflowExecutorUrl.replace(/\/+$/, ''));
16
39
  }
40
+ // Single catch-all: any sub-path/verb under AGENT_PREFIX is forwarded to EXECUTOR_PREFIX, so a
41
+ // new executor route needs no change here (PRD-567).
17
42
  setupRoutes(router) {
18
- router.get('/_internal/workflow-executions/:runId', this.handleProxy.bind(this));
19
- router.post('/_internal/workflow-executions/:runId/trigger', this.handleProxy.bind(this));
43
+ router.all(`${AGENT_PREFIX}/:path(.*)`, this.handleProxy.bind(this));
20
44
  }
21
45
  async handleProxy(context) {
22
- const { runId } = context.params;
23
- const isTrigger = context.method === 'POST';
24
- const qs = context.querystring ? `?${context.querystring}` : '';
25
- const executorRelativeUrl = isTrigger ? `/runs/${runId}/trigger${qs}` : `/runs/${runId}${qs}`;
26
- const targetUrl = new URL(executorRelativeUrl, this.executorUrl);
27
- const forwardedHeaders = {
28
- authorization: context.request.header.authorization,
29
- cookie: context.request.header.cookie,
30
- };
31
- const response = await this.forwardRequest(context.method, targetUrl, context.request.body, forwardedHeaders);
46
+ const targetUrl = this.buildTargetUrl(context);
47
+ const response = await this.forwardRequest({
48
+ method: context.method,
49
+ url: targetUrl,
50
+ // Raw body forwarded verbatim (set by @koa/bodyparser); undefined for GET.
51
+ body: context.method === 'GET' ? undefined : context.request.rawBody,
52
+ headers: this.forwardedHeaders(context),
53
+ });
32
54
  context.response.status = response.status;
55
+ // Forward every executor response header (minus hop-by-hop) so new executor headers never
56
+ // require an agent change (PRD-567: zero breaking, the agent stays a transparent proxy).
57
+ for (const [name, value] of Object.entries(response.headers)) {
58
+ if (value !== undefined && !SKIPPED_HEADERS.has(name.toLowerCase())) {
59
+ context.response.set(name, value);
60
+ }
61
+ }
33
62
  context.response.body = response.body;
34
63
  }
35
- forwardRequest(method, url, body, forwardedHeaders = {}) {
64
+ buildTargetUrl(context) {
65
+ const path = this.executorPath(String(context.params.path ?? ''));
66
+ const qs = context.querystring ? `?${context.querystring}` : '';
67
+ return new URL(`${path}${qs}`, this.executorUrl);
68
+ }
69
+ // Security boundary: the wildcard can only ever map into EXECUTOR_PREFIX. Reject anything that
70
+ // could escape it so executor routes outside EXECUTOR_PREFIX stay unreachable through the proxy.
71
+ executorPath(wildcard) {
72
+ const unsafe = wildcard === '' ||
73
+ wildcard.startsWith('/') ||
74
+ UNSAFE_PATH_FRAGMENTS.some(fragment => wildcard.includes(fragment));
75
+ if (unsafe)
76
+ throw new datasource_toolkit_1.NotFoundError('Invalid workflow executor path');
77
+ return `${EXECUTOR_PREFIX}/${wildcard}`;
78
+ }
79
+ // Forwards every client header except the ones in SKIPPED_HEADERS.
80
+ forwardedHeaders(context) {
81
+ const headers = {};
82
+ for (const [name, value] of Object.entries(context.request.headers)) {
83
+ if (value !== undefined && !SKIPPED_HEADERS.has(name.toLowerCase())) {
84
+ headers[name] = value;
85
+ }
86
+ }
87
+ return headers;
88
+ }
89
+ forwardRequest(request) {
90
+ const { method, url, body, headers } = request;
36
91
  const requestFn = url.protocol === 'https:' ? https_1.request : http_1.request;
37
- const headers = { 'Content-Type': 'application/json' };
38
- // Forward the caller's auth so the executor's JWT middleware can validate it.
39
- // Agent and executor share the same FOREST_AUTH_SECRET so the token is valid on both.
40
- if (forwardedHeaders.authorization)
41
- headers.Authorization = forwardedHeaders.authorization;
42
- if (forwardedHeaders.cookie)
43
- headers.Cookie = forwardedHeaders.cookie;
44
92
  return new Promise((resolve, reject) => {
45
- const req = requestFn(url, { method, headers }, res => {
93
+ const req = requestFn(url, { method, headers, timeout: this.requestTimeoutMs }, res => {
46
94
  const chunks = [];
47
95
  res.on('data', chunk => chunks.push(chunk));
48
96
  res.on('end', () => {
@@ -54,17 +102,21 @@ class WorkflowExecutorProxyRoute extends base_route_1.default {
54
102
  catch {
55
103
  parsed = raw;
56
104
  }
57
- resolve({ status: res.statusCode ?? types_1.HttpCode.InternalServerError, body: parsed });
105
+ resolve({
106
+ status: res.statusCode ?? types_1.HttpCode.InternalServerError,
107
+ body: parsed,
108
+ headers: res.headers,
109
+ });
58
110
  });
59
111
  res.on('error', reject);
60
112
  });
61
113
  req.on('error', reject);
62
- if (body && method !== 'GET') {
63
- req.write(JSON.stringify(body));
64
- }
114
+ req.on('timeout', () => req.destroy(new Error('Workflow executor request timed out')));
115
+ if (body !== undefined && method !== 'GET')
116
+ req.write(body);
65
117
  req.end();
66
118
  });
67
119
  }
68
120
  }
69
121
  exports.default = WorkflowExecutorProxyRoute;
70
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ya2Zsb3ctZXhlY3V0b3ItcHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcm91dGVzL3dvcmtmbG93L3dvcmtmbG93LWV4ZWN1dG9yLXByb3h5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBS0EsK0JBQThDO0FBQzlDLGlDQUFnRDtBQUVoRCx1Q0FBa0Q7QUFDbEQsK0RBQXNDO0FBT3RDLE1BQXFCLDBCQUEyQixTQUFRLG9CQUFTO0lBSS9ELFlBQVksUUFBdUMsRUFBRSxPQUFpQztRQUNwRixLQUFLLENBQUMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBSmxCLFNBQUksR0FBRyxpQkFBUyxDQUFDLFlBQVksQ0FBQztRQUtyQyw4Q0FBOEM7UUFDOUMsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLEdBQUcsQ0FBQyxPQUFPLENBQUMsbUJBQW1CLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQzlFLENBQUM7SUFFRCxXQUFXLENBQUMsTUFBaUI7UUFDM0IsTUFBTSxDQUFDLEdBQUcsQ0FBQyx1Q0FBdUMsRUFBRSxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQ2pGLE1BQU0sQ0FBQyxJQUFJLENBQUMsK0NBQStDLEVBQUUsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUM1RixDQUFDO0lBRU8sS0FBSyxDQUFDLFdBQVcsQ0FBQyxPQUFnQjtRQUN4QyxNQUFNLEVBQUUsS0FBSyxFQUFFLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQztRQUNqQyxNQUFNLFNBQVMsR0FBRyxPQUFPLENBQUMsTUFBTSxLQUFLLE1BQU0sQ0FBQztRQUM1QyxNQUFNLEVBQUUsR0FBRyxPQUFPLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxJQUFJLE9BQU8sQ0FBQyxXQUFXLEVBQUUsQ0FBQyxD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
122
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ya2Zsb3ctZXhlY3V0b3ItcHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcm91dGVzL3dvcmtmbG93L3dvcmtmbG93LWV4ZWN1dG9yLXByb3h5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBTUEsd0VBQWdFO0FBQ2hFLCtCQUE4QztBQUM5QyxpQ0FBZ0Q7QUFFaEQsdUNBQWtEO0FBQ2xELCtEQUFzQztBQUV0QyxNQUFNLFlBQVksR0FBRyxnQ0FBZ0MsQ0FBQztBQUN0RCxNQUFNLGVBQWUsR0FBRyxPQUFPLENBQUM7QUFDaEMsK0ZBQStGO0FBQy9GLGtEQUFrRDtBQUNsRCxNQUFNLGVBQWUsR0FBRyxJQUFJLEdBQUcsQ0FBQztJQUM5QixZQUFZO0lBQ1osWUFBWTtJQUNaLG1CQUFtQjtJQUNuQixTQUFTO0lBQ1QsSUFBSTtJQUNKLFNBQVM7SUFDVCxvQkFBb0I7SUFDcEIscUJBQXFCO0lBQ3JCLE1BQU07SUFDTixnQkFBZ0I7Q0FDakIsQ0FBQyxDQUFDO0FBQ0gsMEZBQTBGO0FBQzFGLHlCQUF5QjtBQUN6QixNQUFNLHFCQUFxQixHQUFHLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDO0FBQy9ELE1BQU0sa0JBQWtCLEdBQUcsTUFBTyxDQUFDO0FBRW5DLE1BQXFCLDBCQUEyQixTQUFRLG9CQUFTO0lBTS9ELFlBQVksUUFBdUMsRUFBRSxPQUFpQztRQUNwRixLQUFLLENBQUMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBTmxCLFNBQUksR0FBRyxpQkFBUyxDQUFDLFlBQVksQ0FBQztRQUV2Qyx5RkFBeUY7UUFDdEUscUJBQWdCLEdBQVcsa0JBQWtCLENBQUM7UUFJL0QsOENBQThDO1FBQzlDLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxHQUFHLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsRUFB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
@@ -3,10 +3,12 @@ import type { Collection } from '@forestadmin/datasource-toolkit';
3
3
  import type { ForestServerCollection } from '@forestadmin/forestadmin-client';
4
4
  export default class SchemaGeneratorCollection {
5
5
  private readonly schemaGeneratorActions;
6
+ private readonly useUnsafeActionEndpoint;
6
7
  constructor(options: AgentOptionsWithDefaults);
7
8
  /** Build forest-server schema for a collection */
8
9
  buildSchema(collection: Collection): Promise<ForestServerCollection>;
9
10
  private buildActions;
11
+ private static assertNoActionSlugCollision;
10
12
  private static buildFields;
11
13
  private static buildSegments;
12
14
  }
@@ -10,6 +10,7 @@ const generator_segments_1 = __importDefault(require("./generator-segments"));
10
10
  class SchemaGeneratorCollection {
11
11
  constructor(options) {
12
12
  this.schemaGeneratorActions = new generator_actions_1.default(options);
13
+ this.useUnsafeActionEndpoint = options.useUnsafeActionEndpoint;
13
14
  }
14
15
  /** Build forest-server schema for a collection */
15
16
  async buildSchema(collection) {
@@ -28,9 +29,23 @@ class SchemaGeneratorCollection {
28
29
  };
29
30
  }
30
31
  buildActions(collection) {
31
- return Promise.all(Object.keys(collection.schema.actions)
32
- .sort()
33
- .map(name => this.schemaGeneratorActions.buildSchema(collection, name)));
32
+ const names = Object.keys(collection.schema.actions).sort();
33
+ if (this.useUnsafeActionEndpoint) {
34
+ SchemaGeneratorCollection.assertNoActionSlugCollision(collection.name, names);
35
+ }
36
+ return Promise.all(names.map(name => this.schemaGeneratorActions.buildSchema(collection, name)));
37
+ }
38
+ static assertNoActionSlugCollision(collectionName, names) {
39
+ const nameBySlug = new Map();
40
+ names.forEach(name => {
41
+ const slug = generator_actions_1.default.getActionSlug(name);
42
+ const existing = nameBySlug.get(slug);
43
+ if (existing) {
44
+ throw new Error(`Actions "${existing}" and "${name}" on collection "${collectionName}" resolve to the ` +
45
+ `same endpoint slug "${slug}". Rename one of them or disable useUnsafeActionEndpoint.`);
46
+ }
47
+ nameBySlug.set(slug, name);
48
+ });
34
49
  }
35
50
  static buildFields(collection) {
36
51
  // Do not export foreign keys as those will be edited using the many to one relationship.
@@ -48,4 +63,4 @@ class SchemaGeneratorCollection {
48
63
  }
49
64
  }
50
65
  exports.default = SchemaGeneratorCollection;
51
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZXJhdG9yLWNvbGxlY3Rpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvdXRpbHMvZm9yZXN0LXNjaGVtYS9nZW5lcmF0b3ItY29sbGVjdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQVNBLHdFQUE4RDtBQUU5RCw0RUFBeUQ7QUFDekQsMEVBQXVEO0FBQ3ZELDhFQUEyRDtBQUUzRCxNQUFxQix5QkFBeUI7SUFHNUMsWUFBWSxPQUFpQztRQUMzQyxJQUFJLENBQUMsc0JBQXNCLEdBQUcsSUFBSSwyQkFBc0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNwRSxDQUFDO0lBRUQsa0RBQWtEO0lBQ2xELEtBQUssQ0FBQyxXQUFXLENBQUMsVUFBc0I7UUFDdEMsT0FBTztZQUNMLE9BQU8sRUFBRSxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsVUFBVSxDQUFDO1lBQzVDLE1BQU0sRUFBRSx5QkFBeUIsQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDO1lBQ3pELElBQUksRUFBRSxJQUFJO1lBQ1YsV0FBVyxFQUFFLElBQUk7WUFDakIsVUFBVSxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxLQUFLLENBQ3ZELEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksS0FBSyxRQUFRLElBQUksS0FBSyxDQUFDLFVBQVUsQ0FDckQ7WUFDRCxZQUFZLEVBQUUsVUFBVSxDQUFDLE1BQU0sQ0FBQyxVQUFVO1lBQzFDLFNBQVMsRUFBRSxLQUFLO1lBQ2hCLElBQUksRUFBRSxVQUFVLENBQUMsSUFBSTtZQUNyQixvQkFBb0IsRUFBRSxLQUFLO1lBQzNCLGNBQWMsRUFBRSxNQUFNO1lBQ3RCLFFBQVEsRUFBRSx5QkFBeUIsQ0FBQyxhQUFhLENBQUMsVUFBVSxDQUFDO1NBQzlELENBQUM7SUFDSixDQUFDO0lBRU8sWUFBWSxDQUFDLFVBQXNCO1FBQ3pDLE9BQU8sT0FBTyxDQUFDLEdBQUcsQ0FDaEIsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQzthQUNuQyxJQUFJLEVBQUU7YUFDTixHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsc0JBQXNCLENBQUMsV0FBVyxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUMxRSxDQUFDO0lBQ0osQ0FBQztJQUVPLE1BQU0sQ0FBQyxXQUFXLENBQUMsVUFBc0I7UUFDL0MseUZBQXlGO1FBQ3pGLDhGQUE4RjtRQUM5RixPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUM7YUFDekMsTUFBTSxDQUNMLElBQUksQ0FBQyxFQUFFLENBQ0wsZ0NBQVcsQ0FBQyxZQUFZLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUM7WUFDakQsQ0FBQyxnQ0FBVyxDQUFDLFlBQVksQ0FBQyxVQUFVLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUNyRDthQUNBLElBQUksRUFBRTthQUNOLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLDBCQUFxQixDQUFDLFdBQVcsQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUN0RSxDQUFDO0lBRU8sTUFBTSxDQUFDLGFBQWEsQ0FBQyxVQUFzQjtRQUNqRCxPQUFPLFVBQVUsQ0FBQyxNQUFNLENBQUMsUUFBUTthQUM5QixJQUFJLEVBQUU7YUFDTixHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyw0QkFBdUIsQ0FBQyxXQUFXLENBQUMsVUFBVSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7SUFDeEUsQ0FBQztDQUNGO0FBcERELDRDQW9EQyJ9
66
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZXJhdG9yLWNvbGxlY3Rpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvdXRpbHMvZm9yZXN0LXNjaGVtYS9nZW5lcmF0b3ItY29sbGVjdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQVNBLHdFQUE4RDtBQUU5RCw0RUFBeUQ7QUFDekQsMEVBQXVEO0FBQ3ZELDhFQUEyRDtBQUUzRCxNQUFxQix5QkFBeUI7SUFJNUMsWUFBWSxPQUFpQztRQUMzQyxJQUFJLENBQUMsc0JBQXNCLEdBQUcsSUFBSSwyQkFBc0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNsRSxJQUFJLENBQUMsdUJBQXVCLEdBQUcsT0FBTyxDQUFDLHVCQUF1QixDQUFDO0lBQ2pFLENBQUM7SUFFRCxrREFBa0Q7SUFDbEQsS0FBSyxDQUFDLFdBQVcsQ0FBQyxVQUFzQjtRQUN0QyxPQUFPO1lBQ0wsT0FBTyxFQUFFLE1BQU0sSUFBSSxDQUFDLFlBQVksQ0FBQyxVQUFVLENBQUM7WUFDNUMsTUFBTSxFQUFFLHlCQUF5QixDQUFDLFdBQVcsQ0FBQyxVQUFVLENBQUM7WUFDekQsSUFBSSxFQUFFLElBQUk7WUFDVixXQUFXLEVBQUUsSUFBSTtZQUNqQixVQUFVLEVBQUUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEtBQUssQ0FDdkQsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsVUFBVSxDQUNyRDtZQUNELFlBQVksRUFBRSxVQUFVLENBQUMsTUFBTSxDQUFDLFVBQVU7WUFDMUMsU0FBUyxFQUFFLEtBQUs7WUFDaEIsSUFBSSxFQUFFLFVBQVUsQ0FBQyxJQUFJO1lBQ3JCLG9CQUFvQixFQUFFLEtBQUs7WUFDM0IsY0FBYyxFQUFFLE1BQU07WUFDdEIsUUFBUSxFQUFFLHlCQUF5QixDQUFDLGFBQWEsQ0FBQyxVQUFVLENBQUM7U0FDOUQsQ0FBQztJQUNKLENBQUM7SUFFTyxZQUFZLENBQUMsVUFBc0I7UUFDekMsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO1FBRTVELElBQUksSUFBSSxDQUFDLHVCQUF1QixFQUFFLENBQUM7WUFDakMseUJBQXlCLENBQUMsMkJBQTJCLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQztRQUNoRixDQUFDO1FBRUQsT0FBTyxPQUFPLENBQUMsR0FBRyxDQUNoQixLQUFLLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLHNCQUFzQixDQUFDLFdBQVcsQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FDN0UsQ0FBQztJQUNKLENBQUM7SUFFTyxNQUFNLENBQUMsMkJBQTJCLENBQUMsY0FBc0IsRUFBRSxLQUFlO1FBQ2hGLE1BQU0sVUFBVSxHQUFHLElBQUksR0FBRyxFQUFrQixDQUFDO1FBRTdDLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUU7WUFDbkIsTUFBTSxJQUFJLEdBQUcsMkJBQXNCLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ3hELE1BQU0sUUFBUSxHQUFHLFVBQVUsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7WUFFdEMsSUFBSSxRQUFRLEVBQUUsQ0FBQztnQkFDYixNQUFNLElBQUksS0FBSyxDQUNiLFlBQVksUUFBUSxVQUFVLElBQUksb0JBQW9CLGNBQWMsbUJBQW1CO29CQUNyRix1QkFBdUIsSUFBSSwyREFBMkQsQ0FDekYsQ0FBQztZQUNKLENBQUM7WUFFRCxVQUFVLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztRQUM3QixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFTyxNQUFNLENBQUMsV0FBVyxDQUFDLFVBQXNCO1FBQy9DLHlGQUF5RjtRQUN6Riw4RkFBOEY7UUFDOUYsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDO2FBQ3pDLE1BQU0sQ0FDTCxJQUFJLENBQUMsRUFBRSxDQUNMLGdDQUFXLENBQUMsWUFBWSxDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDO1lBQ2pELENBQUMsZ0NBQVcsQ0FBQyxZQUFZLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FDckQ7YUFDQSxJQUFJLEVBQUU7YUFDTixHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQywwQkFBcUIsQ0FBQyxXQUFXLENBQUMsVUFBVSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7SUFDdEUsQ0FBQztJQUVPLE1BQU0sQ0FBQyxhQUFhLENBQUMsVUFBc0I7UUFDakQsT0FBTyxVQUFVLENBQUMsTUFBTSxDQUFDLFFBQVE7YUFDOUIsSUFBSSxFQUFFO2FBQ04sR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsNEJBQXVCLENBQUMsV0FBVyxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQ3hFLENBQUM7Q0FDRjtBQTVFRCw0Q0E0RUMifQ==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forestadmin/agent",
3
- "version": "1.79.5",
3
+ "version": "1.80.0",
4
4
  "main": "dist/index.js",
5
5
  "license": "GPL-3.0",
6
6
  "publishConfig": {