REDWash the Racing Top Tube Pack by hand, using a mild diluted soap if necessary. Afterwards, let it air dry.
Do not machine wash, machine dry, or iron.
[]
Tan
{"@context":"http:\/\/schema.org\/","@id":"\/products\/racing-top-tube-pack#product","@type":"ProductGroup","brand":{"@type":"Brand","name":"Apidura"},"category":"bags","description":"\n\nDESCRIPTION\n\n\nA streamlined and secure space to store frequently used items for ultra-distance competition, gravel racing and the needs of time-conscious riders\nThe Racing Top Tube Pack is designed for ultra-distance cycling competition and the needs of time-conscious riders. With an innovative magnetic quick-access flip-top opening, this waterproof top tube bag is ideal for storing battery packs, mobile phones and race essentials.\nThis waterproof top tube bag, constructed from an ultralight laminate developed for Apidura, fastens securely to the top tube and works alongside a wide range of frame bags. The completely waterproof flip-top opening provides fast access and a clear view of the full contents of the bag, while keeping your belongings dry.\nA closed-cell foam padding structure protects your frame and electronics from damage and for added convenience, a protected cable port enables charging of devices on the go. Specially designed, high-contrast reflective features enhance side visibility in all light conditions.\nThe Race Top Tube Pack is a useful standalone pack for daily riding or as part of a full Racing Series setup. For bikes with bolt-on top tube bosses see our Racing Bolt-On Top Tube Pack.\n\n\n\nTECHNICAL\n\n\nMaterials\nThe Racing Top Tube Pack is made from Hexalon, a bespoke laminated fabric developed specifically for Apidura. Designed to fit the demands of ultra-distance competition and audax, the material is waterproof and lightweight, with strong tear and abrasion resistance.\nThe attachment points are reinforced with Hypalon, a durable rubberised nylon that provides extra protection against friction and puncture.\nCare\nWash the Racing Top Tube Pack by hand, using a mild diluted soap if necessary. Afterwards, let it air dry.\nDo not machine wash, machine dry, or iron.\n\n","hasVariant":[{"@id":"\/products\/racing-top-tube-pack?variant=44140302893282#variant","@type":"Product","image":"https:\/\/projektride.co.uk\/cdn\/shop\/products\/Screenshot2023-06-25at10.58.19.png?v=1687687104\u0026width=1920","name":"RACING TOP TUBE PACK - 0.5L","offers":{"@id":"\/products\/racing-top-tube-pack?variant=44140302893282#offer","@type":"Offer","availability":"http:\/\/schema.org\/InStock","price":"46.00","priceCurrency":"GBP","url":"https:\/\/projektride.co.uk\/products\/racing-top-tube-pack?variant=44140302893282"},"sku":"TRS"},{"@id":"\/products\/racing-top-tube-pack?variant=44140302926050#variant","@type":"Product","image":"https:\/\/projektride.co.uk\/cdn\/shop\/products\/Screenshot2023-06-25at10.57.47.png?v=1687687074\u0026width=1920","name":"RACING TOP TUBE PACK - 1L","offers":{"@id":"\/products\/racing-top-tube-pack?variant=44140302926050#offer","@type":"Offer","availability":"http:\/\/schema.org\/InStock","price":"52.00","priceCurrency":"GBP","url":"https:\/\/projektride.co.uk\/products\/racing-top-tube-pack?variant=44140302926050"},"sku":"TRM"}],"name":"RACING TOP TUBE PACK","productGroupID":"8108875088098","url":"https:\/\/projektride.co.uk\/products\/racing-top-tube-pack"}
I'm looking to buy this product, when will it arrive and how much does postage cost?Quantity
If the item is showing in stock, we aim to post the product within 24 hours. Please allow 5 working days to receive the item. 1Postage is free on orders over £50. Orders under £50, our postage charge is £3.99.
We also have a physical store, if you are local please pop in -
These Voile Straps® are made of tough stretch polyurethane, with a UV-resistant additive to increase their lifespan.Their Super-Tough® nylon buckle is non-marring, non-conductive, and non-reflective.
Few things in life are as reliable as the original Voile Strap®. Over the last 30 years they have become the ultimate alternative to duct tape, nylon straps, and bungee cords.
On the job, in the outdoors, and everywhere in between: they are the definitive way to
secure
99310050,45824799342818,45824799375586,45824799408354,45824799441122,45824799473890,45824799506658,45824799539426,45824799572194,45824799604962,45824799637730,45824815366370,45824815399138,45824821952738,45824831389922,45824831422690,45824831455458,45824831488226,45824831520994,45824831553762,45828849008866,45828849041634,45828849074402,45828849107170,45828849139938,45828849172706,45828849205474,45828849238242,45828849271010,45828849303778,45828849336546,45828849369314,45828849402082,45828849434850,45828849467618,45828849500386,45828849533154,45828849565922,45828849598690,45828849631458,45828849664226,45828849696994,45828849729762,45828849762530,45828849795298,45828849828066,45828849860834,45828849893602,45828849926370,45828849959138,45828849991906,45828850024674,45828850057442,45828850090210,45828850122978,45828850155746,45828850188514,45828850221282,45828850254050,45828850286818,45828873814242,45828873847010,45828873879778,45828873912546,45828873945314,45828873978082,45828874010850,45828874043618,4582888642
United States (GBP£)
Åland Islands (EUR€)
9922,45828886462690,45828886495458,45828886528226,45828894621922,45828894654690,45828894687458,45828941611234,45828941644002,45828941676770,45828941709538,45828941742306,45828974051554,45829018910946,45829033558242,45829033623778,45829052432610,45830783205602,45830783238370,45830795133154,45830795198690,45830795231458,45830795264226,45830795329762,45830795362530,45843552108770,45843552141538,45843552174306,45843552207074,45843552239842,45843552272610,45843552338146,45843552403682,45843552436450,45843552469218,45843590643938,45843590676706,45843590709474,45843590742242,45843615514850,45843615547618,45843615580386,45843615613154,45843615645922,45843615678690,45843615711458,45843615744226,45843615908066,45843615940834,45843615973602,45843616006370,45843616039138,45843616071906,45843616104674,45843616137442,45843616170210,45843616202978,45843616235746,45843616268514,45843656442082,45843656474850,45843656507618,45843656540386,45843833553122,45843858587874,45843858620642,45843858686178,45843858718946,45843858751714,45843858817250,45843858981090,45843859013858,45843859079394,45843893190882,45843893223650,45843893256418,45843893289186,45843893321954,45843893354722,45843893387490,45843893420258,45843893453026,45843893485794,45843893518562,45843893551330,45843893584098,45843893616866,45843893649634,45843893682402,45843893715170,45843893747938,45843893780706,45843893813474,45843893846242,45843893879010,45843893911778,45843893944546,45843974062306,45844333854946,45844333887714,45844355809506,45844355842274,45844355907810,45844355940578,45844547043554,45844547076322,45844547109090,45844547141858,45856716652770,45856716685538,45856716718306,45856731791586,45856731824354,45856731889890,45856739131618,45856739164386,45856739197154,45856739229922,45856739262690,45856739295458,45856739328226,45856739360994,45856739393762,45856739426530,45856739459298,45856739492066,45856739524834,45856739557602,45856739590370,45856739623138,45856748732642,45856766951650,45856766984418,45856767017186,45856767049954,45856767082722,45856767115490,458
57406288098,45857466777826,45857466810594,45857466843362,45857466876130,45857466908898,45857466941666,45857466974434,45857467007202,45857467039970,45857780138210,45857780203746,45857780236514,45863184957666,45863184990434,45952080281826,45952102858978,46007564533986,46007564566754,46007565222114,46007565254882,46007565680866,46007565713634,46007565943010,46007565975778,46007567286498,46007567319266,46007570563298,46007580459234,46007588552930,46007590027490,46007600775394,46007605723362,46007732240610,46007732961506,46007734042850,46007735746786,46007736271074,46007736991970,46015843598562,46015862210786,46015871779042,46015875711202,46015893242082,46015910183138,46015911788770,46015943409890,46015948030178,46015949537506,46016021987554,46016025854178,46016027492578,46016029753570,46016029786338,46016029819106,46016030965986,46021694849250,46021694882018,46021694914786,46021694947554,46021694980322,46021695013090,46021759271138,46021759303906,46021759336674,46021759369442,46021759402210,46021759434978,4602176
79,57602997387647,57602997420415,57602997453183,57602997485951,57602997518719,57602997715327,57602997879167,57602998075775,57602998206847,57602998239615,57602998337919,57602998370687,57602998403455,57602998501759,57602998534527,57602998600063,57602998894975,57602998960511,57602999026047,57602999124351,57602999157119,57602999189887,57602999222655,57603001090431,57603001123199,57603001155967,57603001188735,57603001221503,57603001254271,57603001287039,57603001614719,57603001647487,57603001680255,57603001713023,57603001745791,57603001778559,57603001844095,57603001876863,57603001942399,57603002007935,57603002040703,57603002073471,57603002106239,57603002204543,57603002237311,57603002270079,57603002302847,57603002335615,57603002368383,57603002466687,57603002499455,57603002532223,57603003089279,57603003122047,57603003154815,57603003187583,57603003220351,57603003253119,57603003285887,57603003318655,57603003351423,57603003384191],"updated_at":"2026-06-19T06:39:24Z","market_locations_enabled":false,"market_id":382140642
Zambia (GBP£)
Andorra (EUR€)
,"preorder_location_filter_enabled":false,"preorder_location_filter_ids":[],"collection_id":null};window._RestockRocketConfig.cachedOutOfStockVariantIds = { out_of_stock_variant_ids: [] };window._RestockRocketConfig.cachedVariantPreorderLimits = {"variant_preorder_limits":{},"updated_at":"2026-06-19T07:53:09Z","shopify_market_id":382140642,"market_locations_enabled":false};
window._RestockRocketConfig.cachedVariantPreorderLimitsMarketKey = "variant_preorder_limits_for_market_382140642";window._RestockRocketConfig.cachedVariantShippingTexts = {"variant_shipping_texts":{},"updated_at":"2026-06-19T07:53:10Z","shopify_market_id":382140642,"market_locations_enabled":false};
window._RestockRocketConfig.cachedVariantShippingTextsMarketKey = "variant_shipping_texts_for_market_382140642";window._RestockRocketConfig.sellingPlans = [{"shopify_selling_plan_group_id":98590196095,"shopify_selling_plan_id":713071886719,"enabled":true,"variant_ids":[55569712382335,55569712415103,55569712480639,55570017616255,55570017550719,55569712447871,55570017583487],"product_variants_source":"custom","name":"Preorder","preorder_button_text":"Preorder","preorder_button_description":"Note: This is a preorder. Items will ship based on the estimated delivery date.","preorder_button_description_enabled":true,"preorder_button_description_background_color":"#ebebeb","preorder_button_description_text_color":"#000000","preorder_button_description_border_radius":10,"preorder_button_description_show_quantity_limit":false,"preorder_button_description_quantity_limit_suffix":" units available for preorder","preorder_button_description_shipping_text_prefix":"Shipping: ","delivery_exact_time":null,"delivery_after_n_intervals":null,"delivery_at":"2025-07-26T09:20:18.169Z","delivery_type":"asap","quantity_limit_text":"{{ quantity }} units available for preorder","preorder_button_description_show_shipping":true,"preorder_button_description_icons_enabled":true,"preorder_shipping_text":"Shipping: {{ date }}","shipping_applies_to_all_products":true,"shipp
ing_text":"Estimated to ship within 2 months","payment_type":"full","billing_checkout_charge_type":"percentage","billing_checkout_charge_amount":null,"billing_checkout_charge_percentage":"100.0","pricing_type":"no_discount","pricing_amount":null,"pricing_percentage":null,"discount_text":"Save {{ discount }}","billing_title":"Full payment","billing_description":null,"enable_billing_widget":false,"inventory_provider":"stoq","preorder_badge_enabled":false,"preorder_badge_text":"Preorder","preorder_badge_text_color":"#FFFFFF","preorder_badge_background_color":"#000000","preorder_discounted_price_enabled":null,"payment_line_item_property_enabled":false,"shipping_line_item_property_enabled":true,"custom_line_item_property_text":null,"preorder_button_text_color":"#ffffff","preorder_button_background_color":"#565557","preorder_button_colors_enabled":true,"markets_enabled":false,"market_id":13779632354,"shopify_market_ids":[],"use_shopify_selling_plan":true,"use_simplified_shipping_text":false,"translations":{},"payment_options":[{"billing_type":"no_remaining_balance","billing_checkout_charge_type":"percentage","billing_checkout_charge_amount":null,"billing_checkout_charge_percentage":"100.0","billing_at":"2025-07-26T09:20:38.472Z","billing_after_n_intervals":7,"billing_after_interval_type":"day","pricing_type":"no_discount","pricing_amount":null,"pricing_percentage":null,"billing_title":"Full payment","billing_description":null,"discount_text":"Save {{ discount }}","shopify_selling_plan_id":713071886719,"is_default":true,"type":"full","translations":{}}],"require_preorder_acknowledgement":false,"preorder_acknowledgement_text":"I acknowledge and agree to the preorder terms and conditions for this product.","disable_button_until_acknowledged":false,"preorder_min_quantity":null,"preorder_max_quantity":null,"countdown_timer_enabled":false,"countdown_timer_style":"text","countdown_timer_text_color":"#000000","countdown_timer_background_color":"#f5f5f5","countdown_timer_border_radius":8,"countdown_timer_format":"DHMS","countdo
wn_timer_use_schedule_dates":true,"countdown_timer_custom_start_date":null,"countdown_timer_custom_end_date":null,"countdown_timer_starts_text":null,"countdown_timer_ends_text":null,"schedule_offer":false,"schedule_start_date":null,"schedule_end_date":null,"updated_at":"2025-08-19T10:05:43.042Z","allow_mixed_cart":true,"mixed_cart_error_message":"Preorders must be purchased separately from regular items. Please complete your current order first, or clear your cart to continue.","b2b_enabled":true,"preorder_progress_bar_enabled":false,"preorder_progress_bar_text":"{{ sold }} of {{ total }} claimed","preorder_progress_bar_fill_color":"#000000","preorder_progress_bar_background_color":"#e5e5e5","preorder_progress_bar_text_color":"#FFFFFF","preorder_progress_bar_border_radius":4,"preorder_progress_bar_show_percentage":false}];(function() {
const cachedData = {"plans":[{"shopify_selling_plan_group_id":98590196095,"shopify_selling_plan_id":713071886719,"enabled":true,"variant_ids":[55569712382335,55569712415103,55569712480639,55570017616255,55570017550719,55569712447871,55570017583487],"product_variants_source":"custom","name":"Preorder","preorder_button_text":"Preorder","preorder_button_description":"Note: This is a preorder. Items will ship based on the estimated delivery date.","preorder_button_description_enabled":true,"preorder_button_description_background_color":"#ebebeb","preorder_button_description_text_color":"#000000","preorder_button_description_border_radius":10,"preorder_button_description_show_quantity_limit":false,"preorder_button_description_quantity_limit_suffix":" units available for preorder","preorder_button_description_shipping_text_prefix":"Shipping: ","delivery_exact_time":null,"delivery_after_n_intervals":null,"delivery_at":"2025-07-26T09:20:18.169Z","delivery_type":"asap","quantity_limit_text":"{{ quantity }} units available for preorder","preorder_button_description_show_shipping":true,"preorder_button_description_icons_enabled":true,"preorder_shipping_text":"Shipping: {{ date }}","ship
ping_applies_to_all_products":true,"shipping_text":"Estimated to ship within 2 months","payment_type":"full","billing_checkout_charge_type":"percentage","billing_checkout_charge_amount":null,"billing_checkout_charge_percentage":"100.0","pricing_type":"no_discount","pricing_amount":null,"pricing_percentage":null,"discount_text":"Save {{ discount }}","billing_title":"Full payment","billing_description":null,"enable_billing_widget":false,"inventory_provider":"stoq","preorder_badge_enabled":false,"preorder_badge_text":"Preorder","preorder_badge_text_color":"#FFFFFF","preorder_badge_background_color":"#000000","preorder_discounted_price_enabled":null,"payment_line_item_property_enabled":false,"shipping_line_item_property_enabled":true,"custom_line_item_property_text":null,"preorder_button_text_color":"#ffffff","preorder_button_background_color":"#565557","preorder_button_colors_enabled":true,"markets_enabled":false,"market_id":13779632354,"shopify_market_ids":[],"use_shopify_selling_plan":true,"use_simplified_ship
3e\n\u003cp data-mce-fragment=\"1\"\u003e\u003cstrong data-mce-fragment=\"1\"\u003eA streamlined and secure space to store frequently used items for ultra-distance competition, gravel racing and the needs of time-conscious riders\u003c\/strong\u003e\u003c\/p\u003e\n\u003cp data-mce-fragment=\"1\"\u003eThe Racing Top Tube Pack is designed for ultra-distance cycling competition and the needs of time-conscious riders. With an innovative magnetic quick-access flip-top opening, this waterproof top tube bag is ideal for storing battery packs, mobile phones and race essentials.\u003c\/p\u003e\n\u003cp data-mce-fragment=\"1\"\u003eThis waterproof top tube bag, constructed from an ultralight laminate developed for Apidura, fastens securely to the top tube and works alongside a wide range of frame bags. The completely waterproof flip-top opening provides fast access and a clear view of the full contents of the bag, while keeping your belongings dry.\u003c\/p\u003e\n\u003cp data-mce-fragment=\"1\"\u003eA closed-cell foa
m padding structure protects your frame and electronics from damage and for added convenience, a protected cable port enables charging of devices on the go. Specially designed, high-contrast reflective features enhance side visibility in all light conditions.\u003c\/p\u003e\n\u003cp data-mce-fragment=\"1\"\u003eThe Race Top Tube Pack is a useful standalone pack for daily riding or as part of a full Racing Series setup. For bikes with bolt-on top tube bosses see our \u003ca data-mce-fragment=\"1\" href=\"https:\/\/www.apidura.com\/shop\/racing-bolt-on-top-tube-pack\/\" data-mce-href=\"https:\/\/www.apidura.com\/shop\/racing-bolt-on-top-tube-pack\/\"\u003eRacing Bolt-On Top Tube Pack\u003c\/a\u003e.\u003c\/p\u003e\n\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv data-mce-fragment=\"1\" class=\"wrap-details read-more\"\u003e\n\u003ch3 data-mce-fragment=\"1\"\u003eTECHNICAL\u003ci data-mce-fragment=\"1\" class=\"tab-arrow\"\u003e\u003c\/i\u003e\n\u003c\/h3\u003e\n\u003cdiv data-mce-fragment=\"1\"\u003e\n\u003cp
data-mce-fragment=\"1\"\u003e\u003cstrong data-mce-fragment=\"1\"\u003eMaterials\u003c\/strong\u003e\u003c\/p\u003e\n\u003cp data-mce-fragment=\"1\"\u003eThe Racing Top Tube Pack is made from \u003ca data-mce-fragment=\"1\" href=\"https:\/\/www.apidura.com\/journal\/introducing-hexalon\/\" data-mce-href=\"https:\/\/www.apidura.com\/journal\/introducing-hexalon\/\"\u003eHexalon\u003c\/a\u003e, a bespoke laminated fabric developed specifically for Apidura. Designed to fit the demands of ultra-distance competition and audax, the material is waterproof and lightweight, with strong tear and abrasion resistance.\u003c\/p\u003e\n\u003cp data-mce-fragment=\"1\"\u003eThe attachment points are reinforced with Hypalon, a durable rubberised nylon that provides extra protection against friction and puncture.\u003c\/p\u003e\n\u003cp data-mce-fragment=\"1\"\u003e\u003cstrong data-mce-fragment=\"1\"\u003eCare\u003c\/strong\u003e\u003c\/p\u003e\n\u003cp data-mce-fragment=\"1\"\u003eWash the Racing Top Tube Pack by hand, usin
g a mild diluted soap if necessary. Afterwards, let it air dry.\u003c\/p\u003e\n\u003cp data-mce-fragment=\"1\"\u003eDo not machine wash, machine dry, or iron.\u003c\/p\u003e\n\u003c\/div\u003e\n\u003c\/div\u003e"};
window._RestockRocketConfig.variantsInventoryPolicy = {44140302893282 : "continue",44140302926050 : "continue",};
window._RestockRocketConfig.variantsInventoryQuantity = {44140302893282 : parseInt("0"),44140302926050 : parseInt("0"),};
window._RestockRocketConfig.variantsPreorderCount = {44140302893282 : parseInt(""),44140302926050 : parseInt(""),};
window._RestockRocketConfig.variantsPreorderCountForMarket = {44140302893282 : null,44140302926050 : null,};
window._RestockRocketConfig.variantsPreorderMaxCount = {44140302893282 : parseInt(""),44140302926050 : parseInt(""),};
window._RestockRocketConfig.variantsPreorderMaxCountForMarket = {44140302893282 : null,44140302926050 : null,};
window._RestockRocketConfig.variantsShippingText = {44140302893282 : "",44140302926050 :
0) {
console.debug(`STOQ - Client clock appears ahead of server by ${Math.abs(Math.round(liquidCacheAge / 60))} minutes, assuming cache fresh`);
window._RestockRocketConfig.isLiquidCacheFresh = true;
} else if (liquidCacheAge
<= LIQUID_CACHE_MAX_AGE) {
console.debug(`STOQ - Liquid cache is fresh (${Math.round(liquidCacheAge / 60)} minutes old)`);
window._RestockRocketConfig.isLiquidCacheFresh = true;
} else {
console.debug(`STOQ - Liquid cache is stale (${Math.round(liquidCacheAge / 60)} minutes old, max ${Math.round(LIQUID_CACHE_MAX_AGE / 60)} minutes)`);
window._RestockRocketConfig.isLiquidCacheFresh = false;
}
}
function checkSettingsExpiry(settings) {
try {
if (!settings || !settings.updated_at) {
console.debug('STOQ - Invalid settings data structure');
return null;
}
if (!settings.cache) {
console.debug('STOQ - settings caching disabled');
return null;
}
// Check if translations are enabled but missing from cache
// This handles the backfill period where DB has translations but metafield doesn't
if (settings.multi_language_enabled) {
if (!settings.translations) {
// Translations enabled but no
Powered by
translation data in metafield
// Metafield hasn't been backfilled yet - force refresh
console.debug('STOQ - multi-language enabled but no translation data in cache, fetching fresh');
return null;
}
// Translations object exists in metafield - cache is valid
// If current locale isn't translated, applyTranslations will gracefully use default locale from base fields
if (window._RestockRocketConfig.normalizedLocale &&
!Object.prototype.hasOwnProperty.call(settings.translations, window._RestockRocketConfig.normalizedLocale)) {
console.debug('STOQ - locale not explicitly translated, will use default language from cache');
}
// Don't return null - continue using cache even for untranslated locales
}
const updatedAt = new Date(settings.updated_at);
if (isNaN(updatedAt.getTime())) {
console.debug('STOQ - Invalid updated_at date format in settings');
return null;
}
const
age = Date.now() - updatedAt.getTime();
if (age
Shopify
< SETTINGS_CACHE_DURATION) {
console.debug('STOQ - settings changed recently, skipping cache');
return null;
}
return settings;
} catch (error) {
console.debug('STOQ - Error checking settings cache:', error);
return null;
}
}
function createRestockRocketContainer() {
const restockRocketContainer = document.createElement('div');
restockRocketContainer.id = 'restock-rocket';
document.body.appendChild(restockRocketContainer);
}
function createRestockRocketScript(scriptUrl) {
const restockRocketScriptElement = document.createElement('script');
restockRocketScriptElement.setAttribute('defer', 'defer');
restockRocketScriptElement.src = scriptUrl;
document.body.appendChild(restockRocketScriptElement);
}
createRestockRocketContainer()
console.debug('STOQ - extension activated')
// Fire stoq_initialized once per page load so the funnel pipeline has a definitive
// "our code ran on this page" signal independent of any cus
.Belarus (GBP£)
tomer interaction.
// Detected variants: the variants present in this page's Liquid context (product page has them;
// collection/index/etc. don't expose variants from Liquid). Used to disambiguate "embed didn't
// load" vs "embed loaded but the variant wasn't a preorder/BIS candidate" in order debug.
try {
const _stoqInitConfig = window._RestockRocketConfig;
const _stoqDetectedVariantIds = (_stoqInitConfig.product && Array.isArray(_stoqInitConfig.product.variants))
? _stoqInitConfig.product.variants.map(function(v) { return v.id })
: [];
const _stoqSelectedVariantId = _stoqInitConfig.selected_variant_id;
Shopify?.analytics?.publish?.('stoq_initialized', {
cart_token: _stoqInitConfig.cartToken || '',
page_url: window.location.href,
page_type: _stoqInitConfig.pageType || '',
shop_domain: _stoqInitConfig.shop || '',
market_id: _stoqInitConfig.marketId || '',
detected_variant_ids: _stoqDetectedVariantIds,
selected_variant_id: _stoqSelectedVariantId || '',
liquid_rendered_at: _stoqInitConfig.liquidRenderedAt || 0,
app_version: _stoqInitConfig.appVersion || '',
liquid_cache_age: _stoqInitConfig.liquidCacheAge,
// Selected variant's stock posture as our app saw it at render — explains
// whether we *should* have treated it as a preorder candidate.
inventory_policy: (_stoqInitConfig.variantsInventoryPolicy || {})[_stoqSelectedVariantId] || '',
inventory_quantity: (_stoqInitConfig.variantsInventoryQuantity || {})[_stoqSelectedVariantId],
});
} catch (e) {
console.debug('STOQ - stoq_initialized publish failed:', e);
}
function applyTranslations(settings) {
try {
// Skip translation logic entirely if multi-language is not enabled
if (!settings || !settings.multi_language_enabled) {
return settings;
}
if (!settings.translations) {
console.debug('STOQ - No translations found, skipping translation');
return settings;
}
const n
ormalizedLocale = window._RestockRocketConfig.normalizedLocale;
const translations = settings.translations;
if (!normalizedLocale) {
// No matching locale has translations; drop payload to save memory
console.debug('STOQ - No matching locale for translations. Available:', Object.keys(translations || {}));
delete settings.translations;
return settings;
}
console.debug(`STOQ - Applying translations for normalized locale: ${normalizedLocale} (original: ${window._RestockRocketConfig.locale})`);
const translatedFields = translations[normalizedLocale];
if (translatedFields && typeof translatedFields === 'object') {
Object.keys(translatedFields).forEach(function(key) {
const value = translatedFields[key];
if (value !== null && value !== undefined && value !== '') {
settings[key] = value;
}
});
} else {
console.debug('STOQ - No translated fields found for locale:', normalizedLocale);
}
delete settings.translations;
return settings;
} catch (e) {
console.debug('STOQ - error applying translations:', e);
return settings;
}
}
// Setup event listener for cart selling plan updates
// This must be called before any scripts are loaded to avoid race conditions
function setupCartSellingPlanUpdater(settings) {
// Setup listener regardless - updateCartSellingPlans has its own guards
// This ensures cleanup happens even when preorders are disabled globally
// Listen for stoq:inventory-data-loaded event dispatched by api.js
window.addEventListener('stoq:inventory-data-loaded', function(event) {
console.debug('STOQ - Inventory data loaded, updating cart selling plans');
if (window._RestockRocket && window._RestockRocket.updateCartSellingPlans) {
window._RestockRocket.updateCartSellingPlans()
.then(hasUpdates => {
if (hasUpdates) {
console.debug('STOQ - cart selling plans update
d successfully');
} else {
console.debug('STOQ - no cart selling plan updates needed');
}
})
.catch(error => {
console.error('STOQ - error updating cart selling plans:', error);
});
}
});
}
// First try to get settings from metafields with expiry check
const cachedSettings = window._RestockRocketConfig.cachedSettings;
const validCachedSettings = cachedSettings ? checkSettingsExpiry(cachedSettings) : null;
if (validCachedSettings) {
console.debug('STOQ - using cached settings');
initializeScripts(validCachedSettings);
} else {
console.debug('STOQ - fetching fresh settings');
const headers = {
'X-Shopify-Shop-Domain': window._RestockRocketConfig.shop || window.Shopify.shop,
'ngrok-skip-browser-warning': 'skip'
};
if (window.Shopify?.theme?.role === 'main') {
headers['X-Shopify-Theme-Schema-Name'] = window.Shopify.theme.schema_name;
headers['X-Shopify-Theme-Sche
ma-Version'] = window.Shopify.theme.schema_version;
headers['X-Shopify-Theme-Store-Id'] = window.Shopify.theme.theme_store_id;
}
fetch(
`${window._RestockRocketConfig.host}/api/v1/setting.json?translation_locale=${window._RestockRocketConfig.normalizedLocale}`,
{ headers }
)
.then(function(response) {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(function(settings) {
initializeScripts(settings);
})
.catch(function(error) {
// If request failed and we have cached settings (even if expired), use them as fallback
if (cachedSettings) {
console.debug('STOQ - using expired cached settings as fallback');
initializeScripts(cachedSettings);
} else {
console.error('STOQ - failed to load settings:', error);
}
})
.catch(function(e) {
console.error(e)
})
}
function fetchEmbedConfig(endpoint, apply) {
return fet
ch(
`${window._RestockRocketConfig.host}/api/v1/embed/${endpoint}.json`,
{
headers: {
'X-Shopify-Shop-Domain': window._RestockRocketConfig.shop || window.Shopify.shop,
'ngrok-skip-browser-warning': 'skip'
}
}
)
.then(function(response) {
if (!response.ok) throw new Error(`Failed to fetch ${endpoint}`);
return response.json();
})
.then(function(data) {
try {
apply(data);
} catch (applyError) {
// Apply failures are programming bugs (e.g. response shape changed
// server-side and the assignment threw). Surface them as console.error
// so they're visible in browser logs, then re-throw to fall through
// to the same Liquid-cached fallback as a fetch failure.
console.error('STOQ - apply failed for ' + endpoint + ':', applyError);
throw applyError;
}
})
.catch(function(error) {
console.debug(`STOQ - using cached ${endpoint}:`, error.message);
});
}
function initializeScripts(settings) {
settings = applyTranslations(settings);
window._RestockRocketConfig.settings = settings;
console.debug(`STOQ - settings configured for ${window._RestockRocketConfig.pageType}`);
// Stale-Liquid resilience (default-on, per-shop opt-out via the
// `disable_refresh_on_stale_liquid` Toggle, surfaced as the negative
// `disable_refresh_on_stale_liquid` flag in settings.json so that
// `undefined` -- in CDN-cached metafield payloads that predate this
// key -- reads as `!undefined === true` and gets default-on behavior
// immediately, no metafield rewrite required).
// When the Liquid CDN cache is older than LIQUID_CACHE_MAX_AGE the in-page
// selling_plans / integrations metafields can be wrong; refresh both from
// the API before launching scripts. Race against a 1000ms timeout so a slow
// API can't block init indefinitely. If the timeout wins, the in-flight
// fetches still complete and update window._Resto
American ExpressBelgium (EUR€)
ckRocketConfig — the
// bundle re-reads sellingPlans/integrations on every interaction, so the
// late-arriving values benefit subsequent renders even though the first
// paint may use the Liquid-cached values. On any failure the existing
// Liquid-loaded values stay in place via fetchEmbedConfig's catch.
if (!window._RestockRocketConfig.isLiquidCacheFresh && !settings.disable_refresh_on_stale_liquid) {
console.debug('STOQ - Liquid cache stale, refreshing selling_plans + integrations');
Promise.race([
Promise.all([
fetchEmbedConfig('selling_plans', function(data) {
if (data && Array.isArray(data.plans)) {
window._RestockRocketConfig.sellingPlans = data.plans;
window._RestockRocketConfig.disabledSellingPlanIds = data.disabled_plan_ids || [];
}
}),
fetchEmbedConfig('integrations', function(data) {
if (Array.isArray(data)) {
window._RestockRocketConfig.integrations
= data;
}
})
]),
new Promise(function(resolve) { setTimeout(resolve, 1000); })
]).then(function() { loadScripts(settings); });
return;
}
loadScripts(settings);
}
function loadScripts(settings) {
// Setup cart selling plan updater BEFORE loading any scripts to avoid race conditions
setupCartSellingPlanUpdater(settings);
if(settings.enable_app) {
cons
t hijackIntegration = window._RestockRocketConfig.integrations.find(function(integration) {
return integration.type === 'hijack' && integration.enabled && integration.page_types.includes(window._RestockRocketConfig.pageType);
})
// STOQ-1520: serve the lean back-in-stock-only build (no preorder/hijack code)
// only to shops with NO preorder plans. Use the full build if preorder is on,
// an enabled offer exists, or a disabled-but-kept plan id remains (cart sweep
// must still strip those). Rationale in the PR.
const hasEnabledOffer = Array.isArray(window._RestockRocketConfig.sellingPlans)
&& window._RestockRocketConfig.sellingPlans.some(function(plan) { return plan && plan.enabled; });
const hasDisabledPlanIds = Array.isArray(window._RestockRocketConfig.disabledSellingPlanIds)
&& window._RestockRocketConfig.disabledSellingPlanIds.length > 0;
const usePreorderBuild = settings.preorder_enabled || hasEnabledOffer || hasDisabledPlanIds;
const collectionScriptUrl = usePreorderBuild
? window._RestockRocketConfig.scriptUrlCollection
: window._RestockRocketConfig.scriptUrlCollectionBis;
const productScriptUrl = usePreorderBuild
? window._RestockRocketConfig.scriptUrlProduct
: window._RestockRocketConfig.scriptUrlProductBis;
const pageType = window._RestockRocketConfig.pageType;
const collectionPageTypes = ['collection', 'index', 'search', 'page'];
if(collectionPageTypes.indexOf(pageType) !== -1 && (settings[`show_button_on_${pageType}`] || settings[`preorder_${pageType}_enabled`])) {
createRestockRocketScript(collectionScriptUrl);
} else if(pageType === 'product') {
createRestockRocketScript(productScriptUrl);
} else if(hijackIntegration) {
createRestockRocketScript(window._RestockRocketConfig.scriptUrlCollection);
} else if(usePreorderBuild) {
// cart/article/blog/list-collections: full build so the cart sweep runs.
createResto
ckRocketScript(window._RestockRocketConfig.scriptUrlCollection);
} else {
console.debug(`STOQ - no scripts enabled for ${pageType}`);
}
// Dispatch custom event when app is loaded
// Cart selling plan updates will be triggered by stoq:inventory-data-loaded event
const appLoadedEvent = new CustomEvent('stoq:loaded', {
detail: {
pageType: window._RestockRocketConfig.pageType,
enabled: settings.enable_app,
settings: settings,
preorderEnabled: settings.preorder_enabled
}
});
console.debug('STOQ - dispatching app loaded event');
window.dispatchEvent(appLoadedEvent);
}
}
Apple Pay
Benin (XOFFr)
DiscoverBosnia & Herzegovina (BAMКМ)
Google Pay
Botswana (BWPP)