import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { triggerPixel, politeTriggerPixel, mergeDeep, replaceAuctionPrice, generateUUID } from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'floxis'; const GVLID = 1609; const DEFAULT_BID_TTL = 300; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_NET_REVENUE = true; const DEFAULT_REGION = 'us-e'; const DEFAULT_PARTNER = BIDDER_CODE; const SYNC_PATH = '/sync'; const FLOXIS_ID_KEY = 'flx_uid'; const FLOXIS_ID_COOKIE_EXP = 2592000000; // 30 days const UUID_LENGTH = 36; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); // Server-echo user-sync: the /pbjs response carries seat + region in this header (on bid and no-bid // alike), so getUserSyncs derives sync targets from serverResponses statelessly — no module state that // could leak across concurrent auctions. Absent header (older backend) => no sync, a safe no-op. const SYNC_HEADER = 'x-floxis-sync'; // partner/region are interpolated into the request host, so they must be valid DNS labels — // otherwise a value with URL delimiters (e.g. 'evil.com/x?') would change the request origin. const HOST_LABEL_REGEX = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i; function isValidHostLabel(label) { return typeof label === 'string' && HOST_LABEL_REGEX.test(label); } // Bidding host: the supply partner's regional subdomain (floxis itself has no partner prefix). function getBidHost(region, partner) { if (!isValidHostLabel(region) || !isValidHostLabel(partner)) return null; return partner === BIDDER_CODE ? `${region}.floxis.tech` : `${partner}-${region}.floxis.tech`; } function getEndpointUrl(seat, region, partner) { const host = getBidHost(region, partner); return host ? `https://${host}/pbjs?seat=${encodeURIComponent(seat)}` : null; } // Cookie-sync host is Floxis-operated and region-scoped (px-.floxis.tech), independent of // the partner subdomain used for bidding. The trackers /sync endpoint resolves seat -> supply partner. function getSyncHost(region) { return isValidHostLabel(region) ? `https://px-${region}.floxis.tech` : null; } // Telemetry event host is pinned to px-us-e regardless of bid region. Only us-e is provisioned; // a beacon to an unprovisioned host would lose the very signal meant to catch misconfiguration. // SHIPPING-INTENT: switch to region-derived host (getSyncHost(region)) when px-eu and px-apac are provisioned. const TELEMETRY_HOST = 'https://px-us-e.floxis.tech'; const TELEMETRY_PATH = '/event'; // Assemble an event-beacon URL. consentSuffix is a pre-built '&k=v&...' string (may be empty). // extras is a plain object of optional dimension key→value pairs; falsy values are omitted. function buildEventUrl(eventType, { seat, region }, extras, consentSuffix) { const base = `${TELEMETRY_HOST}${TELEMETRY_PATH}?event=${encodeURIComponent(eventType)}&seat=${encodeURIComponent(seat)}®ion=${encodeURIComponent(region)}`; const extraParams = Object.entries(extras) .filter(([, v]) => v != null && v !== '') .map(([k, v]) => `${k}=${encodeURIComponent(v)}`) .join('&'); return base + (extraParams ? '&' + extraParams : '') + consentSuffix; } // IAB consent query params for the trackers /sync endpoint. function buildConsentQuery(gdprConsent, uspConsent, gppConsent) { const query = []; if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { query.push('gdpr=' + Number(gdprConsent.gdprApplies)); } if (gdprConsent.consentString) { query.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString)); } } if (uspConsent) { query.push('us_privacy=' + encodeURIComponent(uspConsent)); } if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { query.push('gpp=' + encodeURIComponent(gppConsent.gppString)); query.push('gpp_sid=' + encodeURIComponent(gppConsent.applicableSections.join(','))); } return query; } function normalizeBidParams(params = {}) { return { seat: params.seat, region: params.region || DEFAULT_REGION, partner: params.partner || DEFAULT_PARTNER }; } function isValidFloxisId(id) { return typeof id === 'string' && id.length === UUID_LENGTH; } // Adpushup-only behavior from the 9.41 fork. Official 11.24 does not include it, so the upgrade has to copy it or Floxis loses the first-party id. // Never returns an id that didn't persist (no store, or storageControl denying the key while the // keyless enablement checks pass) — an unpersisted id would rotate every auction, worse than none. function getOrCreateFloxisId() { if (typeof window === 'undefined') return null; try { const localOk = storage.localStorageIsEnabled(); const cookieOk = storage.cookiesAreEnabled(); if (!localOk && !cookieOk) return null; let id = localOk ? storage.getDataFromLocalStorage(FLOXIS_ID_KEY) : null; if (!isValidFloxisId(id) && cookieOk) { id = storage.getCookie(FLOXIS_ID_KEY); } const minted = !isValidFloxisId(id); if (minted) { id = generateUUID(); } if (localOk) { storage.setDataInLocalStorage(FLOXIS_ID_KEY, id); } if (cookieOk) { const expires = new Date(Date.now() + FLOXIS_ID_COOKIE_EXP).toUTCString(); storage.setCookie(FLOXIS_ID_KEY, id, expires); } if (minted && storage.getDataFromLocalStorage(FLOXIS_ID_KEY) !== id && storage.getCookie(FLOXIS_ID_KEY) !== id) { return null; } return id; } catch (e) { return null; } } // Parse the server-echoed sync header (`seat=®ion=