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.
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