@scalemule/news-publication 0.0.11 → 0.0.13

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/dist/index.cjs CHANGED
@@ -788,6 +788,8 @@ var COOKIE_CONSENT_KEY = "sm_cookie_consent_v1";
788
788
  var COOKIE_CONSENT_BACKUP_KEY = "napsite_cookie_consent_v1";
789
789
  var COOKIE_CONSENT_EVENT = "scalemule-cookie-consent-change";
790
790
  var COOKIE_CONSENT_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
791
+ var DEFAULT_CONSENT_HUB_URL = "https://bayareachronicle.com/consent-bridge.html";
792
+ var NETWORK_CONSENT_STORAGE_KEY = "sm_network_consent_status";
791
793
  var DEFAULT_PREFERENCES_REJECTED = {
792
794
  essential: true,
793
795
  performance: false,
@@ -942,6 +944,248 @@ function getCookieConsent() {
942
944
  }
943
945
  return memoryConsent;
944
946
  }
947
+ var EU_EEA_UK_COUNTRIES = /* @__PURE__ */ new Set([
948
+ "AT",
949
+ "BE",
950
+ "BG",
951
+ "HR",
952
+ "CY",
953
+ "CZ",
954
+ "DK",
955
+ "EE",
956
+ "FI",
957
+ "FR",
958
+ "DE",
959
+ "GR",
960
+ "HU",
961
+ "IE",
962
+ "IT",
963
+ "LV",
964
+ "LT",
965
+ "LU",
966
+ "MT",
967
+ "NL",
968
+ "PL",
969
+ "PT",
970
+ "RO",
971
+ "SK",
972
+ "SI",
973
+ "ES",
974
+ "SE",
975
+ "IS",
976
+ "LI",
977
+ "NO",
978
+ "GB",
979
+ "CH"
980
+ ]);
981
+ function isGeoConsentRequired(countryCode) {
982
+ if (countryCode && typeof countryCode === "string") {
983
+ const code = countryCode.trim().toUpperCase();
984
+ if (code.length === 2) {
985
+ return EU_EEA_UK_COUNTRIES.has(code);
986
+ }
987
+ }
988
+ if (typeof window !== "undefined") {
989
+ try {
990
+ const cached = readCookie("sm_geo_country") || window.localStorage?.getItem("sm_geo_country");
991
+ if (cached && typeof cached === "string" && cached.length === 2) {
992
+ return EU_EEA_UK_COUNTRIES.has(cached.toUpperCase());
993
+ }
994
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
995
+ if (tz) {
996
+ if (/^(America|US|Canada|Pacific)\//i.test(tz)) {
997
+ return false;
998
+ }
999
+ if (/^(Europe|Atlantic\/Reykjavik|Atlantic\/Canary|Atlantic\/Faeroe|Atlantic\/Madeira|Atlantic\/Azores|WET|CET|EET|GMT)/i.test(tz)) {
1000
+ return true;
1001
+ }
1002
+ }
1003
+ } catch {
1004
+ }
1005
+ }
1006
+ return false;
1007
+ }
1008
+ function checkUrlConsentBridge() {
1009
+ if (typeof window === "undefined") return false;
1010
+ try {
1011
+ const url = new URL(window.location.href);
1012
+ const param = url.searchParams.get("sm_consent");
1013
+ if (!param) return false;
1014
+ if (param === "accepted" || param === "1" || param === "all") {
1015
+ acceptAllCookies();
1016
+ } else if (param === "rejected" || param === "0" || param === "essential") {
1017
+ rejectOptionalCookies();
1018
+ } else {
1019
+ return false;
1020
+ }
1021
+ url.searchParams.delete("sm_consent");
1022
+ const newSearch = url.searchParams.toString() ? `?${url.searchParams.toString()}` : "";
1023
+ window.history.replaceState(null, "", `${url.pathname}${newSearch}${url.hash}`);
1024
+ return true;
1025
+ } catch {
1026
+ return false;
1027
+ }
1028
+ }
1029
+ function decorateNetworkUrl(targetUrl) {
1030
+ const consent = getCookieConsent();
1031
+ if (!consent) return targetUrl;
1032
+ try {
1033
+ const base = typeof window !== "undefined" ? window.location.origin : "https://localhost";
1034
+ const url = new URL(targetUrl, base);
1035
+ const value = consent.preferences.performance || consent.preferences.advertising ? "1" : "0";
1036
+ url.searchParams.set("sm_consent", value);
1037
+ return url.toString();
1038
+ } catch {
1039
+ return targetUrl;
1040
+ }
1041
+ }
1042
+ function queryNetworkConsentHub(hubUrl = DEFAULT_CONSENT_HUB_URL, timeoutMs = 1e3) {
1043
+ if (typeof window === "undefined") return Promise.resolve(null);
1044
+ try {
1045
+ const hubOrigin = new URL(hubUrl).origin;
1046
+ if (window.location.origin === hubOrigin) {
1047
+ try {
1048
+ const direct = window.localStorage?.getItem(NETWORK_CONSENT_STORAGE_KEY);
1049
+ if (direct === "accepted" || direct === "rejected") {
1050
+ return Promise.resolve(direct);
1051
+ }
1052
+ } catch {
1053
+ }
1054
+ return Promise.resolve(null);
1055
+ }
1056
+ return new Promise((resolve) => {
1057
+ let settled = false;
1058
+ let iframe = null;
1059
+ let timer = null;
1060
+ const finish = (result) => {
1061
+ if (settled) return;
1062
+ settled = true;
1063
+ if (timer !== null) window.clearTimeout(timer);
1064
+ window.removeEventListener("message", onMessage);
1065
+ if (iframe && iframe.parentNode) {
1066
+ try {
1067
+ iframe.parentNode.removeChild(iframe);
1068
+ } catch {
1069
+ }
1070
+ }
1071
+ resolve(result);
1072
+ };
1073
+ const sendGet = () => {
1074
+ try {
1075
+ iframe?.contentWindow?.postMessage({ type: "SM_CONSENT_GET" }, hubOrigin);
1076
+ } catch {
1077
+ }
1078
+ };
1079
+ const onMessage = (event) => {
1080
+ if (event.origin !== hubOrigin) return;
1081
+ const data = event.data;
1082
+ if (!data || typeof data !== "object") return;
1083
+ if (data.type === "SM_CONSENT_STATUS") {
1084
+ const status = data.status === "accepted" || data.status === "rejected" ? data.status : null;
1085
+ finish(status);
1086
+ } else if (data.type === "SM_CONSENT_BRIDGE_READY") {
1087
+ sendGet();
1088
+ }
1089
+ };
1090
+ window.addEventListener("message", onMessage);
1091
+ timer = window.setTimeout(() => {
1092
+ finish(null);
1093
+ }, timeoutMs);
1094
+ try {
1095
+ iframe = document.createElement("iframe");
1096
+ iframe.src = hubUrl;
1097
+ iframe.style.position = "absolute";
1098
+ iframe.style.width = "0";
1099
+ iframe.style.height = "0";
1100
+ iframe.style.border = "0";
1101
+ iframe.style.display = "none";
1102
+ iframe.style.visibility = "hidden";
1103
+ iframe.setAttribute("aria-hidden", "true");
1104
+ iframe.setAttribute("tabindex", "-1");
1105
+ iframe.onload = () => {
1106
+ sendGet();
1107
+ };
1108
+ iframe.onerror = () => {
1109
+ finish(null);
1110
+ };
1111
+ const target = document.body || document.documentElement;
1112
+ target.appendChild(iframe);
1113
+ } catch {
1114
+ finish(null);
1115
+ }
1116
+ });
1117
+ } catch {
1118
+ return Promise.resolve(null);
1119
+ }
1120
+ }
1121
+ function setNetworkConsentHub(status, hubUrl = DEFAULT_CONSENT_HUB_URL) {
1122
+ if (typeof window === "undefined") return;
1123
+ try {
1124
+ const hubOrigin = new URL(hubUrl).origin;
1125
+ if (window.location.origin === hubOrigin) {
1126
+ try {
1127
+ if (status) {
1128
+ window.localStorage?.setItem(NETWORK_CONSENT_STORAGE_KEY, status);
1129
+ } else {
1130
+ window.localStorage?.removeItem(NETWORK_CONSENT_STORAGE_KEY);
1131
+ }
1132
+ } catch {
1133
+ }
1134
+ return;
1135
+ }
1136
+ let iframe = null;
1137
+ let timer = null;
1138
+ const cleanup = () => {
1139
+ if (timer !== null) window.clearTimeout(timer);
1140
+ window.removeEventListener("message", onMessage);
1141
+ if (iframe && iframe.parentNode) {
1142
+ try {
1143
+ iframe.parentNode.removeChild(iframe);
1144
+ } catch {
1145
+ }
1146
+ }
1147
+ };
1148
+ const sendSet = () => {
1149
+ try {
1150
+ iframe?.contentWindow?.postMessage({ type: "SM_CONSENT_SET", status }, hubOrigin);
1151
+ } catch {
1152
+ }
1153
+ };
1154
+ const onMessage = (event) => {
1155
+ if (event.origin !== hubOrigin) return;
1156
+ const data = event.data;
1157
+ if (!data || typeof data !== "object") return;
1158
+ if (data.type === "SM_CONSENT_SAVED") {
1159
+ cleanup();
1160
+ } else if (data.type === "SM_CONSENT_BRIDGE_READY") {
1161
+ sendSet();
1162
+ }
1163
+ };
1164
+ window.addEventListener("message", onMessage);
1165
+ timer = window.setTimeout(cleanup, 2500);
1166
+ try {
1167
+ iframe = document.createElement("iframe");
1168
+ iframe.src = hubUrl;
1169
+ iframe.style.position = "absolute";
1170
+ iframe.style.width = "0";
1171
+ iframe.style.height = "0";
1172
+ iframe.style.border = "0";
1173
+ iframe.style.display = "none";
1174
+ iframe.style.visibility = "hidden";
1175
+ iframe.setAttribute("aria-hidden", "true");
1176
+ iframe.setAttribute("tabindex", "-1");
1177
+ iframe.onload = () => {
1178
+ sendSet();
1179
+ };
1180
+ iframe.onerror = cleanup;
1181
+ const target = document.body || document.documentElement;
1182
+ target.appendChild(iframe);
1183
+ } catch {
1184
+ cleanup();
1185
+ }
1186
+ } catch {
1187
+ }
1188
+ }
945
1189
  function hasConsented() {
946
1190
  return getCookieConsent() !== null;
947
1191
  }
