@builder.io/sdk-svelte 5.0.1 → 5.1.1
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/lib/browser/components/block/components/block-wrapper.svelte +6 -0
- package/lib/browser/components/block/components/interactive-element.svelte +6 -0
- package/lib/browser/constants/sdk-version.d.ts +1 -1
- package/lib/browser/constants/sdk-version.js +1 -1
- package/lib/browser/functions/evaluate/evaluate.d.ts +1 -1
- package/lib/browser/functions/evaluate/evaluate.js +2 -2
- package/lib/browser/functions/evaluate/helpers.d.ts +9 -1
- package/lib/browser/functions/evaluate/helpers.js +21 -2
- package/lib/browser/functions/evaluate/node-runtime/node-runtime.js +72 -67
- package/lib/browser/functions/get-block-actions-handler.d.ts +2 -0
- package/lib/browser/functions/get-block-actions-handler.js +2 -1
- package/lib/browser/functions/get-block-actions.d.ts +2 -0
- package/lib/edge/components/block/components/block-wrapper.svelte +6 -0
- package/lib/edge/components/block/components/interactive-element.svelte +6 -0
- package/lib/edge/constants/sdk-version.d.ts +1 -1
- package/lib/edge/constants/sdk-version.js +1 -1
- package/lib/edge/functions/evaluate/evaluate.d.ts +1 -1
- package/lib/edge/functions/evaluate/evaluate.js +2 -2
- package/lib/edge/functions/evaluate/helpers.d.ts +9 -1
- package/lib/edge/functions/evaluate/helpers.js +21 -2
- package/lib/edge/functions/evaluate/node-runtime/node-runtime.js +72 -67
- package/lib/edge/functions/get-block-actions-handler.d.ts +2 -0
- package/lib/edge/functions/get-block-actions-handler.js +2 -1
- package/lib/edge/functions/get-block-actions.d.ts +2 -0
- package/lib/node/components/block/components/block-wrapper.svelte +6 -0
- package/lib/node/components/block/components/interactive-element.svelte +6 -0
- package/lib/node/constants/sdk-version.d.ts +1 -1
- package/lib/node/constants/sdk-version.js +1 -1
- package/lib/node/functions/evaluate/evaluate.d.ts +1 -1
- package/lib/node/functions/evaluate/evaluate.js +2 -2
- package/lib/node/functions/evaluate/helpers.d.ts +9 -1
- package/lib/node/functions/evaluate/helpers.js +21 -2
- package/lib/node/functions/evaluate/node-runtime/node-runtime.js +72 -67
- package/lib/node/functions/get-block-actions-handler.d.ts +2 -0
- package/lib/node/functions/get-block-actions-handler.js +2 -1
- package/lib/node/functions/get-block-actions.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/block/components/block-wrapper.svelte +6 -0
- package/src/components/block/components/interactive-element.svelte +6 -0
- package/src/constants/sdk-version.ts +1 -1
- package/src/functions/evaluate/evaluate.ts +3 -2
- package/src/functions/evaluate/helpers.ts +29 -2
- package/src/functions/evaluate/node-runtime/node-runtime.ts +73 -69
- package/src/functions/get-block-actions-handler.ts +4 -1
- package/src/functions/get-block-actions.ts +2 -0
|
@@ -24,5 +24,11 @@ export let context;
|
|
|
24
24
|
localState: $context.localState,
|
|
25
25
|
context: $context.context,
|
|
26
26
|
stripPrefix: true,
|
|
27
|
+
trackingContext: {
|
|
28
|
+
apiKey: $context.apiKey,
|
|
29
|
+
canTrack: $context.canTrack ?? true,
|
|
30
|
+
contentId: $context.content?.id,
|
|
31
|
+
variationId: $context.content?.testVariationId,
|
|
32
|
+
},
|
|
27
33
|
})}><slot /></DynamicRenderer
|
|
28
34
|
>
|
|
@@ -24,6 +24,12 @@ $: attributes = () => {
|
|
|
24
24
|
rootSetState: $context.rootSetState,
|
|
25
25
|
localState: $context.localState,
|
|
26
26
|
context: $context.context,
|
|
27
|
+
trackingContext: {
|
|
28
|
+
apiKey: $context.apiKey,
|
|
29
|
+
canTrack: $context.canTrack ?? true,
|
|
30
|
+
contentId: $context.content?.id,
|
|
31
|
+
variationId: $context.content?.testVariationId,
|
|
32
|
+
},
|
|
27
33
|
}),
|
|
28
34
|
}
|
|
29
35
|
: {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "5.
|
|
1
|
+
export declare const SDK_VERSION = "5.1.1";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "5.
|
|
1
|
+
export const SDK_VERSION = "5.1.1";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EvaluatorArgs } from './helpers';
|
|
2
2
|
type EvalValue = unknown;
|
|
3
3
|
export declare const getSimpleExpressionGetPath: (code: string) => string | undefined;
|
|
4
|
-
export declare function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression }: EvaluatorArgs): EvalValue;
|
|
4
|
+
export declare function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression, trackingContext }: EvaluatorArgs): EvalValue;
|
|
5
5
|
export {};
|
|
@@ -16,7 +16,7 @@ const VIRTUAL_INDEX_REGEX = /(\s)*var(\s)+_virtual_index(\s)*=(\s)*state(?<getPa
|
|
|
16
16
|
export const getSimpleExpressionGetPath = (code) => {
|
|
17
17
|
return STATE_GETTER_REGEX.exec(code.trim())?.groups?.getPath?.slice(1) || VIRTUAL_INDEX_REGEX.exec(code.trim())?.groups?.getPath?.slice(1);
|
|
18
18
|
};
|
|
19
|
-
export function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression = true }) {
|
|
19
|
+
export function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression = true, trackingContext }) {
|
|
20
20
|
if (code.trim() === '') {
|
|
21
21
|
return undefined;
|
|
22
22
|
}
|
|
@@ -37,7 +37,7 @@ export function evaluate({ code, context, localState, rootState, rootSetState, e
|
|
|
37
37
|
code: parseCode(code, {
|
|
38
38
|
isExpression
|
|
39
39
|
}),
|
|
40
|
-
builder: getBuilderGlobals(),
|
|
40
|
+
builder: getBuilderGlobals(trackingContext),
|
|
41
41
|
context,
|
|
42
42
|
event,
|
|
43
43
|
rootSetState,
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import type { BuilderContextInterface, BuilderRenderState } from '../../context/types';
|
|
2
2
|
import { getUserAttributes } from '../track/helpers';
|
|
3
|
+
export type TrackingContext = {
|
|
4
|
+
apiKey: string | null;
|
|
5
|
+
canTrack?: boolean;
|
|
6
|
+
contentId?: string;
|
|
7
|
+
variationId?: string;
|
|
8
|
+
};
|
|
3
9
|
export type EvaluatorArgs = Omit<ExecutorArgs, 'builder' | 'event'> & {
|
|
4
10
|
event?: Event;
|
|
5
11
|
isExpression?: boolean;
|
|
12
|
+
trackingContext?: TrackingContext;
|
|
6
13
|
};
|
|
7
14
|
export type BuilderGlobals = {
|
|
8
15
|
isEditing: boolean | undefined;
|
|
9
16
|
isBrowser: boolean | undefined;
|
|
10
17
|
isServer: boolean | undefined;
|
|
11
18
|
getUserAttributes: typeof getUserAttributes;
|
|
19
|
+
trackConversion: (amount?: number, customProperties?: Record<string, any>) => void;
|
|
12
20
|
};
|
|
13
21
|
export type ExecutorArgs = Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'> & {
|
|
14
22
|
code: string;
|
|
@@ -20,7 +28,7 @@ export type FunctionArguments = ReturnType<typeof getFunctionArguments>;
|
|
|
20
28
|
export declare const getFunctionArguments: ({ builder, context, event, state }: Pick<ExecutorArgs, "context" | "builder" | "event"> & {
|
|
21
29
|
state: BuilderRenderState;
|
|
22
30
|
}) => [string, Event | BuilderRenderState | import("../../context/types").BuilderRenderContext | BuilderGlobals | undefined][];
|
|
23
|
-
export declare const getBuilderGlobals: () => BuilderGlobals;
|
|
31
|
+
export declare const getBuilderGlobals: (trackingContext?: TrackingContext) => BuilderGlobals;
|
|
24
32
|
export declare const parseCode: (code: string, { isExpression }: Pick<EvaluatorArgs, 'isExpression'>) => string;
|
|
25
33
|
export declare function flattenState({ rootState, localState, rootSetState }: {
|
|
26
34
|
rootState: Record<string | symbol, any>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isBrowser } from '../is-browser';
|
|
2
2
|
import { isEditing } from '../is-editing';
|
|
3
3
|
import { getUserAttributes } from '../track/helpers';
|
|
4
|
+
import { _track } from '../track/index';
|
|
4
5
|
export const getFunctionArguments = ({ builder, context, event, state }) => {
|
|
5
6
|
return Object.entries({
|
|
6
7
|
state,
|
|
@@ -11,11 +12,29 @@ export const getFunctionArguments = ({ builder, context, event, state }) => {
|
|
|
11
12
|
event
|
|
12
13
|
});
|
|
13
14
|
};
|
|
14
|
-
export const getBuilderGlobals = () => ({
|
|
15
|
+
export const getBuilderGlobals = (trackingContext) => ({
|
|
15
16
|
isEditing: isEditing(),
|
|
16
17
|
isBrowser: isBrowser(),
|
|
17
18
|
isServer: !isBrowser(),
|
|
18
|
-
getUserAttributes: () => getUserAttributes()
|
|
19
|
+
getUserAttributes: () => getUserAttributes(),
|
|
20
|
+
trackConversion: (amount, customProperties) => {
|
|
21
|
+
if (!trackingContext?.apiKey || trackingContext?.canTrack === false) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
_track({
|
|
25
|
+
type: 'conversion',
|
|
26
|
+
apiKey: trackingContext.apiKey,
|
|
27
|
+
canTrack: trackingContext.canTrack ?? true,
|
|
28
|
+
contentId: trackingContext.contentId,
|
|
29
|
+
variationId: trackingContext.variationId !== trackingContext.contentId ? trackingContext.variationId : undefined,
|
|
30
|
+
metadata: {
|
|
31
|
+
...(customProperties || {}),
|
|
32
|
+
...(amount !== undefined ? {
|
|
33
|
+
amount
|
|
34
|
+
} : {})
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
19
38
|
});
|
|
20
39
|
export const parseCode = (code, { isExpression = true }) => {
|
|
21
40
|
// Be able to handle simple expressions like "state.foo" or "1 + 1"
|
|
@@ -56,17 +56,23 @@ if (typeof output === 'object' && output !== null) {
|
|
|
56
56
|
`;
|
|
57
57
|
};
|
|
58
58
|
let IVM_INSTANCE = null;
|
|
59
|
-
|
|
59
|
+
// Create fresh isolates per request to prevent memory leaks
|
|
60
|
+
let IVM_OPTIONS = {
|
|
61
|
+
memoryLimit: 128
|
|
62
|
+
};
|
|
60
63
|
/**
|
|
61
64
|
* Set the `isolated-vm` instance to be used by the node runtime.
|
|
62
65
|
* This is useful for environments that are not able to rely on our
|
|
63
66
|
* `safeDynamicRequire` trick to import the `isolated-vm` package.
|
|
64
67
|
*/
|
|
65
|
-
export const setIvm = (ivm, options
|
|
68
|
+
export const setIvm = (ivm, options) => {
|
|
66
69
|
if (IVM_INSTANCE)
|
|
67
70
|
return;
|
|
68
71
|
IVM_INSTANCE = ivm;
|
|
69
|
-
|
|
72
|
+
// Store options to be used per-request in runInNode
|
|
73
|
+
if (options) {
|
|
74
|
+
IVM_OPTIONS = options;
|
|
75
|
+
}
|
|
70
76
|
};
|
|
71
77
|
// only mention the script for SDKs that have it.
|
|
72
78
|
const SHOULD_MENTION_INITIALIZE_SCRIPT = SDK_NAME === '@builder.io/sdk-react-nextjs' || SDK_NAME === '@builder.io/sdk-react' || SDK_NAME === '@builder.io/sdk-qwik' || SDK_NAME === '@builder.io/sdk-vue';
|
|
@@ -91,73 +97,72 @@ const getIvm = () => {
|
|
|
91
97
|
For more information, visit https://builder.io/c/docs/integration-tips#enabling-data-bindings-in-node-environments`;
|
|
92
98
|
throw new Error(ERROR_MESSAGE);
|
|
93
99
|
};
|
|
94
|
-
function setIsolateContext(options = {
|
|
95
|
-
memoryLimit: 128
|
|
96
|
-
}) {
|
|
97
|
-
if (IVM_CONTEXT)
|
|
98
|
-
return IVM_CONTEXT;
|
|
99
|
-
const ivm = getIvm();
|
|
100
|
-
const isolate = new ivm.Isolate(options);
|
|
101
|
-
const context = isolate.createContextSync();
|
|
102
|
-
const jail = context.global;
|
|
103
|
-
// This makes the global object available in the context as `global`. We use `derefInto()` here
|
|
104
|
-
// because otherwise `global` would actually be a Reference{} object in the new isolate.
|
|
105
|
-
jail.setSync('global', jail.derefInto());
|
|
106
|
-
// We will create a basic `log` function for the new isolate to use.
|
|
107
|
-
jail.setSync('log', function (...logArgs) {
|
|
108
|
-
console.log(...logArgs);
|
|
109
|
-
});
|
|
110
|
-
jail.setSync(INJECTED_IVM_GLOBAL, ivm);
|
|
111
|
-
IVM_CONTEXT = context;
|
|
112
|
-
return context;
|
|
113
|
-
}
|
|
114
|
-
const getIsolateContext = () => {
|
|
115
|
-
return setIsolateContext();
|
|
116
|
-
};
|
|
117
100
|
export const runInNode = ({ code, builder, context, event, localState, rootSetState, rootState }) => {
|
|
118
101
|
const ivm = getIvm();
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
...localState
|
|
122
|
-
});
|
|
123
|
-
const args = getFunctionArguments({
|
|
124
|
-
builder,
|
|
125
|
-
context,
|
|
126
|
-
event,
|
|
127
|
-
state
|
|
128
|
-
});
|
|
129
|
-
const isolateContext = getIsolateContext();
|
|
130
|
-
const jail = isolateContext.global;
|
|
131
|
-
/**
|
|
132
|
-
* Propagate state changes back to the reactive root state.
|
|
133
|
-
*/
|
|
134
|
-
jail.setSync(BUILDER_SET_STATE_NAME, function (key, value) {
|
|
135
|
-
// mutate the `rootState` object itself. Important for cases where we do not have `rootSetState`
|
|
136
|
-
// like Qwik.
|
|
137
|
-
set(rootState, key, value);
|
|
138
|
-
// call the `rootSetState` function if it exists
|
|
139
|
-
rootSetState?.(rootState);
|
|
140
|
-
});
|
|
141
|
-
args.forEach(([key, arg]) => {
|
|
142
|
-
const val = typeof arg === 'object' ? new ivm.Reference(
|
|
143
|
-
// workaround: methods with default values for arguments is not being cloned over
|
|
144
|
-
key === 'builder' ? {
|
|
145
|
-
...arg,
|
|
146
|
-
getUserAttributes: () => arg.getUserAttributes()
|
|
147
|
-
} : arg) : null;
|
|
148
|
-
jail.setSync(getSyncValName(key), val);
|
|
149
|
-
});
|
|
150
|
-
const evalStr = processCode({
|
|
151
|
-
code,
|
|
152
|
-
args
|
|
153
|
-
});
|
|
154
|
-
const resultStr = isolateContext.evalClosureSync(evalStr);
|
|
102
|
+
// Use stored options from setIvm, or default to { memoryLimit: 128 }
|
|
103
|
+
let isolate;
|
|
155
104
|
try {
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
105
|
+
isolate = new ivm.Isolate(IVM_OPTIONS);
|
|
106
|
+
const isolateContext = isolate.createContextSync();
|
|
107
|
+
const jail = isolateContext.global;
|
|
108
|
+
// Setup the isolate
|
|
109
|
+
jail.setSync('global', jail.derefInto());
|
|
110
|
+
jail.setSync('log', function (...logArgs) {
|
|
111
|
+
console.log(...logArgs);
|
|
112
|
+
});
|
|
113
|
+
jail.setSync(INJECTED_IVM_GLOBAL, ivm);
|
|
114
|
+
const state = fastClone({
|
|
115
|
+
...rootState,
|
|
116
|
+
...localState
|
|
117
|
+
});
|
|
118
|
+
const args = getFunctionArguments({
|
|
119
|
+
builder,
|
|
120
|
+
context,
|
|
121
|
+
event,
|
|
122
|
+
state
|
|
123
|
+
});
|
|
124
|
+
/**
|
|
125
|
+
* Propagate state changes back to the reactive root state.
|
|
126
|
+
*/
|
|
127
|
+
jail.setSync(BUILDER_SET_STATE_NAME, function (key, value) {
|
|
128
|
+
// mutate the `rootState` object itself. Important for cases where we do not have `rootSetState`
|
|
129
|
+
// like Qwik.
|
|
130
|
+
set(rootState, key, value);
|
|
131
|
+
// call the `rootSetState` function if it exists
|
|
132
|
+
rootSetState?.(rootState);
|
|
133
|
+
});
|
|
134
|
+
args.forEach(([key, arg]) => {
|
|
135
|
+
const val = typeof arg === 'object' ? new ivm.Reference(
|
|
136
|
+
// workaround: methods with default values for arguments is not being cloned over
|
|
137
|
+
key === 'builder' ? {
|
|
138
|
+
...arg,
|
|
139
|
+
getUserAttributes: () => arg.getUserAttributes()
|
|
140
|
+
} : arg) : null;
|
|
141
|
+
jail.setSync(getSyncValName(key), val);
|
|
142
|
+
});
|
|
143
|
+
const evalStr = processCode({
|
|
144
|
+
code,
|
|
145
|
+
args
|
|
146
|
+
});
|
|
147
|
+
const resultStr = isolateContext.evalClosureSync(evalStr);
|
|
148
|
+
try {
|
|
149
|
+
// returning objects throw errors in isolated vm, so we stringify it and parse it back
|
|
150
|
+
const res = JSON.parse(resultStr);
|
|
151
|
+
return res;
|
|
152
|
+
}
|
|
153
|
+
catch (_error) {
|
|
154
|
+
return resultStr;
|
|
155
|
+
}
|
|
159
156
|
}
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
finally {
|
|
158
|
+
// Destroy the entire VM, this frees ALL memory at the C++ level.
|
|
159
|
+
if (isolate) {
|
|
160
|
+
try {
|
|
161
|
+
isolate.dispose();
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
// Ignore disposal errors
|
|
165
|
+
}
|
|
166
|
+
}
|
|
162
167
|
}
|
|
163
168
|
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { BuilderContextInterface } from '../context/types';
|
|
2
2
|
import type { BuilderBlock } from '../types/builder-block';
|
|
3
|
+
import type { TrackingContext } from './evaluate/helpers';
|
|
3
4
|
type Options = {
|
|
4
5
|
block: BuilderBlock;
|
|
6
|
+
trackingContext?: TrackingContext;
|
|
5
7
|
} & Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'>;
|
|
6
8
|
type EventHandler = (event: Event) => any;
|
|
7
9
|
export declare const createEventHandler: (value: string, options: Options) => EventHandler;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { BuilderContextInterface } from '../context/types';
|
|
2
2
|
import type { BuilderBlock } from '../types/builder-block';
|
|
3
|
+
import type { TrackingContext } from './evaluate/helpers';
|
|
3
4
|
type Actions = {
|
|
4
5
|
[index: string]: (event: Event) => any;
|
|
5
6
|
};
|
|
6
7
|
export declare function getBlockActions(options: {
|
|
7
8
|
block: BuilderBlock;
|
|
8
9
|
stripPrefix?: boolean;
|
|
10
|
+
trackingContext?: TrackingContext;
|
|
9
11
|
} & Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'>): Actions;
|
|
10
12
|
export {};
|
|
@@ -24,5 +24,11 @@ export let context;
|
|
|
24
24
|
localState: $context.localState,
|
|
25
25
|
context: $context.context,
|
|
26
26
|
stripPrefix: true,
|
|
27
|
+
trackingContext: {
|
|
28
|
+
apiKey: $context.apiKey,
|
|
29
|
+
canTrack: $context.canTrack ?? true,
|
|
30
|
+
contentId: $context.content?.id,
|
|
31
|
+
variationId: $context.content?.testVariationId,
|
|
32
|
+
},
|
|
27
33
|
})}><slot /></DynamicRenderer
|
|
28
34
|
>
|
|
@@ -24,6 +24,12 @@ $: attributes = () => {
|
|
|
24
24
|
rootSetState: $context.rootSetState,
|
|
25
25
|
localState: $context.localState,
|
|
26
26
|
context: $context.context,
|
|
27
|
+
trackingContext: {
|
|
28
|
+
apiKey: $context.apiKey,
|
|
29
|
+
canTrack: $context.canTrack ?? true,
|
|
30
|
+
contentId: $context.content?.id,
|
|
31
|
+
variationId: $context.content?.testVariationId,
|
|
32
|
+
},
|
|
27
33
|
}),
|
|
28
34
|
}
|
|
29
35
|
: {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "5.
|
|
1
|
+
export declare const SDK_VERSION = "5.1.1";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "5.
|
|
1
|
+
export const SDK_VERSION = "5.1.1";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EvaluatorArgs } from './helpers';
|
|
2
2
|
type EvalValue = unknown;
|
|
3
3
|
export declare const getSimpleExpressionGetPath: (code: string) => string | undefined;
|
|
4
|
-
export declare function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression }: EvaluatorArgs): EvalValue;
|
|
4
|
+
export declare function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression, trackingContext }: EvaluatorArgs): EvalValue;
|
|
5
5
|
export {};
|
|
@@ -16,7 +16,7 @@ const VIRTUAL_INDEX_REGEX = /(\s)*var(\s)+_virtual_index(\s)*=(\s)*state(?<getPa
|
|
|
16
16
|
export const getSimpleExpressionGetPath = (code) => {
|
|
17
17
|
return STATE_GETTER_REGEX.exec(code.trim())?.groups?.getPath?.slice(1) || VIRTUAL_INDEX_REGEX.exec(code.trim())?.groups?.getPath?.slice(1);
|
|
18
18
|
};
|
|
19
|
-
export function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression = true }) {
|
|
19
|
+
export function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression = true, trackingContext }) {
|
|
20
20
|
if (code.trim() === '') {
|
|
21
21
|
return undefined;
|
|
22
22
|
}
|
|
@@ -37,7 +37,7 @@ export function evaluate({ code, context, localState, rootState, rootSetState, e
|
|
|
37
37
|
code: parseCode(code, {
|
|
38
38
|
isExpression
|
|
39
39
|
}),
|
|
40
|
-
builder: getBuilderGlobals(),
|
|
40
|
+
builder: getBuilderGlobals(trackingContext),
|
|
41
41
|
context,
|
|
42
42
|
event,
|
|
43
43
|
rootSetState,
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import type { BuilderContextInterface, BuilderRenderState } from '../../context/types';
|
|
2
2
|
import { getUserAttributes } from '../track/helpers';
|
|
3
|
+
export type TrackingContext = {
|
|
4
|
+
apiKey: string | null;
|
|
5
|
+
canTrack?: boolean;
|
|
6
|
+
contentId?: string;
|
|
7
|
+
variationId?: string;
|
|
8
|
+
};
|
|
3
9
|
export type EvaluatorArgs = Omit<ExecutorArgs, 'builder' | 'event'> & {
|
|
4
10
|
event?: Event;
|
|
5
11
|
isExpression?: boolean;
|
|
12
|
+
trackingContext?: TrackingContext;
|
|
6
13
|
};
|
|
7
14
|
export type BuilderGlobals = {
|
|
8
15
|
isEditing: boolean | undefined;
|
|
9
16
|
isBrowser: boolean | undefined;
|
|
10
17
|
isServer: boolean | undefined;
|
|
11
18
|
getUserAttributes: typeof getUserAttributes;
|
|
19
|
+
trackConversion: (amount?: number, customProperties?: Record<string, any>) => void;
|
|
12
20
|
};
|
|
13
21
|
export type ExecutorArgs = Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'> & {
|
|
14
22
|
code: string;
|
|
@@ -20,7 +28,7 @@ export type FunctionArguments = ReturnType<typeof getFunctionArguments>;
|
|
|
20
28
|
export declare const getFunctionArguments: ({ builder, context, event, state }: Pick<ExecutorArgs, "context" | "builder" | "event"> & {
|
|
21
29
|
state: BuilderRenderState;
|
|
22
30
|
}) => [string, Event | BuilderRenderState | import("../../context/types").BuilderRenderContext | BuilderGlobals | undefined][];
|
|
23
|
-
export declare const getBuilderGlobals: () => BuilderGlobals;
|
|
31
|
+
export declare const getBuilderGlobals: (trackingContext?: TrackingContext) => BuilderGlobals;
|
|
24
32
|
export declare const parseCode: (code: string, { isExpression }: Pick<EvaluatorArgs, 'isExpression'>) => string;
|
|
25
33
|
export declare function flattenState({ rootState, localState, rootSetState }: {
|
|
26
34
|
rootState: Record<string | symbol, any>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isBrowser } from '../is-browser';
|
|
2
2
|
import { isEditing } from '../is-editing';
|
|
3
3
|
import { getUserAttributes } from '../track/helpers';
|
|
4
|
+
import { _track } from '../track/index';
|
|
4
5
|
export const getFunctionArguments = ({ builder, context, event, state }) => {
|
|
5
6
|
return Object.entries({
|
|
6
7
|
state,
|
|
@@ -11,11 +12,29 @@ export const getFunctionArguments = ({ builder, context, event, state }) => {
|
|
|
11
12
|
event
|
|
12
13
|
});
|
|
13
14
|
};
|
|
14
|
-
export const getBuilderGlobals = () => ({
|
|
15
|
+
export const getBuilderGlobals = (trackingContext) => ({
|
|
15
16
|
isEditing: isEditing(),
|
|
16
17
|
isBrowser: isBrowser(),
|
|
17
18
|
isServer: !isBrowser(),
|
|
18
|
-
getUserAttributes: () => getUserAttributes()
|
|
19
|
+
getUserAttributes: () => getUserAttributes(),
|
|
20
|
+
trackConversion: (amount, customProperties) => {
|
|
21
|
+
if (!trackingContext?.apiKey || trackingContext?.canTrack === false) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
_track({
|
|
25
|
+
type: 'conversion',
|
|
26
|
+
apiKey: trackingContext.apiKey,
|
|
27
|
+
canTrack: trackingContext.canTrack ?? true,
|
|
28
|
+
contentId: trackingContext.contentId,
|
|
29
|
+
variationId: trackingContext.variationId !== trackingContext.contentId ? trackingContext.variationId : undefined,
|
|
30
|
+
metadata: {
|
|
31
|
+
...(customProperties || {}),
|
|
32
|
+
...(amount !== undefined ? {
|
|
33
|
+
amount
|
|
34
|
+
} : {})
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
19
38
|
});
|
|
20
39
|
export const parseCode = (code, { isExpression = true }) => {
|
|
21
40
|
// Be able to handle simple expressions like "state.foo" or "1 + 1"
|
|
@@ -56,17 +56,23 @@ if (typeof output === 'object' && output !== null) {
|
|
|
56
56
|
`;
|
|
57
57
|
};
|
|
58
58
|
let IVM_INSTANCE = null;
|
|
59
|
-
|
|
59
|
+
// Create fresh isolates per request to prevent memory leaks
|
|
60
|
+
let IVM_OPTIONS = {
|
|
61
|
+
memoryLimit: 128
|
|
62
|
+
};
|
|
60
63
|
/**
|
|
61
64
|
* Set the `isolated-vm` instance to be used by the node runtime.
|
|
62
65
|
* This is useful for environments that are not able to rely on our
|
|
63
66
|
* `safeDynamicRequire` trick to import the `isolated-vm` package.
|
|
64
67
|
*/
|
|
65
|
-
export const setIvm = (ivm, options
|
|
68
|
+
export const setIvm = (ivm, options) => {
|
|
66
69
|
if (IVM_INSTANCE)
|
|
67
70
|
return;
|
|
68
71
|
IVM_INSTANCE = ivm;
|
|
69
|
-
|
|
72
|
+
// Store options to be used per-request in runInNode
|
|
73
|
+
if (options) {
|
|
74
|
+
IVM_OPTIONS = options;
|
|
75
|
+
}
|
|
70
76
|
};
|
|
71
77
|
// only mention the script for SDKs that have it.
|
|
72
78
|
const SHOULD_MENTION_INITIALIZE_SCRIPT = SDK_NAME === '@builder.io/sdk-react-nextjs' || SDK_NAME === '@builder.io/sdk-react' || SDK_NAME === '@builder.io/sdk-qwik' || SDK_NAME === '@builder.io/sdk-vue';
|
|
@@ -91,73 +97,72 @@ const getIvm = () => {
|
|
|
91
97
|
For more information, visit https://builder.io/c/docs/integration-tips#enabling-data-bindings-in-node-environments`;
|
|
92
98
|
throw new Error(ERROR_MESSAGE);
|
|
93
99
|
};
|
|
94
|
-
function setIsolateContext(options = {
|
|
95
|
-
memoryLimit: 128
|
|
96
|
-
}) {
|
|
97
|
-
if (IVM_CONTEXT)
|
|
98
|
-
return IVM_CONTEXT;
|
|
99
|
-
const ivm = getIvm();
|
|
100
|
-
const isolate = new ivm.Isolate(options);
|
|
101
|
-
const context = isolate.createContextSync();
|
|
102
|
-
const jail = context.global;
|
|
103
|
-
// This makes the global object available in the context as `global`. We use `derefInto()` here
|
|
104
|
-
// because otherwise `global` would actually be a Reference{} object in the new isolate.
|
|
105
|
-
jail.setSync('global', jail.derefInto());
|
|
106
|
-
// We will create a basic `log` function for the new isolate to use.
|
|
107
|
-
jail.setSync('log', function (...logArgs) {
|
|
108
|
-
console.log(...logArgs);
|
|
109
|
-
});
|
|
110
|
-
jail.setSync(INJECTED_IVM_GLOBAL, ivm);
|
|
111
|
-
IVM_CONTEXT = context;
|
|
112
|
-
return context;
|
|
113
|
-
}
|
|
114
|
-
const getIsolateContext = () => {
|
|
115
|
-
return setIsolateContext();
|
|
116
|
-
};
|
|
117
100
|
export const runInNode = ({ code, builder, context, event, localState, rootSetState, rootState }) => {
|
|
118
101
|
const ivm = getIvm();
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
...localState
|
|
122
|
-
});
|
|
123
|
-
const args = getFunctionArguments({
|
|
124
|
-
builder,
|
|
125
|
-
context,
|
|
126
|
-
event,
|
|
127
|
-
state
|
|
128
|
-
});
|
|
129
|
-
const isolateContext = getIsolateContext();
|
|
130
|
-
const jail = isolateContext.global;
|
|
131
|
-
/**
|
|
132
|
-
* Propagate state changes back to the reactive root state.
|
|
133
|
-
*/
|
|
134
|
-
jail.setSync(BUILDER_SET_STATE_NAME, function (key, value) {
|
|
135
|
-
// mutate the `rootState` object itself. Important for cases where we do not have `rootSetState`
|
|
136
|
-
// like Qwik.
|
|
137
|
-
set(rootState, key, value);
|
|
138
|
-
// call the `rootSetState` function if it exists
|
|
139
|
-
rootSetState?.(rootState);
|
|
140
|
-
});
|
|
141
|
-
args.forEach(([key, arg]) => {
|
|
142
|
-
const val = typeof arg === 'object' ? new ivm.Reference(
|
|
143
|
-
// workaround: methods with default values for arguments is not being cloned over
|
|
144
|
-
key === 'builder' ? {
|
|
145
|
-
...arg,
|
|
146
|
-
getUserAttributes: () => arg.getUserAttributes()
|
|
147
|
-
} : arg) : null;
|
|
148
|
-
jail.setSync(getSyncValName(key), val);
|
|
149
|
-
});
|
|
150
|
-
const evalStr = processCode({
|
|
151
|
-
code,
|
|
152
|
-
args
|
|
153
|
-
});
|
|
154
|
-
const resultStr = isolateContext.evalClosureSync(evalStr);
|
|
102
|
+
// Use stored options from setIvm, or default to { memoryLimit: 128 }
|
|
103
|
+
let isolate;
|
|
155
104
|
try {
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
105
|
+
isolate = new ivm.Isolate(IVM_OPTIONS);
|
|
106
|
+
const isolateContext = isolate.createContextSync();
|
|
107
|
+
const jail = isolateContext.global;
|
|
108
|
+
// Setup the isolate
|
|
109
|
+
jail.setSync('global', jail.derefInto());
|
|
110
|
+
jail.setSync('log', function (...logArgs) {
|
|
111
|
+
console.log(...logArgs);
|
|
112
|
+
});
|
|
113
|
+
jail.setSync(INJECTED_IVM_GLOBAL, ivm);
|
|
114
|
+
const state = fastClone({
|
|
115
|
+
...rootState,
|
|
116
|
+
...localState
|
|
117
|
+
});
|
|
118
|
+
const args = getFunctionArguments({
|
|
119
|
+
builder,
|
|
120
|
+
context,
|
|
121
|
+
event,
|
|
122
|
+
state
|
|
123
|
+
});
|
|
124
|
+
/**
|
|
125
|
+
* Propagate state changes back to the reactive root state.
|
|
126
|
+
*/
|
|
127
|
+
jail.setSync(BUILDER_SET_STATE_NAME, function (key, value) {
|
|
128
|
+
// mutate the `rootState` object itself. Important for cases where we do not have `rootSetState`
|
|
129
|
+
// like Qwik.
|
|
130
|
+
set(rootState, key, value);
|
|
131
|
+
// call the `rootSetState` function if it exists
|
|
132
|
+
rootSetState?.(rootState);
|
|
133
|
+
});
|
|
134
|
+
args.forEach(([key, arg]) => {
|
|
135
|
+
const val = typeof arg === 'object' ? new ivm.Reference(
|
|
136
|
+
// workaround: methods with default values for arguments is not being cloned over
|
|
137
|
+
key === 'builder' ? {
|
|
138
|
+
...arg,
|
|
139
|
+
getUserAttributes: () => arg.getUserAttributes()
|
|
140
|
+
} : arg) : null;
|
|
141
|
+
jail.setSync(getSyncValName(key), val);
|
|
142
|
+
});
|
|
143
|
+
const evalStr = processCode({
|
|
144
|
+
code,
|
|
145
|
+
args
|
|
146
|
+
});
|
|
147
|
+
const resultStr = isolateContext.evalClosureSync(evalStr);
|
|
148
|
+
try {
|
|
149
|
+
// returning objects throw errors in isolated vm, so we stringify it and parse it back
|
|
150
|
+
const res = JSON.parse(resultStr);
|
|
151
|
+
return res;
|
|
152
|
+
}
|
|
153
|
+
catch (_error) {
|
|
154
|
+
return resultStr;
|
|
155
|
+
}
|
|
159
156
|
}
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
finally {
|
|
158
|
+
// Destroy the entire VM, this frees ALL memory at the C++ level.
|
|
159
|
+
if (isolate) {
|
|
160
|
+
try {
|
|
161
|
+
isolate.dispose();
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
// Ignore disposal errors
|
|
165
|
+
}
|
|
166
|
+
}
|
|
162
167
|
}
|
|
163
168
|
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { BuilderContextInterface } from '../context/types';
|
|
2
2
|
import type { BuilderBlock } from '../types/builder-block';
|
|
3
|
+
import type { TrackingContext } from './evaluate/helpers';
|
|
3
4
|
type Options = {
|
|
4
5
|
block: BuilderBlock;
|
|
6
|
+
trackingContext?: TrackingContext;
|
|
5
7
|
} & Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'>;
|
|
6
8
|
type EventHandler = (event: Event) => any;
|
|
7
9
|
export declare const createEventHandler: (value: string, options: Options) => EventHandler;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { BuilderContextInterface } from '../context/types';
|
|
2
2
|
import type { BuilderBlock } from '../types/builder-block';
|
|
3
|
+
import type { TrackingContext } from './evaluate/helpers';
|
|
3
4
|
type Actions = {
|
|
4
5
|
[index: string]: (event: Event) => any;
|
|
5
6
|
};
|
|
6
7
|
export declare function getBlockActions(options: {
|
|
7
8
|
block: BuilderBlock;
|
|
8
9
|
stripPrefix?: boolean;
|
|
10
|
+
trackingContext?: TrackingContext;
|
|
9
11
|
} & Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'>): Actions;
|
|
10
12
|
export {};
|