media/js/products/shared/affiliate-attribution.es6.js (197 lines of code) (raw):
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import FxaProductButton from '../../base/fxa-product-button.es6.js';
const AffiliateAttribution = {};
const _marketingCookieID = 'moz-cj-affiliate';
AffiliateAttribution.hasMarketingCookie = function () {
return Mozilla.Cookies.hasItem(_marketingCookieID);
};
AffiliateAttribution.getMarketingCookie = function () {
return Mozilla.Cookies.getItem(_marketingCookieID);
};
AffiliateAttribution.setMarketingCookie = function (value, expires) {
// convert timestamp to milliseconds.
const date = new Date(expires * 1000);
Mozilla.Cookies.setItem(
_marketingCookieID,
value,
date.toUTCString(),
'/',
null,
false,
'lax'
);
};
AffiliateAttribution.removeMarketingCookie = function () {
return Mozilla.Cookies.removeItem(
_marketingCookieID,
'/',
null,
false,
'lax'
);
};
AffiliateAttribution.meetsRequirements = function () {
if (
typeof Mozilla.Cookies === 'undefined' ||
!Mozilla.Cookies.enabled() ||
typeof window._SearchParams === 'undefined'
) {
return false;
}
return 'Promise' in window && 'fetch' in window;
};
AffiliateAttribution.getQueryStringParam = function (name, qs) {
const query = typeof qs === 'string' ? qs : window.location.search;
const params = new window._SearchParams(query);
return decodeURIComponent(params.get(name));
};
AffiliateAttribution.getCJEventParam = function () {
const value = AffiliateAttribution.getQueryStringParam('cjevent');
const allowedChars = /^\w{1,64}$/; // alpha-numeric string up to 64 chars.
if (value === 'undefined' || !value || !allowedChars.test(value)) {
return false;
}
return value.toString();
};
AffiliateAttribution.getCJMSEndpoint = function () {
return document
.getElementsByTagName('html')[0]
.getAttribute('data-cj-affiliate-endpoint');
};
AffiliateAttribution.fetch = function (flowId, cjID, value) {
return new window.Promise((resolve, reject) => {
const endpoint = AffiliateAttribution.getCJMSEndpoint();
if (!endpoint) {
reject('CJMS endpoint was not found.');
return;
}
const endpointPath =
typeof value === 'string' ? `${endpoint}/${value}` : endpoint;
const method = typeof value === 'string' ? 'PUT' : 'POST';
const obj = cjID
? { flow_id: flowId, cj_id: cjID }
: { flow_id: flowId };
window
.fetch(endpointPath, {
method: method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(obj)
})
.then((resp) => {
if (resp.status >= 200 && resp.status <= 299) {
return resp.json();
} else if (resp.status === 404 && method === 'PUT') {
return 'Unknown aicID';
} else {
return resp
.text()
.then((message) => {
return message;
})
.catch((e) => {
return e;
});
}
})
.then((resp) => {
if (resp.aic_id && resp.expires) {
resolve(resp);
} else {
reject(resp);
}
})
.catch((e) => {
reject(e);
});
});
};
AffiliateAttribution.addFlowParams = function () {
FxaProductButton.init();
};
AffiliateAttribution.init = function () {
return new window.Promise((resolve, reject) => {
const cjEventParamValue = AffiliateAttribution.getCJEventParam();
FxaProductButton.init()
.then((flowParams) => {
if (!flowParams) {
reject('FxA flow params are undefined.');
return;
}
const flowId = AffiliateAttribution.getQueryStringParam(
'flow_id',
flowParams
);
// If `cjevent` param exists in page URL.
if (cjEventParamValue) {
// If marketing cookie already exists.
if (AffiliateAttribution.hasMarketingCookie()) {
const value = AffiliateAttribution.getMarketingCookie();
// Send the flow ID, cjevent param and cookie ID to the micro service.
AffiliateAttribution.fetch(
flowId,
cjEventParamValue,
value
)
.then((data) => {
AffiliateAttribution.setMarketingCookie(
data.aic_id,
data.expires
);
resolve();
})
.catch((e) => {
/**
* If PUT throws a 404 due to unknown aic id then the cookie
* is no longer active in CJMS and has been archived. In this
* instance we should treat it as a new POST request instead.
* See https://github.com/mozilla/bedrock/issues/11506
*/
if (e === 'Unknown aicID') {
AffiliateAttribution.fetch(
flowId,
cjEventParamValue
)
.then((data) => {
AffiliateAttribution.setMarketingCookie(
data.aic_id,
data.expires
);
resolve();
})
.catch((e) => {
reject(e);
});
} else {
reject(e);
}
});
} else {
// Else just send the flow ID and cjevent param.
AffiliateAttribution.fetch(flowId, cjEventParamValue)
.then((data) => {
AffiliateAttribution.setMarketingCookie(
data.aic_id,
data.expires
);
resolve();
})
.catch((e) => {
reject(e);
});
}
// Else if no`cjevent` param exists but there is an marketing cookie set.
} else if (AffiliateAttribution.hasMarketingCookie()) {
const value = AffiliateAttribution.getMarketingCookie();
// Send only the flow ID and cookie ID to the micro service.
AffiliateAttribution.fetch(flowId, null, value)
.then((data) => {
AffiliateAttribution.setMarketingCookie(
data.aic_id,
data.expires
);
resolve();
})
.catch((e) => {
/**
* If PUT throws a 404 due to unknown aic id then the cookie
* is no longer active in CJMS and has been archived. We can't
* treat this as a new affiliate request since there is no
* cjevent param, so instead we delete the existing marketing
* cookie rather than throw an unhandled error.
* See https://github.com/mozilla/bedrock/issues/11506
*/
if (e === 'Unknown aicID') {
AffiliateAttribution.removeMarketingCookie();
resolve();
}
reject(e);
});
}
})
.catch((e) => {
reject(e);
});
});
};
export default AffiliateAttribution;