@storyblok/astro 7.3.8 → 7.4.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ ---
2
+ /**
3
+ * StoryblokServerData Component
4
+ *
5
+ * Pass server-side data to the client that persists across live preview updates.
6
+ * All props passed to this component will be serialized as JSON and available
7
+ * to the client via the extraData parameter in getLiveStory().
8
+ *
9
+ * Data is automatically sanitized to prevent XSS attacks and script injection.
10
+ *
11
+ * @example
12
+ * ```astro
13
+ * ---
14
+ * import { StoryblokServerData } from '@storyblok/astro';
15
+ * const users = await getUsers();
16
+ * const config = { apiUrl: 'https://api.example.com' };
17
+ * ---
18
+ *
19
+ * <StoryblokServerData users={users} config={config} />
20
+ * ```
21
+ */
22
+
23
+ import { sanitizeJSON } from '@storyblok/astro';
24
+
25
+ const props = Astro.props;
26
+ const safeJson = sanitizeJSON(props);
27
+ ---
28
+
29
+ <script is:inline id="__STORYBLOK_SERVERDATA__" type="application/json" set:html={safeJson} />
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  /// <reference path="./public.d.ts" />
2
2
  import { default as storyblokIntegration } from './lib/storyblok-integration';
3
- export { getLiveStory, useStoryblokApi } from './lib/helpers';
3
+ export { getLiveStory, getPayload, useStoryblokApi } from './lib/helpers';
4
+ export { sanitizeJSON } from './lib/sanitizeJSON';
4
5
  export type { IntegrationOptions } from './lib/storyblok-integration';
5
6
  export { handleStoryblokMessage } from './live-preview/handleStoryblokMessage';
6
7
  export * from './types';
8
+ export { isEditorRequest } from './utils/isEditorRequest';
7
9
  export { toCamelCase } from './utils/toCamelCase';
8
10
  export { loadStoryblokBridge, renderRichText, richTextResolver, storyblokEditable, } from '@storyblok/js';
9
11
  export { storyblokIntegration as storyblok };
@@ -23,6 +23,9 @@ export declare function useStoryblokApi(): StoryblokClient;
23
23
  * This function is primarily useful when working with the Storyblok Visual Editor
24
24
  * and live preview updates in an Astro project.
25
25
  *
26
+ * @deprecated Use `getPayload()` instead for better type safety and to access both story and serverData.
27
+ * @see {@link getPayload}
28
+ *
26
29
  * @param {Readonly<AstroGlobal>} Astro - The Astro global object.
27
30
  * @returns {Promise<ISbStoryData | null>} The Storyblok story data if available,
28
31
  * otherwise `null`.
@@ -36,4 +39,47 @@ export declare function useStoryblokApi(): StoryblokClient;
36
39
  * ```
37
40
  */
38
41
  export declare function getLiveStory(Astro: Readonly<AstroGlobal>): Promise<ISbStoryData | null>;
42
+ /**
43
+ * Retrieves the live Storyblok story and server data from Astro's `locals` during preview mode.
44
+ *
45
+ * This function is primarily useful when working with the Storyblok Visual Editor
46
+ * and live preview updates in an Astro project.
47
+ *
48
+ * @template ServerData - The type of server data, constrained to StoryblokServerData
49
+ * @template Story - The type of story data, constrained to ISbStoryData
50
+ * @param {object} params - The function parameters
51
+ * @param {object} params.locals - The Astro locals object
52
+ * @returns {Promise<{ story?: Story; serverData?: ServerData }>} An object containing the story and serverData if available
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * // Basic usage:
57
+ * const payload = await getPayload({ locals: Astro.locals });
58
+ * const story = payload.story ?? null;
59
+ *
60
+ * // With typed server data:
61
+ * interface ServerData {
62
+ * users?: User[];
63
+ * }
64
+ *
65
+ * interface MyStory extends ISbStoryData {
66
+ * content: { myField: string };
67
+ * }
68
+ *
69
+ * const payload = await getPayload<ServerData, MyStory>({ locals: Astro.locals });
70
+ * const story = payload.story ?? null;
71
+ * const users = payload.serverData?.users ?? [];
72
+ * ```
73
+ */
74
+ export declare function getPayload<ServerData extends object = object, Story extends ISbStoryData = ISbStoryData>({ locals, }: {
75
+ locals: {
76
+ _storyblok_preview_data?: {
77
+ serverData?: ServerData;
78
+ story?: Story;
79
+ };
80
+ };
81
+ }): Promise<{
82
+ story?: Story;
83
+ serverData?: ServerData;
84
+ }>;
39
85
  export declare function initStoryblokBridge(config: boolean | StoryblokBridgeConfigV2): string;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Safely serializes data to JSON and escapes characters that could break out of a script tag
3
+ * or execute malicious code when embedded in HTML.
4
+ *
5
+ * This prevents XSS attacks by escaping:
6
+ * - `</script>` tags that could close the script tag early
7
+ * - `<!--` and `-->` HTML comment sequences
8
+ * - Line separator (U+2028) and paragraph separator (U+2029) which are valid JSON but can break JS
9
+ * - Other potentially dangerous Unicode characters
10
+ *
11
+ * @param data - The data to serialize
12
+ * @returns A safe JSON string that can be embedded in HTML
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const safeJSON = sanitizeJSON({
17
+ * message: '</script><script>alert("XSS")</script>'
18
+ * });
19
+ * // Returns escaped version that won't execute
20
+ * ```
21
+ */
22
+ export declare function sanitizeJSON(data: unknown): string;
23
+ /**
24
+ * Parses JSON that was sanitized with sanitizeJSON()
25
+ * This is just JSON.parse but provided for symmetry and clarity
26
+ *
27
+ * @param json - The sanitized JSON string
28
+ * @returns The parsed data
29
+ */
30
+ export declare function parseSanitizedJSON<T = unknown>(json: string): T;
@@ -1,18 +1,17 @@
1
1
  import { defineMiddleware } from 'astro/middleware';
2
+ import { isEditorRequest } from '@storyblok/astro';
2
3
 
3
4
  export const onRequest = defineMiddleware(async ({ locals, request }, next) => {
4
5
  if (request.method === 'POST') {
5
- const url = new URL(request.url);
6
- // First do a basic check if its coming from within storyblok
7
- const isStoryblokRequest
8
- = url.searchParams.has('_storyblok')
9
- && url.searchParams.has('_storyblok_c');
6
+ // First do a check if its coming from within storyblok
7
+ const editorRequest
8
+ = isEditorRequest(new URL(request.url));
10
9
 
11
- if (isStoryblokRequest) {
10
+ if (editorRequest) {
12
11
  try {
13
12
  // Create a copy of the request
14
13
  const requestBody = await request.clone().json();
15
- if (requestBody && requestBody.is_storyblok_preview) {
14
+ if (requestBody?.story?.is_storyblok_preview) {
16
15
  locals._storyblok_preview_data = requestBody;
17
16
  }
18
17
  }
package/dist/public.d.ts CHANGED
@@ -16,6 +16,14 @@ declare module '@storyblok/astro/StoryblokComponent.astro' {
16
16
  /** Renders a dynamic Storyblok component */
17
17
  export default StoryblokComponent;
18
18
  }
19
+ declare module '@storyblok/astro/StoryblokServerData.astro' {
20
+ function StoryblokServerData(
21
+ _props: Record<string, unknown>
22
+ ): any;
23
+
24
+ /** Renders a dynamic Storyblok component */
25
+ export default StoryblokServerData;
26
+ }
19
27
  declare module '@storyblok/astro/client' {
20
28
  import type {
21
29
  StoryblokClient,