@builder.io/sdk-svelte 0.6.4 → 0.7.1-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.
Files changed (81) hide show
  1. package/lib/browser/blocks/custom-code/custom-code.svelte +35 -37
  2. package/lib/browser/components/content/components/enable-editor.svelte +86 -70
  3. package/lib/browser/components/content/components/enable-editor.svelte.d.ts +4 -4
  4. package/lib/browser/constants/sdk-version.d.ts +1 -1
  5. package/lib/browser/constants/sdk-version.js +1 -1
  6. package/lib/browser/functions/evaluate/choose-eval.d.ts +6 -0
  7. package/lib/browser/functions/evaluate/choose-eval.js +19 -0
  8. package/lib/browser/functions/evaluate/evaluate.d.ts +1 -5
  9. package/lib/browser/functions/evaluate/evaluate.js +7 -30
  10. package/lib/browser/functions/evaluate/helpers.d.ts +7 -1
  11. package/lib/browser/functions/evaluate/helpers.js +18 -0
  12. package/lib/browser/functions/evaluate/node-runtime/index.d.ts +1 -1
  13. package/lib/browser/functions/evaluate/node-runtime/index.js +1 -1
  14. package/lib/browser/functions/evaluate/node-runtime/node-runtime.d.ts +2 -0
  15. package/lib/browser/functions/evaluate/node-runtime/node-runtime.js +120 -0
  16. package/lib/browser/functions/evaluate/node-runtime/safeDynamicRequire.d.ts +13 -0
  17. package/lib/browser/functions/evaluate/node-runtime/safeDynamicRequire.js +18 -0
  18. package/lib/browser/functions/get-content/generate-content-url.js +32 -1
  19. package/lib/browser/functions/get-content/types.d.ts +94 -3
  20. package/lib/browser/functions/is-edge-runtime.js +2 -2
  21. package/lib/browser/functions/is-node-runtime.d.ts +4 -0
  22. package/lib/browser/functions/is-node-runtime.js +7 -0
  23. package/lib/edge/blocks/custom-code/custom-code.svelte +35 -37
  24. package/lib/edge/components/content/components/enable-editor.svelte +86 -70
  25. package/lib/edge/components/content/components/enable-editor.svelte.d.ts +4 -4
  26. package/lib/edge/constants/sdk-version.d.ts +1 -1
  27. package/lib/edge/constants/sdk-version.js +1 -1
  28. package/lib/edge/functions/evaluate/choose-eval.d.ts +6 -0
  29. package/lib/edge/functions/evaluate/choose-eval.js +19 -0
  30. package/lib/edge/functions/evaluate/evaluate.d.ts +1 -5
  31. package/lib/edge/functions/evaluate/evaluate.js +7 -30
  32. package/lib/edge/functions/evaluate/helpers.d.ts +7 -1
  33. package/lib/edge/functions/evaluate/helpers.js +18 -0
  34. package/lib/edge/functions/evaluate/node-runtime/index.d.ts +1 -1
  35. package/lib/edge/functions/evaluate/node-runtime/index.js +1 -1
  36. package/lib/edge/functions/evaluate/node-runtime/node-runtime.d.ts +2 -0
  37. package/lib/edge/functions/evaluate/node-runtime/node-runtime.js +120 -0
  38. package/lib/edge/functions/evaluate/node-runtime/safeDynamicRequire.d.ts +13 -0
  39. package/lib/edge/functions/evaluate/node-runtime/safeDynamicRequire.js +18 -0
  40. package/lib/edge/functions/get-content/generate-content-url.js +32 -1
  41. package/lib/edge/functions/get-content/types.d.ts +94 -3
  42. package/lib/edge/functions/is-edge-runtime.js +2 -2
  43. package/lib/edge/functions/is-node-runtime.d.ts +4 -0
  44. package/lib/edge/functions/is-node-runtime.js +7 -0
  45. package/lib/node/blocks/custom-code/custom-code.svelte +35 -37
  46. package/lib/node/components/content/components/enable-editor.svelte +86 -70
  47. package/lib/node/components/content/components/enable-editor.svelte.d.ts +4 -4
  48. package/lib/node/constants/sdk-version.d.ts +1 -1
  49. package/lib/node/constants/sdk-version.js +1 -1
  50. package/lib/node/functions/evaluate/choose-eval.d.ts +6 -0
  51. package/lib/node/functions/evaluate/choose-eval.js +19 -0
  52. package/lib/node/functions/evaluate/evaluate.d.ts +1 -5
  53. package/lib/node/functions/evaluate/evaluate.js +7 -30
  54. package/lib/node/functions/evaluate/helpers.d.ts +7 -1
  55. package/lib/node/functions/evaluate/helpers.js +18 -0
  56. package/lib/node/functions/evaluate/node-runtime/index.d.ts +1 -1
  57. package/lib/node/functions/evaluate/node-runtime/index.js +1 -1
  58. package/lib/node/functions/evaluate/node-runtime/node-runtime.d.ts +2 -0
  59. package/lib/node/functions/evaluate/node-runtime/node-runtime.js +120 -0
  60. package/lib/node/functions/evaluate/node-runtime/safeDynamicRequire.d.ts +13 -0
  61. package/lib/node/functions/evaluate/node-runtime/safeDynamicRequire.js +18 -0
  62. package/lib/node/functions/get-content/generate-content-url.js +32 -1
  63. package/lib/node/functions/get-content/types.d.ts +94 -3
  64. package/lib/node/functions/is-edge-runtime.js +2 -2
  65. package/lib/node/functions/is-node-runtime.d.ts +4 -0
  66. package/lib/node/functions/is-node-runtime.js +7 -0
  67. package/package.json +3 -1
  68. package/src/blocks/custom-code/custom-code.svelte +37 -40
  69. package/src/blocks/image/image.svelte +1 -1
  70. package/src/components/content/components/enable-editor.svelte +101 -84
  71. package/src/constants/sdk-version.ts +1 -1
  72. package/src/functions/evaluate/choose-eval.ts +22 -0
  73. package/src/functions/evaluate/evaluate.ts +8 -37
  74. package/src/functions/evaluate/helpers.ts +24 -1
  75. package/src/functions/evaluate/node-runtime/index.ts +1 -1
  76. package/src/functions/evaluate/node-runtime/node-runtime.ts +138 -0
  77. package/src/functions/evaluate/node-runtime/safeDynamicRequire.ts +18 -0
  78. package/src/functions/get-content/generate-content-url.ts +44 -2
  79. package/src/functions/get-content/types.ts +101 -3
  80. package/src/functions/is-edge-runtime.ts +2 -2
  81. package/src/functions/is-node-runtime.ts +8 -0
