@decocms/apps-wake 8.1.0-next.0 → 8.1.0-next.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/package.json +7 -30
- package/src/index.ts +1 -61
- package/src/utils/gql.ts +1 -2
- package/src/__tests__/wakeClient.test.ts +0 -136
- package/src/actions/cart/addCoupon.ts +0 -43
- package/src/actions/cart/addItem.ts +0 -10
- package/src/actions/cart/addItems.ts +0 -50
- package/src/actions/cart/addKit.ts +0 -53
- package/src/actions/cart/partnerAssociate.ts +0 -48
- package/src/actions/cart/partnerDisassociate.ts +0 -43
- package/src/actions/cart/removeCoupon.ts +0 -39
- package/src/actions/cart/removeKit.ts +0 -56
- package/src/actions/cart/updateItemQuantity.ts +0 -69
- package/src/actions/newsletter/register.ts +0 -32
- package/src/actions/notifyme.ts +0 -28
- package/src/actions/review/create.ts +0 -30
- package/src/actions/shippingSimulation.ts +0 -60
- package/src/actions/submmitForm.ts +0 -25
- package/src/actions/wishlist/addProduct.ts +0 -43
- package/src/actions/wishlist/removeProduct.ts +0 -45
- package/src/client.ts +0 -123
- package/src/commerceLoaders.ts +0 -137
- package/src/handlers/sitemap.ts +0 -61
- package/src/loaders/cart.ts +0 -46
- package/src/loaders/partners.ts +0 -58
- package/src/loaders/productDetailsPage.ts +0 -174
- package/src/loaders/productList.ts +0 -262
- package/src/loaders/productListingPage.ts +0 -333
- package/src/loaders/proxy.ts +0 -79
- package/src/loaders/recommendations.ts +0 -84
- package/src/loaders/shop.ts +0 -37
- package/src/loaders/suggestion.ts +0 -67
- package/src/loaders/user.ts +0 -48
- package/src/loaders/wishlist.ts +0 -40
- package/src/manifest.gen.ts +0 -69
- package/src/mod.ts +0 -88
- package/src/registry.ts +0 -9
- package/src/utils/authError.ts +0 -107
- package/src/utils/authenticate.ts +0 -42
- package/src/utils/capitalize.ts +0 -9
- package/src/utils/cart.ts +0 -33
- package/src/utils/cookies.ts +0 -82
- package/src/utils/getVariations.ts +0 -47
- package/src/utils/graphql.ts +0 -72
- package/src/utils/httpError.ts +0 -15
- package/src/utils/instrumentedFetch.ts +0 -46
- package/src/utils/operationRouter.ts +0 -51
- package/src/utils/parseHeaders.ts +0 -19
- package/src/utils/partner.ts +0 -34
- package/src/utils/requestCtx.ts +0 -41
- package/src/utils/transform.ts +0 -460
- package/src/utils/types.ts +0 -6
- package/src/utils/user.ts +0 -9
- package/tsconfig.json +0 -7
package/src/manifest.gen.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
// AUTO-GENERATED-STYLE manifest — mirrors scripts/generate-manifests.ts output.
|
|
2
|
-
// Lists every loader/action module keyed by its CMS resolve path.
|
|
3
|
-
|
|
4
|
-
import * as actions_cart_addCoupon from "./actions/cart/addCoupon";
|
|
5
|
-
import * as actions_cart_addItem from "./actions/cart/addItem";
|
|
6
|
-
import * as actions_cart_addItems from "./actions/cart/addItems";
|
|
7
|
-
import * as actions_cart_addKit from "./actions/cart/addKit";
|
|
8
|
-
import * as actions_cart_partnerAssociate from "./actions/cart/partnerAssociate";
|
|
9
|
-
import * as actions_cart_partnerDisassociate from "./actions/cart/partnerDisassociate";
|
|
10
|
-
import * as actions_cart_removeCoupon from "./actions/cart/removeCoupon";
|
|
11
|
-
import * as actions_cart_removeKit from "./actions/cart/removeKit";
|
|
12
|
-
import * as actions_cart_updateItemQuantity from "./actions/cart/updateItemQuantity";
|
|
13
|
-
import * as actions_newsletter_register from "./actions/newsletter/register";
|
|
14
|
-
import * as actions_notifyme from "./actions/notifyme";
|
|
15
|
-
import * as actions_review_create from "./actions/review/create";
|
|
16
|
-
import * as actions_shippingSimulation from "./actions/shippingSimulation";
|
|
17
|
-
import * as actions_submmitForm from "./actions/submmitForm";
|
|
18
|
-
import * as actions_wishlist_addProduct from "./actions/wishlist/addProduct";
|
|
19
|
-
import * as actions_wishlist_removeProduct from "./actions/wishlist/removeProduct";
|
|
20
|
-
import * as loaders_cart from "./loaders/cart";
|
|
21
|
-
import * as loaders_partners from "./loaders/partners";
|
|
22
|
-
import * as loaders_productDetailsPage from "./loaders/productDetailsPage";
|
|
23
|
-
import * as loaders_productList from "./loaders/productList";
|
|
24
|
-
import * as loaders_productListingPage from "./loaders/productListingPage";
|
|
25
|
-
import * as loaders_proxy from "./loaders/proxy";
|
|
26
|
-
import * as loaders_recommendations from "./loaders/recommendations";
|
|
27
|
-
import * as loaders_shop from "./loaders/shop";
|
|
28
|
-
import * as loaders_suggestion from "./loaders/suggestion";
|
|
29
|
-
import * as loaders_user from "./loaders/user";
|
|
30
|
-
import * as loaders_wishlist from "./loaders/wishlist";
|
|
31
|
-
|
|
32
|
-
const manifest = {
|
|
33
|
-
name: "wake",
|
|
34
|
-
loaders: {
|
|
35
|
-
"wake/loaders/cart": loaders_cart,
|
|
36
|
-
"wake/loaders/partners": loaders_partners,
|
|
37
|
-
"wake/loaders/productDetailsPage": loaders_productDetailsPage,
|
|
38
|
-
"wake/loaders/productList": loaders_productList,
|
|
39
|
-
"wake/loaders/productListingPage": loaders_productListingPage,
|
|
40
|
-
"wake/loaders/proxy": loaders_proxy,
|
|
41
|
-
"wake/loaders/recommendations": loaders_recommendations,
|
|
42
|
-
"wake/loaders/shop": loaders_shop,
|
|
43
|
-
"wake/loaders/suggestion": loaders_suggestion,
|
|
44
|
-
"wake/loaders/user": loaders_user,
|
|
45
|
-
"wake/loaders/wishlist": loaders_wishlist,
|
|
46
|
-
},
|
|
47
|
-
actions: {
|
|
48
|
-
"wake/actions/cart/addCoupon": actions_cart_addCoupon,
|
|
49
|
-
"wake/actions/cart/addItem": actions_cart_addItem,
|
|
50
|
-
"wake/actions/cart/addItems": actions_cart_addItems,
|
|
51
|
-
"wake/actions/cart/addKit": actions_cart_addKit,
|
|
52
|
-
"wake/actions/cart/partnerAssociate": actions_cart_partnerAssociate,
|
|
53
|
-
"wake/actions/cart/partnerDisassociate": actions_cart_partnerDisassociate,
|
|
54
|
-
"wake/actions/cart/removeCoupon": actions_cart_removeCoupon,
|
|
55
|
-
"wake/actions/cart/removeKit": actions_cart_removeKit,
|
|
56
|
-
"wake/actions/cart/updateItemQuantity": actions_cart_updateItemQuantity,
|
|
57
|
-
"wake/actions/newsletter/register": actions_newsletter_register,
|
|
58
|
-
"wake/actions/notifyme": actions_notifyme,
|
|
59
|
-
"wake/actions/review/create": actions_review_create,
|
|
60
|
-
"wake/actions/shippingSimulation": actions_shippingSimulation,
|
|
61
|
-
"wake/actions/submmitForm": actions_submmitForm,
|
|
62
|
-
"wake/actions/wishlist/addProduct": actions_wishlist_addProduct,
|
|
63
|
-
"wake/actions/wishlist/removeProduct": actions_wishlist_removeProduct,
|
|
64
|
-
},
|
|
65
|
-
sections: {},
|
|
66
|
-
} as const;
|
|
67
|
-
|
|
68
|
-
export type Manifest = typeof manifest;
|
|
69
|
-
export default manifest;
|
package/src/mod.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wake app module — standard autoconfig contract.
|
|
3
|
-
*
|
|
4
|
-
* Exports `configure` following the AppModContract pattern; the framework's
|
|
5
|
-
* `autoconfigApps()` calls it generically. Ported from the deco-cx/apps Deno
|
|
6
|
-
* Wake integration.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { AppDefinition, AppHandler, ResolveSecretFn } from "@decocms/apps-commerce/app-types";
|
|
10
|
-
import {
|
|
11
|
-
configureWake,
|
|
12
|
-
getWakeAdminTokenFromEnv,
|
|
13
|
-
getWakeStorefrontTokenFromEnv,
|
|
14
|
-
type WakeConfig,
|
|
15
|
-
} from "./client";
|
|
16
|
-
import Sitemap from "./handlers/sitemap";
|
|
17
|
-
import manifest from "./manifest.gen";
|
|
18
|
-
|
|
19
|
-
/** @title Wake */
|
|
20
|
-
export interface Props {
|
|
21
|
-
/**
|
|
22
|
-
* @title Account Name
|
|
23
|
-
* @description erploja2 etc
|
|
24
|
-
*/
|
|
25
|
-
account: string;
|
|
26
|
-
/**
|
|
27
|
-
* @title Checkout Url
|
|
28
|
-
* @description https://secure.sprint55.com.br — checkout/login domain
|
|
29
|
-
*/
|
|
30
|
-
checkoutUrl: string;
|
|
31
|
-
/**
|
|
32
|
-
* @description Use Wake as backend platform
|
|
33
|
-
* @hide true
|
|
34
|
-
*/
|
|
35
|
-
platform?: "wake";
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface WakeState {
|
|
39
|
-
config: WakeConfig;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Configure the Wake app from CMS block data.
|
|
44
|
-
*
|
|
45
|
-
* The Wake tokens are NOT stored in the CMS block — they come only from
|
|
46
|
-
* environment variables (`WAKE_TOKEN` storefront, `WAKE_KEY` admin), so
|
|
47
|
-
* `resolveSecret` is intentionally unused. Returns null if the account or the
|
|
48
|
-
* storefront token env var is missing.
|
|
49
|
-
*/
|
|
50
|
-
export async function configure(
|
|
51
|
-
block: Record<string, unknown> | null | undefined,
|
|
52
|
-
_resolveSecret: ResolveSecretFn,
|
|
53
|
-
): Promise<AppDefinition<WakeState> | null> {
|
|
54
|
-
if (!block?.account) return null;
|
|
55
|
-
|
|
56
|
-
const storefrontToken = getWakeStorefrontTokenFromEnv();
|
|
57
|
-
if (!storefrontToken) return null;
|
|
58
|
-
|
|
59
|
-
const config: WakeConfig = {
|
|
60
|
-
account: block.account as string,
|
|
61
|
-
checkoutUrl: (block.checkoutUrl as string) ?? "",
|
|
62
|
-
storefrontToken,
|
|
63
|
-
storefrontEndpoint: block.storefrontEndpoint as string | undefined,
|
|
64
|
-
token: getWakeAdminTokenFromEnv(),
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Bridge: maintain global singleton for backward compat
|
|
68
|
-
configureWake(config);
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
name: "wake",
|
|
72
|
-
manifest,
|
|
73
|
-
state: { config },
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** HTTP handlers exposed by the app (not part of the loader/action manifest). */
|
|
78
|
-
export const handlers: Record<string, AppHandler> = {
|
|
79
|
-
"wake/handlers/sitemap": (props, request) => Sitemap(props)(request),
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
/** Placeholder preview for CMS editor — evolves when admin supports it. */
|
|
83
|
-
export const preview = undefined;
|
|
84
|
-
|
|
85
|
-
/** Default export for schema generation and Deno-style app bridges. */
|
|
86
|
-
export default function Wake(props: Props) {
|
|
87
|
-
return { state: props };
|
|
88
|
-
}
|
package/src/registry.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { AppRegistryEntry } from "@decocms/apps-commerce/registry";
|
|
2
|
-
|
|
3
|
-
export const WAKE_REGISTRY_ENTRY: AppRegistryEntry = {
|
|
4
|
-
blockKey: "deco-wake",
|
|
5
|
-
module: () => import("./mod"),
|
|
6
|
-
displayName: "Wake",
|
|
7
|
-
category: "commerce",
|
|
8
|
-
description: "Wake Commerce (Storefront API) integration",
|
|
9
|
-
};
|
package/src/utils/authError.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Best-effort dev detection without the deco runtime `context`.
|
|
3
|
-
* Treats anything other than an explicit production NODE_ENV as dev.
|
|
4
|
-
*/
|
|
5
|
-
const isDevEnv = (): boolean => {
|
|
6
|
-
const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process
|
|
7
|
-
?.env;
|
|
8
|
-
return (env?.NODE_ENV ?? "development") !== "production";
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Checks if an error is an authentication error and throws a user-friendly error
|
|
13
|
-
* with development guidance if in development mode.
|
|
14
|
-
*/
|
|
15
|
-
export function handleAuthError(error: unknown, operationContext: string = "operation"): never {
|
|
16
|
-
const errorObj = error as {
|
|
17
|
-
message?: string;
|
|
18
|
-
extensions?: { code?: string };
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
errorObj?.message?.includes("AUTH_NOT_AUTHENTICATED") ||
|
|
23
|
-
errorObj?.extensions?.code === "AUTH_NOT_AUTHENTICATED" ||
|
|
24
|
-
errorObj?.message?.includes("unauthorized")
|
|
25
|
-
) {
|
|
26
|
-
if (isDevEnv()) {
|
|
27
|
-
console.error(`
|
|
28
|
-
🔐 Wake API Authentication Error 🔐
|
|
29
|
-
|
|
30
|
-
Failed to ${operationContext} due to missing or invalid Wake API tokens.
|
|
31
|
-
|
|
32
|
-
This error occurs when:
|
|
33
|
-
- Wake Storefront Token is missing or invalid
|
|
34
|
-
- Wake API Token is missing or invalid
|
|
35
|
-
- Tokens are configured but expired
|
|
36
|
-
|
|
37
|
-
To fix (Development):
|
|
38
|
-
1. Create a .env file in your project root
|
|
39
|
-
2. Add these environment variables:
|
|
40
|
-
WAKE_TOKEN=your_storefront_token_here
|
|
41
|
-
WAKE_KEY=your_api_token_here
|
|
42
|
-
|
|
43
|
-
To fix (Production):
|
|
44
|
-
1. Go to deco.cx admin
|
|
45
|
-
2. Find the Wake app configuration
|
|
46
|
-
3. Set up secrets for Storefront Token and API Token
|
|
47
|
-
|
|
48
|
-
For help: https://wakecommerce.readme.io/docs/storefront-api-criacao-e-autenticacao-do-token
|
|
49
|
-
`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Throw a more user-friendly error for the frontend
|
|
53
|
-
const authError = new Error(
|
|
54
|
-
`Authentication failed (while trying to ${operationContext}) — Wake API tokens are missing or invalid`,
|
|
55
|
-
);
|
|
56
|
-
authError.cause = error;
|
|
57
|
-
throw authError;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Re-throw if not an auth error
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Checks if an error is an authentication error without throwing.
|
|
66
|
-
*/
|
|
67
|
-
export function isAuthError(error: unknown): boolean {
|
|
68
|
-
const errorObj = error as {
|
|
69
|
-
message?: string;
|
|
70
|
-
extensions?: { code?: string };
|
|
71
|
-
status?: number;
|
|
72
|
-
response?: { status?: number };
|
|
73
|
-
errors?: Array<{ message?: string; extensions?: { code?: string } }>;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// Normalize text for case-insensitive comparison
|
|
77
|
-
const msg = (errorObj?.message ?? "").toLowerCase();
|
|
78
|
-
const code = (errorObj?.extensions?.code ?? "").toUpperCase();
|
|
79
|
-
|
|
80
|
-
// Check HTTP status codes
|
|
81
|
-
const httpStatus = errorObj?.status ?? errorObj?.response?.status;
|
|
82
|
-
|
|
83
|
-
// Check for aggregate errors (multiple errors in an array)
|
|
84
|
-
const isAggregate =
|
|
85
|
-
Array.isArray(errorObj?.errors) &&
|
|
86
|
-
errorObj.errors.some(
|
|
87
|
-
(e) =>
|
|
88
|
-
String(e?.message ?? "")
|
|
89
|
-
.toLowerCase()
|
|
90
|
-
.includes("unauthorized") ||
|
|
91
|
-
String(e?.extensions?.code ?? "").toUpperCase() === "UNAUTHENTICATED" ||
|
|
92
|
-
String(e?.extensions?.code ?? "").toUpperCase() === "AUTH_NOT_AUTHENTICATED" ||
|
|
93
|
-
String(e?.extensions?.code ?? "").toUpperCase() === "FORBIDDEN",
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
return !!(
|
|
97
|
-
msg.includes("auth_not_authenticated") ||
|
|
98
|
-
msg.includes("unauthorized") ||
|
|
99
|
-
msg.includes("forbidden") ||
|
|
100
|
-
code === "AUTH_NOT_AUTHENTICATED" ||
|
|
101
|
-
code === "UNAUTHENTICATED" ||
|
|
102
|
-
code === "FORBIDDEN" ||
|
|
103
|
-
httpStatus === 401 ||
|
|
104
|
-
httpStatus === 403 ||
|
|
105
|
-
isAggregate
|
|
106
|
-
);
|
|
107
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { withFetchTimeout } from "@decocms/blocks/sdk/fetchTimeout";
|
|
2
|
-
import { RequestContext } from "@decocms/blocks/sdk/requestContext";
|
|
3
|
-
import { getCheckoutUrl } from "../client";
|
|
4
|
-
import type { UserAuthenticate } from "./client";
|
|
5
|
-
import { getUserCookie } from "./user";
|
|
6
|
-
|
|
7
|
-
const fetchSafe = withFetchTimeout();
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Exchange the `fbits-login` cookie for a Wake `CustomerAccessToken` via the
|
|
11
|
-
* checkout REST endpoint `GET /api/Login/Get`, forwarding the incoming request
|
|
12
|
-
* headers (cookies) so Wake can resolve the logged-in customer.
|
|
13
|
-
*
|
|
14
|
-
* Returns `null` when there is no login cookie or the exchange fails.
|
|
15
|
-
*/
|
|
16
|
-
const authenticate = async (): Promise<string | null> => {
|
|
17
|
-
const req = RequestContext.current?.request;
|
|
18
|
-
if (!req) return null;
|
|
19
|
-
|
|
20
|
-
const loginCookie = getUserCookie(req.headers);
|
|
21
|
-
if (!loginCookie) return null;
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
// Forward only the Cookie header (carries `fbits-login`) rather than the
|
|
25
|
-
// whole incoming header set — the checkout endpoint only needs the session
|
|
26
|
-
// cookie to resolve the customer.
|
|
27
|
-
const forwardHeaders = new Headers();
|
|
28
|
-
const cookie = req.headers.get("cookie");
|
|
29
|
-
if (cookie) forwardHeaders.set("cookie", cookie);
|
|
30
|
-
|
|
31
|
-
const response = await fetchSafe(new URL("/api/Login/Get", getCheckoutUrl()).href, {
|
|
32
|
-
headers: forwardHeaders,
|
|
33
|
-
});
|
|
34
|
-
if (!response.ok) return null;
|
|
35
|
-
const data = (await response.json()) as UserAuthenticate | null;
|
|
36
|
-
return data?.CustomerAccessToken ?? null;
|
|
37
|
-
} catch {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export default authenticate;
|
package/src/utils/capitalize.ts
DELETED
package/src/utils/cart.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { getCookies, setCookie } from "./cookies";
|
|
2
|
-
import { isSecureRequest } from "./requestCtx";
|
|
3
|
-
|
|
4
|
-
const CART_COOKIE = "carrinho-id";
|
|
5
|
-
|
|
6
|
-
const TEN_DAYS_MS = 10 * 24 * 3600 * 1_000;
|
|
7
|
-
|
|
8
|
-
export const getCartCookie = (headers: Headers): string | undefined => {
|
|
9
|
-
const cookies = getCookies(headers);
|
|
10
|
-
|
|
11
|
-
return cookies[CART_COOKIE];
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const setCartCookie = (headers: Headers, cartId: string) =>
|
|
15
|
-
setCookie(headers, {
|
|
16
|
-
name: CART_COOKIE,
|
|
17
|
-
value: cartId,
|
|
18
|
-
path: "/",
|
|
19
|
-
expires: new Date(Date.now() + TEN_DAYS_MS),
|
|
20
|
-
// Not HttpOnly: the client mirrors this cookie via `setClientCookie`.
|
|
21
|
-
// Secure only on HTTPS — a Secure cookie is dropped over http:// (localhost dev).
|
|
22
|
-
secure: isSecureRequest(),
|
|
23
|
-
sameSite: "Lax",
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
/** Browser-only: mirror the cart id into a client-readable cookie. */
|
|
27
|
-
export const setClientCookie = (value: string) => {
|
|
28
|
-
const date = new Date(Date.now() + TEN_DAYS_MS);
|
|
29
|
-
const expires = `; expires=${date.toUTCString()}`;
|
|
30
|
-
|
|
31
|
-
// biome-ignore lint/suspicious/noDocumentCookie: browser-only mirror of the cart id for client reads
|
|
32
|
-
document.cookie = `${CART_COOKIE}=${value || ""}${expires}; path=/`;
|
|
33
|
-
};
|
package/src/utils/cookies.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Node.js-compatible cookie helpers.
|
|
3
|
-
* Replaces Deno's std/http/cookie.ts.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export function getCookies(headers: Headers): Record<string, string> {
|
|
7
|
-
const cookieHeader = headers.get("cookie") || "";
|
|
8
|
-
const cookies: Record<string, string> = {};
|
|
9
|
-
for (const pair of cookieHeader.split(";")) {
|
|
10
|
-
const [key, ...rest] = pair.trim().split("=");
|
|
11
|
-
if (key) {
|
|
12
|
-
cookies[key.trim()] = decodeURIComponent(rest.join("=").trim());
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return cookies;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function setCookie(
|
|
19
|
-
headers: Headers,
|
|
20
|
-
options: {
|
|
21
|
-
name: string;
|
|
22
|
-
value: string;
|
|
23
|
-
path?: string;
|
|
24
|
-
expires?: Date;
|
|
25
|
-
maxAge?: number;
|
|
26
|
-
httpOnly?: boolean;
|
|
27
|
-
secure?: boolean;
|
|
28
|
-
sameSite?: "Strict" | "Lax" | "None";
|
|
29
|
-
},
|
|
30
|
-
) {
|
|
31
|
-
const parts = [`${options.name}=${encodeURIComponent(options.value)}`];
|
|
32
|
-
if (options.path) parts.push(`Path=${options.path}`);
|
|
33
|
-
if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
34
|
-
if (options.maxAge !== undefined) parts.push(`Max-Age=${options.maxAge}`);
|
|
35
|
-
if (options.httpOnly) parts.push("HttpOnly");
|
|
36
|
-
if (options.secure) parts.push("Secure");
|
|
37
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
38
|
-
|
|
39
|
-
headers.append("Set-Cookie", parts.join("; "));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface Cookie {
|
|
43
|
-
name: string;
|
|
44
|
-
value: string;
|
|
45
|
-
path?: string;
|
|
46
|
-
expires?: Date;
|
|
47
|
-
maxAge?: number;
|
|
48
|
-
httpOnly?: boolean;
|
|
49
|
-
secure?: boolean;
|
|
50
|
-
sameSite?: "Strict" | "Lax" | "None";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function getSetCookies(headers: Headers): Cookie[] {
|
|
54
|
-
const cookies: Cookie[] = [];
|
|
55
|
-
const setCookieHeaders = headers.getSetCookie?.() ?? [];
|
|
56
|
-
for (const header of setCookieHeaders) {
|
|
57
|
-
const parts = header.split(";").map((p) => p.trim());
|
|
58
|
-
const [nameValue, ...attrs] = parts;
|
|
59
|
-
const [name, ...rest] = nameValue.split("=");
|
|
60
|
-
const value = rest.join("=");
|
|
61
|
-
const cookie: Cookie = { name: name.trim(), value };
|
|
62
|
-
for (const attr of attrs) {
|
|
63
|
-
const [k, v] = attr.split("=");
|
|
64
|
-
const key = k.trim().toLowerCase();
|
|
65
|
-
if (key === "path") cookie.path = v?.trim();
|
|
66
|
-
if (key === "httponly") cookie.httpOnly = true;
|
|
67
|
-
if (key === "secure") cookie.secure = true;
|
|
68
|
-
if (key === "samesite") cookie.sameSite = v?.trim() as any;
|
|
69
|
-
if (key === "max-age") cookie.maxAge = Number(v?.trim());
|
|
70
|
-
}
|
|
71
|
-
cookies.push(cookie);
|
|
72
|
-
}
|
|
73
|
-
return cookies;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function debounce<T extends (...args: any[]) => void>(fn: T, delay: number): T {
|
|
77
|
-
let timer: ReturnType<typeof setTimeout>;
|
|
78
|
-
return ((...args: any[]) => {
|
|
79
|
-
clearTimeout(timer);
|
|
80
|
-
timer = setTimeout(() => fn(...args), delay);
|
|
81
|
-
}) as T;
|
|
82
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { getWakeClient } from "../client";
|
|
2
|
-
import { GetProducts } from "./graphql/queries";
|
|
3
|
-
import type {
|
|
4
|
-
GetProductsQuery,
|
|
5
|
-
GetProductsQueryVariables,
|
|
6
|
-
ProductFragment,
|
|
7
|
-
} from "./graphql/storefront.graphql.gen";
|
|
8
|
-
import { toProduct } from "./transform";
|
|
9
|
-
|
|
10
|
-
export const MAXIMUM_REQUEST_QUANTITY = 50;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Fetch every variant for the given product ids (paginating the storefront
|
|
14
|
-
* `products` query) and map them to canonical commerce `Product`s.
|
|
15
|
-
*/
|
|
16
|
-
export const getVariations = async (
|
|
17
|
-
productIds: number[],
|
|
18
|
-
base: URL,
|
|
19
|
-
extraHeaders?: Record<string, string>,
|
|
20
|
-
) => {
|
|
21
|
-
const storefront = getWakeClient();
|
|
22
|
-
const variations: ProductFragment[] = [];
|
|
23
|
-
|
|
24
|
-
const fetchData = async (cursor?: string) => {
|
|
25
|
-
const variables: GetProductsQueryVariables = {
|
|
26
|
-
first: MAXIMUM_REQUEST_QUANTITY,
|
|
27
|
-
filters: { productId: productIds },
|
|
28
|
-
sortDirection: "ASC",
|
|
29
|
-
sortKey: "RANDOM",
|
|
30
|
-
after: cursor,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const data = await storefront.query<GetProductsQuery>(GetProducts, variables, extraHeaders);
|
|
34
|
-
|
|
35
|
-
if (data.products?.nodes?.length) {
|
|
36
|
-
variations.push(...data.products.nodes.filter((v): v is ProductFragment => Boolean(v)));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (data.products?.pageInfo.hasNextPage && data.products.pageInfo.endCursor) {
|
|
40
|
-
await fetchData(data.products.pageInfo.endCursor);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
await fetchData();
|
|
45
|
-
|
|
46
|
-
return variations.map((i) => toProduct(i, { base }));
|
|
47
|
-
};
|
package/src/utils/graphql.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { type FetchFn, withFetchTimeout } from "@decocms/blocks/sdk/fetchTimeout";
|
|
2
|
-
import type { InstrumentedFetchInit } from "@decocms/blocks/sdk/instrumentedFetch";
|
|
3
|
-
import { buildQuery, type QueryDefinition } from "./gql";
|
|
4
|
-
import { extractGraphqlOperationName } from "./graphqlOperationName";
|
|
5
|
-
|
|
6
|
-
export { buildQuery, gql, type QueryDefinition } from "./gql";
|
|
7
|
-
|
|
8
|
-
export interface GraphQLClient {
|
|
9
|
-
/**
|
|
10
|
-
* Run a GraphQL operation. `extraHeaders` is merged over the client's
|
|
11
|
-
* base headers for the single call — Wake loaders use it to forward the
|
|
12
|
-
* client IP (`X-Forwarded-For`) via `parseHeaders(...)`.
|
|
13
|
-
*/
|
|
14
|
-
query<T>(
|
|
15
|
-
query: string | QueryDefinition,
|
|
16
|
-
variables?: Record<string, unknown>,
|
|
17
|
-
extraHeaders?: Record<string, string>,
|
|
18
|
-
): Promise<T>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function createGraphqlClient(
|
|
22
|
-
endpoint: string,
|
|
23
|
-
headers: Record<string, string>,
|
|
24
|
-
fetchFn?: FetchFn,
|
|
25
|
-
): GraphQLClient {
|
|
26
|
-
const _fetch = fetchFn ?? withFetchTimeout();
|
|
27
|
-
return {
|
|
28
|
-
async query<T>(
|
|
29
|
-
queryOrDef: string | QueryDefinition,
|
|
30
|
-
variables?: Record<string, unknown>,
|
|
31
|
-
extraHeaders?: Record<string, string>,
|
|
32
|
-
): Promise<T> {
|
|
33
|
-
const query = typeof queryOrDef === "string" ? queryOrDef : buildQuery(queryOrDef);
|
|
34
|
-
|
|
35
|
-
// Stamp the GraphQL operation as init.operation so the framework's
|
|
36
|
-
// span name becomes `wake.<OperationName>` instead of the generic
|
|
37
|
-
// `wake.storefront.graphql` from the URL router. The extra field is
|
|
38
|
-
// silently dropped by plain `fetch` and read by any `InstrumentedFetch`
|
|
39
|
-
// configured via `setWakeFetch`.
|
|
40
|
-
const operation = extractGraphqlOperationName(query);
|
|
41
|
-
const init: InstrumentedFetchInit = {
|
|
42
|
-
method: "POST",
|
|
43
|
-
headers: {
|
|
44
|
-
// Per-call `extraHeaders` (e.g. forwarded client IP) come first so
|
|
45
|
-
// they can never override the app's auth token or Content-Type.
|
|
46
|
-
...extraHeaders,
|
|
47
|
-
"Content-Type": "application/json",
|
|
48
|
-
...headers,
|
|
49
|
-
},
|
|
50
|
-
body: JSON.stringify({ query, variables }),
|
|
51
|
-
...(operation ? { operation } : {}),
|
|
52
|
-
};
|
|
53
|
-
const response = await _fetch(endpoint, init);
|
|
54
|
-
|
|
55
|
-
if (!response.ok) {
|
|
56
|
-
throw new Error(`Wake GraphQL error: ${response.status} ${response.statusText}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const json = (await response.json()) as { data?: T; errors?: Array<{ message: string }> };
|
|
60
|
-
|
|
61
|
-
if (json.errors?.length) {
|
|
62
|
-
throw new Error(`Wake GraphQL errors: ${json.errors.map((e) => e.message).join(", ")}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (json.data === undefined) {
|
|
66
|
-
throw new Error("Wake GraphQL response missing data");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return json.data;
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
}
|
package/src/utils/httpError.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal HTTP error carrying a status code.
|
|
3
|
-
*
|
|
4
|
-
* The Deno Wake app threw `HttpError` from `deco/utils/http.ts`; in this
|
|
5
|
-
* architecture actions just throw `Error`, so this local class preserves the
|
|
6
|
-
* `.status` field that some callers/handlers inspect.
|
|
7
|
-
*/
|
|
8
|
-
export class HttpError extends Error {
|
|
9
|
-
status: number;
|
|
10
|
-
constructor(status: number, message?: string) {
|
|
11
|
-
super(message ?? `HTTP ${status}`);
|
|
12
|
-
this.name = "HttpError";
|
|
13
|
-
this.status = status;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pre-wired instrumented fetch factory for Wake. Mirrors
|
|
3
|
-
* `apps-shopify/utils/instrumentedFetch.ts`.
|
|
4
|
-
*
|
|
5
|
-
* Bundles `createInstrumentedFetch` (spans, traceparent, URL redaction), the
|
|
6
|
-
* `wakeOperationRouter` URL fallback, and an `onComplete` that records the
|
|
7
|
-
* canonical `http.client.request.duration` histogram with `provider: "wake"`.
|
|
8
|
-
*
|
|
9
|
-
* Sites do:
|
|
10
|
-
* ```ts
|
|
11
|
-
* import { setWakeFetch, createWakeFetch } from "@decocms/apps-wake";
|
|
12
|
-
* setWakeFetch(createWakeFetch());
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type { FetchFn } from "@decocms/blocks/sdk/fetchTimeout";
|
|
17
|
-
import {
|
|
18
|
-
createInstrumentedFetch,
|
|
19
|
-
type InstrumentedFetch,
|
|
20
|
-
} from "@decocms/blocks/sdk/instrumentedFetch";
|
|
21
|
-
import { recordCommerceMetric } from "@decocms/blocks/sdk/observability";
|
|
22
|
-
import { wakeOperationRouter } from "./operationRouter";
|
|
23
|
-
|
|
24
|
-
export interface CreateWakeFetchOptions {
|
|
25
|
-
baseFetch?: FetchFn;
|
|
26
|
-
disableHistogram?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function createWakeFetch(options: CreateWakeFetchOptions = {}): InstrumentedFetch {
|
|
30
|
-
const { baseFetch, disableHistogram = false } = options;
|
|
31
|
-
return createInstrumentedFetch({
|
|
32
|
-
name: "wake",
|
|
33
|
-
baseFetch,
|
|
34
|
-
resolveOperation: wakeOperationRouter,
|
|
35
|
-
onComplete: disableHistogram
|
|
36
|
-
? undefined
|
|
37
|
-
: ({ operation, status, durationMs, cached }) => {
|
|
38
|
-
recordCommerceMetric(durationMs, {
|
|
39
|
-
provider: "wake",
|
|
40
|
-
operation,
|
|
41
|
-
status_class: `${Math.floor(status / 100)}xx`,
|
|
42
|
-
cached,
|
|
43
|
-
});
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* URL-derived operation name router for Wake API calls.
|
|
3
|
-
*
|
|
4
|
-
* Plugged into `@decocms/blocks`'s `createInstrumentedFetch` via the
|
|
5
|
-
* `resolveOperation(url, method)` option. Wake's API surface here is the
|
|
6
|
-
* GraphQL storefront (`/graphql`) plus a couple of checkout REST endpoints.
|
|
7
|
-
* The semantic GraphQL operation name is extracted from the document itself
|
|
8
|
-
* (see `./graphqlOperationName.ts`) and stamped as `init.operation`, which
|
|
9
|
-
* always wins over this router; this router is the fallback.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
type OperationResolver = string | ((match: RegExpMatchArray, method: string) => string);
|
|
13
|
-
|
|
14
|
-
interface Matcher {
|
|
15
|
-
pattern: RegExp;
|
|
16
|
-
operation: OperationResolver;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const m = (pattern: RegExp, operation: OperationResolver): Matcher => ({ pattern, operation });
|
|
20
|
-
|
|
21
|
-
const MATCHERS: ReadonlyArray<Matcher> = [
|
|
22
|
-
m(/^\/graphql/, "storefront.graphql"),
|
|
23
|
-
m(/^\/api\/Login\/Get/, "checkout.login"),
|
|
24
|
-
m(/^\/api\/carrinho/, "checkout.cart"),
|
|
25
|
-
m(/^\/api\//, "checkout.api"),
|
|
26
|
-
m(/^\/Sitemap\.xml/i, "sitemap"),
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Resolve an operation name for a Wake URL. Returns `undefined` if no matcher
|
|
31
|
-
* fires, which causes the framework to fall back to `wake.fetch`.
|
|
32
|
-
*/
|
|
33
|
-
export function wakeOperationRouter(url: string, method: string): string | undefined {
|
|
34
|
-
let pathname: string;
|
|
35
|
-
try {
|
|
36
|
-
pathname = new URL(url).pathname;
|
|
37
|
-
} catch {
|
|
38
|
-
const qs = url.indexOf("?");
|
|
39
|
-
const hash = url.indexOf("#");
|
|
40
|
-
const end = [qs, hash].filter((i) => i >= 0).sort((a, b) => a - b)[0];
|
|
41
|
-
pathname = end === undefined ? url : url.slice(0, end);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const upperMethod = method.toUpperCase();
|
|
45
|
-
for (const { pattern, operation } of MATCHERS) {
|
|
46
|
-
const match = pathname.match(pattern);
|
|
47
|
-
if (!match) continue;
|
|
48
|
-
return typeof operation === "function" ? operation(match, upperMethod) : operation;
|
|
49
|
-
}
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|