@@ -989,6 +1233,8 @@ function setCookieConsent(preferences, source = "settings_save") {
989
1233
  window.localStorage?.setItem(COOKIE_CONSENT_KEY, serialized);
990
1234
  } catch {
991
1235
  }
1236
+ const networkStatus = mergedPreferences.performance || mergedPreferences.advertising ? "accepted" : "rejected";
1237
+ setNetworkConsentHub(networkStatus);
992
1238
  window.dispatchEvent(new CustomEvent(COOKIE_CONSENT_EVENT, { detail: record }));
993
1239
  }
994
1240
  return record;
@@ -1010,6 +1256,7 @@ function clearCookieConsent() {
1010
1256
  window.localStorage?.removeItem(COOKIE_CONSENT_KEY);
1011
1257
  } catch {
1012
1258
  }
1259
+ setNetworkConsentHub(null);
1013
1260
  window.dispatchEvent(new CustomEvent(COOKIE_CONSENT_EVENT, { detail: null }));
1014
1261
  }
1015
1262
  }
@@ -1046,16 +1293,52 @@ function CookieConsentBanner({
1046
1293
  settingsUrl = "/cookies",
1047
1294
  className = "",
1048
1295
  publicationSlug,
1049
- complianceEndpoint = "/api/compliance/consent"
1296
+ complianceEndpoint = "/api/compliance/consent",
1297
+ countryCode,
1298
+ forceShow = false,
1299
+ consentHubUrl = DEFAULT_CONSENT_HUB_URL
1050
1300
  }) {
1051
1301
  const [mounted, setMounted] = react.useState(false);
1052
1302
  const [visible, setVisible] = react.useState(false);
1053
1303
  react.useEffect(() => {
1054
1304
  setMounted(true);
1055
- if (!hasConsented()) {
1056
- const timer = window.setTimeout(() => setVisible(true), 300);
1057
- return () => window.clearTimeout(timer);
1305
+ if (checkUrlConsentBridge()) {
1306
+ setVisible(false);
1307
+ return;
1308
+ }
1309
+ if (hasConsented()) {
1310
+ setVisible(false);
1311
+ return;
1058
1312
  }
1313
+ if (!forceShow && !isGeoConsentRequired(countryCode)) {
1314
+ setVisible(false);
1315
+ void queryNetworkConsentHub(consentHubUrl, 1200).then((networkStatus) => {
1316
+ if (networkStatus === "accepted") {
1317
+ acceptAllCookies();
1318
+ } else if (networkStatus === "rejected") {
1319
+ rejectOptionalCookies();
1320
+ }
1321
+ });
1322
+ return;
1323
+ }
1324
+ let cancelled = false;
1325
+ void queryNetworkConsentHub(consentHubUrl, 1e3).then((networkStatus) => {
1326
+ if (cancelled) return;
1327
+ if (networkStatus === "accepted") {
1328
+ acceptAllCookies();
1329
+ setVisible(false);
1330
+ } else if (networkStatus === "rejected") {
1331
+ rejectOptionalCookies();
1332
+ setVisible(false);
1333
+ } else {
1334
+ setVisible(true);
1335
+ }
1336
+ });
1337
+ return () => {
1338
+ cancelled = true;
1339
+ };
1340
+ }, [countryCode, forceShow, consentHubUrl]);
1341
+ react.useEffect(() => {
1059
1342
  const onConsentChange = () => {
1060
1343
  if (hasConsented()) {
1061
1344
  setVisible(false);
@@ -1070,19 +1353,21 @@ function CookieConsentBanner({
1070
1353
  setVisible(false);
1071
1354
  void recordConsentToCompliance(record, {
1072
1355
  endpoint: complianceEndpoint});
1356
+ setNetworkConsentHub("accepted", consentHubUrl);
1073
1357
  };
1074
1358
  const handleReject = () => {
1075
1359
  const record = rejectOptionalCookies();
1076
1360
  setVisible(false);
1077
1361
  void recordConsentToCompliance(record, {
1078
1362
  endpoint: complianceEndpoint});
1363
+ setNetworkConsentHub("rejected", consentHubUrl);
1079
1364
  };
1080
1365
  const defaultText = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1081
- "We use cookies and process data from your device to analyze website performance, personalize ad content, and improve your experience. Your consent includes data transfers outside of the country you\u2019re located. View",
1366
+ "We use cookies and device data to analyze performance, support local sponsors, and remember your reading preferences across our news network. View",
1082
1367
  " ",
1083
1368
  /* @__PURE__ */ jsxRuntime.jsx("a", { href: settingsUrl, className: "sm-cookie-banner-link", children: "Cookie Settings" }),
1084
1369
  " ",
1085
- "for more information."
1370
+ "for details."
1086
1371
  ] });
1087
1372
  return /* @__PURE__ */ jsxRuntime.jsx(
1088
1373
  "aside",
@@ -1660,13 +1945,16 @@ exports.COOKIE_CONSENT_KEY = COOKIE_CONSENT_KEY;
1660
1945
  exports.COOKIE_CONSENT_MAX_AGE_SECONDS = COOKIE_CONSENT_MAX_AGE_SECONDS;
1661
1946
  exports.CookieConsentBanner = CookieConsentBanner;
1662
1947
  exports.CookieSettingsView = CookieSettingsView;
1948
+ exports.DEFAULT_CONSENT_HUB_URL = DEFAULT_CONSENT_HUB_URL;
1663
1949
  exports.DEFAULT_PREFERENCES_ACCEPTED = DEFAULT_PREFERENCES_ACCEPTED;
1664
1950
  exports.DEFAULT_PREFERENCES_REJECTED = DEFAULT_PREFERENCES_REJECTED;
1951
+ exports.EU_EEA_UK_COUNTRIES = EU_EEA_UK_COUNTRIES;
1665
1952
  exports.EVENTS = EVENTS;
1666
1953
  exports.FEATURE_NAMES = FEATURE_NAMES;
1667
1954
  exports.JOB_EMPLOYMENT_TYPES = JOB_EMPLOYMENT_TYPES;
1668
1955
  exports.LAYOUT_COOKIE = LAYOUT_COOKIE;
1669
1956
  exports.MEET_ACTIVITIES = MEET_ACTIVITIES;
1957
+ exports.NETWORK_CONSENT_STORAGE_KEY = NETWORK_CONSENT_STORAGE_KEY;
1670
1958
  exports.NEWS_CACHE = NEWS_CACHE;
1671
1959
  exports.NewsError = NewsError;
1672
1960
  exports.SESSION_IDLE_MS = SESSION_IDLE_MS;
@@ -1691,6 +1979,7 @@ exports.attribution = attribution;
1691
1979
  exports.briefSummary = briefSummary;
1692
1980
  exports.canHostMeetup = canHostMeetup;
1693
1981
  exports.categoryLabel = categoryLabel;
1982
+ exports.checkUrlConsentBridge = checkUrlConsentBridge;
1694
1983
  exports.classifiedPath = classifiedPath;
1695
1984
  exports.classifiedSearchPath = classifiedSearchPath;
1696
1985
  exports.clearCookieConsent = clearCookieConsent;
@@ -1700,6 +1989,7 @@ exports.composeEdition = composeEdition;
1700
1989
  exports.dealDollars = dealDollars;
1701
1990
  exports.dealGoPath = dealGoPath;
1702
1991
  exports.dealsFeedPath = dealsFeedPath;
1992
+ exports.decorateNetworkUrl = decorateNetworkUrl;
1703
1993
  exports.defaultRevalidateTargets = defaultRevalidateTargets;
1704
1994
  exports.definePublication = definePublication;
1705
1995
  exports.displayHeadline = displayHeadline;
@@ -1719,6 +2009,7 @@ exports.isAdult = isAdult;
1719
2009
  exports.isCategoryAllowed = isCategoryAllowed;
1720
2010
  exports.isClassifiedCategory = isClassifiedCategory;
1721
2011
  exports.isEmploymentType = isEmploymentType;
2012
+ exports.isGeoConsentRequired = isGeoConsentRequired;
1722
2013
  exports.jobEmployerBody = jobEmployerBody;
1723
2014
  exports.jobOpeningBody = jobOpeningBody;
1724
2015
  exports.jobPostingBody = jobPostingBody;
@@ -1739,6 +2030,7 @@ exports.publicationFromSnapshot = publicationFromSnapshot;
1739
2030
  exports.publicationJsonLd = publicationJsonLd;
1740
2031
  exports.publicationMeta = publicationMeta;
1741
2032
  exports.publicationRecord = publicationRecord;
2033
+ exports.queryNetworkConsentHub = queryNetworkConsentHub;
1742
2034
  exports.recordConsentToCompliance = recordConsentToCompliance;
1743
2035
  exports.referralCode = referralCode;
1744
2036
  exports.rejectOptionalCookies = rejectOptionalCookies;
@@ -1752,6 +2044,7 @@ exports.sectionDefinition = sectionDefinition;
1752
2044
  exports.sectionId = sectionId;
1753
2045
  exports.sectionPath = sectionPath;
1754
2046
  exports.setCookieConsent = setCookieConsent;
2047
+ exports.setNetworkConsentHub = setNetworkConsentHub;
1755
2048
  exports.setTheme = setTheme;
1756
2049
  exports.shortDeck = shortDeck;
1757
2050
  exports.shortText = shortText;
package/dist/index.d.cts CHANGED
@@ -272,6 +272,8 @@ declare const COOKIE_CONSENT_KEY = "sm_cookie_consent_v1";
272
272
  declare const COOKIE_CONSENT_BACKUP_KEY = "napsite_cookie_consent_v1";
273
273
  declare const COOKIE_CONSENT_EVENT = "scalemule-cookie-consent-change";
274
274
  declare const COOKIE_CONSENT_MAX_AGE_SECONDS: number;
275
+ declare const DEFAULT_CONSENT_HUB_URL = "https://bayareachronicle.com/consent-bridge.html";
276
+ declare const NETWORK_CONSENT_STORAGE_KEY = "sm_network_consent_status";
275
277
  declare const DEFAULT_PREFERENCES_REJECTED: CookiePreferences;
276
278
  declare const DEFAULT_PREFERENCES_ACCEPTED: CookiePreferences;
277
279
  declare const COOKIE_CATEGORIES: CookieCategoryDefinition[];
@@ -280,6 +282,37 @@ declare const COOKIE_CATEGORIES: CookieCategoryDefinition[];
280
282
  * Checks HTTP cookie first, then localStorage fallback, then in-memory state.
281
283
  */
282
284
  declare function getCookieConsent(): CookieConsentRecord | null;
285
+ /**
286
+ * ISO 3166-1 alpha-2 country codes for EU/EEA member states and the UK/Switzerland
287
+ * subject to the GDPR and ePrivacy Directive prior-consent requirement.
288
+ */
289
+ declare const EU_EEA_UK_COUNTRIES: Set<string>;
290
+ /**
291
+ * Determine if cookie consent prior-opt-in banner is required based on visitor geography.
292
+ * For US readers (including California CCPA/CPRA), opt-in banners are not required.
293
+ * Returns true ONLY if the visitor is identified as being within an EU/EEA/UK jurisdiction.
294
+ */
295
+ declare function isGeoConsentRequired(countryCode?: string | null): boolean;
296
+ /**
297
+ * Checks for a cross-domain network consent signal in the URL query (?sm_consent=accepted|rejected|1|0).
298
+ * If present, silently records the consent choice locally and removes the param from the address bar.
299
+ * Returns true if a consent signal was processed.
300
+ */
301
+ declare function checkUrlConsentBridge(): boolean;
302
+ /**
303
+ * Appends the current consent decision to an outgoing URL within the newspaper network
304
+ * so sister publications can adopt the user's choice without requiring a new banner prompt.
305
+ */
306
+ declare function decorateNetworkUrl(targetUrl: string): string;
307
+ /**
308
+ * Queries the central network iframe hub to retrieve the reader's cross-domain consent choice.
309
+ * If running on the hub origin directly, reads from local storage without an iframe.
310
+ */
311
+ declare function queryNetworkConsentHub(hubUrl?: string, timeoutMs?: number): Promise<"accepted" | "rejected" | null>;
312
+ /**
313
+ * Propagates a consent decision to the central network iframe hub so all sister publications adopt it.
314
+ */
315
+ declare function setNetworkConsentHub(status: "accepted" | "rejected" | null, hubUrl?: string): void;
283
316
  /**
284
317
  * Check if the user has already provided an explicit consent decision.
285
318
  */
@@ -356,4 +389,4 @@ declare function subscribeTheme(listener: () => void): () => void;
356
389
  */
357
390
  declare function themeInitScript(): string;
358
391
 
359
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, type CookieCategory, type CookieCategoryDefinition, type CookieConsentRecord, type CookieDisclosure, type CookiePreferences, CouponOffer, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, acceptAllCookies, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearCookieConsent, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getCookieConsent, getStoredTheme, getSystemTheme, hasConsented, inventoryCacheOptions, isCategoryAllowed, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, recordConsentToCompliance, referralCode, rejectOptionalCookies, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setCookieConsent, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
392
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, type CookieCategory, type CookieCategoryDefinition, type CookieConsentRecord, type CookieDisclosure, type CookiePreferences, CouponOffer, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EU_EEA_UK_COUNTRIES, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, NETWORK_CONSENT_STORAGE_KEY, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, acceptAllCookies, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, checkUrlConsentBridge, clearCookieConsent, clearTheme, composeEdition, decorateNetworkUrl, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getCookieConsent, getStoredTheme, getSystemTheme, hasConsented, inventoryCacheOptions, isCategoryAllowed, isGeoConsentRequired, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, queryNetworkConsentHub, recordConsentToCompliance, referralCode, rejectOptionalCookies, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setCookieConsent, setNetworkConsentHub, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
package/dist/index.d.ts CHANGED
@@ -272,6 +272,8 @@ declare const COOKIE_CONSENT_KEY = "sm_cookie_consent_v1";
272
272
  declare const COOKIE_CONSENT_BACKUP_KEY = "napsite_cookie_consent_v1";
273
273
  declare const COOKIE_CONSENT_EVENT = "scalemule-cookie-consent-change";
274
274
  declare const COOKIE_CONSENT_MAX_AGE_SECONDS: number;
275
+ declare const DEFAULT_CONSENT_HUB_URL = "https://bayareachronicle.com/consent-bridge.html";
276
+ declare const NETWORK_CONSENT_STORAGE_KEY = "sm_network_consent_status";
275
277
  declare const DEFAULT_PREFERENCES_REJECTED: CookiePreferences;
276
278
  declare const DEFAULT_PREFERENCES_ACCEPTED: CookiePreferences;
277
279
  declare const COOKIE_CATEGORIES: CookieCategoryDefinition[];
@@ -280,6 +282,37 @@ declare const COOKIE_CATEGORIES: CookieCategoryDefinition[];
280
282
  * Checks HTTP cookie first, then localStorage fallback, then in-memory state.
281
283
  */
282
284
  declare function getCookieConsent(): CookieConsentRecord | null;
285
+ /**
286
+ * ISO 3166-1 alpha-2 country codes for EU/EEA member states and the UK/Switzerland
287
+ * subject to the GDPR and ePrivacy Directive prior-consent requirement.
288
+ */
289
+ declare const EU_EEA_UK_COUNTRIES: Set<string>;
290
+ /**
291
+ * Determine if cookie consent prior-opt-in banner is required based on visitor geography.
292
+ * For US readers (including California CCPA/CPRA), opt-in banners are not required.
293
+ * Returns true ONLY if the visitor is identified as being within an EU/EEA/UK jurisdiction.
294
+ */
295
+ declare function isGeoConsentRequired(countryCode?: string | null): boolean;
296
+ /**
297
+ * Checks for a cross-domain network consent signal in the URL query (?sm_consent=accepted|rejected|1|0).
298
+ * If present, silently records the consent choice locally and removes the param from the address bar.
299
+ * Returns true if a consent signal was processed.
300
+ */
301
+ declare function checkUrlConsentBridge(): boolean;
302
+ /**
303
+ * Appends the current consent decision to an outgoing URL within the newspaper network
304
+ * so sister publications can adopt the user's choice without requiring a new banner prompt.
305
+ */
306
+ declare function decorateNetworkUrl(targetUrl: string): string;
307
+ /**
308
+ * Queries the central network iframe hub to retrieve the reader's cross-domain consent choice.
309
+ * If running on the hub origin directly, reads from local storage without an iframe.
310
+ */
311
+ declare function queryNetworkConsentHub(hubUrl?: string, timeoutMs?: number): Promise<"accepted" | "rejected" | null>;
312
+ /**
313
+ * Propagates a consent decision to the central network iframe hub so all sister publications adopt it.
314
+ */
315
+ declare function setNetworkConsentHub(status: "accepted" | "rejected" | null, hubUrl?: string): void;
283
316
  /**
284
317
  * Check if the user has already provided an explicit consent decision.
285
318
  */
@@ -356,4 +389,4 @@ declare function subscribeTheme(listener: () => void): () => void;
356
389
  */
357
390
  declare function themeInitScript(): string;
358
391
 
359
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, type CookieCategory, type CookieCategoryDefinition, type CookieConsentRecord, type CookieDisclosure, type CookiePreferences, CouponOffer, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, acceptAllCookies, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearCookieConsent, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getCookieConsent, getStoredTheme, getSystemTheme, hasConsented, inventoryCacheOptions, isCategoryAllowed, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, recordConsentToCompliance, referralCode, rejectOptionalCookies, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setCookieConsent, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
392
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, type CookieCategory, type CookieCategoryDefinition, type CookieConsentRecord, type CookieDisclosure, type CookiePreferences, CouponOffer, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EU_EEA_UK_COUNTRIES, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, NETWORK_CONSENT_STORAGE_KEY, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, acceptAllCookies, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, checkUrlConsentBridge, clearCookieConsent, clearTheme, composeEdition, decorateNetworkUrl, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getCookieConsent, getStoredTheme, getSystemTheme, hasConsented, inventoryCacheOptions, isCategoryAllowed, isGeoConsentRequired, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, queryNetworkConsentHub, recordConsentToCompliance, referralCode, rejectOptionalCookies, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setCookieConsent, setNetworkConsentHub, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
package/dist/index.js CHANGED
@@ -3,9 +3,9 @@ import { definePublication } from './chunk-C7JTEHTH.js';
3
3
  export { BROADSHEET_THEME, FEATURE_NAMES, LAYOUT_COOKIE, acceptRemoteRecord, acceptSnapshot, definePublication, featureEnabled, parseSnapshot, publicationFromSnapshot, publicationRecord, resolveLayout, snapshotFromRecord, syncPublication } from './chunk-C7JTEHTH.js';
4
4
  export { CLASSIFIED_CATEGORIES, JOB_EMPLOYMENT_TYPES, categoryLabel, classifiedPath, classifiedSearchPath, clipChars, dealDollars, dealGoPath, dealsFeedPath, employmentLabel, isClassifiedCategory, isEmploymentType, jobEmployerBody, jobOpeningBody, jobPostingBody, prepareClassified, prepareJobPost, publicJobPath, publicJobsPath } from './chunk-C7ZPLH2K.js';
5
5
  export { MEET_ACTIVITIES, activityLabel, canHostMeetup, isAdult, prepareMeetup, screenMessage } from './chunk-GB36YM56.js';
6
- export { CookieConsentBanner } from './chunk-5QHNNRVQ.js';
7
- export { CookieSettingsView } from './chunk-AYSBXLOT.js';
8
- export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, acceptAllCookies, clearCookieConsent, getCookieConsent, hasConsented, isCategoryAllowed, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent } from './chunk-RNXTBN5Z.js';
6
+ export { CookieConsentBanner } from './chunk-5MHPE77S.js';
7
+ export { CookieSettingsView } from './chunk-FUUL2PSF.js';
8
+ export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, decorateNetworkUrl, getCookieConsent, hasConsented, isCategoryAllowed, isGeoConsentRequired, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from './chunk-L5OE6DND.js';
9
9
 
10
10
  // src/preset.ts
11
11
  var walnutCreekTimes = definePublication({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/news-publication",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "Shared server framework for ScaleMule local news publications. Walnut Creek Times is the broadsheet default; each paper supplies its own identity and feature flags.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",