All entries

May 27, 2026

fd311eaee67f493ca75792c076d3327ec6b3de5fa16a152f064b5a5bdf9a0167
Previous: af2eb2f3 Bundle: 89.8 KB

Added idToken timeout and error hardening, sidekick conversation stream events, and title bar button pressed state.

Highlights

  • The id-token module now enforces a 60-second timeout when requesting session tokens from the host, rejecting with a clear error if the host does not respond in time.
  • The id-token module now validates that retrieved tokens are non-empty strings, throwing an idToken unavailable error for missing or invalid tokens.
  • The fetch module now handles idToken failures gracefully, logging an error and skipping the Authorization header instead of propagating the exception.
  • The sidekick module now forwards shopify:sidekick:active-conversation-stream-event events to the global window, enabling apps to listen for active conversation stream updates.
  • Title bar buttons now support a pressed property, allowing buttons to reflect a toggled or active visual state.
21 files changed +107 -71

Infrastructure Changes

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

Generated: 2026-05-14T08:53:17.664Z
Generated: 2026-05-27T09:03:22.529Z

## Files

@@ -8,37 +8,37 @@ Generated: 2026-05-14T08:53:17.664Z
|------|------|-------|------|
| _bootstrap.js | 29.2KB | 1013 | Infrastructure |
| _remote-ui.js | 6.0KB | 255 | Infrastructure |
| _utilities.js | 71.4KB | 2668 | Infrastructure |
| _utilities.js | 72.3KB | 2704 | 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 | 2.8KB | 81 | Module |
| fetch.js | 3.0KB | 94 | Module |
| id-token.js | 640B | 28 | Module |
| id-token.js | 908B | 34 | Module |
| index.js | 2.2KB | 48 | Index |
| intents.js | 3.1KB | 106 | Module |
| internal-only.js | 539B | 26 | Module |
| internal-only.js | 528B | 26 | Module |
| loading.js | 605B | 31 | Module |
| navigation.js | 408B | 19 | Module |
| navigation.js | 416B | 19 | Module |
| picker.js | 451B | 18 | Module |
| polaris.js | 240B | 12 | Module |
| pos.js | 5.7KB | 248 | Module |
| print.js | 570B | 25 | Module |
| print.js | 573B | 25 | Module |
| resource-picker.js | 2.8KB | 119 | Module |
| reviews.js | 365B | 16 | Module |
| s-app-nav.js | 175B | 10 | Module |
| s-app-window.js | 228B | 12 | Module |
| save-bar.js | 3.4KB | 138 | Module |
| scanner.js | 2.9KB | 101 | Module |
| scanner.js | 2.8KB | 101 | Module |
| scopes.js | 797B | 26 | Module |
| share.js | 1.3KB | 58 | Module |
| shopifyQL.js | 231B | 12 | Module |
| shortcut.js | 461B | 24 | Module |
| sidekick.js | 4.4KB | 147 | Module |
| sidekick.js | 4.7KB | 154 | Module |
| support.js | 508B | 21 | Module |
| telemetry.js | 813B | 30 | Module |
| title-bar.js | 7.5KB | 284 | Module |
| title-bar.js | 7.5KB | 287 | Module |
| toast.js | 1.6KB | 71 | Module |
| tools.js | 1.6KB | 57 | Module |
| ui-modal.js | 153B | 10 | Module |
@@ -46,7 +46,7 @@ Generated: 2026-05-14T08:53:17.664Z
| user.js | 940B | 37 | Module |
| visibility.js | 973B | 34 | Module |
| web-vitals.js | 1.8KB | 64 | Module |
| **Total** | **169.5KB** | **6441** | |
| **Total** | **171.2KB** | **6506** | |

## Pipeline Stages


