@teleporthq/teleport-plugin-next-workflows 0.43.74 → 0.44.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/__tests__/booking-calendar-feed-route.test.ts +494 -0
- package/__tests__/custom-node-result-taken-branch.test.ts +107 -0
- package/__tests__/data-api-date-parser.test.ts +45 -0
- package/__tests__/emit-custom-event-channel.test.ts +13 -0
- package/__tests__/invoice-billing-details.test.ts +278 -0
- package/__tests__/middleware-admin-panel-roles.test.ts +127 -0
- package/__tests__/page-arrival-remount.test.ts +247 -0
- package/__tests__/payment-driver-stripe.test.ts +23 -0
- package/__tests__/payment-drivers-module.test.ts +19 -0
- package/__tests__/payment-subscription-nodes.test.ts +8 -0
- package/__tests__/raw-query-error-code.test.ts +47 -0
- package/__tests__/runtime-template-token-resolution.test.ts +16 -0
- package/__tests__/segment-reply-changed-keys.test.ts +31 -0
- package/__tests__/segment-route-skipped-branch.test.ts +101 -0
- package/dist/cjs/api-route-generator.d.ts.map +1 -1
- package/dist/cjs/api-route-generator.js +23 -13
- package/dist/cjs/api-route-generator.js.map +1 -1
- package/dist/cjs/auth-generator.d.ts.map +1 -1
- package/dist/cjs/auth-generator.js +75 -27
- package/dist/cjs/auth-generator.js.map +1 -1
- package/dist/cjs/booking-calendar-feed-route-generator.d.ts +28 -0
- package/dist/cjs/booking-calendar-feed-route-generator.d.ts.map +1 -0
- package/dist/cjs/booking-calendar-feed-route-generator.js +296 -0
- package/dist/cjs/booking-calendar-feed-route-generator.js.map +1 -0
- package/dist/cjs/data-api-route-generator.d.ts +6 -8
- package/dist/cjs/data-api-route-generator.d.ts.map +1 -1
- package/dist/cjs/data-api-route-generator.js +12 -16
- package/dist/cjs/data-api-route-generator.js.map +1 -1
- package/dist/cjs/executor-generator.d.ts.map +1 -1
- package/dist/cjs/executor-generator.js +30 -2
- package/dist/cjs/executor-generator.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/invoice/invoice-assembly-code.d.ts.map +1 -1
- package/dist/cjs/invoice/invoice-assembly-code.js +46 -6
- package/dist/cjs/invoice/invoice-assembly-code.js.map +1 -1
- package/dist/cjs/invoice/invoice-html-code.d.ts.map +1 -1
- package/dist/cjs/invoice/invoice-html-code.js +10 -2
- package/dist/cjs/invoice/invoice-html-code.js.map +1 -1
- package/dist/cjs/nodes/data/data-raw-query.d.ts.map +1 -1
- package/dist/cjs/nodes/data/data-raw-query.js +11 -1
- package/dist/cjs/nodes/data/data-raw-query.js.map +1 -1
- package/dist/cjs/nodes/general/general-custom-js.d.ts.map +1 -1
- package/dist/cjs/nodes/general/general-custom-js.js +11 -0
- package/dist/cjs/nodes/general/general-custom-js.js.map +1 -1
- package/dist/cjs/nodes/general/general-emit-custom-event.d.ts.map +1 -1
- package/dist/cjs/nodes/general/general-emit-custom-event.js +3 -1
- package/dist/cjs/nodes/general/general-emit-custom-event.js.map +1 -1
- package/dist/cjs/page-arrival.d.ts +10 -0
- package/dist/cjs/page-arrival.d.ts.map +1 -0
- package/dist/cjs/page-arrival.js +86 -0
- package/dist/cjs/page-arrival.js.map +1 -0
- package/dist/cjs/payments/drivers/stripe-driver-code.d.ts.map +1 -1
- package/dist/cjs/payments/drivers/stripe-driver-code.js +11 -0
- package/dist/cjs/payments/drivers/stripe-driver-code.js.map +1 -1
- package/dist/cjs/payments/index.d.ts +10 -4
- package/dist/cjs/payments/index.d.ts.map +1 -1
- package/dist/cjs/payments/index.js +18 -7
- package/dist/cjs/payments/index.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/workflow-component-plugin.d.ts.map +1 -1
- package/dist/cjs/workflow-component-plugin.js +35 -149
- package/dist/cjs/workflow-component-plugin.js.map +1 -1
- package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
- package/dist/cjs/workflow-project-plugin.js +14 -4
- package/dist/cjs/workflow-project-plugin.js.map +1 -1
- package/dist/cjs/workflow-scope.d.ts +19 -0
- package/dist/cjs/workflow-scope.d.ts.map +1 -0
- package/dist/cjs/workflow-scope.js +147 -0
- package/dist/cjs/workflow-scope.js.map +1 -0
- package/dist/esm/api-route-generator.d.ts.map +1 -1
- package/dist/esm/api-route-generator.js +23 -13
- package/dist/esm/api-route-generator.js.map +1 -1
- package/dist/esm/auth-generator.d.ts.map +1 -1
- package/dist/esm/auth-generator.js +75 -27
- package/dist/esm/auth-generator.js.map +1 -1
- package/dist/esm/booking-calendar-feed-route-generator.d.ts +28 -0
- package/dist/esm/booking-calendar-feed-route-generator.d.ts.map +1 -0
- package/dist/esm/booking-calendar-feed-route-generator.js +291 -0
- package/dist/esm/booking-calendar-feed-route-generator.js.map +1 -0
- package/dist/esm/data-api-route-generator.d.ts +6 -8
- package/dist/esm/data-api-route-generator.d.ts.map +1 -1
- package/dist/esm/data-api-route-generator.js +13 -17
- package/dist/esm/data-api-route-generator.js.map +1 -1
- package/dist/esm/executor-generator.d.ts.map +1 -1
- package/dist/esm/executor-generator.js +30 -2
- package/dist/esm/executor-generator.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/invoice/invoice-assembly-code.d.ts.map +1 -1
- package/dist/esm/invoice/invoice-assembly-code.js +46 -6
- package/dist/esm/invoice/invoice-assembly-code.js.map +1 -1
- package/dist/esm/invoice/invoice-html-code.d.ts.map +1 -1
- package/dist/esm/invoice/invoice-html-code.js +10 -2
- package/dist/esm/invoice/invoice-html-code.js.map +1 -1
- package/dist/esm/nodes/data/data-raw-query.d.ts.map +1 -1
- package/dist/esm/nodes/data/data-raw-query.js +11 -1
- package/dist/esm/nodes/data/data-raw-query.js.map +1 -1
- package/dist/esm/nodes/general/general-custom-js.d.ts.map +1 -1
- package/dist/esm/nodes/general/general-custom-js.js +11 -0
- package/dist/esm/nodes/general/general-custom-js.js.map +1 -1
- package/dist/esm/nodes/general/general-emit-custom-event.d.ts.map +1 -1
- package/dist/esm/nodes/general/general-emit-custom-event.js +3 -1
- package/dist/esm/nodes/general/general-emit-custom-event.js.map +1 -1
- package/dist/esm/page-arrival.d.ts +10 -0
- package/dist/esm/page-arrival.d.ts.map +1 -0
- package/dist/esm/page-arrival.js +81 -0
- package/dist/esm/page-arrival.js.map +1 -0
- package/dist/esm/payments/drivers/stripe-driver-code.d.ts.map +1 -1
- package/dist/esm/payments/drivers/stripe-driver-code.js +11 -0
- package/dist/esm/payments/drivers/stripe-driver-code.js.map +1 -1
- package/dist/esm/payments/index.d.ts +10 -4
- package/dist/esm/payments/index.d.ts.map +1 -1
- package/dist/esm/payments/index.js +16 -6
- package/dist/esm/payments/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/workflow-component-plugin.d.ts.map +1 -1
- package/dist/esm/workflow-component-plugin.js +32 -146
- package/dist/esm/workflow-component-plugin.js.map +1 -1
- package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
- package/dist/esm/workflow-project-plugin.js +14 -4
- package/dist/esm/workflow-project-plugin.js.map +1 -1
- package/dist/esm/workflow-scope.d.ts +19 -0
- package/dist/esm/workflow-scope.d.ts.map +1 -0
- package/dist/esm/workflow-scope.js +142 -0
- package/dist/esm/workflow-scope.js.map +1 -0
- package/package.json +4 -4
- package/src/api-route-generator.ts +23 -13
- package/src/auth-generator.ts +80 -25
- package/src/booking-calendar-feed-route-generator.ts +293 -0
- package/src/data-api-route-generator.ts +14 -19
- package/src/executor-generator.ts +30 -2
- package/src/index.ts +1 -0
- package/src/invoice/invoice-assembly-code.ts +46 -6
- package/src/invoice/invoice-html-code.ts +10 -2
- package/src/nodes/data/data-raw-query.ts +11 -1
- package/src/nodes/general/general-custom-js.ts +10 -0
- package/src/nodes/general/general-emit-custom-event.ts +3 -1
- package/src/page-arrival.ts +115 -0
- package/src/payments/drivers/stripe-driver-code.ts +11 -0
- package/src/payments/index.ts +18 -5
- package/src/workflow-component-plugin.ts +43 -188
- package/src/workflow-project-plugin.ts +15 -4
- package/src/workflow-scope.ts +184 -0
|
@@ -36,7 +36,17 @@ async function data_raw_query(config: any, context: any) {
|
|
|
36
36
|
const data = await response.json()
|
|
37
37
|
|
|
38
38
|
if (!response.ok) {
|
|
39
|
-
|
|
39
|
+
const failure: Record<string, unknown> = {
|
|
40
|
+
rows: [],
|
|
41
|
+
result: [],
|
|
42
|
+
error: data.error || 'Raw query failed',
|
|
43
|
+
}
|
|
44
|
+
// The database's SQLSTATE (the route keeps its message in the server log): a
|
|
45
|
+
// statement that raises on purpose is told apart from a broken one by it.
|
|
46
|
+
if (data.code) {
|
|
47
|
+
failure.code = data.code
|
|
48
|
+
}
|
|
49
|
+
return failure
|
|
40
50
|
}
|
|
41
51
|
const rows = data.rows || []
|
|
42
52
|
return { rows, result: rows }
|
|
@@ -194,6 +194,16 @@ async function general_custom_js(config: any, context: Record<string, unknown>)
|
|
|
194
194
|
Object.keys(resolvedNamedParams).length > 0
|
|
195
195
|
) {
|
|
196
196
|
list.push(resolvedNamedParams)
|
|
197
|
+
// The named inputs are also readable as `params.<name>`, the way the
|
|
198
|
+
// custom-node path overlays `__customParams`: generated functions
|
|
199
|
+
// reach for `params.activeStep` as often as `params.slice(-1)[0]`, and
|
|
200
|
+
// an undefined there silently saved `{}` to storage.
|
|
201
|
+
const namedKeys = Object.keys(resolvedNamedParams)
|
|
202
|
+
for (let nk = 0; nk < namedKeys.length; nk++) {
|
|
203
|
+
if (!(namedKeys[nk] in list)) {
|
|
204
|
+
;(list as any)[namedKeys[nk]] = resolvedNamedParams[namedKeys[nk]]
|
|
205
|
+
}
|
|
206
|
+
}
|
|
197
207
|
}
|
|
198
208
|
params = list
|
|
199
209
|
}
|
|
@@ -10,7 +10,9 @@ async function general_emit_custom_event(config: any, context: Record<string, un
|
|
|
10
10
|
const scope = config.scope || GLOBAL_SCOPE
|
|
11
11
|
|
|
12
12
|
try {
|
|
13
|
-
|
|
13
|
+
// The "A Custom Event is Triggered" workflow listens on this channel (see
|
|
14
|
+
// trigger-generator); a bare name started nothing.
|
|
15
|
+
const event = new CustomEvent('workflow:custom:' + eventName, {
|
|
14
16
|
detail: eventData,
|
|
15
17
|
bubbles: scope === GLOBAL_SCOPE,
|
|
16
18
|
cancelable: true,
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComponentUIDL,
|
|
3
|
+
UIDLElement,
|
|
4
|
+
UIDLNode,
|
|
5
|
+
UIDLWorkflow,
|
|
6
|
+
UIDLWorkflows,
|
|
7
|
+
} from '@teleporthq/teleport-types'
|
|
8
|
+
import { UIDLUtils } from '@teleporthq/teleport-shared'
|
|
9
|
+
import { getRelevantWorkflows, resolveLifecycleTriggerElementId } from './workflow-scope'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Lifecycle triggers that fire because the page appeared. They run when the
|
|
13
|
+
* page or component carrying them mounts, so the page has to remount on every
|
|
14
|
+
* arrival (see `PageArrival.remountPageOnArrival`).
|
|
15
|
+
*/
|
|
16
|
+
const PAGE_ARRIVAL_TRIGGER_TYPES = new Set([
|
|
17
|
+
'event-page-loaded',
|
|
18
|
+
'event-element-visible',
|
|
19
|
+
'event-component-loaded',
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
export const isPageArrivalWorkflow = (workflow: UIDLWorkflow): boolean =>
|
|
23
|
+
PAGE_ARRIVAL_TRIGGER_TYPES.has(workflow.trigger.type)
|
|
24
|
+
|
|
25
|
+
/** Every static `id` in a node tree — the ids an element-visible trigger can observe. */
|
|
26
|
+
const collectStaticElementIds = (node: UIDLNode): Set<string> => {
|
|
27
|
+
const ids = new Set<string>()
|
|
28
|
+
UIDLUtils.traverseElements(node, (element) => {
|
|
29
|
+
const id = element.attrs?.id
|
|
30
|
+
if (id?.type === 'static' && typeof id.content === 'string') {
|
|
31
|
+
ids.add(id.content)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
return ids
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The project component a node renders. Local dependencies are resolved before
|
|
39
|
+
* the pages are generated, which rewrites `semanticType` to the component's
|
|
40
|
+
* class name — the original key still matches a component that was not renamed.
|
|
41
|
+
*/
|
|
42
|
+
const findRenderedComponent = (
|
|
43
|
+
element: UIDLElement,
|
|
44
|
+
projectComponents: Record<string, ComponentUIDL>
|
|
45
|
+
): ComponentUIDL | undefined => {
|
|
46
|
+
if (element.dependency?.type !== 'local') {
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
const reference = element.semanticType || element.elementType
|
|
50
|
+
return (
|
|
51
|
+
projectComponents[reference] ||
|
|
52
|
+
Object.values(projectComponents).find(
|
|
53
|
+
(component) => UIDLUtils.getComponentClassName(component) === reference
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The components a node tree renders, directly or through other components. */
|
|
59
|
+
const collectRenderedComponents = (
|
|
60
|
+
node: UIDLNode,
|
|
61
|
+
projectComponents: Record<string, ComponentUIDL>,
|
|
62
|
+
rendered: Set<ComponentUIDL>
|
|
63
|
+
): Set<ComponentUIDL> => {
|
|
64
|
+
UIDLUtils.traverseElements(node, (element) => {
|
|
65
|
+
const component = findRenderedComponent(element, projectComponents)
|
|
66
|
+
if (component && !rendered.has(component)) {
|
|
67
|
+
rendered.add(component)
|
|
68
|
+
collectRenderedComponents(component.node, projectComponents, rendered)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
return rendered
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Mirrors what the workflow plugin emits into the component: the workflows it
|
|
76
|
+
* finds relevant, with an element-visible trigger kept only when the observed
|
|
77
|
+
* element is rendered by the component itself.
|
|
78
|
+
*/
|
|
79
|
+
const componentRunsOnArrival = (component: ComponentUIDL, workflows: UIDLWorkflows): boolean => {
|
|
80
|
+
const arrivalWorkflows = getRelevantWorkflows(workflows, component, false).filter(
|
|
81
|
+
isPageArrivalWorkflow
|
|
82
|
+
)
|
|
83
|
+
if (arrivalWorkflows.length === 0) {
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
const elementIds = collectStaticElementIds(component.node)
|
|
87
|
+
return arrivalWorkflows.some((workflow) => {
|
|
88
|
+
const targetId = resolveLifecycleTriggerElementId(
|
|
89
|
+
workflow.trigger.type,
|
|
90
|
+
(workflow.trigger.config || {}) as Record<string, unknown>
|
|
91
|
+
)
|
|
92
|
+
return (
|
|
93
|
+
workflow.trigger.type !== 'event-element-visible' ||
|
|
94
|
+
(targetId !== null && elementIds.has(targetId))
|
|
95
|
+
)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* True when a component the page renders runs a workflow when it appears — an
|
|
101
|
+
* element-visible trigger on one of its elements, a component-loaded trigger.
|
|
102
|
+
* The component mounts again only when the page around it does, so such a page
|
|
103
|
+
* remounts on every arrival even when it has no arrival trigger of its own.
|
|
104
|
+
*/
|
|
105
|
+
export const pageRendersArrivalComponent = (
|
|
106
|
+
pageNode: UIDLNode,
|
|
107
|
+
workflows: UIDLWorkflows,
|
|
108
|
+
projectComponents?: Record<string, ComponentUIDL>
|
|
109
|
+
): boolean => {
|
|
110
|
+
if (!projectComponents) {
|
|
111
|
+
return false
|
|
112
|
+
}
|
|
113
|
+
const rendered = collectRenderedComponents(pageNode, projectComponents, new Set())
|
|
114
|
+
return Array.from(rendered).some((component) => componentRunsOnArrival(component, workflows))
|
|
115
|
+
}
|
|
@@ -16,6 +16,15 @@ module.exports = function createStripeDriver(core) {
|
|
|
16
16
|
// An older store's test key, read after the names the editor saves.
|
|
17
17
|
var LEGACY_SECRET_KEY_NAMES = ['STRIPE_TEST_KEY'];
|
|
18
18
|
var MISSING_KEY = 'STRIPE_SECRET_KEY is not configured';
|
|
19
|
+
// How long a Checkout page takes money. Stripe's default is a day, which is
|
|
20
|
+
// how long an abandoned checkout would hold its stock on a store without
|
|
21
|
+
// the scheduled release; the store's own hold is 60 minutes, and Stripe
|
|
22
|
+
// takes no less than 30.
|
|
23
|
+
var CHECKOUT_LIFETIME_MINUTES = 60;
|
|
24
|
+
|
|
25
|
+
function checkoutExpiresAt() {
|
|
26
|
+
return Math.floor(Date.now() / 1000) + CHECKOUT_LIFETIME_MINUTES * 60;
|
|
27
|
+
}
|
|
19
28
|
var INTERVALS = ['day', 'week', 'month', 'year'];
|
|
20
29
|
// Checkout tolerates a signature this old, as Stripe's own libraries do.
|
|
21
30
|
var SIGNATURE_TOLERANCE_SECONDS = 300;
|
|
@@ -62,6 +71,7 @@ module.exports = function createStripeDriver(core) {
|
|
|
62
71
|
mode: 'payment',
|
|
63
72
|
success_url: input.successUrl || undefined,
|
|
64
73
|
cancel_url: input.cancelUrl || undefined,
|
|
74
|
+
expires_at: checkoutExpiresAt(),
|
|
65
75
|
};
|
|
66
76
|
var lineItems = input.lineItems;
|
|
67
77
|
if (lineItems && Array.isArray(lineItems) && lineItems.length > 0) {
|
|
@@ -151,6 +161,7 @@ module.exports = function createStripeDriver(core) {
|
|
|
151
161
|
mode: 'subscription',
|
|
152
162
|
success_url: input.successUrl || undefined,
|
|
153
163
|
cancel_url: input.cancelUrl || undefined,
|
|
164
|
+
expires_at: checkoutExpiresAt(),
|
|
154
165
|
line_items: [
|
|
155
166
|
{
|
|
156
167
|
price_data: {
|
package/src/payments/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
FileType,
|
|
3
3
|
ProjectPluginStructure,
|
|
4
4
|
ProjectUIDL,
|
|
5
|
+
UIDLEcommercePaymentProvider,
|
|
5
6
|
UIDLWorkflows,
|
|
6
7
|
} from '@teleporthq/teleport-types'
|
|
7
8
|
import { generatePaymentCoreCode } from './payment-core-code'
|
|
@@ -63,6 +64,18 @@ export const isPaymentWebhookConfig = (webhookConfig: unknown): boolean =>
|
|
|
63
64
|
((webhookConfig || {}) as { signatureAlgorithm?: unknown }).signatureAlgorithm ===
|
|
64
65
|
PAYMENT_DRIVER_SIGNATURE_ALGORITHM
|
|
65
66
|
|
|
67
|
+
/**
|
|
68
|
+
* The providers the site is paid through: the store's (`ecommerceSettings`),
|
|
69
|
+
* or — on a site that takes payments without a store, for its bookings —
|
|
70
|
+
* `paymentSettings`'.
|
|
71
|
+
*/
|
|
72
|
+
export const readUidlPaymentProviders = (
|
|
73
|
+
uidl: Pick<ProjectUIDL, 'ecommerceSettings' | 'paymentSettings'> | undefined
|
|
74
|
+
): UIDLEcommercePaymentProvider[] =>
|
|
75
|
+
(uidl?.ecommerceSettings
|
|
76
|
+
? uidl.ecommerceSettings.paymentProviders
|
|
77
|
+
: uidl?.paymentSettings?.paymentProviders) || []
|
|
78
|
+
|
|
66
79
|
/**
|
|
67
80
|
* The providers this store needs a driver for: every provider its checkout
|
|
68
81
|
* offers, every provider a payment webhook verifies through, and Stripe +
|
|
@@ -71,11 +84,11 @@ export const isPaymentWebhookConfig = (webhookConfig: unknown): boolean =>
|
|
|
71
84
|
* payment, in which case no driver module is written.
|
|
72
85
|
*/
|
|
73
86
|
export const resolveStorePaymentDriverIds = (
|
|
74
|
-
uidl: Pick<ProjectUIDL, 'ecommerceSettings'> & { workflows?: UIDLWorkflows },
|
|
87
|
+
uidl: Pick<ProjectUIDL, 'ecommerceSettings' | 'paymentSettings'> & { workflows?: UIDLWorkflows },
|
|
75
88
|
usedNodeTypes: Iterable<string>
|
|
76
89
|
): PaymentDriverId[] => {
|
|
77
90
|
const wanted = new Set<PaymentDriverId>()
|
|
78
|
-
for (const provider of uidl
|
|
91
|
+
for (const provider of readUidlPaymentProviders(uidl)) {
|
|
79
92
|
const id = String(provider?.type || '').toLowerCase()
|
|
80
93
|
if (isPaymentDriverId(id)) {
|
|
81
94
|
wanted.add(id)
|
|
@@ -98,13 +111,13 @@ const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/
|
|
|
98
111
|
|
|
99
112
|
/**
|
|
100
113
|
* The secret the merchant saved each live provider credential under, as the
|
|
101
|
-
* editor exported it (`
|
|
114
|
+
* editor exported it (`paymentProviders[].credentials` of either block).
|
|
102
115
|
*/
|
|
103
116
|
export const readDeclaredPaymentCredentials = (
|
|
104
|
-
uidl: Pick<ProjectUIDL, 'ecommerceSettings'> | undefined
|
|
117
|
+
uidl: Pick<ProjectUIDL, 'ecommerceSettings' | 'paymentSettings'> | undefined
|
|
105
118
|
): DeclaredPaymentCredentials => {
|
|
106
119
|
const declared: DeclaredPaymentCredentials = {}
|
|
107
|
-
for (const provider of uidl
|
|
120
|
+
for (const provider of readUidlPaymentProviders(uidl)) {
|
|
108
121
|
const id = String(provider?.type || '').toLowerCase()
|
|
109
122
|
if (!isPaymentDriverId(id)) {
|
|
110
123
|
continue
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
UIDLCustomWorkflowNode,
|
|
13
13
|
} from '@teleporthq/teleport-types'
|
|
14
14
|
import * as types from '@babel/types'
|
|
15
|
-
import { GenericUtils, JSIdentifiers, StringUtils } from '@teleporthq/teleport-shared'
|
|
15
|
+
import { GenericUtils, JSIdentifiers, PageArrival, StringUtils } from '@teleporthq/teleport-shared'
|
|
16
16
|
import {
|
|
17
17
|
splitIntoSegments,
|
|
18
18
|
resolveNodeExecutionEnv,
|
|
@@ -34,6 +34,8 @@ import { elementVisibleObserverCode } from './element-visible-trigger'
|
|
|
34
34
|
import { restoreControlledSelectValue } from './controlled-select'
|
|
35
35
|
import { resolveScrollPoint } from './scroll-points'
|
|
36
36
|
import { findFormStateKeyForHydration } from './admin-form-hydration'
|
|
37
|
+
import { getRelevantWorkflows, resolveLifecycleTriggerElementId } from './workflow-scope'
|
|
38
|
+
import { isPageArrivalWorkflow, pageRendersArrivalComponent } from './page-arrival'
|
|
37
39
|
|
|
38
40
|
interface WorkflowPluginConfig {
|
|
39
41
|
isPage?: boolean
|
|
@@ -85,29 +87,6 @@ const DOM_TO_REACT_EVENT: Record<string, string> = {
|
|
|
85
87
|
drag: 'onDrag',
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
/**
|
|
89
|
-
* Trigger types that run as LIFECYCLE code (no React event prop) but are still
|
|
90
|
-
* bound to ONE element, so the workflow only belongs on the page that renders
|
|
91
|
-
* it. Returns the DOM id to prune against, or null when the trigger is not
|
|
92
|
-
* element-scoped.
|
|
93
|
-
*
|
|
94
|
-
* Same fallback chain as the element-trigger branch: the HTML id first, the
|
|
95
|
-
* project-document node id only as a last resort.
|
|
96
|
-
*/
|
|
97
|
-
const resolveLifecycleTriggerElementId = (
|
|
98
|
-
triggerType: string,
|
|
99
|
-
config: Record<string, unknown>
|
|
100
|
-
): string | null => {
|
|
101
|
-
if (
|
|
102
|
-
triggerType !== 'event-element-visible' &&
|
|
103
|
-
triggerType !== 'event-chapter-reached' &&
|
|
104
|
-
triggerType !== 'event-scene-scrolled-past'
|
|
105
|
-
) {
|
|
106
|
-
return null
|
|
107
|
-
}
|
|
108
|
-
return ((config.elementHtmlId || config.nodeId) as string) || null
|
|
109
|
-
}
|
|
110
|
-
|
|
111
90
|
const getReactEventProp = (triggerType: string, config: Record<string, unknown>): string | null => {
|
|
112
91
|
switch (triggerType) {
|
|
113
92
|
case 'event-element-clicked':
|
|
@@ -229,6 +208,12 @@ export const createNextWorkflowPlugin: ComponentPluginFactory<WorkflowPluginConf
|
|
|
229
208
|
return structure
|
|
230
209
|
}
|
|
231
210
|
|
|
211
|
+
// Decided before the page's own workflows: a page can have none and still
|
|
212
|
+
// render a component that runs one when it appears.
|
|
213
|
+
if (isPage && pageRendersArrivalComponent(uidl.node, workflows, options.projectComponents)) {
|
|
214
|
+
PageArrival.remountPageOnArrival(structure)
|
|
215
|
+
}
|
|
216
|
+
|
|
232
217
|
const relevantWorkflows = getRelevantWorkflows(workflows, uidl, isPage)
|
|
233
218
|
|
|
234
219
|
if (relevantWorkflows.length === 0) {
|
|
@@ -533,6 +518,13 @@ export const createNextWorkflowPlugin: ComponentPluginFactory<WorkflowPluginConf
|
|
|
533
518
|
globalStateChangeWorkflows
|
|
534
519
|
)
|
|
535
520
|
|
|
521
|
+
const runsOnPageArrival = lifecycleWorkflows.some(
|
|
522
|
+
(wf) => activeWorkflowIds.has(wf.id) && isPageArrivalWorkflow(wf)
|
|
523
|
+
)
|
|
524
|
+
if (isPage && runsOnPageArrival) {
|
|
525
|
+
PageArrival.remountPageOnArrival(structure)
|
|
526
|
+
}
|
|
527
|
+
|
|
536
528
|
// For update/detail pages: initialize form state from props
|
|
537
529
|
const initialPropsData = uidl.outputOptions?.initialPropsData
|
|
538
530
|
if (initialPropsData?.exposeAs?.name && formDataStateKeyForHydration) {
|
|
@@ -2047,13 +2039,18 @@ const generateElementHandler = (et: ElementTriggerInfo): string => {
|
|
|
2047
2039
|
(preventDefault ? ' event.preventDefault();\n' : '') +
|
|
2048
2040
|
(stopPropagation ? ' event.stopPropagation();\n' : '') +
|
|
2049
2041
|
` const __te = event.currentTarget || event.target;\n` +
|
|
2042
|
+
// The control that fired is `event.target`, which differs from the
|
|
2043
|
+
// bound element when the event bubbled: a radio group's wrapper
|
|
2044
|
+
// listens for `change` and the chosen radio is the target.
|
|
2050
2045
|
// `formControlPropertyReads(...)` injects `value`, `checked`, `files`
|
|
2051
|
-
// so
|
|
2052
|
-
//
|
|
2046
|
+
// from THAT control so `change` events surface the user's choice as
|
|
2047
|
+
// `triggerContext.value` (etc.) — without this every
|
|
2053
2048
|
// `state-update-local-state` node bound to `Trigger.value` writes
|
|
2054
|
-
// `undefined` and the picker becomes a no-op
|
|
2055
|
-
|
|
2056
|
-
|
|
2049
|
+
// `undefined` and the picker becomes a no-op; and `target` carries
|
|
2050
|
+
// the control itself (its name, id, dataset) for delegated handlers.
|
|
2051
|
+
` const __tt = event.target || __te;\n` +
|
|
2052
|
+
` const triggerContext = { elementId: '${elementId}', eventType: '${eventType}', triggerElement: __te, element: __te, target: __tt, currentTarget: __te, timestamp: Date.now(), clientX: event.clientX, clientY: event.clientY, offsetX: event.offsetX, offsetY: event.offsetY, button: event.button, deltaX: event.deltaX, deltaY: event.deltaY, deltaMode: event.deltaMode, key: event.key, ${formControlPropertyReads(
|
|
2053
|
+
'__tt'
|
|
2057
2054
|
)} };\n` +
|
|
2058
2055
|
(isDropEvent
|
|
2059
2056
|
? ' if (event.dataTransfer && event.dataTransfer.files) { triggerContext.files = Array.from(event.dataTransfer.files); }\n'
|
|
@@ -2147,6 +2144,21 @@ const normalizeKeyName = (key: string): string => {
|
|
|
2147
2144
|
return keyMap[key] || key
|
|
2148
2145
|
}
|
|
2149
2146
|
|
|
2147
|
+
/**
|
|
2148
|
+
* The run of a page/component-loaded trigger. A delayed run is cancelled when
|
|
2149
|
+
* the triggers are torn down: by then its page has unmounted, or has remounted
|
|
2150
|
+
* for a new arrival that schedules a run of its own.
|
|
2151
|
+
*/
|
|
2152
|
+
const generateLoadedTriggerRun = (
|
|
2153
|
+
execCall: string,
|
|
2154
|
+
delay: number | undefined,
|
|
2155
|
+
safeId: string
|
|
2156
|
+
): string =>
|
|
2157
|
+
delay
|
|
2158
|
+
? ` const __delay_${safeId} = setTimeout(function() { ${execCall}; }, ${delay});\n` +
|
|
2159
|
+
` cleanups.push(function() { clearTimeout(__delay_${safeId}); });\n`
|
|
2160
|
+
: ` ${execCall};\n`
|
|
2161
|
+
|
|
2150
2162
|
const generateLifecycleTrigger = (wf: UIDLWorkflow, safeId: string): string => {
|
|
2151
2163
|
const trigger = wf.trigger
|
|
2152
2164
|
const config = (trigger.config || {}) as Record<string, unknown>
|
|
@@ -2159,9 +2171,7 @@ const generateLifecycleTrigger = (wf: UIDLWorkflow, safeId: string): string => {
|
|
|
2159
2171
|
` // Page loaded (${wf.name || wf.id})\n` +
|
|
2160
2172
|
` {\n` +
|
|
2161
2173
|
` const triggerContext = { url: window.location.href, timestamp: Date.now(), referrer: document.referrer };\n` +
|
|
2162
|
-
(delay
|
|
2163
|
-
? ` setTimeout(function() { ${execCall}; }, ${delay});\n`
|
|
2164
|
-
: ` ${execCall};\n`) +
|
|
2174
|
+
generateLoadedTriggerRun(execCall, delay, safeId) +
|
|
2165
2175
|
` }`
|
|
2166
2176
|
)
|
|
2167
2177
|
}
|
|
@@ -2174,9 +2184,7 @@ const generateLifecycleTrigger = (wf: UIDLWorkflow, safeId: string): string => {
|
|
|
2174
2184
|
` // Component loaded (${wf.name || wf.id})\n` +
|
|
2175
2185
|
` {\n` +
|
|
2176
2186
|
` const triggerContext = { componentId: '${componentId}', componentName: '${componentName}', url: window.location.href, timestamp: Date.now() };\n` +
|
|
2177
|
-
(delay
|
|
2178
|
-
? ` setTimeout(function() { ${execCall}; }, ${delay});\n`
|
|
2179
|
-
: ` ${execCall};\n`) +
|
|
2187
|
+
generateLoadedTriggerRun(execCall, delay, safeId) +
|
|
2180
2188
|
` }`
|
|
2181
2189
|
)
|
|
2182
2190
|
}
|
|
@@ -2778,156 +2786,3 @@ const generateRealtimeUnsubscribeCode = (
|
|
|
2778
2786
|
return ''
|
|
2779
2787
|
}
|
|
2780
2788
|
}
|
|
2781
|
-
|
|
2782
|
-
// Component-scope triggers match by emitted component name. The GUI stamps the
|
|
2783
|
-
// name at export time, but generators may re-case or strip spaces during
|
|
2784
|
-
// resolution, so equality is checked case/separator-insensitively.
|
|
2785
|
-
const normalizeComponentName = (name: string): string =>
|
|
2786
|
-
(name || '').toLowerCase().replace(/[^a-z0-9]/g, '')
|
|
2787
|
-
|
|
2788
|
-
const getRelevantWorkflows = (
|
|
2789
|
-
workflows: UIDLWorkflows,
|
|
2790
|
-
uidl: any,
|
|
2791
|
-
isPage: boolean
|
|
2792
|
-
): UIDLWorkflow[] => {
|
|
2793
|
-
const relevant: UIDLWorkflow[] = []
|
|
2794
|
-
const pageId = uidl.outputOptions?.pageId || uidl.outputOptions?.fileName || uidl.name || ''
|
|
2795
|
-
|
|
2796
|
-
const allWorkflows = Object.values(workflows.workflows) as UIDLWorkflow[]
|
|
2797
|
-
allWorkflows.forEach((wf: UIDLWorkflow) => {
|
|
2798
|
-
const trigger = wf.trigger
|
|
2799
|
-
|
|
2800
|
-
if (trigger.type === 'event-cron-triggered') {
|
|
2801
|
-
return
|
|
2802
|
-
}
|
|
2803
|
-
|
|
2804
|
-
if (trigger.type === 'event-webhook-received') {
|
|
2805
|
-
return
|
|
2806
|
-
}
|
|
2807
|
-
|
|
2808
|
-
if (trigger.type === 'event-state-change') {
|
|
2809
|
-
const cfg = trigger.config || {}
|
|
2810
|
-
const stateDefs = cfg.stateDefinitions as
|
|
2811
|
-
| Array<{
|
|
2812
|
-
pageOrComponentId?: string
|
|
2813
|
-
pageOrComponentName?: string
|
|
2814
|
-
name?: string
|
|
2815
|
-
stateDefinitionId?: string
|
|
2816
|
-
}>
|
|
2817
|
-
| undefined
|
|
2818
|
-
|
|
2819
|
-
let boundContainerType = cfg.boundContainerType as string | undefined
|
|
2820
|
-
if (!boundContainerType) {
|
|
2821
|
-
if (stateDefs?.some((s) => s.pageOrComponentName)) {
|
|
2822
|
-
boundContainerType = 'component'
|
|
2823
|
-
} else if (!isPage && stateDefs && stateDefs.length > 0) {
|
|
2824
|
-
boundContainerType = 'component'
|
|
2825
|
-
} else {
|
|
2826
|
-
boundContainerType = 'page'
|
|
2827
|
-
}
|
|
2828
|
-
}
|
|
2829
|
-
|
|
2830
|
-
const matchesContainer = boundContainerType === 'page' ? isPage : !isPage
|
|
2831
|
-
if (!matchesContainer) {
|
|
2832
|
-
return
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
|
-
const boundFromConfig = cfg.boundPageOrComponentId as string | undefined
|
|
2836
|
-
const matchesByName =
|
|
2837
|
-
!!stateDefs &&
|
|
2838
|
-
stateDefs.some((sd) => sd.pageOrComponentName && sd.pageOrComponentName === uidl.name)
|
|
2839
|
-
const matchesById =
|
|
2840
|
-
boundFromConfig === pageId ||
|
|
2841
|
-
(!!stateDefs && stateDefs.some((sd) => sd.pageOrComponentId === pageId))
|
|
2842
|
-
|
|
2843
|
-
if (matchesByName || matchesById) {
|
|
2844
|
-
relevant.push(wf)
|
|
2845
|
-
}
|
|
2846
|
-
return
|
|
2847
|
-
}
|
|
2848
|
-
|
|
2849
|
-
if (trigger.type === 'event-global-state-change') {
|
|
2850
|
-
if (!isPage) {
|
|
2851
|
-
return
|
|
2852
|
-
}
|
|
2853
|
-
const selectedPages = trigger.config.selectedPages as Array<{ id: string }> | undefined
|
|
2854
|
-
if (
|
|
2855
|
-
selectedPages &&
|
|
2856
|
-
selectedPages.length > 0 &&
|
|
2857
|
-
!selectedPages.some((p) => p.id === pageId)
|
|
2858
|
-
) {
|
|
2859
|
-
return
|
|
2860
|
-
}
|
|
2861
|
-
relevant.push(wf)
|
|
2862
|
-
return
|
|
2863
|
-
}
|
|
2864
|
-
|
|
2865
|
-
if (trigger.scope === 'global') {
|
|
2866
|
-
return
|
|
2867
|
-
}
|
|
2868
|
-
|
|
2869
|
-
if (trigger.scope === 'component') {
|
|
2870
|
-
if (isPage) {
|
|
2871
|
-
return
|
|
2872
|
-
}
|
|
2873
|
-
const cfg = trigger.config || {}
|
|
2874
|
-
const cfgName = (cfg.componentName as string) || ''
|
|
2875
|
-
const cfgId = (cfg.componentId as string) || ''
|
|
2876
|
-
const matches =
|
|
2877
|
-
(cfgName &&
|
|
2878
|
-
(cfgName === uidl.name ||
|
|
2879
|
-
normalizeComponentName(cfgName) === normalizeComponentName(uidl.name))) ||
|
|
2880
|
-
(cfgId && cfgId === pageId)
|
|
2881
|
-
if (matches) {
|
|
2882
|
-
relevant.push(wf)
|
|
2883
|
-
}
|
|
2884
|
-
return
|
|
2885
|
-
}
|
|
2886
|
-
|
|
2887
|
-
if (trigger.scope === 'page' && isPage) {
|
|
2888
|
-
const triggerPageId = trigger.config.pageId as string
|
|
2889
|
-
const selectedPages = trigger.config.selectedPages as Array<{ id: string }> | undefined
|
|
2890
|
-
|
|
2891
|
-
if (trigger.type === 'event-page-loaded') {
|
|
2892
|
-
const allPages = trigger.config.allPages === true
|
|
2893
|
-
if (allPages) {
|
|
2894
|
-
relevant.push(wf)
|
|
2895
|
-
return
|
|
2896
|
-
}
|
|
2897
|
-
// Unscoped page-loaded workflows are SKIPPED, not injected everywhere.
|
|
2898
|
-
// Silently fanning out to every page masks mis-scoped workflows whose
|
|
2899
|
-
// state target lives in a component (the setter no-ops on pages).
|
|
2900
|
-
if (!triggerPageId && (!selectedPages || selectedPages.length === 0)) {
|
|
2901
|
-
return
|
|
2902
|
-
}
|
|
2903
|
-
}
|
|
2904
|
-
|
|
2905
|
-
if (triggerPageId && triggerPageId !== pageId) {
|
|
2906
|
-
return
|
|
2907
|
-
}
|
|
2908
|
-
|
|
2909
|
-
if (selectedPages && !selectedPages.some((p) => p.id === pageId)) {
|
|
2910
|
-
return
|
|
2911
|
-
}
|
|
2912
|
-
|
|
2913
|
-
relevant.push(wf)
|
|
2914
|
-
return
|
|
2915
|
-
}
|
|
2916
|
-
|
|
2917
|
-
if (trigger.scope === 'element') {
|
|
2918
|
-
// If the element-scoped trigger specifies which pages it belongs to, filter by page.
|
|
2919
|
-
// This prevents including ALL element-scoped workflows on every page (massive bloat).
|
|
2920
|
-
const elementSelectedPages = trigger.config.selectedPages as Array<{ id: string }> | undefined
|
|
2921
|
-
if (isPage && elementSelectedPages && elementSelectedPages.length > 0) {
|
|
2922
|
-
if (!elementSelectedPages.some((p) => p.id === pageId)) {
|
|
2923
|
-
return
|
|
2924
|
-
}
|
|
2925
|
-
}
|
|
2926
|
-
|
|
2927
|
-
relevant.push(wf)
|
|
2928
|
-
return
|
|
2929
|
-
}
|
|
2930
|
-
})
|
|
2931
|
-
|
|
2932
|
-
return relevant
|
|
2933
|
-
}
|
|
@@ -98,6 +98,7 @@ import {
|
|
|
98
98
|
import { rewriteLowStockCustomHandlers } from './ecommerce-customhandler-rewriter'
|
|
99
99
|
import { assertWorkflowsAreSecure } from './security-scanner'
|
|
100
100
|
import { parameterizeAllWorkflowRawSql } from './raw-sql-param-binding'
|
|
101
|
+
import { generateBookingCalendarFeedFiles } from './booking-calendar-feed-route-generator'
|
|
101
102
|
|
|
102
103
|
// Resolves the auth IDENTITY (users) table from the UIDL auth tables. Getting
|
|
103
104
|
// this wrong is a security hole (the /api/data guard only protects this table),
|
|
@@ -351,6 +352,10 @@ export class NextWorkflowProjectPlugin implements ProjectPlugin {
|
|
|
351
352
|
this.generateAuthFiles(uidl.authentication, structure)
|
|
352
353
|
}
|
|
353
354
|
|
|
355
|
+
// The bookings' calendar feeds do not depend on any workflow, so they are
|
|
356
|
+
// emitted before the early return for a project without workflows.
|
|
357
|
+
generateBookingCalendarFeedFiles(structure)
|
|
358
|
+
|
|
354
359
|
if (!uidl.workflows || !uidl.workflows.workflows) {
|
|
355
360
|
if (uidl.invoiceSettings?.enabled) {
|
|
356
361
|
const { dataSourceType, dataSourceConfig } = resolveInvoiceDataSource(structure)
|
|
@@ -1616,11 +1621,14 @@ async function customNode_${safeId}(outerContext, parameters, nodeHandlers) {
|
|
|
1616
1621
|
|
|
1617
1622
|
// The custom node returns its last executed node's output. A fire-and-forget
|
|
1618
1623
|
// node is excluded: it always stores \`null\`, so if it happens to be last it
|
|
1619
|
-
// would turn the whole custom node's result into null for every caller.
|
|
1624
|
+
// would turn the whole custom node's result into null for every caller. So is
|
|
1625
|
+
// a node of a branch not taken: whatever a segment answered for it must never
|
|
1626
|
+
// stand in for the terminal of the branch that ran.
|
|
1620
1627
|
var __returnableNodes = nodes
|
|
1621
1628
|
.slice()
|
|
1622
1629
|
.sort(function(a, b) { return a.stepNumber - b.stepNumber; })
|
|
1623
|
-
.filter(function(n) { return !__utils.isFireAndForgetNode(n); })
|
|
1630
|
+
.filter(function(n) { return !__utils.isFireAndForgetNode(n); })
|
|
1631
|
+
.filter(function(n) { return !(context.__skippedNodes && context.__skippedNodes[n.id]); });
|
|
1624
1632
|
var lastNodeId = null;
|
|
1625
1633
|
for (var __ri = __returnableNodes.length - 1; __ri >= 0; __ri--) {
|
|
1626
1634
|
if (context[__returnableNodes[__ri].id] !== undefined) { lastNodeId = __returnableNodes[__ri].id; break; }
|
|
@@ -1682,11 +1690,14 @@ async function customNode_${safeId}(outerContext, parameters, nodeHandlers) {
|
|
|
1682
1690
|
|
|
1683
1691
|
// The custom node returns its last executed node's output. A fire-and-forget
|
|
1684
1692
|
// node is excluded: it always stores \`null\`, so if it happens to be last it
|
|
1685
|
-
// would turn the whole custom node's result into null for every caller.
|
|
1693
|
+
// would turn the whole custom node's result into null for every caller. So is
|
|
1694
|
+
// a node of a branch not taken: whatever a segment answered for it must never
|
|
1695
|
+
// stand in for the terminal of the branch that ran.
|
|
1686
1696
|
var __returnableNodes = nodes
|
|
1687
1697
|
.slice()
|
|
1688
1698
|
.sort(function(a, b) { return a.stepNumber - b.stepNumber; })
|
|
1689
|
-
.filter(function(n) { return !__utils.isFireAndForgetNode(n); })
|
|
1699
|
+
.filter(function(n) { return !__utils.isFireAndForgetNode(n); })
|
|
1700
|
+
.filter(function(n) { return !(context.__skippedNodes && context.__skippedNodes[n.id]); });
|
|
1690
1701
|
var lastNodeId = null;
|
|
1691
1702
|
for (var __ri = __returnableNodes.length - 1; __ri >= 0; __ri--) {
|
|
1692
1703
|
if (context[__returnableNodes[__ri].id] !== undefined) { lastNodeId = __returnableNodes[__ri].id; break; }
|