@dronedeploy/rocos-js-sdk 3.0.11 → 3.0.13

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.
Files changed (73) hide show
  1. package/cjs/api/StreamRegister.d.ts +3 -3
  2. package/cjs/api/StreamRegister.js +6 -6
  3. package/cjs/api/streams/telemetry/TelemetryStreamAbstract.js +8 -7
  4. package/cjs/helpers/kscript/Context.d.ts +14 -8
  5. package/cjs/helpers/kscript/Context.js +59 -22
  6. package/cjs/helpers/kscript/kscript.js +53 -1
  7. package/cjs/helpers/kscript/nodes/Identifier.js +1 -1
  8. package/cjs/helpers/kscript/nodes/Node.d.ts +0 -1
  9. package/cjs/helpers/kscript/nodes/Node.js +0 -1
  10. package/cjs/helpers/kscript/nodes/Program.js +8 -5
  11. package/cjs/helpers/kscript/nodes/VariableDeclaration.d.ts +8 -0
  12. package/cjs/helpers/kscript/nodes/VariableDeclaration.js +43 -0
  13. package/cjs/helpers/kscript/nodes/expressions/ArrowFunctionExpression.js +11 -4
  14. package/cjs/helpers/kscript/nodes/expressions/AssignmentExpression.d.ts +8 -0
  15. package/cjs/helpers/kscript/nodes/expressions/AssignmentExpression.js +33 -0
  16. package/cjs/helpers/kscript/nodes/expressions/BinaryExpression.d.ts +2 -0
  17. package/cjs/helpers/kscript/nodes/expressions/BinaryExpression.js +16 -0
  18. package/cjs/helpers/kscript/nodes/expressions/LogicalExpression.js +1 -1
  19. package/cjs/helpers/kscript/nodes/expressions/NewExpression.d.ts +9 -0
  20. package/cjs/helpers/kscript/nodes/expressions/NewExpression.js +44 -0
  21. package/cjs/helpers/kscript/nodes/expressions/index.d.ts +2 -1
  22. package/cjs/helpers/kscript/nodes/expressions/index.js +5 -3
  23. package/cjs/helpers/kscript/nodes/nodeTypes.d.ts +9 -1
  24. package/cjs/helpers/kscript/nodes/nodeTypes.js +9 -1
  25. package/cjs/helpers/kscript/nodes/statements/BlockStatement.d.ts +8 -0
  26. package/cjs/helpers/kscript/nodes/statements/BlockStatement.js +29 -0
  27. package/cjs/helpers/kscript/nodes/statements/ReturnStatement.d.ts +12 -0
  28. package/cjs/helpers/kscript/nodes/statements/ReturnStatement.js +33 -0
  29. package/cjs/helpers/kscript/nodes/statements/SwitchStatement.d.ts +9 -0
  30. package/cjs/helpers/kscript/nodes/statements/SwitchStatement.js +52 -0
  31. package/cjs/helpers/kscript/nodes/statements/index.d.ts +4 -0
  32. package/cjs/helpers/kscript/nodes/statements/index.js +14 -0
  33. package/cjs/services/BaseStreamService.js +1 -1
  34. package/cjs/services/WebRTCSignallingService.js +1 -2
  35. package/esm/api/StreamRegister.d.ts +3 -3
  36. package/esm/api/StreamRegister.js +6 -6
  37. package/esm/api/streams/telemetry/TelemetryStreamAbstract.js +8 -7
  38. package/esm/helpers/kscript/Context.d.ts +14 -8
  39. package/esm/helpers/kscript/Context.js +59 -22
  40. package/esm/helpers/kscript/kscript.js +53 -1
  41. package/esm/helpers/kscript/nodes/Identifier.js +1 -1
  42. package/esm/helpers/kscript/nodes/Node.d.ts +0 -1
  43. package/esm/helpers/kscript/nodes/Node.js +0 -1
  44. package/esm/helpers/kscript/nodes/Program.js +8 -5
  45. package/esm/helpers/kscript/nodes/VariableDeclaration.d.ts +8 -0
  46. package/esm/helpers/kscript/nodes/VariableDeclaration.js +37 -0
  47. package/esm/helpers/kscript/nodes/expressions/ArrowFunctionExpression.js +11 -4
  48. package/esm/helpers/kscript/nodes/expressions/AssignmentExpression.d.ts +8 -0
  49. package/esm/helpers/kscript/nodes/expressions/AssignmentExpression.js +27 -0
  50. package/esm/helpers/kscript/nodes/expressions/BinaryExpression.d.ts +2 -0
  51. package/esm/helpers/kscript/nodes/expressions/BinaryExpression.js +16 -0
  52. package/esm/helpers/kscript/nodes/expressions/LogicalExpression.js +1 -1
  53. package/esm/helpers/kscript/nodes/expressions/NewExpression.d.ts +9 -0
  54. package/esm/helpers/kscript/nodes/expressions/NewExpression.js +38 -0
  55. package/esm/helpers/kscript/nodes/expressions/index.d.ts +2 -1
  56. package/esm/helpers/kscript/nodes/expressions/index.js +2 -1
  57. package/esm/helpers/kscript/nodes/nodeTypes.d.ts +9 -1
  58. package/esm/helpers/kscript/nodes/nodeTypes.js +9 -1
  59. package/esm/helpers/kscript/nodes/statements/BlockStatement.d.ts +8 -0
  60. package/esm/helpers/kscript/nodes/statements/BlockStatement.js +23 -0
  61. package/esm/helpers/kscript/nodes/statements/ReturnStatement.d.ts +12 -0
  62. package/esm/helpers/kscript/nodes/statements/ReturnStatement.js +25 -0
  63. package/esm/helpers/kscript/nodes/statements/SwitchStatement.d.ts +9 -0
  64. package/esm/helpers/kscript/nodes/statements/SwitchStatement.js +46 -0
  65. package/esm/helpers/kscript/nodes/statements/index.d.ts +4 -0
  66. package/esm/helpers/kscript/nodes/statements/index.js +4 -0
  67. package/esm/services/BaseStreamService.js +1 -1
  68. package/esm/services/WebRTCSignallingService.js +1 -2
  69. package/package.json +1 -1
  70. /package/cjs/helpers/kscript/nodes/{expressions → statements}/ExpressionStatement.d.ts +0 -0
  71. /package/cjs/helpers/kscript/nodes/{expressions → statements}/ExpressionStatement.js +0 -0
  72. /package/esm/helpers/kscript/nodes/{expressions → statements}/ExpressionStatement.d.ts +0 -0
  73. /package/esm/helpers/kscript/nodes/{expressions → statements}/ExpressionStatement.js +0 -0
