Skip to content

Instantly share code, notes, and snippets.

@tylerzey
Created September 22, 2025 20:35
Show Gist options
  • Select an option

  • Save tylerzey/f6cb5caf963b1edbaf5a821975ddcabd to your computer and use it in GitHub Desktop.

Select an option

Save tylerzey/f6cb5caf963b1edbaf5a821975ddcabd to your computer and use it in GitHub Desktop.
var OPReplay=function(){"use strict";class c{constructor(){this._cfg=null,this._dispatcher=null,this._memStore={}}nowTs(){return new Date().toISOString()}clamp(t,e,n){return Math.max(e,Math.min(n,t))}safeJsonParse(t){try{return JSON.parse(t)}catch(e){return null}}sha1(t){let e=0;if(!t)return"0";for(let n=0;n<t.length;n++){const s=t.charCodeAt(n);e=(e<<5)-e+s,e|=0}return String(e)}getStorage(t){const e=t==="session"?window.sessionStorage:window.localStorage;try{const n="__opreplay_test__"+Math.random();return e.setItem(n,"1"),e.removeItem(n),e}catch(n){return{getItem:s=>this._memStore[s]||null,setItem:(s,o)=>{this._memStore[s]=o},removeItem:s=>{delete this._memStore[s]}}}}nsKey(t){var e;return(((e=this._cfg)==null?void 0:e.namespace)||"default")+":opreplay:"+t}loadBag(){if(!this._cfg)throw new Error("Plugin not initialized");const t=this._cfg._store.getItem(this.nsKey("bag")),e=this.safeJsonParse(t||"");if(!e||!Array.isArray(e.items))return{v:1,items:[]};const n=Date.now(),s=this._cfg.ttlMs||24*60*60*1e3,o=this._cfg.postConsentBufferTtlMs||30*60*1e3;return e.items=e.items.filter(i=>{const a=n-(i._ts||0),r=i._phase==="post"?o:s;return a<=r}),e}saveBag(t){if(!this._cfg)throw new Error("Plugin not initialized");const e=this.clamp(this._cfg.maxItems||300,10,5e3);t.items.length>e&&(t.items=t.items.slice(t.items.length-e)),this._cfg._store.setItem(this.nsKey("bag"),JSON.stringify(t))}mkIdempotencyKey(t){var n;const e=[((n=this._cfg)==null?void 0:n.namespace)||"default",t.name||"",String(t.original_ts||""),t.props&&(t.props.path||t.props.url||"")||""].join("|");return this.sha1(e)}init(t){this._cfg=t||{},this._cfg._store=this.getStorage(this._cfg.storage==="session"?"session":"local"),this.saveBag(this.loadBag())}connect(t){this._dispatcher=t}track(t,e){if(!this._cfg)throw new Error("EventReplay.init() must be called first");const n=this.loadBag(),s={name:String(t||""),props:e?JSON.parse(JSON.stringify(e)):{},original_ts:this.nowTs(),_ts:Date.now(),_phase:this._dispatcher?"post":"pre",idempotency_key:""};s.idempotency_key=this.mkIdempotencyKey(s),n.items.push(s),this.saveBag(n)}flush(t={}){if(!this._dispatcher)return 0;const e=this.loadBag();let n=0;return e.items.forEach(s=>{if(!s.__sent){const o={name:s.name,props:s.props||{},original_ts:s.original_ts,idempotency_key:s.idempotency_key};t.reason&&(o.replay_reason=t.reason,o.replayed_at=this.nowTs()),this._dispatcher(o),s.__sent=!0,n++}}),e.items=e.items.filter(s=>!s.__sent&&s._phase==="post"),this.saveBag(e),n}onConsentChange(t){var a;if(!this._dispatcher)return;const e=((a=this._cfg)==null?void 0:a.eventCategories)||{};if(Object.keys(e).length===0)return;const n=this.loadBag(),s=this.nowTs(),o=(t==null?void 0:t.reason)||"category_flip",i=(t==null?void 0:t.newlyAllowedCategories)||[];n.items.forEach(r=>{if(r._phase!=="post"||r.__sent||!(e[r.name]||[]).some(l=>i.includes(l)))return;const g={name:r.name,props:r.props||{},original_ts:r.original_ts,idempotency_key:r.idempotency_key,replay_reason:o+(i.length?":"+i.join(","):""),replayed_at:s};this._dispatcher(g),r.__sent=!0}),n.items=n.items.filter(r=>!(r.__sent&&r._phase==="post")),this.saveBag(n)}clear(){this._cfg&&this._cfg._store.removeItem(this.nsKey("bag")),this._cfg=null,this._dispatcher=null}getConfig(){return this._cfg}getStats(){const t=this.loadBag(),e=t.items.filter(s=>s._phase==="pre").length,n=t.items.filter(s=>s._phase==="post").length;return{totalItems:t.items.length,preItems:e,postItems:n}}}const h=new c;return typeof window!="undefined"&&(window.EventReplay=h),h}();
//# sourceMappingURL=replay-plugin.iife.js.map

