@modainteract/moda-interact-shared 5.8.0 → 5.10.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.
|
@@ -33,6 +33,7 @@ import { z } from "zod";
|
|
|
33
33
|
var hostId = z.string().trim().min(1).max(191);
|
|
34
34
|
var positive = z.number().int().positive().safe();
|
|
35
35
|
var https = z.string().max(2048).url().refine((v) => {
|
|
36
|
+
if (!URL.canParse(v)) return false;
|
|
36
37
|
const u = new URL(v);
|
|
37
38
|
return u.protocol === "https:" && !u.username && !u.password;
|
|
38
39
|
});
|
|
@@ -88,7 +89,21 @@ var AssignedVariantSchema = z2.strictObject({ id: hostId, shopId: hostId, provid
|
|
|
88
89
|
var ProjectionSchema = z2.strictObject({ variant: AssignedVariantSchema, canonicalLanguageTag: text, selectionSource: z2.enum(["exact", "base", "merchant-fallback", "platform-fallback"]) });
|
|
89
90
|
var PreparedProjectionSchema = ProjectionSchema.extend({ textFallback: ProjectionSchema.nullable().optional() });
|
|
90
91
|
var ReadyMediaSchema = z2.strictObject({ shopId: hostId, role: z2.enum(["LOGO", "SQUARE_LOGO", "COVER_IMAGE", "CHECKOUT_PRODUCT"]), status: z2.literal("READY"), kind: z2.literal("R2_DERIVATIVE"), httpsUrl: https });
|
|
91
|
-
var
|
|
92
|
+
var PreparedOutreachBrandSchema = z2.strictObject({
|
|
93
|
+
shopId: hostId,
|
|
94
|
+
brandName: z2.string().max(200).nullable(),
|
|
95
|
+
logoAltText: z2.string().max(200).nullable(),
|
|
96
|
+
logoWidth: z2.number().int().min(1).max(1e4).nullable(),
|
|
97
|
+
logoHeight: z2.number().int().min(1).max(1e4).nullable(),
|
|
98
|
+
squareLogoAltText: z2.string().max(200).nullable()
|
|
99
|
+
});
|
|
100
|
+
var PreparedOutreachBrandMediaItemSchema = ReadyMediaSchema;
|
|
101
|
+
var PreparedOutreachBrandMediaSchema = z2.strictObject({
|
|
102
|
+
logo: PreparedOutreachBrandMediaItemSchema.nullable(),
|
|
103
|
+
squareLogo: PreparedOutreachBrandMediaItemSchema.nullable(),
|
|
104
|
+
coverImage: PreparedOutreachBrandMediaItemSchema.nullable()
|
|
105
|
+
});
|
|
106
|
+
var InitialOutreachRenderInputSchema = z2.strictObject({ schemaVersion: z2.literal(1), evaluation: ReadyEvaluationSchema, preparedProjection: PreparedProjectionSchema.nullable(), variables: z2.strictObject({ shopName: z2.string().max(200).nullable(), customerFirstName: z2.string().max(200).nullable(), checkoutUrl: https, shopCategory: z2.string().max(200).nullable() }), headerMedia: ReadyMediaSchema.nullable(), preparedBrand: PreparedOutreachBrandSchema.nullable().optional(), preparedBrandMedia: PreparedOutreachBrandMediaSchema.nullable().optional() });
|
|
92
107
|
var RenderReasonSchema = z2.enum(["INVALID_REQUEST", "INVALID_LANGUAGE", "SHOP_UNAVAILABLE", "SENDER_UNAVAILABLE", "SENDER_MISMATCH", "NO_LANGUAGE", "NO_APPROVED_ASSIGNMENT", "LEGACY_ASSIGNMENT", "AMBIGUOUS_ASSIGNMENT", "REGISTRATION_UNAVAILABLE", "PROVIDER_UNAVAILABLE", "PROVIDER_DRIFT", "IDENTITY_MISMATCH", "DEFINITION_UNAVAILABLE", "LOCALE_UNAVAILABLE", "BINDINGS_INVALID", "BINDING_FINGERPRINT_MISMATCH", "BINDING_VALUE_UNAVAILABLE", "CHECKOUT_UNAVAILABLE", "CHECKOUT_UNTRUSTED", "MEDIA_NOT_READY", "PROMOTION_PRODUCT_URL_UNAVAILABLE", "MESSAGE_TOO_LARGE"]);
|
|
93
108
|
var TextParameterSchema = z2.strictObject({ type: z2.literal("text"), text: z2.string().max(4096) });
|
|
94
109
|
var PreparedComponentSchema = z2.union([
|
|
@@ -100,6 +115,12 @@ var InitialOutreachRenderResultSchema = z2.discriminatedUnion("kind", [
|
|
|
100
115
|
z2.strictObject({ kind: z2.literal("ready"), purpose: z2.enum(["INITIAL_NO_OFFER", "INITIAL_CODE_OFFER", "INITIAL_AUTOMATIC_OFFER"]), templateProvenance: z2.strictObject({ variantId: hostId, registrationId: hostId, definitionId: hostId, providerTemplateId: hostId, providerTemplateName: text, providerLanguageCode: text, submittedContentHash: HashSchema, bindingFingerprint: HashSchema }), parameters: z2.array(PreparedComponentSchema).max(4), headerPreview: ReadyMediaSchema.nullable(), renderedText: z2.string().max(8192) }),
|
|
101
116
|
z2.strictObject({ kind: z2.literal("refused"), reasonCode: RenderReasonSchema })
|
|
102
117
|
]);
|
|
118
|
+
var StudioOutreachTemplateSchema = z2.strictObject({ templateId: hostId, localeId: hostId, languageTag: z2.string().min(1).max(512), components: z2.array(ComponentSchema).min(1).max(4), bindings: FrozenBindingsSchema });
|
|
119
|
+
var StudioInitialOutreachRenderInputSchema = InitialOutreachRenderInputSchema.omit({ preparedProjection: true }).extend({ template: StudioOutreachTemplateSchema });
|
|
120
|
+
var StudioInitialOutreachRenderResultSchema = z2.discriminatedUnion("kind", [
|
|
121
|
+
z2.strictObject({ kind: z2.literal("ready"), renderedText: z2.string().max(8192), parameters: z2.array(PreparedComponentSchema).max(4), headerPreview: ReadyMediaSchema.nullable(), templateId: hostId, localeId: hostId, templateValidation: z2.literal("NOT_EVALUATED") }),
|
|
122
|
+
z2.strictObject({ kind: z2.literal("refused"), reasonCode: RenderReasonSchema })
|
|
123
|
+
]);
|
|
103
124
|
|
|
104
125
|
// src/commerce/outreach/evaluate.ts
|
|
105
126
|
import { z as z3 } from "zod";
|
|
@@ -773,6 +794,188 @@ async function evaluateInitialOutreach(raw, ports) {
|
|
|
773
794
|
}
|
|
774
795
|
var evaluateInitialOutreachPreview = evaluateInitialOutreach;
|
|
775
796
|
|
|
797
|
+
// src/commerce/outreach/recovery-template-items-summary.ts
|
|
798
|
+
var MAX_LINE_ITEMS = 100;
|
|
799
|
+
var MAX_DISPLAYED_ITEMS = 3;
|
|
800
|
+
var MAX_TITLE_LENGTH = 160;
|
|
801
|
+
var MAX_SUMMARY_LENGTH = 400;
|
|
802
|
+
function safeText(value, limit) {
|
|
803
|
+
if (typeof value !== "string")
|
|
804
|
+
return null;
|
|
805
|
+
const text2 = value.normalize("NFC").trim().replace(/\s+/gu, " ");
|
|
806
|
+
if (!text2 || text2.length > limit || /[\p{Cc}\p{Cf}{}]/u.test(text2) || text2.includes("{{") || text2.includes("}}"))
|
|
807
|
+
return null;
|
|
808
|
+
try {
|
|
809
|
+
encodeURIComponent(text2);
|
|
810
|
+
} catch {
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
return text2;
|
|
814
|
+
}
|
|
815
|
+
function parseItem(value) {
|
|
816
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
817
|
+
return null;
|
|
818
|
+
const item = value;
|
|
819
|
+
const title = safeText(item.title, MAX_TITLE_LENGTH);
|
|
820
|
+
const quantity = item.quantity;
|
|
821
|
+
if (!title || typeof quantity !== "number" || !Number.isSafeInteger(quantity) || quantity < 1 || quantity > 99999)
|
|
822
|
+
return null;
|
|
823
|
+
const variant = safeText(item.variantTitle, MAX_TITLE_LENGTH);
|
|
824
|
+
const displayTitle = variant && variant.toLowerCase() !== "default title" && variant !== title ? `${title} (${variant})` : title;
|
|
825
|
+
return { title: displayTitle, quantity };
|
|
826
|
+
}
|
|
827
|
+
function formatCheckoutItemsSummary(lineItems, languageTag) {
|
|
828
|
+
if (!Array.isArray(lineItems) || lineItems.length === 0 || lineItems.length > MAX_LINE_ITEMS)
|
|
829
|
+
return null;
|
|
830
|
+
let locale;
|
|
831
|
+
try {
|
|
832
|
+
locale = Intl.getCanonicalLocales(languageTag)[0] ?? "en";
|
|
833
|
+
} catch {
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
const items = lineItems.map(parseItem).filter((item) => item !== null);
|
|
837
|
+
if (!items.length)
|
|
838
|
+
return null;
|
|
839
|
+
const numbers = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
|
|
840
|
+
const list = new Intl.ListFormat(locale, { style: "long", type: "conjunction" });
|
|
841
|
+
const visible = items.slice(0, MAX_DISPLAYED_ITEMS).map(({ title, quantity }) => `${numbers.format(quantity)} \xD7 ${title}`);
|
|
842
|
+
if (items.length > MAX_DISPLAYED_ITEMS) {
|
|
843
|
+
visible.push(`+${numbers.format(items.length - MAX_DISPLAYED_ITEMS)}`);
|
|
844
|
+
}
|
|
845
|
+
const summary = list.format(visible);
|
|
846
|
+
if (summary.length <= MAX_SUMMARY_LENGTH)
|
|
847
|
+
return summary;
|
|
848
|
+
return `${Array.from(summary).slice(0, MAX_SUMMARY_LENGTH - 1).join("").trimEnd()}\u2026`;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// src/commerce/outreach/variables.ts
|
|
852
|
+
function isSemanticKey(v) {
|
|
853
|
+
return SemanticKeySchema.safeParse(v).success;
|
|
854
|
+
}
|
|
855
|
+
var BindingUnavailable = class extends Error {
|
|
856
|
+
constructor(reason) {
|
|
857
|
+
super(reason);
|
|
858
|
+
this.reason = reason;
|
|
859
|
+
}
|
|
860
|
+
reason;
|
|
861
|
+
};
|
|
862
|
+
function boundedText(v, limit) {
|
|
863
|
+
if (typeof v !== "string" || !v.trim() || v.length > limit || /[\x00-\x1f\x7f\u2028\u2029]/u.test(v) || v.includes("{{") || v.includes("}}"))
|
|
864
|
+
throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
|
|
865
|
+
try {
|
|
866
|
+
encodeURIComponent(v);
|
|
867
|
+
} catch {
|
|
868
|
+
throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
|
|
869
|
+
}
|
|
870
|
+
return v.trim();
|
|
871
|
+
}
|
|
872
|
+
var PreparedVariableResolver = class {
|
|
873
|
+
constructor(input, languageTag, sendable) {
|
|
874
|
+
this.input = input;
|
|
875
|
+
this.languageTag = languageTag;
|
|
876
|
+
this.sendable = sendable;
|
|
877
|
+
}
|
|
878
|
+
input;
|
|
879
|
+
languageTag;
|
|
880
|
+
sendable;
|
|
881
|
+
encodeCheckoutButtonSuffix(_prefix) {
|
|
882
|
+
return encodeURIComponent(this.input.variables.checkoutUrl);
|
|
883
|
+
}
|
|
884
|
+
encodePromotionButtonSuffix(_prefix) {
|
|
885
|
+
throw new BindingUnavailable("PROMOTION_PRODUCT_URL_UNAVAILABLE");
|
|
886
|
+
}
|
|
887
|
+
checkStaticButtonUrl(value) {
|
|
888
|
+
const u = new URL(value), checkout = new URL(this.input.variables.checkoutUrl);
|
|
889
|
+
if (u.protocol !== "https:" || u.username || u.password || u.hash || u.hostname !== checkout.hostname)
|
|
890
|
+
throw new BindingUnavailable("CHECKOUT_UNTRUSTED");
|
|
891
|
+
}
|
|
892
|
+
imageHeader(role) {
|
|
893
|
+
const m = this.input.headerMedia;
|
|
894
|
+
if (!m || m.shopId !== this.input.evaluation.shopId || m.role !== role)
|
|
895
|
+
throw new BindingUnavailable("MEDIA_NOT_READY");
|
|
896
|
+
return m.httpsUrl;
|
|
897
|
+
}
|
|
898
|
+
brandMedia(role) {
|
|
899
|
+
const media = this.input.preparedBrandMedia?.[role];
|
|
900
|
+
if (!media) throw new BindingUnavailable("MEDIA_NOT_READY");
|
|
901
|
+
boundedText(media.httpsUrl, 2048);
|
|
902
|
+
return media.httpsUrl;
|
|
903
|
+
}
|
|
904
|
+
resolve(key) {
|
|
905
|
+
const i = this.input, d = this.sendable;
|
|
906
|
+
switch (key) {
|
|
907
|
+
case "customer.firstName":
|
|
908
|
+
return boundedText(i.variables.customerFirstName, 200);
|
|
909
|
+
case "shop.name":
|
|
910
|
+
return boundedText(i.variables.shopName, 200);
|
|
911
|
+
case "shop.categoryName":
|
|
912
|
+
return boundedText(i.variables.shopCategory, 200);
|
|
913
|
+
case "checkout.url":
|
|
914
|
+
return i.variables.checkoutUrl;
|
|
915
|
+
case "checkout.itemsSummary": {
|
|
916
|
+
const items = i.evaluation.lines.map((l) => ({ title: l.currentProduct.title, quantity: l.quantity, variantTitle: l.currentProduct.selectedOptions?.map((o) => o.value).join(" / ") || null }));
|
|
917
|
+
if (items.some((l) => l.quantity > 99999 || /[\p{Cc}\p{Cf}{}]/u.test(l.title) || l.title.length > 160 || l.variantTitle !== null && (/[\p{Cc}\p{Cf}{}]/u.test(l.variantTitle) || l.variantTitle.length > 160)))
|
|
918
|
+
throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
|
|
919
|
+
return boundedText(formatCheckoutItemsSummary(items, this.languageTag), 500);
|
|
920
|
+
}
|
|
921
|
+
case "offer.code":
|
|
922
|
+
if (d.presentation === "CODE_OFFER" && d.members.length === 1 && d.members[0]?.method === "CODE")
|
|
923
|
+
return boundedText(d.members[0].code, 100);
|
|
924
|
+
break;
|
|
925
|
+
case "offer.codes":
|
|
926
|
+
if (d.presentation === "CODE_OFFER" && d.members.every((m) => m.method === "CODE"))
|
|
927
|
+
return d.members.map((m) => boundedText(m.code, 100)).join(", ");
|
|
928
|
+
break;
|
|
929
|
+
case "offer.description":
|
|
930
|
+
if (d.presentation !== "NO_OFFER")
|
|
931
|
+
return boundedText(d.members.map((m) => m.description).join(", "), 200);
|
|
932
|
+
break;
|
|
933
|
+
case "shopBrand.brandName":
|
|
934
|
+
return boundedText(i.preparedBrand?.brandName, 200);
|
|
935
|
+
case "shopBrand.logoAltText":
|
|
936
|
+
return boundedText(i.preparedBrand?.logoAltText, 200);
|
|
937
|
+
case "shopBrand.squareLogoAltText":
|
|
938
|
+
return boundedText(i.preparedBrand?.squareLogoAltText, 200);
|
|
939
|
+
case "shopBrand.logoWidth":
|
|
940
|
+
case "shopBrand.logoHeight": {
|
|
941
|
+
const value = key === "shopBrand.logoWidth" ? i.preparedBrand?.logoWidth : i.preparedBrand?.logoHeight;
|
|
942
|
+
if (value === null || value === void 0) throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
|
|
943
|
+
return boundedText(String(value), 200);
|
|
944
|
+
}
|
|
945
|
+
case "shopBrand.logoUrl":
|
|
946
|
+
return this.brandMedia("logo");
|
|
947
|
+
case "shopBrand.squareLogoUrl":
|
|
948
|
+
return this.brandMedia("squareLogo");
|
|
949
|
+
case "shopBrand.coverImageUrl":
|
|
950
|
+
return this.brandMedia("coverImage");
|
|
951
|
+
}
|
|
952
|
+
throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
// src/commerce/outreach/component-render.ts
|
|
957
|
+
function renderStoredComponentText(components, parameters) {
|
|
958
|
+
function substitute(text3, type) {
|
|
959
|
+
const values = parameters.find((p) => p.type === type)?.parameters ?? [];
|
|
960
|
+
return text3.replace(/\{\{([1-9][0-9]*)\}\}/g, (_, position) => {
|
|
961
|
+
const value = values[Number(position) - 1];
|
|
962
|
+
if (!value || value.type !== "text") throw new BindingUnavailable("BINDINGS_INVALID");
|
|
963
|
+
return value.text;
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
const body = components.find((c) => c.type === "BODY");
|
|
967
|
+
if (!body || body.type !== "BODY") throw new BindingUnavailable("BINDINGS_INVALID");
|
|
968
|
+
if (substitute(body.text, "body").length > 4096) throw new BindingUnavailable("MESSAGE_TOO_LARGE");
|
|
969
|
+
const text2 = components.flatMap((c) => {
|
|
970
|
+
if (c.type === "BODY") return [substitute(c.text, "body")];
|
|
971
|
+
if (c.type === "FOOTER") return [c.text];
|
|
972
|
+
if (c.type === "HEADER" && c.format === "TEXT") return [substitute(c.text, "header")];
|
|
973
|
+
return [];
|
|
974
|
+
}).join("\n");
|
|
975
|
+
if (text2.length > 8192) throw new BindingUnavailable("MESSAGE_TOO_LARGE");
|
|
976
|
+
return text2;
|
|
977
|
+
}
|
|
978
|
+
|
|
776
979
|
// src/commerce/outreach/initial-checkout-items-policy.ts
|
|
777
980
|
function decideInitialCheckoutItemsBinding(input) {
|
|
778
981
|
if (input.stage === "FOLLOW_UP")
|
|
@@ -940,152 +1143,6 @@ function selectRecoveryAssignment(input) {
|
|
|
940
1143
|
|
|
941
1144
|
// src/commerce/outreach/bindings.ts
|
|
942
1145
|
import { createHash as createHash2 } from "crypto";
|
|
943
|
-
|
|
944
|
-
// src/commerce/outreach/recovery-template-items-summary.ts
|
|
945
|
-
var MAX_LINE_ITEMS = 100;
|
|
946
|
-
var MAX_DISPLAYED_ITEMS = 3;
|
|
947
|
-
var MAX_TITLE_LENGTH = 160;
|
|
948
|
-
var MAX_SUMMARY_LENGTH = 400;
|
|
949
|
-
function safeText(value, limit) {
|
|
950
|
-
if (typeof value !== "string")
|
|
951
|
-
return null;
|
|
952
|
-
const text2 = value.normalize("NFC").trim().replace(/\s+/gu, " ");
|
|
953
|
-
if (!text2 || text2.length > limit || /[\p{Cc}\p{Cf}{}]/u.test(text2) || text2.includes("{{") || text2.includes("}}"))
|
|
954
|
-
return null;
|
|
955
|
-
try {
|
|
956
|
-
encodeURIComponent(text2);
|
|
957
|
-
} catch {
|
|
958
|
-
return null;
|
|
959
|
-
}
|
|
960
|
-
return text2;
|
|
961
|
-
}
|
|
962
|
-
function parseItem(value) {
|
|
963
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
964
|
-
return null;
|
|
965
|
-
const item = value;
|
|
966
|
-
const title = safeText(item.title, MAX_TITLE_LENGTH);
|
|
967
|
-
const quantity = item.quantity;
|
|
968
|
-
if (!title || typeof quantity !== "number" || !Number.isSafeInteger(quantity) || quantity < 1 || quantity > 99999)
|
|
969
|
-
return null;
|
|
970
|
-
const variant = safeText(item.variantTitle, MAX_TITLE_LENGTH);
|
|
971
|
-
const displayTitle = variant && variant.toLowerCase() !== "default title" && variant !== title ? `${title} (${variant})` : title;
|
|
972
|
-
return { title: displayTitle, quantity };
|
|
973
|
-
}
|
|
974
|
-
function formatCheckoutItemsSummary(lineItems, languageTag) {
|
|
975
|
-
if (!Array.isArray(lineItems) || lineItems.length === 0 || lineItems.length > MAX_LINE_ITEMS)
|
|
976
|
-
return null;
|
|
977
|
-
let locale;
|
|
978
|
-
try {
|
|
979
|
-
locale = Intl.getCanonicalLocales(languageTag)[0] ?? "en";
|
|
980
|
-
} catch {
|
|
981
|
-
return null;
|
|
982
|
-
}
|
|
983
|
-
const items = lineItems.map(parseItem).filter((item) => item !== null);
|
|
984
|
-
if (!items.length)
|
|
985
|
-
return null;
|
|
986
|
-
const numbers = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
|
|
987
|
-
const list = new Intl.ListFormat(locale, { style: "long", type: "conjunction" });
|
|
988
|
-
const visible = items.slice(0, MAX_DISPLAYED_ITEMS).map(({ title, quantity }) => `${numbers.format(quantity)} \xD7 ${title}`);
|
|
989
|
-
if (items.length > MAX_DISPLAYED_ITEMS) {
|
|
990
|
-
visible.push(`+${numbers.format(items.length - MAX_DISPLAYED_ITEMS)}`);
|
|
991
|
-
}
|
|
992
|
-
const summary = list.format(visible);
|
|
993
|
-
if (summary.length <= MAX_SUMMARY_LENGTH)
|
|
994
|
-
return summary;
|
|
995
|
-
return `${Array.from(summary).slice(0, MAX_SUMMARY_LENGTH - 1).join("").trimEnd()}\u2026`;
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
// src/commerce/outreach/variables.ts
|
|
999
|
-
function isSemanticKey(v) {
|
|
1000
|
-
return SemanticKeySchema.safeParse(v).success;
|
|
1001
|
-
}
|
|
1002
|
-
var BindingUnavailable = class extends Error {
|
|
1003
|
-
constructor(reason) {
|
|
1004
|
-
super(reason);
|
|
1005
|
-
this.reason = reason;
|
|
1006
|
-
}
|
|
1007
|
-
reason;
|
|
1008
|
-
};
|
|
1009
|
-
function boundedText(v, limit) {
|
|
1010
|
-
if (typeof v !== "string" || !v.trim() || v.length > limit || /[\x00-\x1f\x7f\u2028\u2029]/u.test(v) || v.includes("{{") || v.includes("}}"))
|
|
1011
|
-
throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
|
|
1012
|
-
try {
|
|
1013
|
-
encodeURIComponent(v);
|
|
1014
|
-
} catch {
|
|
1015
|
-
throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
|
|
1016
|
-
}
|
|
1017
|
-
return v.trim();
|
|
1018
|
-
}
|
|
1019
|
-
var PreparedVariableResolver = class {
|
|
1020
|
-
constructor(input, languageTag, sendable) {
|
|
1021
|
-
this.input = input;
|
|
1022
|
-
this.languageTag = languageTag;
|
|
1023
|
-
this.sendable = sendable;
|
|
1024
|
-
}
|
|
1025
|
-
input;
|
|
1026
|
-
languageTag;
|
|
1027
|
-
sendable;
|
|
1028
|
-
encodeCheckoutButtonSuffix(_prefix) {
|
|
1029
|
-
return encodeURIComponent(this.input.variables.checkoutUrl);
|
|
1030
|
-
}
|
|
1031
|
-
encodePromotionButtonSuffix(_prefix) {
|
|
1032
|
-
throw new BindingUnavailable("PROMOTION_PRODUCT_URL_UNAVAILABLE");
|
|
1033
|
-
}
|
|
1034
|
-
checkStaticButtonUrl(value) {
|
|
1035
|
-
const u = new URL(value), checkout = new URL(this.input.variables.checkoutUrl);
|
|
1036
|
-
if (u.protocol !== "https:" || u.username || u.password || u.hash || u.hostname !== checkout.hostname)
|
|
1037
|
-
throw new BindingUnavailable("CHECKOUT_UNTRUSTED");
|
|
1038
|
-
}
|
|
1039
|
-
imageHeader(role) {
|
|
1040
|
-
const m = this.input.headerMedia;
|
|
1041
|
-
if (!m || m.shopId !== this.input.evaluation.shopId || m.role !== role)
|
|
1042
|
-
throw new BindingUnavailable("MEDIA_NOT_READY");
|
|
1043
|
-
return m.httpsUrl;
|
|
1044
|
-
}
|
|
1045
|
-
resolve(key) {
|
|
1046
|
-
const i = this.input, d = this.sendable;
|
|
1047
|
-
switch (key) {
|
|
1048
|
-
case "customer.firstName":
|
|
1049
|
-
return boundedText(i.variables.customerFirstName, 200);
|
|
1050
|
-
case "shop.name":
|
|
1051
|
-
return boundedText(i.variables.shopName, 200);
|
|
1052
|
-
case "shop.categoryName":
|
|
1053
|
-
return boundedText(i.variables.shopCategory, 200);
|
|
1054
|
-
case "checkout.url":
|
|
1055
|
-
return i.variables.checkoutUrl;
|
|
1056
|
-
case "checkout.itemsSummary": {
|
|
1057
|
-
const items = i.evaluation.lines.map((l) => ({ title: l.currentProduct.title, quantity: l.quantity, variantTitle: l.currentProduct.selectedOptions?.map((o) => o.value).join(" / ") || null }));
|
|
1058
|
-
if (items.some((l) => l.quantity > 99999 || /[\p{Cc}\p{Cf}{}]/u.test(l.title) || l.title.length > 160 || l.variantTitle !== null && (/[\p{Cc}\p{Cf}{}]/u.test(l.variantTitle) || l.variantTitle.length > 160)))
|
|
1059
|
-
throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
|
|
1060
|
-
return boundedText(formatCheckoutItemsSummary(items, this.languageTag), 500);
|
|
1061
|
-
}
|
|
1062
|
-
case "offer.code":
|
|
1063
|
-
if (d.presentation === "CODE_OFFER" && d.members.length === 1 && d.members[0]?.method === "CODE")
|
|
1064
|
-
return boundedText(d.members[0].code, 100);
|
|
1065
|
-
break;
|
|
1066
|
-
case "offer.codes":
|
|
1067
|
-
if (d.presentation === "CODE_OFFER" && d.members.every((m) => m.method === "CODE"))
|
|
1068
|
-
return d.members.map((m) => boundedText(m.code, 100)).join(", ");
|
|
1069
|
-
break;
|
|
1070
|
-
case "offer.description":
|
|
1071
|
-
if (d.presentation !== "NO_OFFER")
|
|
1072
|
-
return boundedText(d.members.map((m) => m.description).join(", "), 200);
|
|
1073
|
-
break;
|
|
1074
|
-
// This input exposes only the host's prepared shop name/category and media, not arbitrary brand records.
|
|
1075
|
-
case "shopBrand.brandName":
|
|
1076
|
-
return boundedText(i.variables.shopName, 200);
|
|
1077
|
-
case "shopBrand.logoUrl":
|
|
1078
|
-
return this.imageHeader("LOGO");
|
|
1079
|
-
case "shopBrand.squareLogoUrl":
|
|
1080
|
-
return this.imageHeader("SQUARE_LOGO");
|
|
1081
|
-
case "shopBrand.coverImageUrl":
|
|
1082
|
-
return this.imageHeader("COVER_IMAGE");
|
|
1083
|
-
}
|
|
1084
|
-
throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
|
|
1085
|
-
}
|
|
1086
|
-
};
|
|
1087
|
-
|
|
1088
|
-
// src/commerce/outreach/bindings.ts
|
|
1089
1146
|
var MEDIA_ROLES = ["LOGO", "SQUARE_LOGO", "COVER_IMAGE", "CHECKOUT_PRODUCT"];
|
|
1090
1147
|
var obj = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1091
1148
|
function invalid() {
|
|
@@ -1196,6 +1253,12 @@ function prepareFrozenComponents(input) {
|
|
|
1196
1253
|
}
|
|
1197
1254
|
if (registration.category !== "MARKETING")
|
|
1198
1255
|
return invalid();
|
|
1256
|
+
return { ...prepareStoredComponents({ components: approved, bindings: frozen, variables }), bindingFingerprint: fingerprint };
|
|
1257
|
+
}
|
|
1258
|
+
function prepareStoredComponents(input) {
|
|
1259
|
+
const approved = strictComponents(input.components);
|
|
1260
|
+
const frozen = parseFrozenBindings(input.bindings);
|
|
1261
|
+
const variables = input.variables;
|
|
1199
1262
|
const result2 = [];
|
|
1200
1263
|
let matchedHeader = false;
|
|
1201
1264
|
let matchedBody = false;
|
|
@@ -1259,7 +1322,7 @@ function prepareFrozenComponents(input) {
|
|
|
1259
1322
|
}
|
|
1260
1323
|
if (!matchedBody || frozen.header.length > 0 && !matchedHeader || frozen.buttons.length > 0 && !matchedButtons)
|
|
1261
1324
|
return invalid();
|
|
1262
|
-
return { components: result2
|
|
1325
|
+
return { components: result2 };
|
|
1263
1326
|
}
|
|
1264
1327
|
|
|
1265
1328
|
// src/commerce/outreach/sendable-offer.ts
|
|
@@ -1283,10 +1346,17 @@ function selectSendableOutreachOffer(decision2) {
|
|
|
1283
1346
|
|
|
1284
1347
|
// src/commerce/outreach/render.ts
|
|
1285
1348
|
function renderInitialOutreachPreview(raw) {
|
|
1286
|
-
|
|
1349
|
+
let parsed;
|
|
1350
|
+
try {
|
|
1351
|
+
parsed = InitialOutreachRenderInputSchema.safeParse(raw);
|
|
1352
|
+
} catch {
|
|
1353
|
+
return { kind: "refused", reasonCode: "INVALID_REQUEST" };
|
|
1354
|
+
}
|
|
1287
1355
|
if (!parsed.success)
|
|
1288
1356
|
return { kind: "refused", reasonCode: "INVALID_REQUEST" };
|
|
1289
1357
|
const input = parsed.data;
|
|
1358
|
+
const assetError = validatePreparedAssets(input);
|
|
1359
|
+
if (assetError) return { kind: "refused", reasonCode: assetError };
|
|
1290
1360
|
let projection = input.preparedProjection;
|
|
1291
1361
|
if (!projection)
|
|
1292
1362
|
return { kind: "refused", reasonCode: "NO_APPROVED_ASSIGNMENT" };
|
|
@@ -1308,48 +1378,65 @@ function renderInitialOutreachPreview(raw) {
|
|
|
1308
1378
|
const registration = v.metaRegistration;
|
|
1309
1379
|
try {
|
|
1310
1380
|
const prepared = prepareFrozenComponents({ registration, variables: new PreparedVariableResolver(input, projection.canonicalLanguageTag, sendable) });
|
|
1311
|
-
const
|
|
1312
|
-
if (!body || !("text" in body))
|
|
1313
|
-
throw new BindingUnavailable("BINDINGS_INVALID");
|
|
1314
|
-
const params = prepared.components.find((c) => c.type === "body")?.parameters ?? [];
|
|
1315
|
-
const renderedText = body.text.replace(/\{\{([1-9][0-9]*)\}\}/g, (_, n) => {
|
|
1316
|
-
const value = params[Number(n) - 1];
|
|
1317
|
-
if (!value || value.type !== "text")
|
|
1318
|
-
throw new BindingUnavailable("BINDINGS_INVALID");
|
|
1319
|
-
return value.text;
|
|
1320
|
-
});
|
|
1321
|
-
if (renderedText.length > 4096)
|
|
1322
|
-
return { kind: "refused", reasonCode: "MESSAGE_TOO_LARGE" };
|
|
1323
|
-
const fullText = registration.componentsSnapshot.flatMap((component) => {
|
|
1324
|
-
if (component.type === "BODY")
|
|
1325
|
-
return [renderedText];
|
|
1326
|
-
if (component.type === "FOOTER")
|
|
1327
|
-
return [component.text];
|
|
1328
|
-
if (component.type === "HEADER" && component.format === "TEXT") {
|
|
1329
|
-
const headerParams = prepared.components.find((c) => c.type === "header")?.parameters ?? [];
|
|
1330
|
-
return [component.text.replace(/\{\{([1-9][0-9]*)\}\}/g, (_, n) => {
|
|
1331
|
-
const p = headerParams[Number(n) - 1];
|
|
1332
|
-
if (!p || p.type !== "text")
|
|
1333
|
-
throw new BindingUnavailable("BINDINGS_INVALID");
|
|
1334
|
-
return p.text;
|
|
1335
|
-
})];
|
|
1336
|
-
}
|
|
1337
|
-
return [];
|
|
1338
|
-
}).join("\n");
|
|
1381
|
+
const fullText = renderStoredComponentText(registration.componentsSnapshot, prepared.components);
|
|
1339
1382
|
return InitialOutreachRenderResultSchema.parse({ kind: "ready", purpose, templateProvenance: { variantId: v.id, registrationId: registration.id, definitionId: registration.locale.definition.id, providerTemplateId: registration.providerTemplateId, providerTemplateName: registration.providerTemplateName, providerLanguageCode: registration.providerLanguageCode, submittedContentHash: registration.submittedContentHash, bindingFingerprint: prepared.bindingFingerprint }, parameters: prepared.components, headerPreview: image ? input.headerMedia : null, renderedText: fullText });
|
|
1340
1383
|
} catch (e) {
|
|
1341
1384
|
return { kind: "refused", reasonCode: e instanceof BindingUnavailable ? e.reason : "BINDINGS_INVALID" };
|
|
1342
1385
|
}
|
|
1343
1386
|
}
|
|
1387
|
+
function validatePreparedAssets(input) {
|
|
1388
|
+
if (input.preparedBrand && input.preparedBrand.shopId !== input.evaluation.shopId)
|
|
1389
|
+
return "IDENTITY_MISMATCH";
|
|
1390
|
+
for (const [key, role] of [["logo", "LOGO"], ["squareLogo", "SQUARE_LOGO"], ["coverImage", "COVER_IMAGE"]]) {
|
|
1391
|
+
const media = input.preparedBrandMedia?.[key];
|
|
1392
|
+
if (media) {
|
|
1393
|
+
const url = new URL(media.httpsUrl);
|
|
1394
|
+
if (media.shopId !== input.evaluation.shopId || media.role !== role || url.hash || media.httpsUrl.includes("#") || !url.hostname.includes("."))
|
|
1395
|
+
return "MEDIA_NOT_READY";
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return null;
|
|
1399
|
+
}
|
|
1400
|
+
function renderStudioInitialOutreachPreview(raw) {
|
|
1401
|
+
let parsed;
|
|
1402
|
+
try {
|
|
1403
|
+
parsed = StudioInitialOutreachRenderInputSchema.safeParse(raw);
|
|
1404
|
+
} catch {
|
|
1405
|
+
return { kind: "refused", reasonCode: "INVALID_REQUEST" };
|
|
1406
|
+
}
|
|
1407
|
+
if (!parsed.success) return { kind: "refused", reasonCode: "INVALID_REQUEST" };
|
|
1408
|
+
const input = parsed.data;
|
|
1409
|
+
const assetError = validatePreparedAssets(input);
|
|
1410
|
+
if (assetError) return { kind: "refused", reasonCode: assetError };
|
|
1411
|
+
try {
|
|
1412
|
+
const sendable = selectSendableOutreachOffer(input.evaluation.offerDecision);
|
|
1413
|
+
const prepared = prepareStoredComponents({ components: input.template.components, bindings: input.template.bindings, variables: new PreparedVariableResolver(input, input.template.languageTag, sendable) });
|
|
1414
|
+
const image = input.template.components.some((c) => c.type === "HEADER" && c.format === "IMAGE");
|
|
1415
|
+
return StudioInitialOutreachRenderResultSchema.parse({ kind: "ready", renderedText: renderStoredComponentText(input.template.components, prepared.components), parameters: prepared.components, headerPreview: image ? input.headerMedia : null, templateId: input.template.templateId, localeId: input.template.localeId, templateValidation: "NOT_EVALUATED" });
|
|
1416
|
+
} catch (error) {
|
|
1417
|
+
return { kind: "refused", reasonCode: error instanceof BindingUnavailable ? error.reason : "BINDINGS_INVALID" };
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1344
1420
|
export {
|
|
1421
|
+
ComponentSchema,
|
|
1422
|
+
FrozenBindingsSchema,
|
|
1345
1423
|
InitialOutreachEvaluationInputSchema,
|
|
1346
1424
|
InitialOutreachEvaluationResultSchema,
|
|
1347
1425
|
InitialOutreachRenderInputSchema,
|
|
1348
1426
|
InitialOutreachRenderResultSchema,
|
|
1427
|
+
PreparedOutreachBrandMediaItemSchema,
|
|
1428
|
+
PreparedOutreachBrandMediaSchema,
|
|
1429
|
+
PreparedOutreachBrandSchema,
|
|
1430
|
+
StudioInitialOutreachRenderInputSchema,
|
|
1431
|
+
StudioInitialOutreachRenderResultSchema,
|
|
1432
|
+
StudioOutreachTemplateSchema,
|
|
1349
1433
|
evaluateInitialOutreach,
|
|
1350
1434
|
evaluateInitialOutreachPreview,
|
|
1351
1435
|
fingerprintInitialOutreachBasket,
|
|
1436
|
+
purposeFor,
|
|
1352
1437
|
renderInitialOutreachPreview,
|
|
1353
|
-
|
|
1438
|
+
renderStudioInitialOutreachPreview,
|
|
1439
|
+
selectRecoveryAssignment,
|
|
1440
|
+
selectSendableOutreachOffer
|
|
1354
1441
|
};
|
|
1355
1442
|
//# sourceMappingURL=index.js.map
|