@@ -1,4 +1,5 @@
1
- import { ArrayExpression, ArrowFunctionExpression, BinaryExpression, CallExpression, ChainExpression, ConditionalExpression, ExpressionStatement, LogicalExpression, MemberExpression, ObjectExpression, UnaryExpression } from './expressions';
1
+ import { ArrayExpression, ArrowFunctionExpression, AssignmentExpression, BinaryExpression, CallExpression, ChainExpression, ConditionalExpression, LogicalExpression, MemberExpression, NewExpression, ObjectExpression, UnaryExpression } from './expressions';
2
+ import { BlockStatement, ExpressionStatement, ReturnStatement, SwitchStatement } from './statements';
2
3
  import { AnyNode } from 'acorn';
3
4
  import type Context from '../Context';
4
5
  import Identifier from './Identifier';
@@ -6,6 +7,7 @@ import Literal from './Literal';
6
7
  import Node from './Node';
7
8
  import Program from './Program';
8
9
  import TemplateLiteral from './TemplateLiteral';
10
+ import VariableDeclaration from './VariableDeclaration';
9
11
  type NodeConstructor = {
10
12
  new (node: AnyNode, scope: Context): Node<AnyNode>;
11
13
  };
@@ -27,6 +29,12 @@ declare const nodes: {
27
29
  readonly CallExpression: typeof CallExpression;
28
30
  readonly MemberExpression: typeof MemberExpression;
29
31
  readonly ChainExpression: typeof ChainExpression;
32
+ readonly NewExpression: typeof NewExpression;
33
+ readonly BlockStatement: typeof BlockStatement;
34
+ readonly ReturnStatement: typeof ReturnStatement;
35
+ readonly VariableDeclaration: typeof VariableDeclaration;
36
+ readonly AssignmentExpression: typeof AssignmentExpression;
37
+ readonly SwitchStatement: typeof SwitchStatement;
30
38
  };
