<%@ Page Title="Cross-tab coordination" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" %>
<asp:Content ID="TitleContent" ContentPlaceHolderID="TitleContent" runat="server">Cross-tab coordination - AjaxUploader v5.2 Demos</asp:Content>
<asp:Content ID="HeaderContent" ContentPlaceHolderID="HeaderTitle" runat="server">Cross-tab coordination (BroadcastChannel)</asp:Content>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server"> 
<div class="demo-card"> <div class="demo-card-header">Don't upload the same file twice when the page is open in multiple tabs</div> <div class="demo-card-body"> <p class="demo-description"> A common foot-gun: the user has the upload page open in two tabs (cmd-clicked a link, or navigated and didn't close the old one). They drag the same file into both. Without coordination, the server gets two parallel uploads, two database rows, two thumbnails, two billable events. AjaxUploader's <code>crossTab</code> option uses <code>BroadcastChannel</code> to lock each in-flight file by content hash (or name+size+lastModified fallback) so only one tab actually transfers it.
 </p> <div class="demo-expect"><strong>What you should see:</strong> open this page in two tabs and act in one - the other mirrors queue and progress over a BroadcastChannel, and the same file is not uploaded twice.</div> <div class="d-flex gap-2 mb-2 mt-4">
 <button type="button" class="btn btn-primary" id="startBtn_crosstabUploader">Start upload</button>
 <button type="button" class="btn btn-outline-secondary" id="clearBtn_crosstabUploader">Clear queue</button>
 </div>
 <div id="crosstabUploader"></div> <script> (function () {
 function init() {
 if (!window.AjaxUploader || typeof AjaxUploader.create !== 'function') return;
 var uploader = AjaxUploader.create('#crosstabUploader', {
 uploadUrl: '<%= ResolveUrl("~/ajaxupload.axd/upload") %>',
 multiple: true,
 autoUpload: false,
 crossTab: true,
 crossTabChannel: 'demo-coordination'
 });
 var startButton = document.getElementById('startBtn_crosstabUploader');
 var clearButton = document.getElementById('clearBtn_crosstabUploader');
 if (startButton) {
 startButton.addEventListener('click', function () {
 if (uploader && typeof uploader.upload === 'function') uploader.upload();
 });
 }
 if (clearButton) {
 clearButton.addEventListener('click', function () {
 if (uploader && typeof uploader.clear === 'function') uploader.clear();
 });
 }
 }
 if (document.readyState !== 'loading') init();
 else document.addEventListener('DOMContentLoaded', init);
 })();
 </script> <div class="alert alert-info mt-3" role="alert"> <strong>Try it:</strong> <ol class="mb-0 mt-1"> <li>Open this demo in <strong>two</strong> browser tabs (right-click -> "Open in new tab").</li> <li>Pick the same file in tab&nbsp;1 and start uploading.</li> <li>Pick the same file in tab&nbsp;2 - it appears in the queue but immediately fails with <code>cross_tab_locked</code> instead of starting a duplicate transfer.</li> <li>Cancel in tab&nbsp;1; tab&nbsp;2 will succeed on its next retry.</li> </ol> </div> <h5 class="mt-4">Configuration</h5> <div class="code-panel">
<pre><code>AjaxUploader.create('#uploader', {
 uploadUrl: '<%= ResolveUrl("~/ajaxupload.axd/upload") %>',
 crossTab: <span style="color:#94a3b8">true</span>,
 crossTabChannel: <span style="color:#94a3b8">'my-app-uploads'</span> <span style="color:#94a3b8">// optional namespace</span>
});</code></pre> </div> <h5 class="mt-4">Lock key strategy</h5> <ul class="mb-3"> <li><strong>Preferred:</strong> the file's content hash (when <code>computeHash: true</code>). Identical bytes -> identical key -> only one tab proceeds even when filenames differ.</li> <li><strong>Fallback:</strong> <code>name + size + lastModified</code>. Catches the obvious case (same drag-drop in both tabs) without requiring a hash pass.</li> <li>Locks are held for the life of the upload - released on success, cancel, fatal failure, and tab unload.</li> </ul> <h5 class="mt-4">Pair with content-addressable storage</h5> <p>If your backend already deduplicates by hash, cross-tab coordination is the client-side complement: it stops the duplicate request from ever leaving the browser, saving network + server round-trips.</p> <div class="code-panel">
<pre><code>AjaxUploader.create('#uploader', {
 computeHash: <span style="color:#94a3b8">true</span>,
 hashAlgorithm: <span style="color:#94a3b8">'sha256'</span>,
 crossTab: <span style="color:#94a3b8">true</span>
});</code></pre> </div> <h5 class="mt-4">Browser support</h5> <ul class="mb-3"> <li><strong>BroadcastChannel:</strong> all modern browsers (Chrome 54+, Firefox 38+, Safari 15.4+, Edge 79+). Service Workers + Web Workers can post to the same channel.</li> <li><strong>Older browsers:</strong> the option becomes a no-op - uploads work normally without coordination, no errors thrown.</li> <li><strong>Cross-origin tabs:</strong> BroadcastChannel is same-origin only by spec, so this protects against <em>same-origin</em> duplicates only (which is the realistic case).</li> </ul> <h5 class="mt-4">Why this is unique</h5> <p class="text-muted small">Uppy, FilePond, Dropzone, and FineUploader all leave duplicate-tab handling to the application layer. AjaxUploader is the first major upload component to bake it into the core option set.</p> </div>
</div> 
</asp:Content>
