in packages/fxa-auth-server/lib/senders/email.js [3076:3306]
Mailer.prototype._generateLinks = function (
primaryLink,
message,
query,
templateName,
appStoreLink,
playStoreLink
) {
const { email, uid, metricsEnabled } = message;
// set this to avoid passing `metricsEnabled` around to all link functions
this.metricsEnabled = metricsEnabled;
const localizedUrls = (message) => {
const defaultSureyUrl =
'https://survey.alchemer.com/s3/6534408/Privacy-Security-Product-Cancellation-of-Service-Q4-21';
const urls = {};
if (
config.subscriptions.productConfigsFirestore.enabled &&
message.planConfig
) {
// we are not using `determineLocale` because the product config might support more locales than the FxA supported locales list
const locales = message.acceptLanguage ? [message.acceptLanguage] : [];
const localizedConfigs = localizedPlanConfig(
message.planConfig,
locales
);
// the ToS and Privacy Notice URLs are actually not localized in the product config; the redirect endpoint on the payments server does that. but we do need it in the urls object so we can overwrite the metadata ones with the Firestore ones.
// eslint did not like ??=
localizedConfigs['urls'] ?? (localizedConfigs['urls'] = {});
const urlKeys = {
termsOfServiceDownloadURL: 'termsOfServiceDownload',
privacyNoticeDownloadURL: 'privacyNoticeDownload',
cancellationSurveyUrl: 'cancellationSurvey',
};
Object.entries(urlKeys).forEach(([urlKey, configKey]) => {
if (localizedConfigs.urls[configKey]) {
urls[urlKey] = localizedConfigs.urls[configKey];
}
});
}
const cancellationSurveyUrl =
(message.productMetadata &&
message.productMetadata['product:cancellationSurveyURL']) ||
defaultSureyUrl;
return {
...productDetailsFromPlan(
{
product_metadata:
message.productMetadata || message.subscription?.productMetadata,
},
determineLocale(message.acceptLanguage)
),
cancellationSurveyUrl,
...urls,
};
};
const {
termsOfServiceDownloadURL = this.subscriptionTermsUrl,
privacyNoticeDownloadURL = this.privacyUrl,
cancellationSurveyUrl,
} = localizedUrls(message);
// Generate all possible links. The option to use a specific link
// is left up to the template.
const links = {};
const utmContent = templateNameToContentMap[templateName];
if (primaryLink && utmContent) {
links['link'] = this._generateUTMLink(
primaryLink,
query,
templateName,
utmContent
);
}
if (appStoreLink && utmContent) {
links['appStoreLink'] = this._generateUTMLink(
appStoreLink,
query,
templateName,
utmContent
);
}
if (playStoreLink && utmContent) {
links['playStoreLink'] = this._generateUTMLink(
playStoreLink,
query,
templateName,
utmContent
);
}
links['privacyUrl'] = this.createPrivacyLink(templateName);
links['supportLinkAttributes'] = this._supportLinkAttributes(templateName);
links['supportUrl'] = this.createSupportLink(templateName);
links['subscriptionSupportUrl'] = this._generateUTMLink(
this.subscriptionSupportUrl,
{},
templateName,
'subscription-support'
);
links['passwordChangeLink'] = this.createPasswordChangeLink(
email,
templateName
);
links['passwordChangeLinkAttributes'] = this._passwordChangeLinkAttributes(
email,
templateName
);
links['resetLink'] = this.createPasswordResetLink(
email,
templateName,
query.emailToHashWith
);
links['resetLinkAttributes'] = this._passwordResetLinkAttributes(
email,
templateName,
query.emailToHashWith
);
links['androidLink'] = this._generateUTMLink(
this.androidUrl,
query,
templateName,
'connect-android'
);
links['iosLink'] = this._generateUTMLink(
this.iosUrl,
query,
templateName,
'connect-ios'
);
links['passwordManagerInfoUrl'] = this._generateUTMLink(
this.passwordManagerInfoUrl,
query,
templateName,
'password-info'
);
links['reportSignInLink'] = this.createReportSignInLink(
templateName,
query
);
links['reportSignInLinkAttributes'] = this._reportSignInLinkAttributes(
email,
templateName,
query
);
links['revokeAccountRecoveryLink'] =
this.createRevokeAccountRecoveryLink(templateName);
links['revokeAccountRecoveryLinkAttributes'] =
this._revokeAccountRecoveryLinkAttributes(templateName);
links['createAccountRecoveryLink'] =
this.createAccountRecoveryLink(templateName);
links.accountSettingsUrl = this._generateUTMLink(
this.accountSettingsUrl,
{ ...query, email, uid },
templateName,
'account-settings'
);
links.accountSettingsLinkAttributes = `href="${links.accountSettingsUrl}" target="_blank" rel="noopener noreferrer" style="color:#ffffff;font-weight:500;"`;
links.cancellationSurveyUrl = cancellationSurveyUrl;
links.cancellationSurveyLinkAttributes = `href="${links.cancellationSurveyUrl}" style="text-decoration: none; color: #0060DF;"`;
links.subscriptionTermsUrl = this._legalDocsRedirectUrl(
this._generateUTMLink(
termsOfServiceDownloadURL,
{},
templateName,
'subscription-terms'
)
);
links.subscriptionPrivacyUrl = this._legalDocsRedirectUrl(
this._generateUTMLink(
privacyNoticeDownloadURL,
{},
templateName,
'subscription-privacy'
)
);
links.cancelSubscriptionUrl = this._generateUTMLink(
this.subscriptionSettingsUrl,
{ ...query, email, uid },
templateName,
'cancel-subscription'
);
links.reactivateSubscriptionUrl = this._generateUTMLink(
this.subscriptionSettingsUrl,
{ ...query, email, uid },
templateName,
'reactivate-subscription'
);
links.updateBillingUrl = this._generateUTMLink(
this.subscriptionSettingsUrl,
{ ...query, email, uid },
templateName,
'update-billing'
);
links.unsubscribeUrl = this.unsubscribeUrl;
const queryOneClick = extend(query, { one_click: true });
if (primaryLink && utmContent) {
links['oneClickLink'] = this._generateUTMLink(
primaryLink,
queryOneClick,
templateName,
`${utmContent}-oneclick`
);
}
return links;
};