All entries

May 14, 2026

af2eb2f30284d5ebd529abafcfef4dea48348b71a4dc209cf354d9fbd36a6027
Previous: 5fe14787 Bundle: 88.9 KB

The fetch interceptor removes extensionOriginFetch support, simplifying all intercepted requests to use native fetch.

Highlights

  • The fetch module removes the extensionOriginFetch code path, which previously allowed extension-origin requests to be routed through a custom fetch implementation from internalApiPromise.
  • Helper utilities for serializing Request objects to plain objects and reconstructing Responses from ArrayBuffers were removed from the utilities module alongside extensionOriginFetch.
  • The session token retry logic in the fetch interceptor is simplified, with both the initial request and the auth-header retry now always using native self.fetch directly.
17 files changed +64 -118

Infrastructure Changes

REPORT.md
+8 -8
@@ -1,6 +1,6 @@
# Shopify App Bridge — Unminification Report

Generated: 2026-05-07T08:51:17.772Z
Generated: 2026-05-14T08:53:17.664Z

## Files

@@ -8,23 +8,23 @@ Generated: 2026-05-07T08:51:17.772Z
|------|------|-------|------|
| _bootstrap.js | 29.2KB | 1013 | Infrastructure |
| _remote-ui.js | 6.0KB | 255 | Infrastructure |
| _utilities.js | 72.2KB | 2707 | Infrastructure |
| _utilities.js | 71.4KB | 2668 | Infrastructure |
| _web-vitals.js | 11.6KB | 527 | Infrastructure |
| analytics.js | 211B | 11 | Module |
| app.js | 346B | 15 | Module |
| client.js | 303B | 15 | Module |
| environment.js | 241B | 14 | Module |
| fetch.js | 3.1KB | 94 | Module |
| fetch.js | 2.8KB | 81 | Module |
| id-token.js | 661B | 28 | Module |
| id-token.js | 640B | 28 | Module |
| index.js | 2.2KB | 48 | Index |
| intents.js | 3.1KB | 106 | Module |
| internal-only.js | 543B | 26 | Module |
| internal-only.js | 539B | 26 | Module |
| loading.js | 605B | 31 | Module |
| navigation.js | 416B | 19 | Module |
| navigation.js | 408B | 19 | Module |
| picker.js | 451B | 18 | Module |
| polaris.js | 240B | 12 | Module |
| pos.js | 5.7KB | 248 | Module |
| print.js | 569B | 25 | Module |
| print.js | 570B | 25 | Module |
| resource-picker.js | 2.8KB | 119 | Module |
| reviews.js | 365B | 16 | Module |
| s-app-nav.js | 175B | 10 | Module |
@@ -46,7 +46,7 @@ Generated: 2026-05-07T08:51:17.772Z
| user.js | 940B | 37 | Module |
| visibility.js | 973B | 34 | Module |
| web-vitals.js | 1.8KB | 64 | Module |
| **Total** | **170.7KB** | **6493** | |
| **Total** | **169.5KB** | **6441** | |

## Pipeline Stages


