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