Commit Graph
5 Commits
Author SHA1 Message Date
Your NameandClaude Sonnet 5 e78437cb2a fix(web): upgrade http OSS URLs to https to fix mixed-content block
Aliyun OSS sometimes returns presigned URLs as http:// even though every
page here is served over https:// -- the browser silently blocks these
as mixed content, which is why images (e.g. avatar) failed to display
after upload. Added upgradeToHttps() and wired it into fileDownloadUrl()
(the single chokepoint every image/attachment fetch in the app resolves
through) plus the three chat-attachment send paths, so both freshly
fetched and newly-persisted URLs get the scheme fix.

Also fixed ProfileSettingsPage's avatar upload handler, which extracted
the file id by parsing the upload response's `url` with .split('/').pop()
instead of just reading the `file_id` field the response already
provides -- fragile against exactly this kind of full-URL response and
the most likely actual trigger for the reported bug.

vite.config.ts: dev server port now respects $PORT if set, falls back
to 8094 -- unrelated cleanup, needed it to run a second local dev
server for verification alongside another concurrent session's.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 00:18:31 +08:00
Your NameandClaude Sonnet 5 5c3f3b7653 feat(web): complete groups/contacts/favorites/settings/calls/system domains + extensive review-round fixes
Rounds out the full rewrite with the remaining feature domains: group
management (admin panel, mute, files, announcements, QR-based invite/join),
contacts (friend profile, add-friend, blocklist, new-friends), favorites
(real media rendering across text/image/video/voice/file/link), settings
(profile/security/privacy/general/about), calls (Agora-backed audio/video
signaling + call history), and system messages/file assistant.

Also folds in a large batch of live-verified fixes surfaced by repeated
fresh code-review passes and hands-on E2E testing against the real
backend, including: WS reconnect not resubscribing any topic after a
disconnect, missing x-im-client-mid on every send path (protocol
compliance for retry dedup), group mute/member-settings/self-profile
endpoint corrections, relationship-state type fixes across search/QR/
friend-profile, tenant-switch logout gap, password-policy now read from
the real per-tenant config instead of guessed, NavRail highlight/
aria-current desync, several call-state-machine races, and responsive
layout fixes across all four breakpoints (mobile/tablet/laptop/desktop)
including a full mobile rebuild of favorites, call UI, and the settings
home/sub-list split.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 20:16:33 +08:00
Your NameandClaude Sonnet 5 986432addd fix(web): code review round 1 -- critical data-layer bugs
Three parallel adversarial code-review passes surfaced real defects across
the app. This commit covers the data/logic layer:

- CRITICAL: file upload/download used the wrong auth header entirely.
  Built from docs/IM-前后端对接文档.md as `X-MeetingIM-Auth: token <token>`,
  but the old app's own upload code carries an explicit comment that the
  real header is `Authorization: Token <token>` (capitalized scheme,
  standard header name), "verified live against the real server, not just
  read from source" -- and separately warns the SDK's own built-in
  LargeFileHelper gets this wrong too (sends X-Tinode-APIKey/X-Tinode-Auth,
  which this backend doesn't recognize). This means every external
  (non-inline) image/file/voice attachment -- anything too large to send as
  base64 -- was silently broken; only the earlier live tests happened to
  use small inline attachments, which never exercise this code path.
  Confirmed fixed live: a real >256KB image now uploads (200, real file_id)
  and downloads (blob URL, correct 800x600 dimensions) correctly.
- CRITICAL: TopicMe.onContactUpdate was never wired. Marking a conversation
  read (topic.noteRead()) updates the contact's unread count through ONLY
  this single-slot callback (confirmed against the SDK's own
  Topic.note()/TopicMe._refreshContact -- it does not also fire onData/
  onInfo/onPres, unlike every other _refreshContact call site, which do).
  Without it the chat list's unread badge/ordering went stale after reading
  a conversation until an unrelated topic event happened to invalidate the
  shared snapshot cache.
- HIGH: chat-list message preview didn't check isServerDeleted(msg) (the
  `hi` soft-delete flag), only `content == null` -- a hi-flagged message
  with non-null content leaked its real text/attachment label into the
  preview instead of showing "[消息已删除]".
