@decocms/nextjs 7.31.1 → 7.31.2
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/nextjs",
|
|
3
|
-
"version": "7.31.
|
|
3
|
+
"version": "7.31.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"lint:unused": "knip"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@decocms/blocks": "7.31.
|
|
33
|
-
"@decocms/blocks-admin": "7.31.
|
|
32
|
+
"@decocms/blocks": "7.31.2",
|
|
33
|
+
"@decocms/blocks-admin": "7.31.2"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"next": ">=15.0.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { renderToString } from "react-dom/server";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
3
3
|
|
|
4
4
|
import { DraftPreviewBadge } from "./DraftPreviewBadge";
|
|
5
5
|
|
|
@@ -13,4 +13,20 @@ describe("DraftPreviewBadge (render)", () => {
|
|
|
13
13
|
const html = renderToString(<DraftPreviewBadge pointer="fila.vtex.app@v1" />);
|
|
14
14
|
expect(html).toContain("data:image/png;base64,");
|
|
15
15
|
});
|
|
16
|
+
|
|
17
|
+
describe("embedded in an iframe", () => {
|
|
18
|
+
const realTop = window.top;
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
Object.defineProperty(window, "top", {
|
|
21
|
+
value: realTop,
|
|
22
|
+
configurable: true,
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("renders nothing — Studio's own preview surface already has its own chrome", () => {
|
|
27
|
+
Object.defineProperty(window, "top", { value: {}, configurable: true });
|
|
28
|
+
const html = renderToString(<DraftPreviewBadge pointer="fila.vtex.app@v1" />);
|
|
29
|
+
expect(html).toBe("");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
16
32
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
|
-
import { buildExitUrl, buildShareUrl } from "./DraftPreviewBadge";
|
|
3
|
+
import { buildExitUrl, buildShareUrl, isFramed } from "./DraftPreviewBadge";
|
|
4
4
|
|
|
5
5
|
describe("buildShareUrl", () => {
|
|
6
6
|
it("pins the exact draft version so a recipient sees the same working tree", () => {
|
|
@@ -35,3 +35,21 @@ describe("buildExitUrl", () => {
|
|
|
35
35
|
);
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
|
+
|
|
39
|
+
describe("isFramed", () => {
|
|
40
|
+
it("is false for a same top window — the ordinary top-level tab case", () => {
|
|
41
|
+
const win = {} as typeof window;
|
|
42
|
+
win.top = win;
|
|
43
|
+
expect(isFramed(win)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("is true when top differs — embedded in another page's iframe", () => {
|
|
47
|
+
const win = {} as typeof window;
|
|
48
|
+
win.top = {} as typeof window; // a distinct object: the parent frame
|
|
49
|
+
expect(isFramed(win)).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("fails open (not framed) with no window — SSR / no hydration", () => {
|
|
53
|
+
expect(isFramed(undefined)).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
* the host site's CSS/Tailwind), and a very high z-index, so it renders the
|
|
16
16
|
* same on any consumer site. Only mounted when a draft is active
|
|
17
17
|
* (`DraftPreviewIndicator` gates it), so it never costs ordinary traffic.
|
|
18
|
+
*
|
|
19
|
+
* Hidden inside an iframe (`isFramed`): Studio's own preview surface embeds
|
|
20
|
+
* this exact draft render in an iframe that already has its own chrome
|
|
21
|
+
* (toolbar, version status) — floating a second "you're in preview" badge
|
|
22
|
+
* inside that frame would be redundant clutter, not a signal a reviewer
|
|
23
|
+
* needs. A reviewer following a shared preview link in their own top-level
|
|
24
|
+
* tab still sees it; only the embedded case is suppressed.
|
|
18
25
|
*/
|
|
19
26
|
import { type ReactNode, useCallback, useEffect, useRef, useState } from "react";
|
|
20
27
|
import { DECO_MARK_DATA_URI } from "./decoMark";
|
|
@@ -49,6 +56,19 @@ export function buildExitUrl(href: string): string {
|
|
|
49
56
|
return url.toString();
|
|
50
57
|
}
|
|
51
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Whether this render is embedded in another page's iframe — Studio's own
|
|
61
|
+
* preview surface, primarily. `window.top !== window` is a same-origin
|
|
62
|
+
* reference comparison, so it never throws cross-origin (unlike reading a
|
|
63
|
+
* cross-origin frame's properties). No `window` (SSR, or a consumer site
|
|
64
|
+
* that never hydrates this component) reports not-framed — fails open,
|
|
65
|
+
* matching the badge's "renders the same on any consumer site" guarantee.
|
|
66
|
+
* Pure and exported for tests.
|
|
67
|
+
*/
|
|
68
|
+
export function isFramed(win: typeof window | undefined = globalThis.window): boolean {
|
|
69
|
+
return typeof win !== "undefined" && win.top !== win;
|
|
70
|
+
}
|
|
71
|
+
|
|
52
72
|
const DECO_GREEN = "#0b3d1e";
|
|
53
73
|
/** Toast background — the deco lime. Text sits on it in dark green for contrast. */
|
|
54
74
|
const DECO_LIME = "#d0ec1a";
|
|
@@ -137,6 +157,12 @@ export function DraftPreviewBadge({ pointer }: DraftPreviewBadgeProps) {
|
|
|
137
157
|
}
|
|
138
158
|
}, [pointer]);
|
|
139
159
|
|
|
160
|
+
// Checked inline, not cached in state behind an effect: it's a free
|
|
161
|
+
// reference compare, and computing it during render means the client's
|
|
162
|
+
// first (hydration) pass already agrees with what it will show — no
|
|
163
|
+
// post-hydration flash for the common (unframed) case.
|
|
164
|
+
if (isFramed()) return null;
|
|
165
|
+
|
|
140
166
|
return (
|
|
141
167
|
<div
|
|
142
168
|
ref={rootRef}
|