ttp-agent-sdk 2.45.1 → 2.45.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-widget.dev.js +241 -41
- package/dist/agent-widget.dev.js.map +1 -1
- package/dist/agent-widget.esm.js +1 -1
- package/dist/agent-widget.esm.js.map +1 -1
- package/dist/agent-widget.js +1 -1
- package/dist/agent-widget.js.map +1 -1
- package/dist/index.html +150 -0
- package/package.json +1 -1
- package/dist/examples/ocado-cart-network-probe.js +0 -1
- package/dist/examples/sobeys-cart-dispatch-probe.js +0 -1
- package/dist/examples/sobeys-cart-read-test.html +0 -112
- package/dist/examples/sobeys-cart-read-test.js +0 -1
- package/dist/examples/sobeys-cart-subscribe-probe.js +0 -1
- package/dist/examples/sobeys-cart-subscribe-test.html +0 -168
- package/dist/examples/sobeys-cart-subscribe-test.js +0 -1
- package/dist/examples/sobeys-inject-probe-min.js +0 -1
- package/dist/examples/sobeys-inject-probe.js +0 -1
package/dist/index.html
CHANGED
|
@@ -56,6 +56,8 @@
|
|
|
56
56
|
<li><a href="#vanilla-js" class="nav-link">Vanilla JavaScript</a></li>
|
|
57
57
|
<li><a href="#react" class="nav-link">React Integration</a></li>
|
|
58
58
|
<li><a href="#voice-button" class="nav-link">VoiceButton Component</a></li>
|
|
59
|
+
<li><a href="#client-script-tools" class="nav-link">Client-Script Tools</a></li>
|
|
60
|
+
<li><a href="#chain-tools" class="nav-link">Chain Tools</a></li>
|
|
59
61
|
</ul>
|
|
60
62
|
</div>
|
|
61
63
|
|
|
@@ -2642,6 +2644,154 @@ function App() {
|
|
|
2642
2644
|
</table>
|
|
2643
2645
|
</section>
|
|
2644
2646
|
|
|
2647
|
+
<!-- Client-Script Tools -->
|
|
2648
|
+
<section id="client-script-tools" class="doc-section">
|
|
2649
|
+
<h1>Client-Script Tools</h1>
|
|
2650
|
+
<p>Client-script tools let you attach <strong>backend-authored JavaScript</strong> to a client tool (<code>tool_type: 'client'</code>) that runs in the visitor's browser when the LLM calls the tool — <strong>no host-page <code>registerToolHandler()</code> code required</strong>. The script is configured in the dashboard (tool form → "Agent scripts"), stored with the tool, and delivered to the widget automatically at session start. Available since SDK <strong>v2.45.3</strong>.</p>
|
|
2651
|
+
|
|
2652
|
+
<div class="info-box">
|
|
2653
|
+
<strong>When to use which:</strong> use <code>registerToolHandler()</code> when the host page owns the logic and ships its own JS. Use a client-script tool when the agent owner wants browser-side behavior (DOM reads, page API calls, UI nudges) configurable from the dashboard without touching the embedding site.
|
|
2654
|
+
</div>
|
|
2655
|
+
|
|
2656
|
+
<h2>How it works</h2>
|
|
2657
|
+
<ol>
|
|
2658
|
+
<li><strong>Session init</strong> — the backend pushes a <code>partner_bundle</code> message with the reserved partner id <code>__client_tools__</code>, carrying every scripted tool attached to the agent (plus auto-run library scripts). The bundle is pushed on both the voice and text-chat channels, works with or without a widget flavor, and is re-pushed on reconnect (a new bundle replaces the previous one).</li>
|
|
2659
|
+
<li><strong>Compile on load</strong> — each entry compiles into a strict-mode async function immediately when the bundle lands. A syntax error poisons only that entry (logged at load time); the rest of the bundle still works.</li>
|
|
2660
|
+
<li><strong>Invocation</strong> — when the LLM calls the tool, the SDK runs the compiled script and returns its result to the backend.</li>
|
|
2661
|
+
<li><strong>Auto-run</strong> — library scripts flagged <code>auto_run</code> execute exactly once when the bundle arrives (e.g. to set up page listeners).</li>
|
|
2662
|
+
</ol>
|
|
2663
|
+
|
|
2664
|
+
<h2>Authoring styles</h2>
|
|
2665
|
+
<p>Two styles compile — the SDK detects the shape and picks the right wrapping:</p>
|
|
2666
|
+
<pre><code>// 1. Raw statement body (typical in the dashboard UI; `ctx` is in scope)
|
|
2667
|
+
alert("hi");
|
|
2668
|
+
return { ok: true, clicked: true };
|
|
2669
|
+
|
|
2670
|
+
// 2. Function expression
|
|
2671
|
+
async (ctx) => {
|
|
2672
|
+
const res = await ctx.fetch('/api/something');
|
|
2673
|
+
return { ok: true, data: await res.json() };
|
|
2674
|
+
}</code></pre>
|
|
2675
|
+
<p>Both run inside an async function, so <code>await</code> is always allowed. A script that returns nothing resolves to <code>{ ok: true }</code>.</p>
|
|
2676
|
+
|
|
2677
|
+
<h2>The <code>ctx</code> object</h2>
|
|
2678
|
+
<table class="properties-table">
|
|
2679
|
+
<thead>
|
|
2680
|
+
<tr><th>Property</th><th>Description</th></tr>
|
|
2681
|
+
</thead>
|
|
2682
|
+
<tbody>
|
|
2683
|
+
<tr><td><code>ctx.args</code></td><td>Parameters from the LLM tool call.</td></tr>
|
|
2684
|
+
<tr><td><code>ctx.host</code></td><td><code>window.location.hostname</code>.</td></tr>
|
|
2685
|
+
<tr><td><code>ctx.fetch</code></td><td>Bound <code>window.fetch</code>.</td></tr>
|
|
2686
|
+
<tr><td><code>ctx.log(...)</code></td><td><code>console.log</code> prefixed with <code>[adapter:<tool>]</code>.</td></tr>
|
|
2687
|
+
<tr><td><code>ctx.runAdapter(action, args)</code></td><td>Invoke another script in the same bundle (max nesting depth 16).</td></tr>
|
|
2688
|
+
<tr><td><code>ctx.emit(msg)</code></td><td>Send an unsolicited JSON message back to the backend over the active channel.</td></tr>
|
|
2689
|
+
</tbody>
|
|
2690
|
+
</table>
|
|
2691
|
+
<p>Client-tool scripts run with a <strong>generic context</strong> — the ecommerce-only helpers (<code>ctx.platform</code>, <code>ctx.normalizeCart</code>, <code>ctx.refreshUI</code>) are not available; those exist only in ecommerce partner-adapter bundles.</p>
|
|
2692
|
+
|
|
2693
|
+
<h2>Pre / main / post steps</h2>
|
|
2694
|
+
<p>A scripted tool may carry up to three steps, executed in order: <strong>pre → main → post</strong>.</p>
|
|
2695
|
+
<ul>
|
|
2696
|
+
<li><strong>Pre</strong> is best-effort — if it throws, the error is logged and main still runs.</li>
|
|
2697
|
+
<li><strong>Main</strong> produces the tool result. A thrown error becomes an <code>ok: false</code> result.</li>
|
|
2698
|
+
<li><strong>Post</strong> runs only when main succeeded; its <code>ctx.args._mainResult</code> holds main's return value.</li>
|
|
2699
|
+
</ul>
|
|
2700
|
+
|
|
2701
|
+
<h2>Sync vs async results</h2>
|
|
2702
|
+
<p>Configured per tool in the dashboard ("Wait for result"):</p>
|
|
2703
|
+
<table class="properties-table">
|
|
2704
|
+
<thead>
|
|
2705
|
+
<tr><th>Mode</th><th>Behavior</th></tr>
|
|
2706
|
+
</thead>
|
|
2707
|
+
<tbody>
|
|
2708
|
+
<tr><td><strong>Wait ON</strong> (sync)</td><td>The backend awaits the script result up to <code>timeoutMs</code> (1000–15000 ms, default 8000) and feeds it to the LLM as the tool result.</td></tr>
|
|
2709
|
+
<tr><td><strong>Wait OFF</strong> (async)</td><td>The LLM immediately receives the configured pending message; the real result is injected later: <code>SPOKEN_EVENT</code> (spoken immediately), <code>SPOKEN_DEFERRED</code> (next natural turn, default), or <code>SILENT_CONTEXT</code> (silent context update).</td></tr>
|
|
2710
|
+
</tbody>
|
|
2711
|
+
</table>
|
|
2712
|
+
|
|
2713
|
+
<h2>Limits</h2>
|
|
2714
|
+
<table class="properties-table">
|
|
2715
|
+
<thead>
|
|
2716
|
+
<tr><th>Limit</th><th>Value</th></tr>
|
|
2717
|
+
</thead>
|
|
2718
|
+
<tbody>
|
|
2719
|
+
<tr><td>Per-step code size</td><td>32 KB</td></tr>
|
|
2720
|
+
<tr><td>Result size</td><td>50 KB serialized (oversize → <code>ok: false, error: "RESULT_TOO_LARGE"</code>)</td></tr>
|
|
2721
|
+
<tr><td>Sync timeout</td><td>1000–15000 ms (default 8000)</td></tr>
|
|
2722
|
+
<tr><td>Pending message</td><td>500 chars</td></tr>
|
|
2723
|
+
</tbody>
|
|
2724
|
+
</table>
|
|
2725
|
+
|
|
2726
|
+
<h2>Wire protocol (reference)</h2>
|
|
2727
|
+
<p>Script invocations arrive on two wire forms, both handled by the SDK on both channels:</p>
|
|
2728
|
+
<pre><code>// Bundle (session init / reconnect)
|
|
2729
|
+
{ "t": "partner_bundle", "partner_id": "__client_tools__",
|
|
2730
|
+
"adapters": { "my_tool": { "code_js": "...", "pre_code_js": "...", "post_code_js": "..." } },
|
|
2731
|
+
"autoRun": { "my_script": { "code_js": "..." } } }
|
|
2732
|
+
|
|
2733
|
+
// Form A — dedicated envelope (async path / chain steps)
|
|
2734
|
+
→ { "t": "run_partner_script", "requestId": "...", "partnerId": "__client_tools__",
|
|
2735
|
+
"action": "my_tool", "args": { ... } }
|
|
2736
|
+
← { "t": "run_partner_script_result", "requestId": "...", "ok": true, "result": { ... } }
|
|
2737
|
+
|
|
2738
|
+
// Form B — client_tool_call piggyback (backend sync-await path)
|
|
2739
|
+
→ { "t": "client_tool_call", "toolCallId": "...", "toolName": "run_partner_script",
|
|
2740
|
+
"parameters": { "action": "my_tool", "args": { ... }, "partner_id": "__client_tools__" } }
|
|
2741
|
+
← { "t": "client_tool_result", "toolCallId": "...", "result": { ... } }</code></pre>
|
|
2742
|
+
<p>Routing rule: <code>partner_id === '__client_tools__'</code> → the flavor-independent <code>ClientScriptManager</code>; any other partner id → the ecommerce flavor's partner-adapter path. The two bundles coexist.</p>
|
|
2743
|
+
|
|
2744
|
+
<h2>Troubleshooting</h2>
|
|
2745
|
+
<table class="properties-table">
|
|
2746
|
+
<thead>
|
|
2747
|
+
<tr><th>Symptom</th><th>Cause / fix</th></tr>
|
|
2748
|
+
</thead>
|
|
2749
|
+
<tbody>
|
|
2750
|
+
<tr><td><code>compile failed ... SyntaxError</code> at bundle load</td><td>Script doesn't parse. On SDK < 2.45.1, raw statement bodies (e.g. <code>alert("stop");</code>) failed even when valid — check the SDK version banner in the console and hard-refresh if stale.</td></tr>
|
|
2751
|
+
<tr><td><code>NO_HANDLER</code> for <code>run_partner_script</code></td><td>SDK < 2.45.3 didn't route the <code>client_tool_call</code> piggyback form without an ecommerce flavor. Fixed in 2.45.3.</td></tr>
|
|
2752
|
+
<tr><td><code>adapter_not_in_bundle</code></td><td>Tool not attached to the agent, or the bundle hasn't arrived yet. Check the session-start <code>partner_bundle</code> log.</td></tr>
|
|
2753
|
+
<tr><td>Sync result is a timeout</td><td>Script exceeded <code>timeoutMs</code>. Raise it (max 15 s) or switch to async delivery.</td></tr>
|
|
2754
|
+
</tbody>
|
|
2755
|
+
</table>
|
|
2756
|
+
<p>For the full internals reference see <code>CLIENT_SCRIPT_TOOLS_GUIDE.md</code> in the repository root.</p>
|
|
2757
|
+
</section>
|
|
2758
|
+
|
|
2759
|
+
<!-- Chain Tools -->
|
|
2760
|
+
<section id="chain-tools" class="doc-section">
|
|
2761
|
+
<h1>Chain Tools</h1>
|
|
2762
|
+
<p>A chain tool (<code>tool_type: 'chain'</code>) runs a <strong>graph of steps</strong> — server tools, client tools, agent switches, and library scripts — as a single LLM tool call. Chains are edited visually in the dashboard (Chain Editor: nodes are steps, edges are data-flow dependencies) and executed by the backend; the widget participates only when a step targets the browser.</p>
|
|
2763
|
+
|
|
2764
|
+
<h2>Execution model</h2>
|
|
2765
|
+
<ul>
|
|
2766
|
+
<li>Steps with no dependencies run first; steps at the same level run <strong>in parallel</strong>; levels run sequentially.</li>
|
|
2767
|
+
<li>A step's <code>input_from</code> lists the upstream steps whose outputs feed it. Dependent steps receive the original LLM arguments shallow-merged with each upstream result (a non-object result lands under the upstream step's id).</li>
|
|
2768
|
+
<li><strong>Fail-fast</strong>: the first failing step aborts the chain with <code>{ "success": false, "error": ..., "step": ... }</code>.</li>
|
|
2769
|
+
<li>The LLM receives the terminal step's result (multiple terminals merge keyed by step id).</li>
|
|
2770
|
+
<li>Inside a chain, every step is awaited — a referenced client tool's own "wait for result = off" setting is ignored.</li>
|
|
2771
|
+
</ul>
|
|
2772
|
+
|
|
2773
|
+
<h2>Step types and the widget</h2>
|
|
2774
|
+
<table class="properties-table">
|
|
2775
|
+
<thead>
|
|
2776
|
+
<tr><th>Step type</th><th>Runs where</th><th>Widget involvement</th></tr>
|
|
2777
|
+
</thead>
|
|
2778
|
+
<tbody>
|
|
2779
|
+
<tr><td><code>server_tool</code></td><td>Backend (webhook)</td><td>None.</td></tr>
|
|
2780
|
+
<tr><td><code>switch_agent</code></td><td>Backend</td><td>None.</td></tr>
|
|
2781
|
+
<tr><td><code>client_tool</code> (plain)</td><td>Browser</td><td>Arrives as a normal <code>client_tool_call</code> → your <code>registerToolHandler()</code> handler.</td></tr>
|
|
2782
|
+
<tr><td><code>client_tool</code> (scripted)</td><td>Browser</td><td>Dispatched as a <code>__client_tools__</code> bundle action keyed by <strong>tool name</strong>.</td></tr>
|
|
2783
|
+
<tr><td><code>client_script</code> (library)</td><td>Browser</td><td>Dispatched as a <code>__client_tools__</code> bundle action keyed by <code>script:{scriptId}</code>.</td></tr>
|
|
2784
|
+
</tbody>
|
|
2785
|
+
</table>
|
|
2786
|
+
<p>Scripts and scripted client tools referenced by a chain are resolved into the session bundle automatically — they do <strong>not</strong> need to be attached to the agent.</p>
|
|
2787
|
+
|
|
2788
|
+
<h2>Constraints</h2>
|
|
2789
|
+
<ul>
|
|
2790
|
+
<li>1–30 steps per chain; no cycles; chains cannot reference other chain tools (no recursion).</li>
|
|
2791
|
+
<li>Deleting a tool or library script that a chain references is blocked in the dashboard (the delete dialog lists the referencing chains).</li>
|
|
2792
|
+
</ul>
|
|
2793
|
+
</section>
|
|
2794
|
+
|
|
2645
2795
|
<!-- VoiceSDK API -->
|
|
2646
2796
|
<section id="voicesdk" class="doc-section">
|
|
2647
2797
|
<h1>VoiceSDK Class</h1>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ttp-agent-sdk",
|
|
3
|
-
"version": "2.45.
|
|
3
|
+
"version": "2.45.6",
|
|
4
4
|
"description": "Comprehensive Voice Agent SDK with Customizable Widget - Real-time audio, WebSocket communication, React components, and extensive customization options",
|
|
5
5
|
"main": "dist/agent-widget.js",
|
|
6
6
|
"module": "dist/agent-widget.esm.js",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
!function(){"use strict";if(window.__ttpCartProbeInstalled)return console.warn("[TTP-CART-PROBE] already installed — run __ttpCartProbeReport() or __ttpCartProbeUninstall()"),window.__ttpCartProbeHits;const t=[];function e(t){return"string"==typeof t&&/\/api\/cart\//i.test(t)}function o(e,o,n){const r={at:(new Date).toISOString(),kind:e,url:String(o),extra:n||null};t.push(r),console.log("[TTP-CART-PROBE]",e,r.url,n||"")}window.__ttpCartProbeHits=t;const n=window.fetch;window.__ttpCartProbeNativeFetch=n,window.fetch=function(t,r){const s="string"==typeof t?t:t&&t.url||"",i=n.apply(this,arguments);return e(s)&&(o("fetch-start",s,{method:r&&r.method||"GET"}),i.then(function(t){o("fetch-end",s,{status:t.status,ok:t.ok})}).catch(function(t){o("fetch-error",s,{message:String(t&&t.message||t)})})),i};const r=XMLHttpRequest.prototype.open,s=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(t,e){return this.__ttpProbeMethod=t,this.__ttpProbeUrl="string"==typeof e?e:String(e||""),r.apply(this,arguments)},XMLHttpRequest.prototype.send=function(t){const n=this.__ttpProbeUrl||"",r=this.__ttpProbeMethod||"GET";if(e(n)){const t=this;o("xhr-start",n,{method:r}),t.addEventListener("loadend",function(){o("xhr-end",n,{method:r,status:t.status})})}return s.apply(this,arguments)},window.__ttpCartProbeReport=function(){const e={hostname:location.hostname,fetchPatched:window.fetch!==n,hitCount:t.length,hits:t.slice(),hint:0===t.length?"No /api/cart/ via fetch or XHR. Open Network tab, add to cart, note the real request URL.":"Hooks see cart traffic — cart.subscribe should be able to listen here."};return console.table(t),console.log("[TTP-CART-PROBE] report",e),e},window.__ttpCartProbeUninstall=function(){window.__ttpCartProbeNativeFetch&&(window.fetch=window.__ttpCartProbeNativeFetch),XMLHttpRequest.prototype.open=r,XMLHttpRequest.prototype.send=s,delete window.__ttpCartProbeInstalled,delete window.__ttpCartProbeHits,delete window.__ttpCartProbeReport,delete window.__ttpCartProbeUninstall,delete window.__ttpCartProbeNativeFetch,console.log("[TTP-CART-PROBE] uninstalled")},window.__ttpCartProbeInstalled=!0,console.log("[TTP-CART-PROBE] installed on",location.hostname,"— manually add to cart, then __ttpCartProbeReport()")}();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
!function(){"use strict";if(window.__ttpSobeysDispatchProbeInstalled)return console.warn("[SOBEYS-PROBE] already installed"),window.__ttpSobeysDispatchProbeReport();const t=[];function e(){if(window.__reduxStore&&"function"==typeof window.__reduxStore.dispatch)return window.__reduxStore;function t(t,e){const o=[t];for(;o.length&&e-- >0;){const t=o.shift();if(!t)continue;const e=t.memoizedProps?.store||t.memoizedState?.store||t.pendingProps?.store||t.stateNode?.store;if(e&&"function"==typeof e.dispatch)return e;t.child&&o.push(t.child),t.sibling&&o.push(t.sibling),t.return&&o.push(t.return)}return null}function e(e){if(!e)return null;const o=Object.keys(e).filter(t=>t.startsWith("__reactFiber")||t.startsWith("__reactContainer")||t.startsWith("__reactInternalInstance"));for(const n of o){const o=t(e[n]?.current||e[n],800);if(o)return o}return null}const o=[document.getElementById("root"),document.getElementById("app"),document.getElementById("__next"),document.body,document.body?.firstElementChild].filter(Boolean);for(const t of o){const o=e(t);if(o)return o}for(const t of document.querySelectorAll("body *")){const o=e(t);if(o)return o}return null}function o(t){try{const e=t?.getState?.()||{},o=e.shoppingListSlice?.shoppingListProducts?.products;if(Array.isArray(o))return{source:"redux",products:o}}catch(t){}return n(t)}function n(t){try{const e=t?.getState?.()||{};if(e.manageListSlice)return{source:"redux",data:e.manageListSlice}}catch(t){}try{const t=sessionStorage.getItem("persist:root");if(!t)return{source:"none",data:null};const e=JSON.parse(t);return{source:"persist:root",data:e.manageListSlice?JSON.parse(e.manageListSlice):null,persistKeys:Object.keys(e)}}catch(t){return{source:"error",error:String(t.message||t)}}}function r(t,e){if(!t||!e)return 0;const o=t.products||t.items||t.listItems||t.data;if(Array.isArray(o)){const t=o.find(t=>t&&(t.item_id===e||t.objectID===e||t.objectId===e||t.id===e||t.productId===e));return Number(t?.quantity??t?.qty??t?.count??0)||0}if(o&&"object"==typeof o){const t=o[e];return Number(t?.quantity??t?.qty??t??0)||0}return 0}window.__ttpSobeysDispatchProbeHits=t;const i=e();if(window.__ttpSobeysReduxStore=i,i){const e=i.dispatch.bind(i);i.dispatch=function(o){try{const e=String(("string"==typeof o?o:o?.type)||"");if(/list|cart|add|item|manage|persist|favour|favorite|product|algolia/i.test(e)){const n={at:(new Date).toISOString(),type:e,payload:o?.payload,meta:o?.meta};t.push(n),console.log("[SOBEYS-PROBE] dispatch",n)}}catch(t){console.warn("[SOBEYS-PROBE] log error",t)}return e.apply(i,arguments)}}else console.warn("[SOBEYS-PROBE] Redux store not found yet — will retry on report/click");window.__ttpSobeysDispatchProbeReport=function(){const r=window.__ttpSobeysReduxStore||e();window.__ttpSobeysReduxStore=r;const i=o(r),s=Array.from(document.querySelectorAll('[id^="ObjectID-"]')).slice(0,8).map(t=>({tileId:t.id,addBtn:!!t.querySelector('#add_to_cart_product button, button[aria-label*="Add to list" i]')})),c={hostname:location.hostname,storeFound:!!r,stateKeys:r?Object.keys(r.getState()||{}):[],shoppingList:i,manageList:n(r),hitCount:t.length,hits:t.slice(),objectTiles:s,hint:0===t.length?"Click Add to list on a product tile, then run report again.":"Use hits[].type + hits[].payload to implement cart.add dispatch."};return console.table(t),console.log("[SOBEYS-PROBE] report",c),c},window.__ttpSobeysDispatchAdd=function(t,n){const i=window.__ttpSobeysReduxStore||e();if(!i)return{ok:!1,reason:"redux_store_not_found"};const s=Number(n);if(!Number.isFinite(s)||s<0)return{ok:!1,reason:"invalid_qty",quantity:n};const c=r(o(i),t);i.dispatch({type:"shoppingListSlice/setItemListData",payload:{items:[{item_id:String(t),product_type:"Product",quantity:s}]}});const a=r(o(i),t);return{ok:a===s||0===s&&0===a,objectId:t,previousQty:c,newQty:a,action:0===s?"remove":0===c?"add":"update"}},window.__ttpSobeysTryAddByObjectId=function(t,e){e=null==e?1:e;const n=document.getElementById("ObjectID-"+t+" OfferType-"+e);if(!n)return console.warn("[SOBEYS-PROBE] tile not found for",t,e),{ok:!1,reason:"tile_not_found"};const i=n.querySelector("#add_to_cart_product button")||n.querySelector('button[aria-label*="Add to list" i]')||Array.from(n.querySelectorAll("button")).find(t=>/add to list/i.test(t.textContent||t.getAttribute("aria-label")||""));if(!i)return{ok:!1,reason:"add_button_not_found",tileId:n.id};const s=r(o(window.__ttpSobeysReduxStore).data,t);return i.click(),{ok:!0,clicked:!0,tileId:n.id,prevQty:s,note:"Wait ~1s then __ttpSobeysDispatchProbeReport()"}},window.__ttpSobeysDispatchProbeUninstall=function(){console.log("[SOBEYS-PROBE] uninstall not fully implemented — refresh page to reset dispatch wrapper")},window.__ttpSobeysDispatchProbeInstalled=!0,console.log("[SOBEYS-PROBE] installed — click Add to list or __ttpSobeysTryAddByObjectId(objectId, offerType)"),window.__ttpSobeysDispatchProbeReport()}();
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<title>Sobeys cart.read adapter test (mock Redux)</title>
|
|
6
|
-
<style>
|
|
7
|
-
body { font-family: system-ui, sans-serif; margin: 24px; max-width: 960px; }
|
|
8
|
-
pre { background: #111; color: #0f0; padding: 16px; overflow: auto; font-size: 13px; }
|
|
9
|
-
.pass { color: #0a0; font-weight: bold; }
|
|
10
|
-
.fail { color: #c00; font-weight: bold; }
|
|
11
|
-
button { padding: 8px 16px; margin-right: 8px; cursor: pointer; }
|
|
12
|
-
</style>
|
|
13
|
-
</head>
|
|
14
|
-
<body>
|
|
15
|
-
<h1>Sobeys <code>cart.read</code> adapter test</h1>
|
|
16
|
-
<p>Mocks <code>shoppingListSlice</code> on <code>window.__reduxStore</code> (same path the live adapter uses).</p>
|
|
17
|
-
<button id="run">Run test</button>
|
|
18
|
-
<button id="seed">Seed cart item</button>
|
|
19
|
-
<button id="clear">Clear cart</button>
|
|
20
|
-
<div id="status"></div>
|
|
21
|
-
<pre id="out">Click Run test…</pre>
|
|
22
|
-
|
|
23
|
-
<script>
|
|
24
|
-
window.__TTP_SOBEYS_TEST__ = true;
|
|
25
|
-
// Mock Sobeys Redux store
|
|
26
|
-
(function initMockStore() {
|
|
27
|
-
const state = {
|
|
28
|
-
shoppingListSlice: {
|
|
29
|
-
listId: 'mock-list-42',
|
|
30
|
-
listName: 'My List',
|
|
31
|
-
shoppingListProducts: {
|
|
32
|
-
products: [
|
|
33
|
-
{
|
|
34
|
-
item_id: '577689_EA_0320',
|
|
35
|
-
product_type: 'Product',
|
|
36
|
-
quantity: 2,
|
|
37
|
-
name: 'Neilson 2% Milk',
|
|
38
|
-
brand: 'Neilson',
|
|
39
|
-
price: 5.49,
|
|
40
|
-
unit: '2 L',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
item_id: '123456_KG_0320',
|
|
44
|
-
product_type: 'Product',
|
|
45
|
-
quantity: 1,
|
|
46
|
-
name: 'Bananas',
|
|
47
|
-
price: 1.99,
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
},
|
|
51
|
-
taxSummary: { subtotal: 12.97, grandTotal: 14.65 },
|
|
52
|
-
},
|
|
53
|
-
productsSlice: { products: [] },
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
window.__reduxStore = {
|
|
57
|
-
getState: () => state,
|
|
58
|
-
dispatch: (action) => {
|
|
59
|
-
if (action.type === 'shoppingListSlice/setItemListData' && action.payload && action.payload.items) {
|
|
60
|
-
const map = {};
|
|
61
|
-
state.shoppingListSlice.shoppingListProducts.products.forEach((p) => {
|
|
62
|
-
map[p.item_id] = { ...p };
|
|
63
|
-
});
|
|
64
|
-
action.payload.items.forEach((item) => {
|
|
65
|
-
if (!item.item_id) return;
|
|
66
|
-
const qty = Number(item.quantity) || 0;
|
|
67
|
-
if (qty <= 0) delete map[item.item_id];
|
|
68
|
-
else map[item.item_id] = { ...map[item.item_id], ...item, quantity: qty };
|
|
69
|
-
});
|
|
70
|
-
state.shoppingListSlice.shoppingListProducts.products = Object.values(map);
|
|
71
|
-
}
|
|
72
|
-
return action;
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
})();
|
|
76
|
-
|
|
77
|
-
</script>
|
|
78
|
-
<script src="sobeys-cart-read-test.js"></script>
|
|
79
|
-
<script>
|
|
80
|
-
const out = document.getElementById('out');
|
|
81
|
-
const status = document.getElementById('status');
|
|
82
|
-
|
|
83
|
-
async function runTest() {
|
|
84
|
-
status.textContent = 'Running…';
|
|
85
|
-
const report = await window.__ttpSobeysCartReadTestReport();
|
|
86
|
-
out.textContent = JSON.stringify(report, null, 2);
|
|
87
|
-
status.innerHTML = report.pass
|
|
88
|
-
? '<span class="pass">PASS</span> — cart.read returned items'
|
|
89
|
-
: '<span class="fail">FAIL</span> — see JSON below';
|
|
90
|
-
return report;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
document.getElementById('run').onclick = runTest;
|
|
94
|
-
document.getElementById('seed').onclick = async () => {
|
|
95
|
-
window.__reduxStore.dispatch({
|
|
96
|
-
type: 'shoppingListSlice/setItemListData',
|
|
97
|
-
payload: { items: [{ item_id: '247400_EA_0320', product_type: 'Product', quantity: 1, name: 'Bread' }] },
|
|
98
|
-
});
|
|
99
|
-
await runTest();
|
|
100
|
-
};
|
|
101
|
-
document.getElementById('clear').onclick = async () => {
|
|
102
|
-
window.__reduxStore.dispatch({
|
|
103
|
-
type: 'shoppingListSlice/setItemListData',
|
|
104
|
-
payload: { items: [{ item_id: '577689_EA_0320', quantity: 0 }, { item_id: '123456_KG_0320', quantity: 0 }] },
|
|
105
|
-
});
|
|
106
|
-
await runTest();
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
runTest();
|
|
110
|
-
</script>
|
|
111
|
-
</body>
|
|
112
|
-
</html>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
!function(){"use strict";async function t(t){if(!window.__TTP_SOBEYS_TEST__&&!/(^|\.)sobeys\.com$/.test(location.hostname))return{ok:!1,success:!1,reason:"cross_origin_widget",host:location.hostname};function e(t){try{return t.getState().shoppingListSlice||{}}catch(t){return{}}}function n(t,e,n){const r={productName:"",brand:"",price:0,imageUrl:"",unit:""};if(!e||!n)return r;r.productName=String(e.name||e.product_name||e.productName||e.title||"").trim(),r.brand=String(e.brand||"").trim(),r.unit=String(e.unit||e.uom||e.packSize||"").trim(),r.imageUrl=String(e.imageUrl||e.image_url||e.image||"").trim();const i=Number(null!=e.price?e.price:e.unit_price);if(Number.isFinite(i)&&i>0&&(r.price=i),r.productName)return r;try{const e=t.getState(),i=[e.cartProductSlice,e.productsSlice,e.getShoppingListDataSlice,e.getProductDetailsDataSlice];for(let t=0;t<i.length;t++){const e=i[t];if(!e)continue;const o=Array.isArray(e)?e:e.products||e.items||e.data;if(Array.isArray(o))for(let t=0;t<o.length;t++){const e=o[t];if(!e)continue;if(String(e.item_id||e.objectID||e.id||e.productId||"")!==n)continue;r.productName=String(e.name||e.product_name||e.productName||"").trim();const i=Number(null!=e.price?e.price:e.unit_price);if(!r.price&&Number.isFinite(i)&&i>0&&(r.price=i),r.productName)return r}}}catch(t){}try{const t=document.querySelectorAll('[id^="ObjectID-'+n+'"]');for(let e=0;e<t.length;e++){const n=t[e].querySelector("a[title], a[aria-label]");if(!n)continue;const i=String(n.getAttribute("title")||n.getAttribute("aria-label")||"").trim();if(0===i.indexOf("Click here to go to")?r.productName=i.replace(/^Click here to go to\s+/i,"").replace(/\s+product detail page$/i,""):i&&(r.productName=i),r.productName)break}}catch(t){}return r}function r(t,e){return"KG"===(String(t||"").split("_")[1]||"")||e&&"weight"===String(e.product_type||"").toLowerCase()?"weight":"quantity"}const i=function(){if(window.__reduxStore&&"function"==typeof window.__reduxStore.dispatch)return window.__reduxStore;function t(t,e){const n=[t];for(;n.length&&e-- >0;){const t=n.shift();if(!t)continue;const e=t.memoizedProps&&t.memoizedProps.store||t.memoizedState&&t.memoizedState.store||t.pendingProps&&t.pendingProps.store||t.stateNode&&t.stateNode.store;if(e&&"function"==typeof e.dispatch)return e;t.child&&n.push(t.child),t.sibling&&n.push(t.sibling),t.return&&n.push(t.return)}return null}function e(e){if(!e)return null;const n=Object.keys(e).filter(function(t){return 0===t.indexOf("__reactFiber")||0===t.indexOf("__reactContainer")||0===t.indexOf("__reactInternalInstance")});for(let r=0;r<n.length;r++){const i=t(e[n[r]]&&(e[n[r]].current||e[n[r]])||null,800);if(i)return i}return null}const n=[document.getElementById("root"),document.getElementById("app"),document.body,document.body&&document.body.firstElementChild].filter(Boolean);for(let t=0;t<n.length;t++){const r=e(n[t]);if(r)return r}const r=document.querySelectorAll("body *");for(let t=0;t<r.length;t++){const n=e(r[t]);if(n)return n}return null}();if(!i)return{ok:!1,success:!1,reason:"redux_store_not_found"};const o=e(i),c=function(t){const n=e(t),r=n.shoppingListProducts&&n.shoppingListProducts.products;return Array.isArray(r)?r:[]}(i),u=o.taxSummary||{};if(0===c.length)return{ok:!0,success:!0,items:[],itemCount:0,total:Number(null!=u.grandTotal?u.grandTotal:u.subtotal)||0,currency:"CAD",checkoutUrl:"",listId:o.listId||"",cartId:o.listId||null};const s=[];let a=0,d=0,l=0;for(let t=0;t<c.length;t++){const e=c[t];if(!e||!e.item_id)continue;const o=String(e.item_id),u=Number(e.quantity)||0;if(u<=0)continue;const m=n(i,e,o);m.productName||l++;const p=m.price||0,f=p>0?+(p*u).toFixed(2):0;f>0&&(d+=f),a+=u,s.push({productId:o,productName:m.productName,brand:m.brand,unit:m.unit,imageUrl:m.imageUrl,quantity:u,price:p,subtotal:f,sellBy:r(o,e),product_type:e.product_type||"Product"})}const m=Number(null!=u.grandTotal?u.grandTotal:u.subtotal),p={ok:!0,success:!0,items:s,itemCount:a,total:Number.isFinite(m)&&m>0?m:d>0?+d.toFixed(2):0,currency:"CAD",checkoutUrl:"",listId:o.listId||"",cartId:o.listId||null,listName:o.listName||""};return l>0&&l===s.length?p.degraded="no_product_names":l>0&&(p.degraded="partial_product_names"),p}window.__ttpSobeysCartRead=t,window.__ttpSobeysCartReadTestReport=async function(){window.fetch.bind(window),console.log.bind(console,"[adapter:cart.read]");const e=await t();let n=null;if(0===(e.items||[]).length){const t=function(){const t=Array.from(document.querySelectorAll('[id^="ObjectID-"]')).find(function(t){return-1===t.id.indexOf("undefined")&&t.querySelector('button[aria-label="Add to list"]')});if(!t)return null;const e=t.id.match(/^ObjectID-(.+?) OfferType-/);return e?e[1]:null}()||"247400_EA_0320",e=function(){const t=document.getElementById("root")||document.body.firstElementChild;if(!t)return null;const e=Object.keys(t).find(function(t){return 0===t.indexOf("__reactFiber")||0===t.indexOf("__reactContainer")});return e?function(t,e){const n=[t];for(;n.length&&e-- >0;){const t=n.shift();if(!t)continue;const e=t.memoizedProps&&t.memoizedProps.store||t.memoizedState&&t.memoizedState.store||t.pendingProps&&t.pendingProps.store;if(e&&"function"==typeof e.dispatch)return e;t.child&&n.push(t.child),t.sibling&&n.push(t.sibling),t.return&&n.push(t.return)}return null}(t[e].current||t[e],800):null}();e&&(e.dispatch({type:"shoppingListSlice/setItemListData",payload:{items:[{item_id:t,product_type:"Product",quantity:1}]}}),await new Promise(function(t){setTimeout(t,500)}),n={objectId:t,via:"redux_dispatch"})}const r=await t(),i={hostname:location.hostname,emptyCartRead:e,seeded:n,cartRead:r,pass:!!(r.ok&&r.success&&(r.itemCount||0)>0&&(r.items||[]).length>0)};return console.log("[SOBEYS-CART-READ-TEST]",i),i},window.__ttpSobeysCartReadTestReport()}();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
!function(){"use strict";async function t(){const t={hostname:location.hostname,hostOk:/(^|\.)sobeys\.com$/.test(location.hostname),subscribeInstalled:!!window.__ttpSobeysCartSubscribeInstalled,scheduleRefresh:"function"==typeof window.__ttpSobeysCartScheduleRefresh,dispatchPatched:!1,persistPoll:!!window.__ttpSobeysPersistPollId,suppressedCount:window.__ttpSobeysSuppressedIds?window.__ttpSobeysSuppressedIds.size:0,prevMapSize:window.__ttpSobeysPrevItemMap?window.__ttpSobeysPrevItemMap.size:null,ecommerce:void 0!==window.__TTP_ECOMMERCE__,bundleActions:window.__TTP_ECOMMERCE__&&window.__TTP_ECOMMERCE__.runner?[...window.__TTP_ECOMMERCE__.runner.adapters?.keys?.()||[]]:[],persistFingerprint:"",reduxLines:[]},e=function(){if(window.__reduxStore)return window.__reduxStore;const t=document.getElementById("root")||document.body.firstElementChild;if(!t)return null;const e=Object.keys(t).find(function(t){return 0===t.indexOf("__reactFiber")||0===t.indexOf("__reactContainer")});if(!e)return null;const n=[t[e].current||t[e]];let i=400;for(;n.length&&i-- >0;){const t=n.shift();if(!t)continue;const e=t.memoizedProps&&t.memoizedProps.store||t.memoizedState&&t.memoizedState.store;if(e&&"function"==typeof e.dispatch)return e;t.child&&n.push(t.child),t.sibling&&n.push(t.sibling)}return null}();if(e){t.dispatchPatched=!!e.__ttpSobeysDispatchPatched;try{const n=e.getState().shoppingListSlice?.shoppingListProducts?.products||[];t.reduxLines=n.map(function(t){return{item_id:t.item_id,quantity:t.quantity}})}catch(e){t.reduxError=String(e.message||e)}}else t.reduxStore="not_found";try{const e=sessionStorage.getItem("persist:root");if(e){const n=JSON.parse(e),i=JSON.parse(n.shoppingListSlice||"{}"),s=i.shoppingListProducts?.products||[];t.persistFingerprint=s.map(function(t){return t.item_id+":"+t.quantity}).join("|")}}catch(e){t.persistError=String(e.message||e)}return window.__TTP_ECOMMERCE__&&window.__TTP_ECOMMERCE__.runAdapter&&(t.manualSubscribe=await window.__TTP_ECOMMERCE__.runAdapter("cart.subscribe",{}),t.cartRead=await window.__TTP_ECOMMERCE__.runAdapter("cart.read",{})),console.log("[SOBEYS-CART-SUBSCRIBE-PROBE]",t),t}window.__ttpSobeysCartSubscribeProbe=t,t()}();
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<title>Sobeys cart.subscribe test</title>
|
|
6
|
-
<style>
|
|
7
|
-
body { font-family: system-ui, sans-serif; margin: 24px; max-width: 960px; }
|
|
8
|
-
pre { background: #111; color: #0f0; padding: 16px; overflow: auto; font-size: 12px; }
|
|
9
|
-
button { margin: 4px 8px 4px 0; padding: 8px 12px; cursor: pointer; }
|
|
10
|
-
.pass { color: #0a0; font-weight: bold; }
|
|
11
|
-
.fail { color: #c00; font-weight: bold; }
|
|
12
|
-
</style>
|
|
13
|
-
</head>
|
|
14
|
-
<body>
|
|
15
|
-
<h1>Sobeys <code>cart.subscribe</code> + <code>cart.add</code> integration test</h1>
|
|
16
|
-
<p>Mocks Redux store, cart.read adapter, and <code>netcost:partner-action:cart_mutation</code>.</p>
|
|
17
|
-
<button id="runAll">Run all tests</button>
|
|
18
|
-
<button id="manualAdd">Simulate manual site add</button>
|
|
19
|
-
<button id="voiceAdd">Simulate voice cart.add (with suppression)</button>
|
|
20
|
-
<div id="status"></div>
|
|
21
|
-
<pre id="out">Click Run all tests…</pre>
|
|
22
|
-
|
|
23
|
-
<script>
|
|
24
|
-
window.__TTP_SOBEYS_TEST__ = true;
|
|
25
|
-
|
|
26
|
-
const state = {
|
|
27
|
-
shoppingListSlice: {
|
|
28
|
-
listId: 'mock-list',
|
|
29
|
-
shoppingListProducts: { products: [] },
|
|
30
|
-
taxSummary: { grandTotal: 0 },
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
window.__reduxStore = {
|
|
35
|
-
getState: () => state,
|
|
36
|
-
_subs: [],
|
|
37
|
-
subscribe(fn) {
|
|
38
|
-
this._subs.push(fn);
|
|
39
|
-
return () => { this._subs = this._subs.filter((f) => f !== fn); };
|
|
40
|
-
},
|
|
41
|
-
dispatch(action) {
|
|
42
|
-
if (action.type === 'shoppingListSlice/setItemListData' && action.payload && action.payload.items) {
|
|
43
|
-
const map = {};
|
|
44
|
-
state.shoppingListSlice.shoppingListProducts.products.forEach((p) => { map[p.item_id] = { ...p }; });
|
|
45
|
-
action.payload.items.forEach((item) => {
|
|
46
|
-
const qty = Number(item.quantity) || 0;
|
|
47
|
-
if (qty <= 0) delete map[item.item_id];
|
|
48
|
-
else map[item.item_id] = { ...map[item.item_id], ...item, quantity: qty };
|
|
49
|
-
});
|
|
50
|
-
state.shoppingListSlice.shoppingListProducts.products = Object.values(map);
|
|
51
|
-
}
|
|
52
|
-
this._subs.forEach((fn) => { try { fn(); } catch (e) {} });
|
|
53
|
-
return action;
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
async function mockCartRead() {
|
|
58
|
-
const products = state.shoppingListSlice.shoppingListProducts.products || [];
|
|
59
|
-
const items = products.filter((p) => (Number(p.quantity) || 0) > 0).map((p) => ({
|
|
60
|
-
productId: p.item_id,
|
|
61
|
-
productName: p.name || '',
|
|
62
|
-
quantity: Number(p.quantity) || 0,
|
|
63
|
-
price: Number(p.price) || 0,
|
|
64
|
-
subtotal: 0,
|
|
65
|
-
sellBy: String(p.item_id).indexOf('_KG_') >= 0 ? 'weight' : 'quantity',
|
|
66
|
-
}));
|
|
67
|
-
const itemCount = items.reduce((n, i) => n + i.quantity, 0);
|
|
68
|
-
return { ok: true, success: true, items, itemCount, total: 0, currency: 'CAD' };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const emits = [];
|
|
72
|
-
|
|
73
|
-
async function installSubscribe() {
|
|
74
|
-
// Reset for repeatable test runs
|
|
75
|
-
if (typeof window.__ttpSobeysStoreUnsub === 'function') {
|
|
76
|
-
window.__ttpSobeysStoreUnsub();
|
|
77
|
-
window.__ttpSobeysStoreUnsub = null;
|
|
78
|
-
}
|
|
79
|
-
window.__ttpSobeysCartSubscribeInstalled = false;
|
|
80
|
-
window.__ttpSobeysPrevItemMap = new Map();
|
|
81
|
-
window.__ttpSobeysSuppressedIds = new Map();
|
|
82
|
-
|
|
83
|
-
if (!window.__ttpSobeysCartSubscribeInstall) {
|
|
84
|
-
const script = document.createElement('script');
|
|
85
|
-
script.src = 'sobeys-cart-subscribe-test.js';
|
|
86
|
-
document.head.appendChild(script);
|
|
87
|
-
await new Promise((r) => { script.onload = r; });
|
|
88
|
-
}
|
|
89
|
-
const ctx = {
|
|
90
|
-
args: {},
|
|
91
|
-
log: (...a) => console.log('[adapter:cart.subscribe]', ...a),
|
|
92
|
-
emit: (payload) => { emits.push(payload); },
|
|
93
|
-
runAdapter: async (action) => {
|
|
94
|
-
if (action === 'cart.read') return mockCartRead();
|
|
95
|
-
return { ok: false, reason: 'unknown' };
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
await window.__ttpSobeysCartSubscribeInstall(ctx);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function firePartnerMutation(args) {
|
|
102
|
-
window.dispatchEvent(new CustomEvent('netcost:partner-action:cart_mutation', {
|
|
103
|
-
detail: { action: 'cart.add', args },
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function voiceCartAdd(sku, qty) {
|
|
108
|
-
firePartnerMutation({ sku, qty, name: 'voice item' });
|
|
109
|
-
window.__reduxStore.dispatch({
|
|
110
|
-
type: 'shoppingListSlice/setItemListData',
|
|
111
|
-
payload: { items: [{ item_id: sku, product_type: 'Product', quantity: qty, name: 'voice item' }] },
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function manualSiteAdd(sku, name) {
|
|
116
|
-
window.__reduxStore.dispatch({
|
|
117
|
-
type: 'shoppingListSlice/setItemListData',
|
|
118
|
-
payload: { items: [{ item_id: sku, product_type: 'Product', quantity: 1, name }] },
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function runAllTests() {
|
|
123
|
-
emits.length = 0;
|
|
124
|
-
state.shoppingListSlice.shoppingListProducts.products = [];
|
|
125
|
-
await installSubscribe();
|
|
126
|
-
await new Promise((r) => setTimeout(r, 700));
|
|
127
|
-
|
|
128
|
-
const initialCount = emits.filter((e) => e.t === 'cart_change_event' && !e.change).length;
|
|
129
|
-
emits.length = 0;
|
|
130
|
-
|
|
131
|
-
voiceCartAdd('577689_EA_0320', 1);
|
|
132
|
-
await new Promise((r) => setTimeout(r, 700));
|
|
133
|
-
const voiceEmits = emits.filter((e) => e.change).length;
|
|
134
|
-
|
|
135
|
-
manualSiteAdd('247400_EA_0320', 'Bread');
|
|
136
|
-
await new Promise((r) => setTimeout(r, 700));
|
|
137
|
-
const manualEmits = emits.filter((e) => e.change && e.change.added.some((a) => a.id === '247400_EA_0320')).length;
|
|
138
|
-
|
|
139
|
-
const report = {
|
|
140
|
-
initialSnapshot: initialCount === 1,
|
|
141
|
-
voiceAddSuppressed: voiceEmits === 0,
|
|
142
|
-
voiceAddCacheOnly: emits.some(function (e) {
|
|
143
|
-
return e.t === 'cart_change_event' && !e.change &&
|
|
144
|
-
(e.cart && e.cart.items || []).some(function (i) { return i.productId === '577689_EA_0320'; });
|
|
145
|
-
}),
|
|
146
|
-
manualAddEmitted: manualEmits >= 1,
|
|
147
|
-
pass: initialCount === 1 && voiceEmits === 0 && manualEmits >= 1,
|
|
148
|
-
emits,
|
|
149
|
-
};
|
|
150
|
-
document.getElementById('out').textContent = JSON.stringify(report, null, 2);
|
|
151
|
-
document.getElementById('status').innerHTML = report.pass
|
|
152
|
-
? '<span class="pass">PASS</span>'
|
|
153
|
-
: '<span class="fail">FAIL</span>';
|
|
154
|
-
return report;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
document.getElementById('runAll').onclick = runAllTests;
|
|
158
|
-
document.getElementById('manualAdd').onclick = async () => {
|
|
159
|
-
await installSubscribe();
|
|
160
|
-
manualSiteAdd('999999_EA_0320', 'Manual Product');
|
|
161
|
-
};
|
|
162
|
-
document.getElementById('voiceAdd').onclick = async () => {
|
|
163
|
-
await installSubscribe();
|
|
164
|
-
voiceCartAdd('888888_EA_0320', 1);
|
|
165
|
-
};
|
|
166
|
-
</script>
|
|
167
|
-
</body>
|
|
168
|
-
</html>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
window.__ttpSobeysCartSubscribeInstall=async t=>{if(!window.__TTP_SOBEYS_TEST__&&!/(^|\.)sobeys\.com$/.test(location.hostname))return{ok:!1,reason:"cross_origin_widget",host:location.hostname};if("function"!=typeof t.emit)return t.log("ctx.emit not available — falling back to on-demand get_cart"),{ok:!0,subscribed:!1,reason:"emit_unavailable"};window.__ttpSobeysSuppressedIds||(window.__ttpSobeysSuppressedIds=new Map);const e=window.__ttpSobeysSuppressedIds;function n(t){if(!t)return!1;const n=String(t).trim(),o=e.get(n);return!(!o||Date.now()>o&&(e.delete(n),1))}function o(){if(window.__reduxStore&&"function"==typeof window.__reduxStore.dispatch)return window.__reduxStore;function t(t,e){const n=[t];for(;n.length&&e-- >0;){const t=n.shift();if(!t)continue;const e=t.memoizedProps&&t.memoizedProps.store||t.memoizedState&&t.memoizedState.store||t.pendingProps&&t.pendingProps.store||t.stateNode&&t.stateNode.store;if(e&&"function"==typeof e.dispatch)return e;t.child&&n.push(t.child),t.sibling&&n.push(t.sibling),t.return&&n.push(t.return)}return null}function e(e){if(!e)return null;const n=Object.keys(e).filter(function(t){return 0===t.indexOf("__reactFiber")||0===t.indexOf("__reactContainer")||0===t.indexOf("__reactInternalInstance")});for(let o=0;o<n.length;o++){const r=n[o],i=t(e[r]&&(e[r].current||e[r])||null,800);if(i)return i}return null}const n=[document.getElementById("root"),document.getElementById("app"),document.body,document.body&&document.body.firstElementChild].filter(Boolean);for(let t=0;t<n.length;t++){const o=e(n[t]);if(o)return o}const o=document.querySelectorAll("body *");for(let t=0;t<Math.min(o.length,200);t++){const n=e(o[t]);if(n)return n}return null}function r(t){const e=new Map,n=t&&t.shoppingListSlice&&t.shoppingListSlice.shoppingListProducts&&t.shoppingListSlice.shoppingListProducts.products;if(!Array.isArray(n))return e;for(let t=0;t<n.length;t++){const o=n[t];if(!o||!o.item_id)continue;const r=Number(o.quantity)||0;r<=0||e.set(String(o.item_id),{qty:r,name:String(o.name||o.product_name||o.productName||"").trim()})}return e}async function i(){window.__ttpSobeysCartReadInFlight=(window.__ttpSobeysCartReadInFlight||0)+1;try{const e=await t.runAdapter("cart.read",{});return e&&!1!==e.ok?e:null}catch(e){return t.log("[cart.subscribe] cart.read error:",String(e&&e.message||e)),null}finally{window.__ttpSobeysCartReadInFlight=Math.max(0,(window.__ttpSobeysCartReadInFlight||1)-1)}}function s(){return window.__ttpSobeysPrevItemMap||new Map}function c(t){window.__ttpSobeysPrevItemMap=t}function a(e,a){window.__ttpSobeysDebounceTimer&&clearTimeout(window.__ttpSobeysDebounceTimer),window.__ttpSobeysDebounceTimer=setTimeout(function(){window.__ttpSobeysDebounceTimer=null,async function(e,a){const u=o(),d=r(u?u.getState():{});let l=s();if(a){const n=await i();return n?(c(d),t.emit({t:"cart_change_event",cart:n}),void t.log("[cart.subscribe] initial snapshot lines="+d.size+" itemCount="+(n.itemCount||0))):void t.log("[cart.subscribe] cart.read null — skip initial emit (source="+e+")")}const p=function(t,e){const n=[],o=[],r=[];for(const o of e){const e=o[0],i=o[1],s=t.get(e);s?s.qty!==i.qty&&r.push({id:e,name:i.name,from:s.qty,to:i.qty}):n.push({id:e,qty:i.qty,name:i.name})}for(const n of t){const t=n[0],r=n[1];e.has(t)||o.push({id:t,qty:r.qty,name:r.name})}return{added:n,removed:o,qtyChanged:r}}(l,d),_=p.added.filter(function(t){return!n(t.id)}),b=p.removed.filter(function(t){return!n(t.id)}),f=p.qtyChanged.filter(function(t){return!n(t.id)});if(t.log("[cart.subscribe] diff source="+e+" raw added="+p.added.length+" removed="+p.removed.length+" qty="+p.qtyChanged.length+" filtered added="+_.length+" removed="+b.length+" qty="+f.length),c(d),!_.length&&!b.length&&!f.length)return void t.log("[cart.subscribe] no emit (suppressed or no cart diff)");const w=await i();w?(t.log("[cart.subscribe] emitting cart_change_event"),t.emit({t:"cart_change_event",cart:w,change:{type:_.length?"add":b.length?"remove":"qty",added:_,removed:b,qtyChanged:f,at:Date.now()}})):t.log("[cart.subscribe] cart.read null — skip emit (source="+e+")")}(e,!!a).catch(function(e){t.log("[cart.subscribe] flush failed:",String(e&&e.message||e))})},500)}function u(){try{const t=sessionStorage.getItem("persist:root");if(!t)return"";const e=JSON.parse(t),n=e.shoppingListSlice?JSON.parse(e.shoppingListSlice):null,o=n&&n.shoppingListProducts&&n.shoppingListProducts.products;return Array.isArray(o)?o.map(function(t){return String(t.item_id||"")+":"+String(t.quantity||0)}).sort().join("|"):"empty"}catch(t){return""}}window.__ttpSobeysSuppressListenerBound||(window.__ttpSobeysSuppressListenerBound=!0,window.addEventListener("netcost:partner-action:cart_mutation",function(t){"function"==typeof window.__ttpSobeysOnPartnerMutation&&window.__ttpSobeysOnPartnerMutation(t)})),window.__ttpSobeysOnPartnerMutation=function(n){const o=function(t){if(!t||"object"!=typeof t)return[];const e=[],n=function(t){if(null==t||""===t)return;const n=String(t).trim();n&&e.push(n)};if(n(t.productId),n(t.sku),Array.isArray(t.items))for(let e=0;e<t.items.length;e++){const o=t.items[e];o&&"object"==typeof o&&(n(o.productId),n(o.sku),n(o.item_id))}return e}(n&&n.detail&&n.detail.args||{}),r=Date.now()+8e3;o.forEach(function(t){e.set(t,r)}),t.log("[cart.subscribe] suppressing productIds="+o.join(",")+" for 8000ms")},window.__ttpSobeysCartSubscribeInstalled?(t.log("[cart.subscribe] re-wiring refresh handler for new session ctx"),"function"==typeof window.__ttpSobeysStoreUnsub&&(window.__ttpSobeysStoreUnsub(),window.__ttpSobeysStoreUnsub=null),window.__ttpSobeysPersistPollId&&(clearInterval(window.__ttpSobeysPersistPollId),window.__ttpSobeysPersistPollId=null),window.__ttpSobeysDebounceTimer&&(clearTimeout(window.__ttpSobeysDebounceTimer),window.__ttpSobeysDebounceTimer=null)):window.__ttpSobeysCartSubscribeInstalled=!0,window.__ttpSobeysPrevItemMap||(window.__ttpSobeysPrevItemMap=new Map),window.__ttpSobeysCartScheduleRefresh=a;const d=await async function(){const e=Date.now()+3e4;let n=0;for(;Date.now()<e;){n++;const e=o();if(e)return t.log("[cart.subscribe] redux store found after "+n+" attempt(s)"),e;await new Promise(function(t){setTimeout(t,250)})}return t.log("[cart.subscribe] redux store not found after "+n+" attempts / 30000ms"),null}();if(!d)return{ok:!1,reason:"redux_store_not_found",detail:"timeout_30000ms"};!function(e){if(!e||e.__ttpSobeysDispatchPatched)return;e.__ttpSobeysDispatchPatched=!0;const n=e.dispatch.bind(e);e.dispatch=function(e){const o=n(e);try{const n=e&&"object"==typeof e?e.type:null;n&&0===String(n).indexOf("shoppingListSlice/")&&(t.log("[cart.subscribe] dispatch-hit "+n),a("dispatch",!1))}catch(t){}return o},t.log("[cart.subscribe] dispatch hook installed on redux store")}(d),c(r(d.getState()));const l=d.subscribe(function(){const t=r(d.getState());(function(t,e){if(t.size!==e.size)return!1;for(const n of t){const t=n[0],o=n[1],r=e.get(t);if(!r||r.qty!==o.qty)return!1}return!0})(s(),t)||a("redux",!1)});return window.__ttpSobeysStoreUnsub=l,window.__ttpSobeysPersistPollId||(window.__ttpSobeysPersistFingerprint=u(),window.__ttpSobeysPersistPollId=setInterval(function(){const e=u();e!==window.__ttpSobeysPersistFingerprint&&(window.__ttpSobeysPersistFingerprint=e,t.log("[cart.subscribe] persist:root fingerprint changed"),a("persist",!1))},400),t.log("[cart.subscribe] sessionStorage persist:root poll installed")),a("initial",!0),{ok:!0,listening:!0,strategy:"redux_dispatch_hook + subscribe + persist_poll",autoRunExpected:!0}};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
!function(){"use strict";const t="ACSYSHF8AU",e="dxp_product_en";function n(t){const e=String(t||"").replace(/\D/g,"");return e?e.length>=4?e.slice(-4):e.padStart(4,"0"):null}function o(t){return t.includes("algolia.net")&&t.includes("/indexes/")}async function r(n,o,r){const i="https://"+t+"-dsn.algolia.net/1/indexes/*/queries?x-algolia-api-key=fe555974f588b3e76ad0f1c548113b22&x-algolia-application-id="+t,s=JSON.stringify({requests:[{indexName:e,query:n,hitsPerPage:r||3,clickAnalytics:!0,filters:"storeId:"+o+" AND isVisible:true AND isMassOffers:false",analyticsTags:["A","website"]}]}),a=await fetch(i,{method:"POST",credentials:"omit",headers:{Accept:"application/json","Content-Type":"application/x-www-form-urlencoded"},body:s});return(((await a.json()).results||[])[0]||{}).hits||[]}function i(t){const e=new URL(location.origin+"/");e.searchParams.set("query",t),e.searchParams.set("tab","products"),history.pushState(null,"",e.toString()),window.dispatchEvent(new PopStateEvent("popstate",{state:null}))}function s(){return document.querySelectorAll('[id^="ObjectID-"]').length}function a(t){return new Promise(function(e){setTimeout(e,t)})}window.__ttpSobeysInjectProbeMin=async function(t){const c=(t=t||{}).hookMode||"both",u=function(){try{const t=JSON.parse(localStorage.getItem("store")||"{}"),e=n(t.storeNumber||t.objectID);if(e)return e}catch(t){}const t=document.cookie.match(/(?:^|;\s*)storeId=([^;]+)/);return t?n(decodeURIComponent(t[1]).split("_")[0]):null}();if(!u)throw new Error("Pick a store first");const l={transport:[],forged:[],hookMode:c,sid:u};if(!(await r("coffee",u,1)).length)throw new Error("Algolia donor failed");const d=[];for(const e of t.queries||["tomatoes","cucumbers"]){const t=await r(e,u,1);if(!t.length)throw new Error("No hits for "+e);d.push(t[0])}const p="spcrt-probe-"+Math.random().toString(36).slice(2,8),f={prefix:p,revision:0,algoliaHits:[],meta:{},index:e};function h(){const t=f.revision?p+"-r"+f.revision:p;return JSON.stringify({results:[{hits:f.algoliaHits,nbHits:f.algoliaHits.length,page:0,nbPages:f.algoliaHits.length?1:0,hitsPerPage:50,exhaustiveNbHits:!0,query:t,index:e,processingTimeMS:1}]})}function g(t,e){return o(t)&&(function(t,e){try{const n=JSON.parse(t).requests;return Array.isArray(n)&&n.some(function(t){return String(t.query||"").includes(e)})}catch(t){return!1}}(e,p)||t.includes(encodeURIComponent(p)))}function y(t,e,n,o,r){const i={kind:e,forged:!!r,q:function(){try{return JSON.parse(o).requests[0].query}catch(t){return null}}()};l[t].push(i)}window.__speacart_inject_state=window.__speacart_inject_state||{},window.__speacart_inject_state[p]=f;const w=window.fetch;"fetch"!==c&&"both"!==c||(window.fetch=function(t,e){const n="string"==typeof t?t:t&&t.url||"",r=e&&null!=e.body?String(e.body):"";return o(n)&&y("transport","fetch",0,r,!1),g(n,r)?(y("forged","fetch",0,r,!0),Promise.resolve(new Response(h(),{status:200,headers:{"content-type":"application/json"}}))):w.apply(this,arguments)});const b=XMLHttpRequest.prototype.open,S=XMLHttpRequest.prototype.send;"xhr"!==c&&"both"!==c||(XMLHttpRequest.prototype.open=function(t,e){return this.__u=e,b.apply(this,arguments)},XMLHttpRequest.prototype.send=function(t){const e=this.__u||"",n=null!=t?String(t):"";if(o(e)&&y("transport","xhr",0,n,!1),g(e,n)){y("forged","xhr",0,n,!0);const t=this,e=h();return void setTimeout(function(){try{Object.defineProperty(t,"readyState",{configurable:!0,writable:!0,value:4}),Object.defineProperty(t,"status",{configurable:!0,writable:!0,value:200}),Object.defineProperty(t,"responseText",{configurable:!0,writable:!0,value:e}),Object.defineProperty(t,"response",{configurable:!0,writable:!0,value:e})}catch(t){}t.dispatchEvent(new Event("readystatechange")),t.dispatchEvent(new Event("load")),t.dispatchEvent(new Event("loadend"))},0)}return S.apply(this,arguments)});const _=s();i(p),await a(2e3);const m=s();for(const t of d)f.algoliaHits.push(JSON.parse(JSON.stringify(t))),f.revision++,i(p+"-r"+f.revision),await a(2e3);const v=s(),O=l.transport.reduce(function(t,e){return t[e.kind]=(t[e.kind]||0)+1,t},{}),E=v>0?"SUCCESS":0===l.forged.length?"NO_FORGE — Sobeys did not Algolia-query the sentinel; try hookMode both":"FORGED_BUT_NO_TILES — hook works, React/grid issue",I={ok:v>0,hookMode:c,prefix:p,sid:u,tiles:{before:_,afterInit:m,afterAppend:v},hitsInState:f.algoliaHits.length,productIds:f.algoliaHits.map(function(t){return t.objectID}),transportByKind:O,forgedCount:l.forged.length,transport:l.transport,forged:l.forged,url:location.href,verdict:E};return console.log("[SOBEYS-INJECT-MIN]",I),window.__ttpSobeysInjectProbeMinLast=I,I},console.log("[SOBEYS-INJECT-MIN] ready — await __ttpSobeysInjectProbeMin()")}();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
!function(){"use strict";if(window.__ttpSobeysInjectProbeInstalled)return void console.warn("[SOBEYS-INJECT-PROBE] already installed");const t="ACSYSHF8AU",e="dxp_product_en",o=[],n=[];let r=null,i=!1;function s(t){if(null==t||""===t)return null;const e=String(t).replace(/\D/g,"");return e?e.length>=4?e.slice(-4):e.padStart(4,"0"):null}function a(t){return"string"==typeof t&&-1!==t.indexOf("algolia.net")&&-1!==t.indexOf("/indexes/")}async function c(o,n,r){const i="https://"+t+"-dsn.algolia.net/1/indexes/*/queries?x-algolia-agent="+encodeURIComponent("Algolia for JavaScript (5.52.1); Search (5.52.1); Browser")+"&x-algolia-api-key=fe555974f588b3e76ad0f1c548113b22&x-algolia-application-id="+t,s={requests:[{indexName:e,query:String(o).trim(),hitsPerPage:r||3,clickAnalytics:!0,userToken:"",filters:"storeId:"+n+" AND isVisible:true AND isMassOffers:false",analyticsTags:["A","website"]}]},a=await fetch(i,{method:"POST",credentials:"omit",headers:{Accept:"application/json","Content-Type":"application/x-www-form-urlencoded"},body:JSON.stringify(s)});if(!a.ok)throw new Error("algolia "+a.status);const c=await a.json(),l=c&&c.results&&c.results[0]||{};return{hits:Array.isArray(l.hits)?l.hits:[],meta:{processingTimeMS:l.processingTimeMS,hitsPerPage:l.hitsPerPage,exhaustiveNbHits:l.exhaustiveNbHits}}}function l(){return document.querySelectorAll('[id^="ObjectID-"]').length}function p(t){return new Promise(function(e){setTimeout(e,t)})}function u(t){const e=new URL(location.origin+"/");e.searchParams.set("query",t),e.searchParams.set("tab","products"),history.pushState(null,"",e.toString()),window.dispatchEvent(new PopStateEvent("popstate",{state:null}))}function h(t,e,o,i){const s={at:(new Date).toISOString(),kind:t,url:String(e).slice(0,120),forged:!!i,bodyHasSentinel:!!o&&-1!==o.indexOf(r&&r.prefix)};return n.push(s),console.log("[SOBEYS-INJECT-PROBE] hook",t,i?"FORGED":"pass-through",s.url),s}function d(t){if(t){try{t.origFetch&&(window.fetch=t.origFetch)}catch(t){}try{t.origXhrOpen&&(XMLHttpRequest.prototype.open=t.origXhrOpen),t.origXhrSend&&(XMLHttpRequest.prototype.send=t.origXhrSend)}catch(t){}try{t.styleEl&&t.styleEl.parentNode&&t.styleEl.parentNode.removeChild(t.styleEl)}catch(t){}try{t.cleanupTimer&&clearTimeout(t.cleanupTimer)}catch(t){}window.__speacart_inject_state&&t.sentinel&&delete window.__speacart_inject_state[t.sentinel]}}window.__ttpSobeysAlgoliaTransportProbeInstall=function(){return/(^|\.)sobeys\.com$/.test(location.hostname)||console.warn("[SOBEYS-INJECT-PROBE] not on sobeys.com"),function(){if(i)return;i=!0;const t=window.fetch;window.__ttpSobeysInjectProbeNativeFetch=t,window.fetch=function(e,n){const r="string"==typeof e?e:e&&e.url||"";if(a(r)){let t="";n&&null!=n.body&&(t="string"==typeof n.body?n.body:String(n.body)),o.push({at:(new Date).toISOString(),kind:"fetch",url:r,query:function(){try{const e=JSON.parse(t).requests;return e&&e[0]&&e[0].query}catch(t){return null}}()}),console.log("[SOBEYS-INJECT-PROBE] transport fetch",r.slice(0,80))}return t.apply(this,arguments)};const e=XMLHttpRequest.prototype.open,n=XMLHttpRequest.prototype.send;window.__ttpSobeysInjectProbeNativeXhrOpen=e,window.__ttpSobeysInjectProbeNativeXhrSend=n,XMLHttpRequest.prototype.open=function(t,o){return this.__ttpProbeMethod=t,this.__ttpProbeUrl="string"==typeof o?o:String(o||""),e.apply(this,arguments)},XMLHttpRequest.prototype.send=function(t){const e=this.__ttpProbeUrl||"";if(a(e)){const n="string"==typeof t?t:t?String(t):"";o.push({at:(new Date).toISOString(),kind:"xhr",url:e,query:function(){try{const t=JSON.parse(n).requests;return t&&t[0]&&t[0].query}catch(t){return null}}()}),console.log("[SOBEYS-INJECT-PROBE] transport xhr",e.slice(0,80))}return n.apply(this,arguments)}}(),console.log("[SOBEYS-INJECT-PROBE] transport logger on — search or soft-nav, then __ttpSobeysAlgoliaTransportProbeReport()"),o},window.__ttpSobeysAlgoliaTransportProbeReport=function(){const t=o.reduce(function(t,e){return t[e.kind]=(t[e.kind]||0)+1,t},{}),e={hitCount:o.length,byKind:t,hits:o.slice(),verdict:t.xhr>0&&!t.fetch?"Sobeys uses XHR for Algolia — fetch-only injectInit will NOT render tiles":t.fetch>0&&!t.xhr?"Sobeys uses fetch for Algolia — fetch hook should work":t.fetch>0&&t.xhr>0?"Both fetch and XHR seen — use both hooks":"No Algolia traffic yet — run a search or inject probe first"};return console.table(o),console.log("[SOBEYS-INJECT-PROBE] transport report",e),e},window.__ttpSobeysInjectProbeRun=async function(t){const o=(t=t||{}).hookMode||"both",i=t.queries||["tomatoes","cucumbers"],f=t.templateQuery||"coffee";if(!/(^|\.)sobeys\.com$/.test(location.hostname))throw new Error("Run on www.sobeys.com top-level tab");const g=function(){let t=null;try{const e=JSON.parse(localStorage.getItem("store")||"{}");e&&(e.storeNumber||e.objectID)&&(t=s(e.storeNumber||e.objectID))}catch(t){}if(!t){const e=document.cookie.match(/(?:^|;\s*)storeId=([^;]+)/);e&&(t=s(decodeURIComponent(e[1]).split("_")[0]))}return t}();if(!g)throw new Error("No store — pick a store on sobeys.com first");r&&d(r),n.length=0,console.log("[SOBEYS-INJECT-PROBE] fetching donor template + probe products… storeId="+g);let y=window.__speacart_sobeys_template;const w=Date.now();if(!y||!y.sampleHit||y.storeId!==g){const t=await c(f,g,5);if(!t.hits.length)throw new Error("no donor hit for "+f);y={sampleHit:t.hits[0],resultMeta:t.meta,storeId:g,indexName:e,capturedAt:w},window.__speacart_sobeys_template=y}const b=[];for(let t=0;t<i.length;t++){const e=await c(i[t],g,3);if(!e.hits.length)throw new Error("no hits for "+i[t]);b.push(e.hits[0]),console.log("[SOBEYS-INJECT-PROBE] product",i[t],"→",e.hits[0].objectID,e.hits[0].name)}const S="spcrt-sl-probe-"+Math.random().toString(36).slice(2,10);window.__speacart_inject_state||(window.__speacart_inject_state={});const _={sentinel:S,prefix:S,revision:0,algoliaHits:[],storeId:g,indexName:e,resultMeta:y.resultMeta||{},hookMode:o};window.__speacart_inject_state[S]=_,r=_,function(t,e){const o=t.prefix;function n(){const e=t.revision>0?o+"-r"+t.revision:o;return JSON.stringify({results:[{hits:t.algoliaHits,nbHits:t.algoliaHits.length,page:0,nbPages:t.algoliaHits.length>0?1:0,hitsPerPage:50,exhaustiveNbHits:!0,exhaustiveTypo:!0,query:e,params:"",processingTimeMS:t.resultMeta&&t.resultMeta.processingTimeMS||1,index:t.indexName}]})}function r(t,e){return!!a(t)&&(function(t,e){if(!t||"string"!=typeof t||!e)return!1;try{const o=JSON.parse(t).requests;if(!Array.isArray(o))return!1;for(let t=0;t<o.length;t++)if(-1!==String(o[t]&&o[t].query||"").indexOf(e))return!0}catch(t){}return!1}(e,o)||-1!==t.indexOf(encodeURIComponent(o)))}if("fetch"===e||"both"===e){const e=window.fetch;t.origFetch=e,window.fetch=function(t,o){const i="string"==typeof t?t:t&&t.url||"";let s="";return o&&null!=o.body&&(s="string"==typeof o.body?o.body:String(o.body)),r(i,s)?(h("fetch",i,s,!0),Promise.resolve((c=n(),new Response(c,{status:200,statusText:"OK",headers:{"content-type":"application/json; charset=utf-8"}})))):(a(i)&&h("fetch",i,s,!1),e.apply(this,arguments));var c}}if("xhr"===e||"both"===e){const e=XMLHttpRequest.prototype.open,o=XMLHttpRequest.prototype.send;t.origXhrOpen=e,t.origXhrSend=o,XMLHttpRequest.prototype.open=function(t,o){return this.__ttpInjectUrl="string"==typeof o?o:String(o||""),e.apply(this,arguments)},XMLHttpRequest.prototype.send=function(t){const e=this.__ttpInjectUrl||"",i="string"==typeof t?t:t?String(t):"";if(r(e,i)){h("xhr",e,i,!0);const t=this,o=n();return void setTimeout(function(){!function(t,e){const o=function(e){try{Object.defineProperty(t,"readyState",{configurable:!0,writable:!0,value:e})}catch(t){}t.dispatchEvent(new Event("readystatechange"))};try{Object.defineProperty(t,"status",{configurable:!0,writable:!0,value:200}),Object.defineProperty(t,"statusText",{configurable:!0,writable:!0,value:"OK"}),Object.defineProperty(t,"responseText",{configurable:!0,writable:!0,value:e}),Object.defineProperty(t,"response",{configurable:!0,writable:!0,value:e})}catch(t){}o(2),o(3),o(4),t.dispatchEvent(new Event("load")),t.dispatchEvent(new Event("loadend"))}(t,o)},0)}return a(e)&&h("xhr",e,i,!1),o.apply(this,arguments)}}t.buildForgedAlgoliaJson=n}(_,o);const m=document.createElement("style");m.id="speacart-probe-hide-promo-"+S,m.textContent='[data-testid="in-grid-module"] { display: none !important; }',document.head.appendChild(m),_.styleEl=m,_.cleanupTimer=setTimeout(function(){console.log("[SOBEYS-INJECT-PROBE] auto cleanup after 60s"),window.__ttpSobeysInjectProbeUninstall()},6e4);const I=l();u(S),await p(1500);const O=l();n.filter(function(t){return t.forged}).length;for(let t=0;t<b.length;t++)_.algoliaHits.push(JSON.parse(JSON.stringify(b[t]))),_.revision+=1,u(S+"-r"+_.revision),await p(1200);const E=l(),P=n.filter(function(t){return t.forged}).length,N={ok:E>0,hookMode:o,storeId:g,sentinel:S,tilesBefore:I,tilesAfterInit:O,tilesAfterAppend:E,algoliaHitsInState:_.algoliaHits.length,hookIntercepts:n.slice(),forgedInterceptCount:P,productObjectIds:_.algoliaHits.map(function(t){return t.objectID}),url:location.href,verdict:E>0?"SUCCESS — inject channel rendered "+E+" tile(s) with hookMode="+o:0===P?"FAIL — no forged intercepts; Sobeys never queried Algolia with sentinel (try hookMode both + longer wait)":"FAIL — hook forged "+P+" response(s) but 0 tiles; hit shape or React path issue"};return console.log("[SOBEYS-INJECT-PROBE] run report",N),console.table(n),N},window.__ttpSobeysInjectProbeReport=function(){const t=r,e=t&&t.sentinel,i=e&&window.__speacart_inject_state&&window.__speacart_inject_state[e];return{activeSentinel:e||null,hookMode:t&&t.hookMode,algoliaHits:i?i.algoliaHits.length:0,revision:i?i.revision:null,tiles:l(),hookIntercepts:n.slice(),transportHits:o.slice(),url:location.href}},window.__ttpSobeysInjectProbeUninstall=function(){r&&d(r),r=null,window.__ttpSobeysInjectProbeNativeFetch&&(window.fetch=window.__ttpSobeysInjectProbeNativeFetch),window.__ttpSobeysInjectProbeNativeXhrOpen&&(XMLHttpRequest.prototype.open=window.__ttpSobeysInjectProbeNativeXhrOpen,XMLHttpRequest.prototype.send=window.__ttpSobeysInjectProbeNativeXhrSend),i=!1,console.log("[SOBEYS-INJECT-PROBE] uninstalled")},window.__ttpSobeysInjectProbeInstalled=!0,console.log("[SOBEYS-INJECT-PROBE] installed on",location.hostname,"— await __ttpSobeysInjectProbeRun() (store required)")}();
|