@replohq/sdk 1.13.6 → 1.14.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/_vendor/replo-utils/lib/misc.d.ts +4 -0
- package/_vendor/replo-utils/lib/misc.mjs +1 -1
- package/_vendor/schemas/contentEntry.d.ts +1 -2
- package/_vendor/schemas/money.mjs +1 -1
- package/_vendor/schemas/routing/locale.mjs +1 -1
- package/_vendor/schemas/routing/rules.mjs +1 -1
- package/cart/buy-now-action.js +6 -3
- package/cart/cart-schema.d.ts +10 -0
- package/cart/cart-schema.js +7 -1
- package/cart/cart-types.d.ts +18 -0
- package/cart/utils/cart-adjustments.js +16 -2
- package/{chunk-ZKAX2FW3.mjs → chunk-HFKL4ZR5.mjs} +21 -1
- package/content.d.ts +1 -1
- package/lib/buildMetadata.js +3 -3
- package/loaders/invoke-loader-server.d.ts +2 -2
- package/loaders/judgeme-review-aggregate-loader.d.ts +1 -1
- package/loaders/judgeme-reviews-loader.d.ts +1 -1
- package/loaders/loader-schemas.d.ts +3 -0
- package/loaders/loader-schemas.js +2 -0
- package/loaders/loox-review-aggregate-loader.d.ts +1 -1
- package/loaders/loox-reviews-loader.d.ts +1 -1
- package/loaders/rebuy-recommendations-loader.d.ts +1 -0
- package/loaders/reviews-io-review-aggregate-loader.d.ts +1 -1
- package/loaders/reviews-io-reviews-loader.d.ts +1 -1
- package/loaders/statsig-experiment-loader.d.ts +23 -9
- package/loaders/statsig-experiment-loader.js +8 -2
- package/loaders/yotpo-review-aggregate-loader.d.ts +1 -1
- package/loaders/yotpo-reviews-loader.d.ts +1 -1
- package/package.json +8 -4
- package/routing/visitor-id.d.ts +41 -0
- package/routing/visitor-id.js +97 -0
|
@@ -142,4 +142,8 @@ export declare function coerceNumberToString(value: string | number | null): str
|
|
|
142
142
|
*/
|
|
143
143
|
export declare function formatFileSize(byteCount: number, decimalPlaces?: number): string;
|
|
144
144
|
export declare const sleep: (ms: number) => Promise<unknown>;
|
|
145
|
+
export declare function sleepWithSignal({ delayMs, signal, }: {
|
|
146
|
+
delayMs: number;
|
|
147
|
+
signal: AbortSignal | null;
|
|
148
|
+
}): Promise<unknown>;
|
|
145
149
|
export declare function errorMessage(error: unknown): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { coerceNumberToString, deepCloneAndMergeReplacingArrays, errorMessage, exhaustiveSwitch, formatFileSize, getFromRecordOrNull, hasOwnProperty, invariant, isEmpty, isNotNullish, isNullish, isPrimitive, mergeReplacingArrays, mergeReplacingObjects, noop, preventDefault, sleep, warning } from '../../../chunk-
|
|
1
|
+
export { coerceNumberToString, deepCloneAndMergeReplacingArrays, errorMessage, exhaustiveSwitch, formatFileSize, getFromRecordOrNull, hasOwnProperty, invariant, isEmpty, isNotNullish, isNullish, isPrimitive, mergeReplacingArrays, mergeReplacingObjects, noop, preventDefault, sleep, sleepWithSignal, warning } from '../../../chunk-HFKL4ZR5.mjs';
|
|
2
2
|
export { isNumber_default as isNumber, isString_default as isString } from '../../../chunk-EUIVDITZ.mjs';
|
|
3
3
|
export { isFunction_default as isFunction } from '../../../chunk-SSBIMQES.mjs';
|
|
4
4
|
import '../../../chunk-UJCSKKID.mjs';
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { LegacyContentEntryStatus } from "./contentCollection";
|
|
2
|
-
import { contentCollectionFieldSchema } from "./contentCollection";
|
|
1
|
+
import type { contentCollectionFieldSchema, LegacyContentEntryStatus } from "./contentCollection";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
export declare const CONTENT_MANAGED_ENTRY_VALUE_KEYS: readonly ["slug", "title", "status", "author", "authorInitials", "excerpt", "coverImage", "coverImageAlt", "tags", "updatedAt", "publishedAt", "archivedAt", "seoTitle", "seoDescription", "related", "scratchpad", "backlinks"];
|
|
5
4
|
export declare const contentEntryFrontmatterSchema: z.ZodObject<{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getParamSegment, getRouteParams, hasRouteParam, matchSource } from '../../../chunk-VPEVERWE.mjs';
|
|
2
2
|
import { ReploError } from '../../../chunk-Y3AYABMK.mjs';
|
|
3
|
-
import '../../../chunk-
|
|
3
|
+
import '../../../chunk-HFKL4ZR5.mjs';
|
|
4
4
|
import '../../../chunk-EUIVDITZ.mjs';
|
|
5
5
|
import '../../../chunk-SSBIMQES.mjs';
|
|
6
6
|
import '../../../chunk-UJCSKKID.mjs';
|
package/cart/buy-now-action.js
CHANGED
|
@@ -9,6 +9,7 @@ import { buildBaseUrl } from "../lib/url-utils";
|
|
|
9
9
|
import { createCartAction } from "./cart-actions";
|
|
10
10
|
import { REPLO_ATTRIBUTION_PROPERTY } from "./cart-types";
|
|
11
11
|
import { getReturnUrls } from "./return-urls";
|
|
12
|
+
const REPLO_PROJECT_ID_KEY = "rpid";
|
|
12
13
|
class BuyNowError extends CanopyError {
|
|
13
14
|
}
|
|
14
15
|
class BuyNowCartCreationError extends CanopyError {
|
|
@@ -53,7 +54,7 @@ async function buyNowWithRecoveryAction(input) {
|
|
|
53
54
|
"[Replo] Stripe buy now does not support selling plans or discount codes; those options were ignored."
|
|
54
55
|
);
|
|
55
56
|
}
|
|
56
|
-
const
|
|
57
|
+
const env2 = await getEnv();
|
|
57
58
|
const temporaryCartId = input.checkoutAttemptId ?? uuidv4();
|
|
58
59
|
const body = {
|
|
59
60
|
cartId: temporaryCartId,
|
|
@@ -63,11 +64,11 @@ async function buyNowWithRecoveryAction(input) {
|
|
|
63
64
|
quantity: line.quantity,
|
|
64
65
|
properties: line.properties
|
|
65
66
|
})),
|
|
66
|
-
projectId:
|
|
67
|
+
projectId: env2.PROJECT_ID,
|
|
67
68
|
successUrl: `${origin}/checkout/success`,
|
|
68
69
|
cancelUrl
|
|
69
70
|
};
|
|
70
|
-
const canopyApiUrl = `${buildBaseUrl(
|
|
71
|
+
const canopyApiUrl = `${buildBaseUrl(env2.CANOPY_API_HOST)}/api/v1/stripe-checkout/create-session`;
|
|
71
72
|
const response = await fetch(canopyApiUrl, {
|
|
72
73
|
method: "POST",
|
|
73
74
|
headers: { "Content-Type": "application/json" },
|
|
@@ -130,6 +131,8 @@ async function buyNowWithRecoveryAction(input) {
|
|
|
130
131
|
});
|
|
131
132
|
}
|
|
132
133
|
const checkoutUrl = new URL(cart.checkoutUrl, origin);
|
|
134
|
+
const env = await getEnv();
|
|
135
|
+
checkoutUrl.searchParams.set(REPLO_PROJECT_ID_KEY, env.PROJECT_ID);
|
|
133
136
|
return { result: "created", redirectUrl: checkoutUrl.toString() };
|
|
134
137
|
}
|
|
135
138
|
export {
|
package/cart/cart-schema.d.ts
CHANGED
|
@@ -59,6 +59,11 @@ export declare const cartSchema: z.ZodObject<{
|
|
|
59
59
|
currencyCode: z.ZodString;
|
|
60
60
|
}, z.core.$strip>;
|
|
61
61
|
}, z.core.$strip>>>;
|
|
62
|
+
components: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
63
|
+
merchandiseId: z.ZodString;
|
|
64
|
+
quantity: z.ZodNumber;
|
|
65
|
+
sellingPlanId: z.ZodNullable<z.ZodString>;
|
|
66
|
+
}, z.core.$strip>>>;
|
|
62
67
|
}, z.core.$strip>>;
|
|
63
68
|
cost: z.ZodObject<{
|
|
64
69
|
subtotalAmount: z.ZodNumber;
|
|
@@ -147,6 +152,11 @@ export declare const clientCartSchema: z.ZodObject<{
|
|
|
147
152
|
currencyCode: z.ZodString;
|
|
148
153
|
}, z.core.$strip>;
|
|
149
154
|
}, z.core.$strip>>>;
|
|
155
|
+
components: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
156
|
+
merchandiseId: z.ZodString;
|
|
157
|
+
quantity: z.ZodNumber;
|
|
158
|
+
sellingPlanId: z.ZodNullable<z.ZodString>;
|
|
159
|
+
}, z.core.$strip>>>;
|
|
150
160
|
merchandise: z.ZodNullable<z.ZodObject<{
|
|
151
161
|
id: z.ZodString;
|
|
152
162
|
title: z.ZodString;
|
package/cart/cart-schema.js
CHANGED
|
@@ -40,12 +40,18 @@ const cartLineMerchandiseSchema = z.object({
|
|
|
40
40
|
image: cartImageSchema.nullable(),
|
|
41
41
|
selectedOptions: z.array(z.object({ name: z.string(), value: z.string() }))
|
|
42
42
|
});
|
|
43
|
+
const cartLineComponentSchema = z.object({
|
|
44
|
+
merchandiseId: z.string(),
|
|
45
|
+
quantity: z.number(),
|
|
46
|
+
sellingPlanId: z.string().nullable()
|
|
47
|
+
});
|
|
43
48
|
const cartLineSchema = z.object({
|
|
44
49
|
id: z.string(),
|
|
45
50
|
quantity: z.number(),
|
|
46
51
|
merchandise: cartLineMerchandiseSchema,
|
|
47
52
|
attributes: z.array(cartLineAttributeSchema),
|
|
48
|
-
sellingPlanAllocation: sellingPlanAllocationSchema.nullish()
|
|
53
|
+
sellingPlanAllocation: sellingPlanAllocationSchema.nullish(),
|
|
54
|
+
components: z.array(cartLineComponentSchema).optional()
|
|
49
55
|
});
|
|
50
56
|
const cartCostSchema = z.object({
|
|
51
57
|
subtotalAmount: amountSchema,
|
package/cart/cart-types.d.ts
CHANGED
|
@@ -77,6 +77,24 @@ export interface CartLine {
|
|
|
77
77
|
merchandise: CartLineMerchandise | null;
|
|
78
78
|
attributes: CartLineAttribute[];
|
|
79
79
|
sellingPlanAllocation?: SellingPlanAllocation | null;
|
|
80
|
+
/**
|
|
81
|
+
* For a Shopify bundle line, the variants it holds; empty on other lines.
|
|
82
|
+
* The line itself is what checkout charges, so render and price the line
|
|
83
|
+
* and use components only to show what is inside it.
|
|
84
|
+
*/
|
|
85
|
+
components?: CartLineComponent[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A variant inside a bundle line. For a bundle Shopify merged from lines the
|
|
89
|
+
* shopper added, these are those lines.
|
|
90
|
+
*/
|
|
91
|
+
export interface CartLineComponent {
|
|
92
|
+
/** The component's variant ID */
|
|
93
|
+
merchandiseId: string;
|
|
94
|
+
/** How many of this variant the bundle line holds in total */
|
|
95
|
+
quantity: number;
|
|
96
|
+
/** The component's selling plan, or null for a one-time purchase */
|
|
97
|
+
sellingPlanId: string | null;
|
|
80
98
|
}
|
|
81
99
|
export interface CartCost {
|
|
82
100
|
/** Integer minor units (Stripe convention) in currencyCode */
|
|
@@ -25,6 +25,20 @@ function totalsFromCart(cart) {
|
|
|
25
25
|
}
|
|
26
26
|
return totals;
|
|
27
27
|
}
|
|
28
|
+
function totalsWithBundleComponents(cart) {
|
|
29
|
+
const totals = totalsFromCart(cart);
|
|
30
|
+
for (const line of cart.lines) {
|
|
31
|
+
for (const component of line.components ?? []) {
|
|
32
|
+
addToTotals(
|
|
33
|
+
totals,
|
|
34
|
+
component.merchandiseId,
|
|
35
|
+
component.sellingPlanId,
|
|
36
|
+
component.quantity
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return totals;
|
|
41
|
+
}
|
|
28
42
|
function diffTotals(intended, saved) {
|
|
29
43
|
const adjustments = [];
|
|
30
44
|
for (const [key, entry] of intended) {
|
|
@@ -48,11 +62,11 @@ function deriveAddToCartAdjustments({
|
|
|
48
62
|
requestedLines,
|
|
49
63
|
savedCart
|
|
50
64
|
}) {
|
|
51
|
-
const intended =
|
|
65
|
+
const intended = totalsWithBundleComponents(cartBeforeAdd);
|
|
52
66
|
for (const line of requestedLines) {
|
|
53
67
|
addToTotals(intended, line.id, line.sellingPlanId ?? null, line.quantity);
|
|
54
68
|
}
|
|
55
|
-
return diffTotals(intended,
|
|
69
|
+
return diffTotals(intended, totalsWithBundleComponents(savedCart));
|
|
56
70
|
}
|
|
57
71
|
function deriveUpdateCartAdjustments({
|
|
58
72
|
cartBeforeUpdate,
|
|
@@ -689,6 +689,26 @@ function formatFileSize(byteCount, decimalPlaces = 0) {
|
|
|
689
689
|
var sleep = async (ms) => {
|
|
690
690
|
return await new Promise((resolve) => setTimeout(resolve, ms));
|
|
691
691
|
};
|
|
692
|
+
function sleepWithSignal({
|
|
693
|
+
delayMs,
|
|
694
|
+
signal
|
|
695
|
+
}) {
|
|
696
|
+
if (signal === null) {
|
|
697
|
+
return sleep(delayMs);
|
|
698
|
+
}
|
|
699
|
+
signal.throwIfAborted();
|
|
700
|
+
return new Promise((resolve, reject) => {
|
|
701
|
+
const onAbort = () => {
|
|
702
|
+
clearTimeout(timeoutId);
|
|
703
|
+
reject(signal.reason);
|
|
704
|
+
};
|
|
705
|
+
const timeoutId = setTimeout(() => {
|
|
706
|
+
signal.removeEventListener("abort", onAbort);
|
|
707
|
+
resolve();
|
|
708
|
+
}, delayMs);
|
|
709
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
710
|
+
});
|
|
711
|
+
}
|
|
692
712
|
function errorMessage(error) {
|
|
693
713
|
if (error instanceof Error) {
|
|
694
714
|
return error.message;
|
|
@@ -700,4 +720,4 @@ function errorMessage(error) {
|
|
|
700
720
|
return String(error);
|
|
701
721
|
}
|
|
702
722
|
|
|
703
|
-
export { coerceNumberToString, deepCloneAndMergeReplacingArrays, errorMessage, exhaustiveSwitch, formatFileSize, getFromRecordOrNull, hasOwnProperty4 as hasOwnProperty, invariant, isEmpty2 as isEmpty, isNotNullish, isNullish, isPrimitive, mergeReplacingArrays, mergeReplacingObjects, noop, preventDefault, sleep, warning };
|
|
723
|
+
export { coerceNumberToString, deepCloneAndMergeReplacingArrays, errorMessage, exhaustiveSwitch, formatFileSize, getFromRecordOrNull, hasOwnProperty4 as hasOwnProperty, invariant, isEmpty2 as isEmpty, isNotNullish, isNullish, isPrimitive, mergeReplacingArrays, mergeReplacingObjects, noop, preventDefault, sleep, sleepWithSignal, warning };
|
package/content.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
import type { Metadata } from "next";
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
|
+
import type { contentCollectionFieldSchema } from "./_vendor/schemas/contentCollection";
|
|
4
5
|
import type { ContentManifest } from "./_vendor/schemas/contentManifest";
|
|
5
|
-
import { contentCollectionFieldSchema } from "./_vendor/schemas/contentCollection";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
/** Public name for the shared collection-field contract. */
|
|
8
8
|
export type ContentField = z.infer<typeof contentCollectionFieldSchema>;
|
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.14.1",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-
|
|
5
|
+
"commit": "19094c206182dc6886a38bbc6fcd1541c8123b64",
|
|
6
|
+
"builtAt": "2026-10-08T22:22:26.957Z"
|
|
7
7
|
};
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Results are cached via Next.js `unstable_cache` so repeated SSR renders
|
|
6
6
|
* for the same loader+args return instantly without a network round-trip
|
|
7
|
-
* to canopy-api.
|
|
8
|
-
*
|
|
7
|
+
* to canopy-api. Canopy can also cache opted-in loaders in shared KV when
|
|
8
|
+
* its deployment enables caching.
|
|
9
9
|
*
|
|
10
10
|
* A canopy 404 is cached as a not-found marker and rethrown as a 404
|
|
11
11
|
* `CanopyApiGatewayError`, so a successful revalidation can displace a stale
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import { z } from "zod";
|
|
2
|
+
import type { z } from "zod";
|
|
3
3
|
import { judgemeProductReviewAggregateLoaderOutputSchema } from "./loader-schemas";
|
|
4
4
|
type JudgemeReviewAggregateData = z.infer<typeof judgemeProductReviewAggregateLoaderOutputSchema>;
|
|
5
5
|
type JudgemeReviewAggregateLoaderProps = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import { z } from "zod";
|
|
2
|
+
import type { z } from "zod";
|
|
3
3
|
import { judgemeProductReviewsLoaderOutputSchema } from "./loader-schemas";
|
|
4
4
|
type JudgemeReviewsData = z.infer<typeof judgemeProductReviewsLoaderOutputSchema>;
|
|
5
5
|
type JudgemeReviewsLoaderProps = {
|
|
@@ -371,6 +371,7 @@ export declare const productSchema: z.ZodObject<{
|
|
|
371
371
|
externalId: z.ZodNullable<z.ZodString>;
|
|
372
372
|
}, z.core.$strip>>;
|
|
373
373
|
}, z.core.$strip>>>;
|
|
374
|
+
requiresSellingPlan: z.ZodOptional<z.ZodBoolean>;
|
|
374
375
|
metafields: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
375
376
|
key: z.ZodString;
|
|
376
377
|
namespace: z.ZodString;
|
|
@@ -583,6 +584,7 @@ export declare const fullProductSchema: z.ZodObject<{
|
|
|
583
584
|
externalId: z.ZodNullable<z.ZodString>;
|
|
584
585
|
}, z.core.$strip>>;
|
|
585
586
|
}, z.core.$strip>>;
|
|
587
|
+
requiresSellingPlan: z.ZodOptional<z.ZodBoolean>;
|
|
586
588
|
metafields: z.ZodArray<z.ZodObject<{
|
|
587
589
|
key: z.ZodString;
|
|
588
590
|
namespace: z.ZodString;
|
|
@@ -776,6 +778,7 @@ export declare const paginatedProductsSchema: z.ZodObject<{
|
|
|
776
778
|
externalId: z.ZodNullable<z.ZodString>;
|
|
777
779
|
}, z.core.$strip>>;
|
|
778
780
|
}, z.core.$strip>>>;
|
|
781
|
+
requiresSellingPlan: z.ZodOptional<z.ZodBoolean>;
|
|
779
782
|
metafields: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
780
783
|
key: z.ZodString;
|
|
781
784
|
namespace: z.ZodString;
|
|
@@ -144,6 +144,7 @@ const productSchema = z.object({
|
|
|
144
144
|
productType: z.string().nullish(),
|
|
145
145
|
tags: z.array(z.string()).optional(),
|
|
146
146
|
sellingPlanGroups: z.array(sellingPlanGroupSchema).optional(),
|
|
147
|
+
requiresSellingPlan: z.boolean().optional(),
|
|
147
148
|
metafields: z.array(metafieldSchema).optional()
|
|
148
149
|
});
|
|
149
150
|
const okendoReviewSchema = z.object({
|
|
@@ -185,6 +186,7 @@ const fullProductSchema = z.object({
|
|
|
185
186
|
productType: z.string().nullable(),
|
|
186
187
|
tags: z.array(z.string()),
|
|
187
188
|
sellingPlanGroups: z.array(sellingPlanGroupSchema),
|
|
189
|
+
requiresSellingPlan: productSchema.shape.requiresSellingPlan,
|
|
188
190
|
metafields: z.array(metafieldSchema)
|
|
189
191
|
});
|
|
190
192
|
const fullCollectionSchema = z.object({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import { z } from "zod";
|
|
2
|
+
import type { z } from "zod";
|
|
3
3
|
import { looxProductReviewAggregateLoaderOutputSchema } from "./loader-schemas";
|
|
4
4
|
type LooxReviewAggregateData = z.infer<typeof looxProductReviewAggregateLoaderOutputSchema>;
|
|
5
5
|
type LooxReviewAggregateLoaderProps = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import { z } from "zod";
|
|
2
|
+
import type { z } from "zod";
|
|
3
3
|
import { looxProductReviewsLoaderOutputSchema } from "./loader-schemas";
|
|
4
4
|
type LooxReviewsData = z.infer<typeof looxProductReviewsLoaderOutputSchema>;
|
|
5
5
|
type LooxReviewsLoaderProps = {
|
|
@@ -146,6 +146,7 @@ declare const rebuyRecommendationsOutputSchema: z.ZodObject<{
|
|
|
146
146
|
externalId: z.ZodNullable<z.ZodString>;
|
|
147
147
|
}, z.core.$strip>>;
|
|
148
148
|
}, z.core.$strip>>>;
|
|
149
|
+
requiresSellingPlan: z.ZodOptional<z.ZodBoolean>;
|
|
149
150
|
metafields: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
150
151
|
key: z.ZodString;
|
|
151
152
|
namespace: z.ZodString;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import { z } from "zod";
|
|
2
|
+
import type { z } from "zod";
|
|
3
3
|
import { reviewsIoProductReviewAggregateLoaderOutputSchema } from "./loader-schemas";
|
|
4
4
|
type ReviewsIoReviewAggregateData = z.infer<typeof reviewsIoProductReviewAggregateLoaderOutputSchema>;
|
|
5
5
|
type ReviewsIoReviewAggregateLoaderProps = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import { z } from "zod";
|
|
2
|
+
import type { z } from "zod";
|
|
3
3
|
import { reviewsIoProductReviewsLoaderOutputSchema } from "./loader-schemas";
|
|
4
4
|
type ReviewsIoReviewsData = z.infer<typeof reviewsIoProductReviewsLoaderOutputSchema>;
|
|
5
5
|
type ReviewsIoReviewsLoaderProps = {
|
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { StatsigExperimentAssignment } from "./loader-schemas";
|
|
3
|
-
type
|
|
4
|
-
loaderKey: string;
|
|
5
|
-
experimentName: string;
|
|
3
|
+
type StatsigVisitorIds = {
|
|
6
4
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `PrefetchedLoaders` and this component so the SSR-prefetched assignment
|
|
10
|
-
* hydrates without a refetch and stays consistent across renders.
|
|
5
|
+
* Shorthand for `customIds.stableID`. Use it only when the experiment's ID
|
|
6
|
+
* type is `stableID`.
|
|
11
7
|
*/
|
|
12
|
-
stableId
|
|
8
|
+
stableId?: string;
|
|
13
9
|
userId?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Visitor IDs keyed by the experiment's Statsig ID type, e.g.
|
|
12
|
+
* `{ publicID: visitorId }`. Read the ID in the page's server component and
|
|
13
|
+
* pass the same value to both `PrefetchedLoaders` and this component so the
|
|
14
|
+
* SSR-prefetched assignment hydrates without a refetch.
|
|
15
|
+
*/
|
|
16
|
+
customIds?: Record<string, string>;
|
|
17
|
+
};
|
|
18
|
+
type AtLeastOneVisitorId = (StatsigVisitorIds & {
|
|
19
|
+
stableId: string;
|
|
20
|
+
}) | (StatsigVisitorIds & {
|
|
21
|
+
userId: string;
|
|
22
|
+
}) | (StatsigVisitorIds & {
|
|
23
|
+
customIds: Record<string, string>;
|
|
24
|
+
});
|
|
25
|
+
type StatsigExperimentLoaderProps = AtLeastOneVisitorId & {
|
|
26
|
+
loaderKey: string;
|
|
27
|
+
experimentName: string;
|
|
14
28
|
environmentTier?: string;
|
|
15
29
|
customAttributes?: Record<string, string>;
|
|
16
30
|
children: (assignment: StatsigExperimentAssignment) => ReactNode;
|
|
17
31
|
fallback?: ReactNode;
|
|
18
32
|
};
|
|
19
|
-
export declare function StatsigExperimentLoader({ loaderKey, experimentName, stableId, userId, environmentTier, customAttributes, children, fallback, }: StatsigExperimentLoaderProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
export declare function StatsigExperimentLoader({ loaderKey, experimentName, stableId, userId, customIds, environmentTier, customAttributes, children, fallback, }: StatsigExperimentLoaderProps): import("react/jsx-runtime").JSX.Element;
|
|
20
34
|
export {};
|
|
@@ -9,13 +9,15 @@ function buildArgs({
|
|
|
9
9
|
experimentName,
|
|
10
10
|
stableId,
|
|
11
11
|
userId,
|
|
12
|
+
customIds,
|
|
12
13
|
environmentTier,
|
|
13
14
|
customAttributes
|
|
14
15
|
}) {
|
|
15
16
|
return {
|
|
16
17
|
experimentName,
|
|
17
|
-
stableId,
|
|
18
|
+
...stableId !== void 0 ? { stableId } : {},
|
|
18
19
|
...userId !== void 0 ? { userId } : {},
|
|
20
|
+
...customIds !== void 0 ? { customIds } : {},
|
|
19
21
|
...environmentTier !== void 0 ? { environmentTier } : {},
|
|
20
22
|
...customAttributes !== void 0 ? { customAttributes } : {}
|
|
21
23
|
};
|
|
@@ -25,6 +27,7 @@ function StatsigExperimentLoaderInner({
|
|
|
25
27
|
experimentName,
|
|
26
28
|
stableId,
|
|
27
29
|
userId,
|
|
30
|
+
customIds,
|
|
28
31
|
environmentTier,
|
|
29
32
|
customAttributes,
|
|
30
33
|
children
|
|
@@ -33,6 +36,7 @@ function StatsigExperimentLoaderInner({
|
|
|
33
36
|
experimentName,
|
|
34
37
|
stableId,
|
|
35
38
|
userId,
|
|
39
|
+
customIds,
|
|
36
40
|
environmentTier,
|
|
37
41
|
customAttributes
|
|
38
42
|
});
|
|
@@ -50,6 +54,7 @@ function StatsigExperimentLoader({
|
|
|
50
54
|
experimentName,
|
|
51
55
|
stableId,
|
|
52
56
|
userId,
|
|
57
|
+
customIds,
|
|
53
58
|
environmentTier,
|
|
54
59
|
customAttributes,
|
|
55
60
|
children,
|
|
@@ -59,7 +64,7 @@ function StatsigExperimentLoader({
|
|
|
59
64
|
LoaderErrorBoundary,
|
|
60
65
|
{
|
|
61
66
|
fallback: fallback ?? null,
|
|
62
|
-
resetKey: `${loaderKey}:${experimentName}:${stableId}`,
|
|
67
|
+
resetKey: `${loaderKey}:${experimentName}:${JSON.stringify({ stableId, userId, customIds })}`,
|
|
63
68
|
children: /* @__PURE__ */ jsx(Suspense, { fallback: fallback ?? null, children: /* @__PURE__ */ jsx(
|
|
64
69
|
StatsigExperimentLoaderInner,
|
|
65
70
|
{
|
|
@@ -67,6 +72,7 @@ function StatsigExperimentLoader({
|
|
|
67
72
|
experimentName,
|
|
68
73
|
stableId,
|
|
69
74
|
userId,
|
|
75
|
+
customIds,
|
|
70
76
|
environmentTier,
|
|
71
77
|
customAttributes,
|
|
72
78
|
children
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import { z } from "zod";
|
|
2
|
+
import type { z } from "zod";
|
|
3
3
|
import { yotpoProductReviewAggregateLoaderOutputSchema } from "./loader-schemas";
|
|
4
4
|
type YotpoReviewAggregateData = z.infer<typeof yotpoProductReviewAggregateLoaderOutputSchema>;
|
|
5
5
|
type YotpoReviewAggregateLoaderProps = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import { z } from "zod";
|
|
2
|
+
import type { z } from "zod";
|
|
3
3
|
import { yotpoProductReviewsLoaderOutputSchema } from "./loader-schemas";
|
|
4
4
|
type YotpoReviewsData = z.infer<typeof yotpoProductReviewsLoaderOutputSchema>;
|
|
5
5
|
type YotpoReviewsLoaderProps = {
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.1",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sdk",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.14.1",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-
|
|
8
|
+
"commit": "19094c206182dc6886a38bbc6fcd1541c8123b64",
|
|
9
|
+
"builtAt": "2026-10-08T22:22:26.957Z"
|
|
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",
|
|
@@ -261,6 +261,10 @@
|
|
|
261
261
|
"types": "./routing/geo.d.ts",
|
|
262
262
|
"default": "./routing/geo.js"
|
|
263
263
|
},
|
|
264
|
+
"./routing/visitor-id": {
|
|
265
|
+
"types": "./routing/visitor-id.d.ts",
|
|
266
|
+
"default": "./routing/visitor-id.js"
|
|
267
|
+
},
|
|
264
268
|
"./_vendor/schemas/loaderKeys": {
|
|
265
269
|
"types": "./_vendor/schemas/loaderKeys.d.ts",
|
|
266
270
|
"default": "./_vendor/schemas/loaderKeys.mjs"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import type { ConsentCategory } from "../consent/types";
|
|
3
|
+
/**
|
|
4
|
+
* Gives a first-time visitor a durable visitor ID (a UUID) in the `cookieName`
|
|
5
|
+
* cookie, so experiments bucket every page view of theirs the same way. Call it
|
|
6
|
+
* in `middleware.ts` on the response the rest of the middleware returns. It
|
|
7
|
+
* never replaces an existing cookie, so a visitor keeps any ID the customer's
|
|
8
|
+
* own site already gave them, and it skips redirects.
|
|
9
|
+
*
|
|
10
|
+
* When the request's host is `cookieDomain` or one of its subdomains, the
|
|
11
|
+
* cookie is scoped to `cookieDomain` so the customer's other subdomains (their
|
|
12
|
+
* funnel or checkout) see the same ID. On any other host, such as a preview or
|
|
13
|
+
* a `replosites.com` address, or with `cookieDomain: null`, the cookie stays on
|
|
14
|
+
* the current host.
|
|
15
|
+
*
|
|
16
|
+
* With a `requiredConsent` category, the ID is only created once the visitor
|
|
17
|
+
* has granted that category in Replo's consent banner. Pass `null` when the
|
|
18
|
+
* site needs no consent for it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ensureVisitorId({ request, response, cookieName, cookieDomain, requiredConsent, }: {
|
|
21
|
+
request: NextRequest;
|
|
22
|
+
response: NextResponse;
|
|
23
|
+
cookieName: string;
|
|
24
|
+
cookieDomain: string | null;
|
|
25
|
+
requiredConsent: VisitorIdConsent;
|
|
26
|
+
}): NextResponse;
|
|
27
|
+
/**
|
|
28
|
+
* Reads the visitor ID for an experiment evaluation, or `null` when the
|
|
29
|
+
* visitor has none or hasn't granted `requiredConsent`. Pass it the page's
|
|
30
|
+
* `await cookies()` and the same `cookieName` and `requiredConsent` as
|
|
31
|
+
* `ensureVisitorId`, so a visitor who withdraws consent sees the default and
|
|
32
|
+
* logs no exposure.
|
|
33
|
+
*/
|
|
34
|
+
export declare function findVisitorId({ cookies, cookieName, requiredConsent, }: {
|
|
35
|
+
cookies: VisitorIdCookies;
|
|
36
|
+
cookieName: string;
|
|
37
|
+
requiredConsent: VisitorIdConsent;
|
|
38
|
+
}): string | null;
|
|
39
|
+
type VisitorIdCookies = Pick<NextRequest["cookies"], "get">;
|
|
40
|
+
type VisitorIdConsent = Exclude<ConsentCategory, "necessary"> | null;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { CONSENT_COOKIE_NAME, parseConsentCookie } from "../consent/cookie";
|
|
2
|
+
const ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
|
|
3
|
+
const SHARED_REPLO_DOMAINS = /* @__PURE__ */ new Set([
|
|
4
|
+
"reploshops.com",
|
|
5
|
+
"replosites.com",
|
|
6
|
+
"replosites-staging.com",
|
|
7
|
+
"replosites-dev.com"
|
|
8
|
+
]);
|
|
9
|
+
const REGISTRY_SUFFIX_LABELS = /* @__PURE__ */ new Set([
|
|
10
|
+
"ac",
|
|
11
|
+
"co",
|
|
12
|
+
"com",
|
|
13
|
+
"edu",
|
|
14
|
+
"gov",
|
|
15
|
+
"ne",
|
|
16
|
+
"net",
|
|
17
|
+
"or",
|
|
18
|
+
"org"
|
|
19
|
+
]);
|
|
20
|
+
function ensureVisitorId({
|
|
21
|
+
request,
|
|
22
|
+
response,
|
|
23
|
+
cookieName,
|
|
24
|
+
cookieDomain,
|
|
25
|
+
requiredConsent
|
|
26
|
+
}) {
|
|
27
|
+
const isRedirect = response.status >= 300 && response.status < 400;
|
|
28
|
+
const isAlreadySet = response.headers.getSetCookie().some((setCookie) => {
|
|
29
|
+
return setCookie.startsWith(`${cookieName}=`);
|
|
30
|
+
});
|
|
31
|
+
if (isRedirect || isAlreadySet || !isConsentGranted({ cookies: request.cookies, requiredConsent }) || request.cookies.get(cookieName)?.value) {
|
|
32
|
+
return response;
|
|
33
|
+
}
|
|
34
|
+
const appendedSetCookies = response.headers.getSetCookie().filter((setCookie) => {
|
|
35
|
+
return !response.cookies.has(setCookie.split("=")[0]?.trim() ?? "");
|
|
36
|
+
});
|
|
37
|
+
const domain = findCookieDomain({
|
|
38
|
+
hostname: request.nextUrl.hostname,
|
|
39
|
+
cookieDomain
|
|
40
|
+
});
|
|
41
|
+
response.cookies.set({
|
|
42
|
+
name: cookieName,
|
|
43
|
+
value: crypto.randomUUID(),
|
|
44
|
+
path: "/",
|
|
45
|
+
maxAge: ONE_YEAR_SECONDS,
|
|
46
|
+
sameSite: "lax",
|
|
47
|
+
secure: request.nextUrl.protocol === "https:",
|
|
48
|
+
httpOnly: false,
|
|
49
|
+
...domain !== null ? { domain } : {}
|
|
50
|
+
});
|
|
51
|
+
for (const setCookie of appendedSetCookies) {
|
|
52
|
+
response.headers.append("set-cookie", setCookie);
|
|
53
|
+
}
|
|
54
|
+
response.headers.set("Cache-Control", "private, no-store");
|
|
55
|
+
return response;
|
|
56
|
+
}
|
|
57
|
+
function findVisitorId({
|
|
58
|
+
cookies,
|
|
59
|
+
cookieName,
|
|
60
|
+
requiredConsent
|
|
61
|
+
}) {
|
|
62
|
+
const visitorId = cookies.get(cookieName)?.value;
|
|
63
|
+
if (!visitorId || !isConsentGranted({ cookies, requiredConsent })) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return visitorId;
|
|
67
|
+
}
|
|
68
|
+
function isConsentGranted({
|
|
69
|
+
cookies,
|
|
70
|
+
requiredConsent
|
|
71
|
+
}) {
|
|
72
|
+
if (requiredConsent === null) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
const consent = parseConsentCookie(cookies.get(CONSENT_COOKIE_NAME)?.value);
|
|
76
|
+
return consent?.categories[requiredConsent] ?? false;
|
|
77
|
+
}
|
|
78
|
+
function findCookieDomain({
|
|
79
|
+
hostname,
|
|
80
|
+
cookieDomain
|
|
81
|
+
}) {
|
|
82
|
+
if (cookieDomain === null) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const domain = cookieDomain.trim().toLowerCase().replace(/^\./, "");
|
|
86
|
+
const labels = domain.split(".");
|
|
87
|
+
const isRegistrySuffix = labels.length < 2 || labels.length === 2 && REGISTRY_SUFFIX_LABELS.has(labels[0] ?? "");
|
|
88
|
+
if (isRegistrySuffix || SHARED_REPLO_DOMAINS.has(domain)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const host = hostname.toLowerCase();
|
|
92
|
+
return host === domain || host.endsWith(`.${domain}`) ? domain : null;
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
ensureVisitorId,
|
|
96
|
+
findVisitorId
|
|
97
|
+
};
|