| … | … | @@ -101,6 +101,10 @@ export class Router { |
| 101 | 101 | case 'file.remove': return this._fileRemove(params) |
| 102 | 102 | case 'file.clear': return this._fileClear(params) |
| 103 | 103 | case 'file.fetch': return this._fileFetch(params) |
| 104 | case 'file.preview': return this._filePreview(params) |
| 105 | case 'file.subscribe': return { ok: true } |
| 106 | case 'file.unsubscribe': return { ok: true } |
| 107 | case 'file.path': return this._filePath(params) |
| 104 | 108 | |
| 105 | 109 | // --- Advanced Members --- |
| 106 | 110 | case 'members.active': return this._membersActive(params) |
| … | … | @@ -245,6 +249,17 @@ export class Router { |
| 245 | 249 | } else if (generic) { |
| 246 | 250 | system = generic |
| 247 | 251 | } |
| 252 | // Extract file attachments |
| 253 | const rawFiles = msg.message?.files || [] |
| 254 | const files = Array.isArray(rawFiles) ? rawFiles.map(f => ({ |
| 255 | id: f.pointer?.externalBlob?.id || null, |
| 256 | name: f.name || f.fileName || 'file', |
| 257 | type: f.mimeType || '', |
| 258 | size: f.size || 0, |
| 259 | dimensions: f.dimensions || null, |
| 260 | httpLink: f.httpLink || '' |
| 261 | })) : [] |
| 262 | |
| 248 | 263 | return { |
| 249 | 264 | memberId, |
| 250 | 265 | name: nameMap[memberId] || memberId.slice(0, 8), |
| … | … | @@ -252,10 +267,11 @@ export class Router { |
| 252 | 267 | timestamp: msg.timestamp || null, |
| 253 | 268 | deleted: msg.deleted || false, |
| 254 | 269 | system, |
| 270 | files: files.length > 0 ? files : null, |
| 255 | 271 | seq: msg.seq ?? msg.clock ?? null, |
| 256 | 272 | id: msg.id || null |
| 257 | 273 | } |
| 258 | | }).filter(m => m.text || m.system) |
| 274 | }).filter(m => m.text || m.system || m.files) |
| 259 | 275 | } |
| 260 | 276 | |
| 261 | 277 | async _getMemberNames (roomId) { |
| … | … | @@ -745,6 +761,34 @@ export class Router { |
| 745 | 761 | return { base64: buf.toString('base64'), contentType, size: buf.length } |
| 746 | 762 | } |
| 747 | 763 | |
| 764 | async _filePath (params) { |
| 765 | if (!params.roomId) throw new Error('Missing required param: roomId') |
| 766 | if (!params.fileId) throw new Error('Missing required param: fileId') |
| 767 | const result = await this.backend.api.core.getFileEntry(params.roomId, params.fileId) |
| 768 | return result || {} |
| 769 | } |
| 770 | |
| 771 | async _filePreview (params) { |
| 772 | if (!params.roomId) throw new Error('Missing required param: roomId') |
| 773 | if (!params.fileId) throw new Error('Missing required param: fileId') |
| 774 | // getFileEntry is broken — use getLocalFiles + search |
| 775 | const files = await this.backend.api.core.getLocalFiles(params.roomId) |
| 776 | const entries = Array.isArray(files) ? files : (files?.data || files?.entries || []) |
| 777 | const entry = entries.find(f => { |
| 778 | const id = f.id || f.fileId || f.pointer?.externalBlob?.id |
| 779 | return id === params.fileId |
| 780 | }) |
| 781 | if (!entry) throw new Error('File not found: ' + params.fileId) |
| 782 | return { |
| 783 | name: entry.fileName || entry.name || 'file', |
| 784 | type: entry.mimeType || entry.type || '', |
| 785 | size: entry.size || 0, |
| 786 | httpLink: entry.httpLink || '', |
| 787 | fullHttpLink: entry.httpLink || '', |
| 788 | dimensions: entry.dimensions || null |
| 789 | } |
| 790 | } |
| 791 | |
| 748 | 792 | // --- Advanced Members --- |
| 749 | 793 | |
| 750 | 794 | async _membersActive (params) { |