Skip to content

fix(images): setImages builds malformed URL when api.port is empty #3963

Description

@PierreBrisorgueil

Context

Since the Gravatar → UserAvatarComponent refactor (PR #3927, commit a5f4e9ab, 2026-04-09), user avatars are rendered via setImages(config.api, user.avatar, size * 2, null) — routed through the shared setImages helper in src/lib/plugins/images.js.

On downstream projects deployed over HTTPS with an empty api.port (the standard prod setup — port 443 implicit), avatars are broken: the rendered URL contains a stray colon with no port, e.g. https://trawl.me:/api/uploads/images/abc.jpg, which browsers reject.

Repro confirmed on trawl_vue (built with DEVKIT_VUE_api_port= in CI). Affects every user with an uploaded avatar on the scraps list and anywhere else UserAvatarComponent is used. Users without uploaded avatars see the initials fallback and are unaffected.

Root cause

src/lib/plugins/images.js:17 unconditionally concatenates :${api.port}:

return \`${api.protocol}://${api.host}:${api.port}/api/uploads/images/${name}\`;

When api.port === '', this produces https://host:/api/... — malformed.

Other URL builders in the codebase already handle this correctly — e.g. trawl_vue/src/modules/scraps/utils/screenshotUrl.js:12-13:

const port = config.api.port ? \`:${config.api.port}\` : '';
return \`${config.api.protocol}://${config.api.host}${port}/${config.api.base}/uploads/${screenshot}\`;

The setImages plugin was never updated with this pattern, but the bug was latent because pre-refactor avatars went through Gravatar (remote URL, not setImages) — the refactor exposed it.

Scope

Bug affects all downstream projects built with empty api.port (i.e., HTTPS prod on standard 443). Since the refactor shipped 2026-04-09, every downstream that ran /update-stack since then has broken avatars.

Fix

1. src/lib/plugins/images.js

Replace the single return line with conditional port handling:

app.config.globalProperties.setImages = (api, file, size, operation) => {
  const base = file.split('.');
  if (base.length !== 2) return file;
  let [name] = base;
  if (size) name = \`${name}-${size}\`;
  if (operation) name = \`${name}-${operation}\`;
  name = \`${name}.${base[1]}\`;
  const port = api.port ? \`:${api.port}\` : '';
  return \`${api.protocol}://${api.host}${port}/api/uploads/images/${name}\`;
};

Single-line change in the return, plus one port local. No signature change, no API break.

2. src/lib/plugins/tests/images.unit.tests.js

Add a test case for empty port:

it('omits port when api.port is empty', () => {
  const api = { protocol: 'https', host: 'trawl.me', port: '' };
  expect(setImages(api, 'photo.jpg', null, null))
    .toBe('https://trawl.me/api/uploads/images/photo.jpg');
});

it('omits port when api.port is undefined', () => {
  const api = { protocol: 'https', host: 'trawl.me' };
  expect(setImages(api, 'photo.jpg', null, null))
    .toBe('https://trawl.me/api/uploads/images/photo.jpg');
});

Existing tests (which pass port: '3000') remain valid and unchanged.

3. No other files touched

  • UserAvatarComponent: no change — it passes config.api as-is, the fix is transparent.
  • MIGRATIONS.md: no entry needed (bugfix, not a breaking change).

Risks

None. Purely additive / defensive — URLs with a port continue to render identically.

Validation

  • /verify on the stack (lint + unit + audit)
  • Manual: deploy a downstream (trawl_vue first via /update-stack), check https://trawl.me scraps list — avatars should load instead of 404.

Downstream rollout

Once merged, each affected downstream needs /update-stack to pick up the fix: trawl_vue, pierreb_vue, comes_vue, montaine_vue (and any other HTTPS-deployed project).

Estimated cost

~10 min real work. Single PR, ~5 LOC in source + ~10 LOC in tests.

Activity

  1. added a commit that references this issue on Apr 13, 2026
    848d0c8
  2. added a commit that references this issue on Apr 13, 2026
    70c2e4c
  3. added a commit that references this issue on May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions