@shopify/hydrogen 1.3.2 → 1.4.1
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/esnext/components/CartProvider/CartActions.client.d.ts +62 -0
- package/dist/esnext/components/CartProvider/CartActions.client.js +232 -0
- package/dist/esnext/components/CartProvider/CartProviderV2.client.d.ts +49 -0
- package/dist/esnext/components/CartProvider/CartProviderV2.client.js +370 -0
- package/dist/esnext/components/CartProvider/hooks.client.js +1 -1
- package/dist/esnext/components/CartProvider/types.d.ts +165 -0
- package/dist/esnext/components/CartProvider/useCartAPIStateMachine.client.d.ts +22 -0
- package/dist/esnext/components/CartProvider/useCartAPIStateMachine.client.js +251 -0
- package/dist/esnext/entry-client.js +26 -8
- package/dist/esnext/experimental.d.ts +1 -0
- package/dist/esnext/experimental.js +1 -0
- package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.d.ts +3 -2
- package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.js +1 -0
- package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.client.d.ts +3 -3
- package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.d.ts +1 -0
- package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.js +21 -6
- package/dist/esnext/foundation/ShopifyProvider/types.d.ts +5 -2
- package/dist/esnext/foundation/useShop/use-shop.d.ts +1 -1
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +1 -0
- package/dist/esnext/framework/plugins/vite-plugin-hydrogen-middleware.js +0 -8
- package/dist/esnext/hooks/useDelay/useDelay.d.ts +1 -0
- package/dist/esnext/hooks/useDelay/useDelay.js +17 -0
- package/dist/esnext/hooks/useShopQuery/hooks.js +4 -2
- package/dist/esnext/index.d.ts +1 -0
- package/dist/esnext/index.js +1 -0
- package/dist/esnext/shared-types.d.ts +2 -1
- package/dist/esnext/utilities/apiRoutes.js +4 -2
- package/dist/esnext/utilities/object.d.ts +1 -1
- package/dist/esnext/utilities/storefrontApi.d.ts +4 -2
- package/dist/esnext/utilities/storefrontApi.js +31 -6
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +1 -0
- package/dist/node/framework/plugins/vite-plugin-hydrogen-middleware.js +0 -8
- package/dist/node/shared-types.d.ts +2 -1
- package/package.json +3 -1
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { useMachine } from '@xstate/react/fsm';
|
|
2
|
+
import { createMachine, assign } from '@xstate/fsm';
|
|
3
|
+
import { flattenConnection } from '../../utilities/flattenConnection/index.js';
|
|
4
|
+
import { useCartActions } from './CartActions.client.js';
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
function invokeCart(action, options) {
|
|
7
|
+
return {
|
|
8
|
+
entry: [
|
|
9
|
+
...(options?.entryActions || []),
|
|
10
|
+
'onCartActionEntry',
|
|
11
|
+
'onCartActionOptimisticUI',
|
|
12
|
+
action,
|
|
13
|
+
],
|
|
14
|
+
on: {
|
|
15
|
+
RESOLVE: {
|
|
16
|
+
target: options?.resolveTarget || 'idle',
|
|
17
|
+
actions: [
|
|
18
|
+
assign({
|
|
19
|
+
prevCart: (context) => context?.cart,
|
|
20
|
+
cart: (_, event) => event?.payload?.cart,
|
|
21
|
+
rawCartResult: (_, event) => event?.payload?.rawCartResult,
|
|
22
|
+
errors: (_) => undefined,
|
|
23
|
+
}),
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
ERROR: {
|
|
27
|
+
target: options?.errorTarget || 'error',
|
|
28
|
+
actions: [
|
|
29
|
+
assign({
|
|
30
|
+
prevCart: (context) => context?.cart,
|
|
31
|
+
cart: (context, _) => context?.lastValidCart,
|
|
32
|
+
errors: (_, event) => event?.payload?.errors,
|
|
33
|
+
}),
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
CART_COMPLETED: {
|
|
37
|
+
target: 'cartCompleted',
|
|
38
|
+
actions: assign({
|
|
39
|
+
prevCart: (_) => undefined,
|
|
40
|
+
cart: (_) => undefined,
|
|
41
|
+
lastValidCart: (_) => undefined,
|
|
42
|
+
errors: (_) => undefined,
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
exit: ['onCartActionComplete', ...(options?.exitActions || [])],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const INITIALIZING_CART_EVENTS = {
|
|
50
|
+
CART_FETCH: {
|
|
51
|
+
target: 'cartFetching',
|
|
52
|
+
},
|
|
53
|
+
CART_CREATE: {
|
|
54
|
+
target: 'cartCreating',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
const UPDATING_CART_EVENTS = {
|
|
58
|
+
CARTLINE_ADD: {
|
|
59
|
+
target: 'cartLineAdding',
|
|
60
|
+
},
|
|
61
|
+
CARTLINE_UPDATE: {
|
|
62
|
+
target: 'cartLineUpdating',
|
|
63
|
+
},
|
|
64
|
+
CARTLINE_REMOVE: {
|
|
65
|
+
target: 'cartLineRemoving',
|
|
66
|
+
},
|
|
67
|
+
NOTE_UPDATE: {
|
|
68
|
+
target: 'noteUpdating',
|
|
69
|
+
},
|
|
70
|
+
BUYER_IDENTITY_UPDATE: {
|
|
71
|
+
target: 'buyerIdentityUpdating',
|
|
72
|
+
},
|
|
73
|
+
CART_ATTRIBUTES_UPDATE: {
|
|
74
|
+
target: 'cartAttributesUpdating',
|
|
75
|
+
},
|
|
76
|
+
DISCOUNT_CODES_UPDATE: {
|
|
77
|
+
target: 'discountCodesUpdating',
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
const cartMachine = createMachine({
|
|
81
|
+
id: 'Cart',
|
|
82
|
+
initial: 'uninitialized',
|
|
83
|
+
states: {
|
|
84
|
+
uninitialized: {
|
|
85
|
+
on: INITIALIZING_CART_EVENTS,
|
|
86
|
+
},
|
|
87
|
+
cartCompleted: {
|
|
88
|
+
on: INITIALIZING_CART_EVENTS,
|
|
89
|
+
},
|
|
90
|
+
initializationError: {
|
|
91
|
+
on: INITIALIZING_CART_EVENTS,
|
|
92
|
+
},
|
|
93
|
+
idle: {
|
|
94
|
+
on: UPDATING_CART_EVENTS,
|
|
95
|
+
},
|
|
96
|
+
error: {
|
|
97
|
+
on: UPDATING_CART_EVENTS,
|
|
98
|
+
},
|
|
99
|
+
cartFetching: invokeCart('cartFetchAction', {
|
|
100
|
+
errorTarget: 'initializationError',
|
|
101
|
+
}),
|
|
102
|
+
cartCreating: invokeCart('cartCreateAction', {
|
|
103
|
+
errorTarget: 'initializationError',
|
|
104
|
+
}),
|
|
105
|
+
cartLineRemoving: invokeCart('cartLineRemoveAction'),
|
|
106
|
+
cartLineUpdating: invokeCart('cartLineUpdateAction'),
|
|
107
|
+
cartLineAdding: invokeCart('cartLineAddAction'),
|
|
108
|
+
noteUpdating: invokeCart('noteUpdateAction'),
|
|
109
|
+
buyerIdentityUpdating: invokeCart('buyerIdentityUpdateAction'),
|
|
110
|
+
cartAttributesUpdating: invokeCart('cartAttributesUpdateAction'),
|
|
111
|
+
discountCodesUpdating: invokeCart('discountCodesUpdateAction'),
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
export function useCartAPIStateMachine({ numCartLines, onCartActionEntry, onCartActionOptimisticUI, onCartActionComplete, data: cart, cartFragment, countryCode, }) {
|
|
115
|
+
const { cartFetch, cartCreate, cartLineAdd, cartLineUpdate, cartLineRemove, noteUpdate, buyerIdentityUpdate, cartAttributesUpdate, discountCodesUpdate, } = useCartActions({
|
|
116
|
+
numCartLines,
|
|
117
|
+
cartFragment,
|
|
118
|
+
countryCode,
|
|
119
|
+
});
|
|
120
|
+
const [state, send, service] = useMachine(cartMachine, {
|
|
121
|
+
actions: {
|
|
122
|
+
cartFetchAction: async (_, event) => {
|
|
123
|
+
if (event.type !== 'CART_FETCH')
|
|
124
|
+
return;
|
|
125
|
+
const { data, errors } = await cartFetch(event?.payload?.cartId);
|
|
126
|
+
const resultEvent = eventFromFetchResult(event, data?.cart, errors);
|
|
127
|
+
send(resultEvent);
|
|
128
|
+
},
|
|
129
|
+
cartCreateAction: async (_, event) => {
|
|
130
|
+
if (event.type !== 'CART_CREATE')
|
|
131
|
+
return;
|
|
132
|
+
const { data, errors } = await cartCreate(event?.payload);
|
|
133
|
+
const resultEvent = eventFromFetchResult(event, data?.cartCreate?.cart, errors);
|
|
134
|
+
send(resultEvent);
|
|
135
|
+
},
|
|
136
|
+
cartLineAddAction: async (context, event) => {
|
|
137
|
+
if (event.type !== 'CARTLINE_ADD' || !context?.cart?.id)
|
|
138
|
+
return;
|
|
139
|
+
const { data, errors } = await cartLineAdd(context.cart.id, event.payload.lines);
|
|
140
|
+
const resultEvent = eventFromFetchResult(event, data?.cartLinesAdd?.cart, errors);
|
|
141
|
+
send(resultEvent);
|
|
142
|
+
},
|
|
143
|
+
cartLineUpdateAction: async (context, event) => {
|
|
144
|
+
if (event.type !== 'CARTLINE_UPDATE' || !context?.cart?.id)
|
|
145
|
+
return;
|
|
146
|
+
const { data, errors } = await cartLineUpdate(context.cart.id, event.payload.lines);
|
|
147
|
+
const resultEvent = eventFromFetchResult(event, data?.cartLinesUpdate?.cart, errors);
|
|
148
|
+
send(resultEvent);
|
|
149
|
+
},
|
|
150
|
+
cartLineRemoveAction: async (context, event) => {
|
|
151
|
+
if (event.type !== 'CARTLINE_REMOVE' || !context?.cart?.id)
|
|
152
|
+
return;
|
|
153
|
+
const { data, errors } = await cartLineRemove(context.cart.id, event.payload.lines);
|
|
154
|
+
const resultEvent = eventFromFetchResult(event, data?.cartLinesRemove?.cart, errors);
|
|
155
|
+
send(resultEvent);
|
|
156
|
+
},
|
|
157
|
+
noteUpdateAction: async (context, event) => {
|
|
158
|
+
if (event.type !== 'NOTE_UPDATE' || !context?.cart?.id)
|
|
159
|
+
return;
|
|
160
|
+
const { data, errors } = await noteUpdate(context.cart.id, event.payload.note);
|
|
161
|
+
const resultEvent = eventFromFetchResult(event, data?.cartNoteUpdate?.cart, errors);
|
|
162
|
+
send(resultEvent);
|
|
163
|
+
},
|
|
164
|
+
buyerIdentityUpdateAction: async (context, event) => {
|
|
165
|
+
if (event.type !== 'BUYER_IDENTITY_UPDATE' || !context?.cart?.id)
|
|
166
|
+
return;
|
|
167
|
+
const { data, errors } = await buyerIdentityUpdate(context.cart.id, event.payload.buyerIdentity);
|
|
168
|
+
const resultEvent = eventFromFetchResult(event, data?.cartBuyerIdentityUpdate?.cart, errors);
|
|
169
|
+
send(resultEvent);
|
|
170
|
+
},
|
|
171
|
+
cartAttributesUpdateAction: async (context, event) => {
|
|
172
|
+
if (event.type !== 'CART_ATTRIBUTES_UPDATE' || !context?.cart?.id)
|
|
173
|
+
return;
|
|
174
|
+
const { data, errors } = await cartAttributesUpdate(context.cart.id, event.payload.attributes);
|
|
175
|
+
const resultEvent = eventFromFetchResult(event, data?.cartAttributesUpdate?.cart, errors);
|
|
176
|
+
send(resultEvent);
|
|
177
|
+
},
|
|
178
|
+
discountCodesUpdateAction: async (context, event) => {
|
|
179
|
+
if (event.type !== 'DISCOUNT_CODES_UPDATE' || !context?.cart?.id)
|
|
180
|
+
return;
|
|
181
|
+
const { data, errors } = await discountCodesUpdate(context.cart.id, event.payload.discountCodes);
|
|
182
|
+
const resultEvent = eventFromFetchResult(event, data?.cartDiscountCodesUpdate?.cart, errors);
|
|
183
|
+
send(resultEvent);
|
|
184
|
+
},
|
|
185
|
+
...(onCartActionEntry && {
|
|
186
|
+
onCartActionEntry: (context, event) => {
|
|
187
|
+
if (isCartActionEvent(event)) {
|
|
188
|
+
onCartActionEntry(context, event);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
}),
|
|
192
|
+
...(onCartActionOptimisticUI && {
|
|
193
|
+
onCartActionOptimisticUI: assign((context, event) => {
|
|
194
|
+
return onCartActionOptimisticUI(context, event);
|
|
195
|
+
}),
|
|
196
|
+
}),
|
|
197
|
+
...(onCartActionComplete && {
|
|
198
|
+
onCartActionComplete: (context, event) => {
|
|
199
|
+
if (isCartFetchResultEvent(event)) {
|
|
200
|
+
onCartActionComplete(context, event);
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
}),
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
return useMemo(() => [state, send, service], [state, send, service]);
|
|
207
|
+
}
|
|
208
|
+
export function cartFromGraphQL(cart) {
|
|
209
|
+
return {
|
|
210
|
+
...cart,
|
|
211
|
+
// @ts-expect-error While the cart still uses fragments, there will be a TS error here until we remove those fragments and get the type in-line
|
|
212
|
+
lines: flattenConnection(cart.lines),
|
|
213
|
+
note: cart.note ?? undefined,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function eventFromFetchResult(cartActionEvent, cart, errors) {
|
|
217
|
+
if (errors) {
|
|
218
|
+
return { type: 'ERROR', payload: { errors, cartActionEvent } };
|
|
219
|
+
}
|
|
220
|
+
if (!cart) {
|
|
221
|
+
return {
|
|
222
|
+
type: 'CART_COMPLETED',
|
|
223
|
+
payload: {
|
|
224
|
+
cartActionEvent,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
type: 'RESOLVE',
|
|
230
|
+
payload: {
|
|
231
|
+
cart: cartFromGraphQL(cart),
|
|
232
|
+
rawCartResult: cart,
|
|
233
|
+
cartActionEvent,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function isCartActionEvent(event) {
|
|
238
|
+
return (event.type === 'CART_CREATE' ||
|
|
239
|
+
event.type === 'CARTLINE_ADD' ||
|
|
240
|
+
event.type === 'CARTLINE_UPDATE' ||
|
|
241
|
+
event.type === 'CARTLINE_REMOVE' ||
|
|
242
|
+
event.type === 'NOTE_UPDATE' ||
|
|
243
|
+
event.type === 'BUYER_IDENTITY_UPDATE' ||
|
|
244
|
+
event.type === 'CART_ATTRIBUTES_UPDATE' ||
|
|
245
|
+
event.type === 'DISCOUNT_CODES_UPDATE');
|
|
246
|
+
}
|
|
247
|
+
function isCartFetchResultEvent(event) {
|
|
248
|
+
return (event.type === 'RESOLVE' ||
|
|
249
|
+
event.type === 'ERROR' ||
|
|
250
|
+
event.type === 'CART_COMPLETED');
|
|
251
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { Suspense, useState, StrictMode, Fragment, useEffect, } from 'react';
|
|
1
|
+
import React, { Suspense, useState, StrictMode, Fragment, startTransition, useEffect, } from 'react';
|
|
2
2
|
import { hydrateRoot } from 'react-dom/client';
|
|
3
3
|
import { ErrorBoundary } from 'react-error-boundary/dist/react-error-boundary.esm';
|
|
4
4
|
import { createFromFetch, createFromReadableStream,
|
|
@@ -14,6 +14,20 @@ const cache = new Map();
|
|
|
14
14
|
// Hydrate an SSR response from <meta> tags placed in the DOM.
|
|
15
15
|
const flightChunks = [];
|
|
16
16
|
const FLIGHT_ATTRIBUTE = 'data-flight';
|
|
17
|
+
const requestIdleCallbackHydrogen = (typeof self !== 'undefined' &&
|
|
18
|
+
self.requestIdleCallback &&
|
|
19
|
+
self.requestIdleCallback.bind(window)) ||
|
|
20
|
+
function (cb) {
|
|
21
|
+
const start = Date.now();
|
|
22
|
+
return setTimeout(function () {
|
|
23
|
+
cb({
|
|
24
|
+
didTimeout: false,
|
|
25
|
+
timeRemaining() {
|
|
26
|
+
return Math.max(0, 50 - (Date.now() - start));
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}, 1);
|
|
30
|
+
};
|
|
17
31
|
function addElementToFlightChunks(el) {
|
|
18
32
|
// We don't need to decode, because `.getAttribute` already decodes
|
|
19
33
|
const chunk = el.getAttribute(FLIGHT_ATTRIBUTE);
|
|
@@ -93,13 +107,17 @@ const renderHydrogen = async (ClientWrapper) => {
|
|
|
93
107
|
config.strictMode !== false ? StrictMode : Fragment;
|
|
94
108
|
// Fixes hydration in `useId`: https://github.com/Shopify/hydrogen/issues/1589
|
|
95
109
|
const ServerRequestProviderMock = () => null;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
:
|
|
101
|
-
|
|
102
|
-
|
|
110
|
+
requestIdleCallbackHydrogen(() => {
|
|
111
|
+
startTransition(() => {
|
|
112
|
+
hydrateRoot(root, React.createElement(RootComponent, null,
|
|
113
|
+
React.createElement(ServerRequestProviderMock, null),
|
|
114
|
+
React.createElement(ErrorBoundary, { FallbackComponent: CustomErrorPage
|
|
115
|
+
? ({ error }) => (React.createElement(CustomErrorWrapper, { error: error, errorPage: CustomErrorPage }))
|
|
116
|
+
: DefaultError },
|
|
117
|
+
React.createElement(Suspense, { fallback: null },
|
|
118
|
+
React.createElement(Content, { clientWrapper: ClientWrapper })))));
|
|
119
|
+
});
|
|
120
|
+
});
|
|
103
121
|
};
|
|
104
122
|
export default renderHydrogen;
|
|
105
123
|
function Content({ clientWrapper: ClientWrapper = ({ children }) => children, }) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ShopifyContextServerValue, LocalizationContextValue } from '../ShopifyProvider/types.js';
|
|
2
2
|
import type { QueryCacheControlHeaders } from '../../utilities/log/log-cache-header.js';
|
|
3
3
|
import type { QueryTiming } from '../../utilities/log/log-query-timeline.js';
|
|
4
4
|
import type { ResolvedHydrogenConfig, PreloadOptions, QueryKey, RuntimeContext } from '../../types.js';
|
|
@@ -38,7 +38,7 @@ export declare class HydrogenRequest extends Request {
|
|
|
38
38
|
cache: Map<string, any>;
|
|
39
39
|
head: HeadData;
|
|
40
40
|
hydrogenConfig?: ResolvedHydrogenConfig;
|
|
41
|
-
shopifyConfig?:
|
|
41
|
+
shopifyConfig?: ShopifyContextServerValue;
|
|
42
42
|
queryCacheControl: Array<QueryCacheControlHeaders>;
|
|
43
43
|
queryTimings: Array<QueryTiming>;
|
|
44
44
|
preloadQueries: PreloadQueriesByURL;
|
|
@@ -51,6 +51,7 @@ export declare class HydrogenRequest extends Request {
|
|
|
51
51
|
scopes: Map<string, Record<string, any>>;
|
|
52
52
|
localization?: LocalizationContextValue;
|
|
53
53
|
[key: string]: any;
|
|
54
|
+
throttledRequests: Record<string, any>;
|
|
54
55
|
};
|
|
55
56
|
constructor(input: any);
|
|
56
57
|
constructor(input: RequestInfo, init?: RequestInit);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ShopifyContextClientValue, LocalizationContextValue } from './types.js';
|
|
2
2
|
import React, { ReactNode } from 'react';
|
|
3
|
-
export declare const ShopifyContext: React.Context<
|
|
3
|
+
export declare const ShopifyContext: React.Context<ShopifyContextClientValue | null>;
|
|
4
4
|
export declare const LocalizationContext: React.Context<LocalizationContextValue | null>;
|
|
5
5
|
export declare function ShopifyProviderClient({ children, shopifyConfig, localization, }: {
|
|
6
6
|
children: ReactNode;
|
|
7
|
-
shopifyConfig:
|
|
7
|
+
shopifyConfig: ShopifyContextClientValue;
|
|
8
8
|
localization: LocalizationContextValue;
|
|
9
9
|
}): JSX.Element;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ShopifyProviderProps, LocalizationContextValue } from './types.js';
|
|
2
2
|
import type { CountryCode, LanguageCode } from '../../storefront-api-types.js';
|
|
3
|
+
export declare const CLIENT_CONTEXT_ALLOW_LIST: readonly ["defaultCountryCode", "defaultLanguageCode", "storeDomain", "storefrontToken", "storefrontApiVersion", "storefrontId"];
|
|
3
4
|
export declare const SHOPIFY_PROVIDER_CONTEXT_KEY: unique symbol;
|
|
4
5
|
/**
|
|
5
6
|
* The `ShopifyProvider` component wraps your entire app and provides support for hooks.
|
|
@@ -5,18 +5,33 @@ import { useRequestCacheData, useServerRequest, } from '../ServerRequestProvider
|
|
|
5
5
|
import { getOxygenVariable } from '../../utilities/storefrontApi.js';
|
|
6
6
|
import { SHOPIFY_STOREFRONT_ID_VARIABLE } from '../../constants.js';
|
|
7
7
|
import { getLocale } from '../../utilities/locale/index.js';
|
|
8
|
+
export const CLIENT_CONTEXT_ALLOW_LIST = [
|
|
9
|
+
'defaultCountryCode',
|
|
10
|
+
'defaultLanguageCode',
|
|
11
|
+
'storeDomain',
|
|
12
|
+
'storefrontToken',
|
|
13
|
+
'storefrontApiVersion',
|
|
14
|
+
'storefrontId',
|
|
15
|
+
];
|
|
8
16
|
function makeShopifyContext(shopifyConfig) {
|
|
9
17
|
const countryCode = shopifyConfig.defaultCountryCode ?? DEFAULT_COUNTRY;
|
|
10
18
|
const languageCode = shopifyConfig.defaultLanguageCode ?? DEFAULT_LANGUAGE;
|
|
11
19
|
const storefrontId = getOxygenVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);
|
|
12
|
-
|
|
20
|
+
const shopifyProviderServerValue = {
|
|
13
21
|
defaultCountryCode: countryCode.toUpperCase(),
|
|
14
22
|
defaultLanguageCode: languageCode.toUpperCase(),
|
|
15
23
|
storeDomain: shopifyConfig?.storeDomain?.replace(/^https?:\/\//, ''),
|
|
16
24
|
storefrontToken: shopifyConfig.storefrontToken,
|
|
17
25
|
storefrontApiVersion: shopifyConfig.storefrontApiVersion,
|
|
18
|
-
multipassSecret: shopifyConfig.multipassSecret,
|
|
19
26
|
storefrontId,
|
|
27
|
+
privateStorefrontToken: shopifyConfig.privateStorefrontToken,
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
shopifyProviderServerValue,
|
|
31
|
+
shopifyProviderClientValue: CLIENT_CONTEXT_ALLOW_LIST.reduce((clientConfigValue, key) => {
|
|
32
|
+
clientConfigValue[key] = shopifyProviderServerValue[key];
|
|
33
|
+
return clientConfigValue;
|
|
34
|
+
}, {}),
|
|
20
35
|
};
|
|
21
36
|
}
|
|
22
37
|
export const SHOPIFY_PROVIDER_CONTEXT_KEY = Symbol.for('SHOPIFY_PROVIDER_RSC');
|
|
@@ -55,11 +70,11 @@ children, }) {
|
|
|
55
70
|
else {
|
|
56
71
|
actualShopifyConfig = shopifyConfig;
|
|
57
72
|
}
|
|
58
|
-
const
|
|
59
|
-
const localization = getLocalizationContextValue(
|
|
73
|
+
const { shopifyProviderServerValue, shopifyProviderClientValue } = useMemo(() => makeShopifyContext(actualShopifyConfig), [actualShopifyConfig]);
|
|
74
|
+
const localization = getLocalizationContextValue(shopifyProviderServerValue.defaultLanguageCode, shopifyProviderServerValue.defaultCountryCode, languageCode, countryCode);
|
|
60
75
|
request.ctx.localization = localization;
|
|
61
|
-
request.ctx.shopifyConfig =
|
|
62
|
-
return (React.createElement(ShopifyProviderClient, { shopifyConfig:
|
|
76
|
+
request.ctx.shopifyConfig = shopifyProviderServerValue;
|
|
77
|
+
return (React.createElement(ShopifyProviderClient, { shopifyConfig: shopifyProviderClientValue, localization: localization }, children));
|
|
63
78
|
}
|
|
64
79
|
export function getLocalizationContextValue(defaultLanguageCode, defaultCountryCode, languageCode, countryCode) {
|
|
65
80
|
return useMemo(() => {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { CountryCode, LanguageCode } from '../../storefront-api-types.js';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import type { ShopifyConfigFetcher, ShopifyConfig } from '../../types.js';
|
|
4
|
-
|
|
4
|
+
import type { CLIENT_CONTEXT_ALLOW_LIST } from './ShopifyProvider.server.js';
|
|
5
|
+
export interface ShopifyContextServerValue extends Omit<ShopifyConfig, 'defaultLanguageCode' | 'defaultCountryCode'> {
|
|
5
6
|
defaultLanguageCode: `${LanguageCode}`;
|
|
6
7
|
defaultCountryCode: `${CountryCode}`;
|
|
7
|
-
storefrontId: string | null;
|
|
8
8
|
}
|
|
9
|
+
declare type CLIENT_KEYS = typeof CLIENT_CONTEXT_ALLOW_LIST[number];
|
|
10
|
+
export declare type ShopifyContextClientValue = Pick<ShopifyContextServerValue, CLIENT_KEYS>;
|
|
9
11
|
export declare type Locale = string;
|
|
10
12
|
export interface LocalizationContextValue {
|
|
11
13
|
country: {
|
|
@@ -30,3 +32,4 @@ export declare type ShopifyProviderProps = {
|
|
|
30
32
|
*/
|
|
31
33
|
languageCode?: `${LanguageCode}`;
|
|
32
34
|
};
|
|
35
|
+
export {};
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
|
|
4
4
|
* The `useShop` hook must be a descendent of a `ShopifyProvider` component.
|
|
5
5
|
*/
|
|
6
|
-
export declare function useShop(): import("../ShopifyProvider/types.js").
|
|
6
|
+
export declare function useShop(): import("../ShopifyProvider/types.js").ShopifyContextServerValue;
|
|
@@ -56,13 +56,5 @@ export default (pluginOptions) => {
|
|
|
56
56
|
};
|
|
57
57
|
async function polyfillOxygenEnv(config) {
|
|
58
58
|
const env = await loadEnv(config.mode, config.root, '');
|
|
59
|
-
const publicPrefixes = Array.isArray(config.envPrefix)
|
|
60
|
-
? config.envPrefix
|
|
61
|
-
: [config.envPrefix || ''];
|
|
62
|
-
for (const key of Object.keys(env)) {
|
|
63
|
-
if (publicPrefixes.some((prefix) => key.startsWith(prefix))) {
|
|
64
|
-
delete env[key];
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
59
|
globalThis.Oxygen = { env };
|
|
68
60
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useDelay: (data: unknown, time: number) => unknown;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useServerRequest } from '../../foundation/ServerRequestProvider/index.js';
|
|
2
|
+
import { wrapPromise } from '../../utilities/index.js';
|
|
3
|
+
import { log } from '../../utilities/log/log.js';
|
|
4
|
+
export const useDelay = function (data, time) {
|
|
5
|
+
if (!__HYDROGEN_DEV__) {
|
|
6
|
+
log.warn(new Error('The `useDelay` hook introduces an artificial delay that should not be used in production!').stack);
|
|
7
|
+
return data;
|
|
8
|
+
}
|
|
9
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
10
|
+
const serverRequest = useServerRequest();
|
|
11
|
+
const key = JSON.stringify(data);
|
|
12
|
+
if (!serverRequest.ctx.throttledRequests[key]) {
|
|
13
|
+
serverRequest.ctx.throttledRequests[key] = wrapPromise(new Promise((resolve) => setTimeout(resolve, time)));
|
|
14
|
+
}
|
|
15
|
+
serverRequest.ctx.throttledRequests[key].read();
|
|
16
|
+
return data;
|
|
17
|
+
};
|
|
@@ -145,12 +145,14 @@ export function useShopQuery({ query, variables = {}, cache, preload = false, })
|
|
|
145
145
|
return data;
|
|
146
146
|
}
|
|
147
147
|
function useCreateShopRequest(body) {
|
|
148
|
-
const { storeDomain, storefrontToken, storefrontApiVersion } = useShop();
|
|
148
|
+
const { storeDomain, storefrontToken, storefrontApiVersion, storefrontId, privateStorefrontToken, } = useShop();
|
|
149
149
|
const request = useServerRequest();
|
|
150
150
|
const buyerIp = request.getBuyerIp();
|
|
151
151
|
const extraHeaders = getStorefrontApiRequestHeaders({
|
|
152
152
|
buyerIp,
|
|
153
|
-
storefrontToken,
|
|
153
|
+
publicStorefrontToken: storefrontToken,
|
|
154
|
+
privateStorefrontToken,
|
|
155
|
+
storefrontId,
|
|
154
156
|
});
|
|
155
157
|
return {
|
|
156
158
|
key: [storeDomain, storefrontApiVersion, body],
|
package/dist/esnext/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export { ShopifyAnalytics } from './foundation/Analytics/connectors/Shopify/Shop
|
|
|
28
28
|
export { ShopifyAnalyticsConstants } from './foundation/Analytics/connectors/Shopify/const.js';
|
|
29
29
|
export { useSession } from './foundation/useSession/useSession.js';
|
|
30
30
|
export { Cookie } from './foundation/Cookie/Cookie.js';
|
|
31
|
+
export { useDelay } from './hooks/useDelay/useDelay.js';
|
|
31
32
|
/**
|
|
32
33
|
* Export server-only CartQuery here instead of `CartProvider.client` to prevent
|
|
33
34
|
* it from being bundled with other client components
|
package/dist/esnext/index.js
CHANGED
|
@@ -28,6 +28,7 @@ export { ShopifyAnalytics } from './foundation/Analytics/connectors/Shopify/Shop
|
|
|
28
28
|
export { ShopifyAnalyticsConstants } from './foundation/Analytics/connectors/Shopify/const.js';
|
|
29
29
|
export { useSession } from './foundation/useSession/useSession.js';
|
|
30
30
|
export { Cookie } from './foundation/Cookie/Cookie.js';
|
|
31
|
+
export { useDelay } from './hooks/useDelay/useDelay.js';
|
|
31
32
|
/**
|
|
32
33
|
* Export server-only CartQuery here instead of `CartProvider.client` to prevent
|
|
33
34
|
* it from being bundled with other client components
|
|
@@ -81,11 +81,13 @@ function queryShopBuilder(shopifyConfigGetter, request) {
|
|
|
81
81
|
if (!shopifyConfig) {
|
|
82
82
|
throw new Error('Shopify connection info was not found in Hydrogen config');
|
|
83
83
|
}
|
|
84
|
-
const { storeDomain, storefrontApiVersion, storefrontToken } = shopifyConfig;
|
|
84
|
+
const { storeDomain, storefrontApiVersion, storefrontToken, privateStorefrontToken, storefrontId, } = shopifyConfig;
|
|
85
85
|
const buyerIp = request.getBuyerIp();
|
|
86
86
|
const extraHeaders = getStorefrontApiRequestHeaders({
|
|
87
87
|
buyerIp,
|
|
88
|
-
storefrontToken,
|
|
88
|
+
publicStorefrontToken: storefrontToken,
|
|
89
|
+
privateStorefrontToken,
|
|
90
|
+
storefrontId,
|
|
89
91
|
});
|
|
90
92
|
const fetcher = fetchBuilder(`https://${storeDomain}/api/${storefrontApiVersion}/graphql.json`, {
|
|
91
93
|
method: 'POST',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
declare type Descriptor = Parameters<typeof Object.defineProperty>[2];
|
|
2
|
-
export declare function createObject<T = object>(properties: T, { prototype, ...descriptor }?: {
|
|
2
|
+
export declare function createObject<T extends {} = object>(properties: T, { prototype, ...descriptor }?: {
|
|
3
3
|
prototype?: any;
|
|
4
4
|
} & Exclude<Descriptor, 'value'>): T;
|
|
5
5
|
export {};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export declare function getStorefrontApiRequestHeaders({ buyerIp,
|
|
1
|
+
export declare function getStorefrontApiRequestHeaders({ buyerIp, publicStorefrontToken, privateStorefrontToken, storefrontId, }: {
|
|
2
2
|
buyerIp?: string | null;
|
|
3
|
-
|
|
3
|
+
publicStorefrontToken: string;
|
|
4
|
+
privateStorefrontToken: string | undefined;
|
|
5
|
+
storefrontId: string | undefined;
|
|
4
6
|
}): Record<string, any>;
|
|
5
7
|
export declare function getOxygenVariable(key: string): any;
|
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
/* global Oxygen */
|
|
2
2
|
import { OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE, STOREFRONT_API_SECRET_TOKEN_HEADER, STOREFRONT_API_PUBLIC_TOKEN_HEADER, STOREFRONT_API_BUYER_IP_HEADER, SHOPIFY_STOREFRONT_ID_VARIABLE, SHOPIFY_STOREFRONT_ID_HEADER, } from '../constants.js';
|
|
3
|
-
|
|
3
|
+
import { log } from './log/log.js';
|
|
4
|
+
let secretTokenWarned = false;
|
|
5
|
+
let storefrontIdWarned = false;
|
|
6
|
+
export function getStorefrontApiRequestHeaders({ buyerIp, publicStorefrontToken, privateStorefrontToken, storefrontId, }) {
|
|
4
7
|
const headers = {};
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
if (!privateStorefrontToken) {
|
|
9
|
+
privateStorefrontToken = getOxygenVariable(OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE);
|
|
10
|
+
if (!secretTokenWarned) {
|
|
11
|
+
secretTokenWarned = true;
|
|
12
|
+
if (!privateStorefrontToken && !__HYDROGEN_DEV__) {
|
|
13
|
+
log.error('No secret Shopify storefront API token was defined. This means your app will be rate limited!\nSee how to add the token: ');
|
|
14
|
+
}
|
|
15
|
+
else if (privateStorefrontToken) {
|
|
16
|
+
log.warn('The private shopify storefront API token was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: ');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (!storefrontId) {
|
|
21
|
+
storefrontId = getOxygenVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);
|
|
22
|
+
if (!storefrontIdWarned) {
|
|
23
|
+
storefrontIdWarned = true;
|
|
24
|
+
if (!storefrontId && !__HYDROGEN_DEV__) {
|
|
25
|
+
log.warn('No storefrontId was defined. This means the analytics on your admin dashboard will be broken!\nSee how to fix it: ');
|
|
26
|
+
}
|
|
27
|
+
else if (storefrontId) {
|
|
28
|
+
log.warn('The storefrontId was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: ');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
7
32
|
/**
|
|
8
33
|
* Only pass one type of storefront token at a time.
|
|
9
34
|
*/
|
|
10
|
-
if (
|
|
11
|
-
headers[STOREFRONT_API_SECRET_TOKEN_HEADER] =
|
|
35
|
+
if (privateStorefrontToken) {
|
|
36
|
+
headers[STOREFRONT_API_SECRET_TOKEN_HEADER] = privateStorefrontToken;
|
|
12
37
|
}
|
|
13
38
|
else {
|
|
14
|
-
headers[STOREFRONT_API_PUBLIC_TOKEN_HEADER] =
|
|
39
|
+
headers[STOREFRONT_API_PUBLIC_TOKEN_HEADER] = publicStorefrontToken;
|
|
15
40
|
}
|
|
16
41
|
if (buyerIp) {
|
|
17
42
|
headers[STOREFRONT_API_BUYER_IP_HEADER] = buyerIp;
|
package/dist/esnext/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LIB_VERSION = "1.
|
|
1
|
+
export declare const LIB_VERSION = "1.4.1";
|
package/dist/esnext/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const LIB_VERSION = '1.
|
|
1
|
+
export const LIB_VERSION = '1.4.1';
|