From dec7d1fd968de59b8de4bc0525cbf7d2b55ce83c Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Thu, 5 Nov 2026 20:07:05 -0800 Subject: [PATCH] Load the app browser embed's script from beside the GUI bundle (#4511) The embed page always loaded /dist/apps-embed.min.js from the server, while the GUI bundle and the embed build are published to gui_bundle's location (a CDN in production). The server's /dist copy can be stale, so the embed ran an old build. It now loads apps-embed.min.js from gui_bundle's directory, keeping /dist when gui_bundle isn't set. --- .../homepage/PuterHomepageService.test.ts | 15 +++++++++++++++ .../services/homepage/PuterHomepageService.ts | 19 ++++++++++++++++++- src/backend/types.ts | 15 +++++++-------- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/backend/services/homepage/PuterHomepageService.test.ts b/src/backend/services/homepage/PuterHomepageService.test.ts index 8a117a8cc..583561ff7 100644 --- a/src/backend/services/homepage/PuterHomepageService.test.ts +++ b/src/backend/services/homepage/PuterHomepageService.test.ts @@ -70,6 +70,21 @@ describe('app browser embed shell', () => { expect(html).not.toContain('gui('); }); + it.each([ + [{}, '/dist/apps-embed.min.js'], + [ + { gui_bundle: 'https://gui-cdn.deployment.test/bundle.min.js' }, + 'https://gui-cdn.deployment.test/apps-embed.min.js', + ], + [ + { gui_bundle: '/static/bundle.min.js?v=42' }, + '/static/apps-embed.min.js?v=42', + ], + ])('loads its script from beside the GUI bundle %j', (config, script) => { + const html = makeService(config).renderAppBrowserEmbed(); + expect(html).toContain(``); + }); + it('escapes configured values without breaking its JSON bootstrap', () => { const sdkUrl = '/sdk.js?value='; const html = makeService({ diff --git a/src/backend/services/homepage/PuterHomepageService.ts b/src/backend/services/homepage/PuterHomepageService.ts index 81587f058..39791afaf 100644 --- a/src/backend/services/homepage/PuterHomepageService.ts +++ b/src/backend/services/homepage/PuterHomepageService.ts @@ -191,11 +191,28 @@ export class PuterHomepageService extends PuterService {
- + `; } + /** + * The embed's script is built beside the GUI bundle, so it is served from + * wherever `gui_bundle` is (a CDN in production), with the same query. + */ + #appBrowserEmbedScript(): string { + const base = 'http://relative.invalid'; + const bundle = new URL( + this.config.gui_bundle ?? '/dist/bundle.min.js', + base, + ); + const script = new URL('apps-embed.min.js', bundle); + script.search = bundle.search; + return script.origin === base + ? `${script.pathname}${script.search}` + : script.href; + } + /** `scriptPath` is a same-origin absolute path, like `/ext/push/worker.js`. */ registerServiceWorkerScript(scriptPath: string): void { if ( diff --git a/src/backend/types.ts b/src/backend/types.ts index 4a3ac5514..4b14754fa 100644 --- a/src/backend/types.ts +++ b/src/backend/types.ts @@ -256,12 +256,7 @@ export interface IPreludeConfig { * an RCS agent provisioned in the Prelude account to actually use RCS. */ preferredChannel?: - | 'sms' - | 'rcs' - | 'whatsapp' - | 'viber' - | 'zalo' - | 'telegram'; + 'sms' | 'rcs' | 'whatsapp' | 'viber' | 'zalo' | 'telegram'; } /** @@ -1106,7 +1101,10 @@ interface IConfigOptional { builtin_apps: Record; /** Force the bundled GUI even in dev. Default: false. */ use_bundled_gui: boolean; - /** Override the GUI bundle JS path. Default: `/dist/bundle.min.js`. */ + /** + * Override the GUI bundle JS path. Default: `/dist/bundle.min.js`. The app + * browser embed loads `apps-embed.min.js` from the same directory. + */ gui_bundle: string; /** Override the GUI CSS path when bundled. Default: `/dist/bundle.min.css`. */ gui_css: string; @@ -1393,7 +1391,8 @@ export interface ServerDrainInfo { } export interface WithCostsReporting extends WithLifecycle { - getReportedCosts?: () => // eslint-disable-next-line @typescript-eslint/no-explicit-any + getReportedCosts?: () => + // eslint-disable-next-line @typescript-eslint/no-explicit-any | Promise[]> // eslint-disable-next-line @typescript-eslint/no-explicit-any | Record[];