import { reactive, watch, watchEffect } from "vue";
import createUpsellApp from "/src/create-upsell-app.js";
import UpsellStore from "/src/upsell-store.js";
import AnalyticsObserver from "/src/analytics-observer.js";
import { captureException } from "@sentry/vue";
/**
* A config object for the {@link main} function that is easy to override for local development.
* Required to initialize the upsell system.
* @typedef {Object} UpsellAppConfig
* @property {number} orderTotal - Total price of the order. Used to calculate analytics event values.
* @property {Object[]} cartItems - Array of cart items.
* @property {string} assetHostingURL - URL for the asset hosting service
* @property {string} upsellEndpointURL - URL for the upsell endpoint
* @property {string} rechargeUpsellServiceURL - URL for the Recharge Upsell Service
*/
/**
* Main entry point for the app.
* This will be loaded directly onto the "Thank You" page.
* It fetches the upsell flow from the server and creates the UpsellStore and Vue app.
* @public
* @async
* @function main
* @param {UpsellAppConfig} config - Config object which may override the {@link defaultConfig}
* @returns {void}
*/
export default async function main(config = {}) {
// If the store can be restored from snapshot, we don't need to fetch the API again
const store = getSavedState() || await createUpsellStore(config);
// A snapshot is saved every time the store updates.
saveState(store);
watch(store, () => saveState(store));
// 600 seconds = 10 minutes
// ReCharge Upsell Service automatically closes the upsell window after 10 minutes.
store.exitFlowOnTimeout(600);
createUpsellApp(store);
/**
* Analytics depend on the global dataLayer object from Google Tag Manager.
*/
const analytics = new AnalyticsObserver(dataLayer, store);
watch(store, (newStore) => analytics.update(newStore));
}
/**
* Fetches the flow from the /upsell API and creates the {@link UpsellStore}.
* The csrf token is also retrieved from the page, if it exists.
* @param {UpsellAppConfig} config - Config object which may override the {@link defaultConfig}
* @returns {Promise<UpsellStore>} - The upsell store
*/
async function createUpsellStore(config) {
let csrfToken = "";
let flow = null;
const upsellEndpointURL = config.upsellEndpointURL || "https://cf.earthbreezedev.com/api/upsell";
/**
* Get the CSRF token first. Without it, there is no point loading the upsell flow.
*/
try {
csrfToken = getCSRFToken();
flow = await getFlow(upsellEndpointURL, cartItems);
console.log("DEBUG: flow", flow);
} catch (error) {
captureException(error);
}
const upsellStoreOptions = {
assetHostingURL: "https://cf.earthbreezedev.com/offer-views",
rechargeUpsellServiceURL: window.location.href,
csrfToken,
...config
};
return reactive(new UpsellStore(flow, upsellStoreOptions));
}
/**
* Get the CSRF token from the hidden input on the page.
* The CSRF token is required for the ReCharge Upsell Service endpoint.
* If it is not found, an error will be thrown.
* @function getCSRFToken
* @returns {string} - CSRF token
* @throws {Error} - If the CSRF token is not found
*/
function getCSRFToken() {
const csrfInput = document.getElementById("csrf_token");
const csrfToken = csrfInput ? csrfInput.value : null;
if (!csrfToken) throw new Error("CSRF token not found");
return csrfToken;
}
/**
* Fetch the upsell flow from the server.
* This is what provides the data for the {@link UpsellStore}
* @async
* @function getFlow
* @param {string} endpoint - URL for the /upsell API endpoint
* @param {Object[]} cartItems - Array of cart items
* @returns {Object} - Upsell flow
* @throws {Error} - If the response is not successful
*/
async function getFlow(endpoint, cartItems) {
const response = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ cartItems }),
});
const { flow } = await response.json();
return flow;
}
/**
* Creates a snapshot of the store and saves it to sessionStorage.
* @param {UpsellStore} store - The store to save
*/
async function saveState(store) {
const snapshot = await store.createSnapshot();
sessionStorage.setItem('upsellStoreSnapshot', snapshot);
}
/**
* Checks for a saved store in sessionStorage. If it exists, it will be restored. Otherwise, returns null.
* @returns {UpsellStore|null} - The saved store, or null if there is no saved store
*/
function getSavedState() {
const snapshot = sessionStorage.getItem('upsellStoreSnapshot');
return snapshot
? reactive(UpsellStore.fromSnapshot(snapshot))
: null;
}
/**
* If this is a production build, run the main function.
*/
if (import.meta.env.PROD) {
window.upsell = main;
}