EventReplay

A lightweight library for tracking events BEFORE Ours Privacy loads. Captures events during the critical window when users interact with your site before consent is granted.

Installation & Usage

<!-- Include EventReplay -->
<script src="/path/to/eventreplay.js"></script>

<script>
  // Initialize EventReplay
  EventReplay.init({ namespace: "your_site" });

  // Create global track function that tries Ours Privacy first, then EventReplay
  window.track = function (eventName, eventProps = {}, userProps = {}) {
    if (typeof window.ours === "function") {
      // Ours Privacy is available, track directly
      ours("track", eventName, eventProps, userProps);
    } else {
      // Ours Privacy not ready, buffer in EventReplay
      EventReplay.track(eventName, eventProps);
    }
  };

  // Track events - they'll go to Ours Privacy if available, otherwise buffered
  track("page_view", { path: location.pathname });
  track("button_click", { button_id: "cta" });
  track("purchase", { value: 99.99, currency: "USD" });

  // CMP inserts Ours Privacy when consent is granted

  let intervalId = setInterval(() => {
    if (typeof window.ours === "function") {
      EventReplay.connect(function (evt) {
        ours(
          "track",
          evt.name,
          {
            ...evt.props,
            original_ts: evt.original_ts,
            idempotency_key: evt.idempotency_key,
          },
          {}
        );
      });
      EventReplay.flush();
      clearInterval(intervalId);
    }
  }, 1000);
</script>

This library captures events before Ours Privacy loads and sends them once it's ready, with category-specific replay logic.

EventReplay Consent

A focused library for handling consent changes and replaying allowlisted events. Works alongside Ours Privacy to ensure no events are lost when users change their consent preferences.

Installation & Usage

<!-- Include EventReplay Consent -->
<script src="/path/to/eventreplay-consent.js"></script>

<script>
  // Initialize EventReplay
  EventReplay.init({
    namespace: "your_site",
    eventCategories: {
      purchase: ["marketing", "analytics"],
      lead_submitted: ["marketing"],
      page_view: ["analytics"],
      button_click: ["analytics"],
    },
  });

  // Connect to Ours Privacy
  EventReplay.connect(function (evt) {
    ours("track", evt.name, {
      ...evt.props,
      original_ts: evt.original_ts,
      replayed_at: evt.replayed_at,
      replay_reason: evt.replay_reason,
      idempotency_key: evt.idempotency_key,
    }, {});
  });

  // Create unified track function that spies on ours.queue
  window.track = function (eventName, eventProps = {}, userProps = {}) {
    // Track to Ours Privacy (goes to queue)
    ours("track", eventName, eventProps, userProps);
    
    // Also track to consent library for replay capability
    EventReplay.track(eventName, eventProps);
  };

  // Track events - they go to both Ours Privacy and consent library
  track("purchase", { value: 99.99, currency: "USD" });
  track("page_view", { path: location.pathname });

  // Handle consent changes from your CMP
  function onConsentChange(newlyAllowedCategories) {
    EventReplay.onConsentChange({
      newlyAllowedCategories,
      reason: "category_flip",
    });
  }

  // Example: OneTrust integration
  OneTrust.OnConsentChanged(function () {
    const allowedCategories = OneTrust.GetDomainData()
      .ConsentGroups
      .filter((group) => group.ConsentStatus === "consent")
      .map((group) => group.CustomGroupId);

    onConsentChange(allowedCategories);
  });
</script>

This library handles consent changes and replays the right events at the right time, with category-specific precision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment