@lowdefy/server-dev 0.0.0-experimental-20260720072521 → 0.0.0-experimental-20260720135014
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/.eslintrc.yaml +1 -0
- package/lib/build/app.js +1 -8
- package/lib/build/appMeta.js +1 -8
- package/lib/build/auth.js +1 -8
- package/lib/build/config.js +1 -8
- package/lib/build/i18n.js +1 -8
- package/lib/build/logger.js +1 -8
- package/lib/build/theme.js +1 -8
- package/lib/client/App.js +80 -0
- package/{client/Page.jsx → lib/client/Page.js} +5 -5
- package/{client/Reload.jsx → lib/client/Reload.js} +7 -5
- package/lib/client/auth/{Auth.jsx → Auth.js} +5 -12
- package/lib/client/auth/AuthConfigured.js +61 -0
- package/lib/client/auth/AuthNotConfigured.js +2 -22
- package/lib/client/setPageId.js +5 -6
- package/lib/client/utils/useMutateCache.js +1 -14
- package/lib/client/utils/usePageConfig.js +11 -31
- package/lib/server/apiWrapper.js +116 -0
- package/lib/server/auth/{getStrategies.js → getAuthOptions.js} +9 -14
- package/lib/server/auth/{getMockUser.js → getMockSession.js} +19 -10
- package/{src/routes/docs/checkpointsRevert.js → lib/server/auth/getServerSession.js} +13 -11
- package/lib/server/compileCss.js +39 -0
- package/lib/server/jitPageBuilder.js +11 -36
- package/lib/server/log/logRequest.js +3 -1
- package/manager/getContext.mjs +7 -3
- package/manager/processes/compileCss.mjs +54 -0
- package/manager/processes/initialBuild.mjs +2 -0
- package/manager/processes/installPlugins.mjs +11 -19
- package/manager/processes/lowdefyBuild.mjs +7 -29
- package/manager/processes/nextBuild.mjs +50 -0
- package/manager/processes/readDotEnv.mjs +1 -13
- package/manager/processes/shutdownServer.mjs +6 -6
- package/manager/processes/startServer.mjs +7 -8
- package/manager/processes/startWatchers.mjs +2 -2
- package/manager/run.mjs +0 -41
- package/manager/utils/createCustomPluginMessagesMap.mjs +1 -8
- package/manager/utils/createCustomPluginTypesMap.mjs +2 -3
- package/manager/utils/{getViteBin.mjs → getNextBin.mjs} +10 -7
- package/manager/utils/updatePageTailwindCss.mjs +1 -7
- package/manager/watchers/lowdefyBuildWatcher.mjs +1 -0
- package/manager/watchers/moduleBuildWatcher.mjs +1 -0
- package/manager/watchers/{serverArtifactWatcher.mjs → nextBuildWatcher.mjs} +22 -18
- package/next.config.js +25 -0
- package/package.json +47 -63
- package/package.original.json +55 -66
- package/{src/routes/devTools.js → pages/404.js} +2 -4
- package/{src/routes/docs/appMap.js → pages/[[...pageId]].js} +2 -6
- package/{client/App.jsx → pages/_app.js} +33 -21
- package/pages/_document.js +107 -0
- package/pages/api/agent/[...path].js +86 -0
- package/pages/api/auth/[...nextauth].js +48 -0
- package/pages/api/client-error.js +45 -0
- package/{src/routes/docs/buildStatus.js → pages/api/dev-tools.js} +4 -5
- package/{lib/docs/clientErrorStore.js → pages/api/endpoints/[...endpointId].js} +11 -15
- package/{src/lib/serveBuildJs.js → pages/api/icons/dynamic.js} +12 -8
- package/{src/routes/jsEnv.js → pages/api/js/[env].js} +20 -8
- package/pages/api/page/[...pageId].js +74 -0
- package/{src/routes → pages/api}/ping.js +2 -4
- package/pages/api/reload.js +53 -0
- package/pages/api/request/[...path].js +41 -0
- package/{src/routes → pages/api}/root.js +4 -4
- package/client/Inspector.jsx +0 -206
- package/client/Routing.jsx +0 -106
- package/client/feedback/FeedbackMount.jsx +0 -83
- package/client/feedback/FeedbackOverlay.jsx +0 -901
- package/client/feedback/captureTabScreenshot.js +0 -84
- package/client/feedback/copyToClipboard.js +0 -45
- package/client/feedback/drawAnnotationsSvg.js +0 -119
- package/client/feedback/elementInspect.js +0 -99
- package/client/feedback/feedbackStyles.js +0 -301
- package/client/feedback/pinResponsiveImages.js +0 -59
- package/client/feedback/sendFeedback.js +0 -39
- package/client/feedback/useFeedbackToggle.js +0 -70
- package/client/main.jsx +0 -42
- package/client/openInEditor/OpenInEditorListener.jsx +0 -261
- package/client/openInEditor/findBlockLocation.js +0 -51
- package/lib/client/auth/AuthConfigured.jsx +0 -155
- package/lib/docs/captureAnnotatedScreenshot.js +0 -88
- package/lib/docs/checkpointPaths.js +0 -91
- package/lib/docs/checkpointStore.js +0 -234
- package/lib/docs/checkpointToMocks.js +0 -65
- package/lib/docs/configCheckpoints.test.mjs +0 -156
- package/lib/docs/createConfigCheckpoint.js +0 -92
- package/lib/docs/createDocsMcpServer.js +0 -506
- package/lib/docs/createDocsMcpServer.test.mjs +0 -123
- package/lib/docs/devMockRegistry.js +0 -68
- package/lib/docs/docs.test.mjs +0 -448
- package/lib/docs/enrichFeedback.js +0 -71
- package/lib/docs/evalOperator.js +0 -39
- package/lib/docs/evalOperator.test.mjs +0 -112
- package/lib/docs/evalOperatorHeadless.js +0 -87
- package/lib/docs/evalOperatorHeadless.test.mjs +0 -72
- package/lib/docs/evalOperatorInTab.js +0 -48
- package/lib/docs/findConfig.js +0 -260
- package/lib/docs/formatFeedback.js +0 -121
- package/lib/docs/formatFeedback.test.mjs +0 -204
- package/lib/docs/getAppMap.js +0 -180
- package/lib/docs/getBrowser.js +0 -115
- package/lib/docs/getBuildStatus.js +0 -37
- package/lib/docs/getCoreDoc.js +0 -63
- package/lib/docs/getDocsManifest.js +0 -40
- package/lib/docs/getExamples.js +0 -58
- package/lib/docs/getOverview.js +0 -115
- package/lib/docs/getPageConfig.js +0 -53
- package/lib/docs/getPluginDoc.js +0 -55
- package/lib/docs/getSchema.js +0 -65
- package/lib/docs/inspectState.js +0 -41
- package/lib/docs/inspectState.test.mjs +0 -91
- package/lib/docs/inspectStateFromTab.js +0 -40
- package/lib/docs/inspectStateHeadless.js +0 -84
- package/lib/docs/inspectStateHeadless.test.mjs +0 -60
- package/lib/docs/isWriteRequestsAllowed.js +0 -50
- package/lib/docs/listConfigCheckpoints.js +0 -55
- package/lib/docs/listPlugins.js +0 -80
- package/lib/docs/listTypes.js +0 -105
- package/lib/docs/loadState.js +0 -139
- package/lib/docs/mapPageBuildErrors.js +0 -28
- package/lib/docs/normalizeTypeKind.js +0 -40
- package/lib/docs/readBuildArtifact.js +0 -35
- package/lib/docs/readPageArtifact.js +0 -26
- package/lib/docs/resolvePluginDir.js +0 -49
- package/lib/docs/revertConfigCheckpoint.js +0 -73
- package/lib/docs/runRequest.js +0 -141
- package/lib/docs/saveAnnotatedScreenshot.js +0 -43
- package/lib/docs/scaffoldPage.js +0 -75
- package/lib/docs/scaffoldPage.test.mjs +0 -97
- package/lib/docs/screenshotPage.js +0 -109
- package/lib/docs/screenshotPage.test.mjs +0 -59
- package/lib/docs/screenshotPageClip.test.mjs +0 -102
- package/lib/docs/searchDocs.js +0 -67
- package/lib/docs/setupTestFixtures.mjs +0 -335
- package/lib/docs/snapshotState.js +0 -59
- package/lib/docs/stateCheckpoints.test.mjs +0 -348
- package/lib/docs/tabAvailable.js +0 -28
- package/lib/docs/tabChannel.js +0 -119
- package/lib/docs/tabChannel.test.mjs +0 -130
- package/lib/server/auth/createSystemContext.js +0 -70
- package/lib/server/auth/getAuth.js +0 -46
- package/lib/server/auth/getHeadlessUser.js +0 -39
- package/lib/server/auth/headlessUser.js +0 -30
- package/lib/server/createLowdefyContext.js +0 -133
- package/lib/server/loadDynamicJsMap.js +0 -47
- package/manager/processes/lowdefyBuild.test.mjs +0 -102
- package/manager/utils/formatNoticeBox.mjs +0 -30
- package/manager/utils/writeBuildStatus.mjs +0 -39
- package/manager/utils/writeBuildStatus.test.mjs +0 -76
- package/postcss.config.cjs +0 -1
- package/src/app.js +0 -188
- package/src/html/renderDevPage.js +0 -86
- package/src/lib/getPathSegments.js +0 -25
- package/src/lib/safeScriptJson.js +0 -38
- package/src/middleware/apiContext.js +0 -32
- package/src/middleware/errorHandler.js +0 -55
- package/src/routes/agent.js +0 -64
- package/src/routes/auth.js +0 -43
- package/src/routes/clientError.js +0 -57
- package/src/routes/cron.js +0 -44
- package/src/routes/detached.js +0 -51
- package/src/routes/devInspect.js +0 -94
- package/src/routes/docs/checkpointsCreate.js +0 -28
- package/src/routes/docs/checkpointsList.js +0 -23
- package/src/routes/docs/content.js +0 -31
- package/src/routes/docs/evalOperator.js +0 -46
- package/src/routes/docs/examples.js +0 -33
- package/src/routes/docs/find.js +0 -30
- package/src/routes/docs/index.js +0 -23
- package/src/routes/docs/inspectState.js +0 -34
- package/src/routes/docs/loadState.js +0 -38
- package/src/routes/docs/mcp.js +0 -30
- package/src/routes/docs/pageConfig.js +0 -36
- package/src/routes/docs/pluginDoc.js +0 -33
- package/src/routes/docs/plugins.js +0 -23
- package/src/routes/docs/runRequest.js +0 -34
- package/src/routes/docs/schema.js +0 -37
- package/src/routes/docs/screenshot.js +0 -66
- package/src/routes/docs/search.js +0 -27
- package/src/routes/docs/snapshotState.js +0 -36
- package/src/routes/docs/stateCheckpointsList.js +0 -26
- package/src/routes/docs/types.js +0 -33
- package/src/routes/endpoints.js +0 -76
- package/src/routes/feedback.js +0 -110
- package/src/routes/feedback.test.mjs +0 -204
- package/src/routes/iconsDynamic.js +0 -25
- package/src/routes/jitPage.js +0 -99
- package/src/routes/reload.js +0 -81
- package/src/routes/request.js +0 -66
- package/src/routes/usage.js +0 -46
- package/src/routes/user.js +0 -27
- package/src/routes/websocket.js +0 -33
- package/src/websocket/devWebSocket.js +0 -78
- package/vite.config.js +0 -111
- /package/lib/client/{BuildErrorPage.jsx → BuildErrorPage.js} +0 -0
- /package/lib/client/{BuildingPage.jsx → BuildingPage.js} +0 -0
- /package/lib/client/{ErrorBar.jsx → ErrorBar.js} +0 -0
- /package/lib/client/{InstallingPluginsPage.jsx → InstallingPluginsPage.js} +0 -0
- /package/lib/client/{RestartingPage.jsx → RestartingPage.js} +0 -0
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { type } from '@lowdefy/helpers';
|
|
18
|
-
|
|
19
|
-
import screenshotPage from '../../../lib/docs/screenshotPage.js';
|
|
20
|
-
|
|
21
|
-
// Parses a query param as a number, or undefined if absent/not a valid
|
|
22
|
-
// number — undefined (not NaN/0) so callers can tell "not provided" apart
|
|
23
|
-
// from an explicit 0.
|
|
24
|
-
function queryNumber(c, key) {
|
|
25
|
-
const raw = c.req.query(key);
|
|
26
|
-
if (type.isNone(raw)) {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
const value = Number(raw);
|
|
30
|
-
return Number.isFinite(value) ? value : undefined;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function docsScreenshotHandler(c) {
|
|
34
|
-
const pageId = c.req.param('pageId');
|
|
35
|
-
const fullPage = c.req.query('fullPage') === 'true';
|
|
36
|
-
// Derived from the incoming request rather than a config value — this is
|
|
37
|
-
// the origin an agent can actually reach the dev server on (host/port it
|
|
38
|
-
// just connected to), regardless of how the server is bound.
|
|
39
|
-
const origin = new URL(c.req.url).origin;
|
|
40
|
-
|
|
41
|
-
const clipX = queryNumber(c, 'x');
|
|
42
|
-
const clipY = queryNumber(c, 'y');
|
|
43
|
-
const clipWidth = queryNumber(c, 'width');
|
|
44
|
-
const clipHeight = queryNumber(c, 'height');
|
|
45
|
-
// Only build a clip when all four dimensions are present — a partial
|
|
46
|
-
// clip is ambiguous, so screenshotPage falls back to a normal viewport
|
|
47
|
-
// screenshot in that case (same as an invalid clip).
|
|
48
|
-
const clip =
|
|
49
|
-
!type.isNone(clipX) &&
|
|
50
|
-
!type.isNone(clipY) &&
|
|
51
|
-
!type.isNone(clipWidth) &&
|
|
52
|
-
!type.isNone(clipHeight)
|
|
53
|
-
? { x: clipX, y: clipY, width: clipWidth, height: clipHeight }
|
|
54
|
-
: undefined;
|
|
55
|
-
const scrollX = queryNumber(c, 'scrollX') ?? 0;
|
|
56
|
-
const scrollY = queryNumber(c, 'scrollY') ?? 0;
|
|
57
|
-
|
|
58
|
-
const result = await screenshotPage({ origin, pageId, fullPage, clip, scrollX, scrollY });
|
|
59
|
-
if (result.error) {
|
|
60
|
-
return c.json({ error: result.error }, 502);
|
|
61
|
-
}
|
|
62
|
-
const buffer = Buffer.from(result.data, 'base64');
|
|
63
|
-
return c.body(buffer, 200, { 'Content-Type': result.mimeType });
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export default docsScreenshotHandler;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import searchDocs from '../../../lib/docs/searchDocs.js';
|
|
18
|
-
|
|
19
|
-
function docsSearchHandler(c) {
|
|
20
|
-
const query = c.req.query('q');
|
|
21
|
-
if (!query || query.trim() === '') {
|
|
22
|
-
return c.json({ error: 'Missing search query. Use GET /lowdefy-docs/search?q=your+keywords.' }, 400);
|
|
23
|
-
}
|
|
24
|
-
return c.json(searchDocs({ query }));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export default docsSearchHandler;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import snapshotState from '../../../lib/docs/snapshotState.js';
|
|
18
|
-
|
|
19
|
-
// Lets an agent (or a developer) snapshot the running app's page state and
|
|
20
|
-
// recorded request/api responses into a checkpoint folder under .lowdefy,
|
|
21
|
-
// for later replay via POST /lowdefy-docs/state/load.
|
|
22
|
-
async function docsSnapshotStateHandler(c) {
|
|
23
|
-
const { pageId, name, notes, source, overwrite } = await c.req.json();
|
|
24
|
-
// Derived from the incoming request rather than a config value — this is
|
|
25
|
-
// the origin an agent can actually reach the dev server on (host/port it
|
|
26
|
-
// just connected to), regardless of how the server is bound.
|
|
27
|
-
const origin = new URL(c.req.url).origin;
|
|
28
|
-
|
|
29
|
-
const result = await snapshotState({ origin, pageId, name, notes, source, overwrite });
|
|
30
|
-
if (result.error) {
|
|
31
|
-
return c.json({ error: result.error }, 502);
|
|
32
|
-
}
|
|
33
|
-
return c.json(result);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default docsSnapshotStateHandler;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { listStateCheckpoints } from '../../../lib/docs/checkpointStore.js';
|
|
18
|
-
|
|
19
|
-
// Lists state checkpoints (page state + recorded request/api
|
|
20
|
-
// responses) — distinct from GET /lowdefy-docs/checkpoints, which lists
|
|
21
|
-
// config-directory-snapshot checkpoints (see checkpointStore.js).
|
|
22
|
-
function docsStateCheckpointsListHandler(c) {
|
|
23
|
-
return c.json(listStateCheckpoints());
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export default docsStateCheckpointsListHandler;
|
package/src/routes/docs/types.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import listTypes from '../../../lib/docs/listTypes.js';
|
|
18
|
-
import normalizeTypeKind from '../../../lib/docs/normalizeTypeKind.js';
|
|
19
|
-
|
|
20
|
-
function docsTypesHandler(c) {
|
|
21
|
-
const kind = c.req.param('kind');
|
|
22
|
-
if (normalizeTypeKind({ kind }) === null) {
|
|
23
|
-
return c.json(
|
|
24
|
-
{
|
|
25
|
-
error: `Unknown docs kind "${kind}". Use one of: blocks, operators, actions, connections, requests, agents, notifications, websockets. See GET /lowdefy-docs for all routes.`,
|
|
26
|
-
},
|
|
27
|
-
404
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
return c.json(listTypes({ kind }));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default docsTypesHandler;
|
package/src/routes/endpoints.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { callEndpoint, getEndpointConfig, runWebhookEndpoint } from '@lowdefy/api';
|
|
18
|
-
|
|
19
|
-
import getPathSegments from '../lib/getPathSegments.js';
|
|
20
|
-
|
|
21
|
-
async function endpointsHandler(c) {
|
|
22
|
-
if (c.req.method !== 'POST') {
|
|
23
|
-
throw new Error('Only POST requests are supported.');
|
|
24
|
-
}
|
|
25
|
-
const context = c.get('lowdefyContext');
|
|
26
|
-
const endpointId = getPathSegments(c, '/api/endpoints/').join('/');
|
|
27
|
-
|
|
28
|
-
// Endpoints opting in with `webhook` (true or a { verify } object) are
|
|
29
|
-
// third-party webhook receivers (SNS, Event Grid, Stripe, ...): they take the
|
|
30
|
-
// request RAW — body in the caller's own format (not the { payload }
|
|
31
|
-
// envelope), plus query + headers — and their return value is sent back
|
|
32
|
-
// verbatim, because webhook handshakes (e.g. Event Grid's validationResponse)
|
|
33
|
-
// require exact response shapes. The config lookup is a cached file read; an
|
|
34
|
-
// unknown endpoint falls through to the standard path so its error shape is
|
|
35
|
-
// unchanged. Everything about non-webhook endpoints is untouched.
|
|
36
|
-
let endpointConfig = null;
|
|
37
|
-
try {
|
|
38
|
-
endpointConfig = await getEndpointConfig(context, { endpointId });
|
|
39
|
-
} catch {
|
|
40
|
-
/* unknown endpoint — the standard path below reports it exactly as before */
|
|
41
|
-
}
|
|
42
|
-
if (endpointConfig?.webhook) {
|
|
43
|
-
// SNS posts JSON as text/plain — parse regardless of content-type; an
|
|
44
|
-
// unparseable body arrives as the raw string.
|
|
45
|
-
const raw = await c.req.text();
|
|
46
|
-
let body = raw;
|
|
47
|
-
try {
|
|
48
|
-
body = JSON.parse(raw);
|
|
49
|
-
} catch {
|
|
50
|
-
/* non-JSON body — raw string passthrough */
|
|
51
|
-
}
|
|
52
|
-
context.logger.info({ event: 'call_webhook_endpoint', endpointId });
|
|
53
|
-
const result = await runWebhookEndpoint(context, {
|
|
54
|
-
endpointId,
|
|
55
|
-
body,
|
|
56
|
-
query: c.req.query(),
|
|
57
|
-
headers: c.req.header(),
|
|
58
|
-
});
|
|
59
|
-
// A failed verify gate is a rejected caller (401), distinct from a routine
|
|
60
|
-
// error (500).
|
|
61
|
-
if (result.status === 'unauthorized') {
|
|
62
|
-
return c.json({ error: 'Webhook verification failed.' }, 401);
|
|
63
|
-
}
|
|
64
|
-
if (!result.success) {
|
|
65
|
-
return c.json({ error: 'Webhook failed.' }, 500);
|
|
66
|
-
}
|
|
67
|
-
return c.json(result.response ?? { ok: true });
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const { blockId, payload, pageId } = await c.req.json();
|
|
71
|
-
context.logger.info({ event: 'call_api_endpoint', blockId, endpointId, pageId });
|
|
72
|
-
const response = await callEndpoint(context, { blockId, endpointId, pageId, payload });
|
|
73
|
-
return c.json(response);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export default endpointsHandler;
|
package/src/routes/feedback.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import enrichFeedback from '../../lib/docs/enrichFeedback.js';
|
|
18
|
-
import formatFeedback from '../../lib/docs/formatFeedback.js';
|
|
19
|
-
import createLogger from '../../lib/server/log/createLogger.js';
|
|
20
|
-
|
|
21
|
-
const logger = createLogger({ server: 'lowdefy-dev-feedback' });
|
|
22
|
-
|
|
23
|
-
// Receives annotation batches from the dev browser overlay (a developer
|
|
24
|
-
// draws on a live page and comments), enriches each annotation with its
|
|
25
|
-
// yaml file:line via findConfig, and returns the formatted agent-readable
|
|
26
|
-
// text. The overlay copies that text to the clipboard — the developer
|
|
27
|
-
// pastes it into whichever agent session they choose, which is also what
|
|
28
|
-
// makes delivery unambiguous when several sessions share one dev server.
|
|
29
|
-
// Same-origin check cloned from routes/clientError.js: only a page served
|
|
30
|
-
// by this dev server (not an arbitrary site) should be able to use it.
|
|
31
|
-
async function feedbackHandler(c) {
|
|
32
|
-
const origin = c.req.header('origin');
|
|
33
|
-
if (!origin) {
|
|
34
|
-
return c.json({ error: 'Forbidden' }, 403);
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
if (new URL(origin).host !== c.req.header('host')) {
|
|
38
|
-
return c.json({ error: 'Forbidden' }, 403);
|
|
39
|
-
}
|
|
40
|
-
} catch {
|
|
41
|
-
return c.json({ error: 'Forbidden' }, 403);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const batch = await c.req.json();
|
|
45
|
-
if (!Array.isArray(batch?.annotations)) {
|
|
46
|
-
return c.json(
|
|
47
|
-
{
|
|
48
|
-
error:
|
|
49
|
-
'Feedback batch requires an "annotations" array. See GET /lowdefy-docs for the ' +
|
|
50
|
-
'feedback batch shape.',
|
|
51
|
-
},
|
|
52
|
-
400
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// The tab screenshot rides in as a multi-MB data URL — lift it off the
|
|
57
|
-
// batch before enrichment so it never reaches the formatted text or the
|
|
58
|
-
// headless capture path.
|
|
59
|
-
const tabScreenshot = batch.screenshot;
|
|
60
|
-
delete batch.screenshot;
|
|
61
|
-
let enriched = await enrichFeedback({ batch });
|
|
62
|
-
delete enriched.screenshot;
|
|
63
|
-
|
|
64
|
-
// Default on — the overlay sends includeScreenshot: false when the
|
|
65
|
-
// developer unticks it.
|
|
66
|
-
if (batch.includeScreenshot !== false) {
|
|
67
|
-
const fileName = `${batch.pageId}-${new Date().toISOString().replace(/[:.]/g, '-')}.png`;
|
|
68
|
-
if (typeof tabScreenshot === 'string') {
|
|
69
|
-
// Preferred path: the overlay captured the developer's actual tab —
|
|
70
|
-
// theme, loaded data, exact pixels — with annotations already drawn.
|
|
71
|
-
const { default: saveAnnotatedScreenshot } = await import(
|
|
72
|
-
'../../lib/docs/saveAnnotatedScreenshot.js'
|
|
73
|
-
);
|
|
74
|
-
const result = saveAnnotatedScreenshot({ dataUrl: tabScreenshot, fileName });
|
|
75
|
-
if (result.path) {
|
|
76
|
-
enriched = { ...enriched, screenshotPath: result.path };
|
|
77
|
-
} else {
|
|
78
|
-
logger.warn(result.error);
|
|
79
|
-
}
|
|
80
|
-
} else {
|
|
81
|
-
// Fallback: re-render the page headless (batches from agents or older
|
|
82
|
-
// clients, or when the in-tab capture failed). The response must NOT
|
|
83
|
-
// wait for this capture: the overlay writes the clipboard when this
|
|
84
|
-
// returns, and Chrome's transient user-activation (required for
|
|
85
|
-
// clipboard writes) expires within ~5s — a headless browser launch can
|
|
86
|
-
// take longer. So the path is pre-assigned, the formatted text returns
|
|
87
|
-
// immediately, and the PNG lands on disk a moment later — well before
|
|
88
|
-
// a human pastes it anywhere.
|
|
89
|
-
// Deferred import: the capture module pulls in the browser singleton,
|
|
90
|
-
// which reads build artifacts at import time — only load it when a
|
|
91
|
-
// screenshot is actually wanted.
|
|
92
|
-
const { default: captureAnnotatedScreenshot } = await import(
|
|
93
|
-
'../../lib/docs/captureAnnotatedScreenshot.js'
|
|
94
|
-
);
|
|
95
|
-
const serverOrigin = new URL(c.req.url).origin;
|
|
96
|
-
enriched = { ...enriched, screenshotPath: `.lowdefy/annotations/${fileName}` };
|
|
97
|
-
captureAnnotatedScreenshot({ origin: serverOrigin, batch: enriched, fileName }).then(
|
|
98
|
-
(result) => {
|
|
99
|
-
if (result.error) {
|
|
100
|
-
logger.warn(result.error);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return c.json({ ok: true, formatted: formatFeedback({ items: [enriched] }) });
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export default feedbackHandler;
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import fs from 'fs';
|
|
18
|
-
import os from 'os';
|
|
19
|
-
import path from 'path';
|
|
20
|
-
|
|
21
|
-
import { Hono } from 'hono';
|
|
22
|
-
import { jest } from '@jest/globals';
|
|
23
|
-
|
|
24
|
-
// enrichFeedback (findConfig → force-builds a page) is covered on its own in
|
|
25
|
-
// lib/docs/formatFeedback.test.mjs — here it's stubbed as a pass-through so
|
|
26
|
-
// this file only exercises the route: origin check, validation, and enrich +
|
|
27
|
-
// format wiring.
|
|
28
|
-
const mockEnrichFeedback = jest.fn(async ({ batch }) => batch);
|
|
29
|
-
jest.unstable_mockModule('../../lib/docs/enrichFeedback.js', () => ({
|
|
30
|
-
default: mockEnrichFeedback,
|
|
31
|
-
}));
|
|
32
|
-
const mockCaptureAnnotatedScreenshot = jest.fn(async () => ({
|
|
33
|
-
path: '.lowdefy/annotations/test.png',
|
|
34
|
-
}));
|
|
35
|
-
jest.unstable_mockModule('../../lib/docs/captureAnnotatedScreenshot.js', () => ({
|
|
36
|
-
default: mockCaptureAnnotatedScreenshot,
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
const { default: feedbackHandler } = await import('./feedback.js');
|
|
40
|
-
|
|
41
|
-
function createApp() {
|
|
42
|
-
const app = new Hono();
|
|
43
|
-
app.all('/api/feedback', feedbackHandler);
|
|
44
|
-
return app;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let configDirectory;
|
|
48
|
-
const originalConfigDirectory = process.env.LOWDEFY_DIRECTORY_CONFIG;
|
|
49
|
-
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-feedback-test-'));
|
|
52
|
-
process.env.LOWDEFY_DIRECTORY_CONFIG = configDirectory;
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
afterEach(() => {
|
|
56
|
-
mockEnrichFeedback.mockClear();
|
|
57
|
-
mockCaptureAnnotatedScreenshot.mockClear();
|
|
58
|
-
fs.rmSync(configDirectory, { recursive: true, force: true });
|
|
59
|
-
if (originalConfigDirectory === undefined) {
|
|
60
|
-
delete process.env.LOWDEFY_DIRECTORY_CONFIG;
|
|
61
|
-
} else {
|
|
62
|
-
process.env.LOWDEFY_DIRECTORY_CONFIG = originalConfigDirectory;
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('feedbackHandler returns 403 when the Origin header is missing', async () => {
|
|
67
|
-
const res = await createApp().request('/api/feedback', {
|
|
68
|
-
method: 'POST',
|
|
69
|
-
headers: { host: 'localhost:3001', 'content-type': 'application/json' },
|
|
70
|
-
body: JSON.stringify({ annotations: [] }),
|
|
71
|
-
});
|
|
72
|
-
expect(res.status).toEqual(403);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('feedbackHandler returns 403 when the Origin host does not match the request host', async () => {
|
|
76
|
-
const res = await createApp().request('/api/feedback', {
|
|
77
|
-
method: 'POST',
|
|
78
|
-
headers: {
|
|
79
|
-
host: 'localhost:3001',
|
|
80
|
-
origin: 'http://evil.example.com',
|
|
81
|
-
'content-type': 'application/json',
|
|
82
|
-
},
|
|
83
|
-
body: JSON.stringify({ annotations: [] }),
|
|
84
|
-
});
|
|
85
|
-
expect(res.status).toEqual(403);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('feedbackHandler returns 400 with a helpful message when annotations is missing', async () => {
|
|
89
|
-
const res = await createApp().request('/api/feedback', {
|
|
90
|
-
method: 'POST',
|
|
91
|
-
headers: {
|
|
92
|
-
host: 'localhost:3001',
|
|
93
|
-
origin: 'http://localhost:3001',
|
|
94
|
-
'content-type': 'application/json',
|
|
95
|
-
},
|
|
96
|
-
body: JSON.stringify({ pageId: 'login' }),
|
|
97
|
-
});
|
|
98
|
-
expect(res.status).toEqual(400);
|
|
99
|
-
const body = await res.json();
|
|
100
|
-
expect(body.error).toMatch(/annotations/);
|
|
101
|
-
expect(body.error).toMatch(/\/lowdefy-docs/);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('feedbackHandler enriches a valid batch and returns the formatted text', async () => {
|
|
105
|
-
const batch = { pageId: 'login', annotations: [{ id: '1', kind: 'region', comment: 'x' }] };
|
|
106
|
-
const res = await createApp().request('/api/feedback', {
|
|
107
|
-
method: 'POST',
|
|
108
|
-
headers: {
|
|
109
|
-
host: 'localhost:3001',
|
|
110
|
-
origin: 'http://localhost:3001',
|
|
111
|
-
'content-type': 'application/json',
|
|
112
|
-
},
|
|
113
|
-
body: JSON.stringify(batch),
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
expect(res.status).toEqual(200);
|
|
117
|
-
const body = await res.json();
|
|
118
|
-
expect(body.ok).toBe(true);
|
|
119
|
-
expect(typeof body.formatted).toBe('string');
|
|
120
|
-
expect(body.formatted).toContain('login');
|
|
121
|
-
expect(mockEnrichFeedback).toHaveBeenCalledWith({ batch });
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// A real 1x1 red PNG so the saved file is a valid image, not just bytes.
|
|
125
|
-
const PNG_1X1_BASE64 =
|
|
126
|
-
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==';
|
|
127
|
-
|
|
128
|
-
test('feedbackHandler saves a tab-captured screenshot and skips the headless capture', async () => {
|
|
129
|
-
const batch = {
|
|
130
|
-
pageId: 'dashboard',
|
|
131
|
-
annotations: [{ id: '1', kind: 'region', comment: 'x' }],
|
|
132
|
-
screenshot: `data:image/png;base64,${PNG_1X1_BASE64}`,
|
|
133
|
-
};
|
|
134
|
-
const res = await createApp().request('/api/feedback', {
|
|
135
|
-
method: 'POST',
|
|
136
|
-
headers: {
|
|
137
|
-
host: 'localhost:3001',
|
|
138
|
-
origin: 'http://localhost:3001',
|
|
139
|
-
'content-type': 'application/json',
|
|
140
|
-
},
|
|
141
|
-
body: JSON.stringify(batch),
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
expect(res.status).toEqual(200);
|
|
145
|
-
const body = await res.json();
|
|
146
|
-
expect(body.ok).toBe(true);
|
|
147
|
-
expect(body.formatted).toContain('.lowdefy/annotations/dashboard-');
|
|
148
|
-
// The data URL must never leak into the agent-readable text.
|
|
149
|
-
expect(body.formatted).not.toContain(PNG_1X1_BASE64);
|
|
150
|
-
expect(mockCaptureAnnotatedScreenshot).not.toHaveBeenCalled();
|
|
151
|
-
|
|
152
|
-
const dir = path.join(configDirectory, '.lowdefy', 'annotations');
|
|
153
|
-
const files = fs.readdirSync(dir);
|
|
154
|
-
expect(files).toHaveLength(1);
|
|
155
|
-
expect(files[0]).toMatch(/^dashboard-.*\.png$/);
|
|
156
|
-
expect(fs.readFileSync(path.join(dir, files[0]))).toEqual(
|
|
157
|
-
Buffer.from(PNG_1X1_BASE64, 'base64')
|
|
158
|
-
);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test('feedbackHandler falls back to the headless capture when the screenshot is not a PNG data URL', async () => {
|
|
162
|
-
const batch = {
|
|
163
|
-
pageId: 'dashboard',
|
|
164
|
-
annotations: [{ id: '1', kind: 'region', comment: 'x' }],
|
|
165
|
-
screenshot: 'data:image/jpeg;base64,notpng',
|
|
166
|
-
};
|
|
167
|
-
const res = await createApp().request('/api/feedback', {
|
|
168
|
-
method: 'POST',
|
|
169
|
-
headers: {
|
|
170
|
-
host: 'localhost:3001',
|
|
171
|
-
origin: 'http://localhost:3001',
|
|
172
|
-
'content-type': 'application/json',
|
|
173
|
-
},
|
|
174
|
-
body: JSON.stringify(batch),
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
expect(res.status).toEqual(200);
|
|
178
|
-
const body = await res.json();
|
|
179
|
-
expect(body.ok).toBe(true);
|
|
180
|
-
// Not a valid tab capture — no file saved, and no screenshotPath claimed
|
|
181
|
-
// for it (the invalid-string branch logs a warning instead).
|
|
182
|
-
expect(fs.existsSync(path.join(configDirectory, '.lowdefy', 'annotations'))).toBe(false);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test('feedbackHandler uses the headless capture when the batch has no screenshot', async () => {
|
|
186
|
-
const batch = {
|
|
187
|
-
pageId: 'dashboard',
|
|
188
|
-
annotations: [{ id: '1', kind: 'region', comment: 'x' }],
|
|
189
|
-
};
|
|
190
|
-
const res = await createApp().request('/api/feedback', {
|
|
191
|
-
method: 'POST',
|
|
192
|
-
headers: {
|
|
193
|
-
host: 'localhost:3001',
|
|
194
|
-
origin: 'http://localhost:3001',
|
|
195
|
-
'content-type': 'application/json',
|
|
196
|
-
},
|
|
197
|
-
body: JSON.stringify(batch),
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
expect(res.status).toEqual(200);
|
|
201
|
-
const body = await res.json();
|
|
202
|
-
expect(body.formatted).toContain('.lowdefy/annotations/dashboard-');
|
|
203
|
-
expect(mockCaptureAnnotatedScreenshot).toHaveBeenCalledTimes(1);
|
|
204
|
-
});
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import serveBuildJs from '../lib/serveBuildJs.js';
|
|
18
|
-
|
|
19
|
-
function iconsDynamicHandler(c) {
|
|
20
|
-
return c.body(serveBuildJs(['plugins', 'iconsDynamic.js']), 200, {
|
|
21
|
-
'Content-Type': 'application/javascript',
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default iconsDynamicHandler;
|