@pack/react 1.0.0 → 2.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/dist/types.d.ts CHANGED
@@ -7,4 +7,64 @@ export type SectionConfig = {
7
7
  name: string;
8
8
  };
9
9
  export type SectionMap = Map<string, Section>;
10
+ export interface MessageTypeDataMap {
11
+ INIT: {
12
+ version: string;
13
+ storefrontMeta: any[];
14
+ };
15
+ ACK: {
16
+ messageId: string;
17
+ };
18
+ PING: never;
19
+ PONG: {
20
+ url: string;
21
+ };
22
+ ERROR: {
23
+ code: string;
24
+ message: string;
25
+ };
26
+ DISPLAY_ERROR: {
27
+ message: string;
28
+ errorCode?: string;
29
+ };
30
+ NAVIGATE_TO_PAGE: {
31
+ path: string;
32
+ };
33
+ SET_CURRENT_ROUTE: {
34
+ currentPath: string;
35
+ description: string;
36
+ environment: string;
37
+ handle: string;
38
+ title: string;
39
+ template?: string;
40
+ templateType?: string;
41
+ };
42
+ SET_SECTIONS_SCHEMAS: {
43
+ schema: string;
44
+ };
45
+ SET_STOREFRONT_SETTINGS_SCHEMA: {
46
+ schema: string;
47
+ };
48
+ SET_PAGE_DATA: {
49
+ content: Record<string, unknown>;
50
+ };
51
+ SEND_STOREFRONT_SETTINGS: never;
52
+ SET_STOREFRONT_SETTINGS: {
53
+ settings: Record<string, unknown>;
54
+ };
55
+ SCROLL_TO_SECTION: {
56
+ sectionId: string;
57
+ };
58
+ }
59
+ export interface MessageProtocol<T extends keyof MessageTypeDataMap = keyof MessageTypeDataMap> {
60
+ pack: {
61
+ type: T;
62
+ data?: MessageTypeDataMap[T];
63
+ origin: string;
64
+ version: string;
65
+ messageId: string;
66
+ timestamp: number;
67
+ errorCode?: string;
68
+ };
69
+ }
10
70
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACzC,MAAM,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACrE,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACzC,MAAM,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACrE,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAG9C,MAAM,WAAW,kBAAkB;IAEjC,IAAI,EAAE;QACJ,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,EAAE,GAAG,EAAE,CAAC;KACvB,CAAC;IACF,GAAG,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3B,IAAI,EAAE,KAAK,CAAC;IACZ,IAAI,EAAE;QACJ,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IAGF,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,aAAa,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAGvD,gBAAgB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,iBAAiB,EAAE;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IAGF,oBAAoB,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,8BAA8B,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAGnD,aAAa,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IACpD,wBAAwB,EAAE,KAAK,CAAC;IAChC,uBAAuB,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IAG/D,iBAAiB,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1C;AAED,MAAM,WAAW,eAAe,CAC9B,CAAC,SAAS,MAAM,kBAAkB,GAAG,MAAM,kBAAkB;IAE7D,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,CAAC;QACR,IAAI,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH"}
@@ -1,4 +1,8 @@
1
- export declare const useCustomizerShell: ({ environment, sectionComponents, storefrontSettingsSchema, }: any) => {
1
+ export declare const useCustomizerShell: ({ environment, sectionComponents, storefrontSettingsSchema, }: {
2
+ environment: string;
3
+ sectionComponents: any;
4
+ storefrontSettingsSchema?: any;
5
+ }) => {
2
6
  content: any;
3
7
  };
4
8
  //# sourceMappingURL=use-customizer-shell.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-customizer-shell.d.ts","sourceRoot":"","sources":["../src/use-customizer-shell.tsx"],"names":[],"mappings":"AAWA,eAAO,MAAM,kBAAkB,kEAI5B,GAAG;;CA4JL,CAAC"}
1
+ {"version":3,"file":"use-customizer-shell.d.ts","sourceRoot":"","sources":["../src/use-customizer-shell.tsx"],"names":[],"mappings":"AAkBA,eAAO,MAAM,kBAAkB;iBAKhB,MAAM;uBACA,GAAG;+BACK,GAAG;;;CAiQ/B,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import { useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
2
- import { connectToParent, ErrorCode } from "penpal";
2
+ import { VERSION } from "./version";
3
3
  import { PreviewContext } from "./preview/preview-content";
4
4
  export const useCustomizerShell = ({ environment, sectionComponents, storefrontSettingsSchema, }) => {
5
- const { isPreview, content, setContent, setSiteSettings } = useContext(PreviewContext);
5
+ const { content, isPreview, pathname, setContent, setSiteSettings } = useContext(PreviewContext);
6
6
  const data = {
7
7
  content,
8
8
  template: content.template?.type,
@@ -11,27 +11,119 @@ export const useCustomizerShell = ({ environment, sectionComponents, storefrontS
11
11
  title: content.title,
12
12
  description: content.description,
13
13
  };
14
- const windowLocationRef = useRef();
15
- const [parentConnection, setParentConnection] = useState(null);
16
- const [shouldConnectToParent, setShouldConnectToParent] = useState(false);
17
- const navigate = (path) => {
18
- window.location.href = path;
19
- };
20
- useEffect(() => {
21
- /*
22
- * Should not try to connect to customizer
23
- * if not in an iframe or not in preview mode
24
- */
25
- const isIframe = window.self !== window.top;
26
- if (!isIframe || !isPreview)
27
- return;
28
- setShouldConnectToParent(true);
14
+ const [hasInit, setHasInit] = useState(false);
15
+ const pendingMessagesRef = useRef(new Map());
16
+ const currentPathRef = useRef();
17
+ const canConnectToParent = useCanConnectCheck({ isPreview });
18
+ const sendToParent = useCallback(async (type, data) => {
19
+ const messageId = crypto.randomUUID();
20
+ const message = {
21
+ pack: {
22
+ type,
23
+ data,
24
+ origin: "*",
25
+ version: VERSION,
26
+ messageId,
27
+ timestamp: Date.now(),
28
+ },
29
+ };
30
+ if (type === "ACK") {
31
+ window.parent.postMessage(message, "*");
32
+ return Promise.resolve();
33
+ }
34
+ const messagePromise = new Promise((resolve, reject) => {
35
+ const timeout = setTimeout(() => {
36
+ pendingMessagesRef.current.delete(messageId);
37
+ reject(new Error(`Message ${messageId} timed out waiting for ACK`));
38
+ }, 2000);
39
+ pendingMessagesRef.current.set(messageId, {
40
+ messageId,
41
+ resolve,
42
+ reject,
43
+ timeout,
44
+ });
45
+ });
46
+ window.parent.postMessage(message, "*");
47
+ return messagePromise;
29
48
  }, []);
30
49
  useEffect(() => {
31
- windowLocationRef.current = window.location;
32
- }, [data.handle]);
50
+ if (!isPreview || !canConnectToParent || hasInit)
51
+ return;
52
+ sendToParent("INIT", {
53
+ storefrontMeta: [],
54
+ version: VERSION,
55
+ }).catch((error) => {
56
+ console.error("Failed to initialize:", error);
57
+ });
58
+ setHasInit(true);
59
+ const handleParentMessages = (event) => {
60
+ if (!event.data?.pack) {
61
+ return;
62
+ }
63
+ const message = event.data;
64
+ // Handle ACK messages
65
+ if (message.pack.type === "ACK") {
66
+ const acknowledgedMessageId = message.pack.data?.messageId;
67
+ const pendingMessage = pendingMessagesRef.current.get(acknowledgedMessageId);
68
+ if (pendingMessage) {
69
+ clearTimeout(pendingMessage.timeout);
70
+ pendingMessage.resolve();
71
+ pendingMessagesRef.current.delete(acknowledgedMessageId);
72
+ }
73
+ return;
74
+ }
75
+ // Handle other message types
76
+ sendToParent("ACK", { messageId: message.pack.messageId }).catch(console.error);
77
+ switch (message.pack.type) {
78
+ case "PING":
79
+ sendToParent("PONG", {
80
+ url: window.location.href,
81
+ });
82
+ break;
83
+ case "NAVIGATE_TO_PAGE":
84
+ const path = message.pack.data?.path;
85
+ if (path) {
86
+ window.location.href = path;
87
+ }
88
+ break;
89
+ case "SCROLL_TO_SECTION":
90
+ const sectionId = message.pack.data?.sectionId;
91
+ if (sectionId) {
92
+ const sectionEl = document.querySelector(`[data-comp-id="${sectionId}"]`);
93
+ if (sectionEl)
94
+ sectionEl.scrollIntoView({ behavior: "smooth" });
95
+ }
96
+ break;
97
+ case "SET_PAGE_DATA":
98
+ setContent(message.pack.data?.content);
99
+ break;
100
+ case "SET_STOREFRONT_SETTINGS":
101
+ setSiteSettings({
102
+ data: {
103
+ siteSettings: {
104
+ settings: message.pack
105
+ .data?.settings,
106
+ },
107
+ },
108
+ });
109
+ break;
110
+ default:
111
+ break;
112
+ }
113
+ };
114
+ window.addEventListener("message", handleParentMessages);
115
+ return () => {
116
+ window.removeEventListener("message", handleParentMessages);
117
+ // Clean up any pending messages
118
+ pendingMessagesRef.current.forEach((pending) => {
119
+ clearTimeout(pending.timeout);
120
+ pending.reject(new Error("Component unmounted"));
121
+ });
122
+ pendingMessagesRef.current.clear();
123
+ };
124
+ }, [canConnectToParent, isPreview, sendToParent]);
33
125
  const refreshSections = useCallback(() => {
34
- if (!sectionComponents || !parentConnection)
126
+ if (!sectionComponents)
35
127
  return [];
36
128
  const sectionSchemas = Array.from(sectionComponents)
37
129
  .map(([, section]) => {
@@ -41,78 +133,60 @@ export const useCustomizerShell = ({ environment, sectionComponents, storefrontS
41
133
  })
42
134
  .filter(Boolean);
43
135
  try {
44
- parentConnection?.setSectionsSchemas(JSON.stringify(sectionSchemas));
136
+ sendToParent("SET_SECTIONS_SCHEMAS", {
137
+ schema: JSON.stringify(sectionSchemas),
138
+ });
45
139
  }
46
140
  catch (error) {
47
- if (error.code !== ErrorCode.ConnectionDestroyed) {
48
- parentConnection?.displayError("Something went wrong parsing sections");
141
+ if (error.code !== "ConnectionDestroyed") {
142
+ sendToParent("DISPLAY_ERROR", {
143
+ message: "Something went wrong parsing sections",
144
+ });
49
145
  }
50
146
  }
51
147
  return;
52
- }, [data.handle, parentConnection, sectionComponents]);
148
+ }, [data.handle, sectionComponents, sendToParent]);
53
149
  const refreshStorefrontSettingsSchema = useCallback(() => {
54
- if (!storefrontSettingsSchema || !parentConnection || !sectionComponents) {
150
+ if (!storefrontSettingsSchema || !sectionComponents) {
55
151
  return [];
56
152
  }
57
153
  try {
58
- parentConnection.setStorefrontSettingsSchema(JSON.stringify(storefrontSettingsSchema));
154
+ sendToParent("SET_STOREFRONT_SETTINGS_SCHEMA", {
155
+ schema: JSON.stringify(storefrontSettingsSchema),
156
+ });
59
157
  }
60
158
  catch (error) {
61
- if (error.code !== ErrorCode.ConnectionDestroyed) {
62
- parentConnection?.displayError("Something went wrong parsing sections");
159
+ if (error.code !== "ConnectionDestroyed") {
160
+ sendToParent("DISPLAY_ERROR", {
161
+ message: "Something went wrong parsing settings",
162
+ });
63
163
  }
64
164
  }
65
165
  return;
66
166
  // eslint-disable-next-line react-hooks/exhaustive-deps
67
- }, [data.handle, parentConnection, storefrontSettingsSchema]);
167
+ }, [data.handle, sendToParent, storefrontSettingsSchema]);
168
+ // Update the ref whenever pathname changes
68
169
  useEffect(() => {
69
- if (!isPreview ||
70
- !windowLocationRef.current?.pathname ||
71
- !shouldConnectToParent)
170
+ currentPathRef.current = pathname;
171
+ }, [pathname]);
172
+ // Tell the parent window about the current route
173
+ useEffect(() => {
174
+ if (!hasInit || !currentPathRef.current)
72
175
  return;
73
- const connection = connectToParent({
74
- methods: {
75
- routeToPage(path) {
76
- navigate(path);
77
- },
78
- scrollToSection(sectionId) {
79
- const sectionEl = document.querySelector(`[data-comp-id="${sectionId}"]`);
80
- if (sectionEl)
81
- sectionEl.scrollIntoView({ behavior: "smooth" });
82
- },
83
- setPageData(content) {
84
- setContent(content);
85
- },
86
- setStorefrontSettings(settings) {
87
- setSiteSettings({ data: { siteSettings: { settings } } });
88
- },
89
- },
90
- });
91
- connection.promise.then((parent) => {
92
- const { template, templateType, handle, title, description } = data;
93
- parent.sendStorefrontSettings();
94
- parent.setCurrentRoute({
95
- environment,
96
- currentPath: windowLocationRef.current?.pathname,
97
- template,
98
- templateType,
99
- handle,
100
- title,
101
- description,
102
- });
103
- setParentConnection(parent);
176
+ const { template, templateType, handle, title, description } = data;
177
+ sendToParent("SET_CURRENT_ROUTE", {
178
+ environment,
179
+ currentPath: currentPathRef.current,
180
+ template,
181
+ templateType,
182
+ handle,
183
+ title,
184
+ description,
104
185
  });
105
- return () => connection.destroy();
106
- }, [
107
- shouldConnectToParent,
108
- data.handle,
109
- data.title,
110
- data.description,
111
- data.template,
112
- data.templateType,
113
- ]);
186
+ }, [hasInit, environment, data.handle, sendToParent]);
187
+ // Refresh sections and storefront settings schema
114
188
  useEffect(() => {
115
- if (!isPreview)
189
+ if (!isPreview || !hasInit)
116
190
  return;
117
191
  refreshSections();
118
192
  refreshStorefrontSettingsSchema();
@@ -122,6 +196,7 @@ export const useCustomizerShell = ({ environment, sectionComponents, storefrontS
122
196
  refreshSections,
123
197
  refreshStorefrontSettingsSchema,
124
198
  sectionComponents,
199
+ hasInit,
125
200
  ]);
126
201
  if (!isPreview) {
127
202
  return {
@@ -132,3 +207,13 @@ export const useCustomizerShell = ({ environment, sectionComponents, storefrontS
132
207
  return { content };
133
208
  }, [content]);
134
209
  };
210
+ const useCanConnectCheck = ({ isPreview }) => {
211
+ const [canConnect, setCanConnect] = useState(false);
212
+ useEffect(() => {
213
+ const isIframe = window.self !== window.top;
214
+ if (!isIframe || !isPreview)
215
+ return;
216
+ setCanConnect(isIframe);
217
+ }, [isPreview]);
218
+ return canConnect;
219
+ };
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.1.4";
1
+ export const VERSION = "2.0.0";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pack/react",
3
3
  "description": "React",
4
- "version": "1.0.0",
4
+ "version": "2.0.0",
5
5
  "exports": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "engines": {
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "scripts": {
14
14
  "clean": "npx rimraf dist",
15
- "build": "npx tsc",
15
+ "build": "node scripts/build.js",
16
16
  "test": "npx vitest run",
17
17
  "test:watch": "npx vitest"
18
18
  },
@@ -30,7 +30,5 @@
30
30
  "react": "^18.0.0",
31
31
  "react-dom": "^18.0.0"
32
32
  },
33
- "dependencies": {
34
- "penpal": "^6.2.2"
35
- }
33
+ "dependencies": {}
36
34
  }