astro-tractstack 2.1.1 → 2.1.3
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/package.json +1 -1
- package/templates/custom/minimal/CodeHook.astro +1 -0
- package/templates/custom/with-examples/CodeHook.astro +1 -0
- package/templates/src/components/compositor/Compositor.tsx +6 -0
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +7 -2
- package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +3 -0
- package/templates/src/pages/[...slug].astro +1 -0
- package/templates/src/pages/api/sandbox.ts +40 -5
- package/templates/src/pages/context/[...contextSlug].astro +1 -0
- package/templates/src/pages/sandbox.astro +8 -0
- package/templates/src/stores/storykeep.ts +1 -12
package/package.json
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
brandConfigStore,
|
|
19
19
|
viewportModeStore,
|
|
20
20
|
setViewportMode,
|
|
21
|
+
sandboxTokenStore,
|
|
21
22
|
} from '@/stores/storykeep';
|
|
22
23
|
import { getCtx, ROOT_NODE_NAME, type NodesContext } from '@/stores/nodes';
|
|
23
24
|
import { stopLoadingAnimation } from '@/utils/helpers';
|
|
@@ -53,6 +54,7 @@ export type CompositorProps = {
|
|
|
53
54
|
urlParams: Record<string, string | boolean>;
|
|
54
55
|
fullCanonicalURL: string;
|
|
55
56
|
isSandboxMode?: boolean;
|
|
57
|
+
sandboxToken?: string;
|
|
56
58
|
};
|
|
57
59
|
|
|
58
60
|
const VERBOSE = false;
|
|
@@ -300,12 +302,16 @@ export const Compositor = (props: CompositorProps) => {
|
|
|
300
302
|
preferredThemeStore.set(props.config.THEME as Theme);
|
|
301
303
|
codehookMapStore.set(props.availableCodeHooks);
|
|
302
304
|
brandConfigStore.set(props.config);
|
|
305
|
+
if (props.sandboxToken) {
|
|
306
|
+
sandboxTokenStore.set(props.sandboxToken);
|
|
307
|
+
}
|
|
303
308
|
}, [
|
|
304
309
|
props.fullContentMap,
|
|
305
310
|
props.urlParams,
|
|
306
311
|
props.fullCanonicalURL,
|
|
307
312
|
props.availableCodeHooks,
|
|
308
313
|
props.config,
|
|
314
|
+
props.sandboxToken,
|
|
309
315
|
]);
|
|
310
316
|
|
|
311
317
|
// Initialize nodes tree and set up subscriptions
|
|
@@ -7,7 +7,7 @@ import SquaresPlusIcon from '@heroicons/react/24/outline/SquaresPlusIcon';
|
|
|
7
7
|
import DocumentIcon from '@heroicons/react/24/outline/DocumentIcon';
|
|
8
8
|
import { NodesContext, getCtx } from '@/stores/nodes';
|
|
9
9
|
import { cloneDeep } from '@/utils/helpers';
|
|
10
|
-
import { hasAssemblyAIStore } from '@/stores/storykeep';
|
|
10
|
+
import { hasAssemblyAIStore, sandboxTokenStore } from '@/stores/storykeep';
|
|
11
11
|
import prompts from '@/constants/prompts.json';
|
|
12
12
|
import type { DesignLibraryEntry } from '@/types/tractstack';
|
|
13
13
|
import { PaneAddMode, type TemplatePane } from '@/types/compositorTypes';
|
|
@@ -60,9 +60,14 @@ const callAskLemurAPI = async (
|
|
|
60
60
|
let resultData: any;
|
|
61
61
|
|
|
62
62
|
if (isSandboxMode) {
|
|
63
|
+
const token = sandboxTokenStore.get();
|
|
63
64
|
const response = await fetch(`/api/sandbox`, {
|
|
64
65
|
method: 'POST',
|
|
65
|
-
headers: {
|
|
66
|
+
headers: {
|
|
67
|
+
'Content-Type': 'application/json',
|
|
68
|
+
'X-Tenant-ID': tenantId,
|
|
69
|
+
'X-Sandbox-Token': token || '',
|
|
70
|
+
},
|
|
66
71
|
credentials: 'include',
|
|
67
72
|
body: JSON.stringify({ action: 'askLemur', payload: requestBody }),
|
|
68
73
|
});
|
|
@@ -6,6 +6,7 @@ import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
|
|
6
6
|
import SparklesIcon from '@heroicons/react/24/outline/SparklesIcon';
|
|
7
7
|
import { getCtx } from '@/stores/nodes';
|
|
8
8
|
import { selectionStore } from '@/stores/selection';
|
|
9
|
+
import { sandboxTokenStore } from '@/stores/storykeep';
|
|
9
10
|
import { AiDesignStep, type AiDesignConfig } from './steps/AiDesignStep';
|
|
10
11
|
import prompts from '@/constants/prompts.json';
|
|
11
12
|
import { TractStackAPI } from '@/utils/api';
|
|
@@ -34,11 +35,13 @@ const callAskLemurAPI = async (
|
|
|
34
35
|
let resultData: any;
|
|
35
36
|
|
|
36
37
|
if (isSandboxMode) {
|
|
38
|
+
const token = sandboxTokenStore.get();
|
|
37
39
|
const response = await fetch(`/api/sandbox`, {
|
|
38
40
|
method: 'POST',
|
|
39
41
|
headers: {
|
|
40
42
|
'Content-Type': 'application/json',
|
|
41
43
|
'X-Tenant-ID': tenantId,
|
|
44
|
+
'X-Sandbox-Token': token || '',
|
|
42
45
|
},
|
|
43
46
|
credentials: 'include',
|
|
44
47
|
body: JSON.stringify({ action: 'askLemur', payload: requestBody }),
|
|
@@ -158,6 +158,7 @@ paneIds.forEach((paneId: string) => {
|
|
|
158
158
|
<div class="relative overflow-hidden">
|
|
159
159
|
<CodeHook
|
|
160
160
|
target={codeHookTargets[paneId]}
|
|
161
|
+
paneId={paneId}
|
|
161
162
|
options={(() => {
|
|
162
163
|
const optionsStr =
|
|
163
164
|
codeHookTargets[paneId + '-' + codeHookTargets[paneId]];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHmac } from 'node:crypto';
|
|
1
2
|
import type { APIRoute } from '@/types/astro';
|
|
2
3
|
|
|
3
4
|
export const POST: APIRoute = async ({ request, locals }) => {
|
|
@@ -17,14 +18,48 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
17
18
|
);
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
?.includes('tractstack_profile');
|
|
23
|
-
if (!profileCookie) {
|
|
21
|
+
const tokenHeader = request.headers.get('X-Sandbox-Token');
|
|
22
|
+
if (!tokenHeader) {
|
|
24
23
|
return new Response(
|
|
25
24
|
JSON.stringify({
|
|
26
25
|
success: false,
|
|
27
|
-
error: '
|
|
26
|
+
error: 'Unauthorized: Missing sandbox token',
|
|
27
|
+
}),
|
|
28
|
+
{ status: 403, headers: { 'Content-Type': 'application/json' } }
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const [timestamp, signature] = tokenHeader.split('.');
|
|
33
|
+
if (!timestamp || !signature) {
|
|
34
|
+
return new Response(
|
|
35
|
+
JSON.stringify({
|
|
36
|
+
success: false,
|
|
37
|
+
error: 'Unauthorized: Invalid token format',
|
|
38
|
+
}),
|
|
39
|
+
{ status: 403, headers: { 'Content-Type': 'application/json' } }
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
if (now - parseInt(timestamp, 10) > 2 * 60 * 60 * 1000) {
|
|
45
|
+
return new Response(
|
|
46
|
+
JSON.stringify({
|
|
47
|
+
success: false,
|
|
48
|
+
error: 'Session expired. Please refresh the page.',
|
|
49
|
+
}),
|
|
50
|
+
{ status: 403, headers: { 'Content-Type': 'application/json' } }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const expectedSignature = createHmac('sha256', sharedSecret)
|
|
55
|
+
.update(timestamp)
|
|
56
|
+
.digest('hex');
|
|
57
|
+
|
|
58
|
+
if (signature !== expectedSignature) {
|
|
59
|
+
return new Response(
|
|
60
|
+
JSON.stringify({
|
|
61
|
+
success: false,
|
|
62
|
+
error: 'Unauthorized: Invalid signature',
|
|
28
63
|
}),
|
|
29
64
|
{ status: 403, headers: { 'Content-Type': 'application/json' } }
|
|
30
65
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { ulid } from 'ulid';
|
|
3
|
+
import { createHmac } from 'node:crypto';
|
|
3
4
|
import Layout from '@/layouts/Layout.astro';
|
|
4
5
|
import Header from '@/components/Header.astro';
|
|
5
6
|
import { getFullContentMap } from '@/stores/analytics';
|
|
@@ -48,6 +49,12 @@ const urlParams: Record<string, string | boolean> = {};
|
|
|
48
49
|
for (const [key, value] of Astro.url.searchParams) {
|
|
49
50
|
urlParams[key] = value === '' ? true : value;
|
|
50
51
|
}
|
|
52
|
+
|
|
53
|
+
const timestamp = Date.now().toString();
|
|
54
|
+
const signature = createHmac('sha256', import.meta.env.PRIVATE_SANDBOX_SECRET)
|
|
55
|
+
.update(timestamp)
|
|
56
|
+
.digest('hex');
|
|
57
|
+
const sandboxToken = `${timestamp}.${signature}`;
|
|
51
58
|
---
|
|
52
59
|
|
|
53
60
|
<Layout
|
|
@@ -111,6 +118,7 @@ for (const [key, value] of Astro.url.searchParams) {
|
|
|
111
118
|
urlParams={urlParams}
|
|
112
119
|
availableCodeHooks={Object.keys(codeHookComponents)}
|
|
113
120
|
isSandboxMode={true}
|
|
121
|
+
sandboxToken={sandboxToken}
|
|
114
122
|
client:only="react"
|
|
115
123
|
/>
|
|
116
124
|
</div>
|
|
@@ -30,8 +30,8 @@ export const codehookMapStore = atom<string[]>([]);
|
|
|
30
30
|
export const pendingHomePageSlugStore = atom<string | null>(null);
|
|
31
31
|
|
|
32
32
|
export const saasModalOpenStore = atom<boolean>(false);
|
|
33
|
+
export const sandboxTokenStore = atom<string | null>(null);
|
|
33
34
|
|
|
34
|
-
// Tool mode types
|
|
35
35
|
export type ToolModeVal =
|
|
36
36
|
| 'styles'
|
|
37
37
|
| 'text'
|
|
@@ -40,7 +40,6 @@ export type ToolModeVal =
|
|
|
40
40
|
| 'move'
|
|
41
41
|
| 'debug';
|
|
42
42
|
|
|
43
|
-
// Tool add mode types
|
|
44
43
|
export type ToolAddMode =
|
|
45
44
|
| 'p'
|
|
46
45
|
| 'h2'
|
|
@@ -54,10 +53,8 @@ export type ToolAddMode =
|
|
|
54
53
|
| 'identify'
|
|
55
54
|
| 'toggle';
|
|
56
55
|
|
|
57
|
-
// Header positioning state
|
|
58
56
|
export type HeaderPositionState = 'normal' | 'sticky';
|
|
59
57
|
|
|
60
|
-
// Viewport and display state
|
|
61
58
|
export const viewportModeStore = atom<ViewportKey>('auto');
|
|
62
59
|
export const viewportKeyStore = map<{
|
|
63
60
|
value: 'mobile' | 'tablet' | 'desktop';
|
|
@@ -69,26 +66,19 @@ export const isEditingStore = atom<boolean>(false);
|
|
|
69
66
|
|
|
70
67
|
export const showAnalyticsStore = atom<boolean>(false);
|
|
71
68
|
|
|
72
|
-
// Header positioning
|
|
73
69
|
export const headerPositionStore = atom<HeaderPositionState>('normal');
|
|
74
70
|
|
|
75
|
-
// Settings panel state
|
|
76
71
|
export const settingsPanelOpenStore = atom<boolean>(false);
|
|
77
72
|
export const addPanelOpenStore = atom<boolean>(false);
|
|
78
73
|
|
|
79
|
-
// Mobile-specific behavior
|
|
80
74
|
export const mobileHeaderFadedStore = atom<boolean>(false);
|
|
81
75
|
|
|
82
|
-
// Undo/redo state
|
|
83
76
|
export const canUndoStore = atom<boolean>(false);
|
|
84
77
|
export const canRedoStore = atom<boolean>(false);
|
|
85
78
|
|
|
86
|
-
// Actions
|
|
87
79
|
export const toggleSettingsPanel = () => {
|
|
88
80
|
const isOpen = !settingsPanelOpenStore.get();
|
|
89
81
|
settingsPanelOpenStore.set(isOpen);
|
|
90
|
-
|
|
91
|
-
// Handle mobile behavior
|
|
92
82
|
handleSettingsPanelMobile(isOpen);
|
|
93
83
|
};
|
|
94
84
|
export const toggleAddPanel = () => {
|
|
@@ -112,7 +102,6 @@ const getViewportFromWidth = (
|
|
|
112
102
|
|
|
113
103
|
export const setViewportMode = (mode: ViewportKey) => {
|
|
114
104
|
viewportModeStore.set(mode);
|
|
115
|
-
// Sync viewportKeyStore
|
|
116
105
|
if (mode === 'auto') {
|
|
117
106
|
const actualViewport = getViewportFromWidth(window.innerWidth);
|
|
118
107
|
viewportKeyStore.setKey('value', actualViewport);
|