Convert im_webapp from static mock prototypes to real backend integration

Implements all 11 product chapters (auth, messaging, contacts, groups,
files/favorites, calls, workplace/reports, settings, security) against the
real /v1/im REST API and native Tinode WS protocol, replacing the hardcoded
mock data the ba8f48ee prototype commit shipped with. Adds src/lib/im-api.js
as the REST client, plus supporting fixes across messages-view.jsx and the
attachment/upload/download pipeline (wrong SDK auth headers, group topic
naming, seq/timezone edge cases) found via live end-to-end testing against
the real backend.

Backup checkpoint before reconciling with origin/dev's parallel rewrite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-03 11:40:53 +08:00
co-authored by Claude Sonnet 5
parent ba8f48ee83
commit 251c9de87c
36 changed files with 52812 additions and 5199 deletions
+19 -8
View File
@@ -144,10 +144,13 @@ class SendMessage extends React.PureComponent {
if (this.props.disabled) {
return;
}
// onAttachImage/onAttachFile are explicitly null while forwarding a message
// (messages-view.jsx) -- pasting a file in that state used to throw
// "onAttachImage is not a function" instead of just not attaching anything.
// FIXME: handle large files too.
if (filePasted(e,
file => { this.props.onAttachImage(file); },
file => { this.props.onAttachFile(file); },
file => { if (this.props.onAttachImage) { this.props.onAttachImage(file); } },
file => { if (this.props.onAttachFile) { this.props.onAttachFile(file); } },
this.props.onError)) {
// If a file was pasted, don't paste base64 data into input field.
@@ -156,7 +159,7 @@ class SendMessage extends React.PureComponent {
}
handleAttachImage(e) {
if (e.target.files && e.target.files.length > 0) {
if (e.target.files && e.target.files.length > 0 && this.props.onAttachImage) {
this.props.onAttachImage(e.target.files[0]);
}
// Clear the value so the same file can be uploaded again.
@@ -164,7 +167,7 @@ class SendMessage extends React.PureComponent {
}
handleAttachFile(e) {
if (e.target.files && e.target.files.length > 0) {
if (e.target.files && e.target.files.length > 0 && this.props.onAttachFile) {
this.props.onAttachFile(e.target.files[0]);
}
// Clear the value so the same file can be uploaded again.
@@ -172,22 +175,30 @@ class SendMessage extends React.PureComponent {
}
handleDropAttach(files) {
if (files && files.length > 0) {
if (files && files.length > 0 && this.props.onAttachFile) {
this.props.onAttachFile(files[0]);
}
}
handleAttachAudio(url, preview, duration) {
this.setState({audioRec: false});
this.props.onAttachAudio(url, preview, duration);
if (this.props.onAttachAudio) {
this.props.onAttachAudio(url, preview, duration);
}
}
handleSend(e) {
e.preventDefault();
const message = this.state.message.trim();
if (message || this.props.acceptBlank || this.props.noInput) {
this.props.onSendMessage(message);
this.setState({message: ''});
const result = this.props.onSendMessage(message);
// onSendMessage may return a promise resolving to `false` when a pre-send risk check blocked
// or the user cancelled it -- in that case keep the composer text so nothing typed is lost.
if (result && typeof result.then == 'function') {
result.then(sent => { if (sent !== false) this.setState({message: ''}); });
} else {
this.setState({message: ''});
}
}
}