- MEDIUM: upload failures showed raw untranslated SDK ctrl.text (e.g. "too
  large" instead of "文件大小超出限制") -- ported the old app's
  UPLOAD_CTRL_TEXT_CN table, which it built specifically because this
  native-protocol endpoint's errors never pass through the REST layer's
  own reason-code translation.
- Ambient SDK type corrections (tinode-sdk.d.ts): AccessMode.mode/given/want
  are numeric bitmasks at runtime, not the "JRWPS"-string type they were
  declared as (dormant today, nothing reads them yet, but wrong ahead of
  Phase 3's group-role logic); Drafty.sanitizeEntities's doc comment
  claimed a deep copy it doesn't do (verified it mutates entity .data in
  place); onAutoreconnectIteration's first param was misnamed "seconds"
  when the SDK always passes milliseconds.
- sendVoice() now passes `filename` to Drafty.insertAudio (was the only one
  of sendImage/sendFile/sendVoice omitting it).
- friends.ts's accept/reject-friend-request `version` param used a falsy
  check (`version ? ... : ...`) that would silently drop a legitimate `0`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 03:23:01 +08:00
Your NameandClaude Sonnet 5 03b8098b3c feat(web): voice messages (record, send, playback)
Click-to-record (README §3.3's explicit 取消/发送 buttons), not the old
app's press-and-hold-with-swipe-to-cancel gesture -- the new design replaces
that touch gesture with plain buttons, a reasonable desktop-first
adaptation; the underlying MediaRecorder mechanics (audio/webm or
audio/mp4 depending on browser support, 24kbps, 2s minimum/10min maximum
duration) are ported as-is from the old app's audio-recorder.jsx.

- VoiceRecorder.tsx: recording UI + MediaRecorder lifecycle. Silently
  discards (no send) recordings under MIN_DURATION, matching the old app.
  Deliberately skips the `webm-duration-fix` package the old app pulled in
  for Chrome's well-known missing-webm-duration-metadata bug -- this app's
  own displayed duration comes from its own recording timer, not the file's
  embedded metadata, so only a browser's native <audio> scrubber would be
  affected, not anything this app actually shows.
- attachments.ts: sendVoice() -- inline base64 or upload-then-reference,
  same size-based branch as images/files, Drafty.insertAudio's confirmed
  {mime,val/ref,duration,size} shape.
- AttachmentPreview.tsx: AudioAttachment play/pause button + static
  waveform (CSS repeating-gradient, not real waveform analysis) + duration.

Verified the failure path live (this sandbox blocks getUserMedia): the
recorder's error state shows a clear message and recovers cleanly back to
the normal composer with no crash. The actual record -> encode -> send ->
playback round trip could not be exercised in this environment and needs
a manual check with real microphone access.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 02:49:17 +08:00
Your NameandClaude Sonnet 5 59d1033a92 feat(web): image and file message attachments (send + render)
- lib/api/upload.ts: POST /v0/file/u/ (docs/IM-前后端对接文档.md:460-461) --
  a separate surface from /v1/im/*, at the tenant's file base URL, auth'd
  with X-MeetingIM-Auth: token <token> rather than Authorization: Bearer
  (matches upstream Tinode's own LargeFileHelper convention).
- lib/tinode/attachments.ts: sendImage/sendFile. Files <= the inband limit
  go in directly as base64 (Drafty.insertImage's `bits` field / attachFile's
  `data` field) with no upload round trip; larger ones upload first and
  reference the returned URL. Simplified from the old app's urlPromise
  dance (optimistic local preview while the upload runs in the background)
  by just awaiting the upload before creating the message -- less
  optimistic, meaningfully simpler.
- AttachmentPreview.tsx: image/file bubble rendering. Externally-referenced
  (non-inline) attachments need an authenticated fetch into a blob URL,
  not a plain <img src>/<a href> -- GET /v0/file/s/{file_id} requires the
  same custom auth header uploads do, which a bare element can't attach.
- drafty.tsx: wired the 'IM'/'EX' Drafty style-tag cases to these.

Verified end to end: sent a real inline image (confirmed the exact IM
entity shape in the outgoing WS packet, and that the <img> decoded and
loaded from its own base64 data) and a real inline file (rendered with the
correct icon/size/download affordance).

Known gap: voice messages (MediaRecorder + AU entities) not built yet --
meaningfully more complex (hold-to-record gesture, audio encoding,
duration/waveform) and deliberately left for a following pass rather than
rushed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 02:44:12 +08:00