31
39
  export type NodeType = keyof typeof nodes;
32
40
  declare const _default: NodeMap;
@@ -4,14 +4,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const expressions_1 = require("./expressions");
7
+ const statements_1 = require("./statements");
7
8
  const Identifier_1 = __importDefault(require("./Identifier"));
8
9
  const Literal_1 = __importDefault(require("./Literal"));
9
10
  const Program_1 = __importDefault(require("./Program"));
10
11
  const TemplateLiteral_1 = __importDefault(require("./TemplateLiteral"));
12
+ const VariableDeclaration_1 = __importDefault(require("./VariableDeclaration"));
11
13
  const nodes = {
12
14
  Program: Program_1.default,
13
15
  Literal: Literal_1.default,
14
- ExpressionStatement: expressions_1.ExpressionStatement,
16
+ ExpressionStatement: statements_1.ExpressionStatement,
15
17
  ArrayExpression: expressions_1.ArrayExpression,
16
18
  ObjectExpression: expressions_1.ObjectExpression,
17
19
  Identifier: Identifier_1.default,
@@ -25,5 +27,11 @@ const nodes = {
25
27
  CallExpression: expressions_1.CallExpression,
26
28
  MemberExpression: expressions_1.MemberExpression,
27
29
  ChainExpression: expressions_1.ChainExpression,
30
+ NewExpression: expressions_1.NewExpression,
31
+ BlockStatement: statements_1.BlockStatement,
32
+ ReturnStatement: statements_1.ReturnStatement,
33
+ VariableDeclaration: VariableDeclaration_1.default,
34
+ AssignmentExpression: expressions_1.AssignmentExpression,
35
+ SwitchStatement: statements_1.SwitchStatement,
28
36
  };
29
37
  exports.default = new Map(Object.entries(nodes));
@@ -0,0 +1,8 @@
1
+ import { AnyNode, BlockStatement } from 'acorn';
2
+ import Context from '../../Context';
3
+ import Node from '../Node';
4
+ export default class BlockStatementNode extends Node<BlockStatement> {
5
+ constructor(node: AnyNode, context: Context);
6
+ validate(validateChildren?: boolean): void;
7
+ run(): unknown;
8
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const Node_1 = __importDefault(require("../Node"));
7
+ const utils_1 = require("../../utils");
8
+ class BlockStatementNode extends Node_1.default {
9
+ constructor(node, context) {
10
+ super(node, context.newChild(), 'BlockStatement');
11
+ }
12
+ validate(validateChildren = false) {
13
+ if (!validateChildren) {
14
+ return;
15
+ }
16
+ for (const statement of this.node.body) {
17
+ (0, utils_1.construct)(statement, this.context).validate(true);
18
+ }
19
+ }
20
+ run() {
21
+ this.validate();
22
+ let result;
23
+ for (const statement of this.node.body) {
24
+ result = (0, utils_1.construct)(statement, this.context).run();
25
+ }
26
+ return result;
27
+ }
28
+ }
29
+ exports.default = BlockStatementNode;
@@ -0,0 +1,12 @@
1
+ import { AnyNode, ReturnStatement } from 'acorn';
2
+ import type Context from '../../Context';
3
+ import Node from '../Node';
4
+ export declare class ReturnStatementError extends Error {
5
+ readonly value: unknown;
6
+ constructor(value: unknown);
7
+ }
8
+ export default class ReturnStatementNode extends Node<ReturnStatement> {
9
+ constructor(node: AnyNode, context: Context);
10
+ validate(validateChildren?: boolean): void;
11
+ run(): unknown;
12
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ReturnStatementError = void 0;
7
+ const Node_1 = __importDefault(require("../Node"));
8
+ const utils_1 = require("../../utils");
9
+ class ReturnStatementError extends Error {
10
+ constructor(value) {
11
+ super('Illegal return statement');
12
+ this.value = value;
13
+ }
14
+ }
15
+ exports.ReturnStatementError = ReturnStatementError;
16
+ class ReturnStatementNode extends Node_1.default {
17
+ constructor(node, context) {
18
+ super(node, context, 'ReturnStatement');
19
+ }
20
+ validate(validateChildren = false) {
21
+ const arg = this.node.argument;
22
+ if (!validateChildren || arg === undefined || arg === null) {
23
+ return;
24
+ }
25
+ (0, utils_1.construct)(arg, this.context).validate(true);
26
+ }
27
+ run() {
28
+ this.validate();
29
+ const result = this.node.argument ? (0, utils_1.construct)(this.node.argument, this.context).run() : undefined;
30
+ throw new ReturnStatementError(result);
31
+ }
32
+ }
33
+ exports.default = ReturnStatementNode;
@@ -0,0 +1,9 @@
1
+ import { AnyNode, SwitchStatement } from 'acorn';
2
+ import Context from '../../Context';
3
+ import Node from '../Node';
4
+ export default class SwitchStatementNode extends Node<SwitchStatement> {
5
+ constructor(node: AnyNode, context: Context);
6
+ private test_match;
7
+ validate(validateChildren?: boolean): void;
8
+ run(): unknown;
9
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const Node_1 = __importDefault(require("../Node"));
7
+ const utils_1 = require("../../utils");
8
+ class SwitchStatementNode extends Node_1.default {
9
+ constructor(node, context) {
10
+ super(node, context, 'SwitchStatement');
11
+ }
12
+ test_match(test, discriminant) {
13
+ if (!test) {
14
+ return true;
15
+ }
16
+ const testValue = (0, utils_1.construct)(test, this.context).run();
17
+ return testValue === discriminant;
18
+ }
19
+ validate(validateChildren = false) {
20
+ if (!validateChildren) {
21
+ return;
22
+ }
23
+ (0, utils_1.construct)(this.node.discriminant, this.context).validate(true);
24
+ for (const c of this.node.cases) {
25
+ if (c.test) {
26
+ (0, utils_1.construct)(c.test, this.context).validate(true);
27
+ }
28
+ for (const s of c.consequent) {
29
+ (0, utils_1.construct)(s, this.context).validate(true);
30
+ }
31
+ }
32
+ }
33
+ run() {
34
+ this.validate();
35
+ const discriminant = (0, utils_1.construct)(this.node.discriminant, this.context).run();
36
+ let result;
37
+ let matched = false;
38
+ for (const c of this.node.cases) {
39
+ if (!matched && this.test_match(c.test, discriminant)) {
40
+ matched = true;
41
+ }
42
+ if (!matched) {
43
+ continue;
44
+ }
45
+ for (const s of c.consequent) {
46
+ result = (0, utils_1.construct)(s, this.context).run();
47
+ }
48
+ }
49
+ return result;
50
+ }
51
+ }
52
+ exports.default = SwitchStatementNode;
@@ -0,0 +1,4 @@
1
+ export { default as BlockStatement } from './BlockStatement';
2
+ export { default as ReturnStatement } from './ReturnStatement';
3
+ export { default as ExpressionStatement } from './ExpressionStatement';
4
+ export { default as SwitchStatement } from './SwitchStatement';
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SwitchStatement = exports.ExpressionStatement = exports.ReturnStatement = exports.BlockStatement = void 0;
7
+ var BlockStatement_1 = require("./BlockStatement");
8
+ Object.defineProperty(exports, "BlockStatement", { enumerable: true, get: function () { return __importDefault(BlockStatement_1).default; } });
9
+ var ReturnStatement_1 = require("./ReturnStatement");
10
+ Object.defineProperty(exports, "ReturnStatement", { enumerable: true, get: function () { return __importDefault(ReturnStatement_1).default; } });
11
+ var ExpressionStatement_1 = require("./ExpressionStatement");
12
+ Object.defineProperty(exports, "ExpressionStatement", { enumerable: true, get: function () { return __importDefault(ExpressionStatement_1).default; } });
13
+ var SwitchStatement_1 = require("./SwitchStatement");
14
+ Object.defineProperty(exports, "SwitchStatement", { enumerable: true, get: function () { return __importDefault(SwitchStatement_1).default; } });
@@ -44,7 +44,7 @@ class BaseStreamService {
44
44
  this.status = msg === models_1.SubscriberStatusEnum.STOPPED || msg === models_1.SubscriberStatusEnum.ALIVE;
45
45
  this.status$.next(msg);
46
46
  });
47
- await streamRegister.addStream(stream);
47
+ streamRegister.addStream(stream);
48
48
  await this.initStream(stream);
49
49
  }
50
50
  return { stream, isNew };
@@ -53,8 +53,7 @@ class WebRTCSignallingService {
53
53
  this.status = msg === SubscriberStatusEnum_1.SubscriberStatusEnum.STOPPED || msg === SubscriberStatusEnum_1.SubscriberStatusEnum.ALIVE;
54
54
  this.status$.next(msg);
55
55
  });
56
- // don't wait for these promises, they can be init after startup
57
- await streamRegister.addStream(stream);
56
+ streamRegister.addStream(stream);
58
57
  await this.initStream(stream);
59
58
  }
