@modainteract/moda-interact-shared 5.9.0 → 5.11.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.
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  PLATFORM_INSTRUCTIONS,
6
6
  RUNTIME_DATA_AUTHORITY_INSTRUCTION
7
- } from "../../chunk-WHZAAF2Q.js";
7
+ } from "../../chunk-POCE2AES.js";
8
8
  import {
9
9
  CommerceModelInvocationFailure
10
10
  } from "../../chunk-WONND3OK.js";
@@ -12,7 +12,7 @@ import {
12
12
  CommerceProductSchema,
13
13
  CommerceToolInputs,
14
14
  CommerceToolOutputs
15
- } from "../../chunk-SW2AFMT7.js";
15
+ } from "../../chunk-GADJQUWU.js";
16
16
  import {
17
17
  CurrencySchema,
18
18
  DateSchema,
@@ -115,6 +115,12 @@ var InitialOutreachRenderResultSchema = z2.discriminatedUnion("kind", [
115
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) }),
116
116
  z2.strictObject({ kind: z2.literal("refused"), reasonCode: RenderReasonSchema })
117
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
+ ]);
118
124
 
119
125
  // src/commerce/outreach/evaluate.ts
120
126
  import { z as z3 } from "zod";
@@ -788,6 +794,188 @@ async function evaluateInitialOutreach(raw, ports) {
788
794
  }
789
795
  var evaluateInitialOutreachPreview = evaluateInitialOutreach;
790
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
+
791
979
  // src/commerce/outreach/initial-checkout-items-policy.ts
