@teleporthq/teleport-plugin-next-workflows 0.43.75 → 0.44.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/__tests__/_helpers/fake-store-database.ts +428 -0
- package/__tests__/_helpers/invoice-store.ts +382 -0
- 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__/email-locale-senders.test.ts +3 -1
- package/__tests__/email-sender-name.test.ts +513 -0
- package/__tests__/graph-utils-topological-order.test.ts +155 -1
- package/__tests__/invoice-billing-details.test.ts +384 -0
- package/__tests__/invoice-data-access-replace.test.ts +236 -2
- package/__tests__/invoice-order-items-settle.test.ts +1 -1
- package/__tests__/invoice-payment-email.test.ts +191 -0
- package/__tests__/invoice-regenerate-email.test.ts +288 -0
- package/__tests__/invoice-regenerate-route.test.ts +9 -7
- package/__tests__/invoice-reservation.test.ts +1 -1
- package/__tests__/invoice-tender.test.ts +1 -1
- package/__tests__/middleware-admin-panel-roles.test.ts +127 -0
- package/__tests__/page-arrival-remount.test.ts +247 -0
- package/__tests__/payment-drivers-module.test.ts +19 -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/__tests__/segment-splitter-checkout-order.test.ts +306 -0
- package/__tests__/streaming-on-end-env-filter.test.ts +18 -18
- package/__tests__/streaming-on-end-order.test.ts +387 -0
- package/dist/cjs/account-delete-route-generator.d.ts +1 -0
- package/dist/cjs/account-delete-route-generator.d.ts.map +1 -1
- package/dist/cjs/account-delete-route-generator.js +2 -1
- package/dist/cjs/account-delete-route-generator.js.map +1 -1
- 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 +1 -0
- package/dist/cjs/auth-generator.d.ts.map +1 -1
- package/dist/cjs/auth-generator.js +77 -28
- 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.map +1 -1
- package/dist/cjs/data-api-route-generator.js +6 -1
- 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 +39 -28
- package/dist/cjs/executor-generator.js.map +1 -1
- package/dist/cjs/graph-utils.d.ts +31 -1
- package/dist/cjs/graph-utils.d.ts.map +1 -1
- package/dist/cjs/graph-utils.js +112 -10
- package/dist/cjs/graph-utils.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/api-routes-code.d.ts +12 -3
- package/dist/cjs/invoice/api-routes-code.d.ts.map +1 -1
- package/dist/cjs/invoice/api-routes-code.js +152 -21
- package/dist/cjs/invoice/api-routes-code.js.map +1 -1
- package/dist/cjs/invoice/data-access-code.d.ts.map +1 -1
- package/dist/cjs/invoice/data-access-code.js +133 -21
- package/dist/cjs/invoice/data-access-code.js.map +1 -1
- package/dist/cjs/invoice/email-sender-code.d.ts.map +1 -1
- package/dist/cjs/invoice/email-sender-code.js +15 -25
- package/dist/cjs/invoice/email-sender-code.js.map +1 -1
- package/dist/cjs/invoice/index.js +1 -1
- package/dist/cjs/invoice/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 +50 -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 +13 -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/email/email-mailgun.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-mailgun.js +4 -2
- package/dist/cjs/nodes/email/email-mailgun.js.map +1 -1
- package/dist/cjs/nodes/email/email-postmark.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-postmark.js +5 -2
- package/dist/cjs/nodes/email/email-postmark.js.map +1 -1
- package/dist/cjs/nodes/email/email-resend.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-resend.js +5 -2
- package/dist/cjs/nodes/email/email-resend.js.map +1 -1
- package/dist/cjs/nodes/email/email-sendgrid.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-sendgrid.js +8 -2
- package/dist/cjs/nodes/email/email-sendgrid.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/storage/storage-local-remove.d.ts.map +1 -1
- package/dist/cjs/nodes/storage/storage-local-remove.js +2 -0
- package/dist/cjs/nodes/storage/storage-local-remove.js.map +1 -1
- package/dist/cjs/nodes/storage/storage-local-set.d.ts.map +1 -1
- package/dist/cjs/nodes/storage/storage-local-set.js +4 -0
- package/dist/cjs/nodes/storage/storage-local-set.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/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/segment-splitter.d.ts.map +1 -1
- package/dist/cjs/segment-splitter.js +65 -27
- package/dist/cjs/segment-splitter.js.map +1 -1
- package/dist/cjs/transactional-email-code.d.ts.map +1 -1
- package/dist/cjs/transactional-email-code.js +32 -18
- package/dist/cjs/transactional-email-code.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 +16 -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/account-delete-route-generator.d.ts +1 -0
- package/dist/esm/account-delete-route-generator.d.ts.map +1 -1
- package/dist/esm/account-delete-route-generator.js +2 -1
- package/dist/esm/account-delete-route-generator.js.map +1 -1
- 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 +1 -0
- package/dist/esm/auth-generator.d.ts.map +1 -1
- package/dist/esm/auth-generator.js +77 -28
- 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.map +1 -1
- package/dist/esm/data-api-route-generator.js +6 -1
- 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 +39 -28
- package/dist/esm/executor-generator.js.map +1 -1
- package/dist/esm/graph-utils.d.ts +31 -1
- package/dist/esm/graph-utils.d.ts.map +1 -1
- package/dist/esm/graph-utils.js +109 -9
- package/dist/esm/graph-utils.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/api-routes-code.d.ts +12 -3
- package/dist/esm/invoice/api-routes-code.d.ts.map +1 -1
- package/dist/esm/invoice/api-routes-code.js +152 -21
- package/dist/esm/invoice/api-routes-code.js.map +1 -1
- package/dist/esm/invoice/data-access-code.d.ts.map +1 -1
- package/dist/esm/invoice/data-access-code.js +133 -21
- package/dist/esm/invoice/data-access-code.js.map +1 -1
- package/dist/esm/invoice/email-sender-code.d.ts.map +1 -1
- package/dist/esm/invoice/email-sender-code.js +15 -25
- package/dist/esm/invoice/email-sender-code.js.map +1 -1
- package/dist/esm/invoice/index.js +1 -1
- package/dist/esm/invoice/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 +50 -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 +13 -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/email/email-mailgun.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-mailgun.js +4 -2
- package/dist/esm/nodes/email/email-mailgun.js.map +1 -1
- package/dist/esm/nodes/email/email-postmark.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-postmark.js +5 -2
- package/dist/esm/nodes/email/email-postmark.js.map +1 -1
- package/dist/esm/nodes/email/email-resend.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-resend.js +5 -2
- package/dist/esm/nodes/email/email-resend.js.map +1 -1
- package/dist/esm/nodes/email/email-sendgrid.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-sendgrid.js +8 -2
- package/dist/esm/nodes/email/email-sendgrid.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/storage/storage-local-remove.d.ts.map +1 -1
- package/dist/esm/nodes/storage/storage-local-remove.js +2 -0
- package/dist/esm/nodes/storage/storage-local-remove.js.map +1 -1
- package/dist/esm/nodes/storage/storage-local-set.d.ts.map +1 -1
- package/dist/esm/nodes/storage/storage-local-set.js +4 -0
- package/dist/esm/nodes/storage/storage-local-set.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/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/segment-splitter.d.ts.map +1 -1
- package/dist/esm/segment-splitter.js +66 -28
- package/dist/esm/segment-splitter.js.map +1 -1
- package/dist/esm/transactional-email-code.d.ts.map +1 -1
- package/dist/esm/transactional-email-code.js +32 -18
- package/dist/esm/transactional-email-code.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 +16 -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/account-delete-route-generator.ts +3 -1
- package/src/api-route-generator.ts +23 -13
- package/src/auth-generator.ts +83 -26
- package/src/booking-calendar-feed-route-generator.ts +293 -0
- package/src/data-api-route-generator.ts +6 -1
- package/src/executor-generator.ts +39 -28
- package/src/graph-utils.ts +143 -11
- package/src/index.ts +1 -0
- package/src/invoice/api-routes-code.ts +166 -21
- package/src/invoice/data-access-code.ts +139 -21
- package/src/invoice/email-sender-code.ts +15 -25
- package/src/invoice/index.ts +1 -1
- package/src/invoice/invoice-assembly-code.ts +50 -6
- package/src/invoice/invoice-html-code.ts +13 -2
- package/src/nodes/data/data-raw-query.ts +11 -1
- package/src/nodes/email/email-mailgun.ts +11 -2
- package/src/nodes/email/email-postmark.ts +12 -2
- package/src/nodes/email/email-resend.ts +12 -2
- package/src/nodes/email/email-sendgrid.ts +15 -2
- package/src/nodes/general/general-custom-js.ts +10 -0
- package/src/nodes/storage/storage-local-remove.ts +4 -0
- package/src/nodes/storage/storage-local-set.ts +6 -0
- package/src/page-arrival.ts +115 -0
- package/src/payments/index.ts +18 -5
- package/src/segment-splitter.ts +90 -36
- package/src/transactional-email-code.ts +32 -21
- package/src/workflow-component-plugin.ts +43 -188
- package/src/workflow-project-plugin.ts +17 -4
- package/src/workflow-scope.ts +184 -0
package/src/auth-generator.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
generateLocalizedEmailCopyFn,
|
|
14
14
|
WELCOME_EMAIL_CONFIG_KEYS,
|
|
15
15
|
} from './transactional-email-code'
|
|
16
|
+
import { TableAccess } from '@teleporthq/teleport-shared'
|
|
16
17
|
import { generateSessionTokenResolverCode } from './session-cookie-resolver'
|
|
17
18
|
|
|
18
19
|
// GUI provider id → the actual next-auth v4 provider MODULE name, for the few
|
|
@@ -1117,6 +1118,7 @@ module.exports = function nextAuthRoute(req, res) {
|
|
|
1117
1118
|
export interface WelcomeEmailOptions {
|
|
1118
1119
|
emailProvider?: string | null
|
|
1119
1120
|
fromEmail?: string
|
|
1121
|
+
fromName?: string
|
|
1120
1122
|
emailSecretEnvName?: string | null
|
|
1121
1123
|
emailSubject?: string
|
|
1122
1124
|
emailBodyHtml?: string
|
|
@@ -1165,6 +1167,7 @@ const sanitizeUser = authUtils.sanitizeUser;`
|
|
|
1165
1167
|
welcomeHelpers = `
|
|
1166
1168
|
const WELCOME_EMAIL_PROVIDER = ${JSON.stringify(welcomeProvider)};
|
|
1167
1169
|
const WELCOME_EMAIL_FROM = ${JSON.stringify(welcome.fromEmail || '')};
|
|
1170
|
+
const WELCOME_EMAIL_FROM_NAME = ${JSON.stringify(welcome.fromName || '')};
|
|
1168
1171
|
const WELCOME_EMAIL_SECRET_ENV_NAME = ${JSON.stringify(welcome.emailSecretEnvName || '')};
|
|
1169
1172
|
const WELCOME_EMAIL_SUBJECT = ${JSON.stringify(welcome.emailSubject || 'Welcome')};
|
|
1170
1173
|
const WELCOME_EMAIL_BODY_HTML = ${JSON.stringify(welcome.emailBodyHtml || '')};
|
|
@@ -1200,7 +1203,7 @@ async function sendWelcomeEmail(toEmail, tokenValues, userId, locale) {
|
|
|
1200
1203
|
var copy = resolveWelcomeEmailCopy(locale);
|
|
1201
1204
|
var subject = fillTemplate(copy.subject, tokenValues);
|
|
1202
1205
|
var html = fillTemplate(copy.body, tokenValues);
|
|
1203
|
-
await __sendProviderEmail({ from: from, to: toEmail, subject: subject, html: html, apiKey: apiKey, tokenValues: tokenValues, userId: userId });
|
|
1206
|
+
await __sendProviderEmail({ from: from, fromName: WELCOME_EMAIL_FROM_NAME, to: toEmail, subject: subject, html: html, apiKey: apiKey, tokenValues: tokenValues, userId: userId });
|
|
1204
1207
|
}
|
|
1205
1208
|
`
|
|
1206
1209
|
|
|
@@ -1413,6 +1416,70 @@ const selfGuardedRoutePatternOf = (protection: {
|
|
|
1413
1416
|
return base === '/' ? `/[${differentiator}]` : `${base}/[${differentiator}]`
|
|
1414
1417
|
}
|
|
1415
1418
|
|
|
1419
|
+
/** The same-site path the project redirects `/` to, or null (absent, `/` itself, or off-site). */
|
|
1420
|
+
const resolveHomeRedirect = (auth: UIDLAuthentication): string | null => {
|
|
1421
|
+
const value = typeof auth.homeRedirect === 'string' ? auth.homeRedirect : ''
|
|
1422
|
+
const path = value.split('?')[0].split('#')[0]
|
|
1423
|
+
if (!path.startsWith('/') || path.startsWith('//') || path.indexOf('\\') !== -1) {
|
|
1424
|
+
return null
|
|
1425
|
+
}
|
|
1426
|
+
const normalized = path.length > 1 ? path.replace(/\/+$/, '') : path
|
|
1427
|
+
return normalized === '/' ? null : normalized
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
/**
|
|
1431
|
+
* Where a signed-in visitor without the role a page asks for lands. A project
|
|
1432
|
+
* whose home page is not redirected keeps the code it always had. One that
|
|
1433
|
+
* redirects `/` (e.g. to an admin dashboard) only sends a visitor home when
|
|
1434
|
+
* that page admits them; otherwise to sign-in, saying why.
|
|
1435
|
+
*/
|
|
1436
|
+
const generateRoleDeniedRedirectCode = (
|
|
1437
|
+
signInRoute: string,
|
|
1438
|
+
homeRedirect: string | null
|
|
1439
|
+
): string => {
|
|
1440
|
+
if (!homeRedirect) {
|
|
1441
|
+
return `// Where to send an authenticated user who lacks the required role. Normally the
|
|
1442
|
+
// home page ("you don't have access, here's the public site"). But when "/" is
|
|
1443
|
+
// itself a protected page (e.g. an admin dashboard published at the root), a
|
|
1444
|
+
// redirect to "/" re-enters this middleware and loops forever — so fall back to
|
|
1445
|
+
// the sign-in page (with a callbackUrl) in that case.
|
|
1446
|
+
function roleDeniedRedirect(request, pathname) {
|
|
1447
|
+
if (pathname !== '/' && !protectedRoutes['/']) {
|
|
1448
|
+
return redirectWithinLocale(request, '/');
|
|
1449
|
+
}
|
|
1450
|
+
return redirectWithinLocale(request, '${signInRoute}', pathname);
|
|
1451
|
+
}`
|
|
1452
|
+
}
|
|
1453
|
+
return `// The project redirects "/" here before this middleware ever runs.
|
|
1454
|
+
var homeRedirect = ${JSON.stringify(homeRedirect)};
|
|
1455
|
+
|
|
1456
|
+
// Whether "/" is a way out for this visitor: the page it redirects to is not
|
|
1457
|
+
// the one being left, and admits their role. Otherwise sending them home would
|
|
1458
|
+
// land them right back on a page that refuses them — a redirect loop.
|
|
1459
|
+
function homeAdmitsRole(pathname, userRole) {
|
|
1460
|
+
var landing = normalizeMatchPath(homeRedirect);
|
|
1461
|
+
if (landing === normalizeMatchPath(pathname)) return false;
|
|
1462
|
+
var protection = resolveProtection(landing).matched;
|
|
1463
|
+
if (!protection) return true;
|
|
1464
|
+
if (protection.requiresSubscription) return false;
|
|
1465
|
+
var roles = protection.allowedRoles || [];
|
|
1466
|
+
return roles.length === 0 || roles.indexOf(userRole) >= 0;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// Where to send an authenticated user who lacks the required role: home when
|
|
1470
|
+
// home admits them, else the sign-in page, which is told why (error=forbidden)
|
|
1471
|
+
// and keeps a callbackUrl for an account that may open the page.
|
|
1472
|
+
function roleDeniedRedirect(request, pathname, userRole) {
|
|
1473
|
+
if (pathname !== '/' && !protectedRoutes['/'] && homeAdmitsRole(pathname, userRole)) {
|
|
1474
|
+
return redirectWithinLocale(request, '/');
|
|
1475
|
+
}
|
|
1476
|
+
var target = new URL(localizePathname(request, '${signInRoute}'), request.url);
|
|
1477
|
+
target.searchParams.set('callbackUrl', localizePathname(request, pathname));
|
|
1478
|
+
target.searchParams.set('error', 'forbidden');
|
|
1479
|
+
return NextResponse.redirect(target);
|
|
1480
|
+
}`
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1416
1483
|
export const generateMiddlewareFile = (
|
|
1417
1484
|
auth: UIDLAuthentication,
|
|
1418
1485
|
options: MiddlewareOptions = {}
|
|
@@ -1506,20 +1573,19 @@ export const generateMiddlewareFile = (
|
|
|
1506
1573
|
delete protectedRoutes[route]
|
|
1507
1574
|
}
|
|
1508
1575
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
adminRoles = ar
|
|
1517
|
-
break
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1576
|
+
// `/admin` itself, and every admin page the map has no entry for (one an
|
|
1577
|
+
// owner added without protection), admit the admin panel's own roles — its
|
|
1578
|
+
// folder's — never the first admin route's, which may also admit another
|
|
1579
|
+
// role (a staff dashboard).
|
|
1580
|
+
const sawAdminRoute = Object.keys(protectedRoutes).some(
|
|
1581
|
+
(k) => k === '/admin' || k.indexOf('/admin/') === 0
|
|
1582
|
+
)
|
|
1521
1583
|
if (sawAdminRoute && !protectedRoutes['/admin']) {
|
|
1522
|
-
|
|
1584
|
+
const panelRoles = TableAccess.resolveAdminPanelRoles(auth)
|
|
1585
|
+
protectedRoutes['/admin'] = {
|
|
1586
|
+
requiresAuth: true,
|
|
1587
|
+
allowedRoles: panelRoles.length > 0 ? panelRoles : ['admin'],
|
|
1588
|
+
}
|
|
1523
1589
|
}
|
|
1524
1590
|
|
|
1525
1591
|
const protectedRoutesJson = JSON.stringify(protectedRoutes, null, 2)
|
|
@@ -1530,6 +1596,7 @@ export const generateMiddlewareFile = (
|
|
|
1530
1596
|
options.subscriptionFallbackRoute && options.subscriptionFallbackRoute.startsWith('/')
|
|
1531
1597
|
? options.subscriptionFallbackRoute
|
|
1532
1598
|
: '/'
|
|
1599
|
+
const homeRedirect = resolveHomeRedirect(auth)
|
|
1533
1600
|
|
|
1534
1601
|
return `import { NextResponse } from 'next/server';
|
|
1535
1602
|
import { getToken } from 'next-auth/jwt';
|
|
@@ -1642,17 +1709,7 @@ function redirectWithinLocale(request, pathname, callbackPath) {
|
|
|
1642
1709
|
return NextResponse.redirect(target);
|
|
1643
1710
|
}
|
|
1644
1711
|
|
|
1645
|
-
|
|
1646
|
-
// home page ("you don't have access, here's the public site"). But when "/" is
|
|
1647
|
-
// itself a protected page (e.g. an admin dashboard published at the root), a
|
|
1648
|
-
// redirect to "/" re-enters this middleware and loops forever — so fall back to
|
|
1649
|
-
// the sign-in page (with a callbackUrl) in that case.
|
|
1650
|
-
function roleDeniedRedirect(request, pathname) {
|
|
1651
|
-
if (pathname !== '/' && !protectedRoutes['/']) {
|
|
1652
|
-
return redirectWithinLocale(request, '/');
|
|
1653
|
-
}
|
|
1654
|
-
return redirectWithinLocale(request, '${signInRoute}', pathname);
|
|
1655
|
-
}
|
|
1712
|
+
${generateRoleDeniedRedirectCode(signInRoute, homeRedirect)}
|
|
1656
1713
|
|
|
1657
1714
|
// Route keys carry no trailing slash, so neither may the path we match with.
|
|
1658
1715
|
// Next.js serves "/orders/ORD-42/" and "/orders/ORD-42" as the same route;
|
|
@@ -1839,7 +1896,7 @@ async function middleware(request) {
|
|
|
1839
1896
|
}
|
|
1840
1897
|
var userRole = getUserRoleFromToken(sessionUser);
|
|
1841
1898
|
if (userRole == null || allowedRoles.indexOf(userRole) < 0) {
|
|
1842
|
-
return roleDeniedRedirect(request, pathname);
|
|
1899
|
+
return roleDeniedRedirect(request, pathname${homeRedirect ? ', userRole' : ''});
|
|
1843
1900
|
}
|
|
1844
1901
|
}
|
|
1845
1902
|
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bookings' calendar feeds: `pages/api/bookings/ical/[file].js`, emitted
|
|
3
|
+
* when the UIDL carries `bookingCalendars.feedStatement`.
|
|
4
|
+
*
|
|
5
|
+
* Each calendar link of a site's bookings is an iCalendar (RFC 5545) feed that
|
|
6
|
+
* Airbnb, Booking.com, Vrbo or Google Calendar import to block the dates the
|
|
7
|
+
* site has booked. The editor builds the one statement behind every feed (its
|
|
8
|
+
* tables and columns are the editor's business); the generated route only runs
|
|
9
|
+
* it with the link's key and writes the rows out as a calendar.
|
|
10
|
+
*/
|
|
11
|
+
import { FileType, ProjectPluginStructure } from '@teleporthq/teleport-types'
|
|
12
|
+
import { generatePgClientCode } from './pg-client-code'
|
|
13
|
+
import { getDatabaseDriverDependencies } from './auth-generator'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The route's source (CommonJS). GET or HEAD `/api/bookings/ical/<key>.ics`:
|
|
17
|
+
* - another method → 405 with `Allow: GET, HEAD`;
|
|
18
|
+
* - a key of the wrong shape, or one the statement returns no calendar row
|
|
19
|
+
* for → 404 (the key is the link's only credential, so both look the same);
|
|
20
|
+
* - a failed query → 500 'Calendar unavailable', the cause logged only;
|
|
21
|
+
* - else 200 with the calendar, never cached. An event row with a missing or
|
|
22
|
+
* invalid value is left out rather than failing the whole feed.
|
|
23
|
+
*/
|
|
24
|
+
export const generateBookingCalendarFeedRoute = (feedStatement: string): string => {
|
|
25
|
+
return `'use strict';
|
|
26
|
+
|
|
27
|
+
// GENERATED — see generateBookingCalendarFeedRoute in
|
|
28
|
+
// @teleporthq/teleport-plugin-next-workflows/src/booking-calendar-feed-route-generator.ts.
|
|
29
|
+
//
|
|
30
|
+
// The iCalendar feed of one calendar link of the site's bookings: Airbnb,
|
|
31
|
+
// Booking.com, Vrbo or Google Calendar poll it and block the booked dates.
|
|
32
|
+
${generatePgClientCode()}
|
|
33
|
+
|
|
34
|
+
// Built by the editor: $1 is the link's key. It returns one 'calendar' row and
|
|
35
|
+
// one 'event' row per blocked period.
|
|
36
|
+
var FEED_STATEMENT = ${JSON.stringify(feedStatement)};
|
|
37
|
+
var FEED_FILE_RE = /^([A-Za-z0-9_-]{20,80})[.]ics$/;
|
|
38
|
+
var DATE_RE = /^([0-9]{4})([0-9]{2})([0-9]{2})$/;
|
|
39
|
+
var DATE_TIME_RE = /^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z$/;
|
|
40
|
+
var TEXT_TYPE = 'text/plain; charset=utf-8';
|
|
41
|
+
var CALENDAR_TYPE = 'text/calendar; charset=utf-8';
|
|
42
|
+
var CRLF = String.fromCharCode(13, 10);
|
|
43
|
+
var BACKSLASH = String.fromCharCode(92);
|
|
44
|
+
// RFC 5545: a content line is at most 75 octets, the line break excluded.
|
|
45
|
+
var MAX_LINE_OCTETS = 75;
|
|
46
|
+
|
|
47
|
+
// RFC 5545 TEXT: a backslash, semicolon or comma is escaped with a backslash
|
|
48
|
+
// and every line break is written as the two characters backslash and n.
|
|
49
|
+
// Other control characters are not allowed in TEXT, so they are dropped.
|
|
50
|
+
function icalText(value) {
|
|
51
|
+
var text = String(value == null ? '' : value);
|
|
52
|
+
var out = '';
|
|
53
|
+
for (var i = 0; i < text.length; i++) {
|
|
54
|
+
var ch = text.charAt(i);
|
|
55
|
+
var code = text.charCodeAt(i);
|
|
56
|
+
if (ch === BACKSLASH || ch === ';' || ch === ',') {
|
|
57
|
+
out += BACKSLASH + ch;
|
|
58
|
+
} else if (code === 13 || code === 10) {
|
|
59
|
+
// CRLF, a lone CR and a lone LF are each one line break.
|
|
60
|
+
if (code === 13 && text.charCodeAt(i + 1) === 10) {
|
|
61
|
+
i += 1;
|
|
62
|
+
}
|
|
63
|
+
out += BACKSLASH + 'n';
|
|
64
|
+
} else if (code === 9 || (code >= 32 && code !== 127)) {
|
|
65
|
+
out += ch;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// One content line, folded: a continuation line starts with one space (which
|
|
72
|
+
// counts toward its 75 octets), and no fold splits a UTF-8 character.
|
|
73
|
+
function icalFold(line) {
|
|
74
|
+
var out = '';
|
|
75
|
+
var used = 0;
|
|
76
|
+
for (var i = 0; i < line.length; i++) {
|
|
77
|
+
var ch = line.charAt(i);
|
|
78
|
+
var code = line.charCodeAt(i);
|
|
79
|
+
var octets = code < 0x80 ? 1 : code < 0x800 ? 2 : 3;
|
|
80
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
81
|
+
var low = line.charCodeAt(i + 1);
|
|
82
|
+
if (low >= 0xdc00 && low <= 0xdfff) {
|
|
83
|
+
// A surrogate pair is one 4-octet character.
|
|
84
|
+
ch = line.slice(i, i + 2);
|
|
85
|
+
octets = 4;
|
|
86
|
+
i += 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (used + octets > MAX_LINE_OCTETS) {
|
|
90
|
+
out += CRLF + ' ';
|
|
91
|
+
used = 1;
|
|
92
|
+
}
|
|
93
|
+
out += ch;
|
|
94
|
+
used += octets;
|
|
95
|
+
}
|
|
96
|
+
return out + CRLF;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// all_day as Postgres hands it over: a boolean, or 't' / 'f' / 'true' / 'false'.
|
|
100
|
+
// null for anything else.
|
|
101
|
+
function icalAllDay(value) {
|
|
102
|
+
if (value === true || value === false) {
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
var text = String(value == null ? '' : value).trim().toLowerCase();
|
|
106
|
+
if (text === 't' || text === 'true') {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (text === 'f' || text === 'false') {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 'YYYYMMDD' (all day) or 'YYYYMMDDTHHMMSSZ' naming a real day and time, else
|
|
116
|
+
// null — a value like 20260231 would otherwise reach the importing calendar.
|
|
117
|
+
function icalDateValue(value, allDay) {
|
|
118
|
+
if (typeof value !== 'string') {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
var text = value.trim();
|
|
122
|
+
var match = (allDay ? DATE_RE : DATE_TIME_RE).exec(text);
|
|
123
|
+
if (!match) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
var parts = [];
|
|
127
|
+
for (var i = 1; i <= 6; i++) {
|
|
128
|
+
parts.push(allDay && i > 3 ? 0 : Number(match[i]));
|
|
129
|
+
}
|
|
130
|
+
var date = new Date(Date.UTC(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]));
|
|
131
|
+
var actual = [
|
|
132
|
+
date.getUTCFullYear(),
|
|
133
|
+
date.getUTCMonth() + 1,
|
|
134
|
+
date.getUTCDate(),
|
|
135
|
+
date.getUTCHours(),
|
|
136
|
+
date.getUTCMinutes(),
|
|
137
|
+
date.getUTCSeconds(),
|
|
138
|
+
];
|
|
139
|
+
return actual.join(',') === parts.join(',') ? text : null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// The VEVENT lines of one event row, or null when a value is missing or invalid.
|
|
143
|
+
function icalEventLines(row, stamp) {
|
|
144
|
+
var uid = row.uid == null ? '' : String(row.uid).trim();
|
|
145
|
+
var allDay = icalAllDay(row.all_day);
|
|
146
|
+
if (uid === '' || allDay === null) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
var start = icalDateValue(row.start_value, allDay);
|
|
150
|
+
var end = icalDateValue(row.end_value, allDay);
|
|
151
|
+
// The end is exclusive, so it must come after the start. Two values of the
|
|
152
|
+
// same format compare correctly as strings.
|
|
153
|
+
if (start === null || end === null || end <= start) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
var valueParam = allDay ? ';VALUE=DATE:' : ':';
|
|
157
|
+
return [
|
|
158
|
+
'BEGIN:VEVENT',
|
|
159
|
+
'UID:' + icalText(uid),
|
|
160
|
+
'DTSTAMP:' + stamp,
|
|
161
|
+
'DTSTART' + valueParam + start,
|
|
162
|
+
'DTEND' + valueParam + end,
|
|
163
|
+
'SUMMARY:' + icalText(row.summary),
|
|
164
|
+
'TRANSP:OPAQUE',
|
|
165
|
+
'END:VEVENT',
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 'YYYYMMDDTHHMMSSZ' of an instant, in UTC.
|
|
170
|
+
function icalStamp(date) {
|
|
171
|
+
var iso = date.toISOString();
|
|
172
|
+
return (
|
|
173
|
+
iso.slice(0, 4) + iso.slice(5, 7) + iso.slice(8, 10) + 'T' +
|
|
174
|
+
iso.slice(11, 13) + iso.slice(14, 16) + iso.slice(17, 19) + 'Z'
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function icalCalendar(calendarRow, rows, now) {
|
|
179
|
+
var stamp = icalStamp(now);
|
|
180
|
+
var lines = [
|
|
181
|
+
'BEGIN:VCALENDAR',
|
|
182
|
+
'VERSION:2.0',
|
|
183
|
+
'PRODID:-//TeleportHQ//Bookings//EN',
|
|
184
|
+
'CALSCALE:GREGORIAN',
|
|
185
|
+
'METHOD:PUBLISH',
|
|
186
|
+
'X-WR-CALNAME:' + icalText(calendarRow.calendar_name),
|
|
187
|
+
'REFRESH-INTERVAL;VALUE=DURATION:PT30M',
|
|
188
|
+
'X-PUBLISHED-TTL:PT30M',
|
|
189
|
+
];
|
|
190
|
+
for (var i = 0; i < rows.length; i++) {
|
|
191
|
+
if (rows[i] && rows[i].kind === 'event') {
|
|
192
|
+
var eventLines = icalEventLines(rows[i], stamp);
|
|
193
|
+
if (eventLines) {
|
|
194
|
+
lines = lines.concat(eventLines);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
lines.push('END:VCALENDAR');
|
|
199
|
+
var body = '';
|
|
200
|
+
for (var j = 0; j < lines.length; j++) {
|
|
201
|
+
body += icalFold(lines[j]);
|
|
202
|
+
}
|
|
203
|
+
return body;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Never cached: a booking can block a date between two polls. A HEAD request
|
|
207
|
+
// gets the headers GET would, without the body.
|
|
208
|
+
function icalSend(res, status, contentType, body, headOnly) {
|
|
209
|
+
res.statusCode = status;
|
|
210
|
+
res.setHeader('Content-Type', contentType);
|
|
211
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
212
|
+
res.setHeader('Content-Length', String(Buffer.byteLength(body, 'utf8')));
|
|
213
|
+
if (headOnly) {
|
|
214
|
+
res.end();
|
|
215
|
+
} else {
|
|
216
|
+
res.end(body);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = async function handler(req, res) {
|
|
221
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
222
|
+
res.setHeader('Allow', 'GET, HEAD');
|
|
223
|
+
return icalSend(res, 405, TEXT_TYPE, 'Method not allowed', false);
|
|
224
|
+
}
|
|
225
|
+
var headOnly = req.method === 'HEAD';
|
|
226
|
+
var file = req.query && typeof req.query.file === 'string' ? req.query.file : '';
|
|
227
|
+
var fileMatch = FEED_FILE_RE.exec(file);
|
|
228
|
+
if (!fileMatch) {
|
|
229
|
+
return icalSend(res, 404, TEXT_TYPE, 'Not found', headOnly);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
var rows = null;
|
|
233
|
+
var client = null;
|
|
234
|
+
try {
|
|
235
|
+
client = getClient();
|
|
236
|
+
await client.connect();
|
|
237
|
+
var result = await client.query(FEED_STATEMENT, [fileMatch[1]]);
|
|
238
|
+
rows = result && Array.isArray(result.rows) ? result.rows : [];
|
|
239
|
+
} catch (e) {
|
|
240
|
+
console.error('[bookings-ical] calendar feed query failed:', e && e.message ? e.message : e);
|
|
241
|
+
} finally {
|
|
242
|
+
if (client) {
|
|
243
|
+
try { await client.end(); } catch (e) {}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (rows === null) {
|
|
247
|
+
return icalSend(res, 500, TEXT_TYPE, 'Calendar unavailable', headOnly);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
var calendarRow = null;
|
|
251
|
+
for (var i = 0; i < rows.length && calendarRow === null; i++) {
|
|
252
|
+
if (rows[i] && rows[i].kind === 'calendar') {
|
|
253
|
+
calendarRow = rows[i];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (calendarRow === null) {
|
|
257
|
+
return icalSend(res, 404, TEXT_TYPE, 'Not found', headOnly);
|
|
258
|
+
}
|
|
259
|
+
res.setHeader('Content-Disposition', 'inline; filename="' + file + '"');
|
|
260
|
+
return icalSend(res, 200, CALENDAR_TYPE, icalCalendar(calendarRow, rows, new Date()), headOnly);
|
|
261
|
+
};
|
|
262
|
+
`
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Emits the feed route when the UIDL names a feed statement, plus the `pg`
|
|
267
|
+
* driver it connects with (already present on a project with a Teleport
|
|
268
|
+
* database, which every bookings site has).
|
|
269
|
+
*/
|
|
270
|
+
export const generateBookingCalendarFeedFiles = (structure: ProjectPluginStructure): void => {
|
|
271
|
+
const feedStatement = structure.uidl.bookingCalendars?.feedStatement
|
|
272
|
+
if (typeof feedStatement !== 'string' || feedStatement.trim() === '') {
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
structure.files.set('bookings-ical-feed-route', {
|
|
277
|
+
path: ['pages', 'api', 'bookings', 'ical'],
|
|
278
|
+
files: [
|
|
279
|
+
{
|
|
280
|
+
name: '[file]',
|
|
281
|
+
fileType: FileType.JS,
|
|
282
|
+
content: generateBookingCalendarFeedRoute(feedStatement),
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
const driverDependencies = getDatabaseDriverDependencies('teleport')
|
|
288
|
+
for (const [pkg, version] of Object.entries(driverDependencies)) {
|
|
289
|
+
if (!structure.dependencies[pkg]) {
|
|
290
|
+
structure.dependencies[pkg] = version
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -101,7 +101,12 @@ export const generateDataAPIRoute = (options: DataAPIRouteOptions = {}): string
|
|
|
101
101
|
? options.lowStockThreshold
|
|
102
102
|
: 5
|
|
103
103
|
|
|
104
|
-
return `const { Client } = require('pg');
|
|
104
|
+
return `const { Client, types: pgTypes } = require('pg');
|
|
105
|
+
// A DATE stays the day it names, whatever the server's time zone: node-pg would read it as
|
|
106
|
+
// local midnight, which a server east of UTC sends as the day before.
|
|
107
|
+
if (pgTypes && typeof pgTypes.setTypeParser === 'function') {
|
|
108
|
+
pgTypes.setTypeParser(1082, (value) => value);
|
|
109
|
+
}
|
|
105
110
|
${
|
|
106
111
|
// The session cookie is resolved from the REQUEST rather than from
|
|
107
112
|
// process.env.NEXTAUTH_URL — see session-cookie-resolver.ts. Reading it the
|
|
@@ -387,8 +387,19 @@ function applyTemplateParams(text, params) {
|
|
|
387
387
|
function finalizeResolvedConfig(nodeType, config) {
|
|
388
388
|
var isDataNode = typeof nodeType === 'string' && nodeType.indexOf('data-') === 0;
|
|
389
389
|
var error = null;
|
|
390
|
+
// A config value may be a live element (a trigger's target handed to a
|
|
391
|
+
// custom-js node) or an object that refers back to itself: walking into a
|
|
392
|
+
// DOM node follows its React fibers forever (a page's every element-bound
|
|
393
|
+
// behavior died with "Maximum call stack size exceeded"). Elements are
|
|
394
|
+
// left as they are; an object is walked once.
|
|
395
|
+
var seen = typeof WeakSet === 'function' ? new WeakSet() : null;
|
|
390
396
|
function walk(obj, insideFilters) {
|
|
391
397
|
if (!obj || typeof obj !== 'object') return;
|
|
398
|
+
if (typeof obj.nodeType === 'number' && typeof obj.nodeName === 'string') return;
|
|
399
|
+
if (seen) {
|
|
400
|
+
if (seen.has(obj)) return;
|
|
401
|
+
seen.add(obj);
|
|
402
|
+
}
|
|
392
403
|
if (Array.isArray(obj)) {
|
|
393
404
|
for (var ai = 0; ai < obj.length; ai++) walk(obj[ai], insideFilters);
|
|
394
405
|
return;
|
|
@@ -1398,15 +1409,31 @@ var SEGMENT_REPLY_PRIVATE_KEYS = {
|
|
|
1398
1409
|
// be echoed back wholesale (every earlier node's result, the trigger, the whole
|
|
1399
1410
|
// page state), so each round trip carried the page twice over and the network
|
|
1400
1411
|
// tab showed every node's data on every hop.
|
|
1412
|
+
//
|
|
1413
|
+
// A node marked \`keepResultOnServer\` is the exception: its result serves the
|
|
1414
|
+
// nodes after it in the same segment and never leaves the server (a statement's
|
|
1415
|
+
// rows read only to build what the browser may see, a sender's recipients).
|
|
1416
|
+
// Nothing in a later segment can read it.
|
|
1417
|
+
// The nodes whose result the client branches on: never kept on the server.
|
|
1418
|
+
function isBranchingNodeType(type) {
|
|
1419
|
+
return type === 'general-if-statement' || type === 'general-switch';
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1401
1422
|
function segmentReply(segmentConfig, context, snapshot) {
|
|
1402
1423
|
var reply = {};
|
|
1403
1424
|
var own = {};
|
|
1425
|
+
var kept = {};
|
|
1404
1426
|
var nodes = (segmentConfig && segmentConfig.nodes) || [];
|
|
1405
|
-
for (var ni = 0; ni < nodes.length; ni++)
|
|
1427
|
+
for (var ni = 0; ni < nodes.length; ni++) {
|
|
1428
|
+
own[nodes[ni].id] = true;
|
|
1429
|
+
if (nodes[ni].config && nodes[ni].config.keepResultOnServer === true && !isBranchingNodeType(nodes[ni].type)) {
|
|
1430
|
+
kept[nodes[ni].id] = true;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1406
1433
|
var keys = Object.keys(context);
|
|
1407
1434
|
for (var i = 0; i < keys.length; i++) {
|
|
1408
1435
|
var key = keys[i];
|
|
1409
|
-
if (SEGMENT_REPLY_PRIVATE_KEYS[key]) continue;
|
|
1436
|
+
if (SEGMENT_REPLY_PRIVATE_KEYS[key] || kept[key]) continue;
|
|
1410
1437
|
var value = context[key];
|
|
1411
1438
|
if (value === undefined) continue;
|
|
1412
1439
|
if (own[key] || !snapshot || !Object.prototype.hasOwnProperty.call(snapshot, key)) {
|
|
@@ -1497,6 +1524,7 @@ module.exports = {
|
|
|
1497
1524
|
fatalNodeResultMessage,
|
|
1498
1525
|
continuesOnError,
|
|
1499
1526
|
isFireAndForgetNode,
|
|
1527
|
+
isBranchingNodeType,
|
|
1500
1528
|
startFireAndForgetNode,
|
|
1501
1529
|
registerPendingNodePromise,
|
|
1502
1530
|
settlePendingNodePromises
|
|
@@ -1821,25 +1849,19 @@ function findStreamingAINodes(allNodes, allEdges) {
|
|
|
1821
1849
|
const n = allNodes[ni];
|
|
1822
1850
|
if (aiTypes.indexOf(n.type) >= 0 && n.config && n.config.streaming) {
|
|
1823
1851
|
let onStreamEdge = null;
|
|
1824
|
-
let onEndEdge = null;
|
|
1825
1852
|
for (let ei = 0; ei < allEdges.length; ei++) {
|
|
1826
|
-
if (allEdges[ei].source === n.id)
|
|
1827
|
-
if (allEdges[ei].sourceHandle === 'on-stream') onStreamEdge = allEdges[ei];
|
|
1828
|
-
if (allEdges[ei].sourceHandle === 'on-end') onEndEdge = allEdges[ei];
|
|
1829
|
-
}
|
|
1853
|
+
if (allEdges[ei].source === n.id && allEdges[ei].sourceHandle === 'on-stream') onStreamEdge = allEdges[ei];
|
|
1830
1854
|
}
|
|
1831
1855
|
const onStreamNodes = onStreamEdge ? utils.collectBranchNodes(onStreamEdge.target, allNodes, allEdges, n.id) : [];
|
|
1832
|
-
|
|
1833
|
-
map[n.id] = { node: n, onStreamNodes: onStreamNodes, onEndNodes: onEndNodes };
|
|
1856
|
+
map[n.id] = { node: n, onStreamNodes: onStreamNodes };
|
|
1834
1857
|
}
|
|
1835
1858
|
}
|
|
1836
1859
|
return map;
|
|
1837
1860
|
}
|
|
1838
1861
|
|
|
1839
|
-
// A streaming AI node's on-stream
|
|
1840
|
-
//
|
|
1841
|
-
//
|
|
1842
|
-
// server segments with full configs; the browser bundle only ships their
|
|
1862
|
+
// A streaming AI node's on-stream branch is collected from the FULL workflow
|
|
1863
|
+
// node list, so it may contain server nodes. Those already execute inside their
|
|
1864
|
+
// own server segments with full configs; the browser bundle only ships their
|
|
1843
1865
|
// config redacted down to the client-safe whitelist (segment-splitter's
|
|
1844
1866
|
// redactServerNodeConfig), so executing them here with CLIENT handlers both
|
|
1845
1867
|
// double-executes them and crashes on the missing config. Emitted nodes carry
|
|
@@ -1852,7 +1874,6 @@ function clientExecutableBranchNodes(branchNodes) {
|
|
|
1852
1874
|
async function callStreamingServerSegment(segmentUrl, context, streamingInfo, allNodes, allEdges, clientHandlers, workflowConfig, executionId, stateKeys) {
|
|
1853
1875
|
const prunedContext = pruneContext(context, stateKeys);
|
|
1854
1876
|
const handledNodeIds = {};
|
|
1855
|
-
const streamedNodeIds = {};
|
|
1856
1877
|
// Same relative-URL problem as callServerSegment above — required even
|
|
1857
1878
|
// for streaming segments because a server-side caller (e.g. an AI
|
|
1858
1879
|
// chat webhook) would otherwise fail to parse the URL.
|
|
@@ -1899,7 +1920,6 @@ async function callStreamingServerSegment(segmentUrl, context, streamingInfo, al
|
|
|
1899
1920
|
try { data = JSON.parse(jsonStr); } catch(e) { continue; }
|
|
1900
1921
|
|
|
1901
1922
|
if (data.type === 'chunk' && data.nodeId) {
|
|
1902
|
-
streamedNodeIds[data.nodeId] = true;
|
|
1903
1923
|
context[data.nodeId] = { chunk: data.chunk, fullResponse: data.fullResponse };
|
|
1904
1924
|
const info = streamingInfo[data.nodeId];
|
|
1905
1925
|
if (info && info.onStreamNodes.length > 0) {
|
|
@@ -1914,22 +1934,13 @@ async function callStreamingServerSegment(segmentUrl, context, streamingInfo, al
|
|
|
1914
1934
|
} else if (data.type === 'node-result' && data.nodeId) {
|
|
1915
1935
|
context[data.nodeId] = data.result;
|
|
1916
1936
|
} else if (data.type === 'done') {
|
|
1937
|
+
// The on-end branch is not run here: its steps sit in the segments after
|
|
1938
|
+
// this one, and the caller runs those in graph order, so a browser step
|
|
1939
|
+
// waits for the server steps before it (the chat's "Bump Conversation
|
|
1940
|
+
// After Reply" -> "Get Chat Messages for Final Update").
|
|
1917
1941
|
if (data.results) {
|
|
1918
1942
|
mergeServerResults(context, data.results, workflowConfig && workflowConfig.triggerNodeId);
|
|
1919
1943
|
}
|
|
1920
|
-
const streamedKeys = Object.keys(streamedNodeIds);
|
|
1921
|
-
for (let sk = 0; sk < streamedKeys.length; sk++) {
|
|
1922
|
-
const endInfo = streamingInfo[streamedKeys[sk]];
|
|
1923
|
-
if (endInfo && endInfo.onEndNodes.length > 0) {
|
|
1924
|
-
const runnableEndNodes = clientExecutableBranchNodes(endInfo.onEndNodes);
|
|
1925
|
-
if (runnableEndNodes.length > 0) {
|
|
1926
|
-
await utils.executeNodes(runnableEndNodes, allEdges, context, clientHandlers, workflowConfig, null, executionId);
|
|
1927
|
-
}
|
|
1928
|
-
for (let eni = 0; eni < endInfo.onEndNodes.length; eni++) {
|
|
1929
|
-
handledNodeIds[endInfo.onEndNodes[eni].id] = true;
|
|
1930
|
-
}
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
1944
|
} else if (data.type === 'error') {
|
|
1934
1945
|
throw new Error(data.error || 'Streaming workflow segment failed');
|
|
1935
1946
|
}
|