@teleporthq/teleport-plugin-next-workflows 0.45.4 → 0.45.6
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__/account-delete-password-check.test.ts +39 -0
- package/__tests__/analytics-outcome-visit-ids.test.ts +583 -0
- package/__tests__/automations-bus.test.ts +354 -0
- package/__tests__/public-step-guards.test.ts +26 -0
- package/dist/cjs/account-delete-route-generator.d.ts.map +1 -1
- package/dist/cjs/account-delete-route-generator.js +17 -8
- package/dist/cjs/account-delete-route-generator.js.map +1 -1
- package/dist/cjs/api-route-generator.d.ts +2 -0
- package/dist/cjs/api-route-generator.d.ts.map +1 -1
- package/dist/cjs/api-route-generator.js +43 -1
- package/dist/cjs/api-route-generator.js.map +1 -1
- package/dist/cjs/automations/dispatcher-source.d.ts.map +1 -1
- package/dist/cjs/automations/dispatcher-source.js +13 -5
- package/dist/cjs/automations/dispatcher-source.js.map +1 -1
- package/dist/cjs/executor-generator.d.ts.map +1 -1
- package/dist/cjs/executor-generator.js +4 -0
- package/dist/cjs/executor-generator.js.map +1 -1
- package/dist/cjs/public-step-guards.d.ts +2 -2
- package/dist/cjs/public-step-guards.d.ts.map +1 -1
- package/dist/cjs/public-step-guards.js +11 -8
- package/dist/cjs/public-step-guards.js.map +1 -1
- package/dist/cjs/publish-refusals.js +1 -1
- package/dist/cjs/publish-refusals.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/account-delete-route-generator.d.ts.map +1 -1
- package/dist/esm/account-delete-route-generator.js +17 -8
- package/dist/esm/account-delete-route-generator.js.map +1 -1
- package/dist/esm/api-route-generator.d.ts +2 -0
- package/dist/esm/api-route-generator.d.ts.map +1 -1
- package/dist/esm/api-route-generator.js +44 -2
- package/dist/esm/api-route-generator.js.map +1 -1
- package/dist/esm/automations/dispatcher-source.d.ts.map +1 -1
- package/dist/esm/automations/dispatcher-source.js +13 -5
- package/dist/esm/automations/dispatcher-source.js.map +1 -1
- package/dist/esm/executor-generator.d.ts.map +1 -1
- package/dist/esm/executor-generator.js +4 -0
- package/dist/esm/executor-generator.js.map +1 -1
- package/dist/esm/public-step-guards.d.ts +2 -2
- package/dist/esm/public-step-guards.d.ts.map +1 -1
- package/dist/esm/public-step-guards.js +11 -8
- package/dist/esm/public-step-guards.js.map +1 -1
- package/dist/esm/publish-refusals.js +1 -1
- package/dist/esm/publish-refusals.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/account-delete-route-generator.ts +17 -8
- package/src/api-route-generator.ts +52 -2
- package/src/automations/dispatcher-source.ts +13 -5
- package/src/executor-generator.ts +4 -0
- package/src/public-step-guards.ts +14 -8
- package/src/publish-refusals.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teleporthq/teleport-plugin-next-workflows",
|
|
3
|
-
"version": "0.45.
|
|
3
|
+
"version": "0.45.6",
|
|
4
4
|
"description": "A plugin for workflow code generation in Next.js projects.",
|
|
5
5
|
"author": "teleportHQ",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,5 +69,5 @@
|
|
|
69
69
|
"@teleporthq/teleport-shared": "^0.45.0",
|
|
70
70
|
"@teleporthq/teleport-types": "^0.45.0"
|
|
71
71
|
},
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "c31e265b69774868545e810e31a3c748f62a1729"
|
|
73
73
|
}
|
|
@@ -335,19 +335,28 @@ function readRequestPassword(req) {
|
|
|
335
335
|
return String(body.password);
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
// The sign-in check (utils/auth/hash-password.js
|
|
339
|
-
// the SHA-256 of the password and
|
|
340
|
-
//
|
|
338
|
+
// The sign-in check (utils/auth/hash-password.js checkPassword): bcrypt over
|
|
339
|
+
// the SHA-256 of the password and a pepper, the project's own
|
|
340
|
+
// (AUTH_PEPPER_SECRET) first, then the one every earlier hash used
|
|
341
|
+
// (AUTH_PEPPER). An account without a stored password (a social sign-in)
|
|
342
|
+
// matches no password.
|
|
341
343
|
function passwordMatches(password, storedHash) {
|
|
342
344
|
if (!storedHash || password.length > 1000) { return false; }
|
|
343
345
|
var bcrypt = require('bcryptjs');
|
|
344
346
|
var crypto = require('crypto');
|
|
345
|
-
var
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
347
|
+
var secret = process.env.AUTH_PEPPER_SECRET;
|
|
348
|
+
var peppers = typeof secret === 'string' && secret && secret.indexOf('teleporthq.secrets.') !== 0
|
|
349
|
+
? [secret, process.env.AUTH_PEPPER || '']
|
|
350
|
+
: [process.env.AUTH_PEPPER || ''];
|
|
351
|
+
for (var i = 0; i < peppers.length; i++) {
|
|
352
|
+
var prepared = crypto.createHash('sha256').update(password + peppers[i]).digest('base64');
|
|
353
|
+
try {
|
|
354
|
+
if (bcrypt.compareSync(prepared, String(storedHash))) { return true; }
|
|
355
|
+
} catch (e) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
350
358
|
}
|
|
359
|
+
return false;
|
|
351
360
|
}
|
|
352
361
|
|
|
353
362
|
${generateFillTemplateFn()}
|
|
@@ -14,7 +14,12 @@ import { isCostBearingNodeType } from './nodes/cost-bearing-node-types'
|
|
|
14
14
|
import { nodeRegistry } from './nodes'
|
|
15
15
|
import { NodeHandlerGenerator, ServerHandlerOptions, resolveHandlerEntryName } from './nodes/types'
|
|
16
16
|
import { generalWait } from './nodes/general/general-wait'
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
isOutboundMessageNodeType,
|
|
19
|
+
needsVisitorRequest,
|
|
20
|
+
VISITOR_REQUEST_INJECTION,
|
|
21
|
+
} from './nodes/general/visitor-request'
|
|
22
|
+
import { stepNameOf } from './nodes/email/message-trust'
|
|
18
23
|
import {
|
|
19
24
|
markVisitorBoundMessageNodes,
|
|
20
25
|
markWebhookMessageNodes,
|
|
@@ -2180,6 +2185,33 @@ const generateErrorHandlerRunBlock = (
|
|
|
2180
2185
|
}
|
|
2181
2186
|
}`
|
|
2182
2187
|
|
|
2188
|
+
/**
|
|
2189
|
+
* The email and SMS steps a run of this automation can report as not having
|
|
2190
|
+
* sent. None when it could not tell: a custom node may send by itself, and a
|
|
2191
|
+
* parallel branch the run does not wait for has no result when the run ends.
|
|
2192
|
+
* Steps on the error branch do not count: a run that ends well never reaches them.
|
|
2193
|
+
*/
|
|
2194
|
+
const reportableSendSteps = (
|
|
2195
|
+
workflow: UIDLWorkflow,
|
|
2196
|
+
errorHandlerNodes: Array<{ id: string }> | null
|
|
2197
|
+
): Array<{ id: string; label: string }> => {
|
|
2198
|
+
const nodes = workflow.nodes || []
|
|
2199
|
+
const couldNotTell = nodes.some(
|
|
2200
|
+
(node) =>
|
|
2201
|
+
node.type === 'general-custom-node' ||
|
|
2202
|
+
(node.type === 'general-parallel' &&
|
|
2203
|
+
node.config?.waitForAll !== undefined &&
|
|
2204
|
+
node.config.waitForAll !== true)
|
|
2205
|
+
)
|
|
2206
|
+
if (couldNotTell) {
|
|
2207
|
+
return []
|
|
2208
|
+
}
|
|
2209
|
+
const errorBranch = new Set((errorHandlerNodes || []).map((node) => node.id))
|
|
2210
|
+
return nodes
|
|
2211
|
+
.filter((node) => isOutboundMessageNodeType(node.type) && !errorBranch.has(node.id))
|
|
2212
|
+
.map((node) => ({ id: node.id, label: stepNameOf(node) }))
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2183
2215
|
/**
|
|
2184
2216
|
* An automation's handler module (`lib/automations/handlers/<file>.js`, see
|
|
2185
2217
|
* automations/handler-module-generator.ts): the webhook route's node loop as
|
|
@@ -2199,6 +2231,8 @@ const generateErrorHandlerRunBlock = (
|
|
|
2199
2231
|
* nothing and resolves `{ waiting: true, wouldWait }`; a Wait with no step
|
|
2200
2232
|
* after it stores nothing and the run ends there). A run that fails rejects,
|
|
2201
2233
|
* after the error-handler branch ran, so the dispatcher records the failure.
|
|
2234
|
+
* A run that ends with its email and SMS steps all unsent resolves with their
|
|
2235
|
+
* names under `sentNothing`, so the History can say so instead of Done.
|
|
2202
2236
|
*/
|
|
2203
2237
|
export const generateHandlerFunction = (
|
|
2204
2238
|
workflow: UIDLWorkflow,
|
|
@@ -2277,6 +2311,8 @@ const WORKFLOW_CONFIG = ${workflowConfig};
|
|
|
2277
2311
|
|
|
2278
2312
|
var ERROR_HANDLER_NODES = ${errorHandlerNodesJson};
|
|
2279
2313
|
|
|
2314
|
+
var SEND_STEPS = ${JSON.stringify(reportableSendSteps(workflow, errorHandlerNodes))};
|
|
2315
|
+
|
|
2280
2316
|
const nodeHandlers = {
|
|
2281
2317
|
${nodeHandlersEntries}
|
|
2282
2318
|
};
|
|
@@ -2318,6 +2354,17 @@ ${executionLoop}
|
|
|
2318
2354
|
return { waiting: false };
|
|
2319
2355
|
}
|
|
2320
2356
|
|
|
2357
|
+
// The names of the send steps when the run has some and none of them sent: a
|
|
2358
|
+
// step a condition skipped has no result, one that failed and went on says so.
|
|
2359
|
+
function unsentSendSteps(context) {
|
|
2360
|
+
if (SEND_STEPS.length === 0) return null;
|
|
2361
|
+
var sent = SEND_STEPS.some(function(step) {
|
|
2362
|
+
var result = context[step.id];
|
|
2363
|
+
return !!result && !utils.isFatalNodeResult(result);
|
|
2364
|
+
});
|
|
2365
|
+
return sent ? null : SEND_STEPS.map(function(step) { return step.label; });
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2321
2368
|
async function run(event, context, resumeFrom) {
|
|
2322
2369
|
context = context && typeof context === 'object' ? context : {};
|
|
2323
2370
|
// Shared fire-and-forget queue — drained before the run settles.
|
|
@@ -2350,13 +2397,16 @@ async function run(event, context, resumeFrom) {
|
|
|
2350
2397
|
sourceRef: WORKFLOW_ID,
|
|
2351
2398
|
orderId: trigger.order && trigger.order.id ? String(trigger.order.id) : null,
|
|
2352
2399
|
};
|
|
2400
|
+
var outcome;
|
|
2353
2401
|
try {
|
|
2354
|
-
|
|
2402
|
+
outcome = await runNodes(context, resumeFrom || null);
|
|
2355
2403
|
} catch (error) {
|
|
2356
2404
|
${generateErrorHandlerRunBlock('Automation error handler failed:')}
|
|
2357
2405
|
await utils.settlePendingNodePromises(context);
|
|
2358
2406
|
throw error;
|
|
2359
2407
|
}
|
|
2408
|
+
var unsent = outcome.waiting ? null : unsentSendSteps(context);
|
|
2409
|
+
return unsent ? Object.assign({}, outcome, { sentNothing: unsent }) : outcome;
|
|
2360
2410
|
}
|
|
2361
2411
|
|
|
2362
2412
|
module.exports.run = run;
|
|
@@ -361,6 +361,14 @@ function runStatus(error, value) {
|
|
|
361
361
|
return value && value.waiting === true ? 'waiting' : 'delivered';
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
// What a run leaves in its delivery's error column: its error, or, for a run
|
|
365
|
+
// that finished, the send steps that did not send. Its status says nothing of
|
|
366
|
+
// them: such a run is delivered, and is neither retried nor counted as failed.
|
|
367
|
+
function runNote(error, value) {
|
|
368
|
+
if (error) return store.errorMessage(error);
|
|
369
|
+
return value && value.sentNothing ? JSON.stringify({ sentNothing: value.sentNothing }) : null;
|
|
370
|
+
}
|
|
371
|
+
|
|
364
372
|
// Whether a guarded write found its row (the claim it names still held it).
|
|
365
373
|
function wrote(result) {
|
|
366
374
|
return !!result && result.rowCount > 0;
|
|
@@ -368,7 +376,7 @@ function wrote(result) {
|
|
|
368
376
|
|
|
369
377
|
function lateDeliveryWriter(delivery, testRun, resumePoint) {
|
|
370
378
|
return function (error, value) {
|
|
371
|
-
var message = error
|
|
379
|
+
var message = runNote(error, value);
|
|
372
380
|
var status = testRun ? 'test' : runStatus(error, value);
|
|
373
381
|
store.withClient(async function (client) {
|
|
374
382
|
var finished = await client.query(FINISH_DELIVERY_SQL, [delivery.id, status, message, delivery.attempts]);
|
|
@@ -386,7 +394,7 @@ function lateWaitWriter(wait, resumedAt) {
|
|
|
386
394
|
// Nothing is written once another dispatcher took the stale resume over:
|
|
387
395
|
// that run records the outcome.
|
|
388
396
|
store.withClient(function (client) {
|
|
389
|
-
return finishResume(client, wait, runStatus(error, value), error
|
|
397
|
+
return finishResume(client, wait, runStatus(error, value), runNote(error, value), resumedAt);
|
|
390
398
|
}).catch(function (writeError) {
|
|
391
399
|
console.error('[automations] could not record a late resume: ' + store.errorMessage(writeError));
|
|
392
400
|
});
|
|
@@ -624,7 +632,7 @@ async function deliverEvent(client, row, context) {
|
|
|
624
632
|
var error = ran.error ? store.errorMessage(ran.error) : null;
|
|
625
633
|
if (error) console.error('[automations] ' + entry.name + ' failed for event ' + row.id + ': ' + error);
|
|
626
634
|
// A run that stopped at a Wait is not finished: its delivery waits too.
|
|
627
|
-
var finished = await client.query(FINISH_DELIVERY_SQL, [delivery.id, runStatus(ran.error, ran.value), error, delivery.attempts]);
|
|
635
|
+
var finished = await client.query(FINISH_DELIVERY_SQL, [delivery.id, runStatus(ran.error, ran.value), runNote(ran.error, ran.value), delivery.attempts]);
|
|
628
636
|
if (resumePoint && !error && wrote(finished)) await client.query(SETTLE_FAILED_WAIT_SQL, [resumePoint.id]);
|
|
629
637
|
if (error) outcome.failed += 1;
|
|
630
638
|
else outcome.delivered += 1;
|
|
@@ -690,7 +698,7 @@ async function resumeWaits(client, context) {
|
|
|
690
698
|
wait,
|
|
691
699
|
eventRow,
|
|
692
700
|
runStatus(ran.error, ran.value),
|
|
693
|
-
ran.error
|
|
701
|
+
runNote(ran.error, ran.value),
|
|
694
702
|
resumedAt
|
|
695
703
|
);
|
|
696
704
|
}
|
|
@@ -834,7 +842,7 @@ async function runTest(input, options) {
|
|
|
834
842
|
}, timeoutMs, lateDeliveryWriter(delivery, true));
|
|
835
843
|
if (ran.timedOut) return totals;
|
|
836
844
|
var wouldWait = ran.value && ran.value.wouldWait ? ran.value.wouldWait : null;
|
|
837
|
-
var note =
|
|
845
|
+
var note = wouldWait ? JSON.stringify({ wouldWait: wouldWait }) : runNote(ran.error, ran.value);
|
|
838
846
|
await client.query(FINISH_DELIVERY_SQL, [delivery.id, 'test', note, delivery.attempts]);
|
|
839
847
|
if (ran.error) totals.failed = 1;
|
|
840
848
|
else totals.delivered = 1;
|
|
@@ -1008,6 +1008,9 @@ async function executeWorkflow(workflowConfig, triggerContext, nodeHandlers, opt
|
|
|
1008
1008
|
if (triggerContext && triggerContext.__dynamicRouteParam) {
|
|
1009
1009
|
context.__dynamicRouteParam = triggerContext.__dynamicRouteParam;
|
|
1010
1010
|
}
|
|
1011
|
+
if (triggerContext && triggerContext.__tpa) {
|
|
1012
|
+
context.__tpa = triggerContext.__tpa;
|
|
1013
|
+
}
|
|
1011
1014
|
if (triggerContext && triggerContext.triggerElement) {
|
|
1012
1015
|
context.triggerElement = triggerContext.triggerElement;
|
|
1013
1016
|
}
|
|
@@ -2151,6 +2154,7 @@ function buildContext(workflowConfig, triggerContext) {
|
|
|
2151
2154
|
if (triggerContext.__stateValues) context.__stateValues = triggerContext.__stateValues;
|
|
2152
2155
|
if (triggerContext.__routeParams) context.__routeParams = triggerContext.__routeParams;
|
|
2153
2156
|
if (triggerContext.__dynamicRouteParam) context.__dynamicRouteParam = triggerContext.__dynamicRouteParam;
|
|
2157
|
+
if (triggerContext.__tpa) context.__tpa = triggerContext.__tpa;
|
|
2154
2158
|
if (triggerContext.triggerElement) context.triggerElement = triggerContext.triggerElement;
|
|
2155
2159
|
}
|
|
2156
2160
|
// See executeWorkflow: the page's language travels with the run.
|
|
@@ -103,12 +103,18 @@ const adviceFor = (staffRoles: string[]): string =>
|
|
|
103
103
|
const SHORT_ADVICE =
|
|
104
104
|
'Let only an admin role start it (a role your admin-panel pages admit), or move it into a scheduled, signed webhook or automation workflow, then publish again.'
|
|
105
105
|
|
|
106
|
+
/** What to change on a site without accounts: no role can be required until it has them. */
|
|
107
|
+
const NO_ACCOUNTS_ADVICE =
|
|
108
|
+
'Your site has no sign-in, so no role can be required yet: enable authentication and let only an admin role start it, or move it into a scheduled, signed webhook or automation workflow, then publish again.'
|
|
109
|
+
|
|
106
110
|
/** One line of plain prose, for the person publishing. */
|
|
107
111
|
export const describePublicStepRefusals = (
|
|
108
112
|
refusals: PublicStepRefusal[],
|
|
109
|
-
staffRoles: string[] = [TableAccess.ADMIN_ROLE]
|
|
113
|
+
staffRoles: string[] = [TableAccess.ADMIN_ROLE],
|
|
114
|
+
hasAccounts = true
|
|
110
115
|
): string => {
|
|
111
|
-
const advice = adviceFor(staffRoles)
|
|
116
|
+
const advice = hasAccounts ? adviceFor(staffRoles) : NO_ACCOUNTS_ADVICE
|
|
117
|
+
const shortAdvice = hasAccounts ? SHORT_ADVICE : NO_ACCOUNTS_ADVICE
|
|
112
118
|
const listed = refusals.slice(0, LISTED_STEPS).map(describe)
|
|
113
119
|
const more = refusals.length - listed.length
|
|
114
120
|
const subject =
|
|
@@ -121,10 +127,10 @@ export const describePublicStepRefusals = (
|
|
|
121
127
|
)}${more > 0 ? `; and ${more} more` : ''}.`
|
|
122
128
|
for (const candidate of [
|
|
123
129
|
`${subject} ${advice}`,
|
|
124
|
-
`${subject} ${
|
|
130
|
+
`${subject} ${shortAdvice}`,
|
|
125
131
|
`On your site the step ${describe(refusals[0])}${
|
|
126
132
|
refusals.length > 1 ? `, and ${refusals.length - 1} more` : ''
|
|
127
|
-
}. ${
|
|
133
|
+
}. ${shortAdvice}`,
|
|
128
134
|
]) {
|
|
129
135
|
if (candidate.length <= MAX_SENTENCE_LENGTH) {
|
|
130
136
|
return candidate
|
|
@@ -132,7 +138,7 @@ export const describePublicStepRefusals = (
|
|
|
132
138
|
}
|
|
133
139
|
return `On your site ${
|
|
134
140
|
refusals.length === 1 ? 'a step' : `${refusals.length} steps`
|
|
135
|
-
} meant only for an admin or your own server (a push notification or a stored-file step) can be started by others. ${
|
|
141
|
+
} meant only for an admin or your own server (a push notification or a stored-file step) can be started by others. ${shortAdvice}`
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
/**
|
|
@@ -147,11 +153,11 @@ export class PublicStepRefusalError extends Error {
|
|
|
147
153
|
readonly code = PUBLIC_STEP_REFUSAL_CODE
|
|
148
154
|
readonly refusals: PublicStepRefusal[]
|
|
149
155
|
|
|
150
|
-
constructor(refusals: PublicStepRefusal[], staffRoles?: string[]) {
|
|
156
|
+
constructor(refusals: PublicStepRefusal[], staffRoles?: string[], hasAccounts?: boolean) {
|
|
151
157
|
super(
|
|
152
158
|
`Publishing refused: ${JSON.stringify({
|
|
153
159
|
code: PUBLIC_STEP_REFUSAL_CODE,
|
|
154
|
-
message: describePublicStepRefusals(refusals, staffRoles),
|
|
160
|
+
message: describePublicStepRefusals(refusals, staffRoles, hasAccounts),
|
|
155
161
|
steps: refusals.map((r) => ({ owner: r.ownerId, node: r.nodeId, type: r.nodeType })),
|
|
156
162
|
})}`
|
|
157
163
|
)
|
|
@@ -443,6 +449,6 @@ export const assertNoPublicRestrictedSteps = (
|
|
|
443
449
|
const staffRoles = TableAccess.resolveStaffRoles(authentication)
|
|
444
450
|
const refusals = findPublicRestrictedSteps(workflows, { staffRoles })
|
|
445
451
|
if (refusals.length > 0) {
|
|
446
|
-
throw new PublicStepRefusalError(refusals, staffRoles)
|
|
452
|
+
throw new PublicStepRefusalError(refusals, staffRoles, authentication?.enabled === true)
|
|
447
453
|
}
|
|
448
454
|
}
|
package/src/publish-refusals.ts
CHANGED
|
@@ -168,7 +168,7 @@ const publicStepRefusals = (
|
|
|
168
168
|
ownerId: refusal.ownerId,
|
|
169
169
|
ownerKind: refusal.ownerKind,
|
|
170
170
|
nodeId: refusal.nodeId,
|
|
171
|
-
message: describePublicStepRefusals([refusal], staffRoles),
|
|
171
|
+
message: describePublicStepRefusals([refusal], staffRoles, authentication?.enabled === true),
|
|
172
172
|
}))
|
|
173
173
|
}
|
|
174
174
|
|