792
980
  function decideInitialCheckoutItemsBinding(input) {
793
981
  if (input.stage === "FOLLOW_UP")
@@ -955,167 +1143,6 @@ function selectRecoveryAssignment(input) {
955
1143
 
956
1144
  // src/commerce/outreach/bindings.ts
957
1145
  import { createHash as createHash2 } from "crypto";
958
-
959
- // src/commerce/outreach/recovery-template-items-summary.ts
960
- var MAX_LINE_ITEMS = 100;
961
- var MAX_DISPLAYED_ITEMS = 3;
962
- var MAX_TITLE_LENGTH = 160;
963
- var MAX_SUMMARY_LENGTH = 400;
964
- function safeText(value, limit) {
965
- if (typeof value !== "string")
966
- return null;
967
- const text2 = value.normalize("NFC").trim().replace(/\s+/gu, " ");
968
- if (!text2 || text2.length > limit || /[\p{Cc}\p{Cf}{}]/u.test(text2) || text2.includes("{{") || text2.includes("}}"))
969
- return null;
970
- try {
971
- encodeURIComponent(text2);
972
- } catch {
973
- return null;
974
- }
975
- return text2;
976
- }
977
- function parseItem(value) {
978
- if (!value || typeof value !== "object" || Array.isArray(value))
979
- return null;
980
- const item = value;
981
- const title = safeText(item.title, MAX_TITLE_LENGTH);
982
- const quantity = item.quantity;
983
- if (!title || typeof quantity !== "number" || !Number.isSafeInteger(quantity) || quantity < 1 || quantity > 99999)
984
- return null;
985
- const variant = safeText(item.variantTitle, MAX_TITLE_LENGTH);
986
- const displayTitle = variant && variant.toLowerCase() !== "default title" && variant !== title ? `${title} (${variant})` : title;
987
- return { title: displayTitle, quantity };
988
- }
989
- function formatCheckoutItemsSummary(lineItems, languageTag) {
990
- if (!Array.isArray(lineItems) || lineItems.length === 0 || lineItems.length > MAX_LINE_ITEMS)
991
- return null;
992
- let locale;
993
- try {
994
- locale = Intl.getCanonicalLocales(languageTag)[0] ?? "en";
995
- } catch {
996
- return null;
997
- }
998
- const items = lineItems.map(parseItem).filter((item) => item !== null);
999
- if (!items.length)
1000
- return null;
1001
- const numbers = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
1002
- const list = new Intl.ListFormat(locale, { style: "long", type: "conjunction" });
1003
- const visible = items.slice(0, MAX_DISPLAYED_ITEMS).map(({ title, quantity }) => `${numbers.format(quantity)} \xD7 ${title}`);
1004
- if (items.length > MAX_DISPLAYED_ITEMS) {
1005
- visible.push(`+${numbers.format(items.length - MAX_DISPLAYED_ITEMS)}`);
1006
- }
1007
- const summary = list.format(visible);
1008
- if (summary.length <= MAX_SUMMARY_LENGTH)
1009
- return summary;
1010
- return `${Array.from(summary).slice(0, MAX_SUMMARY_LENGTH - 1).join("").trimEnd()}\u2026`;
1011
- }
1012
-
1013
- // src/commerce/outreach/variables.ts
1014
- function isSemanticKey(v) {
1015
- return SemanticKeySchema.safeParse(v).success;
1016
- }
1017
- var BindingUnavailable = class extends Error {
1018
- constructor(reason) {
1019
- super(reason);
1020
- this.reason = reason;
1021
- }
1022
- reason;
1023
- };
1024
- function boundedText(v, limit) {
1025
- if (typeof v !== "string" || !v.trim() || v.length > limit || /[\x00-\x1f\x7f\u2028\u2029]/u.test(v) || v.includes("{{") || v.includes("}}"))
1026
- throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
1027
- try {
1028
- encodeURIComponent(v);
1029
- } catch {
1030
- throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
1031
- }
1032
- return v.trim();
1033
- }
1034
- var PreparedVariableResolver = class {
1035
- constructor(input, languageTag, sendable) {
1036
- this.input = input;
1037
- this.languageTag = languageTag;
1038
- this.sendable = sendable;
1039
- }
1040
- input;
1041
- languageTag;
1042
- sendable;
1043
- encodeCheckoutButtonSuffix(_prefix) {
1044
- return encodeURIComponent(this.input.variables.checkoutUrl);
1045
- }
1046
- encodePromotionButtonSuffix(_prefix) {
1047
- throw new BindingUnavailable("PROMOTION_PRODUCT_URL_UNAVAILABLE");
1048
- }
1049
- checkStaticButtonUrl(value) {
1050
- const u = new URL(value), checkout = new URL(this.input.variables.checkoutUrl);
1051
- if (u.protocol !== "https:" || u.username || u.password || u.hash || u.hostname !== checkout.hostname)
1052
- throw new BindingUnavailable("CHECKOUT_UNTRUSTED");
1053
- }
1054
- imageHeader(role) {
1055
- const m = this.input.headerMedia;
1056
- if (!m || m.shopId !== this.input.evaluation.shopId || m.role !== role)
1057
- throw new BindingUnavailable("MEDIA_NOT_READY");
1058
- return m.httpsUrl;
1059
- }
1060
- brandMedia(role) {
1061
- const media = this.input.preparedBrandMedia?.[role];
1062
- if (!media) throw new BindingUnavailable("MEDIA_NOT_READY");
1063
- boundedText(media.httpsUrl, 2048);
1064
- return media.httpsUrl;
1065
- }
1066
- resolve(key) {
1067
- const i = this.input, d = this.sendable;
1068
- switch (key) {
1069
- case "customer.firstName":
1070
- return boundedText(i.variables.customerFirstName, 200);
1071
- case "shop.name":
1072
- return boundedText(i.variables.shopName, 200);
1073
- case "shop.categoryName":
1074
- return boundedText(i.variables.shopCategory, 200);
1075
- case "checkout.url":
1076
- return i.variables.checkoutUrl;
1077
- case "checkout.itemsSummary": {
1078
- const items = i.evaluation.lines.map((l) => ({ title: l.currentProduct.title, quantity: l.quantity, variantTitle: l.currentProduct.selectedOptions?.map((o) => o.value).join(" / ") || null }));
1079
- 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)))
1080
- throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
1081
- return boundedText(formatCheckoutItemsSummary(items, this.languageTag), 500);
1082
- }
1083
- case "offer.code":
1084
- if (d.presentation === "CODE_OFFER" && d.members.length === 1 && d.members[0]?.method === "CODE")
1085
- return boundedText(d.members[0].code, 100);
1086
- break;
1087
- case "offer.codes":
1088
- if (d.presentation === "CODE_OFFER" && d.members.every((m) => m.method === "CODE"))
1089
- return d.members.map((m) => boundedText(m.code, 100)).join(", ");
1090
- break;
1091
- case "offer.description":
1092
- if (d.presentation !== "NO_OFFER")
1093
- return boundedText(d.members.map((m) => m.description).join(", "), 200);
1094
- break;
1095
- case "shopBrand.brandName":
1096
- return boundedText(i.preparedBrand?.brandName, 200);
1097
- case "shopBrand.logoAltText":
1098
- return boundedText(i.preparedBrand?.logoAltText, 200);
1099
- case "shopBrand.squareLogoAltText":
1100
- return boundedText(i.preparedBrand?.squareLogoAltText, 200);
1101
- case "shopBrand.logoWidth":
1102
- case "shopBrand.logoHeight": {
1103
- const value = key === "shopBrand.logoWidth" ? i.preparedBrand?.logoWidth : i.preparedBrand?.logoHeight;
1104
- if (value === null || value === void 0) throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
1105
- return boundedText(String(value), 200);
1106
- }
1107
- case "shopBrand.logoUrl":
1108
- return this.brandMedia("logo");
1109
- case "shopBrand.squareLogoUrl":
1110
- return this.brandMedia("squareLogo");
1111
- case "shopBrand.coverImageUrl":
1112
- return this.brandMedia("coverImage");
1113
- }
1114
- throw new BindingUnavailable("BINDING_VALUE_UNAVAILABLE");
1115
- }
1116
- };
1117
-
1118
- // src/commerce/outreach/bindings.ts
1119
1146
  var MEDIA_ROLES = ["LOGO", "SQUARE_LOGO", "COVER_IMAGE", "CHECKOUT_PRODUCT"];
