@rebuy/rebuy 3.23.0 → 3.25.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/README.md +1 -0
- package/dist/cab/asLayout.d.ts +12 -0
- package/dist/cab/contentText.d.ts +8 -0
- package/dist/cab/decoratedOrderId.d.ts +2 -0
- package/dist/cab/gid.d.ts +2 -0
- package/dist/cab/index.cjs +177 -0
- package/dist/cab/index.d.ts +9 -0
- package/dist/cab/index.mjs +154 -0
- package/dist/cab/interpolateAndSanitize.d.ts +8 -0
- package/dist/cab/listOf.d.ts +9 -0
- package/dist/cab/selectLanguage.d.ts +8 -0
- package/dist/cab/tiptapPlain.d.ts +10 -0
- package/dist/cab/tiptapRuns.d.ts +40 -0
- package/dist/cab/toCents.d.ts +2 -0
- package/dist/index.cjs +9 -0
- package/dist/index.mjs +9 -0
- package/dist/schema/checkout-and-beyond/index.cjs +9 -0
- package/dist/schema/checkout-and-beyond/index.mjs +9 -0
- package/dist/schema/widget-data.cjs +6 -0
- package/dist/schema/widget-data.mjs +6 -0
- package/dist/schema/widgets/checkout-and-beyond/monetize.d.ts +15 -0
- package/dist/schema/widgets/checkout-and-beyond/regex.d.ts +23 -1
- package/dist/server/adsEngine.d.ts +10 -1
- package/dist/server/index.cjs +51 -22
- package/dist/server/index.mjs +51 -22
- package/dist/server/monetizeOffers.d.ts +3 -1
- package/dist/server/requestSchemas.d.ts +1 -1
- package/dist/transforms/htmlToTiptap/linkSafety.d.ts +19 -0
- package/dist/transforms/index.cjs +27 -9
- package/dist/transforms/index.mjs +27 -9
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ Node `^22.14.0 || ^24.10.0 || ^26.0.0` for development; the published artifacts
|
|
|
17
17
|
| Subpath | Contents |
|
|
18
18
|
| --- | --- |
|
|
19
19
|
| `@rebuy/rebuy` | Root barrel: schemas, client helpers, utilities |
|
|
20
|
+
| `@rebuy/rebuy/cab` | zod-free CAB renderer helpers shared by every renderer (`contentText`, `tiptapPlainText`, `tiptapRuns`, `asLayout`, `idFromGid`, `toCents`, `decoratedOrderId`) |
|
|
20
21
|
| `@rebuy/rebuy/client` | `RebuyClient` API client |
|
|
21
22
|
| `@rebuy/rebuy/gwp` | Gift-with-purchase validation helpers |
|
|
22
23
|
| `@rebuy/rebuy/pricing` | Variant pricing/discount forecast (`priceVariant`, `currencyDecimals`) |
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CABLayoutSection } from '../schema/widgets/checkout-and-beyond/layout';
|
|
2
|
+
/**
|
|
3
|
+
* Render a layout-derived section (cart-line, offers, carousel, banner) as a plain layout; the variable
|
|
4
|
+
* fields are dropped — the scope was already mounted and the visibility binding already evaluated at
|
|
5
|
+
* dispatch, and `mutators` already has its host (a re-dispatch must not double any of them).
|
|
6
|
+
*
|
|
7
|
+
* The literal (checked against `SectionType`) rather than the runtime enum: `common.ts` is not tree-shakeable,
|
|
8
|
+
* so importing one constant would drag every CAB enum into each renderer that bundles this subpath.
|
|
9
|
+
*/
|
|
10
|
+
export declare const asLayout: <T extends {
|
|
11
|
+
sectionType: string;
|
|
12
|
+
}>(section: T) => CABLayoutSection;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Variables } from '../cab/interpolateAndSanitize';
|
|
2
|
+
import type { CABTextSection } from '../schema/widgets/checkout-and-beyond/text';
|
|
3
|
+
/**
|
|
4
|
+
* A text section's buyer-language copy as ONE interpolated, sanitized plain string — rich content flattens
|
|
5
|
+
* to bare text (marks and structure drop). The shared reading behind button label slots and the `content`
|
|
6
|
+
* mutator trigger, so "what does this copy say" has a single answer.
|
|
7
|
+
*/
|
|
8
|
+
export declare const contentText: (content: CABTextSection["content"], language?: string, variables?: Variables) => string;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/cab/index.ts
|
|
21
|
+
var cab_exports = {};
|
|
22
|
+
__export(cab_exports, {
|
|
23
|
+
asLayout: () => asLayout,
|
|
24
|
+
contentText: () => contentText,
|
|
25
|
+
decoratedOrderId: () => decoratedOrderId,
|
|
26
|
+
idFromGid: () => idFromGid,
|
|
27
|
+
interpolateAndSanitize: () => interpolateAndSanitize,
|
|
28
|
+
isTiptapDoc: () => isTiptapDoc,
|
|
29
|
+
selectLanguage: () => selectLanguage,
|
|
30
|
+
tiptapPlainText: () => tiptapPlainText,
|
|
31
|
+
tiptapRuns: () => tiptapRuns,
|
|
32
|
+
toCents: () => toCents
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(cab_exports);
|
|
35
|
+
|
|
36
|
+
// src/cab/asLayout.ts
|
|
37
|
+
var asLayout = (section) => ({
|
|
38
|
+
...section,
|
|
39
|
+
mutators: void 0,
|
|
40
|
+
sectionType: "layout",
|
|
41
|
+
variables: void 0,
|
|
42
|
+
visibility: void 0
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// src/schema/widgets/checkout-and-beyond/regex.ts
|
|
46
|
+
var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
|
|
47
|
+
var DYNAMIC_TOKEN_ANYWHERE_REGEX = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/;
|
|
48
|
+
var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
|
|
49
|
+
|
|
50
|
+
// src/cab/interpolateAndSanitize.ts
|
|
51
|
+
var TOKEN_REGEX = /{{1,2}\s*([^{}\s]+)\s*}{1,2}/g;
|
|
52
|
+
var toCamelCase = (key) => key.replace(/_([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
53
|
+
var resolveKey = (key, variables) => {
|
|
54
|
+
if (Object.hasOwn(variables, key)) return key;
|
|
55
|
+
const camel = toCamelCase(key);
|
|
56
|
+
if (Object.hasOwn(variables, camel)) return camel;
|
|
57
|
+
const lower = camel.toLowerCase();
|
|
58
|
+
return Object.keys(variables).find((name) => name.toLowerCase() === lower);
|
|
59
|
+
};
|
|
60
|
+
var interpolateAndSanitize = (input, variables) => (input ?? "").replace(TOKEN_REGEX, (match, key) => {
|
|
61
|
+
if (!variables) return match;
|
|
62
|
+
const resolved = resolveKey(key, variables);
|
|
63
|
+
return resolved === void 0 ? match : String(variables[resolved] ?? "");
|
|
64
|
+
}).replace(HTML_TAGS_REGEX, "");
|
|
65
|
+
|
|
66
|
+
// src/cab/selectLanguage.ts
|
|
67
|
+
var FALLBACK_LANGUAGE = "en";
|
|
68
|
+
var selectLanguage = (content, language) => {
|
|
69
|
+
if (!content) return void 0;
|
|
70
|
+
if (language && Object.hasOwn(content, language)) return content[language];
|
|
71
|
+
if (Object.hasOwn(content, FALLBACK_LANGUAGE)) return content[FALLBACK_LANGUAGE];
|
|
72
|
+
return Object.values(content)[0];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/cab/listOf.ts
|
|
76
|
+
var listOf = (value) => Array.isArray(value) ? value.filter((item) => !!item) : [];
|
|
77
|
+
|
|
78
|
+
// src/cab/tiptapPlain.ts
|
|
79
|
+
var isTiptapDoc = (value) => !!value && typeof value === "object" && value.type === "doc";
|
|
80
|
+
var tiptapPlainText = (doc) => listOf(doc?.content).map(
|
|
81
|
+
(paragraph) => listOf(paragraph.content).map((node) => node.text ?? "").join("")
|
|
82
|
+
).join(" ").trim();
|
|
83
|
+
|
|
84
|
+
// src/cab/contentText.ts
|
|
85
|
+
var contentText = (content, language, variables) => {
|
|
86
|
+
const selected = selectLanguage(content, language);
|
|
87
|
+
const text = typeof selected === "string" ? selected : isTiptapDoc(selected) ? tiptapPlainText(selected) : "";
|
|
88
|
+
return interpolateAndSanitize(text, variables);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// src/cab/decoratedOrderId.ts
|
|
92
|
+
var decoratedOrderId = (orderId) => orderId?.match(/\/Order(?:Identity)?\/(\d+)$/)?.[1] ?? orderId;
|
|
93
|
+
|
|
94
|
+
// src/cab/gid.ts
|
|
95
|
+
var idFromGid = (gid) => gid.slice(gid.lastIndexOf("/") + 1);
|
|
96
|
+
|
|
97
|
+
// src/transforms/htmlToTiptap/isValidHref.ts
|
|
98
|
+
var RELATIVE_BASE = "https://x.invalid";
|
|
99
|
+
var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
|
|
100
|
+
var isValidHref = (href) => {
|
|
101
|
+
if (!href) return false;
|
|
102
|
+
if (href === "#") return true;
|
|
103
|
+
if (DYNAMIC_TOKEN_REGEX.test(href)) return true;
|
|
104
|
+
if (href.startsWith("/")) {
|
|
105
|
+
try {
|
|
106
|
+
return new URL(href, RELATIVE_BASE).origin === RELATIVE_BASE;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const url = new URL(href);
|
|
113
|
+
return ALLOWED_PROTOCOLS.has(url.protocol);
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// src/transforms/htmlToTiptap/linkSafety.ts
|
|
120
|
+
var DEFAULT_LINK_TARGET = "_blank";
|
|
121
|
+
var DEFAULT_LINK_REL = "noopener noreferrer nofollow";
|
|
122
|
+
var ensureSafeRel = (rel, target) => {
|
|
123
|
+
if (target !== DEFAULT_LINK_TARGET) return rel;
|
|
124
|
+
const sourceTokens = (rel ?? "").split(/\s+/).filter(Boolean);
|
|
125
|
+
if (sourceTokens.length === 0) return DEFAULT_LINK_REL;
|
|
126
|
+
const tokens = new Set(sourceTokens.filter((token) => token.toLowerCase() !== "opener"));
|
|
127
|
+
tokens.add("noopener");
|
|
128
|
+
tokens.add("noreferrer");
|
|
129
|
+
return Array.from(tokens).join(" ");
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// src/cab/tiptapRuns.ts
|
|
133
|
+
var HEADING = "heading";
|
|
134
|
+
var isHeadingMark = (mark) => mark.type === "textStyle" && mark.attrs?.fontSize === HEADING;
|
|
135
|
+
var isRenderableHref = (href) => isValidHref(href) && !DYNAMIC_TOKEN_ANYWHERE_REGEX.test(href);
|
|
136
|
+
var toRun = (node, variables) => {
|
|
137
|
+
const run = {
|
|
138
|
+
bold: false,
|
|
139
|
+
italic: false,
|
|
140
|
+
strike: false,
|
|
141
|
+
text: interpolateAndSanitize(node.text ?? "", variables)
|
|
142
|
+
};
|
|
143
|
+
let link;
|
|
144
|
+
let textStyle;
|
|
145
|
+
for (const mark of listOf(node.marks)) {
|
|
146
|
+
if (mark.type === "bold") run.bold = true;
|
|
147
|
+
else if (mark.type === "italic") run.italic = true;
|
|
148
|
+
else if (mark.type === "strike") run.strike = true;
|
|
149
|
+
else if (mark.type === "link") link = mark;
|
|
150
|
+
else if (mark.type === "textStyle") textStyle = mark;
|
|
151
|
+
}
|
|
152
|
+
const color = textStyle?.attrs?.color;
|
|
153
|
+
const fontSize = textStyle?.attrs?.fontSize;
|
|
154
|
+
if (color) run.color = color;
|
|
155
|
+
if (fontSize && fontSize !== HEADING) run.fontSize = fontSize;
|
|
156
|
+
if (link) {
|
|
157
|
+
const href = interpolateAndSanitize(link.attrs?.href ?? "", variables);
|
|
158
|
+
if (isRenderableHref(href)) {
|
|
159
|
+
const target = link.attrs?.target ?? DEFAULT_LINK_TARGET;
|
|
160
|
+
const rel = ensureSafeRel(link.attrs?.rel, target);
|
|
161
|
+
run.href = href;
|
|
162
|
+
if (rel) run.rel = rel;
|
|
163
|
+
if (target === DEFAULT_LINK_TARGET) run.target = DEFAULT_LINK_TARGET;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return run;
|
|
167
|
+
};
|
|
168
|
+
var tiptapRuns = (paragraph, variables) => {
|
|
169
|
+
const content = listOf(paragraph?.content);
|
|
170
|
+
return {
|
|
171
|
+
heading: content.some((node) => listOf(node.marks).some(isHeadingMark)),
|
|
172
|
+
runs: content.map((node) => toRun(node, variables))
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// src/cab/toCents.ts
|
|
177
|
+
var toCents = (amount) => Math.round(Number(amount ?? 0) * 100);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from '../cab/asLayout';
|
|
2
|
+
export * from '../cab/contentText';
|
|
3
|
+
export * from '../cab/decoratedOrderId';
|
|
4
|
+
export * from '../cab/gid';
|
|
5
|
+
export * from '../cab/interpolateAndSanitize';
|
|
6
|
+
export * from '../cab/selectLanguage';
|
|
7
|
+
export * from '../cab/tiptapPlain';
|
|
8
|
+
export * from '../cab/tiptapRuns';
|
|
9
|
+
export * from '../cab/toCents';
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// src/cab/asLayout.ts
|
|
2
|
+
var asLayout = (section) => ({
|
|
3
|
+
...section,
|
|
4
|
+
mutators: void 0,
|
|
5
|
+
sectionType: "layout",
|
|
6
|
+
variables: void 0,
|
|
7
|
+
visibility: void 0
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// src/schema/widgets/checkout-and-beyond/regex.ts
|
|
11
|
+
var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
|
|
12
|
+
var DYNAMIC_TOKEN_ANYWHERE_REGEX = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/;
|
|
13
|
+
var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
|
|
14
|
+
|
|
15
|
+
// src/cab/interpolateAndSanitize.ts
|
|
16
|
+
var TOKEN_REGEX = /{{1,2}\s*([^{}\s]+)\s*}{1,2}/g;
|
|
17
|
+
var toCamelCase = (key) => key.replace(/_([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
18
|
+
var resolveKey = (key, variables) => {
|
|
19
|
+
if (Object.hasOwn(variables, key)) return key;
|
|
20
|
+
const camel = toCamelCase(key);
|
|
21
|
+
if (Object.hasOwn(variables, camel)) return camel;
|
|
22
|
+
const lower = camel.toLowerCase();
|
|
23
|
+
return Object.keys(variables).find((name) => name.toLowerCase() === lower);
|
|
24
|
+
};
|
|
25
|
+
var interpolateAndSanitize = (input, variables) => (input ?? "").replace(TOKEN_REGEX, (match, key) => {
|
|
26
|
+
if (!variables) return match;
|
|
27
|
+
const resolved = resolveKey(key, variables);
|
|
28
|
+
return resolved === void 0 ? match : String(variables[resolved] ?? "");
|
|
29
|
+
}).replace(HTML_TAGS_REGEX, "");
|
|
30
|
+
|
|
31
|
+
// src/cab/selectLanguage.ts
|
|
32
|
+
var FALLBACK_LANGUAGE = "en";
|
|
33
|
+
var selectLanguage = (content, language) => {
|
|
34
|
+
if (!content) return void 0;
|
|
35
|
+
if (language && Object.hasOwn(content, language)) return content[language];
|
|
36
|
+
if (Object.hasOwn(content, FALLBACK_LANGUAGE)) return content[FALLBACK_LANGUAGE];
|
|
37
|
+
return Object.values(content)[0];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/cab/listOf.ts
|
|
41
|
+
var listOf = (value) => Array.isArray(value) ? value.filter((item) => !!item) : [];
|
|
42
|
+
|
|
43
|
+
// src/cab/tiptapPlain.ts
|
|
44
|
+
var isTiptapDoc = (value) => !!value && typeof value === "object" && value.type === "doc";
|
|
45
|
+
var tiptapPlainText = (doc) => listOf(doc?.content).map(
|
|
46
|
+
(paragraph) => listOf(paragraph.content).map((node) => node.text ?? "").join("")
|
|
47
|
+
).join(" ").trim();
|
|
48
|
+
|
|
49
|
+
// src/cab/contentText.ts
|
|
50
|
+
var contentText = (content, language, variables) => {
|
|
51
|
+
const selected = selectLanguage(content, language);
|
|
52
|
+
const text = typeof selected === "string" ? selected : isTiptapDoc(selected) ? tiptapPlainText(selected) : "";
|
|
53
|
+
return interpolateAndSanitize(text, variables);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// src/cab/decoratedOrderId.ts
|
|
57
|
+
var decoratedOrderId = (orderId) => orderId?.match(/\/Order(?:Identity)?\/(\d+)$/)?.[1] ?? orderId;
|
|
58
|
+
|
|
59
|
+
// src/cab/gid.ts
|
|
60
|
+
var idFromGid = (gid) => gid.slice(gid.lastIndexOf("/") + 1);
|
|
61
|
+
|
|
62
|
+
// src/transforms/htmlToTiptap/isValidHref.ts
|
|
63
|
+
var RELATIVE_BASE = "https://x.invalid";
|
|
64
|
+
var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
|
|
65
|
+
var isValidHref = (href) => {
|
|
66
|
+
if (!href) return false;
|
|
67
|
+
if (href === "#") return true;
|
|
68
|
+
if (DYNAMIC_TOKEN_REGEX.test(href)) return true;
|
|
69
|
+
if (href.startsWith("/")) {
|
|
70
|
+
try {
|
|
71
|
+
return new URL(href, RELATIVE_BASE).origin === RELATIVE_BASE;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const url = new URL(href);
|
|
78
|
+
return ALLOWED_PROTOCOLS.has(url.protocol);
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/transforms/htmlToTiptap/linkSafety.ts
|
|
85
|
+
var DEFAULT_LINK_TARGET = "_blank";
|
|
86
|
+
var DEFAULT_LINK_REL = "noopener noreferrer nofollow";
|
|
87
|
+
var ensureSafeRel = (rel, target) => {
|
|
88
|
+
if (target !== DEFAULT_LINK_TARGET) return rel;
|
|
89
|
+
const sourceTokens = (rel ?? "").split(/\s+/).filter(Boolean);
|
|
90
|
+
if (sourceTokens.length === 0) return DEFAULT_LINK_REL;
|
|
91
|
+
const tokens = new Set(sourceTokens.filter((token) => token.toLowerCase() !== "opener"));
|
|
92
|
+
tokens.add("noopener");
|
|
93
|
+
tokens.add("noreferrer");
|
|
94
|
+
return Array.from(tokens).join(" ");
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/cab/tiptapRuns.ts
|
|
98
|
+
var HEADING = "heading";
|
|
99
|
+
var isHeadingMark = (mark) => mark.type === "textStyle" && mark.attrs?.fontSize === HEADING;
|
|
100
|
+
var isRenderableHref = (href) => isValidHref(href) && !DYNAMIC_TOKEN_ANYWHERE_REGEX.test(href);
|
|
101
|
+
var toRun = (node, variables) => {
|
|
102
|
+
const run = {
|
|
103
|
+
bold: false,
|
|
104
|
+
italic: false,
|
|
105
|
+
strike: false,
|
|
106
|
+
text: interpolateAndSanitize(node.text ?? "", variables)
|
|
107
|
+
};
|
|
108
|
+
let link;
|
|
109
|
+
let textStyle;
|
|
110
|
+
for (const mark of listOf(node.marks)) {
|
|
111
|
+
if (mark.type === "bold") run.bold = true;
|
|
112
|
+
else if (mark.type === "italic") run.italic = true;
|
|
113
|
+
else if (mark.type === "strike") run.strike = true;
|
|
114
|
+
else if (mark.type === "link") link = mark;
|
|
115
|
+
else if (mark.type === "textStyle") textStyle = mark;
|
|
116
|
+
}
|
|
117
|
+
const color = textStyle?.attrs?.color;
|
|
118
|
+
const fontSize = textStyle?.attrs?.fontSize;
|
|
119
|
+
if (color) run.color = color;
|
|
120
|
+
if (fontSize && fontSize !== HEADING) run.fontSize = fontSize;
|
|
121
|
+
if (link) {
|
|
122
|
+
const href = interpolateAndSanitize(link.attrs?.href ?? "", variables);
|
|
123
|
+
if (isRenderableHref(href)) {
|
|
124
|
+
const target = link.attrs?.target ?? DEFAULT_LINK_TARGET;
|
|
125
|
+
const rel = ensureSafeRel(link.attrs?.rel, target);
|
|
126
|
+
run.href = href;
|
|
127
|
+
if (rel) run.rel = rel;
|
|
128
|
+
if (target === DEFAULT_LINK_TARGET) run.target = DEFAULT_LINK_TARGET;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return run;
|
|
132
|
+
};
|
|
133
|
+
var tiptapRuns = (paragraph, variables) => {
|
|
134
|
+
const content = listOf(paragraph?.content);
|
|
135
|
+
return {
|
|
136
|
+
heading: content.some((node) => listOf(node.marks).some(isHeadingMark)),
|
|
137
|
+
runs: content.map((node) => toRun(node, variables))
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// src/cab/toCents.ts
|
|
142
|
+
var toCents = (amount) => Math.round(Number(amount ?? 0) * 100);
|
|
143
|
+
export {
|
|
144
|
+
asLayout,
|
|
145
|
+
contentText,
|
|
146
|
+
decoratedOrderId,
|
|
147
|
+
idFromGid,
|
|
148
|
+
interpolateAndSanitize,
|
|
149
|
+
isTiptapDoc,
|
|
150
|
+
selectLanguage,
|
|
151
|
+
tiptapPlainText,
|
|
152
|
+
tiptapRuns,
|
|
153
|
+
toCents
|
|
154
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type Variables = Record<string, string | number | null | undefined>;
|
|
2
|
+
/**
|
|
3
|
+
* Interpolate tokens then strip HTML (with the same tag-shaped regex the schema validates against, so the
|
|
4
|
+
* client never mangles copy the server accepts) — defense-in-depth, since token values can carry HTML from
|
|
5
|
+
* cart/merchant settings. Unknown tokens are left intact so missing bindings surface during dev (and so
|
|
6
|
+
* incidental single-brace text that names no variable is never touched).
|
|
7
|
+
*/
|
|
8
|
+
export declare const interpolateAndSanitize: (input: string, variables?: Variables) => string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw-payload tolerance for the list-shaped Tiptap fields. `TiptapDocument.content`,
|
|
3
|
+
* `TiptapParagraph.content` and `TiptapText.marks` are each
|
|
4
|
+
* `z.union([z.array(…), z.strictObject({}).transform(() => [])])` — that second arm exists because stored
|
|
5
|
+
* payloads serialize an empty list as `{}` — and any of them can carry `null` holes. The CAB renderer
|
|
6
|
+
* helpers read UNPARSED payloads, so none of those shapes may throw; a malformed list degrades to empty
|
|
7
|
+
* rather than killing the section around it.
|
|
8
|
+
*/
|
|
9
|
+
export declare const listOf: <T>(value: unknown) => T[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fallback chain: requested language, then 'en', then first available value.
|
|
3
|
+
*
|
|
4
|
+
* `Object.hasOwn`, never `in` — `in` would resolve an inherited `Object.prototype` key (`constructor`,
|
|
5
|
+
* `toString`) as if it were a translation. Far less reachable than in `interpolateAndSanitize`, since a
|
|
6
|
+
* language code is not merchant-authored, but the two must agree on what counts as present.
|
|
7
|
+
*/
|
|
8
|
+
export declare const selectLanguage: <T>(content: Record<string, T> | undefined | null, language: string | undefined) => T | undefined;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TiptapDocument } from '../schema/widgets/checkout-and-beyond/text';
|
|
2
|
+
/** Locale-resolved text content is either a plain string or a Tiptap document. */
|
|
3
|
+
export declare const isTiptapDoc: (value: unknown) => value is TiptapDocument;
|
|
4
|
+
/**
|
|
5
|
+
* The document's bare text — nodes concatenated, paragraphs space-joined; marks and structure drop.
|
|
6
|
+
*
|
|
7
|
+
* Every level goes through `listOf`, including the doc itself, because this reads raw payloads: the `{}`
|
|
8
|
+
* empty-list arm and `null` holes are both shapes the schema accepts, at any depth.
|
|
9
|
+
*/
|
|
10
|
+
export declare const tiptapPlainText: (doc: TiptapDocument) => string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Variables } from '../cab/interpolateAndSanitize';
|
|
2
|
+
import type { InlineTextSizeName, TextColorName } from '../schema/widgets/checkout-and-beyond/common';
|
|
3
|
+
import type { TiptapParagraph } from '../schema/widgets/checkout-and-beyond/text';
|
|
4
|
+
/**
|
|
5
|
+
* One text run of a paragraph with its marks resolved to facts. Tokens (`color`, `fontSize`) are the
|
|
6
|
+
* payload's — mapping them to px/hex/tone is each renderer's. `bold` is reported even inside a heading;
|
|
7
|
+
* whether the heading element is already bold is the renderer's to know.
|
|
8
|
+
*
|
|
9
|
+
* A run carrying `href` is safe to hand to an anchor as-is: the scheme is allowlisted and, for
|
|
10
|
+
* `target: '_blank'`, `rel` is guaranteed to contain `noopener noreferrer`. Render BOTH — dropping `rel`
|
|
11
|
+
* re-opens reverse tabnabbing on merchant-authored links, which is the per-renderer knowledge this
|
|
12
|
+
* subpath exists to delete. A link whose href fails the allowlist yields a run with no `href`/`rel`/
|
|
13
|
+
* `target` at all, so it renders as plain text rather than as a live unsafe link.
|
|
14
|
+
*/
|
|
15
|
+
export type TiptapRun = {
|
|
16
|
+
bold: boolean;
|
|
17
|
+
color?: TextColorName;
|
|
18
|
+
fontSize?: InlineTextSizeName;
|
|
19
|
+
href?: string;
|
|
20
|
+
italic: boolean;
|
|
21
|
+
rel?: string;
|
|
22
|
+
strike: boolean;
|
|
23
|
+
target?: '_blank';
|
|
24
|
+
text: string;
|
|
25
|
+
};
|
|
26
|
+
/** `heading` promotes the WHOLE paragraph (a block signal, never an inline size — see `textSizeNames`). */
|
|
27
|
+
export type TiptapParagraphRuns = {
|
|
28
|
+
heading: boolean;
|
|
29
|
+
runs: TiptapRun[];
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A Tiptap paragraph as ordered styled runs plus its heading promotion — the one mark reading every
|
|
33
|
+
* renderer shares.
|
|
34
|
+
*
|
|
35
|
+
* Takes a payload in EITHER shape: schema-parsed, or raw off the wire. Malformed lists degrade to empty
|
|
36
|
+
* rather than throwing — including a nullish paragraph, so a renderer can map this straight over a raw
|
|
37
|
+
* `doc.content` — and the two link fields the schema defaults (`target`, `rel`) are resolved to the same
|
|
38
|
+
* effective values a parse would have produced, so a run never depends on whether the caller parsed.
|
|
39
|
+
*/
|
|
40
|
+
export declare const tiptapRuns: (paragraph: TiptapParagraph, variables?: Variables) => TiptapParagraphRuns;
|
package/dist/index.cjs
CHANGED
|
@@ -48,6 +48,7 @@ __export(src_exports, {
|
|
|
48
48
|
CABImageSectionFields: () => CABImageSectionFields,
|
|
49
49
|
CABIntegrations: () => CABIntegrations,
|
|
50
50
|
CABLayoutSection: () => CABLayoutSection,
|
|
51
|
+
CABMonetizeAds: () => CABMonetizeAds,
|
|
51
52
|
CABMonetizeSection: () => CABMonetizeSection,
|
|
52
53
|
CABNumberVariable: () => CABNumberVariable,
|
|
53
54
|
CABOffersSection: () => CABOffersSection,
|
|
@@ -90,6 +91,7 @@ __export(src_exports, {
|
|
|
90
91
|
ContentOperator: () => ContentOperator,
|
|
91
92
|
DEFAULT_ENDPOINTS: () => DEFAULT_ENDPOINTS,
|
|
92
93
|
DESCRIPTION_LABELS: () => DESCRIPTION_LABELS,
|
|
94
|
+
DYNAMIC_TOKEN_ANYWHERE_REGEX: () => DYNAMIC_TOKEN_ANYWHERE_REGEX,
|
|
93
95
|
DYNAMIC_TOKEN_REGEX: () => DYNAMIC_TOKEN_REGEX,
|
|
94
96
|
Direction: () => Direction,
|
|
95
97
|
DiscountSource: () => DiscountSource,
|
|
@@ -1936,6 +1938,7 @@ var import_compat = require("es-toolkit/compat");
|
|
|
1936
1938
|
// src/schema/widgets/checkout-and-beyond/regex.ts
|
|
1937
1939
|
var ARRAY_INDEX_STRING = /^(0|[1-9]\d*)$/;
|
|
1938
1940
|
var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
|
|
1941
|
+
var DYNAMIC_TOKEN_ANYWHERE_REGEX = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/;
|
|
1939
1942
|
var HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
1940
1943
|
var ROOT_RELATIVE_PATH_REGEX = /^\/(?!\/)/;
|
|
1941
1944
|
var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
|
|
@@ -2619,8 +2622,14 @@ var CABDiscountSection = import_zod22.z.object({
|
|
|
2619
2622
|
// src/schema/widgets/checkout-and-beyond/monetize.ts
|
|
2620
2623
|
var import_uuid9 = require("uuid");
|
|
2621
2624
|
var import_zod23 = require("zod");
|
|
2625
|
+
var CABMonetizeAds = import_zod23.z.object({
|
|
2626
|
+
requestId: import_zod23.z.string().optional(),
|
|
2627
|
+
/** Defaulted here, never in the beacon: absence of information is `ads` itself being absent. */
|
|
2628
|
+
testing: import_zod23.z.boolean().default(false)
|
|
2629
|
+
});
|
|
2622
2630
|
var CABMonetizeSection = import_zod23.z.object({
|
|
2623
2631
|
...CABVariableFields,
|
|
2632
|
+
ads: CABMonetizeAds.optional(),
|
|
2624
2633
|
name: import_zod23.z.string().optional(),
|
|
2625
2634
|
/**
|
|
2626
2635
|
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
package/dist/index.mjs
CHANGED
|
@@ -1642,6 +1642,7 @@ import { get as get2, isString } from "es-toolkit/compat";
|
|
|
1642
1642
|
// src/schema/widgets/checkout-and-beyond/regex.ts
|
|
1643
1643
|
var ARRAY_INDEX_STRING = /^(0|[1-9]\d*)$/;
|
|
1644
1644
|
var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
|
|
1645
|
+
var DYNAMIC_TOKEN_ANYWHERE_REGEX = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/;
|
|
1645
1646
|
var HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
1646
1647
|
var ROOT_RELATIVE_PATH_REGEX = /^\/(?!\/)/;
|
|
1647
1648
|
var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
|
|
@@ -2325,8 +2326,14 @@ var CABDiscountSection = z22.object({
|
|
|
2325
2326
|
// src/schema/widgets/checkout-and-beyond/monetize.ts
|
|
2326
2327
|
import { v7 as uuidv78 } from "uuid";
|
|
2327
2328
|
import { z as z23 } from "zod";
|
|
2329
|
+
var CABMonetizeAds = z23.object({
|
|
2330
|
+
requestId: z23.string().optional(),
|
|
2331
|
+
/** Defaulted here, never in the beacon: absence of information is `ads` itself being absent. */
|
|
2332
|
+
testing: z23.boolean().default(false)
|
|
2333
|
+
});
|
|
2328
2334
|
var CABMonetizeSection = z23.object({
|
|
2329
2335
|
...CABVariableFields,
|
|
2336
|
+
ads: CABMonetizeAds.optional(),
|
|
2330
2337
|
name: z23.string().optional(),
|
|
2331
2338
|
/**
|
|
2332
2339
|
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
@@ -4873,6 +4880,7 @@ export {
|
|
|
4873
4880
|
CABImageSectionFields,
|
|
4874
4881
|
CABIntegrations,
|
|
4875
4882
|
CABLayoutSection,
|
|
4883
|
+
CABMonetizeAds,
|
|
4876
4884
|
CABMonetizeSection,
|
|
4877
4885
|
CABNumberVariable,
|
|
4878
4886
|
CABOffersSection,
|
|
@@ -4915,6 +4923,7 @@ export {
|
|
|
4915
4923
|
ContentOperator,
|
|
4916
4924
|
DEFAULT_ENDPOINTS,
|
|
4917
4925
|
DESCRIPTION_LABELS,
|
|
4926
|
+
DYNAMIC_TOKEN_ANYWHERE_REGEX,
|
|
4918
4927
|
DYNAMIC_TOKEN_REGEX,
|
|
4919
4928
|
Direction,
|
|
4920
4929
|
DiscountSource,
|
|
@@ -45,6 +45,7 @@ __export(checkout_and_beyond_exports, {
|
|
|
45
45
|
CABImageSectionFields: () => CABImageSectionFields,
|
|
46
46
|
CABIntegrations: () => CABIntegrations,
|
|
47
47
|
CABLayoutSection: () => CABLayoutSection,
|
|
48
|
+
CABMonetizeAds: () => CABMonetizeAds,
|
|
48
49
|
CABMonetizeSection: () => CABMonetizeSection,
|
|
49
50
|
CABNumberVariable: () => CABNumberVariable,
|
|
50
51
|
CABOffersSection: () => CABOffersSection,
|
|
@@ -79,6 +80,7 @@ __export(checkout_and_beyond_exports, {
|
|
|
79
80
|
ContentOperator: () => ContentOperator,
|
|
80
81
|
DEFAULT_ENDPOINTS: () => DEFAULT_ENDPOINTS,
|
|
81
82
|
DESCRIPTION_LABELS: () => DESCRIPTION_LABELS,
|
|
83
|
+
DYNAMIC_TOKEN_ANYWHERE_REGEX: () => DYNAMIC_TOKEN_ANYWHERE_REGEX,
|
|
82
84
|
DYNAMIC_TOKEN_REGEX: () => DYNAMIC_TOKEN_REGEX,
|
|
83
85
|
Direction: () => Direction,
|
|
84
86
|
DiscountSource: () => DiscountSource,
|
|
@@ -510,6 +512,7 @@ var IconTone = freezeEnum(iconTones);
|
|
|
510
512
|
// src/schema/widgets/checkout-and-beyond/regex.ts
|
|
511
513
|
var ARRAY_INDEX_STRING = /^(0|[1-9]\d*)$/;
|
|
512
514
|
var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
|
|
515
|
+
var DYNAMIC_TOKEN_ANYWHERE_REGEX = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/;
|
|
513
516
|
var HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
514
517
|
var ROOT_RELATIVE_PATH_REGEX = /^\/(?!\/)/;
|
|
515
518
|
var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
|
|
@@ -1132,8 +1135,14 @@ var CABDiscountSection = import_zod12.z.object({
|
|
|
1132
1135
|
// src/schema/widgets/checkout-and-beyond/monetize.ts
|
|
1133
1136
|
var import_uuid8 = require("uuid");
|
|
1134
1137
|
var import_zod13 = require("zod");
|
|
1138
|
+
var CABMonetizeAds = import_zod13.z.object({
|
|
1139
|
+
requestId: import_zod13.z.string().optional(),
|
|
1140
|
+
/** Defaulted here, never in the beacon: absence of information is `ads` itself being absent. */
|
|
1141
|
+
testing: import_zod13.z.boolean().default(false)
|
|
1142
|
+
});
|
|
1135
1143
|
var CABMonetizeSection = import_zod13.z.object({
|
|
1136
1144
|
...CABVariableFields,
|
|
1145
|
+
ads: CABMonetizeAds.optional(),
|
|
1137
1146
|
name: import_zod13.z.string().optional(),
|
|
1138
1147
|
/**
|
|
1139
1148
|
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
@@ -306,6 +306,7 @@ var IconTone = freezeEnum(iconTones);
|
|
|
306
306
|
// src/schema/widgets/checkout-and-beyond/regex.ts
|
|
307
307
|
var ARRAY_INDEX_STRING = /^(0|[1-9]\d*)$/;
|
|
308
308
|
var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
|
|
309
|
+
var DYNAMIC_TOKEN_ANYWHERE_REGEX = /\{\{\s*[A-Za-z0-9_]+\s*\}\}/;
|
|
309
310
|
var HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
310
311
|
var ROOT_RELATIVE_PATH_REGEX = /^\/(?!\/)/;
|
|
311
312
|
var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
|
|
@@ -928,8 +929,14 @@ var CABDiscountSection = z12.object({
|
|
|
928
929
|
// src/schema/widgets/checkout-and-beyond/monetize.ts
|
|
929
930
|
import { v7 as uuidv78 } from "uuid";
|
|
930
931
|
import { z as z13 } from "zod";
|
|
932
|
+
var CABMonetizeAds = z13.object({
|
|
933
|
+
requestId: z13.string().optional(),
|
|
934
|
+
/** Defaulted here, never in the beacon: absence of information is `ads` itself being absent. */
|
|
935
|
+
testing: z13.boolean().default(false)
|
|
936
|
+
});
|
|
931
937
|
var CABMonetizeSection = z13.object({
|
|
932
938
|
...CABVariableFields,
|
|
939
|
+
ads: CABMonetizeAds.optional(),
|
|
933
940
|
name: z13.string().optional(),
|
|
934
941
|
/**
|
|
935
942
|
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
@@ -1908,6 +1915,7 @@ export {
|
|
|
1908
1915
|
CABImageSectionFields,
|
|
1909
1916
|
CABIntegrations,
|
|
1910
1917
|
CABLayoutSection,
|
|
1918
|
+
CABMonetizeAds,
|
|
1911
1919
|
CABMonetizeSection,
|
|
1912
1920
|
CABNumberVariable,
|
|
1913
1921
|
CABOffersSection,
|
|
@@ -1942,6 +1950,7 @@ export {
|
|
|
1942
1950
|
ContentOperator,
|
|
1943
1951
|
DEFAULT_ENDPOINTS,
|
|
1944
1952
|
DESCRIPTION_LABELS,
|
|
1953
|
+
DYNAMIC_TOKEN_ANYWHERE_REGEX,
|
|
1945
1954
|
DYNAMIC_TOKEN_REGEX,
|
|
1946
1955
|
Direction,
|
|
1947
1956
|
DiscountSource,
|
|
@@ -966,8 +966,14 @@ var CABDiscountSection = import_zod15.z.object({
|
|
|
966
966
|
// src/schema/widgets/checkout-and-beyond/monetize.ts
|
|
967
967
|
var import_uuid8 = require("uuid");
|
|
968
968
|
var import_zod16 = require("zod");
|
|
969
|
+
var CABMonetizeAds = import_zod16.z.object({
|
|
970
|
+
requestId: import_zod16.z.string().optional(),
|
|
971
|
+
/** Defaulted here, never in the beacon: absence of information is `ads` itself being absent. */
|
|
972
|
+
testing: import_zod16.z.boolean().default(false)
|
|
973
|
+
});
|
|
969
974
|
var CABMonetizeSection = import_zod16.z.object({
|
|
970
975
|
...CABVariableFields,
|
|
976
|
+
ads: CABMonetizeAds.optional(),
|
|
971
977
|
name: import_zod16.z.string().optional(),
|
|
972
978
|
/**
|
|
973
979
|
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|
|
@@ -941,8 +941,14 @@ var CABDiscountSection = z15.object({
|
|
|
941
941
|
// src/schema/widgets/checkout-and-beyond/monetize.ts
|
|
942
942
|
import { v7 as uuidv78 } from "uuid";
|
|
943
943
|
import { z as z16 } from "zod";
|
|
944
|
+
var CABMonetizeAds = z16.object({
|
|
945
|
+
requestId: z16.string().optional(),
|
|
946
|
+
/** Defaulted here, never in the beacon: absence of information is `ads` itself being absent. */
|
|
947
|
+
testing: z16.boolean().default(false)
|
|
948
|
+
});
|
|
944
949
|
var CABMonetizeSection = z16.object({
|
|
945
950
|
...CABVariableFields,
|
|
951
|
+
ads: CABMonetizeAds.optional(),
|
|
946
952
|
name: z16.string().optional(),
|
|
947
953
|
/**
|
|
948
954
|
* Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
|