@@ -3,52 +3,50 @@
3
3
  <script>import { onMount } from "svelte";
4
4
  export let replaceNodes = undefined;
5
5
  export let code;
6
- function findAndRunScripts() {
6
+ let elementRef;
7
+ let scriptsInserted = [];
8
+ let scriptsRun = [];
9
+ onMount(() => {
7
10
  // TODO: Move this function to standalone one in '@builder.io/utils'
8
- if (elem && elem.getElementsByTagName && typeof window !== "undefined") {
9
- const scripts = elem.getElementsByTagName("script");
10
- for (let i = 0; i < scripts.length; i++) {
11
- const script = scripts[i];
12
- if (script.src) {
13
- if (scriptsInserted.includes(script.src)) {
14
- continue;
15
- }
16
- scriptsInserted.push(script.src);
17
- const newScript = document.createElement("script");
18
- newScript.async = true;
19
- newScript.src = script.src;
20
- document.head.appendChild(newScript);
11
+ if (!elementRef?.getElementsByTagName || typeof window === "undefined") {
12
+ return;
13
+ }
14
+ const scripts = elementRef.getElementsByTagName("script");
15
+ for (let i = 0; i < scripts.length; i++) {
16
+ const script = scripts[i];
17
+ if (script.src) {
18
+ if (scriptsInserted.includes(script.src)) {
19
+ continue;
20
+ }
21
+ scriptsInserted.push(script.src);
22
+ const newScript = document.createElement("script");
23
+ newScript.async = true;
24
+ newScript.src = script.src;
25
+ document.head.appendChild(newScript);
26
+ }
27
+ else if (!script.type ||
28
+ [
29
+ "text/javascript",
30
+ "application/javascript",
31
+ "application/ecmascript",
32
+ ].includes(script.type)) {
33
+ if (scriptsRun.includes(script.innerText)) {
34
+ continue;
35
+ }
36
+ try {
37
+ scriptsRun.push(script.innerText);
38
+ new Function(script.innerText)();
21
39
  }
22
- else if (!script.type ||
23
- [
24
- "text/javascript",
25
- "application/javascript",
26
- "application/ecmascript",
27
- ].includes(script.type)) {
28
- if (scriptsRun.includes(script.innerText)) {
29
- continue;
30
- }
31
- try {
32
- scriptsRun.push(script.innerText);
33
- new Function(script.innerText)();
34
- }
35
- catch (error) {
36
- console.warn("`CustomCode`: Error running script:", error);
37
- }
40
+ catch (error) {
41
+ console.warn("`CustomCode`: Error running script:", error);
38
42
  }
39
43
  }
40
44
  }
41
- }
42
- let elem;
43
- let scriptsInserted = [];
44
- let scriptsRun = [];
45
- onMount(() => {
46
- findAndRunScripts();
47
45
  });
48
46
  </script>
49
47
 
50
48
  <div
51
- bind:this={elem}
49
+ bind:this={elementRef}
52
50
  class={"builder-custom-code" + (replaceNodes ? " replace-nodes" : "")}
53
51
  >
54
52
  {@html code}
@@ -9,24 +9,24 @@ import { createRegisterComponentMessage } from "../../../functions/register-comp
9
9
  import { _track } from "../../../functions/track/index.js";
10
10
  import builderContext from "../../../context/builder.context.js";
11
11
  import { registerInsertMenu, setupBrowserForEditing, } from "../../../scripts/init-editing.js";
12
- import { checkIsDefined } from "../../../helpers/nullable.js";
13
12
  import { getInteractionPropertiesForEvent } from "../../../functions/track/interaction.js";
14
- import { logger } from "../../../helpers/logger.js";
15
13
  import { fetchOneEntry } from "../../../functions/get-content/index.js";
16
14
  import { isPreviewing } from "../../../functions/is-previewing.js";
17
15
  import "../../../helpers/preview-lru-cache/set.js";
18
16
  import { fastClone } from "../../../functions/fast-clone.js";
19
- export let canTrack = undefined;
17
+ import { logger } from "../../../helpers/logger.js";
18
+ import { getDefaultCanTrack } from "../../../helpers/canTrack.js";
20
19
  export let builderContextSignal;
21
20
  export let model = undefined;
22
21
  export let context = undefined;
22
+ export let canTrack = undefined;
23
23
  export let apiKey;
24
- export let showContent;
25
- export let classNameProp;
26
- export let content = undefined;
27
24
  export let locale = undefined;
28
25
  export let includeRefs = undefined;
29
26
  export let enrich = undefined;
27
+ export let content = undefined;
28
+ export let showContent;
29
+ export let classNameProp;
30
30
  function mergeNewContent(newContent) {
31
31
  const newContentValue = {
32
32
  ...$builderContextSignal.content,
@@ -99,7 +99,7 @@ function onClick(event) {
99
99
  const contentId = $builderContextSignal.content?.id;
100
100
  _track({
101
101
  type: "click",
102
- canTrack: canTrackToUse,
102
+ canTrack: getDefaultCanTrack(canTrack),
103
103
  contentId,
104
104
  apiKey: apiKey,
105
105
  variationId: variationId !== contentId ? variationId : undefined,
@@ -159,90 +159,99 @@ function emitStateUpdate() {
159
159
  }));
160
160
  }
161
161
  }
162
+ function elementRef_onIniteditingbldr(event) {
163
+ forceReRenderCount = forceReRenderCount + 1;
164
+ window.addEventListener("message", processMessage);
165
+ registerInsertMenu();
166
+ setupBrowserForEditing({
167
+ ...(locale
168
+ ? {
169
+ locale: locale,
170
+ }
171
+ : {}),
172
+ ...(includeRefs
173
+ ? {
174
+ includeRefs: includeRefs,
175
+ }
176
+ : {}),
177
+ ...(enrich
178
+ ? {
179
+ enrich: enrich,
180
+ }
181
+ : {}),
182
+ });
183
+ Object.values($builderContextSignal.componentInfos).forEach((registeredComponent) => {
184
+ const message = createRegisterComponentMessage(registeredComponent);
185
+ window.parent?.postMessage(message, "*");
186
+ });
187
+ window.addEventListener("builder:component:stateChangeListenerActivated", emitStateUpdate);
188
+ }
189
+ function elementRef_onInitpreviewingbldr(event) {
190
+ const searchParams = new URL(location.href).searchParams;
191
+ const searchParamPreviewModel = searchParams.get("builder.preview");
192
+ const searchParamPreviewId = searchParams.get(`builder.preview.${searchParamPreviewModel}`);
193
+ const previewApiKey = searchParams.get("apiKey") || searchParams.get("builder.space");
194
+ /**
195
+ * Make sure that:
196
+ * - the preview model name is the same as the one we're rendering, since there can be multiple models rendered
197
+ * at the same time, e.g. header/page/footer.
198
+ * - the API key is the same, since we don't want to preview content from other organizations.
199
+ * - if there is content, that the preview ID is the same as that of the one we receive.
200
+ *
201
+ * TO-DO: should we only update the state when there is a change?
202
+ **/
203
+ if (searchParamPreviewModel === model &&
204
+ previewApiKey === apiKey &&
205
+ (!content || searchParamPreviewId === content.id)) {
206
+ fetchOneEntry({
207
+ model: model,
208
+ apiKey: apiKey,
209
+ apiVersion: $builderContextSignal.apiVersion,
210
+ }).then((content) => {
211
+ if (content) {
212
+ mergeNewContent(content);
213
+ }
214
+ });
215
+ }
216
+ }
162
217
  let elementRef;
163
- let canTrackToUse = checkIsDefined(canTrack) ? canTrack : true;
164
218
  let forceReRenderCount = 0;
165
219
  let lastUpdated = 0;
166
220
  let shouldSendResetCookie = false;
167
221
  let httpReqsData = {};
168
222
  let clicked = false;
169
223
  onMount(() => {
170
- if (!apiKey) {
171
- logger.error("No API key provided to `RenderContent` component. This can cause issues. Please provide an API key using the `apiKey` prop.");
172
- }
173
224
  if (isBrowser()) {
174
- if (isEditing()) {
175
- forceReRenderCount = forceReRenderCount + 1;
176
- window.addEventListener("message", processMessage);
177
- registerInsertMenu();
178
- setupBrowserForEditing({
179
- ...(locale
180
- ? {
181
- locale: locale,
182
- }
183
- : {}),
184
- ...(includeRefs
185
- ? {
186
- includeRefs: includeRefs,
187
- }
188
- : {}),
189
- ...(enrich
190
- ? {
191
- enrich: enrich,
192
- }
193
- : {}),
194
- });
195
- Object.values($builderContextSignal.componentInfos).forEach((registeredComponent) => {
196
- const message = createRegisterComponentMessage(registeredComponent);
197
- window.parent?.postMessage(message, "*");
198
- });
199
- window.addEventListener("builder:component:stateChangeListenerActivated", emitStateUpdate);
225
+ if (isEditing() && elementRef) {
226
+ elementRef.dispatchEvent(new CustomEvent("initeditingbldr"));
200
227
  }
201
- if ($builderContextSignal.content) {
228
+ const shouldTrackImpression = $builderContextSignal.content && getDefaultCanTrack(canTrack);
229
+ if (shouldTrackImpression) {
202
230
  const variationId = $builderContextSignal.content?.testVariationId;
203
231
  const contentId = $builderContextSignal.content?.id;
232
+ const apiKeyProp = apiKey;
204
233
  _track({
205
234
  type: "impression",
206
- canTrack: canTrackToUse,
235
+ canTrack: true,
207
236
  contentId,
208
- apiKey: apiKey,
237
+ apiKey: apiKeyProp,
209
238
  variationId: variationId !== contentId ? variationId : undefined,
210
239
  });
211
240
  }
212
241
  // override normal content in preview mode
213
- if (isPreviewing()) {
214
- const searchParams = new URL(location.href).searchParams;
215
- const searchParamPreviewModel = searchParams.get("builder.preview");
216
- const searchParamPreviewId = searchParams.get(`builder.preview.${searchParamPreviewModel}`);
217
- const previewApiKey = searchParams.get("apiKey") || searchParams.get("builder.space");
218
- /**
219
- * Make sure that:
220
- * - the preview model name is the same as the one we're rendering, since there can be multiple models rendered
221
- * at the same time, e.g. header/page/footer.
222
- * - the API key is the same, since we don't want to preview content from other organizations.
223
- * - if there is content, that the preview ID is the same as that of the one we receive.
224
- *
225
- * TO-DO: should we only update the state when there is a change?
226
- **/
227
- if (searchParamPreviewModel === model &&
228
- previewApiKey === apiKey &&
229
- (!content || searchParamPreviewId === content.id)) {
230
- fetchOneEntry({
231
- model: model,
232
- apiKey: apiKey,
233
- apiVersion: $builderContextSignal.apiVersion,
234
- }).then((content) => {
235
- if (content) {
236
- mergeNewContent(content);
237
- }
238
- });
239
- }
242
+ if (isPreviewing() && elementRef) {
243
+ elementRef.dispatchEvent(new CustomEvent("initpreviewingbldr"));
240
244
  }
241
- evaluateJsCode();
242
- runHttpRequests();
243
- emitStateUpdate();
244
245
  }
245
246
  });
247
+ onMount(() => {
248
+ if (!apiKey) {
249
+ logger.error("No API key provided to `RenderContent` component. This can cause issues. Please provide an API key using the `apiKey` prop.");
250
+ }
251
+ evaluateJsCode();
252
+ runHttpRequests();
253
+ emitStateUpdate();
254
+ });
246
255
  function onUpdateFn_0(..._args) {
247
256
  if (content) {
248
257
  mergeNewContent(content);
@@ -277,6 +286,7 @@ onDestroy(() => {
277
286
 
278
287
  {#if $builderContextSignal.content}
279
288
  <div
289
+ {...{}}
280
290
  key={forceReRenderCount}
281
291
  bind:this={elementRef}
282
292
  on:click={(event) => {
@@ -292,6 +302,12 @@ onDestroy(() => {
292
302
  "aria-hidden": true,
293
303
  }}
294
304
  class={classNameProp}
305
+ on:initeditingbldr={(event) => {
306
+ elementRef_onIniteditingbldr(event);
307
+ }}
308
+ on:initpreviewingbldr={(event) => {
309
+ elementRef_onInitpreviewingbldr(event);
310
+ }}
295
311
  >
296
312
  <slot />
297
313
  </div>
@@ -9,17 +9,17 @@ import type { ContentProps } from "../content.types.js";
9
9
  import type { Writable } from "svelte/store";
10
10
  declare const __propDef: {
11
11
  props: {
12
- canTrack?: BuilderEditorProps["canTrack"];
13
12
  builderContextSignal: BuilderEditorProps["builderContextSignal"];
14
13
  model?: BuilderEditorProps["model"];
15
14
  context?: BuilderEditorProps["context"];
15
+ canTrack?: BuilderEditorProps["canTrack"];
16
16
  apiKey: BuilderEditorProps["apiKey"];
17
- showContent: BuilderEditorProps["showContent"];
18
- classNameProp: BuilderEditorProps["classNameProp"];
19
- content?: BuilderEditorProps["content"];
20
17
  locale?: BuilderEditorProps["locale"];
21
18
  includeRefs?: BuilderEditorProps["includeRefs"];
22
19
  enrich?: BuilderEditorProps["enrich"];
20
+ content?: BuilderEditorProps["content"];
21
+ showContent: BuilderEditorProps["showContent"];
22
+ classNameProp: BuilderEditorProps["classNameProp"];
23
23
  };
24
24
  events: {
25
25
  [evt: string]: CustomEvent<any>;
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "0.6.4";
1
+ export declare const SDK_VERSION = "0.7.1-0";
@@ -1 +1 @@
1
- export const SDK_VERSION = "0.6.4";
1
+ export const SDK_VERSION = "0.7.1-0";
@@ -0,0 +1,6 @@
1
+ import type { ExecutorArgs } from './helpers.js';
2
+ /**
3
+ * Even though we have separate runtimes for browser/node/edge, sometimes frameworks will
4
+ * end up sending the server runtime code to the browser (most notably in dev mode).
5
+ */
6
+ export declare const chooseBrowserOrServerEval: (args: ExecutorArgs) => any;
@@ -0,0 +1,19 @@
1
+ import { runInBrowser } from './browser-runtime/browser.js';
2
+ import { isBrowser } from '../is-browser.js';
3
+ /**
4
+ * THIS IS A MAGICAL IMPORT. It is aliased by the build process of every SDK configuration, so that
5
+ * it points to the correct runtime for that configuration, which are expected to live exactly at:
6
+ * - ./browser-runtime/index.js
7
+ * - ./node-runtime/index.js
8
+ * - ./edge-runtime/index.js
9
+ *
10
+ * We have code in `/output-generation` that does this aliasing, and is re-used by each SDK.
11
+ * Also, each individual `tsconfig.json` aliases this import to the browser runtime so that the
12
+ * types can be resolved correctly.
13
+ */
14
+ import { evaluator } from '../../../browser/functions/evaluate/browser-runtime/index.js';
15
+ /**
16
+ * Even though we have separate runtimes for browser/node/edge, sometimes frameworks will
17
+ * end up sending the server runtime code to the browser (most notably in dev mode).
18
+ */
19
+ export const chooseBrowserOrServerEval = (args) => isBrowser() ? runInBrowser(args) : evaluator(args);
@@ -1,6 +1,2 @@
1
- import type { ExecutorArgs } from './helpers.js';
2
- export type EvaluatorArgs = Omit<ExecutorArgs, 'builder' | 'event'> & {
3
- event?: Event;
4
- isExpression?: boolean;
5
- };
1
+ import type { EvaluatorArgs } from './helpers.js';
6
2
  export declare function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression }: EvaluatorArgs): any;
@@ -1,39 +1,16 @@
1
1
  import { logger } from '../../helpers/logger.js';
2
- import { isBrowser } from '../is-browser.js';
3
- import { isEditing } from '../is-editing.js';
4
- import { getUserAttributes } from '../track/helpers.js';
5
- /**
6
- * THIS IS A MAGICAL IMPORT. It is aliased by the build process of every SDK configuration, so that
7
- * it points to the correct runtime for that configuration, which are expected to live exactly at:
8
- * - ./browser-runtime/index.js
9
- * - ./node-runtime/index.js
10
- * - ./edge-runtime/index.js
11
- *
12
- * We have code in `/output-generation` that does this aliasing, and is re-used by each SDK.
13
- * Also, each individual `tsconfig.json` aliases this import to the browser runtime so that the
14
- * types can be resolved correctly.
15
- */
16
- import { evaluator } from '../../../browser/functions/evaluate/browser-runtime/index.js';
2
+ import { getBuilderGlobals, parseCode } from './helpers.js';
3
+ import { chooseBrowserOrServerEval } from './choose-eval.js';
17
4
  export function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression = true }) {
18
5
  if (code === '') {
19
6
  logger.warn('Skipping evaluation of empty code block.');
20
7
  return;
21
8
  }
22
- const builder = {
23
- isEditing: isEditing(),
24
- isBrowser: isBrowser(),
25
- isServer: !isBrowser(),
26
- getUserAttributes: () => getUserAttributes()
27
- };
28
- // Be able to handle simple expressions like "state.foo" or "1 + 1"
29
- // as well as full blocks like "var foo = "bar"; return foo"
30
- const useReturn =
31
- // we disable this for cases where we definitely don't want a return
32
- isExpression && !(code.includes(';') || code.includes(' return ') || code.trim().startsWith('return '));
33
- const useCode = useReturn ? `return (${code});` : code;
34
9
  const args = {
35
- code: useCode,
36
- builder,
10
+ code: parseCode(code, {
11
+ isExpression
12
+ }),
13
+ builder: getBuilderGlobals(),
37
14
  context,
38
15
  event,
39
16
  rootSetState,
@@ -41,7 +18,7 @@ export function evaluate({ code, context, localState, rootState, rootSetState, e
41
18
  localState
42
19
  };
43
20
  try {
44
- return evaluator(args);
21
+ return chooseBrowserOrServerEval(args);
45
22
  }
46
23
  catch (e) {
47
24
  logger.error('Failed code evaluation: ' + e.message, {
@@ -1,5 +1,9 @@
1
1
  import type { BuilderContextInterface, BuilderRenderState } from '../../context/types.js';
2
- import type { getUserAttributes } from '../track/helpers.js';
2
+ import { getUserAttributes } from '../track/helpers.js';
3
+ export type EvaluatorArgs = Omit<ExecutorArgs, 'builder' | 'event'> & {
4
+ event?: Event;
5
+ isExpression?: boolean;
6
+ };
3
7
  export type BuilderGlobals = {
4
8
  isEditing: boolean | undefined;
5
9
  isBrowser: boolean | undefined;
@@ -16,3 +20,5 @@ export type FunctionArguments = ReturnType<typeof getFunctionArguments>;
16
20
  export declare const getFunctionArguments: ({ builder, context, event, state }: Pick<ExecutorArgs, "context" | "builder" | "event"> & {
17
21
  state: BuilderRenderState;
18
22
  }) => [string, Event | BuilderRenderState | import("../../context/types.js").BuilderRenderContext | BuilderGlobals | undefined][];
23
+ export declare const getBuilderGlobals: () => BuilderGlobals;
24
+ export declare const parseCode: (code: string, { isExpression }: Pick<EvaluatorArgs, 'isExpression'>) => string;
@@ -1,3 +1,6 @@
1
+ import { isBrowser } from '../is-browser.js';
2
+ import { isEditing } from '../is-editing.js';
3
+ import { getUserAttributes } from '../track/helpers.js';
1
4
  export const getFunctionArguments = ({ builder, context, event, state }) => {
2
5
  return Object.entries({
3
6
  state,
@@ -8,3 +11,18 @@ export const getFunctionArguments = ({ builder, context, event, state }) => {
8
11
  event
9
12
  });
10
13
  };
14
+ export const getBuilderGlobals = () => ({
15
+ isEditing: isEditing(),
16
+ isBrowser: isBrowser(),
17
+ isServer: !isBrowser(),
18
+ getUserAttributes: () => getUserAttributes()
19
+ });
20
+ export const parseCode = (code, { isExpression = true }) => {
21
+ // Be able to handle simple expressions like "state.foo" or "1 + 1"
22
+ // as well as full blocks like "var foo = "bar"; return foo"
23
+ const useReturn =
24
+ // we disable this for cases where we definitely don't want a return
25
+ isExpression && !(code.includes(';') || code.includes(' return ') || code.trim().startsWith('return '));
26
+ const useCode = useReturn ? `return (${code});` : code;
27
+ return useCode;
28
+ };
@@ -1 +1 @@
1
- export { evaluator } from '../browser-runtime/index.js';
1
+ export { runInNode as evaluator } from './node-runtime.js';
@@ -1 +1 @@
1
- export { evaluator } from '../browser-runtime/index.js';
1
+ export { runInNode as evaluator } from './node-runtime.js';
@@ -0,0 +1,2 @@
1
+ import type { ExecutorArgs } from '../helpers.js';
2
+ export declare const runInNode: ({ code, builder, context, event, localState, rootSetState, rootState }: ExecutorArgs) => any;
@@ -0,0 +1,120 @@
1
+ import { getFunctionArguments } from '../helpers.js';
2
+ import { safeDynamicRequire } from './safeDynamicRequire.js';
3
+ import { fastClone } from '../../fast-clone.js';
4
+ import { set } from '../../set.js';
5
+ const ivm = safeDynamicRequire('isolated-vm');
6
+ const getSyncValName = (key) => `bldr_${key}_sync`;
7
+ const BUILDER_SET_STATE_NAME = 'BUILDER_SET_STATE';
8
+ const INJECTED_IVM_GLOBAL = 'BUILDER_IVM';
9
+ // Convert all argument references to proxies, and pass `copySync` method to target object, to return a copy of the original JS object
10
+ // https://github.com/laverdet/isolated-vm#referencecopysync
11
+ const REF_TO_PROXY_FN = `
12
+ var refToProxy = (obj) => {
13
+ if (typeof obj !== 'object' || obj === null) {
14
+ return obj;
15
+ }
16
+ return new Proxy({}, {
17
+ get(target, key) {
18
+ if (key === 'copySync') {
19
+ return () => obj.copySync();
20
+ }
21
+ const val = obj.getSync(key);
22
+ if (typeof val?.getSync === 'function') {
23
+ return refToProxy(val);
24
+ }
25
+ return val;
26
+ },
27
+ set(target, key, value) {
28
+ const v = typeof value === 'object' ? new ${INJECTED_IVM_GLOBAL}.Reference(value) : value;
29
+ obj.setSync(key, v);
30
+ ${BUILDER_SET_STATE_NAME}(key, value)
31
+ },
32
+ deleteProperty(target, key) {
33
+ obj.deleteSync(key);
34
+ }
35
+ })
36
+ }
37
+ `;
38
+ const processCode = ({ code, args }) => {
39
+ const fnArgs = args.map(([name]) => `var ${name} = refToProxy(${getSyncValName(name)}); `).join('');
40
+ // the output is stringified and parsed back to the parent isolate if needed (when it's an `object`)
41
+ return `
42
+ ${REF_TO_PROXY_FN}
43
+ ${fnArgs}
44
+ function theFunction() {
45
+ ${code}
46
+ }
47
+
48
+ let output = theFunction()
49
+
50
+ if (typeof output === 'object' && output !== null) {
51
+ output = JSON.stringify(output.copySync ? output.copySync() : output);
52
+ }
53
+
54
+ output;
55
+ `;
56
+ };
57
+ const getIsolateContext = () => {
58
+ // if (Builder.serverContext) {
59
+ // return Builder.serverContext;
60
+ // }
61
+ // Builder.setServerContext(isolate.createContextSync());
62
+ const isolate = new ivm.Isolate({
63
+ memoryLimit: 128
64
+ });
65
+ return isolate.createContextSync();
66
+ };
67
+ export const runInNode = ({ code, builder, context, event, localState, rootSetState, rootState }) => {
68
+ const state = fastClone({
69
+ ...rootState,
70
+ ...localState
71
+ });
72
+ const args = getFunctionArguments({
73
+ builder,
74
+ context,
75
+ event,
76
+ state
77
+ });
78
+ const isolateContext = getIsolateContext();
79
+ const jail = isolateContext.global;
80
+ // This makes the global object available in the context as `global`. We use `derefInto()` here
81
+ // because otherwise `global` would actually be a Reference{} object in the new isolate.
82
+ jail.setSync('global', jail.derefInto());
83
+ // We will create a basic `log` function for the new isolate to use.
84
+ jail.setSync('log', function (...logArgs) {
85
+ console.log(...logArgs);
86
+ });
87
+ /**
88
+ * Propagate state changes back to the reactive root state.
89
+ */
90
+ jail.setSync(BUILDER_SET_STATE_NAME, function (key, value) {
91
+ // mutate the `rootState` object itself. Important for cases where we do not have `rootSetState`
92
+ // like Qwik.
93
+ set(rootState, key, value);
94
+ // call the `rootSetState` function if it exists
95
+ rootSetState?.(rootState);
96
+ });
97
+ args.forEach(([key, arg]) => {
98
+ const val = typeof arg === 'object' ? new ivm.Reference(
99
+ // workaround: methods with default values for arguments is not being cloned over
100
+ key === 'builder' ? {
101
+ ...arg,
102
+ getUserAttributes: () => arg.getUserAttributes()
103
+ } : arg) : null;
104
+ jail.setSync(getSyncValName(key), val);
105
+ });
106
+ jail.setSync(INJECTED_IVM_GLOBAL, ivm);
107
+ const evalStr = processCode({
108
+ code,
109
+ args
110
+ });
111
+ const resultStr = isolateContext.evalSync(evalStr);
112
+ try {
113
+ // returning objects throw errors in isolated vm, so we stringify it and parse it back
114
+ const res = JSON.parse(resultStr);
115
+ return res;
116
+ }
117
+ catch (_error) {
118
+ return resultStr;
119
+ }
120
+ };
@@ -0,0 +1,13 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="react-native" />
3
+ /**
4
+ * Allows us to require node-specific code safely within bundlers.
5
+ *
6
+ * https://github.com/laverdet/isolated-vm/issues/423
7
+ *
8
+ * NOTE: this code should only ever run in `node`, and never in an `edge` runtime.
9
+ * This is guaranteed by the fact that each SDK has separate bundles for each runtime.
10
+ * However, it might still end up running in the `browser` if the `node` bundle is loaded in the
11
+ * browser (which happens in many apps' dev mode, and certain other scenarios like Qwik's prod mode).
12
+ */
13
+ export declare let safeDynamicRequire: typeof require;
@@ -0,0 +1,18 @@
1
+ const noop = () => null;
2
+ /**
3
+ * Allows us to require node-specific code safely within bundlers.
4
+ *
5
+ * https://github.com/laverdet/isolated-vm/issues/423
6
+ *
7
+ * NOTE: this code should only ever run in `node`, and never in an `edge` runtime.
8
+ * This is guaranteed by the fact that each SDK has separate bundles for each runtime.
9
+ * However, it might still end up running in the `browser` if the `node` bundle is loaded in the
10
+ * browser (which happens in many apps' dev mode, and certain other scenarios like Qwik's prod mode).
11
+ */
12
+ export let safeDynamicRequire = noop;
13
+ try {
14
+ safeDynamicRequire = eval('require');
15
+ }
16
+ catch (error) {
17
+ /* empty */
18
+ }