1120
1147
  var obj = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1121
1148
  function invalid() {
@@ -1226,6 +1253,12 @@ function prepareFrozenComponents(input) {
1226
1253
  }
1227
1254
  if (registration.category !== "MARKETING")
1228
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;
1229
1262
  const result2 = [];
1230
1263
  let matchedHeader = false;
1231
1264
  let matchedBody = false;
@@ -1289,7 +1322,7 @@ function prepareFrozenComponents(input) {
1289
1322
  }
1290
1323
  if (!matchedBody || frozen.header.length > 0 && !matchedHeader || frozen.buttons.length > 0 && !matchedButtons)
1291
1324
  return invalid();
1292
- return { components: result2, bindingFingerprint: fingerprint };
1325
+ return { components: result2 };
1293
1326
  }
1294
1327
 
1295
1328
  // src/commerce/outreach/sendable-offer.ts
@@ -1322,16 +1355,8 @@ function renderInitialOutreachPreview(raw) {
1322
1355
  if (!parsed.success)
1323
1356
  return { kind: "refused", reasonCode: "INVALID_REQUEST" };
1324
1357
  const input = parsed.data;
1325
- if (input.preparedBrand && input.preparedBrand.shopId !== input.evaluation.shopId)
1326
- return { kind: "refused", reasonCode: "IDENTITY_MISMATCH" };
1327
- for (const [key, role] of [["logo", "LOGO"], ["squareLogo", "SQUARE_LOGO"], ["coverImage", "COVER_IMAGE"]]) {
1328
- const media = input.preparedBrandMedia?.[key];
1329
- if (media) {
1330
- const url = new URL(media.httpsUrl);
1331
- if (media.shopId !== input.evaluation.shopId || media.role !== role || url.hash || media.httpsUrl.includes("#") || !url.hostname.includes("."))
1332
- return { kind: "refused", reasonCode: "MEDIA_NOT_READY" };
1333
- }
1334
- }
1358
+ const assetError = validatePreparedAssets(input);
1359
+ if (assetError) return { kind: "refused", reasonCode: assetError };
1335
1360
  let projection = input.preparedProjection;
1336
1361
  if (!projection)
1337
1362
  return { kind: "refused", reasonCode: "NO_APPROVED_ASSIGNMENT" };
@@ -1353,40 +1378,48 @@ function renderInitialOutreachPreview(raw) {
1353
1378
  const registration = v.metaRegistration;
1354
1379
  try {
1355
1380
  const prepared = prepareFrozenComponents({ registration, variables: new PreparedVariableResolver(input, projection.canonicalLanguageTag, sendable) });
1356
- const body = registration.componentsSnapshot.find((c) => c.type === "BODY");
1357
- if (!body || !("text" in body))
1358
- throw new BindingUnavailable("BINDINGS_INVALID");
1359
- const params = prepared.components.find((c) => c.type === "body")?.parameters ?? [];
1360
- const renderedText = body.text.replace(/\{\{([1-9][0-9]*)\}\}/g, (_, n) => {
1361
- const value = params[Number(n) - 1];
1362
- if (!value || value.type !== "text")
1363
- throw new BindingUnavailable("BINDINGS_INVALID");
1364
- return value.text;
1365
- });
1366
- if (renderedText.length > 4096)
1367
- return { kind: "refused", reasonCode: "MESSAGE_TOO_LARGE" };
1368
- const fullText = registration.componentsSnapshot.flatMap((component) => {
1369
- if (component.type === "BODY")
1370
- return [renderedText];
1371
- if (component.type === "FOOTER")
1372
- return [component.text];
1373
- if (component.type === "HEADER" && component.format === "TEXT") {
1374
- const headerParams = prepared.components.find((c) => c.type === "header")?.parameters ?? [];
1375
- return [component.text.replace(/\{\{([1-9][0-9]*)\}\}/g, (_, n) => {
1376
- const p = headerParams[Number(n) - 1];
1377
- if (!p || p.type !== "text")
1378
- throw new BindingUnavailable("BINDINGS_INVALID");
1379
- return p.text;
1380
- })];
1381
- }
1382
- return [];
1383
- }).join("\n");
1381
+ const fullText = renderStoredComponentText(registration.componentsSnapshot, prepared.components);
1384
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 });
1385
1383
  } catch (e) {
1386
1384
  return { kind: "refused", reasonCode: e instanceof BindingUnavailable ? e.reason : "BINDINGS_INVALID" };
1387
1385
  }
1388
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
+ }
1389
1420
  export {
1421
+ ComponentSchema,
1422
+ FrozenBindingsSchema,
1390
1423
  InitialOutreachEvaluationInputSchema,
1391
1424
  InitialOutreachEvaluationResultSchema,
1392
1425
  InitialOutreachRenderInputSchema,
@@ -1394,10 +1427,16 @@ export {
1394
1427
  PreparedOutreachBrandMediaItemSchema,
1395
1428
  PreparedOutreachBrandMediaSchema,
1396
1429
  PreparedOutreachBrandSchema,
1430
+ StudioInitialOutreachRenderInputSchema,
1431
+ StudioInitialOutreachRenderResultSchema,
1432
+ StudioOutreachTemplateSchema,
1397
1433
  evaluateInitialOutreach,
1398
1434
  evaluateInitialOutreachPreview,
1399
1435
  fingerprintInitialOutreachBasket,
1436
+ purposeFor,
1400
1437
  renderInitialOutreachPreview,
1401
- selectRecoveryAssignment
1438
+ renderStudioInitialOutreachPreview,
1439
+ selectRecoveryAssignment,
1440
+ selectSendableOutreachOffer
1402
1441
  };
1403
1442
  //# sourceMappingURL=index.js.map