modules/_bootstrap.js Truncated
+5 -5
@@ -168,7 +168,7 @@
          removeEventListener: globalThis.removeEventListener.bind(globalThis),
          postMessage: globalThis.parent.postMessage.bind(globalThis.parent),
        });
  const c = interceptProperty();
  const c = Tt();
  const l = (function (t) {
    const n = t.decodeSignal;
    return (t) => {
@@ -410,10 +410,10 @@
    value: b,
  });
  const y = new et();
  const v = $();
  const v = subscribeAllErrors();
    return (function (t, n) {
      const url = new URL(location.pathname, location.origin);
      t.forEach((t, n) => {
@@ -434,7 +434,7 @@
      I(
        {
          idToken: It,
          fetch: restoreProperty,
          fetch: Lt,
        },
        [],
      );
@@ -467,25 +467,25 @@
      })(n.get('shopify-reload'));
    }
  }
    v.resolve(undefined);
    return void (async function () {
      const t = window.name.endsWith('/src');
      const n = Wn(`frame:${S.apiKey}/main`) || Wn(C);
      const n = zn(`frame:${L.apiKey}/main`) || zn(S);
      if (n) {
        window.opener = n;
        window.fetch = n.fetch;
        window.shopify = n.shopify;
        window.polaris = n.polaris;
        const t = window.open;
        ORIGINAL_SYMBOL(self, 'open', function (n, e, i) {
        restoreProperty(self, 'open', function (n, e, i) {
          return n != null && Dt(n).protocol !== 'https:'
            ? window.opener.open(n, e, i)
            : t.call(this, n, e, i);
        });
      }
      function e() {
        const t = createDeferred();
        window.top?.postMessage(
          {

Diff truncated at 200 lines

modules/_utilities.js Truncated
+23 -16
@@ -4,10 +4,10 @@
 *           error types, DOM validation framework, navigation menu base
 */

function h() {
  return `${p()}-${p()}-${p()}-${p()}`;
}
function p() {
  return `${h()}-${h()}-${h()}-${h()}`;
}
function h() {
  return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16);
}
function m(t) {
@@ -101,12 +101,12 @@ const R = [
  'UnexpectedAction',
  'UnsupportedOperation',
];
function subscribeAllErrors(t, n, e) {
function $(t, n, e) {
    t.subscribe('Error.' + i, n, e);
  });
}
function $() {
function subscribeAllErrors() {
  let t;
  let n = false;
  const promise = new Promise((n) => {
@@ -134,7 +134,7 @@ function _() {
    },
    has: (t) => !!n[t],
    add(e) {
      const i = $();
      const i = subscribeAllErrors();
      n[e] = i;
      t = t.then(() => i.promise);
    },
@@ -567,7 +567,7 @@ var createPrivateKey = 0;
function checkPrivateField(t) {
  return `__private_${createPrivateKey++}_${t}`;
}
function interceptProperty(t = false) {
function Tt(t = false) {
  let n = null;
  let e = t ? null : new Set();
  var i = checkPrivateField('value');
@@ -682,11 +682,11 @@ function interceptProperty(t = false) {
    },
  };
}
interceptProperty(true);
Tt(true);
const Tt = Symbol();
const interceptProperty = Symbol();
function ORIGINAL_SYMBOL(t, n, e) {
function restoreProperty(t, n, e) {
  const i = t[n];
  e[Tt] = i;
  e[interceptProperty] = i;
  Object.defineProperty(t, n, {
    enumerable: true,
    configurable: true,
@@ -695,8 +695,8 @@ function ORIGINAL_SYMBOL(t, n, e) {
  });
  return i;
}
function Lt(t, n) {
function ORIGINAL_SYMBOL(t, n) {
  const e = t[n][Tt];
  const e = t[n][interceptProperty];
  if (e) {
    Object.defineProperty(t, n, {
      enumerable: true,
@@ -706,9 +706,16 @@ function Lt(t, n) {
    });
  }
}
const restoreProperty = ({ api, protocol, internalApiPromise }) => {
const Lt = ({ api, protocol, internalApiPromise }) => {
  const i = self.fetch;
  async function o(t, n) {
  async function o() {
    try {
      return await api.idToken();
    } catch (err) {
      return void console.error('Failed to fetch an idToken', err);
    }
  }
  async function r(t, n) {
    const e = new Headers(n.headers).get('Shopify-Challenge-Required');
    return e && t?.isChallengeUrl && (await t.isChallengeUrl(e)) && t?.startChallenge
      ? {
@@ -718,19 +725,19 @@ const restoreProperty = ({ api, protocol, internalApiPromise }) => {
          verified: false,
        };
  }
  ORIGINAL_SYMBOL(globalThis, 'fetch', async function (r, a) {
  restoreProperty(globalThis, 'fetch', async function (a, s) {
... (truncated)

Diff truncated at 200 lines

Module Changes

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

// Registry entry referenced as: restoreProperty
// Registry entry referenced as: Lt
const restoreProperty = ({ api, protocol, internalApiPromise }) => {
const Lt = ({ api, protocol, internalApiPromise }) => {
  const i = self.fetch;
  async function o(t, n) {
  async function o() {
    try {
      return await api.idToken();
    } catch (err) {
      return void console.error('Failed to fetch an idToken', err);
    }
  }
  async function r(t, n) {
    const e = new Headers(n.headers).get('Shopify-Challenge-Required');
    return e && t?.isChallengeUrl && (await t.isChallengeUrl(e)) && t?.startChallenge
      ? {
@@ -16,19 +23,19 @@ const restoreProperty = ({ api, protocol, internalApiPromise }) => {
          verified: false,
        };
  }
  ORIGINAL_SYMBOL(globalThis, 'fetch', async function (r, a) {
  restoreProperty(globalThis, 'fetch', async function (a, s) {
    const url = new URL(request.url);
      (url.protocol === location.protocol &&
        (url.hostname === location.hostname || url.hostname.endsWith('.' + location.hostname))) ||
      const t = Array.from(request.headers.entries());
      if (n?.intercept) {
        const n = {
          method: request.method,
@@ -36,45 +43,51 @@ const restoreProperty = ({ api, protocol, internalApiPromise }) => {
          headers: t,
          body: (await request.text()) ?? undefined,
        };
        if (!h) return new Response(e.body, e);
        if (i) {
          return new Response(t.body, t);
        }
        return new Response(e.body, e);
      }
    }
      request.headers.set('Authorization', 'Bearer ' + (await api.idToken()));
      const t = await o();
      if (t) {
        request.headers.set('Authorization', 'Bearer ' + t);
      }
    }
      request.headers.set('X-Requested-With', 'XMLHttpRequest');
    }
      request.headers.set('Accept-Language', api.config.locale);
    }
      m.headers.set('Authorization', 'Bearer ' + (await api.idToken()));
      const t = await o();
      w = await i(m);
      if (t) {
        w.headers.set('Authorization', 'Bearer ' + t);
        b = await i(w);
      }
    }
      protocol.send('Navigation.redirect.remote', {
      });
      return new Promise(() => {});
    }
    }
  });
};

const fetchModule = restoreProperty;
const fetchModule = Lt;

modules/id-token.js
+8 -2
@@ -7,12 +7,16 @@
const It = ({ api, protocol, internalApiPromise }) => {
  api.idToken = async function () {
    const { idToken: t } = (await internalApiPromise) || {};
    return t
    const i = t
      ? await t()
      : new Promise((t) => {
      : await new Promise((t, e) => {
          const i = setTimeout(() => {
            e(Error('idToken unavailable: host did not respond in time'));
          }, 6e4);
          protocol.subscribe(
            'SessionToken.respond',
            ({ sessionToken }) => {
              clearTimeout(i);
              t(sessionToken);
            },
            {
@@ -21,6 +25,8 @@ const It = ({ api, protocol, internalApiPromise }) => {
          );
          protocol.send('SessionToken.request');
        });
    if (typeof i != 'string' || i === '') throw Error('idToken unavailable');
    return i;
  };
};


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 deepClone('configure', 'gid://flow/stepReference/' + t, n, () =>
            return new Mt('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 xt(i.intents.invoke(t, n));
      return new safeAsyncCall(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 = safeAsyncCall(e);
      const i = xt(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) => {
  const n = zt(t);
  const n = generateId(t);
  t.internalApiPromise.then((e) => {
    const { navigation: i } = e || {};
    if (i?.version === 2)
      try {
        n();
      } catch (err) {
        console.error('Failed to set up navigation: ' + err);
      }

modules/pos.js
+4 -4
@@ -6,9 +6,9 @@
const posModule = ({ api, protocol }) => {
  const e = new Set();
  async function i(t, i) {
    const abortController = new AbortController();
    const a = $();
    const a = subscribeAllErrors();
    protocol.subscribe(
      'Cart.update',
      ({ data }) => {
@@ -30,9 +30,9 @@ const posModule = ({ api, protocol }) => {
  const o = {
    cart: {
      async fetch() {
        const abortController = new AbortController();
        const i = $();
        const i = subscribeAllErrors();
        protocol.send('Cart.fetch', {
          id: t,
        });
@@ -197,7 +197,7 @@ const posModule = ({ api, protocol }) => {
      protocol.send('Pos.close');
    },
    async device() {
      const t = $();
      const t = subscribeAllErrors();
      protocol.subscribe(
        'getState',
        ({ pos }) => {
@@ -215,7 +215,7 @@ const posModule = ({ api, protocol }) => {
      return t.promise;
    },
    async location() {
      const t = $();
      const t = subscribeAllErrors();
      protocol.subscribe(
        'getState',
        ({ pos }) => {

modules/print.js
+2 -2
@@ -4,10 +4,10 @@
 */

const printModule = ({ protocol, internalApiPromise }) => {
    ORIGINAL_SYMBOL(self, 'print', function () {
    restoreProperty(self, 'print', function () {
      const e = document.scrollingElement?.scrollHeight || document.body.offsetHeight;
      WINDOW_TARGETS(async () => {
      SHOPIFY_PROTOCOLS(async () => {
        const { print: i } = (await internalApiPromise) || {};
        if (i) {
          await i({

modules/resource-picker.js
+1 -1
@@ -77,10 +77,10 @@ const resourcePickerModule = ({ api, protocol, internalApiPromise }) => {
          },
          {
            id: a,
          },
        );
        subscribeAllErrors(
        $(
          protocol,
          (t) => {
            m();
modules/s-app-nav.js
+1 -1
@@ -3,7 +3,7 @@
 * Custom <s-app-nav> element for app navigation
 */

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


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

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


modules/scanner.js
+2 -2
@@ -22,7 +22,7 @@ const scannerModule = ({ api, protocol, internalApiPromise }) => {
              );
            }
            function s() {
              subscribeAllErrors(protocol, a, {
              $(protocol, a, {
                signal: r,
                id: i,
              });
@@ -50,14 +50,14 @@ const scannerModule = ({ api, protocol, internalApiPromise }) => {
            protocol.subscribe(
              'getState',
              ({ features }) => {
                  s();
                } else {
                  (function () {
                    const e = new AbortController();
                    abortController.signal.addEventListener('abort', () => e.abort());
                    subscribeAllErrors(
                    $(
                      protocol,
                      (t) => {
                        e.abort();
modules/share.js
+2 -2
@@ -4,15 +4,15 @@
 */

const shareModule = ({ protocol, internalApiPromise }) => {
  const e = navigator.share;
  ORIGINAL_SYMBOL(navigator, 'share', async function (i) {
  restoreProperty(navigator, 'share', async function (i) {
    if (!i) return e.call(navigator, i);
    const { share: o } = (await internalApiPromise) || {};
    const { title: r, text: a, url: s } = i;
    if (!o)
      return new Promise((n, e) => {
        const abortController = new AbortController();
        const { signal: c } = abortController;
        function u(t) {
@@ -23,7 +23,7 @@ const shareModule = ({ protocol, internalApiPromise }) => {
            }),
          );
        }
        subscribeAllErrors(protocol, u, {
        $(protocol, u, {
          signal: c,
          id: i,
        });

modules/sidekick.js
+9 -2
@@ -28,6 +28,13 @@ const sidekickModule = ({ api, internalApiPromise, rpcEventTarget }) => {
          }),
        );
      });
      rpcEventTarget.addEventListener('shopify:sidekick:active-conversation-stream-event', (t) => {
        globalThis.dispatchEvent(
          new CustomEvent('shopify:sidekick:active-conversation-stream-event', {
            detail: t.detail,
          }),
        );
      });
    }
  });
  const c = {
@@ -108,7 +115,7 @@ const sidekickModule = ({ api, internalApiPromise, rpcEventTarget }) => {
        s(e);
        e = undefined;
      }
      WINDOW_TARGETS(async () => {
      SHOPIFY_PROTOCOLS(async () => {
        const { sidekick: a } = (await internalApiPromise) || {};
        if (!a || typeof a.registerToolHandler != 'function')
          throw Error('Sidekick API is not available');
@@ -129,7 +136,7 @@ const sidekickModule = ({ api, internalApiPromise, rpcEventTarget }) => {
        s(e);
        e = undefined;
      }
      WINDOW_TARGETS(async () => {
      SHOPIFY_PROTOCOLS(async () => {
        const { sidekick: a } = (await internalApiPromise) || {};
        if (!a || typeof a.registerContextCallback != 'function')
          throw Error('Sidekick API is not available');

modules/title-bar.js
+6 -3
@@ -24,20 +24,20 @@ const titleBarModule = ({ protocol, internalApiPromise }) => {
  function r(t) {
    const n = document.querySelector('s-page');
    if (n) {
      const e = `${xn}, ${Rn}, ${Fn}`;
      const e = `${Fn}, ${Un}, ${_n}`;
      if (i) return void i.click();
      const o = Array.from(document.querySelectorAll('s-menu, s-button-group'));
      for (const n of o) {
        const e = Array.from(n.querySelectorAll('s-button')).find((n) => n[An] == t);
        const e = Array.from(n.querySelectorAll(vn)).find((n) => n[Tn] == t);
        if (e) return void e.click();
      }
    }
    i?.click();
  }
  function a(t) {
    const { id: n, label: e, icon: i, tone: o, disabled: a, loading: s } = t;
    const { id: n, label: e, icon: i, tone: o, disabled: a, loading: s, pressed: c } = t;
    return {
      label: e,
      ...(i && {
@@ -46,6 +46,9 @@ const titleBarModule = ({ protocol, internalApiPromise }) => {
      ...(o && {
        tone: o,
      }),
      ...(c !== undefined && {
        pressed: c,
      }),
      disabled: a,
      loading: s,
      onAction: () => r?.(n),
modules/toast.js
+2 -2
@@ -6,12 +6,12 @@
const toastModule = ({ api, protocol, internalApiPromise }) => {
  api.toast = {
    show(t, i = {}) {
      WINDOW_TARGETS(async () => {
      SHOPIFY_PROTOCOLS(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) {
      WINDOW_TARGETS(async () => {
      SHOPIFY_PROTOCOLS(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();
      WINDOW_TARGETS(async () => {
      SHOPIFY_PROTOCOLS(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);
      WINDOW_TARGETS(async () => {
      SHOPIFY_PROTOCOLS(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();
      WINDOW_TARGETS(async () => {
      SHOPIFY_PROTOCOLS(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: Qn
// Registry entry referenced as: ne


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

// Registry entry referenced as: Zn
// Registry entry referenced as: ee