Skip to content

Commit 3e46ea8

Browse files
committed
New Component: StickyPrepopulation
1 parent 6155d03 commit 3e46ea8

13 files changed

Lines changed: 527 additions & 2 deletions

File tree

packages/scripts/dist/app.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DonationAmount, DonationFrequency, EnForm, ProcessingFees, Country, } from "./events";
2-
import { AmountLabel, Loader, ProgressBar, UpsellLightbox, ENGrid, OptionsDefaults, setRecurrFreq, PageBackground, MediaAttribution, ApplePay, A11y, CapitalizeFields, Ecard, ClickToExpand, Advocacy, DataAttributes, LiveVariables, iFrame, InputPlaceholders, InputHasValueAndFocus, ShowHideRadioCheckboxes, AutoCountrySelect, SkipToMainContentLink, SrcDefer, NeverBounce, AutoYear, Autocomplete, RememberMe, TranslateFields, ShowIfAmount, EngridLogger, OtherAmount, MinMaxAmount, Ticker, DataReplace, DataHide, AddNameToMessage, ExpandRegionName, AppVersion, UrlToForm, RequiredIfVisible, TidyContact, DataLayer, LiveCurrency, Autosubmit, EventTickets, SwapAmounts, DebugPanel, DebugHiddenFields, FreshAddress, BrandingHtml, CountryDisable, PremiumGift, DigitalWallets, MobileCTA, LiveFrequency, UniversalOptIn, Plaid, GiveBySelect, UrlParamsToBodyAttrs, ExitIntentLightbox, SupporterHub, FastFormFill, SetAttr, ShowIfPresent, ENValidators, CustomCurrency, VGS, PostalCodeValidator, CountryRedirect, WelcomeBack, EcardToTarget, UsOnlyForm, ThankYouPageConditionalContent, EmbeddedEcard, CheckboxLabel, UpsellCheckbox, PostDonationEmbed, FrequencyUpsell, CustomPremium, StickyNSG, } from ".";
2+
import { AmountLabel, Loader, ProgressBar, UpsellLightbox, ENGrid, OptionsDefaults, setRecurrFreq, PageBackground, MediaAttribution, ApplePay, A11y, CapitalizeFields, Ecard, ClickToExpand, Advocacy, DataAttributes, LiveVariables, iFrame, InputPlaceholders, InputHasValueAndFocus, ShowHideRadioCheckboxes, AutoCountrySelect, SkipToMainContentLink, SrcDefer, NeverBounce, AutoYear, Autocomplete, RememberMe, TranslateFields, ShowIfAmount, EngridLogger, OtherAmount, MinMaxAmount, Ticker, DataReplace, DataHide, AddNameToMessage, ExpandRegionName, AppVersion, UrlToForm, RequiredIfVisible, TidyContact, DataLayer, LiveCurrency, Autosubmit, EventTickets, SwapAmounts, DebugPanel, DebugHiddenFields, FreshAddress, BrandingHtml, CountryDisable, PremiumGift, DigitalWallets, MobileCTA, LiveFrequency, UniversalOptIn, Plaid, GiveBySelect, UrlParamsToBodyAttrs, ExitIntentLightbox, SupporterHub, FastFormFill, SetAttr, ShowIfPresent, ENValidators, CustomCurrency, VGS, PostalCodeValidator, CountryRedirect, WelcomeBack, EcardToTarget, UsOnlyForm, ThankYouPageConditionalContent, EmbeddedEcard, CheckboxLabel, UpsellCheckbox, PostDonationEmbed, FrequencyUpsell, CustomPremium, StickyNSG, StickyPrepopulation, } from ".";
33
export class App extends ENGrid {
44
constructor(options) {
55
super();
@@ -281,6 +281,7 @@ export class App extends ENGrid {
281281
new CheckboxLabel();
282282
new PostDonationEmbed();
283283
new FrequencyUpsell();
284+
new StickyPrepopulation();
284285
//Debug panel
285286
let showDebugPanel = this.options.Debug;
286287
try {

packages/scripts/dist/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,6 @@ export * from "./post-donation-embed";
8787
export * from "./frequency-upsell-modal";
8888
export * from "./frequency-upsell";
8989
export * from "./sticky-nsg";
90+
export * from "./sticky-prepopulation";
9091
export * from "./events";
9192
export * from "./version";

packages/scripts/dist/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export * from "./post-donation-embed";
8787
export * from "./frequency-upsell-modal";
8888
export * from "./frequency-upsell";
8989
export * from "./sticky-nsg";
90+
export * from "./sticky-prepopulation";
9091
// Events
9192
export * from "./events";
9293
// Version

packages/scripts/dist/interfaces/options.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ export interface Options {
4949
checked?: boolean;
5050
};
5151
StickyNSG?: boolean;
52+
StickyPrepopulation?: false | {
53+
fields: string[];
54+
};
5255
TidyContact?: false | {
5356
cid?: string;
5457
record_field?: string;

packages/scripts/dist/interfaces/options.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export const OptionsDefaults = {
4242
WelcomeBack: false,
4343
OptInLadder: false,
4444
StickyNSG: false,
45+
StickyPrepopulation: false,
4546
PageLayouts: [
4647
"leftleft1col",
4748
"centerleft1col",

packages/scripts/dist/sticky-nsg.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,27 @@ export class StickyNSG {
1515
shouldRun() {
1616
return ENGrid.getOption("StickyNSG") === true;
1717
}
18+
/*
19+
* Determine if NSG provided by EN is active on the page
20+
*/
1821
nsgActiveOnPage() {
1922
return (window.EngagingNetworks &&
2023
window.EngagingNetworks.suggestedGift &&
2124
typeof window.EngagingNetworks.suggestedGift === "object" &&
2225
Object.keys(window.EngagingNetworks.suggestedGift).length > 0);
2326
}
27+
/*
28+
* Delete the cookie if the gift process is complete
29+
*/
2430
deleteCookieIfGiftProcessComplete() {
2531
if (ENGrid.getGiftProcess()) {
2632
this.logger.log("Gift process complete, removing sticky NSG cookie if it exists");
2733
cookie.remove(this.cookieName);
2834
}
2935
}
36+
/*
37+
* Create the sticky NSG cookie if NSG is active on the page
38+
*/
3039
createStickyNSGCookie() {
3140
var _a, _b, _c, _d;
3241
if (!this.nsgActiveOnPage()) {
@@ -38,6 +47,8 @@ export class StickyNSG {
3847
this.logger.log("'skipstickynsg' param present, not creating sticky NSG cookie");
3948
return;
4049
}
50+
// We do some reformating to match the EngridAmounts format
51+
// We also add "Other" to the amounts list
4152
const nsg = window.EngagingNetworks.suggestedGift;
4253
this.logger.log("Creating sticky NSG cookie", nsg);
4354
const cookieValue = JSON.stringify({
@@ -61,6 +72,9 @@ export class StickyNSG {
6172
cookie.set(this.cookieName, cookieValue, { path: "/", expires: 30 });
6273
this.logger.log("Sticky NSG cookie created", cookieValue);
6374
}
75+
/*
76+
* Apply the sticky NSG cookie values to window.EngridAmounts if NSG is not active on the page
77+
*/
6478
applyStickyNSGCookie() {
6579
if (this.nsgActiveOnPage()) {
6680
this.logger.log("NSG active on page, not applying sticky NSG cookie, leaving the EN NSG values.");
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export declare class StickyPrepopulation {
2+
private logger;
3+
private options;
4+
private cookieName;
5+
constructor();
6+
private shouldRun;
7+
private deleteCookieIfGiftProcessComplete;
8+
private createCookie;
9+
private applyPrepopulation;
10+
private getSupporterDetailsFromFields;
11+
private encryptSupporterDetails;
12+
private decryptSupporterDetails;
13+
private createEncryptionKey;
14+
private arrayBufferToBase64;
15+
private base64ToArrayBuffer;
16+
private getSeed;
17+
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3+
return new (P || (P = Promise))(function (resolve, reject) {
4+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7+
step((generator = generator.apply(thisArg, _arguments || [])).next());
8+
});
9+
};
10+
import { ENGrid } from "./engrid";
11+
import { EngridLogger } from "./logger";
12+
import * as cookie from "./cookie";
13+
export class StickyPrepopulation {
14+
constructor() {
15+
this.logger = new EngridLogger("StickyPrepopulation", "teal", "white", "📌");
16+
this.options = { fields: [] };
17+
this.cookieName = "engrid-sticky-prepop";
18+
if (!this.shouldRun()) {
19+
return;
20+
}
21+
this.logger.log("StickyPrepopulation initialized");
22+
this.deleteCookieIfGiftProcessComplete();
23+
this.createCookie();
24+
this.applyPrepopulation();
25+
}
26+
/*
27+
* Determine if we should run the script
28+
* Do not run if RememberMe is active
29+
* Only run if StickyPrepopulation option is set with fields
30+
*/
31+
shouldRun() {
32+
if (ENGrid.getOption("RememberMe")) {
33+
return false;
34+
}
35+
const options = ENGrid.getOption("StickyPrepopulation");
36+
if (options && (options === null || options === void 0 ? void 0 : options.fields.length) > 0) {
37+
this.options = options;
38+
return true;
39+
}
40+
else {
41+
return false;
42+
}
43+
}
44+
/*
45+
* Delete the cookie if the gift process is complete
46+
*/
47+
deleteCookieIfGiftProcessComplete() {
48+
if (ENGrid.getGiftProcess()) {
49+
this.logger.log("Gift process complete, removing sticky prepopulation cookie if it exists");
50+
cookie.remove(this.cookieName);
51+
}
52+
}
53+
/*
54+
* Create the cookie if we're coming from a campaign link and supporterId is present
55+
*/
56+
createCookie() {
57+
var _a;
58+
return __awaiter(this, void 0, void 0, function* () {
59+
// If we're not coming from a campaign link, don't create the cookie
60+
if (!((_a = window.pageJson) === null || _a === void 0 ? void 0 : _a.supporterId)) {
61+
this.logger.log("No supporterId present, not creating sticky prepopulation cookie");
62+
return;
63+
}
64+
try {
65+
const encryptedSupporterDetails = yield this.encryptSupporterDetails(this.getSupporterDetailsFromFields());
66+
cookie.set(this.cookieName, window.btoa(JSON.stringify({
67+
encryptedData: encryptedSupporterDetails.encryptedData,
68+
iv: encryptedSupporterDetails.iv,
69+
pageId: ENGrid.getPageID()
70+
})), { path: "/", expires: 7 });
71+
}
72+
catch (e) {
73+
this.logger.log("Error creating sticky prepopulation cookie");
74+
return;
75+
}
76+
this.logger.log("Sticky prepopulation cookie created");
77+
});
78+
}
79+
/*
80+
* If the cookie is present and supporterId is not (it's not a campaign link prefilled by EN),
81+
* then apply the prepopulation
82+
*/
83+
applyPrepopulation() {
84+
var _a;
85+
return __awaiter(this, void 0, void 0, function* () {
86+
const cookieData = cookie.get(this.cookieName);
87+
if (!cookieData) {
88+
this.logger.log("No sticky prepopulation cookie found, not prepopulating fields");
89+
return;
90+
}
91+
if ((_a = window.pageJson) === null || _a === void 0 ? void 0 : _a.supporterId) {
92+
this.logger.log("SupporterId present, not applying sticky prepopulation");
93+
return;
94+
}
95+
const encryptedSupporterDetails = JSON.parse(window.atob(cookieData));
96+
if (!encryptedSupporterDetails || (encryptedSupporterDetails === null || encryptedSupporterDetails === void 0 ? void 0 : encryptedSupporterDetails.pageId) !== ENGrid.getPageID()) {
97+
this.logger.log("No encrypted supporter details found in cookie, or page ID does not match");
98+
return;
99+
}
100+
let supporterDetails = {};
101+
try {
102+
supporterDetails = JSON.parse(yield this.decryptSupporterDetails(this.base64ToArrayBuffer(encryptedSupporterDetails.encryptedData), new Uint8Array(this.base64ToArrayBuffer(encryptedSupporterDetails.iv))));
103+
}
104+
catch (e) {
105+
this.logger.log("Error decrypting supporter details from cookie");
106+
return;
107+
}
108+
this.options.fields.forEach((fieldName) => {
109+
if (!supporterDetails[fieldName])
110+
return;
111+
ENGrid.setFieldValue(fieldName, decodeURIComponent(supporterDetails[fieldName]));
112+
this.logger.log(`Setting "${fieldName}" to "${decodeURIComponent(supporterDetails[fieldName])}"`);
113+
});
114+
});
115+
}
116+
/*
117+
* Get the supporter details from the form fields
118+
*/
119+
getSupporterDetailsFromFields() {
120+
const supporterDetails = {};
121+
this.options.fields.forEach((fieldName) => {
122+
let field = document.querySelector(`[name="${fieldName}"]`);
123+
// If it is a radio or checkbox, get the checked value
124+
if (field) {
125+
if (field.type === "radio" || field.type === "checkbox") {
126+
field = document.querySelector(`[name="${fieldName}"]:checked`);
127+
}
128+
supporterDetails[fieldName] = encodeURIComponent(field.value);
129+
}
130+
});
131+
return supporterDetails;
132+
}
133+
/*
134+
* Encrypt the supporter details
135+
*/
136+
encryptSupporterDetails(supporterDetails) {
137+
return __awaiter(this, void 0, void 0, function* () {
138+
const encryptionKey = yield this.createEncryptionKey(this.getSeed());
139+
const iv = window.crypto.getRandomValues(new Uint8Array(12));
140+
const supporterDetailsString = JSON.stringify(supporterDetails);
141+
const encryptedData = yield window.crypto.subtle.encrypt({
142+
name: "AES-GCM",
143+
iv: iv,
144+
}, encryptionKey, new TextEncoder().encode(supporterDetailsString));
145+
return {
146+
encryptedData: this.arrayBufferToBase64(encryptedData),
147+
iv: this.arrayBufferToBase64(iv),
148+
};
149+
});
150+
}
151+
/*
152+
* Decrypt the supporter details
153+
*/
154+
decryptSupporterDetails(encryptedSupporterDetails, iv) {
155+
return __awaiter(this, void 0, void 0, function* () {
156+
const encryptionKey = yield this.createEncryptionKey(this.getSeed());
157+
const decryptedData = yield window.crypto.subtle.decrypt({ name: "AES-GCM", iv: iv }, encryptionKey, encryptedSupporterDetails);
158+
return new TextDecoder().decode(decryptedData);
159+
});
160+
}
161+
/*
162+
* Create the encryption key
163+
*/
164+
createEncryptionKey(seed) {
165+
return __awaiter(this, void 0, void 0, function* () {
166+
const encoder = new TextEncoder();
167+
const keyMaterial = yield window.crypto.subtle.importKey("raw", encoder.encode(seed), { name: "PBKDF2" }, false, ["deriveKey"]);
168+
return yield window.crypto.subtle.deriveKey({
169+
name: "PBKDF2",
170+
salt: encoder.encode(seed),
171+
iterations: 100000,
172+
hash: "SHA-256",
173+
}, keyMaterial, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
174+
});
175+
}
176+
/*
177+
* Convert an ArrayBuffer to a base64 string
178+
*/
179+
arrayBufferToBase64(buffer) {
180+
let binary = "";
181+
const bytes = new Uint8Array(buffer);
182+
const len = bytes.byteLength;
183+
for (let i = 0; i < len; i++) {
184+
binary += String.fromCharCode(bytes[i]);
185+
}
186+
return window.btoa(binary);
187+
}
188+
/*
189+
* Create an Array Buffer from a base64 string
190+
*/
191+
base64ToArrayBuffer(base64) {
192+
const binary_string = window.atob(base64);
193+
const len = binary_string.length;
194+
const bytes = new Uint8Array(len);
195+
for (let i = 0; i < len; i++) {
196+
bytes[i] = binary_string.charCodeAt(i);
197+
}
198+
return bytes.buffer;
199+
}
200+
/*
201+
* Derive a seed from the page URL
202+
*/
203+
getSeed() {
204+
const url = new URL(window.location.href);
205+
return url.origin + url.pathname + (url.searchParams.get("ea.tracking.id") ? `?ea.tracking.id=${url.searchParams.get("ea.tracking.id")}` : '');
206+
}
207+
}

packages/scripts/src/app.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ import {
8686
UpsellCheckbox,
8787
PostDonationEmbed,
8888
FrequencyUpsell,
89-
CustomPremium, StickyNSG,
89+
CustomPremium,
90+
StickyNSG,
91+
StickyPrepopulation,
9092
} from ".";
9193

9294
export class App extends ENGrid {
@@ -465,6 +467,8 @@ export class App extends ENGrid {
465467

466468
new FrequencyUpsell();
467469

470+
new StickyPrepopulation();
471+
468472
//Debug panel
469473
let showDebugPanel = this.options.Debug;
470474
try {

packages/scripts/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export * from "./post-donation-embed";
103103
export * from "./frequency-upsell-modal";
104104
export * from "./frequency-upsell";
105105
export * from "./sticky-nsg";
106+
export * from "./sticky-prepopulation";
106107

107108
// Events
108109
export * from "./events";

0 commit comments

Comments
 (0)