@budibase/string-templates 3.2.5 → 3.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,168 +0,0 @@
1
- import { atob, isBackendService, isJSAllowed } from "../utilities"
2
- import { LITERAL_MARKER } from "../helpers/constants"
3
- import { getJsHelperList } from "./list"
4
- import { iifeWrapper } from "../iife"
5
- import { JsTimeoutError, UserScriptError } from "../errors"
6
- import { cloneDeep } from "lodash/fp"
7
-
8
- // The method of executing JS scripts depends on the bundle being built.
9
- // This setter is used in the entrypoint (either index.js or index.mjs).
10
- let runJS: ((js: string, context: Record<string, any>) => any) | undefined =
11
- undefined
12
- export const setJSRunner = (runner: typeof runJS) => (runJS = runner)
13
-
14
- export const removeJSRunner = () => {
15
- runJS = undefined
16
- }
17
-
18
- let onErrorLog: (message: Error) => void
19
- export const setOnErrorLog = (delegate: typeof onErrorLog) =>
20
- (onErrorLog = delegate)
21
-
22
- // Helper utility to strip square brackets from a value
23
- const removeSquareBrackets = (value: string) => {
24
- if (!value || typeof value !== "string") {
25
- return value
26
- }
27
- const regex = /\[+(.+)]+/
28
- const matches = value.match(regex)
29
- if (matches && matches[1]) {
30
- return matches[1]
31
- }
32
- return value
33
- }
34
-
35
- const isReservedKey = (key: string) =>
36
- key === "snippets" ||
37
- key === "helpers" ||
38
- key.startsWith("snippets.") ||
39
- key.startsWith("helpers.")
40
-
41
- // Our context getter function provided to JS code as $.
42
- // Extracts a value from context.
43
- const getContextValue = (path: string, context: any) => {
44
- // We populate `snippets` ourselves, don't allow access to it.
45
- if (isReservedKey(path)) {
46
- return undefined
47
- }
48
- const literalStringRegex = /^(["'`]).*\1$/
49
- let data = context
50
- // check if it's a literal string - just return path if its quoted
51
- if (literalStringRegex.test(path)) {
52
- return path.substring(1, path.length - 1)
53
- }
54
- path.split(".").forEach(key => {
55
- if (data == null || typeof data !== "object") {
56
- return null
57
- }
58
- data = data[removeSquareBrackets(key)]
59
- })
60
-
61
- return data
62
- }
63
-
64
- // Evaluates JS code against a certain context
65
- export function processJS(handlebars: string, context: any) {
66
- if (!isJSAllowed() || !runJS) {
67
- throw new Error("JS disabled in environment.")
68
- }
69
- try {
70
- // Wrap JS in a function and immediately invoke it.
71
- // This is required to allow the final `return` statement to be valid.
72
- const js = iifeWrapper(atob(handlebars))
73
-
74
- // Transform snippets into an object for faster access, and cache previously
75
- // evaluated snippets
76
- let snippetMap: any = {}
77
- let snippetCache: any = {}
78
- for (let snippet of context.snippets || []) {
79
- snippetMap[snippet.name] = snippet.code
80
- }
81
-
82
- let clonedContext: Record<string, any>
83
- if (isBackendService()) {
84
- // On the backned, values are copied across the isolated-vm boundary and
85
- // so we don't need to do any cloning here. This does create a fundamental
86
- // difference in how JS executes on the frontend vs the backend, e.g.
87
- // consider this snippet:
88
- //
89
- // $("array").push(2)
90
- // return $("array")[1]
91
- //
92
- // With the context of `{ array: [1] }`, the backend will return
93
- // `undefined` whereas the frontend will return `2`. We should fix this.
94
- clonedContext = context
95
- } else {
96
- clonedContext = cloneDeep(context)
97
- }
98
-
99
- const sandboxContext = {
100
- $: (path: string) => getContextValue(path, clonedContext),
101
- helpers: getJsHelperList(),
102
-
103
- // Proxy to evaluate snippets when running in the browser
104
- snippets: new Proxy(
105
- {},
106
- {
107
- get: function (_, name) {
108
- if (!(name in snippetCache)) {
109
- snippetCache[name] = eval(iifeWrapper(snippetMap[name]))
110
- }
111
- return snippetCache[name]
112
- },
113
- }
114
- ),
115
- }
116
-
117
- // Create a sandbox with our context and run the JS
118
- const res = { data: runJS(js, sandboxContext) }
119
- return `{{${LITERAL_MARKER} js_result-${JSON.stringify(res)}}}`
120
- } catch (error: any) {
121
- onErrorLog && onErrorLog(error)
122
-
123
- const { noThrow = true } = context.__opts || {}
124
-
125
- // The error handling below is quite messy, because it has fallen to
126
- // string-templates to handle a variety of types of error specific to usages
127
- // above it in the stack. It would be nice some day to refactor this to
128
- // allow each user of processStringSync to handle errors in the way they see
129
- // fit.
130
-
131
- // This is to catch the error vm.runInNewContext() throws when the timeout
132
- // is exceeded.
133
- if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
134
- return "Timed out while executing JS"
135
- }
136
-
137
- // This is to catch the JsRequestTimeoutError we throw when we detect a
138
- // timeout across an entire request in the backend. We use a magic string
139
- // because we can't import from the backend into string-templates.
140
- if (error.code === "JS_REQUEST_TIMEOUT_ERROR") {
141
- return error.message
142
- }
143
-
144
- // This is to catch the JsTimeoutError we throw when we detect a timeout in
145
- // a single JS execution.
146
- if (error.code === JsTimeoutError.code) {
147
- return JsTimeoutError.message
148
- }
149
-
150
- // This is to catch the error that happens if a user-supplied JS script
151
- // throws for reasons introduced by the user.
152
- if (error.code === UserScriptError.code) {
153
- if (noThrow) {
154
- return error.userScriptError.toString()
155
- }
156
- throw error
157
- }
158
-
159
- if (error.name === "SyntaxError") {
160
- if (noThrow) {
161
- return error.toString()
162
- }
163
- throw error
164
- }
165
-
166
- return "Error while executing JS"
167
- }
168
- }
@@ -1,72 +0,0 @@
1
- import { date, duration } from "./date"
2
-
3
- /*
4
- @budibase/handlebars-helpers is not treeshakeable, so we can't use the barrel files.
5
- Otherwise, we have issues when generating the isolated-vm bundle because of the treeshaking
6
- */
7
- /* eslint-disable local-rules/no-budibase-imports */
8
- // @ts-expect-error
9
- import math from "@budibase/handlebars-helpers/lib/math"
10
- // @ts-expect-error
11
- import array from "@budibase/handlebars-helpers/lib/array"
12
- // @ts-expect-error
13
- import number from "@budibase/handlebars-helpers/lib/number"
14
- // @ts-expect-error
15
- import url from "@budibase/handlebars-helpers/lib/url"
16
- // @ts-expect-error
17
- import string from "@budibase/handlebars-helpers/lib/string"
18
- // @ts-expect-error
19
- import comparison from "@budibase/handlebars-helpers/lib/comparison"
20
- // @ts-expect-error
21
- import object from "@budibase/handlebars-helpers/lib/object"
22
- // @ts-expect-error
23
- import regex from "@budibase/handlebars-helpers/lib/regex"
24
- // @ts-expect-error
25
- import uuid from "@budibase/handlebars-helpers/lib/uuid"
26
- /* eslint-enable local-rules/no-budibase-imports */
27
-
28
- // https://github.com/evanw/esbuild/issues/56
29
- const externalCollections = {
30
- math,
31
- array,
32
- number,
33
- url,
34
- string,
35
- comparison,
36
- object,
37
- regex,
38
- uuid,
39
- }
40
-
41
- export const helpersToRemoveForJs = ["sortBy"]
42
-
43
- const addedHelpers = {
44
- date: date,
45
- duration: duration,
46
- }
47
-
48
- let helpers: Record<string, any>
49
-
50
- export function getJsHelperList() {
51
- if (helpers) {
52
- return helpers
53
- }
54
-
55
- helpers = {}
56
- for (let collection of Object.values(externalCollections)) {
57
- for (let [key, func] of Object.entries<any>(collection)) {
58
- // Handlebars injects the hbs options to the helpers by default. We are adding an empty {} as a last parameter to simulate it
59
- helpers[key] = (...props: any) => func(...props, {})
60
- }
61
- }
62
- helpers = {
63
- ...helpers,
64
- ...addedHelpers,
65
- }
66
-
67
- for (const toRemove of helpersToRemoveForJs) {
68
- delete helpers[toRemove]
69
- }
70
- Object.freeze(helpers)
71
- return helpers
72
- }
package/src/iife.ts DELETED
@@ -1,3 +0,0 @@
1
- export const iifeWrapper = (script: string) => {
2
- return `(function(){\n${script}\n})();`
3
- }