mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-13 18:34:51 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3275b1514 |
No files matched your search
@@ -104,6 +104,25 @@ class Lock {
|
||||
// (using defaultGUIOrigin breaks locally-hosted apps)
|
||||
const PROD_ORIGIN = 'https://puter.com';
|
||||
|
||||
// The desktops Puter itself serves. An app learns which one launched it from
|
||||
// the URL (`puter.origin`), and only one of these is believed: the GUI origin
|
||||
// is what every message from the desktop is checked against, so taking an
|
||||
// arbitrary one would let any page that frames an app drive it.
|
||||
const FIRST_PARTY_GUI_ORIGINS = new Set([
|
||||
'https://puter.com',
|
||||
'https://desktop.puter.com',
|
||||
]);
|
||||
|
||||
/** `value`'s origin when it is a first-party desktop, otherwise null. */
|
||||
const firstPartyGUIOrigin = (value) => {
|
||||
try {
|
||||
const { origin } = new URL(value);
|
||||
return FIRST_PARTY_GUI_ORIGINS.has(origin) ? origin : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// localStorage key for the auth token. The retired key below is never read —
|
||||
// only deleted, so a token from before the format change doesn't linger in a
|
||||
// visitor's browser (see `discardRetiredAuthToken_`).
|
||||
@@ -213,6 +232,9 @@ export class Puter {
|
||||
|
||||
#defaultAPIOrigin = 'https://api.puter.com';
|
||||
#defaultGUIOrigin = 'https://puter.com';
|
||||
// In app mode, the first-party desktop that launched this app, when it
|
||||
// isn't the build's default one.
|
||||
#launchGUIOrigin = null;
|
||||
|
||||
/** @returns {string} */
|
||||
get defaultAPIOrigin() {
|
||||
@@ -230,6 +252,7 @@ export class Puter {
|
||||
get defaultGUIOrigin() {
|
||||
return (
|
||||
globalThis.PUTER_ORIGIN ||
|
||||
this.#launchGUIOrigin ||
|
||||
globalThis.PUTER_ORIGIN_ENV ||
|
||||
this.#defaultGUIOrigin
|
||||
);
|
||||
@@ -634,6 +657,15 @@ export class Puter {
|
||||
this.APIOrigin = `https://api.${URLParams.get('puter.domain')}`;
|
||||
}
|
||||
|
||||
// An app launched by a first-party desktop other than puter.com
|
||||
// (e.g. desktop.puter.com) talks to that desktop: its messages come
|
||||
// from there, and replies meant only for it go there.
|
||||
if (this.env === 'app') {
|
||||
this.#launchGUIOrigin = firstPartyGUIOrigin(
|
||||
URLParams.get('puter.origin'),
|
||||
);
|
||||
}
|
||||
|
||||
// === START :: Logger ===
|
||||
|
||||
// Basic logger replacement (console-based)
|
||||
|
||||
@@ -240,6 +240,88 @@ describe('app-mode launch token', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('GUI origin of an app', () => {
|
||||
const DESKTOP = 'https://desktop.puter.com';
|
||||
const launchedBy = (origin) =>
|
||||
bootApp(
|
||||
`${LAUNCH}${APP_TOKEN}&puter.origin=${encodeURIComponent(origin)}`,
|
||||
);
|
||||
|
||||
/** Sends `data` to the app as its parent at `origin` would. */
|
||||
const fromParent = async (origin, data) => {
|
||||
window.dispatchEvent(
|
||||
new MessageEvent('message', {
|
||||
origin,
|
||||
source: globalThis.parent,
|
||||
data,
|
||||
}),
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
};
|
||||
|
||||
const closeAnswers = () =>
|
||||
globalThis.parent.postMessage.mock.calls
|
||||
.map(([message]) => message)
|
||||
.filter((message) => message?.original_msg_id === 'close-1');
|
||||
|
||||
it('is the first-party desktop that launched it', async () => {
|
||||
expect((await launchedBy(DESKTOP)).defaultGUIOrigin).toBe(DESKTOP);
|
||||
expect((await launchedBy('https://puter.com')).defaultGUIOrigin).toBe(
|
||||
'https://puter.com',
|
||||
);
|
||||
});
|
||||
|
||||
it('stays puter.com for any other launcher', async () => {
|
||||
for (const origin of [
|
||||
'https://evil.example',
|
||||
'https://desktop.puter.com.evil.example',
|
||||
'https://x.desktop.puter.com',
|
||||
'http://desktop.puter.com',
|
||||
'not a url',
|
||||
]) {
|
||||
expect((await launchedBy(origin)).defaultGUIOrigin).toBe(
|
||||
'https://puter.com',
|
||||
);
|
||||
}
|
||||
expect((await bootApp(`${LAUNCH}${APP_TOKEN}`)).defaultGUIOrigin).toBe(
|
||||
'https://puter.com',
|
||||
);
|
||||
});
|
||||
|
||||
it('yields to an explicit PUTER_ORIGIN', async () => {
|
||||
globalThis.PUTER_ORIGIN = 'https://self-hosted.test';
|
||||
try {
|
||||
expect((await launchedBy(DESKTOP)).defaultGUIOrigin).toBe(
|
||||
'https://self-hosted.test',
|
||||
);
|
||||
} finally {
|
||||
delete globalThis.PUTER_ORIGIN;
|
||||
}
|
||||
});
|
||||
|
||||
it('is not taken from the URL of a top-level page', async () => {
|
||||
const puter = await bootWeb(
|
||||
`?puter.app_instance_id=i&puter.origin=${encodeURIComponent(DESKTOP)}`,
|
||||
);
|
||||
expect(puter.env).not.toBe('app');
|
||||
expect(puter.defaultGUIOrigin).toBe('https://puter.com');
|
||||
});
|
||||
|
||||
it('answers windowWillClose from that desktop and no other', async () => {
|
||||
await launchedBy(DESKTOP);
|
||||
const willClose = { msg: 'windowWillClose', msg_id: 'close-1' };
|
||||
|
||||
await fromParent('https://evil.example', willClose);
|
||||
await fromParent('https://puter.com', willClose);
|
||||
expect(closeAnswers()).toEqual([]);
|
||||
|
||||
await fromParent(DESKTOP, willClose);
|
||||
expect(closeAnswers()).toEqual([
|
||||
expect.objectContaining({ msg: true, original_msg_id: 'close-1' }),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('token adoption on a top-level page', () => {
|
||||
it('takes a token from a window it opened, and from nothing else', async () => {
|
||||
const puter = await bootWeb('');
|
||||
|
||||
Reference in new issue
Block a user