@shopify/remix-oxygen 2.0.11 → 2.1.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.
@@ -87,13 +87,33 @@ var originalErrorToString = Error.prototype.toString;
87
87
  Error.prototype.toString = function() {
88
88
  return this.stack || originalErrorToString.call(this);
89
89
  };
90
+ var HYDROGEN_SFAPI_PROXY_KEY = "_sfapi_proxy";
91
+ var hasWarnedAboutStorefront = false;
92
+ function warnOnce(message) {
93
+ if (!hasWarnedAboutStorefront) {
94
+ hasWarnedAboutStorefront = true;
95
+ console.warn(message);
96
+ }
97
+ }
98
+ function buildServerTimingHeader(values) {
99
+ return Object.entries(values).map(([key, value]) => value ? `${key};desc=${value}` : void 0).filter(Boolean).join(", ");
100
+ }
101
+ function appendServerTimingHeader(response, values) {
102
+ const header = typeof values === "string" ? values : buildServerTimingHeader(values);
103
+ if (header) {
104
+ response.headers.append("Server-Timing", header);
105
+ }
106
+ }
90
107
  function createRequestHandler({
91
108
  build,
92
109
  mode,
93
110
  poweredByHeader = true,
94
- getLoadContext
111
+ getLoadContext,
112
+ collectTrackingInformation = true,
113
+ proxyStandardRoutes = true
95
114
  }) {
96
115
  const handleRequest = serverRuntime.createRequestHandler(build, mode);
116
+ const appendPoweredByHeader = poweredByHeader ? (response) => response.headers.append("powered-by", "Shopify, Hydrogen") : void 0;
97
117
  return async (request) => {
98
118
  const method = request.method;
99
119
  if ((method === "GET" || method === "HEAD") && request.body) {
@@ -111,14 +131,34 @@ function createRequestHandler({
111
131
  });
112
132
  }
113
133
  const context = getLoadContext ? await getLoadContext(request) : void 0;
134
+ const storefront = context?.storefront;
135
+ if (proxyStandardRoutes) {
136
+ if (!storefront) {
137
+ warnOnce(
138
+ "[h2:createRequestHandler] Storefront instance is required to proxy standard routes."
139
+ );
140
+ }
141
+ if (storefront?.isStorefrontApiUrl(request)) {
142
+ const response2 = await storefront.forward(request);
143
+ appendPoweredByHeader?.(response2);
144
+ return response2;
145
+ }
146
+ }
114
147
  if (context) {
115
148
  globalThis.__H2O_LOG_EVENT ??= createEventLogger(context);
116
149
  }
117
150
  const startTime = Date.now();
118
151
  const response = await handleRequest(request, context);
119
- if (poweredByHeader) {
120
- response.headers.append("powered-by", "Shopify, Hydrogen");
152
+ if (storefront && proxyStandardRoutes) {
153
+ if (collectTrackingInformation) {
154
+ storefront.setCollectedSubrequestHeaders(response);
155
+ }
156
+ const fetchDest = request.headers.get("sec-fetch-dest");
157
+ if (fetchDest && fetchDest === "document" || request.headers.get("accept")?.includes("text/html")) {
158
+ appendServerTimingHeader(response, { [HYDROGEN_SFAPI_PROXY_KEY]: "1" });
159
+ }
121
160
  }
161
+ appendPoweredByHeader?.(response);
122
162
  {
123
163
  globalThis.__H2O_LOG_EVENT?.({
124
164
  eventType: "request",
@@ -141,8 +181,10 @@ function getStorefrontHeaders(request) {
141
181
  return {
142
182
  requestGroupId: headers.get("request-id"),
143
183
  buyerIp: headers.get("oxygen-buyer-ip"),
184
+ buyerIpSig: headers.get("X-Shopify-Client-IP-Sig"),
144
185
  cookie: headers.get("cookie"),
145
- purpose: headers.get("purpose")
186
+ // sec-purpose is added by browsers automatically when using link/prefetch or Speculation Rules
187
+ purpose: headers.get("sec-purpose") || headers.get("purpose")
146
188
  };
147
189
  }
148
190
 
@@ -188,5 +230,5 @@ exports.createMemorySessionStorage = createMemorySessionStorage;
188
230
  exports.createRequestHandler = createRequestHandler;
189
231
  exports.createSessionStorage = createSessionStorage;
190
232
  exports.getStorefrontHeaders = getStorefrontHeaders;
191
- //# sourceMappingURL=out.js.map
233
+ //# sourceMappingURL=index.cjs.map
192
234
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/implementations.ts","../../src/crypto.ts","../../src/server.ts","../../src/event-logger.ts","../../src/index.ts"],"names":["data"],"mappings":"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","sourcesContent":["import {\n createCookieFactory,\n createCookieSessionStorageFactory,\n createMemorySessionStorageFactory,\n createSessionStorageFactory,\n} from '@remix-run/server-runtime';\n\nimport {sign, unsign} from './crypto';\n\nexport const createCookie = createCookieFactory({sign, unsign});\nexport const createCookieSessionStorage =\n createCookieSessionStorageFactory(createCookie);\nexport const createSessionStorage = createSessionStorageFactory(createCookie);\nexport const createMemorySessionStorage =\n createMemorySessionStorageFactory(createSessionStorage);\n","import type {SignFunction, UnsignFunction} from '@remix-run/server-runtime';\n\nconst encoder = new TextEncoder();\n\nexport const sign: SignFunction = async (value, secret) => {\n const key = await createKey(secret, ['sign']);\n const data = encoder.encode(value);\n const signature = await crypto.subtle.sign('HMAC', key, data);\n const hash = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(\n /=+$/,\n '',\n );\n\n return value + '.' + hash;\n};\n\nexport const unsign: UnsignFunction = async (signed, secret) => {\n const index = signed.lastIndexOf('.');\n const value = signed.slice(0, index);\n const hash = signed.slice(index + 1);\n\n const key = await createKey(secret, ['verify']);\n const data = encoder.encode(value);\n const signature = byteStringToUint8Array(atob(hash));\n const valid = await crypto.subtle.verify('HMAC', key, signature, data);\n\n return valid ? value : false;\n};\n\nasync function createKey(\n secret: string,\n usages: CryptoKey['usages'],\n): Promise<CryptoKey> {\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n {name: 'HMAC', hash: 'SHA-256'},\n false,\n usages,\n );\n\n return key;\n}\n\nfunction byteStringToUint8Array(byteString: string): Uint8Array {\n const array = new Uint8Array(byteString.length);\n\n for (let i = 0; i < byteString.length; i++) {\n array[i] = byteString.charCodeAt(i);\n }\n\n return array;\n}\n","/// <reference types=\"@shopify/hydrogen\" />\nimport {\n createRequestHandler as createRemixRequestHandler,\n type AppLoadContext,\n type ServerBuild,\n} from '@remix-run/server-runtime';\nimport {createEventLogger} from './event-logger';\n\nconst originalErrorToString = Error.prototype.toString;\nError.prototype.toString = function () {\n return this.stack || originalErrorToString.call(this);\n};\n\nexport function createRequestHandler<Context = unknown>({\n build,\n mode,\n poweredByHeader = true,\n getLoadContext,\n}: {\n build: ServerBuild;\n mode?: string;\n poweredByHeader?: boolean;\n getLoadContext?: (request: Request) => Promise<Context> | Context;\n}) {\n const handleRequest = createRemixRequestHandler(build, mode);\n\n return async (request: Request) => {\n const method = request.method;\n\n if ((method === 'GET' || method === 'HEAD') && request.body) {\n return new Response(`${method} requests cannot have a body`, {\n status: 400,\n });\n }\n\n const url = new URL(request.url);\n\n if (url.pathname.includes('//')) {\n return new Response(null, {\n status: 301,\n headers: {\n location: url.pathname.replace(/\\/+/g, '/'),\n },\n });\n }\n\n const context = getLoadContext\n ? ((await getLoadContext(request)) as AppLoadContext)\n : undefined;\n\n if (process.env.NODE_ENV === 'development' && context) {\n // Store logger in globalThis so it can be accessed from the worker.\n // The global property must be different from the binding name,\n // otherwise Miniflare throws an error when accessing it.\n globalThis.__H2O_LOG_EVENT ??= createEventLogger(context);\n }\n\n const startTime = Date.now();\n\n const response = await handleRequest(request, context);\n\n if (poweredByHeader) {\n response.headers.append('powered-by', 'Shopify, Hydrogen');\n }\n\n if (process.env.NODE_ENV === 'development') {\n globalThis.__H2O_LOG_EVENT?.({\n eventType: 'request',\n url: request.url,\n requestId: request.headers.get('request-id'),\n purpose: request.headers.get('purpose'),\n startTime,\n responseInit: {\n status: response.status,\n statusText: response.statusText,\n headers: Array.from(response.headers.entries()),\n } satisfies ResponseInit,\n });\n }\n\n return response;\n };\n}\n\ntype StorefrontHeaders = {\n requestGroupId: string | null;\n buyerIp: string | null;\n cookie: string | null;\n purpose: string | null;\n};\n\nexport function getStorefrontHeaders(request: Request): StorefrontHeaders {\n const headers = request.headers;\n return {\n requestGroupId: headers.get('request-id'),\n buyerIp: headers.get('oxygen-buyer-ip'),\n cookie: headers.get('cookie'),\n purpose: headers.get('purpose'),\n };\n}\n","type H2OEvent = Parameters<NonNullable<typeof __H2O_LOG_EVENT>>[0];\n\nlet hasWarned = false;\n\n/**\n * @deprecated Only used with the classic Remix compiler\n */\nexport function createEventLogger(appLoadContext: Record<string, unknown>) {\n const context = (appLoadContext || {}) as {\n env?: Record<string, any>;\n waitUntil?: (promise: Promise<any>) => void;\n };\n\n const eventLoggerService = context?.env?.H2O_LOG_EVENT as\n | undefined\n | {fetch: (req: Request) => Promise<Response>};\n\n if (typeof eventLoggerService?.fetch !== 'function') return;\n\n return ({\n url,\n endTime = Date.now(),\n waitUntil = context?.waitUntil,\n ...rest\n }: H2OEvent) => {\n const promise = Promise.resolve().then(() =>\n eventLoggerService\n .fetch(\n new Request(url, {\n method: 'POST',\n body: JSON.stringify({\n endTime,\n ...rest,\n }),\n }),\n )\n .catch((error: Error) => {\n if (!hasWarned) {\n // This might repeat a lot of times due to\n // the same issue, so we only warn once.\n console.debug('Failed to log H2O event\\n', error.stack);\n hasWarned = true;\n }\n }),\n );\n\n promise && waitUntil?.(promise);\n };\n}\n","export {\n createCookie,\n createCookieSessionStorage,\n createMemorySessionStorage,\n createSessionStorage,\n} from './implementations';\nexport {createRequestHandler, getStorefrontHeaders} from './server';\nexport type {\n ActionFunction,\n ActionFunctionArgs,\n AppLoadContext,\n Cookie,\n CookieOptions,\n CookieParseOptions,\n CookieSerializeOptions,\n CookieSignatureOptions,\n DataFunctionArgs,\n EntryContext,\n ErrorResponse,\n HandleDataRequestFunction,\n HandleDocumentRequestFunction,\n HandleErrorFunction,\n HeadersArgs,\n HeadersFunction,\n HtmlLinkDescriptor,\n JsonFunction,\n LinkDescriptor,\n LinksFunction,\n LoaderFunction,\n LoaderFunctionArgs,\n MemoryUploadHandlerFilterArgs,\n MemoryUploadHandlerOptions,\n ServerRuntimeMetaArgs as MetaArgs,\n ServerRuntimeMetaDescriptor as MetaDescriptor,\n ServerRuntimeMetaFunction as MetaFunction,\n PageLinkDescriptor,\n RequestHandler,\n SerializeFrom,\n ServerBuild,\n ServerEntryModule,\n Session,\n SessionData,\n SessionIdStorageStrategy,\n SessionStorage,\n SignFunction,\n TypedDeferredData,\n TypedResponse,\n UnsignFunction,\n UploadHandler,\n UploadHandlerPart,\n} from '@remix-run/server-runtime';\nexport {\n createSession,\n data,\n defer,\n isCookie,\n isSession,\n json,\n MaxPartSizeExceededError,\n redirect,\n redirectDocument,\n} from '@remix-run/server-runtime';\n"]}
1
+ {"version":3,"sources":["../../src/crypto.ts","../../src/implementations.ts","../../src/event-logger.ts","../../src/server.ts"],"names":["data","createCookieFactory","createCookieSessionStorageFactory","createSessionStorageFactory","createMemorySessionStorageFactory","createRemixRequestHandler","response"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {SignFunction, UnsignFunction} from '@remix-run/server-runtime';\n\nconst encoder = new TextEncoder();\n\nexport const sign: SignFunction = async (value, secret) => {\n const key = await createKey(secret, ['sign']);\n const data = encoder.encode(value);\n const signature = await crypto.subtle.sign('HMAC', key, data);\n const hash = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(\n /=+$/,\n '',\n );\n\n return value + '.' + hash;\n};\n\nexport const unsign: UnsignFunction = async (signed, secret) => {\n const index = signed.lastIndexOf('.');\n const value = signed.slice(0, index);\n const hash = signed.slice(index + 1);\n\n const key = await createKey(secret, ['verify']);\n const data = encoder.encode(value);\n const signature = byteStringToUint8Array(atob(hash));\n const valid = await crypto.subtle.verify('HMAC', key, signature, data);\n\n return valid ? value : false;\n};\n\nasync function createKey(\n secret: string,\n usages: CryptoKey['usages'],\n): Promise<CryptoKey> {\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n {name: 'HMAC', hash: 'SHA-256'},\n false,\n usages,\n );\n\n return key;\n}\n\nfunction byteStringToUint8Array(byteString: string): Uint8Array {\n const array = new Uint8Array(byteString.length);\n\n for (let i = 0; i < byteString.length; i++) {\n array[i] = byteString.charCodeAt(i);\n }\n\n return array;\n}\n","import {\n createCookieFactory,\n createCookieSessionStorageFactory,\n createMemorySessionStorageFactory,\n createSessionStorageFactory,\n} from '@remix-run/server-runtime';\n\nimport {sign, unsign} from './crypto';\n\nexport const createCookie = createCookieFactory({sign, unsign});\nexport const createCookieSessionStorage =\n createCookieSessionStorageFactory(createCookie);\nexport const createSessionStorage = createSessionStorageFactory(createCookie);\nexport const createMemorySessionStorage =\n createMemorySessionStorageFactory(createSessionStorage);\n","type H2OEvent = Parameters<NonNullable<typeof __H2O_LOG_EVENT>>[0];\n\nlet hasWarned = false;\n\n/**\n * @deprecated Only used with the classic Remix compiler\n */\nexport function createEventLogger(appLoadContext: Record<string, unknown>) {\n const context = (appLoadContext || {}) as {\n env?: Record<string, any>;\n waitUntil?: (promise: Promise<any>) => void;\n };\n\n const eventLoggerService = context?.env?.H2O_LOG_EVENT as\n | undefined\n | {fetch: (req: Request) => Promise<Response>};\n\n if (typeof eventLoggerService?.fetch !== 'function') return;\n\n return ({\n url,\n endTime = Date.now(),\n waitUntil = context?.waitUntil,\n ...rest\n }: H2OEvent) => {\n const promise = Promise.resolve().then(() =>\n eventLoggerService\n .fetch(\n new Request(url, {\n method: 'POST',\n body: JSON.stringify({\n endTime,\n ...rest,\n }),\n }),\n )\n .catch((error: Error) => {\n if (!hasWarned) {\n // This might repeat a lot of times due to\n // the same issue, so we only warn once.\n console.debug('Failed to log H2O event\\n', error.stack);\n hasWarned = true;\n }\n }),\n );\n\n promise && waitUntil?.(promise);\n };\n}\n","/// <reference types=\"@shopify/hydrogen\" />\nimport {\n createRequestHandler as createRemixRequestHandler,\n type AppLoadContext,\n type ServerBuild,\n} from '@remix-run/server-runtime';\nimport {createEventLogger} from './event-logger';\n\nconst originalErrorToString = Error.prototype.toString;\nError.prototype.toString = function () {\n return this.stack || originalErrorToString.call(this);\n};\n\n/** Server-Timing header key to signal that the SFAPI proxy is enabled */\nconst HYDROGEN_SFAPI_PROXY_KEY = '_sfapi_proxy';\n\nlet hasWarnedAboutStorefront = false;\n\nfunction warnOnce(message: string) {\n if (!hasWarnedAboutStorefront) {\n hasWarnedAboutStorefront = true;\n console.warn(message);\n }\n}\n\nfunction buildServerTimingHeader(values: Record<string, string | undefined>) {\n return Object.entries(values)\n .map(([key, value]) => (value ? `${key};desc=${value}` : undefined))\n .filter(Boolean)\n .join(', ');\n}\n\nfunction appendServerTimingHeader(\n response: {headers: Headers},\n values: string | Record<string, string | undefined>,\n) {\n const header =\n typeof values === 'string' ? values : buildServerTimingHeader(values);\n\n if (header) {\n response.headers.append('Server-Timing', header);\n }\n}\n\ntype CreateRequestHandlerOptions<Context = unknown> = {\n /** Remix's server build */\n build: ServerBuild;\n /** Remix's mode */\n mode?: string;\n /**\n * Function to provide the load context for each request.\n * It must contain Hydrogen's storefront client instance\n * for other Hydrogen utilities to work properly.\n */\n getLoadContext?: (request: Request) => Promise<Context> | Context;\n /**\n * Whether to include the `powered-by` header in responses\n * @default true\n */\n poweredByHeader?: boolean;\n /**\n * Collect tracking information from subrequests such as cookies\n * and forward them to the browser. Disable this if you are not\n * using Hydrogen's built-in analytics.\n * @default true\n */\n collectTrackingInformation?: boolean;\n /**\n * Whether to proxy standard routes such as `/api/.../graphql.json` (Storefront API).\n * You can disable this if you are handling these routes yourself. Ensure that\n * the proxy works if you rely on Hydrogen's built-in behaviors such as analytics.\n * @default true\n */\n proxyStandardRoutes?: boolean;\n};\n\nexport function createRequestHandler<Context = unknown>({\n build,\n mode,\n poweredByHeader = true,\n getLoadContext,\n collectTrackingInformation = true,\n proxyStandardRoutes = true,\n}: CreateRequestHandlerOptions<Context>) {\n const handleRequest = createRemixRequestHandler(build, mode);\n\n const appendPoweredByHeader = poweredByHeader\n ? (response: Response) =>\n response.headers.append('powered-by', 'Shopify, Hydrogen')\n : undefined;\n\n return async (request: Request) => {\n const method = request.method;\n\n if ((method === 'GET' || method === 'HEAD') && request.body) {\n return new Response(`${method} requests cannot have a body`, {\n status: 400,\n });\n }\n\n const url = new URL(request.url);\n\n if (url.pathname.includes('//')) {\n return new Response(null, {\n status: 301,\n headers: {\n location: url.pathname.replace(/\\/+/g, '/'),\n },\n });\n }\n\n const context = getLoadContext\n ? ((await getLoadContext(request)) as AppLoadContext)\n : undefined;\n\n // Access storefront from context if available\n const storefront = (\n context as {storefront?: StorefrontForProxy} | undefined\n )?.storefront;\n\n if (proxyStandardRoutes) {\n if (!storefront) {\n // TODO: this should throw error in future major version\n warnOnce(\n '[h2:createRequestHandler] Storefront instance is required to proxy standard routes.',\n );\n }\n\n // Proxy Storefront API requests\n if (storefront?.isStorefrontApiUrl(request)) {\n const response = await storefront.forward(request);\n appendPoweredByHeader?.(response);\n return response;\n }\n }\n\n if (process.env.NODE_ENV === 'development' && context) {\n // Store logger in globalThis so it can be accessed from the worker.\n // The global property must be different from the binding name,\n // otherwise Miniflare throws an error when accessing it.\n globalThis.__H2O_LOG_EVENT ??= createEventLogger(context);\n }\n\n const startTime = Date.now();\n\n const response = await handleRequest(request, context);\n\n if (storefront && proxyStandardRoutes) {\n if (collectTrackingInformation) {\n storefront.setCollectedSubrequestHeaders(response);\n }\n\n // TODO: assume SFAPI proxy is available in future major version\n // Signal that SFAPI proxy is enabled for document requests.\n // Note: sec-fetch-dest is automatically added by modern browsers,\n // but we also check the Accept header for other clients.\n const fetchDest = request.headers.get('sec-fetch-dest');\n if (\n (fetchDest && fetchDest === 'document') ||\n request.headers.get('accept')?.includes('text/html')\n ) {\n appendServerTimingHeader(response, {[HYDROGEN_SFAPI_PROXY_KEY]: '1'});\n }\n }\n\n appendPoweredByHeader?.(response);\n\n if (process.env.NODE_ENV === 'development') {\n globalThis.__H2O_LOG_EVENT?.({\n eventType: 'request',\n url: request.url,\n requestId: request.headers.get('request-id'),\n purpose: request.headers.get('purpose'),\n startTime,\n responseInit: {\n status: response.status,\n statusText: response.statusText,\n headers: Array.from(response.headers.entries()),\n } satisfies ResponseInit,\n });\n }\n\n return response;\n };\n}\n\ntype StorefrontHeaders = {\n requestGroupId: string | null;\n buyerIp: string | null;\n buyerIpSig: string | null;\n cookie: string | null;\n purpose: string | null;\n};\n\nexport function getStorefrontHeaders(request: Request): StorefrontHeaders {\n const headers = request.headers;\n return {\n requestGroupId: headers.get('request-id'),\n buyerIp: headers.get('oxygen-buyer-ip'),\n buyerIpSig: headers.get('X-Shopify-Client-IP-Sig'),\n cookie: headers.get('cookie'),\n // sec-purpose is added by browsers automatically when using link/prefetch or Speculation Rules\n purpose: headers.get('sec-purpose') || headers.get('purpose'),\n };\n}\n\n/**\n * Minimal storefront interface needed for proxy functionality.\n * The full Storefront type is defined in @shopify/hydrogen.\n */\ntype StorefrontForProxy = {\n isStorefrontApiUrl: (request: {url?: string}) => boolean;\n forward: (request: Request) => Promise<Response>;\n setCollectedSubrequestHeaders: (response: {headers: Headers}) => void;\n};\n"]}
@@ -86,13 +86,33 @@ var originalErrorToString = Error.prototype.toString;
86
86
  Error.prototype.toString = function() {
87
87
  return this.stack || originalErrorToString.call(this);
88
88
  };
89
+ var HYDROGEN_SFAPI_PROXY_KEY = "_sfapi_proxy";
90
+ var hasWarnedAboutStorefront = false;
91
+ function warnOnce(message) {
92
+ if (!hasWarnedAboutStorefront) {
93
+ hasWarnedAboutStorefront = true;
94
+ console.warn(message);
95
+ }
96
+ }
97
+ function buildServerTimingHeader(values) {
98
+ return Object.entries(values).map(([key, value]) => value ? `${key};desc=${value}` : void 0).filter(Boolean).join(", ");
99
+ }
100
+ function appendServerTimingHeader(response, values) {
101
+ const header = typeof values === "string" ? values : buildServerTimingHeader(values);
102
+ if (header) {
103
+ response.headers.append("Server-Timing", header);
104
+ }
105
+ }
89
106
  function createRequestHandler({
90
107
  build,
91
108
  mode,
92
109
  poweredByHeader = true,
93
- getLoadContext
110
+ getLoadContext,
111
+ collectTrackingInformation = true,
112
+ proxyStandardRoutes = true
94
113
  }) {
95
114
  const handleRequest = createRequestHandler$1(build, mode);
115
+ const appendPoweredByHeader = poweredByHeader ? (response) => response.headers.append("powered-by", "Shopify, Hydrogen") : void 0;
96
116
  return async (request) => {
97
117
  const method = request.method;
98
118
  if ((method === "GET" || method === "HEAD") && request.body) {
@@ -110,14 +130,34 @@ function createRequestHandler({
110
130
  });
111
131
  }
112
132
  const context = getLoadContext ? await getLoadContext(request) : void 0;
133
+ const storefront = context?.storefront;
134
+ if (proxyStandardRoutes) {
135
+ if (!storefront) {
136
+ warnOnce(
137
+ "[h2:createRequestHandler] Storefront instance is required to proxy standard routes."
138
+ );
139
+ }
140
+ if (storefront?.isStorefrontApiUrl(request)) {
141
+ const response2 = await storefront.forward(request);
142
+ appendPoweredByHeader?.(response2);
143
+ return response2;
144
+ }
145
+ }
113
146
  if (context) {
114
147
  globalThis.__H2O_LOG_EVENT ??= createEventLogger(context);
115
148
  }
116
149
  const startTime = Date.now();
117
150
  const response = await handleRequest(request, context);
118
- if (poweredByHeader) {
119
- response.headers.append("powered-by", "Shopify, Hydrogen");
151
+ if (storefront && proxyStandardRoutes) {
152
+ if (collectTrackingInformation) {
153
+ storefront.setCollectedSubrequestHeaders(response);
154
+ }
155
+ const fetchDest = request.headers.get("sec-fetch-dest");
156
+ if (fetchDest && fetchDest === "document" || request.headers.get("accept")?.includes("text/html")) {
157
+ appendServerTimingHeader(response, { [HYDROGEN_SFAPI_PROXY_KEY]: "1" });
158
+ }
120
159
  }
160
+ appendPoweredByHeader?.(response);
121
161
  {
122
162
  globalThis.__H2O_LOG_EVENT?.({
123
163
  eventType: "request",
@@ -140,11 +180,13 @@ function getStorefrontHeaders(request) {
140
180
  return {
141
181
  requestGroupId: headers.get("request-id"),
142
182
  buyerIp: headers.get("oxygen-buyer-ip"),
183
+ buyerIpSig: headers.get("X-Shopify-Client-IP-Sig"),
143
184
  cookie: headers.get("cookie"),
144
- purpose: headers.get("purpose")
185
+ // sec-purpose is added by browsers automatically when using link/prefetch or Speculation Rules
186
+ purpose: headers.get("sec-purpose") || headers.get("purpose")
145
187
  };
146
188
  }
147
189
 
148
190
  export { createCookie, createCookieSessionStorage, createMemorySessionStorage, createRequestHandler, createSessionStorage, getStorefrontHeaders };
149
- //# sourceMappingURL=out.js.map
191
+ //# sourceMappingURL=index.js.map
150
192
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/implementations.ts","../../src/crypto.ts","../../src/server.ts","../../src/event-logger.ts","../../src/index.ts"],"names":["data"],"mappings":"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","sourcesContent":["import {\n createCookieFactory,\n createCookieSessionStorageFactory,\n createMemorySessionStorageFactory,\n createSessionStorageFactory,\n} from '@remix-run/server-runtime';\n\nimport {sign, unsign} from './crypto';\n\nexport const createCookie = createCookieFactory({sign, unsign});\nexport const createCookieSessionStorage =\n createCookieSessionStorageFactory(createCookie);\nexport const createSessionStorage = createSessionStorageFactory(createCookie);\nexport const createMemorySessionStorage =\n createMemorySessionStorageFactory(createSessionStorage);\n","import type {SignFunction, UnsignFunction} from '@remix-run/server-runtime';\n\nconst encoder = new TextEncoder();\n\nexport const sign: SignFunction = async (value, secret) => {\n const key = await createKey(secret, ['sign']);\n const data = encoder.encode(value);\n const signature = await crypto.subtle.sign('HMAC', key, data);\n const hash = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(\n /=+$/,\n '',\n );\n\n return value + '.' + hash;\n};\n\nexport const unsign: UnsignFunction = async (signed, secret) => {\n const index = signed.lastIndexOf('.');\n const value = signed.slice(0, index);\n const hash = signed.slice(index + 1);\n\n const key = await createKey(secret, ['verify']);\n const data = encoder.encode(value);\n const signature = byteStringToUint8Array(atob(hash));\n const valid = await crypto.subtle.verify('HMAC', key, signature, data);\n\n return valid ? value : false;\n};\n\nasync function createKey(\n secret: string,\n usages: CryptoKey['usages'],\n): Promise<CryptoKey> {\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n {name: 'HMAC', hash: 'SHA-256'},\n false,\n usages,\n );\n\n return key;\n}\n\nfunction byteStringToUint8Array(byteString: string): Uint8Array {\n const array = new Uint8Array(byteString.length);\n\n for (let i = 0; i < byteString.length; i++) {\n array[i] = byteString.charCodeAt(i);\n }\n\n return array;\n}\n","/// <reference types=\"@shopify/hydrogen\" />\nimport {\n createRequestHandler as createRemixRequestHandler,\n type AppLoadContext,\n type ServerBuild,\n} from '@remix-run/server-runtime';\nimport {createEventLogger} from './event-logger';\n\nconst originalErrorToString = Error.prototype.toString;\nError.prototype.toString = function () {\n return this.stack || originalErrorToString.call(this);\n};\n\nexport function createRequestHandler<Context = unknown>({\n build,\n mode,\n poweredByHeader = true,\n getLoadContext,\n}: {\n build: ServerBuild;\n mode?: string;\n poweredByHeader?: boolean;\n getLoadContext?: (request: Request) => Promise<Context> | Context;\n}) {\n const handleRequest = createRemixRequestHandler(build, mode);\n\n return async (request: Request) => {\n const method = request.method;\n\n if ((method === 'GET' || method === 'HEAD') && request.body) {\n return new Response(`${method} requests cannot have a body`, {\n status: 400,\n });\n }\n\n const url = new URL(request.url);\n\n if (url.pathname.includes('//')) {\n return new Response(null, {\n status: 301,\n headers: {\n location: url.pathname.replace(/\\/+/g, '/'),\n },\n });\n }\n\n const context = getLoadContext\n ? ((await getLoadContext(request)) as AppLoadContext)\n : undefined;\n\n if (process.env.NODE_ENV === 'development' && context) {\n // Store logger in globalThis so it can be accessed from the worker.\n // The global property must be different from the binding name,\n // otherwise Miniflare throws an error when accessing it.\n globalThis.__H2O_LOG_EVENT ??= createEventLogger(context);\n }\n\n const startTime = Date.now();\n\n const response = await handleRequest(request, context);\n\n if (poweredByHeader) {\n response.headers.append('powered-by', 'Shopify, Hydrogen');\n }\n\n if (process.env.NODE_ENV === 'development') {\n globalThis.__H2O_LOG_EVENT?.({\n eventType: 'request',\n url: request.url,\n requestId: request.headers.get('request-id'),\n purpose: request.headers.get('purpose'),\n startTime,\n responseInit: {\n status: response.status,\n statusText: response.statusText,\n headers: Array.from(response.headers.entries()),\n } satisfies ResponseInit,\n });\n }\n\n return response;\n };\n}\n\ntype StorefrontHeaders = {\n requestGroupId: string | null;\n buyerIp: string | null;\n cookie: string | null;\n purpose: string | null;\n};\n\nexport function getStorefrontHeaders(request: Request): StorefrontHeaders {\n const headers = request.headers;\n return {\n requestGroupId: headers.get('request-id'),\n buyerIp: headers.get('oxygen-buyer-ip'),\n cookie: headers.get('cookie'),\n purpose: headers.get('purpose'),\n };\n}\n","type H2OEvent = Parameters<NonNullable<typeof __H2O_LOG_EVENT>>[0];\n\nlet hasWarned = false;\n\n/**\n * @deprecated Only used with the classic Remix compiler\n */\nexport function createEventLogger(appLoadContext: Record<string, unknown>) {\n const context = (appLoadContext || {}) as {\n env?: Record<string, any>;\n waitUntil?: (promise: Promise<any>) => void;\n };\n\n const eventLoggerService = context?.env?.H2O_LOG_EVENT as\n | undefined\n | {fetch: (req: Request) => Promise<Response>};\n\n if (typeof eventLoggerService?.fetch !== 'function') return;\n\n return ({\n url,\n endTime = Date.now(),\n waitUntil = context?.waitUntil,\n ...rest\n }: H2OEvent) => {\n const promise = Promise.resolve().then(() =>\n eventLoggerService\n .fetch(\n new Request(url, {\n method: 'POST',\n body: JSON.stringify({\n endTime,\n ...rest,\n }),\n }),\n )\n .catch((error: Error) => {\n if (!hasWarned) {\n // This might repeat a lot of times due to\n // the same issue, so we only warn once.\n console.debug('Failed to log H2O event\\n', error.stack);\n hasWarned = true;\n }\n }),\n );\n\n promise && waitUntil?.(promise);\n };\n}\n","export {\n createCookie,\n createCookieSessionStorage,\n createMemorySessionStorage,\n createSessionStorage,\n} from './implementations';\nexport {createRequestHandler, getStorefrontHeaders} from './server';\nexport type {\n ActionFunction,\n ActionFunctionArgs,\n AppLoadContext,\n Cookie,\n CookieOptions,\n CookieParseOptions,\n CookieSerializeOptions,\n CookieSignatureOptions,\n DataFunctionArgs,\n EntryContext,\n ErrorResponse,\n HandleDataRequestFunction,\n HandleDocumentRequestFunction,\n HandleErrorFunction,\n HeadersArgs,\n HeadersFunction,\n HtmlLinkDescriptor,\n JsonFunction,\n LinkDescriptor,\n LinksFunction,\n LoaderFunction,\n LoaderFunctionArgs,\n MemoryUploadHandlerFilterArgs,\n MemoryUploadHandlerOptions,\n ServerRuntimeMetaArgs as MetaArgs,\n ServerRuntimeMetaDescriptor as MetaDescriptor,\n ServerRuntimeMetaFunction as MetaFunction,\n PageLinkDescriptor,\n RequestHandler,\n SerializeFrom,\n ServerBuild,\n ServerEntryModule,\n Session,\n SessionData,\n SessionIdStorageStrategy,\n SessionStorage,\n SignFunction,\n TypedDeferredData,\n TypedResponse,\n UnsignFunction,\n UploadHandler,\n UploadHandlerPart,\n} from '@remix-run/server-runtime';\nexport {\n createSession,\n data,\n defer,\n isCookie,\n isSession,\n json,\n MaxPartSizeExceededError,\n redirect,\n redirectDocument,\n} from '@remix-run/server-runtime';\n"]}
1
+ {"version":3,"sources":["../../src/crypto.ts","../../src/implementations.ts","../../src/event-logger.ts","../../src/server.ts"],"names":["data","createRemixRequestHandler","response"],"mappings":"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","file":"index.js","sourcesContent":["import type {SignFunction, UnsignFunction} from '@remix-run/server-runtime';\n\nconst encoder = new TextEncoder();\n\nexport const sign: SignFunction = async (value, secret) => {\n const key = await createKey(secret, ['sign']);\n const data = encoder.encode(value);\n const signature = await crypto.subtle.sign('HMAC', key, data);\n const hash = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(\n /=+$/,\n '',\n );\n\n return value + '.' + hash;\n};\n\nexport const unsign: UnsignFunction = async (signed, secret) => {\n const index = signed.lastIndexOf('.');\n const value = signed.slice(0, index);\n const hash = signed.slice(index + 1);\n\n const key = await createKey(secret, ['verify']);\n const data = encoder.encode(value);\n const signature = byteStringToUint8Array(atob(hash));\n const valid = await crypto.subtle.verify('HMAC', key, signature, data);\n\n return valid ? value : false;\n};\n\nasync function createKey(\n secret: string,\n usages: CryptoKey['usages'],\n): Promise<CryptoKey> {\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n {name: 'HMAC', hash: 'SHA-256'},\n false,\n usages,\n );\n\n return key;\n}\n\nfunction byteStringToUint8Array(byteString: string): Uint8Array {\n const array = new Uint8Array(byteString.length);\n\n for (let i = 0; i < byteString.length; i++) {\n array[i] = byteString.charCodeAt(i);\n }\n\n return array;\n}\n","import {\n createCookieFactory,\n createCookieSessionStorageFactory,\n createMemorySessionStorageFactory,\n createSessionStorageFactory,\n} from '@remix-run/server-runtime';\n\nimport {sign, unsign} from './crypto';\n\nexport const createCookie = createCookieFactory({sign, unsign});\nexport const createCookieSessionStorage =\n createCookieSessionStorageFactory(createCookie);\nexport const createSessionStorage = createSessionStorageFactory(createCookie);\nexport const createMemorySessionStorage =\n createMemorySessionStorageFactory(createSessionStorage);\n","type H2OEvent = Parameters<NonNullable<typeof __H2O_LOG_EVENT>>[0];\n\nlet hasWarned = false;\n\n/**\n * @deprecated Only used with the classic Remix compiler\n */\nexport function createEventLogger(appLoadContext: Record<string, unknown>) {\n const context = (appLoadContext || {}) as {\n env?: Record<string, any>;\n waitUntil?: (promise: Promise<any>) => void;\n };\n\n const eventLoggerService = context?.env?.H2O_LOG_EVENT as\n | undefined\n | {fetch: (req: Request) => Promise<Response>};\n\n if (typeof eventLoggerService?.fetch !== 'function') return;\n\n return ({\n url,\n endTime = Date.now(),\n waitUntil = context?.waitUntil,\n ...rest\n }: H2OEvent) => {\n const promise = Promise.resolve().then(() =>\n eventLoggerService\n .fetch(\n new Request(url, {\n method: 'POST',\n body: JSON.stringify({\n endTime,\n ...rest,\n }),\n }),\n )\n .catch((error: Error) => {\n if (!hasWarned) {\n // This might repeat a lot of times due to\n // the same issue, so we only warn once.\n console.debug('Failed to log H2O event\\n', error.stack);\n hasWarned = true;\n }\n }),\n );\n\n promise && waitUntil?.(promise);\n };\n}\n","/// <reference types=\"@shopify/hydrogen\" />\nimport {\n createRequestHandler as createRemixRequestHandler,\n type AppLoadContext,\n type ServerBuild,\n} from '@remix-run/server-runtime';\nimport {createEventLogger} from './event-logger';\n\nconst originalErrorToString = Error.prototype.toString;\nError.prototype.toString = function () {\n return this.stack || originalErrorToString.call(this);\n};\n\n/** Server-Timing header key to signal that the SFAPI proxy is enabled */\nconst HYDROGEN_SFAPI_PROXY_KEY = '_sfapi_proxy';\n\nlet hasWarnedAboutStorefront = false;\n\nfunction warnOnce(message: string) {\n if (!hasWarnedAboutStorefront) {\n hasWarnedAboutStorefront = true;\n console.warn(message);\n }\n}\n\nfunction buildServerTimingHeader(values: Record<string, string | undefined>) {\n return Object.entries(values)\n .map(([key, value]) => (value ? `${key};desc=${value}` : undefined))\n .filter(Boolean)\n .join(', ');\n}\n\nfunction appendServerTimingHeader(\n response: {headers: Headers},\n values: string | Record<string, string | undefined>,\n) {\n const header =\n typeof values === 'string' ? values : buildServerTimingHeader(values);\n\n if (header) {\n response.headers.append('Server-Timing', header);\n }\n}\n\ntype CreateRequestHandlerOptions<Context = unknown> = {\n /** Remix's server build */\n build: ServerBuild;\n /** Remix's mode */\n mode?: string;\n /**\n * Function to provide the load context for each request.\n * It must contain Hydrogen's storefront client instance\n * for other Hydrogen utilities to work properly.\n */\n getLoadContext?: (request: Request) => Promise<Context> | Context;\n /**\n * Whether to include the `powered-by` header in responses\n * @default true\n */\n poweredByHeader?: boolean;\n /**\n * Collect tracking information from subrequests such as cookies\n * and forward them to the browser. Disable this if you are not\n * using Hydrogen's built-in analytics.\n * @default true\n */\n collectTrackingInformation?: boolean;\n /**\n * Whether to proxy standard routes such as `/api/.../graphql.json` (Storefront API).\n * You can disable this if you are handling these routes yourself. Ensure that\n * the proxy works if you rely on Hydrogen's built-in behaviors such as analytics.\n * @default true\n */\n proxyStandardRoutes?: boolean;\n};\n\nexport function createRequestHandler<Context = unknown>({\n build,\n mode,\n poweredByHeader = true,\n getLoadContext,\n collectTrackingInformation = true,\n proxyStandardRoutes = true,\n}: CreateRequestHandlerOptions<Context>) {\n const handleRequest = createRemixRequestHandler(build, mode);\n\n const appendPoweredByHeader = poweredByHeader\n ? (response: Response) =>\n response.headers.append('powered-by', 'Shopify, Hydrogen')\n : undefined;\n\n return async (request: Request) => {\n const method = request.method;\n\n if ((method === 'GET' || method === 'HEAD') && request.body) {\n return new Response(`${method} requests cannot have a body`, {\n status: 400,\n });\n }\n\n const url = new URL(request.url);\n\n if (url.pathname.includes('//')) {\n return new Response(null, {\n status: 301,\n headers: {\n location: url.pathname.replace(/\\/+/g, '/'),\n },\n });\n }\n\n const context = getLoadContext\n ? ((await getLoadContext(request)) as AppLoadContext)\n : undefined;\n\n // Access storefront from context if available\n const storefront = (\n context as {storefront?: StorefrontForProxy} | undefined\n )?.storefront;\n\n if (proxyStandardRoutes) {\n if (!storefront) {\n // TODO: this should throw error in future major version\n warnOnce(\n '[h2:createRequestHandler] Storefront instance is required to proxy standard routes.',\n );\n }\n\n // Proxy Storefront API requests\n if (storefront?.isStorefrontApiUrl(request)) {\n const response = await storefront.forward(request);\n appendPoweredByHeader?.(response);\n return response;\n }\n }\n\n if (process.env.NODE_ENV === 'development' && context) {\n // Store logger in globalThis so it can be accessed from the worker.\n // The global property must be different from the binding name,\n // otherwise Miniflare throws an error when accessing it.\n globalThis.__H2O_LOG_EVENT ??= createEventLogger(context);\n }\n\n const startTime = Date.now();\n\n const response = await handleRequest(request, context);\n\n if (storefront && proxyStandardRoutes) {\n if (collectTrackingInformation) {\n storefront.setCollectedSubrequestHeaders(response);\n }\n\n // TODO: assume SFAPI proxy is available in future major version\n // Signal that SFAPI proxy is enabled for document requests.\n // Note: sec-fetch-dest is automatically added by modern browsers,\n // but we also check the Accept header for other clients.\n const fetchDest = request.headers.get('sec-fetch-dest');\n if (\n (fetchDest && fetchDest === 'document') ||\n request.headers.get('accept')?.includes('text/html')\n ) {\n appendServerTimingHeader(response, {[HYDROGEN_SFAPI_PROXY_KEY]: '1'});\n }\n }\n\n appendPoweredByHeader?.(response);\n\n if (process.env.NODE_ENV === 'development') {\n globalThis.__H2O_LOG_EVENT?.({\n eventType: 'request',\n url: request.url,\n requestId: request.headers.get('request-id'),\n purpose: request.headers.get('purpose'),\n startTime,\n responseInit: {\n status: response.status,\n statusText: response.statusText,\n headers: Array.from(response.headers.entries()),\n } satisfies ResponseInit,\n });\n }\n\n return response;\n };\n}\n\ntype StorefrontHeaders = {\n requestGroupId: string | null;\n buyerIp: string | null;\n buyerIpSig: string | null;\n cookie: string | null;\n purpose: string | null;\n};\n\nexport function getStorefrontHeaders(request: Request): StorefrontHeaders {\n const headers = request.headers;\n return {\n requestGroupId: headers.get('request-id'),\n buyerIp: headers.get('oxygen-buyer-ip'),\n buyerIpSig: headers.get('X-Shopify-Client-IP-Sig'),\n cookie: headers.get('cookie'),\n // sec-purpose is added by browsers automatically when using link/prefetch or Speculation Rules\n purpose: headers.get('sec-purpose') || headers.get('purpose'),\n };\n}\n\n/**\n * Minimal storefront interface needed for proxy functionality.\n * The full Storefront type is defined in @shopify/hydrogen.\n */\ntype StorefrontForProxy = {\n isStorefrontApiUrl: (request: {url?: string}) => boolean;\n forward: (request: Request) => Promise<Response>;\n setCollectedSubrequestHeaders: (response: {headers: Headers}) => void;\n};\n"]}
@@ -1,50 +1,3 @@
1
- 'use strict';
2
-
3
- var serverRuntime = require('@remix-run/server-runtime');
4
-
5
- var c=new TextEncoder,l=async(e,t)=>{let r=await y(t,["sign"]),n=c.encode(e),s=await crypto.subtle.sign("HMAC",r,n),o=btoa(String.fromCharCode(...new Uint8Array(s))).replace(/=+$/,"");return e+"."+o},g=async(e,t)=>{let r=e.lastIndexOf("."),n=e.slice(0,r),s=e.slice(r+1),o=await y(t,["verify"]),a=c.encode(n),i=m(atob(s));return await crypto.subtle.verify("HMAC",o,i,a)?n:!1};async function y(e,t){return await crypto.subtle.importKey("raw",c.encode(e),{name:"HMAC",hash:"SHA-256"},!1,t)}function m(e){let t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t}var u=serverRuntime.createCookieFactory({sign:l,unsign:g}),C=serverRuntime.createCookieSessionStorageFactory(u),S=serverRuntime.createSessionStorageFactory(u),k=serverRuntime.createMemorySessionStorageFactory(S);var A=Error.prototype.toString;Error.prototype.toString=function(){return this.stack||A.call(this)};function E({build:e,mode:t,poweredByHeader:r=!0,getLoadContext:n}){let s=serverRuntime.createRequestHandler(e,t);return async o=>{let a=o.method;if((a==="GET"||a==="HEAD")&&o.body)return new Response(`${a} requests cannot have a body`,{status:400});let i=new URL(o.url);if(i.pathname.includes("//"))return new Response(null,{status:301,headers:{location:i.pathname.replace(/\/+/g,"/")}});let p=n?await n(o):void 0,d=await s(o,p);return r&&d.headers.append("powered-by","Shopify, Hydrogen"),d}}function v(e){let t=e.headers;return {requestGroupId:t.get("request-id"),buyerIp:t.get("oxygen-buyer-ip"),cookie:t.get("cookie"),purpose:t.get("purpose")}}
6
-
7
- Object.defineProperty(exports, "MaxPartSizeExceededError", {
8
- enumerable: true,
9
- get: function () { return serverRuntime.MaxPartSizeExceededError; }
10
- });
11
- Object.defineProperty(exports, "createSession", {
12
- enumerable: true,
13
- get: function () { return serverRuntime.createSession; }
14
- });
15
- Object.defineProperty(exports, "data", {
16
- enumerable: true,
17
- get: function () { return serverRuntime.data; }
18
- });
19
- Object.defineProperty(exports, "defer", {
20
- enumerable: true,
21
- get: function () { return serverRuntime.defer; }
22
- });
23
- Object.defineProperty(exports, "isCookie", {
24
- enumerable: true,
25
- get: function () { return serverRuntime.isCookie; }
26
- });
27
- Object.defineProperty(exports, "isSession", {
28
- enumerable: true,
29
- get: function () { return serverRuntime.isSession; }
30
- });
31
- Object.defineProperty(exports, "json", {
32
- enumerable: true,
33
- get: function () { return serverRuntime.json; }
34
- });
35
- Object.defineProperty(exports, "redirect", {
36
- enumerable: true,
37
- get: function () { return serverRuntime.redirect; }
38
- });
39
- Object.defineProperty(exports, "redirectDocument", {
40
- enumerable: true,
41
- get: function () { return serverRuntime.redirectDocument; }
42
- });
43
- exports.createCookie = u;
44
- exports.createCookieSessionStorage = C;
45
- exports.createMemorySessionStorage = k;
46
- exports.createRequestHandler = E;
47
- exports.createSessionStorage = S;
48
- exports.getStorefrontHeaders = v;
49
- //# sourceMappingURL=out.js.map
1
+ 'use strict';var serverRuntime=require('@remix-run/server-runtime');var g=new TextEncoder,m=async(t,e)=>{let r=await x(e,["sign"]),n=g.encode(t),i=await crypto.subtle.sign("HMAC",r,n),s=btoa(String.fromCharCode(...new Uint8Array(i))).replace(/=+$/,"");return t+"."+s},h=async(t,e)=>{let r=t.lastIndexOf("."),n=t.slice(0,r),i=t.slice(r+1),s=await x(e,["verify"]),p=g.encode(n),u=R(atob(i));return await crypto.subtle.verify("HMAC",s,u,p)?n:false};async function x(t,e){return await crypto.subtle.importKey("raw",g.encode(t),{name:"HMAC",hash:"SHA-256"},false,e)}function R(t){let e=new Uint8Array(t.length);for(let r=0;r<t.length;r++)e[r]=t.charCodeAt(r);return e}var f=serverRuntime.createCookieFactory({sign:m,unsign:h}),b=serverRuntime.createCookieSessionStorageFactory(f),H=serverRuntime.createSessionStorageFactory(f),E=serverRuntime.createMemorySessionStorageFactory(H);var v=Error.prototype.toString;Error.prototype.toString=function(){return this.stack||v.call(this)};var T="_sfapi_proxy",C=false;function D(t){C||(C=true,console.warn(t));}function O(t){return Object.entries(t).map(([e,r])=>r?`${e};desc=${r}`:void 0).filter(Boolean).join(", ")}function M(t,e){let r=typeof e=="string"?e:O(e);r&&t.headers.append("Server-Timing",r);}function _({build:t,mode:e,poweredByHeader:r=true,getLoadContext:n,collectTrackingInformation:i=true,proxyStandardRoutes:s=true}){let p=serverRuntime.createRequestHandler(t,e),u=r?o=>o.headers.append("powered-by","Shopify, Hydrogen"):void 0;return async o=>{let l=o.method;if((l==="GET"||l==="HEAD")&&o.body)return new Response(`${l} requests cannot have a body`,{status:400});let y=new URL(o.url);if(y.pathname.includes("//"))return new Response(null,{status:301,headers:{location:y.pathname.replace(/\/+/g,"/")}});let S=n?await n(o):void 0,a=S?.storefront;if(s&&(a||D("[h2:createRequestHandler] Storefront instance is required to proxy standard routes."),a?.isStorefrontApiUrl(o))){let c=await a.forward(o);return u?.(c),c}let d=await p(o,S);if(a&&s){i&&a.setCollectedSubrequestHeaders(d);let c=o.headers.get("sec-fetch-dest");(c&&c==="document"||o.headers.get("accept")?.includes("text/html"))&&M(d,{[T]:"1"});}return u?.(d),d}}function I(t){let e=t.headers;return {requestGroupId:e.get("request-id"),buyerIp:e.get("oxygen-buyer-ip"),buyerIpSig:e.get("X-Shopify-Client-IP-Sig"),cookie:e.get("cookie"),purpose:e.get("sec-purpose")||e.get("purpose")}}
2
+ Object.defineProperty(exports,"MaxPartSizeExceededError",{enumerable:true,get:function(){return serverRuntime.MaxPartSizeExceededError}});Object.defineProperty(exports,"createSession",{enumerable:true,get:function(){return serverRuntime.createSession}});Object.defineProperty(exports,"data",{enumerable:true,get:function(){return serverRuntime.data}});Object.defineProperty(exports,"defer",{enumerable:true,get:function(){return serverRuntime.defer}});Object.defineProperty(exports,"isCookie",{enumerable:true,get:function(){return serverRuntime.isCookie}});Object.defineProperty(exports,"isSession",{enumerable:true,get:function(){return serverRuntime.isSession}});Object.defineProperty(exports,"json",{enumerable:true,get:function(){return serverRuntime.json}});Object.defineProperty(exports,"redirect",{enumerable:true,get:function(){return serverRuntime.redirect}});Object.defineProperty(exports,"redirectDocument",{enumerable:true,get:function(){return serverRuntime.redirectDocument}});exports.createCookie=f;exports.createCookieSessionStorage=b;exports.createMemorySessionStorage=E;exports.createRequestHandler=_;exports.createSessionStorage=H;exports.getStorefrontHeaders=I;//# sourceMappingURL=index.cjs.map
50
3
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/implementations.ts","../../src/crypto.ts","../../src/server.ts","../../src/index.ts"],"names":["createCookieFactory","createCookieSessionStorageFactory","createMemorySessionStorageFactory","createSessionStorageFactory","encoder","sign","value","secret","key","createKey","data","signature","hash","unsign","signed","index","byteStringToUint8Array","usages","byteString","array","i","createCookie","createCookieSessionStorage","createSessionStorage","createMemorySessionStorage","createRemixRequestHandler","originalErrorToString","createRequestHandler","build","mode","poweredByHeader","getLoadContext","handleRequest","request","method","url","context","startTime","response","getStorefrontHeaders","headers","createSession","defer","isCookie","isSession","json","MaxPartSizeExceededError","redirect","redirectDocument"],"mappings":"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","sourcesContent":["import {\n createCookieFactory,\n createCookieSessionStorageFactory,\n createMemorySessionStorageFactory,\n createSessionStorageFactory,\n} from '@remix-run/server-runtime';\n\nimport {sign, unsign} from './crypto';\n\nexport const createCookie = createCookieFactory({sign, unsign});\nexport const createCookieSessionStorage =\n createCookieSessionStorageFactory(createCookie);\nexport const createSessionStorage = createSessionStorageFactory(createCookie);\nexport const createMemorySessionStorage =\n createMemorySessionStorageFactory(createSessionStorage);\n","import type {SignFunction, UnsignFunction} from '@remix-run/server-runtime';\n\nconst encoder = new TextEncoder();\n\nexport const sign: SignFunction = async (value, secret) => {\n const key = await createKey(secret, ['sign']);\n const data = encoder.encode(value);\n const signature = await crypto.subtle.sign('HMAC', key, data);\n const hash = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(\n /=+$/,\n '',\n );\n\n return value + '.' + hash;\n};\n\nexport const unsign: UnsignFunction = async (signed, secret) => {\n const index = signed.lastIndexOf('.');\n const value = signed.slice(0, index);\n const hash = signed.slice(index + 1);\n\n const key = await createKey(secret, ['verify']);\n const data = encoder.encode(value);\n const signature = byteStringToUint8Array(atob(hash));\n const valid = await crypto.subtle.verify('HMAC', key, signature, data);\n\n return valid ? value : false;\n};\n\nasync function createKey(\n secret: string,\n usages: CryptoKey['usages'],\n): Promise<CryptoKey> {\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n {name: 'HMAC', hash: 'SHA-256'},\n false,\n usages,\n );\n\n return key;\n}\n\nfunction byteStringToUint8Array(byteString: string): Uint8Array {\n const array = new Uint8Array(byteString.length);\n\n for (let i = 0; i < byteString.length; i++) {\n array[i] = byteString.charCodeAt(i);\n }\n\n return array;\n}\n","/// <reference types=\"@shopify/hydrogen\" />\nimport {\n createRequestHandler as createRemixRequestHandler,\n type AppLoadContext,\n type ServerBuild,\n} from '@remix-run/server-runtime';\nimport {createEventLogger} from './event-logger';\n\nconst originalErrorToString = Error.prototype.toString;\nError.prototype.toString = function () {\n return this.stack || originalErrorToString.call(this);\n};\n\nexport function createRequestHandler<Context = unknown>({\n build,\n mode,\n poweredByHeader = true,\n getLoadContext,\n}: {\n build: ServerBuild;\n mode?: string;\n poweredByHeader?: boolean;\n getLoadContext?: (request: Request) => Promise<Context> | Context;\n}) {\n const handleRequest = createRemixRequestHandler(build, mode);\n\n return async (request: Request) => {\n const method = request.method;\n\n if ((method === 'GET' || method === 'HEAD') && request.body) {\n return new Response(`${method} requests cannot have a body`, {\n status: 400,\n });\n }\n\n const url = new URL(request.url);\n\n if (url.pathname.includes('//')) {\n return new Response(null, {\n status: 301,\n headers: {\n location: url.pathname.replace(/\\/+/g, '/'),\n },\n });\n }\n\n const context = getLoadContext\n ? ((await getLoadContext(request)) as AppLoadContext)\n : undefined;\n\n if (process.env.NODE_ENV === 'development' && context) {\n // Store logger in globalThis so it can be accessed from the worker.\n // The global property must be different from the binding name,\n // otherwise Miniflare throws an error when accessing it.\n globalThis.__H2O_LOG_EVENT ??= createEventLogger(context);\n }\n\n const startTime = Date.now();\n\n const response = await handleRequest(request, context);\n\n if (poweredByHeader) {\n response.headers.append('powered-by', 'Shopify, Hydrogen');\n }\n\n if (process.env.NODE_ENV === 'development') {\n globalThis.__H2O_LOG_EVENT?.({\n eventType: 'request',\n url: request.url,\n requestId: request.headers.get('request-id'),\n purpose: request.headers.get('purpose'),\n startTime,\n responseInit: {\n status: response.status,\n statusText: response.statusText,\n headers: Array.from(response.headers.entries()),\n } satisfies ResponseInit,\n });\n }\n\n return response;\n };\n}\n\ntype StorefrontHeaders = {\n requestGroupId: string | null;\n buyerIp: string | null;\n cookie: string | null;\n purpose: string | null;\n};\n\nexport function getStorefrontHeaders(request: Request): StorefrontHeaders {\n const headers = request.headers;\n return {\n requestGroupId: headers.get('request-id'),\n buyerIp: headers.get('oxygen-buyer-ip'),\n cookie: headers.get('cookie'),\n purpose: headers.get('purpose'),\n };\n}\n","export {\n createCookie,\n createCookieSessionStorage,\n createMemorySessionStorage,\n createSessionStorage,\n} from './implementations';\nexport {createRequestHandler, getStorefrontHeaders} from './server';\nexport type {\n ActionFunction,\n ActionFunctionArgs,\n AppLoadContext,\n Cookie,\n CookieOptions,\n CookieParseOptions,\n CookieSerializeOptions,\n CookieSignatureOptions,\n DataFunctionArgs,\n EntryContext,\n ErrorResponse,\n HandleDataRequestFunction,\n HandleDocumentRequestFunction,\n HandleErrorFunction,\n HeadersArgs,\n HeadersFunction,\n HtmlLinkDescriptor,\n JsonFunction,\n LinkDescriptor,\n LinksFunction,\n LoaderFunction,\n LoaderFunctionArgs,\n MemoryUploadHandlerFilterArgs,\n MemoryUploadHandlerOptions,\n ServerRuntimeMetaArgs as MetaArgs,\n ServerRuntimeMetaDescriptor as MetaDescriptor,\n ServerRuntimeMetaFunction as MetaFunction,\n PageLinkDescriptor,\n RequestHandler,\n SerializeFrom,\n ServerBuild,\n ServerEntryModule,\n Session,\n SessionData,\n SessionIdStorageStrategy,\n SessionStorage,\n SignFunction,\n TypedDeferredData,\n TypedResponse,\n UnsignFunction,\n UploadHandler,\n UploadHandlerPart,\n} from '@remix-run/server-runtime';\nexport {\n createSession,\n data,\n defer,\n isCookie,\n isSession,\n json,\n MaxPartSizeExceededError,\n redirect,\n redirectDocument,\n} from '@remix-run/server-runtime';\n"]}
1
+ {"version":3,"sources":["../../src/crypto.ts","../../src/implementations.ts","../../src/server.ts"],"names":["encoder","sign","value","secret","key","createKey","data","signature","hash","unsign","signed","index","byteStringToUint8Array","usages","byteString","array","i","createCookie","createCookieFactory","createCookieSessionStorage","createCookieSessionStorageFactory","createSessionStorage","createSessionStorageFactory","createMemorySessionStorage","createMemorySessionStorageFactory","originalErrorToString","HYDROGEN_SFAPI_PROXY_KEY","hasWarnedAboutStorefront","warnOnce","message","buildServerTimingHeader","values","appendServerTimingHeader","response","header","createRequestHandler","build","mode","poweredByHeader","getLoadContext","collectTrackingInformation","proxyStandardRoutes","handleRequest","createRemixRequestHandler","appendPoweredByHeader","request","method","url","context","storefront","fetchDest","getStorefrontHeaders","headers"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {SignFunction, UnsignFunction} from '@remix-run/server-runtime';\n\nconst encoder = new TextEncoder();\n\nexport const sign: SignFunction = async (value, secret) => {\n const key = await createKey(secret, ['sign']);\n const data = encoder.encode(value);\n const signature = await crypto.subtle.sign('HMAC', key, data);\n const hash = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(\n /=+$/,\n '',\n );\n\n return value + '.' + hash;\n};\n\nexport const unsign: UnsignFunction = async (signed, secret) => {\n const index = signed.lastIndexOf('.');\n const value = signed.slice(0, index);\n const hash = signed.slice(index + 1);\n\n const key = await createKey(secret, ['verify']);\n const data = encoder.encode(value);\n const signature = byteStringToUint8Array(atob(hash));\n const valid = await crypto.subtle.verify('HMAC', key, signature, data);\n\n return valid ? value : false;\n};\n\nasync function createKey(\n secret: string,\n usages: CryptoKey['usages'],\n): Promise<CryptoKey> {\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n {name: 'HMAC', hash: 'SHA-256'},\n false,\n usages,\n );\n\n return key;\n}\n\nfunction byteStringToUint8Array(byteString: string): Uint8Array {\n const array = new Uint8Array(byteString.length);\n\n for (let i = 0; i < byteString.length; i++) {\n array[i] = byteString.charCodeAt(i);\n }\n\n return array;\n}\n","import {\n createCookieFactory,\n createCookieSessionStorageFactory,\n createMemorySessionStorageFactory,\n createSessionStorageFactory,\n} from '@remix-run/server-runtime';\n\nimport {sign, unsign} from './crypto';\n\nexport const createCookie = createCookieFactory({sign, unsign});\nexport const createCookieSessionStorage =\n createCookieSessionStorageFactory(createCookie);\nexport const createSessionStorage = createSessionStorageFactory(createCookie);\nexport const createMemorySessionStorage =\n createMemorySessionStorageFactory(createSessionStorage);\n","/// <reference types=\"@shopify/hydrogen\" />\nimport {\n createRequestHandler as createRemixRequestHandler,\n type AppLoadContext,\n type ServerBuild,\n} from '@remix-run/server-runtime';\nimport {createEventLogger} from './event-logger';\n\nconst originalErrorToString = Error.prototype.toString;\nError.prototype.toString = function () {\n return this.stack || originalErrorToString.call(this);\n};\n\n/** Server-Timing header key to signal that the SFAPI proxy is enabled */\nconst HYDROGEN_SFAPI_PROXY_KEY = '_sfapi_proxy';\n\nlet hasWarnedAboutStorefront = false;\n\nfunction warnOnce(message: string) {\n if (!hasWarnedAboutStorefront) {\n hasWarnedAboutStorefront = true;\n console.warn(message);\n }\n}\n\nfunction buildServerTimingHeader(values: Record<string, string | undefined>) {\n return Object.entries(values)\n .map(([key, value]) => (value ? `${key};desc=${value}` : undefined))\n .filter(Boolean)\n .join(', ');\n}\n\nfunction appendServerTimingHeader(\n response: {headers: Headers},\n values: string | Record<string, string | undefined>,\n) {\n const header =\n typeof values === 'string' ? values : buildServerTimingHeader(values);\n\n if (header) {\n response.headers.append('Server-Timing', header);\n }\n}\n\ntype CreateRequestHandlerOptions<Context = unknown> = {\n /** Remix's server build */\n build: ServerBuild;\n /** Remix's mode */\n mode?: string;\n /**\n * Function to provide the load context for each request.\n * It must contain Hydrogen's storefront client instance\n * for other Hydrogen utilities to work properly.\n */\n getLoadContext?: (request: Request) => Promise<Context> | Context;\n /**\n * Whether to include the `powered-by` header in responses\n * @default true\n */\n poweredByHeader?: boolean;\n /**\n * Collect tracking information from subrequests such as cookies\n * and forward them to the browser. Disable this if you are not\n * using Hydrogen's built-in analytics.\n * @default true\n */\n collectTrackingInformation?: boolean;\n /**\n * Whether to proxy standard routes such as `/api/.../graphql.json` (Storefront API).\n * You can disable this if you are handling these routes yourself. Ensure that\n * the proxy works if you rely on Hydrogen's built-in behaviors such as analytics.\n * @default true\n */\n proxyStandardRoutes?: boolean;\n};\n\nexport function createRequestHandler<Context = unknown>({\n build,\n mode,\n poweredByHeader = true,\n getLoadContext,\n collectTrackingInformation = true,\n proxyStandardRoutes = true,\n}: CreateRequestHandlerOptions<Context>) {\n const handleRequest = createRemixRequestHandler(build, mode);\n\n const appendPoweredByHeader = poweredByHeader\n ? (response: Response) =>\n response.headers.append('powered-by', 'Shopify, Hydrogen')\n : undefined;\n\n return async (request: Request) => {\n const method = request.method;\n\n if ((method === 'GET' || method === 'HEAD') && request.body) {\n return new Response(`${method} requests cannot have a body`, {\n status: 400,\n });\n }\n\n const url = new URL(request.url);\n\n if (url.pathname.includes('//')) {\n return new Response(null, {\n status: 301,\n headers: {\n location: url.pathname.replace(/\\/+/g, '/'),\n },\n });\n }\n\n const context = getLoadContext\n ? ((await getLoadContext(request)) as AppLoadContext)\n : undefined;\n\n // Access storefront from context if available\n const storefront = (\n context as {storefront?: StorefrontForProxy} | undefined\n )?.storefront;\n\n if (proxyStandardRoutes) {\n if (!storefront) {\n // TODO: this should throw error in future major version\n warnOnce(\n '[h2:createRequestHandler] Storefront instance is required to proxy standard routes.',\n );\n }\n\n // Proxy Storefront API requests\n if (storefront?.isStorefrontApiUrl(request)) {\n const response = await storefront.forward(request);\n appendPoweredByHeader?.(response);\n return response;\n }\n }\n\n if (process.env.NODE_ENV === 'development' && context) {\n // Store logger in globalThis so it can be accessed from the worker.\n // The global property must be different from the binding name,\n // otherwise Miniflare throws an error when accessing it.\n globalThis.__H2O_LOG_EVENT ??= createEventLogger(context);\n }\n\n const startTime = Date.now();\n\n const response = await handleRequest(request, context);\n\n if (storefront && proxyStandardRoutes) {\n if (collectTrackingInformation) {\n storefront.setCollectedSubrequestHeaders(response);\n }\n\n // TODO: assume SFAPI proxy is available in future major version\n // Signal that SFAPI proxy is enabled for document requests.\n // Note: sec-fetch-dest is automatically added by modern browsers,\n // but we also check the Accept header for other clients.\n const fetchDest = request.headers.get('sec-fetch-dest');\n if (\n (fetchDest && fetchDest === 'document') ||\n request.headers.get('accept')?.includes('text/html')\n ) {\n appendServerTimingHeader(response, {[HYDROGEN_SFAPI_PROXY_KEY]: '1'});\n }\n }\n\n appendPoweredByHeader?.(response);\n\n if (process.env.NODE_ENV === 'development') {\n globalThis.__H2O_LOG_EVENT?.({\n eventType: 'request',\n url: request.url,\n requestId: request.headers.get('request-id'),\n purpose: request.headers.get('purpose'),\n startTime,\n responseInit: {\n status: response.status,\n statusText: response.statusText,\n headers: Array.from(response.headers.entries()),\n } satisfies ResponseInit,\n });\n }\n\n return response;\n };\n}\n\ntype StorefrontHeaders = {\n requestGroupId: string | null;\n buyerIp: string | null;\n buyerIpSig: string | null;\n cookie: string | null;\n purpose: string | null;\n};\n\nexport function getStorefrontHeaders(request: Request): StorefrontHeaders {\n const headers = request.headers;\n return {\n requestGroupId: headers.get('request-id'),\n buyerIp: headers.get('oxygen-buyer-ip'),\n buyerIpSig: headers.get('X-Shopify-Client-IP-Sig'),\n cookie: headers.get('cookie'),\n // sec-purpose is added by browsers automatically when using link/prefetch or Speculation Rules\n purpose: headers.get('sec-purpose') || headers.get('purpose'),\n };\n}\n\n/**\n * Minimal storefront interface needed for proxy functionality.\n * The full Storefront type is defined in @shopify/hydrogen.\n */\ntype StorefrontForProxy = {\n isStorefrontApiUrl: (request: {url?: string}) => boolean;\n forward: (request: Request) => Promise<Response>;\n setCollectedSubrequestHeaders: (response: {headers: Headers}) => void;\n};\n"]}
@@ -7,15 +7,42 @@ declare const createCookieSessionStorage: _remix_run_server_runtime.CreateCookie
7
7
  declare const createSessionStorage: _remix_run_server_runtime.CreateSessionStorageFunction;
8
8
  declare const createMemorySessionStorage: _remix_run_server_runtime.CreateMemorySessionStorageFunction;
9
9
 
10
- declare function createRequestHandler<Context = unknown>({ build, mode, poweredByHeader, getLoadContext, }: {
10
+ type CreateRequestHandlerOptions<Context = unknown> = {
11
+ /** Remix's server build */
11
12
  build: ServerBuild;
13
+ /** Remix's mode */
12
14
  mode?: string;
13
- poweredByHeader?: boolean;
15
+ /**
16
+ * Function to provide the load context for each request.
17
+ * It must contain Hydrogen's storefront client instance
18
+ * for other Hydrogen utilities to work properly.
19
+ */
14
20
  getLoadContext?: (request: Request) => Promise<Context> | Context;
15
- }): (request: Request) => Promise<Response>;
21
+ /**
22
+ * Whether to include the `powered-by` header in responses
23
+ * @default true
24
+ */
25
+ poweredByHeader?: boolean;
26
+ /**
27
+ * Collect tracking information from subrequests such as cookies
28
+ * and forward them to the browser. Disable this if you are not
29
+ * using Hydrogen's built-in analytics.
30
+ * @default true
31
+ */
32
+ collectTrackingInformation?: boolean;
33
+ /**
34
+ * Whether to proxy standard routes such as `/api/.../graphql.json` (Storefront API).
35
+ * You can disable this if you are handling these routes yourself. Ensure that
36
+ * the proxy works if you rely on Hydrogen's built-in behaviors such as analytics.
37
+ * @default true
38
+ */
39
+ proxyStandardRoutes?: boolean;
40
+ };
41
+ declare function createRequestHandler<Context = unknown>({ build, mode, poweredByHeader, getLoadContext, collectTrackingInformation, proxyStandardRoutes, }: CreateRequestHandlerOptions<Context>): (request: Request) => Promise<Response>;
16
42
  type StorefrontHeaders = {
17
43
  requestGroupId: string | null;
18
44
  buyerIp: string | null;
45
+ buyerIpSig: string | null;
19
46
  cookie: string | null;
20
47
  purpose: string | null;
21
48
  };
@@ -7,15 +7,42 @@ declare const createCookieSessionStorage: _remix_run_server_runtime.CreateCookie
7
7
  declare const createSessionStorage: _remix_run_server_runtime.CreateSessionStorageFunction;
8
8
  declare const createMemorySessionStorage: _remix_run_server_runtime.CreateMemorySessionStorageFunction;
9
9
 
10
- declare function createRequestHandler<Context = unknown>({ build, mode, poweredByHeader, getLoadContext, }: {
10
+ type CreateRequestHandlerOptions<Context = unknown> = {
11
+ /** Remix's server build */
11
12
  build: ServerBuild;
13
+ /** Remix's mode */
12
14
  mode?: string;
13
- poweredByHeader?: boolean;
15
+ /**
16
+ * Function to provide the load context for each request.
17
+ * It must contain Hydrogen's storefront client instance
18
+ * for other Hydrogen utilities to work properly.
19
+ */
14
20
  getLoadContext?: (request: Request) => Promise<Context> | Context;
15
- }): (request: Request) => Promise<Response>;
21
+ /**
22
+ * Whether to include the `powered-by` header in responses
23
+ * @default true
24
+ */
25
+ poweredByHeader?: boolean;
26
+ /**
27
+ * Collect tracking information from subrequests such as cookies
28
+ * and forward them to the browser. Disable this if you are not
29
+ * using Hydrogen's built-in analytics.
30
+ * @default true
31
+ */
32
+ collectTrackingInformation?: boolean;
33
+ /**
34
+ * Whether to proxy standard routes such as `/api/.../graphql.json` (Storefront API).
35
+ * You can disable this if you are handling these routes yourself. Ensure that
36
+ * the proxy works if you rely on Hydrogen's built-in behaviors such as analytics.
37
+ * @default true
38
+ */
39
+ proxyStandardRoutes?: boolean;
40
+ };
41
+ declare function createRequestHandler<Context = unknown>({ build, mode, poweredByHeader, getLoadContext, collectTrackingInformation, proxyStandardRoutes, }: CreateRequestHandlerOptions<Context>): (request: Request) => Promise<Response>;
16
42
  type StorefrontHeaders = {
17
43
  requestGroupId: string | null;
18
44
  buyerIp: string | null;
45
+ buyerIpSig: string | null;
19
46
  cookie: string | null;
20
47
  purpose: string | null;
21
48
  };
@@ -1,8 +1,3 @@
1
- import { createCookieFactory, createCookieSessionStorageFactory, createSessionStorageFactory, createMemorySessionStorageFactory, createRequestHandler } from '@remix-run/server-runtime';
2
- export { MaxPartSizeExceededError, createSession, data, defer, isCookie, isSession, json, redirect, redirectDocument } from '@remix-run/server-runtime';
3
-
4
- var c=new TextEncoder,l=async(e,t)=>{let r=await y(t,["sign"]),n=c.encode(e),s=await crypto.subtle.sign("HMAC",r,n),o=btoa(String.fromCharCode(...new Uint8Array(s))).replace(/=+$/,"");return e+"."+o},g=async(e,t)=>{let r=e.lastIndexOf("."),n=e.slice(0,r),s=e.slice(r+1),o=await y(t,["verify"]),a=c.encode(n),i=m(atob(s));return await crypto.subtle.verify("HMAC",o,i,a)?n:!1};async function y(e,t){return await crypto.subtle.importKey("raw",c.encode(e),{name:"HMAC",hash:"SHA-256"},!1,t)}function m(e){let t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t}var u=createCookieFactory({sign:l,unsign:g}),C=createCookieSessionStorageFactory(u),S=createSessionStorageFactory(u),k=createMemorySessionStorageFactory(S);var A=Error.prototype.toString;Error.prototype.toString=function(){return this.stack||A.call(this)};function E({build:e,mode:t,poweredByHeader:r=!0,getLoadContext:n}){let s=createRequestHandler(e,t);return async o=>{let a=o.method;if((a==="GET"||a==="HEAD")&&o.body)return new Response(`${a} requests cannot have a body`,{status:400});let i=new URL(o.url);if(i.pathname.includes("//"))return new Response(null,{status:301,headers:{location:i.pathname.replace(/\/+/g,"/")}});let p=n?await n(o):void 0,d=await s(o,p);return r&&d.headers.append("powered-by","Shopify, Hydrogen"),d}}function v(e){let t=e.headers;return {requestGroupId:t.get("request-id"),buyerIp:t.get("oxygen-buyer-ip"),cookie:t.get("cookie"),purpose:t.get("purpose")}}
5
-
6
- export { u as createCookie, C as createCookieSessionStorage, k as createMemorySessionStorage, E as createRequestHandler, S as createSessionStorage, v as getStorefrontHeaders };
7
- //# sourceMappingURL=out.js.map
1
+ import {createCookieFactory,createCookieSessionStorageFactory,createSessionStorageFactory,createMemorySessionStorageFactory,createRequestHandler}from'@remix-run/server-runtime';export{MaxPartSizeExceededError,createSession,data,defer,isCookie,isSession,json,redirect,redirectDocument}from'@remix-run/server-runtime';var g=new TextEncoder,m=async(t,e)=>{let r=await x(e,["sign"]),n=g.encode(t),i=await crypto.subtle.sign("HMAC",r,n),s=btoa(String.fromCharCode(...new Uint8Array(i))).replace(/=+$/,"");return t+"."+s},h=async(t,e)=>{let r=t.lastIndexOf("."),n=t.slice(0,r),i=t.slice(r+1),s=await x(e,["verify"]),p=g.encode(n),u=R(atob(i));return await crypto.subtle.verify("HMAC",s,u,p)?n:false};async function x(t,e){return await crypto.subtle.importKey("raw",g.encode(t),{name:"HMAC",hash:"SHA-256"},false,e)}function R(t){let e=new Uint8Array(t.length);for(let r=0;r<t.length;r++)e[r]=t.charCodeAt(r);return e}var f=createCookieFactory({sign:m,unsign:h}),b=createCookieSessionStorageFactory(f),H=createSessionStorageFactory(f),E=createMemorySessionStorageFactory(H);var v=Error.prototype.toString;Error.prototype.toString=function(){return this.stack||v.call(this)};var T="_sfapi_proxy",C=false;function D(t){C||(C=true,console.warn(t));}function O(t){return Object.entries(t).map(([e,r])=>r?`${e};desc=${r}`:void 0).filter(Boolean).join(", ")}function M(t,e){let r=typeof e=="string"?e:O(e);r&&t.headers.append("Server-Timing",r);}function _({build:t,mode:e,poweredByHeader:r=true,getLoadContext:n,collectTrackingInformation:i=true,proxyStandardRoutes:s=true}){let p=createRequestHandler(t,e),u=r?o=>o.headers.append("powered-by","Shopify, Hydrogen"):void 0;return async o=>{let l=o.method;if((l==="GET"||l==="HEAD")&&o.body)return new Response(`${l} requests cannot have a body`,{status:400});let y=new URL(o.url);if(y.pathname.includes("//"))return new Response(null,{status:301,headers:{location:y.pathname.replace(/\/+/g,"/")}});let S=n?await n(o):void 0,a=S?.storefront;if(s&&(a||D("[h2:createRequestHandler] Storefront instance is required to proxy standard routes."),a?.isStorefrontApiUrl(o))){let c=await a.forward(o);return u?.(c),c}let d=await p(o,S);if(a&&s){i&&a.setCollectedSubrequestHeaders(d);let c=o.headers.get("sec-fetch-dest");(c&&c==="document"||o.headers.get("accept")?.includes("text/html"))&&M(d,{[T]:"1"});}return u?.(d),d}}function I(t){let e=t.headers;return {requestGroupId:e.get("request-id"),buyerIp:e.get("oxygen-buyer-ip"),buyerIpSig:e.get("X-Shopify-Client-IP-Sig"),cookie:e.get("cookie"),purpose:e.get("sec-purpose")||e.get("purpose")}}
2
+ export{f as createCookie,b as createCookieSessionStorage,E as createMemorySessionStorage,_ as createRequestHandler,H as createSessionStorage,I as getStorefrontHeaders};//# sourceMappingURL=index.js.map
8
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/implementations.ts","../../src/crypto.ts","../../src/server.ts","../../src/index.ts"],"names":["createCookieFactory","createCookieSessionStorageFactory","createMemorySessionStorageFactory","createSessionStorageFactory","encoder","sign","value","secret","key","createKey","data","signature","hash","unsign","signed","index","byteStringToUint8Array","usages","byteString","array","i","createCookie","createCookieSessionStorage","createSessionStorage","createMemorySessionStorage","createRemixRequestHandler","originalErrorToString","createRequestHandler","build","mode","poweredByHeader","getLoadContext","handleRequest","request","method","url","context","startTime","response","getStorefrontHeaders","headers","createSession","defer","isCookie","isSession","json","MaxPartSizeExceededError","redirect","redirectDocument"],"mappings":"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","sourcesContent":["import {\n createCookieFactory,\n createCookieSessionStorageFactory,\n createMemorySessionStorageFactory,\n createSessionStorageFactory,\n} from '@remix-run/server-runtime';\n\nimport {sign, unsign} from './crypto';\n\nexport const createCookie = createCookieFactory({sign, unsign});\nexport const createCookieSessionStorage =\n createCookieSessionStorageFactory(createCookie);\nexport const createSessionStorage = createSessionStorageFactory(createCookie);\nexport const createMemorySessionStorage =\n createMemorySessionStorageFactory(createSessionStorage);\n","import type {SignFunction, UnsignFunction} from '@remix-run/server-runtime';\n\nconst encoder = new TextEncoder();\n\nexport const sign: SignFunction = async (value, secret) => {\n const key = await createKey(secret, ['sign']);\n const data = encoder.encode(value);\n const signature = await crypto.subtle.sign('HMAC', key, data);\n const hash = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(\n /=+$/,\n '',\n );\n\n return value + '.' + hash;\n};\n\nexport const unsign: UnsignFunction = async (signed, secret) => {\n const index = signed.lastIndexOf('.');\n const value = signed.slice(0, index);\n const hash = signed.slice(index + 1);\n\n const key = await createKey(secret, ['verify']);\n const data = encoder.encode(value);\n const signature = byteStringToUint8Array(atob(hash));\n const valid = await crypto.subtle.verify('HMAC', key, signature, data);\n\n return valid ? value : false;\n};\n\nasync function createKey(\n secret: string,\n usages: CryptoKey['usages'],\n): Promise<CryptoKey> {\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n {name: 'HMAC', hash: 'SHA-256'},\n false,\n usages,\n );\n\n return key;\n}\n\nfunction byteStringToUint8Array(byteString: string): Uint8Array {\n const array = new Uint8Array(byteString.length);\n\n for (let i = 0; i < byteString.length; i++) {\n array[i] = byteString.charCodeAt(i);\n }\n\n return array;\n}\n","/// <reference types=\"@shopify/hydrogen\" />\nimport {\n createRequestHandler as createRemixRequestHandler,\n type AppLoadContext,\n type ServerBuild,\n} from '@remix-run/server-runtime';\nimport {createEventLogger} from './event-logger';\n\nconst originalErrorToString = Error.prototype.toString;\nError.prototype.toString = function () {\n return this.stack || originalErrorToString.call(this);\n};\n\nexport function createRequestHandler<Context = unknown>({\n build,\n mode,\n poweredByHeader = true,\n getLoadContext,\n}: {\n build: ServerBuild;\n mode?: string;\n poweredByHeader?: boolean;\n getLoadContext?: (request: Request) => Promise<Context> | Context;\n}) {\n const handleRequest = createRemixRequestHandler(build, mode);\n\n return async (request: Request) => {\n const method = request.method;\n\n if ((method === 'GET' || method === 'HEAD') && request.body) {\n return new Response(`${method} requests cannot have a body`, {\n status: 400,\n });\n }\n\n const url = new URL(request.url);\n\n if (url.pathname.includes('//')) {\n return new Response(null, {\n status: 301,\n headers: {\n location: url.pathname.replace(/\\/+/g, '/'),\n },\n });\n }\n\n const context = getLoadContext\n ? ((await getLoadContext(request)) as AppLoadContext)\n : undefined;\n\n if (process.env.NODE_ENV === 'development' && context) {\n // Store logger in globalThis so it can be accessed from the worker.\n // The global property must be different from the binding name,\n // otherwise Miniflare throws an error when accessing it.\n globalThis.__H2O_LOG_EVENT ??= createEventLogger(context);\n }\n\n const startTime = Date.now();\n\n const response = await handleRequest(request, context);\n\n if (poweredByHeader) {\n response.headers.append('powered-by', 'Shopify, Hydrogen');\n }\n\n if (process.env.NODE_ENV === 'development') {\n globalThis.__H2O_LOG_EVENT?.({\n eventType: 'request',\n url: request.url,\n requestId: request.headers.get('request-id'),\n purpose: request.headers.get('purpose'),\n startTime,\n responseInit: {\n status: response.status,\n statusText: response.statusText,\n headers: Array.from(response.headers.entries()),\n } satisfies ResponseInit,\n });\n }\n\n return response;\n };\n}\n\ntype StorefrontHeaders = {\n requestGroupId: string | null;\n buyerIp: string | null;\n cookie: string | null;\n purpose: string | null;\n};\n\nexport function getStorefrontHeaders(request: Request): StorefrontHeaders {\n const headers = request.headers;\n return {\n requestGroupId: headers.get('request-id'),\n buyerIp: headers.get('oxygen-buyer-ip'),\n cookie: headers.get('cookie'),\n purpose: headers.get('purpose'),\n };\n}\n","export {\n createCookie,\n createCookieSessionStorage,\n createMemorySessionStorage,\n createSessionStorage,\n} from './implementations';\nexport {createRequestHandler, getStorefrontHeaders} from './server';\nexport type {\n ActionFunction,\n ActionFunctionArgs,\n AppLoadContext,\n Cookie,\n CookieOptions,\n CookieParseOptions,\n CookieSerializeOptions,\n CookieSignatureOptions,\n DataFunctionArgs,\n EntryContext,\n ErrorResponse,\n HandleDataRequestFunction,\n HandleDocumentRequestFunction,\n HandleErrorFunction,\n HeadersArgs,\n HeadersFunction,\n HtmlLinkDescriptor,\n JsonFunction,\n LinkDescriptor,\n LinksFunction,\n LoaderFunction,\n LoaderFunctionArgs,\n MemoryUploadHandlerFilterArgs,\n MemoryUploadHandlerOptions,\n ServerRuntimeMetaArgs as MetaArgs,\n ServerRuntimeMetaDescriptor as MetaDescriptor,\n ServerRuntimeMetaFunction as MetaFunction,\n PageLinkDescriptor,\n RequestHandler,\n SerializeFrom,\n ServerBuild,\n ServerEntryModule,\n Session,\n SessionData,\n SessionIdStorageStrategy,\n SessionStorage,\n SignFunction,\n TypedDeferredData,\n TypedResponse,\n UnsignFunction,\n UploadHandler,\n UploadHandlerPart,\n} from '@remix-run/server-runtime';\nexport {\n createSession,\n data,\n defer,\n isCookie,\n isSession,\n json,\n MaxPartSizeExceededError,\n redirect,\n redirectDocument,\n} from '@remix-run/server-runtime';\n"]}
1
+ {"version":3,"sources":["../../src/crypto.ts","../../src/implementations.ts","../../src/server.ts"],"names":["encoder","sign","value","secret","key","createKey","data","signature","hash","unsign","signed","index","byteStringToUint8Array","usages","byteString","array","i","createCookie","createCookieFactory","createCookieSessionStorage","createCookieSessionStorageFactory","createSessionStorage","createSessionStorageFactory","createMemorySessionStorage","createMemorySessionStorageFactory","originalErrorToString","HYDROGEN_SFAPI_PROXY_KEY","hasWarnedAboutStorefront","warnOnce","message","buildServerTimingHeader","values","appendServerTimingHeader","response","header","createRequestHandler","build","mode","poweredByHeader","getLoadContext","collectTrackingInformation","proxyStandardRoutes","handleRequest","createRemixRequestHandler","appendPoweredByHeader","request","method","url","context","storefront","fetchDest","getStorefrontHeaders","headers"],"mappings":"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","file":"index.js","sourcesContent":["import type {SignFunction, UnsignFunction} from '@remix-run/server-runtime';\n\nconst encoder = new TextEncoder();\n\nexport const sign: SignFunction = async (value, secret) => {\n const key = await createKey(secret, ['sign']);\n const data = encoder.encode(value);\n const signature = await crypto.subtle.sign('HMAC', key, data);\n const hash = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(\n /=+$/,\n '',\n );\n\n return value + '.' + hash;\n};\n\nexport const unsign: UnsignFunction = async (signed, secret) => {\n const index = signed.lastIndexOf('.');\n const value = signed.slice(0, index);\n const hash = signed.slice(index + 1);\n\n const key = await createKey(secret, ['verify']);\n const data = encoder.encode(value);\n const signature = byteStringToUint8Array(atob(hash));\n const valid = await crypto.subtle.verify('HMAC', key, signature, data);\n\n return valid ? value : false;\n};\n\nasync function createKey(\n secret: string,\n usages: CryptoKey['usages'],\n): Promise<CryptoKey> {\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n {name: 'HMAC', hash: 'SHA-256'},\n false,\n usages,\n );\n\n return key;\n}\n\nfunction byteStringToUint8Array(byteString: string): Uint8Array {\n const array = new Uint8Array(byteString.length);\n\n for (let i = 0; i < byteString.length; i++) {\n array[i] = byteString.charCodeAt(i);\n }\n\n return array;\n}\n","import {\n createCookieFactory,\n createCookieSessionStorageFactory,\n createMemorySessionStorageFactory,\n createSessionStorageFactory,\n} from '@remix-run/server-runtime';\n\nimport {sign, unsign} from './crypto';\n\nexport const createCookie = createCookieFactory({sign, unsign});\nexport const createCookieSessionStorage =\n createCookieSessionStorageFactory(createCookie);\nexport const createSessionStorage = createSessionStorageFactory(createCookie);\nexport const createMemorySessionStorage =\n createMemorySessionStorageFactory(createSessionStorage);\n","/// <reference types=\"@shopify/hydrogen\" />\nimport {\n createRequestHandler as createRemixRequestHandler,\n type AppLoadContext,\n type ServerBuild,\n} from '@remix-run/server-runtime';\nimport {createEventLogger} from './event-logger';\n\nconst originalErrorToString = Error.prototype.toString;\nError.prototype.toString = function () {\n return this.stack || originalErrorToString.call(this);\n};\n\n/** Server-Timing header key to signal that the SFAPI proxy is enabled */\nconst HYDROGEN_SFAPI_PROXY_KEY = '_sfapi_proxy';\n\nlet hasWarnedAboutStorefront = false;\n\nfunction warnOnce(message: string) {\n if (!hasWarnedAboutStorefront) {\n hasWarnedAboutStorefront = true;\n console.warn(message);\n }\n}\n\nfunction buildServerTimingHeader(values: Record<string, string | undefined>) {\n return Object.entries(values)\n .map(([key, value]) => (value ? `${key};desc=${value}` : undefined))\n .filter(Boolean)\n .join(', ');\n}\n\nfunction appendServerTimingHeader(\n response: {headers: Headers},\n values: string | Record<string, string | undefined>,\n) {\n const header =\n typeof values === 'string' ? values : buildServerTimingHeader(values);\n\n if (header) {\n response.headers.append('Server-Timing', header);\n }\n}\n\ntype CreateRequestHandlerOptions<Context = unknown> = {\n /** Remix's server build */\n build: ServerBuild;\n /** Remix's mode */\n mode?: string;\n /**\n * Function to provide the load context for each request.\n * It must contain Hydrogen's storefront client instance\n * for other Hydrogen utilities to work properly.\n */\n getLoadContext?: (request: Request) => Promise<Context> | Context;\n /**\n * Whether to include the `powered-by` header in responses\n * @default true\n */\n poweredByHeader?: boolean;\n /**\n * Collect tracking information from subrequests such as cookies\n * and forward them to the browser. Disable this if you are not\n * using Hydrogen's built-in analytics.\n * @default true\n */\n collectTrackingInformation?: boolean;\n /**\n * Whether to proxy standard routes such as `/api/.../graphql.json` (Storefront API).\n * You can disable this if you are handling these routes yourself. Ensure that\n * the proxy works if you rely on Hydrogen's built-in behaviors such as analytics.\n * @default true\n */\n proxyStandardRoutes?: boolean;\n};\n\nexport function createRequestHandler<Context = unknown>({\n build,\n mode,\n poweredByHeader = true,\n getLoadContext,\n collectTrackingInformation = true,\n proxyStandardRoutes = true,\n}: CreateRequestHandlerOptions<Context>) {\n const handleRequest = createRemixRequestHandler(build, mode);\n\n const appendPoweredByHeader = poweredByHeader\n ? (response: Response) =>\n response.headers.append('powered-by', 'Shopify, Hydrogen')\n : undefined;\n\n return async (request: Request) => {\n const method = request.method;\n\n if ((method === 'GET' || method === 'HEAD') && request.body) {\n return new Response(`${method} requests cannot have a body`, {\n status: 400,\n });\n }\n\n const url = new URL(request.url);\n\n if (url.pathname.includes('//')) {\n return new Response(null, {\n status: 301,\n headers: {\n location: url.pathname.replace(/\\/+/g, '/'),\n },\n });\n }\n\n const context = getLoadContext\n ? ((await getLoadContext(request)) as AppLoadContext)\n : undefined;\n\n // Access storefront from context if available\n const storefront = (\n context as {storefront?: StorefrontForProxy} | undefined\n )?.storefront;\n\n if (proxyStandardRoutes) {\n if (!storefront) {\n // TODO: this should throw error in future major version\n warnOnce(\n '[h2:createRequestHandler] Storefront instance is required to proxy standard routes.',\n );\n }\n\n // Proxy Storefront API requests\n if (storefront?.isStorefrontApiUrl(request)) {\n const response = await storefront.forward(request);\n appendPoweredByHeader?.(response);\n return response;\n }\n }\n\n if (process.env.NODE_ENV === 'development' && context) {\n // Store logger in globalThis so it can be accessed from the worker.\n // The global property must be different from the binding name,\n // otherwise Miniflare throws an error when accessing it.\n globalThis.__H2O_LOG_EVENT ??= createEventLogger(context);\n }\n\n const startTime = Date.now();\n\n const response = await handleRequest(request, context);\n\n if (storefront && proxyStandardRoutes) {\n if (collectTrackingInformation) {\n storefront.setCollectedSubrequestHeaders(response);\n }\n\n // TODO: assume SFAPI proxy is available in future major version\n // Signal that SFAPI proxy is enabled for document requests.\n // Note: sec-fetch-dest is automatically added by modern browsers,\n // but we also check the Accept header for other clients.\n const fetchDest = request.headers.get('sec-fetch-dest');\n if (\n (fetchDest && fetchDest === 'document') ||\n request.headers.get('accept')?.includes('text/html')\n ) {\n appendServerTimingHeader(response, {[HYDROGEN_SFAPI_PROXY_KEY]: '1'});\n }\n }\n\n appendPoweredByHeader?.(response);\n\n if (process.env.NODE_ENV === 'development') {\n globalThis.__H2O_LOG_EVENT?.({\n eventType: 'request',\n url: request.url,\n requestId: request.headers.get('request-id'),\n purpose: request.headers.get('purpose'),\n startTime,\n responseInit: {\n status: response.status,\n statusText: response.statusText,\n headers: Array.from(response.headers.entries()),\n } satisfies ResponseInit,\n });\n }\n\n return response;\n };\n}\n\ntype StorefrontHeaders = {\n requestGroupId: string | null;\n buyerIp: string | null;\n buyerIpSig: string | null;\n cookie: string | null;\n purpose: string | null;\n};\n\nexport function getStorefrontHeaders(request: Request): StorefrontHeaders {\n const headers = request.headers;\n return {\n requestGroupId: headers.get('request-id'),\n buyerIp: headers.get('oxygen-buyer-ip'),\n buyerIpSig: headers.get('X-Shopify-Client-IP-Sig'),\n cookie: headers.get('cookie'),\n // sec-purpose is added by browsers automatically when using link/prefetch or Speculation Rules\n purpose: headers.get('sec-purpose') || headers.get('purpose'),\n };\n}\n\n/**\n * Minimal storefront interface needed for proxy functionality.\n * The full Storefront type is defined in @shopify/hydrogen.\n */\ntype StorefrontForProxy = {\n isStorefrontApiUrl: (request: {url?: string}) => boolean;\n forward: (request: Request) => Promise<Response>;\n setCollectedSubrequestHeaders: (response: {headers: Headers}) => void;\n};\n"]}
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "@shopify:registry": "https://registry.npmjs.org"
6
6
  },
7
7
  "type": "module",
8
- "version": "2.0.11",
8
+ "version": "2.1.0",
9
9
  "license": "MIT",
10
10
  "main": "dist/index.cjs",
11
11
  "module": "dist/production/index.js",
@@ -45,11 +45,11 @@
45
45
  "dist"
46
46
  ],
47
47
  "devDependencies": {
48
- "@remix-run/server-runtime": "^2.15.3",
49
- "@shopify/oxygen-workers-types": "^4.1.2"
48
+ "@remix-run/server-runtime": "^2.16.1",
49
+ "@shopify/oxygen-workers-types": "^4.1.6"
50
50
  },
51
51
  "peerDependencies": {
52
- "@remix-run/server-runtime": "^2.1.0",
52
+ "@remix-run/server-runtime": "^2.16.1",
53
53
  "@shopify/oxygen-workers-types": "^3.17.3 || ^4.1.2"
54
54
  }
55
55
  }