@storyblok/astro 8.1.0 → 9.0.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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h1 align="center">@storyblok/astro</h1>
6
6
  <p>
7
- The Astro SDK to interact with <a href="http://www.storyblok.com?utm_source=github.com&utm_medium=readme&utm_campaign=storyblok-astro" target="_blank">Storyblok API</a> and enable the <a href="https://www.storyblok.com/docs/guide/essentials/visual-editor?utm_source=github.com&utm_medium=readme&utm_campaign=storyblok-astro" target="_blank">Real-time Visual Editing Experience</a>.
7
+ The Astro SDK to interact with <a href="https://www.storyblok.com/docs/api/content-delivery/v2" target="_blank">Storyblok API</a> and enable the <a href="https://www.storyblok.com/docs/guides/astro/visual-preview#enable-live-preview-in-the-visual-editor" target="_blank">Real-time Visual Editing Experience</a>.
8
8
  </p>
9
9
  <br />
10
10
  </div>
@@ -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, segmentStoryblokRichText, 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`.
@@ -35,5 +38,50 @@ export declare function useStoryblokApi(): StoryblokClient;
35
38
  * }
36
39
  * ```
37
40
  */
38
- export declare function getLiveStory(Astro: Readonly<AstroGlobal>): Promise<ISbStoryData | null>;
41
+ export declare function getLiveStory(Astro: {
42
+ locals: AstroGlobal['locals'];
43
+ }): Promise<ISbStoryData | null>;
44
+ /**
45
+ * Retrieves the live Storyblok story and server data from Astro's `locals` during preview mode.
46
+ *
47
+ * This function is primarily useful when working with the Storyblok Visual Editor
48
+ * and live preview updates in an Astro project.
49
+ *
50
+ * @template ServerData - The type of server data, constrained to StoryblokServerData
51
+ * @template Story - The type of story data, constrained to ISbStoryData
52
+ * @param {object} params - The function parameters
53
+ * @param {object} params.locals - The Astro locals object
54
+ * @returns {Promise<{ story?: Story; serverData?: ServerData }>} An object containing the story and serverData if available
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * // Basic usage:
59
+ * const payload = await getPayload({ locals: Astro.locals });
60
+ * const story = payload.story ?? null;
61
+ *
62
+ * // With typed server data:
63
+ * interface ServerData {
64
+ * users?: User[];
65
+ * }
66
+ *
67
+ * interface MyStory extends ISbStoryData {
68
+ * content: { myField: string };
69
+ * }
70
+ *
71
+ * const payload = await getPayload<ServerData, MyStory>({ locals: Astro.locals });
72
+ * const story = payload.story ?? null;
73
+ * const users = payload.serverData?.users ?? [];
74
+ * ```
75
+ */
76
+ export declare function getPayload<ServerData extends object = object, Story extends ISbStoryData = ISbStoryData>({ locals, }: {
77
+ locals: {
78
+ _storyblok_preview_data?: {
79
+ serverData?: ServerData;
80
+ story?: Story;
81
+ };
82
+ };
83
+ }): Promise<{
84
+ story?: Story;
85
+ serverData?: ServerData;
86
+ }>;
39
87
  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,8 +16,49 @@ 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
- import type { StoryblokClient } from '@storyblok/astro';
28
+ import type {
29
+ StoryblokClient,
30
+ StoryblokRichTextNode,
31
+ StoryblokRichTextResolvers,
32
+ } from '@storyblok/astro';
33
+ /**
34
+ * @experimental Converts a Storyblok RichText field into an HTML string.
35
+ *
36
+ * This API is still under development and may change in future releases.
37
+ * It also relies on Astro’s experimental
38
+ * [experimental_AstroContainer](https://docs.astro.build/en/reference/container-reference/) feature.
39
+ *
40
+ * @async
41
+ * @param {StoryblokRichTextNode} richTextField - The root RichText node to convert.
42
+ * @param {StoryblokRichTextResolvers} [customResolvers] - Optional custom resolvers
43
+ * for customizing how specific nodes or marks are transformed into HTML.
44
+ * @returns {Promise<string>} A promise that resolves to the HTML string representation
45
+ * of the provided RichText content.
46
+ *
47
+ * @example
48
+ * ```astro
49
+ * ---
50
+ * import { richTextToHTML } from '@storyblok/astro/client';
51
+ * const { blok } = Astro.props;
52
+ * const renderedRichText = await richTextToHTML(blok.text);
53
+ * ---
54
+ *
55
+ * <div set:html={renderedRichText} />
56
+ * ```
57
+ */
58
+ export function richTextToHTML(
59
+ richTextField: StoryblokRichTextNode,
60
+ customResolvers?: StoryblokRichTextResolvers
61
+ ): Promise<string>;
21
62
 
22
63
  /**
23
64
  * Provides direct access to the initialized Storyblok API client instance.