@usebruno/js 0.15.0 → 0.16.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/package.json +1 -1
- package/src/bru.js +16 -13
- package/src/bruno-request.js +101 -3
- package/src/interpolate-string.js +3 -1
- package/src/runtime/assert-runtime.js +8 -0
- package/src/runtime/script-runtime.js +6 -20
- package/src/runtime/test-runtime.js +3 -12
- package/src/runtime/vars-runtime.js +2 -63
- package/src/sandbox/quickjs/shims/bru.js +12 -0
package/package.json
CHANGED
package/src/bru.js
CHANGED
|
@@ -5,20 +5,13 @@ const { interpolate } = require('@usebruno/common');
|
|
|
5
5
|
const variableNameRegex = /^[\w-.]*$/;
|
|
6
6
|
|
|
7
7
|
class Bru {
|
|
8
|
-
constructor(
|
|
9
|
-
envVariables,
|
|
10
|
-
runtimeVariables,
|
|
11
|
-
processEnvVars,
|
|
12
|
-
collectionPath,
|
|
13
|
-
historyLogger,
|
|
14
|
-
setVisualizations,
|
|
15
|
-
secretVariables,
|
|
16
|
-
requestVariables
|
|
17
|
-
) {
|
|
8
|
+
constructor(envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, setVisualizations, secretVariables, collectionVariables, folderVariables, requestVariables) {
|
|
18
9
|
this.envVariables = envVariables || {};
|
|
19
10
|
this.runtimeVariables = runtimeVariables || {};
|
|
20
11
|
this.processEnvVars = cloneDeep(processEnvVars || {});
|
|
21
12
|
this.secretVariables = cloneDeep(secretVariables || {});
|
|
13
|
+
this.collectionVariables = collectionVariables || {};
|
|
14
|
+
this.folderVariables = folderVariables || {};
|
|
22
15
|
this.requestVariables = requestVariables || {};
|
|
23
16
|
this.collectionPath = collectionPath;
|
|
24
17
|
this.historyLogger = historyLogger;
|
|
@@ -31,7 +24,9 @@ class Bru {
|
|
|
31
24
|
}
|
|
32
25
|
|
|
33
26
|
const combinedVars = {
|
|
27
|
+
...this.collectionVariables,
|
|
34
28
|
...this.envVariables,
|
|
29
|
+
...this.folderVariables,
|
|
35
30
|
...this.requestVariables,
|
|
36
31
|
...this.runtimeVariables,
|
|
37
32
|
process: {
|
|
@@ -93,7 +88,7 @@ class Bru {
|
|
|
93
88
|
if (variableNameRegex.test(key) === false) {
|
|
94
89
|
throw new Error(
|
|
95
90
|
`Variable name: "${key}" contains invalid characters!` +
|
|
96
|
-
|
|
91
|
+
' Names must only contain alpha-numeric characters, "-", "_", "."'
|
|
97
92
|
);
|
|
98
93
|
}
|
|
99
94
|
|
|
@@ -113,7 +108,7 @@ class Bru {
|
|
|
113
108
|
if (variableNameRegex.test(key) === false) {
|
|
114
109
|
throw new Error(
|
|
115
110
|
`Variable name: "${key}" contains invalid characters!` +
|
|
116
|
-
|
|
111
|
+
' Names must only contain alpha-numeric characters, "-", "_", "."'
|
|
117
112
|
);
|
|
118
113
|
}
|
|
119
114
|
|
|
@@ -124,6 +119,14 @@ class Bru {
|
|
|
124
119
|
delete this.runtimeVariables[key];
|
|
125
120
|
}
|
|
126
121
|
|
|
122
|
+
getCollectionVar(key) {
|
|
123
|
+
return this._interpolate(this.collectionVariables[key]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getFolderVar(key) {
|
|
127
|
+
return this._interpolate(this.folderVariables[key]);
|
|
128
|
+
}
|
|
129
|
+
|
|
127
130
|
getRequestVar(key) {
|
|
128
131
|
return this._interpolate(this.requestVariables[key]);
|
|
129
132
|
}
|
|
@@ -158,7 +161,7 @@ class Bru {
|
|
|
158
161
|
this.setVisualizations({ uid: uuid(), type, data });
|
|
159
162
|
}
|
|
160
163
|
}
|
|
161
|
-
|
|
164
|
+
|
|
162
165
|
sleep(ms) {
|
|
163
166
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
164
167
|
}
|
package/src/bruno-request.js
CHANGED
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
const { uuid } = require('./utils');
|
|
2
2
|
|
|
3
3
|
class BrunoRequest {
|
|
4
|
+
/**
|
|
5
|
+
* The following properties are available as shorthand:
|
|
6
|
+
* - req.url
|
|
7
|
+
* - req.method
|
|
8
|
+
* - req.headers
|
|
9
|
+
* - req.timeout
|
|
10
|
+
* - req.body
|
|
11
|
+
*
|
|
12
|
+
* Above shorthands are useful for accessing the request properties directly in the scripts
|
|
13
|
+
* It must be noted that the user cannot set these properties directly.
|
|
14
|
+
* They should use the respective setter methods to set these properties.
|
|
15
|
+
*/
|
|
4
16
|
constructor(req, historyLogger) {
|
|
5
17
|
this.req = req;
|
|
6
18
|
this.url = req.url;
|
|
7
19
|
this.method = req.method;
|
|
8
20
|
this.headers = req.headers;
|
|
9
|
-
this.body = req.data;
|
|
10
21
|
this.timeout = req.timeout;
|
|
11
22
|
this.historyLogger = historyLogger;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* We automatically parse the JSON body if the content type is JSON
|
|
26
|
+
* This is to make it easier for the user to access the body directly
|
|
27
|
+
*
|
|
28
|
+
* It must be noted that the request data is always a string and is what gets sent over the network
|
|
29
|
+
* If the user wants to access the raw data, they can use getBody({raw: true}) method
|
|
30
|
+
*/
|
|
31
|
+
const isJson = this.hasJSONContentType(this.req.headers);
|
|
32
|
+
if (isJson) {
|
|
33
|
+
this.body = this.__safeParseJSON(req.data);
|
|
34
|
+
}
|
|
12
35
|
}
|
|
13
36
|
|
|
14
37
|
getUrl() {
|
|
@@ -16,6 +39,7 @@ class BrunoRequest {
|
|
|
16
39
|
}
|
|
17
40
|
|
|
18
41
|
setUrl(url) {
|
|
42
|
+
this.url = url;
|
|
19
43
|
this.req.url = url;
|
|
20
44
|
}
|
|
21
45
|
|
|
@@ -40,6 +64,7 @@ class BrunoRequest {
|
|
|
40
64
|
}
|
|
41
65
|
|
|
42
66
|
setMethod(method) {
|
|
67
|
+
this.method = method;
|
|
43
68
|
this.req.method = method;
|
|
44
69
|
}
|
|
45
70
|
|
|
@@ -48,6 +73,7 @@ class BrunoRequest {
|
|
|
48
73
|
}
|
|
49
74
|
|
|
50
75
|
setHeaders(headers) {
|
|
76
|
+
this.headers = headers;
|
|
51
77
|
this.req.headers = headers;
|
|
52
78
|
}
|
|
53
79
|
|
|
@@ -64,14 +90,45 @@ class BrunoRequest {
|
|
|
64
90
|
createdAt: new Date().toISOString()
|
|
65
91
|
});
|
|
66
92
|
}
|
|
93
|
+
this.headers[name] = value;
|
|
67
94
|
this.req.headers[name] = value;
|
|
68
95
|
}
|
|
69
96
|
|
|
70
|
-
|
|
97
|
+
hasJSONContentType(headers) {
|
|
98
|
+
const contentType = headers?.['Content-Type'] || headers?.['content-type'] || '';
|
|
99
|
+
return contentType.includes('json');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get the body of the request
|
|
104
|
+
*
|
|
105
|
+
* We automatically parse and return the JSON body if the content type is JSON
|
|
106
|
+
* If the user wants the raw body, they can pass the raw option as true
|
|
107
|
+
*/
|
|
108
|
+
getBody(options = {}) {
|
|
109
|
+
if (options.raw) {
|
|
110
|
+
return this.req.data;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const isJson = this.hasJSONContentType(this.req.headers);
|
|
114
|
+
if (isJson) {
|
|
115
|
+
return this.__safeParseJSON(this.req.data);
|
|
116
|
+
}
|
|
117
|
+
|
|
71
118
|
return this.req.data;
|
|
72
119
|
}
|
|
73
120
|
|
|
74
|
-
|
|
121
|
+
/**
|
|
122
|
+
* If the content type is JSON and if the data is an object
|
|
123
|
+
* - We set the body property as the object itself
|
|
124
|
+
* - We set the request data as the stringified JSON as it is what gets sent over the network
|
|
125
|
+
* Otherwise
|
|
126
|
+
* - We set the request data as the data itself
|
|
127
|
+
* - We set the body property as the data itself
|
|
128
|
+
*
|
|
129
|
+
* If the user wants to override this behavior, they can pass the raw option as true
|
|
130
|
+
*/
|
|
131
|
+
setBody(data, options = {}) {
|
|
75
132
|
if (this.historyLogger) {
|
|
76
133
|
this.historyLogger({
|
|
77
134
|
uid: uuid(),
|
|
@@ -80,7 +137,22 @@ class BrunoRequest {
|
|
|
80
137
|
createdAt: new Date().toISOString()
|
|
81
138
|
});
|
|
82
139
|
}
|
|
140
|
+
|
|
141
|
+
if (options.raw) {
|
|
142
|
+
this.req.data = data;
|
|
143
|
+
this.body = data;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const isJson = this.hasJSONContentType(this.req.headers);
|
|
148
|
+
if (isJson && this.__isObject(data)) {
|
|
149
|
+
this.body = data;
|
|
150
|
+
this.req.data = this.__safeStringifyJSON(data);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
83
154
|
this.req.data = data;
|
|
155
|
+
this.body = data;
|
|
84
156
|
}
|
|
85
157
|
|
|
86
158
|
setMaxRedirects(maxRedirects) {
|
|
@@ -92,8 +164,34 @@ class BrunoRequest {
|
|
|
92
164
|
}
|
|
93
165
|
|
|
94
166
|
setTimeout(timeout) {
|
|
167
|
+
this.timeout = timeout;
|
|
95
168
|
this.req.timeout = timeout;
|
|
96
169
|
}
|
|
170
|
+
|
|
171
|
+
__safeParseJSON(str) {
|
|
172
|
+
try {
|
|
173
|
+
return JSON.parse(str);
|
|
174
|
+
} catch (e) {
|
|
175
|
+
return str;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
__safeStringifyJSON(obj) {
|
|
180
|
+
try {
|
|
181
|
+
return JSON.stringify(obj);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
return obj;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
__isObject(obj) {
|
|
188
|
+
return obj !== null && typeof obj === 'object';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
disableParsingResponseJson() {
|
|
193
|
+
this.req.__brunoDisableParsingResponseJson = true;
|
|
194
|
+
}
|
|
97
195
|
}
|
|
98
196
|
|
|
99
197
|
module.exports = BrunoRequest;
|
|
@@ -2,14 +2,16 @@ const { interpolate } = require('@usebruno/common');
|
|
|
2
2
|
|
|
3
3
|
const interpolateString = (
|
|
4
4
|
str,
|
|
5
|
-
{ envVariables = {}, runtimeVariables = {}, processEnvVars = {}, requestVariables = {} }
|
|
5
|
+
{ envVariables = {}, runtimeVariables = {}, processEnvVars = {}, collectionVariables = {}, folderVariables = {}, requestVariables = {} }
|
|
6
6
|
) => {
|
|
7
7
|
if (!str || !str.length || typeof str !== 'string') {
|
|
8
8
|
return str;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const combinedVars = {
|
|
12
|
+
...collectionVariables,
|
|
12
13
|
...envVariables,
|
|
14
|
+
...folderVariables,
|
|
13
15
|
...requestVariables,
|
|
14
16
|
...runtimeVariables,
|
|
15
17
|
process: {
|
|
@@ -192,6 +192,8 @@ const evaluateRhsOperand = (rhsOperand, operator, context, runtime) => {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
const interpolationContext = {
|
|
195
|
+
collectionVariables: context.bru.collectionVariables,
|
|
196
|
+
folderVariables: context.bru.folderVariables,
|
|
195
197
|
requestVariables: context.bru.requestVariables,
|
|
196
198
|
runtimeVariables: context.bru.runtimeVariables,
|
|
197
199
|
envVariables: context.bru.envVariables,
|
|
@@ -238,6 +240,8 @@ class AssertRuntime {
|
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars, historyLogger, secretVariables) {
|
|
243
|
+
const collectionVariables = request?.collectionVariables || {};
|
|
244
|
+
const folderVariables = request?.folderVariables || {};
|
|
241
245
|
const requestVariables = request?.requestVariables || {};
|
|
242
246
|
const enabledAssertions = _.filter(assertions, (a) => a.enabled);
|
|
243
247
|
if (!enabledAssertions.length) {
|
|
@@ -252,6 +256,8 @@ class AssertRuntime {
|
|
|
252
256
|
undefined, // historyLogger,
|
|
253
257
|
undefined, // setVisualizations,
|
|
254
258
|
secretVariables,
|
|
259
|
+
collectionVariables,
|
|
260
|
+
folderVariables,
|
|
255
261
|
requestVariables
|
|
256
262
|
);
|
|
257
263
|
const req = new BrunoRequest(request, historyLogger);
|
|
@@ -264,7 +270,9 @@ class AssertRuntime {
|
|
|
264
270
|
};
|
|
265
271
|
|
|
266
272
|
const context = {
|
|
273
|
+
...collectionVariables,
|
|
267
274
|
...envVariables,
|
|
275
|
+
...folderVariables,
|
|
268
276
|
...requestVariables,
|
|
269
277
|
...runtimeVariables,
|
|
270
278
|
...processEnvVars,
|
|
@@ -53,17 +53,10 @@ class ScriptRuntime {
|
|
|
53
53
|
let setVisualizations = (data) => {
|
|
54
54
|
visualizations.push(data);
|
|
55
55
|
}
|
|
56
|
+
const collectionVariables = request?.collectionVariables || {};
|
|
57
|
+
const folderVariables = request?.folderVariables || {};
|
|
56
58
|
const requestVariables = request?.requestVariables || {};
|
|
57
|
-
const bru = new Bru(
|
|
58
|
-
envVariables,
|
|
59
|
-
runtimeVariables,
|
|
60
|
-
processEnvVars,
|
|
61
|
-
collectionPath,
|
|
62
|
-
historyLogger,
|
|
63
|
-
setVisualizations,
|
|
64
|
-
secretVariables,
|
|
65
|
-
requestVariables
|
|
66
|
-
);
|
|
59
|
+
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, setVisualizations, secretVariables, collectionVariables, folderVariables, requestVariables);
|
|
67
60
|
const req = new BrunoRequest(request, historyLogger);
|
|
68
61
|
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
|
69
62
|
const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
|
|
@@ -185,17 +178,10 @@ class ScriptRuntime {
|
|
|
185
178
|
let setVisualizations = (data) => {
|
|
186
179
|
visualizations.push(data);
|
|
187
180
|
}
|
|
181
|
+
const collectionVariables = request?.collectionVariables || {};
|
|
182
|
+
const folderVariables = request?.folderVariables || {};
|
|
188
183
|
const requestVariables = request?.requestVariables || {};
|
|
189
|
-
const bru = new Bru(
|
|
190
|
-
envVariables,
|
|
191
|
-
runtimeVariables,
|
|
192
|
-
processEnvVars,
|
|
193
|
-
collectionPath,
|
|
194
|
-
historyLogger,
|
|
195
|
-
setVisualizations,
|
|
196
|
-
secretVariables,
|
|
197
|
-
requestVariables
|
|
198
|
-
);
|
|
184
|
+
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, setVisualizations, secretVariables, collectionVariables, folderVariables, requestVariables);
|
|
199
185
|
const req = new BrunoRequest(request, historyLogger);
|
|
200
186
|
const res = new BrunoResponse(response);
|
|
201
187
|
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
|
@@ -50,17 +50,10 @@ class TestRuntime {
|
|
|
50
50
|
historyLogger,
|
|
51
51
|
secretVariables
|
|
52
52
|
) {
|
|
53
|
+
const collectionVariables = request?.collectionVariables || {};
|
|
54
|
+
const folderVariables = request?.folderVariables || {};
|
|
53
55
|
const requestVariables = request?.requestVariables || {};
|
|
54
|
-
const bru = new Bru(
|
|
55
|
-
envVariables,
|
|
56
|
-
runtimeVariables,
|
|
57
|
-
processEnvVars,
|
|
58
|
-
collectionPath,
|
|
59
|
-
historyLogger,
|
|
60
|
-
undefined, // setVisualizations
|
|
61
|
-
secretVariables,
|
|
62
|
-
requestVariables
|
|
63
|
-
);
|
|
56
|
+
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, undefined, secretVariables, collectionVariables, folderVariables, requestVariables);
|
|
64
57
|
const req = new BrunoRequest(request, historyLogger);
|
|
65
58
|
const res = new BrunoResponse(response);
|
|
66
59
|
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
|
@@ -95,8 +88,6 @@ class TestRuntime {
|
|
|
95
88
|
};
|
|
96
89
|
}
|
|
97
90
|
|
|
98
|
-
// add 'await' prefix to the test function calls
|
|
99
|
-
testsFile = appendAwaitToTestFunc(testsFile);
|
|
100
91
|
|
|
101
92
|
const context = {
|
|
102
93
|
test,
|
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const Bru = require('../bru');
|
|
3
3
|
const BrunoRequest = require('../bruno-request');
|
|
4
|
-
const {
|
|
4
|
+
const { evaluateJsExpression, createResponseParser } = require('../utils');
|
|
5
5
|
|
|
6
6
|
const { executeQuickJsVm } = require('../sandbox/quickjs');
|
|
7
7
|
|
|
8
|
-
const evaluateJsTemplateLiteralBasedOnRuntime = (literal, context, runtime) => {
|
|
9
|
-
if (runtime === 'quickjs') {
|
|
10
|
-
return executeQuickJsVm({
|
|
11
|
-
script: literal,
|
|
12
|
-
context,
|
|
13
|
-
scriptType: 'template-literal'
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return evaluateJsTemplateLiteral(literal, context);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
8
|
const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime, mode) => {
|
|
21
9
|
if (runtime === 'quickjs') {
|
|
22
10
|
return executeQuickJsVm({
|
|
@@ -35,56 +23,7 @@ class VarsRuntime {
|
|
|
35
23
|
this.mode = props?.mode || 'developer';
|
|
36
24
|
}
|
|
37
25
|
|
|
38
|
-
|
|
39
|
-
if (!request?.requestVariables) {
|
|
40
|
-
request.requestVariables = {};
|
|
41
|
-
}
|
|
42
|
-
const enabledVars = _.filter(vars, (v) => v.enabled);
|
|
43
|
-
if (!enabledVars.length) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const bru = new Bru(
|
|
48
|
-
envVariables,
|
|
49
|
-
runtimeVariables,
|
|
50
|
-
processEnvVars,
|
|
51
|
-
null,
|
|
52
|
-
historyLogger,
|
|
53
|
-
undefined, // setVisualizations
|
|
54
|
-
secretVars,
|
|
55
|
-
undefined, // requestVariables
|
|
56
|
-
);
|
|
57
|
-
const req = new BrunoRequest(request, historyLogger);
|
|
58
|
-
|
|
59
|
-
const bruContext = {
|
|
60
|
-
bru,
|
|
61
|
-
req
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const context = {
|
|
65
|
-
...envVariables,
|
|
66
|
-
...runtimeVariables,
|
|
67
|
-
...secretVars,
|
|
68
|
-
...bruContext
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
_.each(enabledVars, (v) => {
|
|
72
|
-
const value = evaluateJsTemplateLiteralBasedOnRuntime(v.value, context, this.runtime);
|
|
73
|
-
request?.requestVariables && (request.requestVariables[v.name] = value);
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
runPostResponseVars(
|
|
78
|
-
vars,
|
|
79
|
-
request,
|
|
80
|
-
response,
|
|
81
|
-
envVariables,
|
|
82
|
-
runtimeVariables,
|
|
83
|
-
collectionPath,
|
|
84
|
-
processEnvVars,
|
|
85
|
-
historyLogger,
|
|
86
|
-
secretVars = {}
|
|
87
|
-
) {
|
|
26
|
+
runPostResponseVars(vars, request, response, envVariables, runtimeVariables, collectionPath, processEnvVars, historyLogger, secretVars = {}) {
|
|
88
27
|
const requestVariables = request?.requestVariables || {};
|
|
89
28
|
const enabledVars = _.filter(vars, (v) => v.enabled);
|
|
90
29
|
if (!enabledVars.length) {
|
|
@@ -69,6 +69,18 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
69
69
|
vm.setProp(bruObject, 'getRequestVar', getRequestVar);
|
|
70
70
|
getRequestVar.dispose();
|
|
71
71
|
|
|
72
|
+
let getFolderVar = vm.newFunction('getFolderVar', function (key) {
|
|
73
|
+
return marshallToVm(bru.getFolderVar(vm.dump(key)), vm);
|
|
74
|
+
});
|
|
75
|
+
vm.setProp(bruObject, 'getFolderVar', getFolderVar);
|
|
76
|
+
getFolderVar.dispose();
|
|
77
|
+
|
|
78
|
+
let getCollectionVar = vm.newFunction('getCollectionVar', function (key) {
|
|
79
|
+
return marshallToVm(bru.getCollectionVar(vm.dump(key)), vm);
|
|
80
|
+
});
|
|
81
|
+
vm.setProp(bruObject, 'getCollectionVar', getCollectionVar);
|
|
82
|
+
getCollectionVar.dispose();
|
|
83
|
+
|
|
72
84
|
const sleep = vm.newFunction('sleep', (timer) => {
|
|
73
85
|
const t = vm.getString(timer);
|
|
74
86
|
const promise = vm.newPromise();
|