60
59
  return stream;
@@ -6,8 +6,8 @@ export declare class StreamRegister {
6
6
  private constructor();
7
7
  static getInstance(): StreamRegister;
8
8
  static getIdentifier($identifier: string, scope?: string): string;
9
- addStream(stream: IBaseStream): Promise<void>;
9
+ addStream(stream: IBaseStream): void;
10
10
  getStream(identifier: string): IBaseStream | undefined;
11
- removeStream(stream: IBaseStream): Promise<void>;
12
- removeAllStreams(): Promise<boolean>;
11
+ removeStream(stream: IBaseStream): void;
12
+ removeAllStreams(): boolean;
13
13
  }
@@ -14,10 +14,10 @@ export class StreamRegister {
14
14
  static getIdentifier($identifier, scope) {
15
15
  return `${$identifier}-${scope ?? ''}`;
16
16
  }
17
- async addStream(stream) {
17
+ addStream(stream) {
18
18
  try {
19
19
  if (this.teleStreams.has(stream.identifier)) {
20
- await this.removeStream(stream);
20
+ this.removeStream(stream);
21
21
  }
22
22
  this.teleStreams.set(stream.identifier, stream);
23
23
  }
@@ -34,9 +34,9 @@ export class StreamRegister {
34
34
  getStream(identifier) {
35
35
  return this.teleStreams.get(identifier);
36
36
  }
37
- async removeStream(stream) {
37
+ removeStream(stream) {
38
38
  try {
39
- await stream.stopStream();
39
+ stream.stopStream();
40
40
  this.teleStreams.delete(stream.identifier);
41
41
  }
42
42
  catch (e) {
@@ -49,10 +49,10 @@ export class StreamRegister {
49
49
  }
50
50
  }
51
51
  }
52
- async removeAllStreams() {
52
+ removeAllStreams() {
53
53
  try {
54
54
  for (const [, stream] of this.teleStreams.entries()) {
55
- await this.removeStream(stream);
55
+ this.removeStream(stream);
56
56
  }
57
57
  }
58
58
  catch (e) {
@@ -100,6 +100,14 @@ export class TelemetryStreamAbstract {
100
100
  const before = this.getSubscriptions();
101
101
  this.subscriptions.delete(params.uniqueId);
102
102
  const after = this.getSubscriptions();
103
+ // Do this before we send the telemetry request so that new subscriptions don't get this stream from the register while it is closing
104
+ if (!this.subscriptions.size) {
105
+ this.logger.info('No subscriptions remaining closing stream', params.uniqueId);
106
+ this.stopStream();
107
+ // self remove when no subscriptions are left
108
+ StreamRegister.getInstance().removeStream(this);
109
+ this.logger.info('Stream closed');
110
+ }
103
111
  // assign the current values from state after change
104
112
  this.callsignsLookup = new CallsignsLookup(Object.keys(after));
105
113
  this.sources = arrayUnique(Object.values(after).reduce((a, v) => a.concat(v), []));
@@ -113,13 +121,6 @@ export class TelemetryStreamAbstract {
113
121
  this.logger.error(`Failed to unsubscribe: ${err}`);
114
122
  }
115
123
  }
116
- if (!this.subscriptions.size) {
117
- this.logger.info('No subscriptions remaining closing stream', params.uniqueId);
118
- this.stopStream();
119
- // self remove when no subscriptions a re left
120
- void StreamRegister.getInstance().removeStream(this);
121
- this.logger.info('Stream closed');
122
- }
123
124
  }
124
125
  sendAcknowledgment(uid, status, noRetry) {
125
126
  return this.sendAcknowledgmentInternal(uid, status, noRetry);
@@ -7,19 +7,20 @@ import type { NodeType } from './nodes';
7
7
  * If the set is null, all properties are allowed.
8
8
  */
9
9
  export type PrototypeWhitelist = Map<unknown, Set<string> | null>;
10
- export type Scope = Map<string, unknown> | Record<string, unknown>;
10
+ export type Scope = Record<string, unknown> | Map<string, unknown>;
11
11
  export interface ContextOptions {
12
12
  nodeBlacklist?: Set<NodeType>;
13
13
  prototypeWhitelist?: PrototypeWhitelist;
14
14
  functionCallsAllowed?: boolean;
15
15
  }
16
16
  export default class Context {
17
- readonly scope: Map<string, unknown>;
18
- readonly isFunctionCallAllowed: boolean;
19
- private readonly options;
20
- constructor(scope?: Scope, options?: ContextOptions);
21
- isNodeAllowed(nodeType: AnyNode['type']): boolean;
22
- newChildContext(scope: Scope): Context;
17
+ private readonly parent;
18
+ private readonly scope;
19
+ private readonly config;
20
+ static create(scope?: Scope, options?: ContextOptions): Context;
21
+ private static buildMap;
22
+ private constructor();
23
+ newChild(scope?: Scope): Context;
23
24
  /** Returns true if the property is allowed to be accessed on the prototype.
24
25
  *
25
26
  * - If the prototype is not in the whitelist, the property is allowed only if it is an own property.
@@ -30,5 +31,10 @@ export default class Context {
30
31
  * @param ownProperty Whether the property is owned by the object it is being accessed on.
31
32
  */
32
33
  isPrototypeAllowed(prototype: unknown, property: string, ownProperty: boolean): boolean;
33
- private static buildMap;
34
+ isNodeAllowed(nodeType: AnyNode['type']): boolean;
35
+ get isFunctionCallAllowed(): boolean;
36
+ private getDescriptor;
37
+ get(key: string): unknown;
38
+ set(key: string, value: unknown): void;
39
+ declare(key: string, value: unknown, constant: boolean): void;
34
40
  }
@@ -1,26 +1,27 @@
1
1
  export default class Context {
2
- constructor(scope, options) {
2
+ static create(scope, options) {
3
+ return new Context(null, {
4
+ functionCallsAllowed: true,
5
+ nodeBlacklist: new Set(),
6
+ prototypeWhitelist: new Map(),
7
+ ...(options ?? {}),
8
+ }, scope);
9
+ }
10
+ static buildMap(obj) {
11
+ const entries = obj instanceof Map ? Array.from(obj.entries()) : Object.entries(obj);
12
+ return new Map(entries.map(([key, value]) => [key, { value, constant: false }]));
13
+ }
14
+ constructor(parent, config, scope) {
15
+ this.parent = parent;
3
16
  this.scope = scope ? Context.buildMap(scope) : new Map();
4
- this.options = {
5
- nodeBlacklist: options?.nodeBlacklist ?? new Set(),
6
- prototypeWhitelist: options?.prototypeWhitelist ?? new Map(),
7
- functionCallsAllowed: options?.functionCallsAllowed ?? true,
17
+ this.config = {
18
+ nodeBlacklist: config.nodeBlacklist ?? new Set(),
19
+ prototypeWhitelist: config.prototypeWhitelist ?? new Map(),
20
+ functionCallsAllowed: config.functionCallsAllowed ?? true,
8
21
  };
9
- this.isFunctionCallAllowed = this.options.functionCallsAllowed;
10
- }
11
- isNodeAllowed(nodeType) {
12
- return !this.options.nodeBlacklist.has(nodeType);
13
22
  }
14
- newChildContext(scope) {
15
- let newEntries;
16
- if (scope instanceof Map) {
17
- newEntries = [...scope.entries()];
18
- }
19
- else {
20
- newEntries = Object.entries(scope);
21
- }
22
- const newScope = new Map([...this.scope.entries(), ...newEntries]);
23
- return new Context(newScope, this.options);
23
+ newChild(scope) {
24
+ return new Context(this, this.config, scope);
24
25
  }
25
26
  /** Returns true if the property is allowed to be accessed on the prototype.
26
27
  *
@@ -32,7 +33,7 @@ export default class Context {
32
33
  * @param ownProperty Whether the property is owned by the object it is being accessed on.
33
34
  */
34
35
  isPrototypeAllowed(prototype, property, ownProperty) {
35
- const whitelist = this.options.prototypeWhitelist.get(prototype);
36
+ const whitelist = this.config.prototypeWhitelist.get(prototype);
36
37
  // If the object owns the property, and it's not in the whitelist, it's allowed.
37
38
  // This prevents direct calls to non-whitelisted properties on allowed prototypes
38
39
  // e.g. Object.prototype.hasOwnProperty.call(obj, 'toString')
@@ -43,7 +44,43 @@ export default class Context {
43
44
  return true;
44
45
  return whitelist.has(property);
45
46
  }
46
- static buildMap(obj) {
47
- return obj instanceof Map ? obj : new Map(Object.entries(obj));
47
+ isNodeAllowed(nodeType) {
48
+ return !this.config.nodeBlacklist.has(nodeType);
49
+ }
50
+ get isFunctionCallAllowed() {
51
+ return this.config.functionCallsAllowed;
52
+ }
53
+ getDescriptor(key, searchParents = true) {
54
+ if (this.scope.has(key)) {
55
+ return this.scope.get(key);
56
+ }
57
+ if (searchParents && this.parent) {
58
+ return this.parent.getDescriptor(key);
59
+ }
60
+ return undefined;
61
+ }
62
+ get(key) {
63
+ const descriptor = this.getDescriptor(key);
64
+ if (descriptor) {
65
+ return descriptor.value;
66
+ }
67
+ return undefined;
68
+ }
69
+ set(key, value) {
70
+ const descriptor = this.getDescriptor(key);
71
+ if (!descriptor) {
72
+ throw new Error(`no variable with name ${key}`);
73
+ }
74
+ if (descriptor.constant) {
75
+ throw new Error(`cannot reassign to constant variable ${key}`);
76
+ }
77
+ descriptor.value = value;
78
+ }
79
+ declare(key, value, constant) {
80
+ const descriptor = this.getDescriptor(key, false);
81
+ if (descriptor) {
82
+ throw new Error(`variable ${key} already declared`);
83
+ }
84
+ this.scope.set(key, { value, constant });
48
85
  }
49
86
  }
@@ -18,7 +18,7 @@ const parse = (code) => {
18
18
  };
19
19
  export const compile = (code, scope, options) => {
20
20
  const program = parse(code);
21
- const context = new Context(scope ?? SAFE_GLOBALS, {
21
+ const context = Context.create(scope ?? SAFE_GLOBALS, {
22
22
  prototypeWhitelist: SAFE_PROTOTYPES,
23
23
  ...options,
24
24
  });
@@ -47,6 +47,7 @@ export const SAFE_GLOBALS = new Map(Object.entries({
47
47
  Number,
48
48
  Object,
49
49
  Array,
50
+ Boolean,
50
51
  Symbol,
51
52
  Error,
52
53
  JSON,
@@ -56,6 +57,57 @@ export const SAFE_GLOBALS = new Map(Object.entries({
56
57
  }));
57
58
  export const SAFE_PROTOTYPES = new Map([
58
59
  [Number.prototype, new Set(['toString', 'toFixed', 'toExponential', 'toPrecision'])],
60
+ [
61
+ Date.prototype,
62
+ new Set([
63
+ 'toString',
64
+ 'toDateString',
65
+ 'toTimeString',
66
+ 'toISOString',
67
+ 'toUTCString',
68
+ 'toGMTString',
69
+ 'getDate',
70
+ 'setDate',
71
+ 'getDay',
72
+ 'getFullYear',
73
+ 'setFullYear',
74
+ 'getHours',
75
+ 'setHours',
76
+ 'getMilliseconds',
77
+ 'setMilliseconds',
78
+ 'getMinutes',
79
+ 'setMinutes',
80
+ 'getMonth',
81
+ 'setMonth',
82
+ 'getSeconds',
83
+ 'setSeconds',
84
+ 'getTime',
85
+ 'setTime',
86
+ 'getTimezoneOffset',
87
+ 'getUTCDate',
88
+ 'setUTCDate',
89
+ 'getUTCDay',
90
+ 'getUTCFullYear',
91
+ 'setUTCFullYear',
92
+ 'getUTCHours',
93
+ 'setUTCHours',
94
+ 'getUTCMilliseconds',
95
+ 'setUTCMilliseconds',
96
+ 'getUTCMinutes',
97
+ 'setUTCMinutes',
98
+ 'getUTCMonth',
99
+ 'setUTCMonth',
100
+ 'getUTCSeconds',
101
+ 'setUTCSeconds',
102
+ 'valueOf',
103
+ 'getYear',
104
+ 'setYear',
105
+ 'toJSON',
106
+ 'toLocaleString',
107
+ 'toLocaleDateString',
108
+ 'toLocaleTimeString',
109
+ ]),
110
+ ],
59
111
  [
60
112
  String.prototype,
61
113
  new Set([
@@ -10,6 +10,6 @@ export default class IdentifierNode extends Node {
10
10
  }
11
11
  validate() { }
12
12
  run() {
13
- return this.scope.get(this.node.name);
13
+ return this.context.get(this.node.name);
14
14
  }
15
15
  }
@@ -3,7 +3,6 @@ import type Context from '../Context';
3
3
  export default abstract class Node<T extends AnyNode = AnyNode> {
4
4
  protected context: Context;
5
5
  protected node: T;
6
- protected scope: Context['scope'];
7
6
  protected constructor(node: AnyNode, context: Context, expectedType: T['type'] | T['type'][]);
8
7
  /** Runs the node. */
9
8
  abstract run(): unknown;
@@ -7,6 +7,5 @@ export default class Node {
7
7
  throw new Error(`Expected node type to be ${expectedTypes.join(' or ')}, but got ${node.type}`);
8
8
  }
9
9
  this.node = node;
10
- this.scope = context.scope;
11
10
  }
12
11
  }
@@ -9,16 +9,19 @@ export default class ProgramNode extends Node {
9
9
  super(node, scope, 'Program');
10
10
  }
11
11
  validate(validateChildren = false) {
12
- if (this.node.body.length !== 1) {
13
- throw new Error('Only a single statement is supported in the sandbox');
14
- }
15
12
  if (!validateChildren) {
16
13
  return;
17
14
  }
18
- construct(this.node.body[0], this.context).validate(true);
15
+ for (const child of this.node.body) {
16
+ construct(child, this.context).validate(true);
17
+ }
19
18
  }
20
19
  run() {
21
20
  this.validate();
22
- return construct(this.node.body[0], this.context).run();
21
+ let result;
22
+ for (const child of this.node.body) {
23
+ result = construct(child, this.context).run();
24
+ }
25
+ return result;
23
26
  }
24
27
  }
@@ -0,0 +1,8 @@
1
+ import { AnyNode, VariableDeclaration } from 'acorn';
2
+ import Context from '../Context';
3
+ import Node from './Node';
4
+ export default class VariableDeclarationNode extends Node<VariableDeclaration> {
5
+ constructor(node: AnyNode, context: Context);
6
+ validate(validateChildren?: boolean): void;
7
+ run(): void;
8
+ }
@@ -0,0 +1,37 @@
1
+ import Node from './Node';
2
+ import { construct } from '../utils';
3
+ export default class VariableDeclarationNode extends Node {
4
+ constructor(node, context) {
5
+ super(node, context, 'VariableDeclaration');
6
+ }
7
+ validate(validateChildren = false) {
8
+ if (this.node.kind === 'var') {
9
+ throw new Error('var is not supported. use let or const instead');
10
+ }
11
+ for (const declaration of this.node.declarations) {
12
+ if (declaration.id.type !== 'Identifier') {
13
+ throw new Error('variable name must be an identifier');
14
+ }
15
+ }
16
+ if (!validateChildren) {
17
+ return;
18
+ }
19
+ for (const declaration of this.node.declarations) {
20
+ construct(declaration.id, this.context).validate(true);
21
+ if (declaration.init) {
22
+ construct(declaration.init, this.context).validate(true);
23
+ }
24
+ }
25
+ }
26
+ run() {
27
+ this.validate();
28
+ for (const declaration of this.node.declarations) {
29
+ const id = declaration.id.name;
30
+ let init;
31
+ if (declaration.init) {
32
+ init = construct(declaration.init, this.context).run();
33
+ }
34
+ this.context.declare(id, init, this.node.kind === 'const');
35
+ }
36
+ }
37
+ }