<%@ Page Title="Custom upload strategy" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" %> 
<asp:Content ID="TitleContent" ContentPlaceHolderID="TitleContent" runat="server">Custom strategy - AjaxUploader v5.3 Demos</asp:Content> 
<asp:Content ID="HeaderContent" ContentPlaceHolderID="HeaderTitle" runat="server">Custom upload strategy</asp:Content> 
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server"> 
<div class="demo-card"> <div class="demo-card-header">Custom strategy via <code>AjaxUploader.registerStrategy()</code></div> <div class="demo-card-body"> <p class="demo-description"> Built-in strategies (<code>single</code> / <code>chunked</code> / <code>s3</code> / <code>azure</code> / <code>tus</code> / <code>urlImport</code>) cover the common transports. For everything else - signed GCS resumable uploads, a custom protocol against a legacy backend, WebRTC-style peer transport - register your own strategy. A strategy is just an object with an <code>upload(task, uploader)</code> method that returns a Promise.
 </p> <div class="demo-expect"><strong>What you should see:</strong> a pluggable transport (simulated, no server round-trip) so you can watch the strategy hooks fire and see exactly what a custom transport must implement.</div> <div class="d-flex gap-2 mb-2 mt-4">
 <button type="button" class="btn btn-primary" id="startBtn_customStrategyUploader">Start upload</button>
 <button type="button" class="btn btn-outline-secondary" id="clearBtn_customStrategyUploader">Clear queue</button>
 </div>
 <div id="customStrategyUploader"></div> <script> (function () {
 function init() {
 if (!window.AjaxUploader || typeof AjaxUploader.create !== 'function') return; // graceful no-op if runtime missing

 // Define a 'simulated' strategy: no server round-trip, just a
 // progress emulator. Useful as a template for your own.
 if (!AjaxUploader.strategies['simulated']) {
 AjaxUploader.registerStrategy('simulated', {
 upload: function (task, uploader) {
 return new Promise(function (resolve, reject) {
 var total = task.file.size || 1024 * 1024;
 var loaded = 0;
 var step = Math.max(total / 40, 8192);
 var signal = task.abortController && task.abortController.signal;
 function cleanup() {
 clearInterval(tick);
 if (signal) signal.removeEventListener('abort', onAbort);
 }
 function onAbort() {
 cleanup();
 reject(new DOMException('Upload cancelled', 'AbortError'));
 }
 var tick = setInterval(function () {
 loaded = Math.min(loaded + step, total);
 uploader._updateProgress(task, loaded, total);
 if (loaded >= total) {
 cleanup();
 resolve({
 success: true,
 fileGuid: task.id,
 fileName: task.fileName,
 fileSize: total
 });
 }
 }, 60);
 if (signal) {
 if (signal.aborted) onAbort();
 else signal.addEventListener('abort', onAbort, { once: true });
 }
 });
 }
 });
 }

 var startButton = document.getElementById('startBtn_customStrategyUploader');
 var clearButton = document.getElementById('clearBtn_customStrategyUploader');
 var uploader = AjaxUploader.create('#customStrategyUploader', {
 uploadUrl: '<%= ResolveUrl("~/ajaxupload.axd/upload") %>',
 strategy: 'simulated',
 autoUpload: false,
 multiple: true
 });
 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>What this demo does:</strong> registers a fake <code>simulated</code> strategy that emits progress without sending bytes, proving the registry works without needing a special backend. Swap the body for real XHR/fetch calls to your own service.
 </div> <h5 class="mt-4">Strategy contract</h5> <div class="code-panel"> 
<pre><code>AjaxUploader.registerStrategy('my-transport', {
 upload: function (task, uploader) {
 <span style="color:#94a3b8">// Must honour task.abortController for cancel + pause.</span> <span style="color:#94a3b8">// Must call uploader._updateProgress(task, loaded, total) to advance UI.</span> <span style="color:#94a3b8">// Must resolve with an UploadResult-shaped object, or reject.</span> return fetch('/my-endpoint', {
 method: 'POST',
 body: task.file,
 signal: task.abortController.signal
 }).then(function (r) {
 return r.json();
 });
 },
 name: 'my-transport' <span style="color:#94a3b8">// shown in task.metadata.strategy</span> 
});

<span style="color:#94a3b8">// Use it:</span> 
AjaxUploader.create('#uploader', { strategy: 'my-transport' });</code></pre> </div> <h5 class="mt-4">When to roll your own</h5> <ul class="mb-3"> <li>Your backend expects a protocol AjaxUploader doesn't ship (GCS resumable, custom chunk headers, long-polling).</li> <li>You need to inject auth-rotation, signed headers per request, or custom retry semantics.</li> <li>You want to wrap an existing SDK (AWS SDK v3, Uppy's internal uploader) with AjaxUploader's UI + progress model.</li> <li>You need WebRTC / peer upload / P2P transport.</li> </ul> <h5 class="mt-4">Inline strategy objects</h5> <p>You can also pass a strategy object directly - no registry call needed:</p> <div class="code-panel"> 
<pre><code>AjaxUploader.create('#uploader', {
 strategy: { name: 'inline', upload: function (task, uploader) { /* ... */ } }
});</code></pre> </div> </div> 
</div> 
</asp:Content> 