modules/_bootstrap.js
+6 -6
@@ -433,8 +433,8 @@
    {
      I(
        {
          idToken: deepClone,
          idToken: It,
          fetch: Mt,
          fetch: restoreProperty,
        },
        [],
      );
@@ -951,43 +951,43 @@
  p.send('Loading.stop');
  b.ready = Promise.resolve();
})();
var ee;
var ie;
var oe;
var re;
var ae;
var se;
var se = -1;
var ce;
var ce = function (t) {
var ue = -1;
var le = function (t) {
  addEventListener(
    'pageshow',
    function (n) {
      if (n.persisted) {
        t(n);
      }
    },
    true,
  );
};
  return (
    window.performance &&
    performance.getEntriesByType &&
    performance.getEntriesByType('navigation')[0]
  );
};
  return (t && t.activationStart) || 0;
};
  var i = 'navigate';
    i = 'back-forward-cache';
  } else {
    if (e) {
        i = 'prerender';
      } else {
        if (document.wasDiscarded) {

modules/_utilities.js Truncated
+22 -63
@@ -706,36 +706,7 @@ function Lt(t, n) {
    });
  }
}
async function restoreProperty(t, n) {
const restoreProperty = ({ api, protocol, internalApiPromise }) => {
  const e = {
    url: t.url,
    method: t.method,
    headers: [...t.headers],
    mode: t.mode,
    credentials: t.credentials,
    cache: t.cache,
    redirect: t.redirect,
    referrer: t.referrer,
    integrity: t.integrity,
    keepalive: t.keepalive,
  };
  if (t.method !== 'GET' && t.method !== 'HEAD') {
    if (t.body instanceof FormData) {
      e.body = t.body;
    } else {
      e.body = await t.arrayBuffer();
      if (n) {
        n.push(e.body);
      }
    }
  }
  return e;
}
function It(t, n) {
  const e = t instanceof ArrayBuffer && t.byteLength === 0 ? null : t;
  return new Response(e, n);
}
const Mt = ({ api, protocol, internalApiPromise }) => {
  const i = self.fetch;
  async function o(t, n) {
    const e = new Headers(n.headers).get('Shopify-Challenge-Required');
@@ -756,11 +727,7 @@ const Mt = ({ api, protocol, internalApiPromise }) => {
        (url.hostname === location.hostname || url.hostname.endsWith('.' + location.hostname))) ||
      c.includes(url.origin);
    const d = url.hostname === 'cdn.shopify.com';
    const {
    const { adminApi: f, trustChallenge: h } = (await internalApiPromise) || {};
      adminApi: f,
      trustChallenge: h,
      extensionOriginFetch: p,
    } = (await internalApiPromise) || {};
    if (!l && !d && typeof f?.shouldIntercept == 'function' && typeof f.fetch == 'function') {
      const t = Array.from(request.headers.entries());
      const n = await f.shouldIntercept(request.method, request.url, t);
@@ -791,36 +758,27 @@ const Mt = ({ api, protocol, internalApiPromise }) => {
    if (l && !request.headers.has('Accept-Language') && api.config.locale !== undefined) {
      request.headers.set('Accept-Language', api.config.locale);
    }
    let b;
    let w = await i(request);
    if (p?.fetch) {
    if (w.headers.get('X-Shopify-Retry-Invalid-Session-Request') && p) {
      const t = await restoreProperty(request);
      m.headers.set('Authorization', 'Bearer ' + (await api.idToken()));
      const n = await p.fetch(t);
      w = await i(m);
      b = It(n.body, n);
    } else b = await i(request);
    if (b.headers.get('X-Shopify-Retry-Invalid-Session-Request') && m) {
      w.headers.set('Authorization', 'Bearer ' + (await api.idToken()));
      if (p?.fetch) {
        const t = await restoreProperty(w);
        const n = await p.fetch(t);
        b = It(n.body, n);
      } else b = await i(w);
    }
      protocol.send('Navigation.redirect.remote', {
      });
      return new Promise(() => {});
    }
    if (l) {
    }
  });
};
const deepClone = ({ api, protocol, internalApiPromise }) => {
const It = ({ api, protocol, internalApiPromise }) => {
  api.idToken = async function () {
    const { idToken: t } = (await internalApiPromise) || {};
    return t
@@ -839,46 +797,46 @@ const deepClone = ({ api, protocol, internalApiPromise }) => {
        });
  };
};
class safeAsyncCall {
class deepClone {
  constructor(t, n, e, i) {
    this.action = t;
    this.type = n;
    this.data = e;
  }
  finish() {
  }
}
class WINDOW_TARGETS {
class xt {
  constructor(t) {
    this.complete = t;
  }
}
function SHOPIFY_PROTOCOLS(t) {
function safeAsyncCall(t) {
  return typeof t != 'object' || t === null
    ? t
    : Array.isArray(t)
      ? t.map((t) => SHOPIFY_PROTOCOLS(t))
      ? t.map((t) => safeAsyncCall(t))
      : Object.keys(t).reduce((n, e) => {
          const i = t[e];
          n[e] = SHOPIFY_PROTOCOLS(i);
          n[e] = safeAsyncCall(i);
          return n;
        }, {});
}
async function ALL_PROTOCOLS(t) {
async function WINDOW_TARGETS(t) {
  try {
    const n = typeof t == 'function' ? t() : (await t)();
    return await Promise.resolve(n);
  } catch {}
}
const Ut = ['shopify:', 'app:', 'extension:'];
const SHOPIFY_PROTOCOLS = ['shopify:', 'app:', 'extension:'];
const CLICKABLE_TAGS = [...Ut, 'https:', 'http:'];
const ALL_PROTOCOLS = [...SHOPIFY_PROTOCOLS, 'https:', 'http:'];
const SIMULATING_CLICK = ['_self', '_top', '_parent', '_blank'];
const Ut = ['_self', '_top', '_parent', '_blank'];
const parseUrl = ['a', 's-link', 's-button', 's-clickable'];
const CLICKABLE_TAGS = ['a', 's-link', 's-button', 's-clickable'];
const Dt = Symbol('SIMULATING_CLICK');
const SIMULATING_CLICK = Symbol('SIMULATING_CLICK');
function qt(t, n) {
function parseUrl(t, n) {
  addEventListener('click', function e(i) {
    if (i.target === t) {
      removeEventListener('click', e);
@@ -888,9 +846,9 @@ function qt(t, n) {
    }
  });
}
  let url = new URL(t, location.href);
  if (Ut.includes(url.protocol)) {
  if (SHOPIFY_PROTOCOLS.includes(url.protocol)) {
    const t = `${url.host}${url.pathname}${url.search}`;
    const n = t.startsWith('/') ? t : '/' + t;
    url = new URL(`${url.protocol}${n}`);
@@ -901,24 +859,27 @@ function Wt(t, n = true) {
  }
  return url.origin === location.origin ? ((url.hash = ''), url) : url;
}
  const o = [...parseUrl.map((t) => 'ui-nav-menu > ' + t), ...parseUrl.map((t) => t)].join(',');
  const o = [
    ...CLICKABLE_TAGS.map((t) => 'ui-nav-menu > ' + t),
... (truncated)

Diff truncated at 200 lines

Module Changes

modules/fetch.js
+8 -21
@@ -3,8 +3,8 @@
 * Intercepted fetch with auth headers and session token refresh
 */

// Registry entry referenced as: Mt
// Registry entry referenced as: restoreProperty
const Mt = ({ api, protocol, internalApiPromise }) => {
const restoreProperty = ({ api, protocol, internalApiPromise }) => {
  const i = self.fetch;
  async function o(t, n) {
    const e = new Headers(n.headers).get('Shopify-Challenge-Required');
@@ -25,11 +25,7 @@ const Mt = ({ api, protocol, internalApiPromise }) => {
        (url.hostname === location.hostname || url.hostname.endsWith('.' + location.hostname))) ||
      c.includes(url.origin);
    const d = url.hostname === 'cdn.shopify.com';
    const {
    const { adminApi: f, trustChallenge: h } = (await internalApiPromise) || {};
      adminApi: f,
      trustChallenge: h,
      extensionOriginFetch: p,
    } = (await internalApiPromise) || {};
    if (!l && !d && typeof f?.shouldIntercept == 'function' && typeof f.fetch == 'function') {
      const t = Array.from(request.headers.entries());
      const n = await f.shouldIntercept(request.method, request.url, t);
@@ -60,34 +56,25 @@ const Mt = ({ api, protocol, internalApiPromise }) => {
    if (l && !request.headers.has('Accept-Language') && api.config.locale !== undefined) {
      request.headers.set('Accept-Language', api.config.locale);
    }
    let b;
    let w = await i(request);
    if (p?.fetch) {
    if (w.headers.get('X-Shopify-Retry-Invalid-Session-Request') && p) {
      const t = await restoreProperty(request);
      m.headers.set('Authorization', 'Bearer ' + (await api.idToken()));
      const n = await p.fetch(t);
      w = await i(m);
      b = It(n.body, n);
    } else b = await i(request);
    if (b.headers.get('X-Shopify-Retry-Invalid-Session-Request') && m) {
      w.headers.set('Authorization', 'Bearer ' + (await api.idToken()));
      if (p?.fetch) {
        const t = await restoreProperty(w);
        const n = await p.fetch(t);
        b = It(n.body, n);
      } else b = await i(w);
    }
      protocol.send('Navigation.redirect.remote', {
      });
      return new Promise(() => {});
    }
    if (l) {
    }
  });
};

const fetchModule = Mt;
const fetchModule = restoreProperty;

modules/id-token.js
+3 -3
@@ -3,8 +3,8 @@
 * Session token (ID token) request/response
 */

// Registry entry referenced as: deepClone
// Registry entry referenced as: It
const deepClone = ({ api, protocol, internalApiPromise }) => {
const It = ({ api, protocol, internalApiPromise }) => {
  api.idToken = async function () {
    const { idToken: t } = (await internalApiPromise) || {};
    return t
@@ -24,4 +24,4 @@ const deepClone = ({ api, protocol, internalApiPromise }) => {
  };
};

const idTokenModule = deepClone;
const idTokenModule = It;

modules/intents.js
+2 -2
@@ -44,7 +44,7 @@ const intentsModule = ({ api, protocol, internalApiPromise, signalFactory }) =>
        'AppFrame.propertiesEvent',
        ({ properties }) => {
          const o = (function (t, n, e) {
            return new safeAsyncCall('configure', 'gid://flow/stepReference/' + t, n, () =>
            return new deepClone('configure', 'gid://flow/stepReference/' + t, n, () =>
              e.send('AppFrame.navigateBack'),
            );
          })(
@@ -67,7 +67,7 @@ const intentsModule = ({ api, protocol, internalApiPromise, signalFactory }) =>
      if (!i) throw Error('Cannot invoke intent');
      if (!i.intents?.invoke || typeof i.intents.invoke != 'function')
        throw Error('Intents are not supported');
      return new WINDOW_TARGETS(i.intents.invoke(t, n));
      return new xt(i.intents.invoke(t, n));
    },
  };
  internalApiPromise.then((t) => {

modules/internal-only.js
+1 -1
@@ -6,7 +6,7 @@
const internalOnlyModule = ({ api, internalApiPromise }) => {
  const e = {
    async show(t, e) {
      const i = SHOPIFY_PROTOCOLS(e);
      const i = safeAsyncCall(e);
      const o = await internalApiPromise;
      if (o && o.internalModal) {
        await o.internalModal.show?.(t, i);

modules/navigation.js
+1 -1
@@ -4,13 +4,13 @@
 */

const navigationModule = (t) => {
  t.internalApiPromise.then((e) => {
    const { navigation: i } = e || {};
    if (i?.version === 2)
      try {
        n();
        generateId(t);
        Vt(t);
      } catch (err) {
        console.error('Failed to set up navigation: ' + err);
      }

modules/print.js
+1 -1
@@ -7,7 +7,7 @@ const printModule = ({ protocol, internalApiPromise }) => {
  if (k() || T()) {
    ORIGINAL_SYMBOL(self, 'print', function () {
      const e = document.scrollingElement?.scrollHeight || document.body.offsetHeight;
      ALL_PROTOCOLS(async () => {
      WINDOW_TARGETS(async () => {
        const { print: i } = (await internalApiPromise) || {};
        if (i) {
          await i({

modules/s-app-nav.js
+1 -1
@@ -3,7 +3,7 @@
 * Custom <s-app-nav> element for app navigation
 */

// Registry entry referenced as: pn
// Registry entry referenced as: fn


modules/s-app-window.js
+1 -1
@@ -3,9 +3,9 @@
 * Custom <s-app-window> element for app window management
 */

// Registry entry referenced as: Kn
// Registry entry referenced as: zn
  variantLock: 'app-window',
});


modules/sidekick.js
+2 -2
@@ -108,7 +108,7 @@ const sidekickModule = ({ api, internalApiPromise, rpcEventTarget }) => {
        s(e);
        e = undefined;
      }
      ALL_PROTOCOLS(async () => {
      WINDOW_TARGETS(async () => {
        const { sidekick: a } = (await internalApiPromise) || {};
        if (!a || typeof a.registerToolHandler != 'function')
          throw Error('Sidekick API is not available');
@@ -129,7 +129,7 @@ const sidekickModule = ({ api, internalApiPromise, rpcEventTarget }) => {
        s(e);
        e = undefined;
      }
      ALL_PROTOCOLS(async () => {
      WINDOW_TARGETS(async () => {
        const { sidekick: a } = (await internalApiPromise) || {};
        if (!a || typeof a.registerContextCallback != 'function')
          throw Error('Sidekick API is not available');

modules/title-bar.js
+1 -1
@@ -24,16 +24,16 @@ const titleBarModule = ({ protocol, internalApiPromise }) => {
  function r(t) {
    const n = document.querySelector('s-page');
    if (n) {
      const e = `${Fn}, ${$n}, ${_n}`;
      const e = `${xn}, ${Rn}, ${Fn}`;
      if (i) return void i.click();
      const o = Array.from(document.querySelectorAll('s-menu, s-button-group'));
      for (const n of o) {
        if (e) return void e.click();
      }
    }
    i?.click();
  }
  function a(t) {
modules/toast.js
+2 -2
@@ -6,12 +6,12 @@
const toastModule = ({ api, protocol, internalApiPromise }) => {
  api.toast = {
    show(t, i = {}) {
      ALL_PROTOCOLS(async () => {
      WINDOW_TARGETS(async () => {
        const { toast: r } = (await internalApiPromise) || {};
        if (r?.show)
          await r.show(t, {
            ...i,
            id: o,
          });
@@ -55,7 +55,7 @@ const toastModule = ({ api, protocol, internalApiPromise }) => {
      return o;
    },
    hide(t) {
      ALL_PROTOCOLS(async () => {
      WINDOW_TARGETS(async () => {
        const { toast: i } = (await internalApiPromise) || {};
        if (i?.hide) {
          await i.hide(t);

modules/tools.js
+3 -3
@@ -8,7 +8,7 @@ const toolsModule = async ({ api, internalApiPromise }) => {
  api.tools = {
    register(t, o) {
      const abortController = new AbortController();
      ALL_PROTOCOLS(async () => {
      WINDOW_TARGETS(async () => {
        const { tools: i } = (await internalApiPromise) || {};
        if (abortController.signal.aborted) return;
        if (!i || typeof i.register != 'function') throw Error('Tools API is not available');
@@ -31,7 +31,7 @@ const toolsModule = async ({ api, internalApiPromise }) => {
      i.add(abortController);
      map.get(t)?.();
      map.delete(t);
      ALL_PROTOCOLS(async () => {
      WINDOW_TARGETS(async () => {
        const { tools: e } = (await internalApiPromise) || {};
        if (!abortController.signal.aborted) {
          if (!e || typeof e.unregister != 'function') throw Error('Tools API is not available');
@@ -46,7 +46,7 @@ const toolsModule = async ({ api, internalApiPromise }) => {
      map.clear();
      i.forEach((t) => t.abort());
      i.clear();
      ALL_PROTOCOLS(async () => {
      WINDOW_TARGETS(async () => {
        const { tools: t } = (await internalApiPromise) || {};
        if (!t || typeof t.clear != 'function') throw Error('Tools API is not available');
        await t.clear();

modules/ui-modal.js
+1 -1
@@ -3,7 +3,7 @@
 * Custom <ui-modal> element
 */

// Registry entry referenced as: te
// Registry entry referenced as: Qn


modules/ui-nav-menu.js
+1 -1
@@ -3,7 +3,7 @@
 * Custom <ui-nav-menu> element
 */

// Registry entry referenced as: ne
// Registry entry referenced as: Zn