feat: refactor api() to accept optional baseUrl for cross-origin requests
Add 4th parameter `baseUrl` to `api()`. When provided, strips trailing slash and prepends to path, and sets `credentials: 'include'` for cross-origin cookie sending. Behavior is unchanged when no baseUrl is given (backward compatible). Export `api` in module.exports. All 135 tests pass. 🤖 Generated with Amplifier <https://github.com/microsoft/amplifier> Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
This commit is contained in:
@@ -157,13 +157,18 @@ function isMobile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── Fetch wrapper ────────────────────────────────────────────────────────────
|
// ─── Fetch wrapper ────────────────────────────────────────────────────────────
|
||||||
async function api(method, path, body) {
|
async function api(method, path, body, baseUrl) {
|
||||||
const opts = { method, headers: {} };
|
const opts = { method, headers: {} };
|
||||||
if (body !== undefined) {
|
if (body !== undefined) {
|
||||||
opts.headers['Content-Type'] = 'application/json';
|
opts.headers['Content-Type'] = 'application/json';
|
||||||
opts.body = JSON.stringify(body);
|
opts.body = JSON.stringify(body);
|
||||||
}
|
}
|
||||||
const res = await fetch(path, opts);
|
let url = path;
|
||||||
|
if (baseUrl) {
|
||||||
|
url = baseUrl.replace(/\/+$/, '') + path;
|
||||||
|
opts.credentials = 'include';
|
||||||
|
}
|
||||||
|
const res = await fetch(url, opts);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
||||||
}
|
}
|
||||||
@@ -1711,6 +1716,8 @@ if (typeof module !== 'undefined' && module.exports) {
|
|||||||
// Server settings
|
// Server settings
|
||||||
loadServerSettings,
|
loadServerSettings,
|
||||||
patchServerSetting,
|
patchServerSetting,
|
||||||
|
// Fetch wrapper
|
||||||
|
api,
|
||||||
// Header + button with inline name input
|
// Header + button with inline name input
|
||||||
showNewSessionInput,
|
showNewSessionInput,
|
||||||
showFabSessionInput,
|
showFabSessionInput,
|
||||||
|
|||||||
@@ -1928,6 +1928,58 @@ test('buildSidebarHTML includes sidebar-delete button with data-session attribut
|
|||||||
assert.ok(html.includes('data-session="my-session"'), 'sidebar-delete button must have data-session attribute');
|
assert.ok(html.includes('data-session="my-session"'), 'sidebar-delete button must have data-session attribute');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- api ---
|
||||||
|
|
||||||
|
test('api with no baseUrl uses relative path', async () => {
|
||||||
|
const calls = [];
|
||||||
|
const origFetch = globalThis.fetch;
|
||||||
|
globalThis.fetch = async (url, opts) => {
|
||||||
|
calls.push({ url, opts });
|
||||||
|
return { ok: true };
|
||||||
|
};
|
||||||
|
|
||||||
|
await app.api('GET', '/api/sessions');
|
||||||
|
|
||||||
|
assert.strictEqual(calls.length, 1, 'should call fetch once');
|
||||||
|
assert.strictEqual(calls[0].url, '/api/sessions', 'url should be relative path');
|
||||||
|
assert.ok(!calls[0].opts.credentials, 'credentials should not be set without baseUrl');
|
||||||
|
|
||||||
|
globalThis.fetch = origFetch;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('api with baseUrl prepends it to path and sets credentials include', async () => {
|
||||||
|
const calls = [];
|
||||||
|
const origFetch = globalThis.fetch;
|
||||||
|
globalThis.fetch = async (url, opts) => {
|
||||||
|
calls.push({ url, opts });
|
||||||
|
return { ok: true };
|
||||||
|
};
|
||||||
|
|
||||||
|
await app.api('GET', '/api/sessions', undefined, 'https://remote.example.com');
|
||||||
|
|
||||||
|
assert.strictEqual(calls.length, 1, 'should call fetch once');
|
||||||
|
assert.strictEqual(calls[0].url, 'https://remote.example.com/api/sessions', 'url should prepend baseUrl');
|
||||||
|
assert.strictEqual(calls[0].opts.credentials, 'include', 'credentials should be include for cross-origin');
|
||||||
|
|
||||||
|
globalThis.fetch = origFetch;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('api with baseUrl and trailing slash does not double-slash', async () => {
|
||||||
|
const calls = [];
|
||||||
|
const origFetch = globalThis.fetch;
|
||||||
|
globalThis.fetch = async (url, opts) => {
|
||||||
|
calls.push({ url, opts });
|
||||||
|
return { ok: true };
|
||||||
|
};
|
||||||
|
|
||||||
|
await app.api('GET', '/api/sessions', undefined, 'https://remote.example.com/');
|
||||||
|
|
||||||
|
assert.strictEqual(calls.length, 1, 'should call fetch once');
|
||||||
|
assert.strictEqual(calls[0].url, 'https://remote.example.com/api/sessions', 'trailing slash on baseUrl should not create double-slash');
|
||||||
|
|
||||||
|
globalThis.fetch = origFetch;
|
||||||
|
});
|
||||||
|
|
||||||
test('createNewSession polls for session before auto-opening (not immediate setTimeout openSession)', () => {
|
test('createNewSession polls for session before auto-opening (not immediate setTimeout openSession)', () => {
|
||||||
// The old behavior was: setTimeout(() => openSession(...), 500) immediately after POST.
|
// The old behavior was: setTimeout(() => openSession(...), 500) immediately after POST.
|
||||||
// The new behavior must use a polling interval to wait for the session to appear in
|
// The new behavior must use a polling interval to wait for the session to appear in
|
||||||
|
|||||||
Reference in New Issue
Block a user