@replohq/sdk 1.17.1 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_vendor/schemas/routing/locale.d.ts +1 -1
- package/integrations/mailchimp.d.ts +65 -0
- package/integrations/mailchimp.js +133 -0
- package/lib/buildMetadata.js +3 -3
- package/next/open-next.js +1 -1
- package/package.json +8 -4
- package/providers/canopy-client-config.d.ts +14 -0
- package/providers/canopy-client-config.js +17 -0
- package/providers/replo-provider.js +12 -4
|
@@ -25,8 +25,8 @@ export declare const localeRoutingConfigSchema: z.ZodObject<{
|
|
|
25
25
|
language: "language";
|
|
26
26
|
}>>;
|
|
27
27
|
urlMode: z.ZodDefault<z.ZodEnum<{
|
|
28
|
-
prefixed: "prefixed";
|
|
29
28
|
hidden: "hidden";
|
|
29
|
+
prefixed: "prefixed";
|
|
30
30
|
}>>;
|
|
31
31
|
domains: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
32
32
|
locales: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { CanopyError } from "../lib/canopy-error";
|
|
4
|
+
declare const mailchimpSubscribeErrorCodeSchema: z.ZodEnum<{
|
|
5
|
+
notConnected: "notConnected";
|
|
6
|
+
listNotFound: "listNotFound";
|
|
7
|
+
invalidEmail: "invalidEmail";
|
|
8
|
+
invalidRequest: "invalidRequest";
|
|
9
|
+
emailNotSubscribable: "emailNotSubscribable";
|
|
10
|
+
mergeFieldsInvalid: "mergeFieldsInvalid";
|
|
11
|
+
rateLimited: "rateLimited";
|
|
12
|
+
upstreamError: "upstreamError";
|
|
13
|
+
}>;
|
|
14
|
+
/** `pending` means Mailchimp emailed a confirmation link the visitor must click. */
|
|
15
|
+
export type MailchimpSubscribeStatus = "subscribed" | "pending";
|
|
16
|
+
export type MailchimpSubscribeErrorCode = z.infer<typeof mailchimpSubscribeErrorCodeSchema>;
|
|
17
|
+
export declare class MailchimpSubscribeError extends CanopyError {
|
|
18
|
+
readonly code: MailchimpSubscribeErrorCode;
|
|
19
|
+
constructor({ code, message, cause, }: {
|
|
20
|
+
code: MailchimpSubscribeErrorCode;
|
|
21
|
+
message: string;
|
|
22
|
+
cause: unknown;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Adds an opted-in email to a Mailchimp audience from a published Replo form.
|
|
27
|
+
* Call it in a client component under `ReploProvider`, render
|
|
28
|
+
* `<input {...honeypotFieldProps} />` inside the form, and submit with
|
|
29
|
+
* `subscribe.mutate({ email, mergeFields, interests })`. The project id comes
|
|
30
|
+
* from the site, not the caller.
|
|
31
|
+
*
|
|
32
|
+
* `tags` label everyone this form adds (e.g. `["waitlist-spring"]`), `event`
|
|
33
|
+
* records a custom event that can start a Mailchimp Customer Journey, and
|
|
34
|
+
* `interests` opts the visitor into audience groups by id. All three apply
|
|
35
|
+
* only to people new to the audience.
|
|
36
|
+
*
|
|
37
|
+
* `subscribe.error` is a `MailchimpSubscribeError`; branch on its `code`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function useMailchimpSubscribe({ listId, tags, event, }: {
|
|
40
|
+
listId: string;
|
|
41
|
+
tags: string[] | null;
|
|
42
|
+
event: string | null;
|
|
43
|
+
}): {
|
|
44
|
+
subscribe: import("@tanstack/react-query").UseMutationResult<MailchimpSubscribeStatus, MailchimpSubscribeError, {
|
|
45
|
+
email: string;
|
|
46
|
+
mergeFields: Record<string, string> | null;
|
|
47
|
+
interests: Record<string, boolean> | null;
|
|
48
|
+
}, unknown>;
|
|
49
|
+
honeypotFieldProps: {
|
|
50
|
+
name: string;
|
|
51
|
+
value: string;
|
|
52
|
+
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
53
|
+
tabIndex: number;
|
|
54
|
+
autoComplete: "off";
|
|
55
|
+
"aria-hidden": true;
|
|
56
|
+
style: {
|
|
57
|
+
position: "absolute";
|
|
58
|
+
left: string;
|
|
59
|
+
width: string;
|
|
60
|
+
height: string;
|
|
61
|
+
overflow: "hidden";
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { useMutation } from "@tanstack/react-query";
|
|
4
|
+
import { parseJsonOrNull } from "../_vendor/replo-utils/lib/json.mjs";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { CanopyError } from "../lib/canopy-error";
|
|
7
|
+
import { useCanopyClientConfig } from "../providers/canopy-client-config";
|
|
8
|
+
const mailchimpSubscribeErrorCodeSchema = z.enum([
|
|
9
|
+
"notConnected",
|
|
10
|
+
"listNotFound",
|
|
11
|
+
"invalidEmail",
|
|
12
|
+
"invalidRequest",
|
|
13
|
+
"emailNotSubscribable",
|
|
14
|
+
"mergeFieldsInvalid",
|
|
15
|
+
"rateLimited",
|
|
16
|
+
"upstreamError"
|
|
17
|
+
]);
|
|
18
|
+
const mailchimpSubscribeResponseSchema = z.discriminatedUnion("ok", [
|
|
19
|
+
z.object({
|
|
20
|
+
ok: z.literal(true),
|
|
21
|
+
status: z.enum(["subscribed", "pending"])
|
|
22
|
+
}),
|
|
23
|
+
z.object({
|
|
24
|
+
ok: z.literal(false),
|
|
25
|
+
error: z.object({
|
|
26
|
+
code: mailchimpSubscribeErrorCodeSchema,
|
|
27
|
+
message: z.string()
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
]);
|
|
31
|
+
class MailchimpSubscribeError extends CanopyError {
|
|
32
|
+
code;
|
|
33
|
+
constructor({
|
|
34
|
+
code,
|
|
35
|
+
message,
|
|
36
|
+
cause
|
|
37
|
+
}) {
|
|
38
|
+
super({ message, cause, additionalData: { code } });
|
|
39
|
+
this.code = code;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function postSubscribe({
|
|
43
|
+
config,
|
|
44
|
+
request
|
|
45
|
+
}) {
|
|
46
|
+
if (!config) {
|
|
47
|
+
throw new MailchimpSubscribeError({
|
|
48
|
+
code: "upstreamError",
|
|
49
|
+
message: "This site has no canopy-api config, so it cannot reach Mailchimp",
|
|
50
|
+
cause: null
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
let responseText;
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(
|
|
56
|
+
`${config.canopyApiBaseUrl}/api/v1/integrations/mailchimp/subscribe`,
|
|
57
|
+
{
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { "Content-Type": "application/json" },
|
|
60
|
+
body: JSON.stringify({ projectId: config.projectId, ...request })
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
responseText = await response.text();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new MailchimpSubscribeError({
|
|
66
|
+
code: "upstreamError",
|
|
67
|
+
message: "Could not reach the Mailchimp subscribe endpoint",
|
|
68
|
+
cause: error
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const parsed = mailchimpSubscribeResponseSchema.safeParse(
|
|
72
|
+
parseJsonOrNull(responseText)
|
|
73
|
+
);
|
|
74
|
+
if (!parsed.success) {
|
|
75
|
+
throw new MailchimpSubscribeError({
|
|
76
|
+
code: "upstreamError",
|
|
77
|
+
message: "The Mailchimp subscribe endpoint returned an unexpected response",
|
|
78
|
+
cause: parsed.error
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (!parsed.data.ok) {
|
|
82
|
+
throw new MailchimpSubscribeError({ ...parsed.data.error, cause: null });
|
|
83
|
+
}
|
|
84
|
+
return parsed.data.status;
|
|
85
|
+
}
|
|
86
|
+
function useMailchimpSubscribe({
|
|
87
|
+
listId,
|
|
88
|
+
tags,
|
|
89
|
+
event
|
|
90
|
+
}) {
|
|
91
|
+
const config = useCanopyClientConfig();
|
|
92
|
+
const [renderedAtMilliseconds] = React.useState(() => Date.now());
|
|
93
|
+
const [honeypot, setHoneypot] = React.useState("");
|
|
94
|
+
const subscribe = useMutation({
|
|
95
|
+
mutationFn: ({ email, mergeFields, interests }) => {
|
|
96
|
+
return postSubscribe({
|
|
97
|
+
config,
|
|
98
|
+
request: {
|
|
99
|
+
listId,
|
|
100
|
+
email,
|
|
101
|
+
mergeFields,
|
|
102
|
+
tags,
|
|
103
|
+
interests,
|
|
104
|
+
event,
|
|
105
|
+
honeypot,
|
|
106
|
+
elapsedMilliseconds: Date.now() - renderedAtMilliseconds
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
const honeypotFieldProps = {
|
|
112
|
+
name: "company_website",
|
|
113
|
+
value: honeypot,
|
|
114
|
+
onChange: (event2) => {
|
|
115
|
+
setHoneypot(event2.target.value);
|
|
116
|
+
},
|
|
117
|
+
tabIndex: -1,
|
|
118
|
+
autoComplete: "off",
|
|
119
|
+
"aria-hidden": true,
|
|
120
|
+
style: {
|
|
121
|
+
position: "absolute",
|
|
122
|
+
left: "-10000px",
|
|
123
|
+
width: "1px",
|
|
124
|
+
height: "1px",
|
|
125
|
+
overflow: "hidden"
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
return { subscribe, honeypotFieldProps };
|
|
129
|
+
}
|
|
130
|
+
export {
|
|
131
|
+
MailchimpSubscribeError,
|
|
132
|
+
useMailchimpSubscribe
|
|
133
|
+
};
|
package/lib/buildMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const BUILD_METADATA = {
|
|
2
2
|
"packageName": "@replohq/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-10-
|
|
5
|
+
"commit": "e3b248dbf12d7b2d1f401d36170686e094d40e3a",
|
|
6
|
+
"builtAt": "2026-10-14T22:19:29.297Z"
|
|
7
7
|
};
|
package/next/open-next.js
CHANGED
|
@@ -18,7 +18,7 @@ const openNextConfig = defineCloudflareConfig({
|
|
|
18
18
|
incrementalCache: withRegionalCache(r2IncrementalCache, {
|
|
19
19
|
mode: "short-lived"
|
|
20
20
|
}),
|
|
21
|
-
enableCacheInterception:
|
|
21
|
+
enableCacheInterception: true,
|
|
22
22
|
queue: backgroundDurableQueue,
|
|
23
23
|
tagCache: doShardedTagCache({
|
|
24
24
|
baseShardSize: 12,
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sdk",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.19.0",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-10-
|
|
8
|
+
"commit": "e3b248dbf12d7b2d1f401d36170686e094d40e3a",
|
|
9
|
+
"builtAt": "2026-10-14T22:19:29.297Z"
|
|
10
10
|
},
|
|
11
11
|
"description": "Replo SDK — cart, analytics, and data loaders for agent-built Next.js sites.",
|
|
12
12
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -77,6 +77,10 @@
|
|
|
77
77
|
"types": "./hooks/use-formatted-price.d.ts",
|
|
78
78
|
"default": "./hooks/use-formatted-price.js"
|
|
79
79
|
},
|
|
80
|
+
"./integrations/mailchimp": {
|
|
81
|
+
"types": "./integrations/mailchimp.d.ts",
|
|
82
|
+
"default": "./integrations/mailchimp.js"
|
|
83
|
+
},
|
|
80
84
|
"./lib/buildMetadata": {
|
|
81
85
|
"types": "./lib/buildMetadata.d.ts",
|
|
82
86
|
"default": "./lib/buildMetadata.js"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* What browser code needs to call canopy-api directly. Resolved from the worker
|
|
4
|
+
* env by `ReploProvider`; null when the env can't be read (e.g. outside the
|
|
5
|
+
* worker), in which case browser calls fail with a typed error.
|
|
6
|
+
*/
|
|
7
|
+
export type CanopyClientConfig = {
|
|
8
|
+
canopyApiBaseUrl: string;
|
|
9
|
+
projectId: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function CanopyClientConfigProvider({ config, children, }: React.PropsWithChildren<{
|
|
12
|
+
config: CanopyClientConfig | null;
|
|
13
|
+
}>): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export declare function useCanopyClientConfig(): CanopyClientConfig | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import React from "react";
|
|
4
|
+
const CanopyClientConfigContext = React.createContext(null);
|
|
5
|
+
function CanopyClientConfigProvider({
|
|
6
|
+
config,
|
|
7
|
+
children
|
|
8
|
+
}) {
|
|
9
|
+
return /* @__PURE__ */ jsx(CanopyClientConfigContext.Provider, { value: config, children });
|
|
10
|
+
}
|
|
11
|
+
function useCanopyClientConfig() {
|
|
12
|
+
return React.useContext(CanopyClientConfigContext);
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
CanopyClientConfigProvider,
|
|
16
|
+
useCanopyClientConfig
|
|
17
|
+
};
|
|
@@ -2,21 +2,29 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { AnalyticsProvider } from "../analytics/analytics-provider";
|
|
3
3
|
import { ReploFirstPartyPixel } from "../analytics/replo-first-party-pixel";
|
|
4
4
|
import { CartLoader } from "../cart/cart-loader";
|
|
5
|
-
import {
|
|
5
|
+
import { getEnv } from "../env";
|
|
6
|
+
import { buildBaseUrl } from "../lib/url-utils";
|
|
6
7
|
import { LoaderErrorDetailsProvider } from "../loaders/loader-error-boundary";
|
|
7
8
|
import { shouldShowLoaderErrorDetails } from "../loaders/loader-error-details";
|
|
9
|
+
import { CanopyClientConfigProvider } from "./canopy-client-config";
|
|
8
10
|
import { QueryProvider } from "./query-provider";
|
|
9
11
|
async function ReploProvider({
|
|
10
12
|
children,
|
|
11
13
|
markets
|
|
12
14
|
}) {
|
|
13
15
|
let checkoutProvider = "none";
|
|
16
|
+
let canopyClientConfig = null;
|
|
14
17
|
try {
|
|
15
|
-
|
|
18
|
+
const env = await getEnv();
|
|
19
|
+
checkoutProvider = env.CHECKOUT_PROVIDER ?? "none";
|
|
20
|
+
canopyClientConfig = {
|
|
21
|
+
canopyApiBaseUrl: buildBaseUrl(env.CANOPY_API_HOST),
|
|
22
|
+
projectId: env.PROJECT_ID
|
|
23
|
+
};
|
|
16
24
|
} catch {
|
|
17
25
|
checkoutProvider = "none";
|
|
18
26
|
}
|
|
19
|
-
return /* @__PURE__ */ jsx(QueryProvider, { children: /* @__PURE__ */ jsx(LoaderErrorDetailsProvider, { enabled: shouldShowLoaderErrorDetails(), children: /* @__PURE__ */ jsxs(AnalyticsProvider, { checkoutProvider, children: [
|
|
27
|
+
return /* @__PURE__ */ jsx(QueryProvider, { children: /* @__PURE__ */ jsx(CanopyClientConfigProvider, { config: canopyClientConfig, children: /* @__PURE__ */ jsx(LoaderErrorDetailsProvider, { enabled: shouldShowLoaderErrorDetails(), children: /* @__PURE__ */ jsxs(AnalyticsProvider, { checkoutProvider, children: [
|
|
20
28
|
/* @__PURE__ */ jsx(ReploFirstPartyPixel, {}),
|
|
21
29
|
/* @__PURE__ */ jsx(
|
|
22
30
|
CartLoader,
|
|
@@ -26,7 +34,7 @@ async function ReploProvider({
|
|
|
26
34
|
children
|
|
27
35
|
}
|
|
28
36
|
)
|
|
29
|
-
] }) }) });
|
|
37
|
+
] }) }) }) });
|
|
30
38
|
}
|
|
31
39
|
export {
|
|
32
40
|
ReploProvider
|