Compare commits

...

698 commits

Author SHA1 Message Date
7b3a4145a7 fix(channels): back active-workspace persistence with a jotai atom so the native pager sees switcher picks instead of a stale memoized localStorage read 2026-05-22 01:18:15 +03:00
765445c091 feat(discord-widget): render Open-in-Channels card after login via VOJO-LOGIN-SPACE-V1 sentinel and generic open-matrix-to widget action 2026-05-21 14:08:50 +03:00
408b9eefc3 chore(eslint): give widget apps their own Preact-aware root config so host airbnb and react rules stop flagging valid Preact code in pre-commit 2026-05-21 14:08:21 +03:00
8d8b39e897 fix(telegram-widget): stack password row column on mobile so the show toggle does not overflow off the right edge 2026-05-21 13:43:11 +03:00
055b7d3692 fix(workspace-switcher): switch space rows to SurfaceVariant so inactive bg blends with the sheet silhouette instead of reading as dark cards 2026-05-21 01:05:34 +03:00
78504262d3 feat(stream-header): flatten web curtain to a tabsRow-border divider pixel-aligned with PageHeader at WEB_TABS_ROW_PX=54 and gate keyboard probe to native 2026-05-20 23:20:17 +03:00
cde50cff0f fix(bots): pad BotStatePage wrapper with safe-top so the mobile back-arrow header clears the Android status bar on the connect-bot empty state 2026-05-20 02:39:21 +03:00
6ca6b69d48 feat(stream-header): contextual Plus on Channels opens create-channel inside workspace and create-community on landing via StreamHeader.primaryAction 2026-05-20 01:59:04 +03:00
240bb54c29 refactor(stream-header): reset live drag on gesture teardown, drop dead pinned-local fallback, narrow commit() to peek|closed, add exhaustive transition guards and align stale comments 2026-05-20 00:59:17 +03:00
8fb885df1b feat(stream-header): free-range curtain drag through full pin↔closed↔peek range with bottomPinned-aware body bail and native-only handle 2026-05-20 00:26:10 +03:00
ab283e9788 refactor(stream-header): unify curtain gestures onto dual handle+body surfaces with 1:1 handle and rubber-band body, scroll-aware bail 2026-05-19 23:27:19 +03:00
e866cd3830 feat(stream-header): move pin/unpin gesture onto dedicated 32px drag-handle with 1:1 finger tracking and desktop-style grabber animation 2026-05-19 18:26:37 +03:00
7c5a1f2ee7 fix(mobile-tabs-pager): restore segment-button taps in pager mode via opacity-0 per-pane row and route their commits through an instant no-transition strip jump 2026-05-19 14:28:56 +03:00
0422a9832f feat(stream-header): pin chats curtain over static pager header on drag-up with per-tab atom, native-only rubber-band gesture and pinned-aware horseshoe sheet coordination 2026-05-19 11:50:31 +03:00
4a9d5f6384 fix(mobile-tabs-pager): paint Background.OnContainer on pagerRoot so panes mounted outside PageRoot inherit readable text on native 2026-05-18 22:58:53 +03:00
870e13d895 feat(mobile-tabs-pager): swipe between Direct, Channels and Bots on Capacitor native with static header, 24px gap, atom-bridged action icons and inert offscreen panes 2026-05-18 22:00:53 +03:00
727a53a776 fix(horseshoe): extend mobile DM and Channels wrappers up over the safe-top zone so the StreamHeader curtain paints the status-bar strip on drag-up 2026-05-18 15:14:58 +03:00
af97549e48 tweak(stream-header): require curtain drag past 90% of full peek travel to commit so short drags snap back as accidental 2026-05-18 02:12:55 +03:00
0c704aac38 tweak(bubbles): paint peer bg and horseshoe void pure black and flip own/peer flat-corner to bottom-left/top-left at 16px radius 2026-05-18 02:04:39 +03:00
b26340fa7d feat(electron): add desktop wrapper packaging Vojo as Windows zip with privileged vojo:// scheme, HashRouter override and native chrome 2026-05-18 01:50:16 +03:00
5dbe83aa9d feat(push): real sender+room avatars via MXC bridge with adaptive shortcut icons, plus review fixes (GROUP_ALERT_ALL, eventId dedup, isEncrypted privacy default, structured roomname parse, mark-as-read optimistic docs) 2026-05-17 17:39:19 +03:00
4b4454fa1d docs(android): document MessagingStyle pipeline, channel split, callId-session dedup and edit-collapse defer rationale 2026-05-17 02:42:45 +03:00
de348eb4fc fix(push): close E2EE-flip race by re-checking room encryption at reply send time, pre-flight credentials before optimistic echo, share callId-dedup helpers between FCM and Worker 2026-05-17 02:41:27 +03:00
06778702b2 feat(push): inline RemoteInput reply on per-room notifications for cleartext rooms with optimistic local echo and encryption-state re-dump 2026-05-17 02:29:20 +03:00
8a80194fe5 fix(push): dedup re-rings within one call session via composite (roomId, callSessionId) key so a participant rejoin doesn't re-alert 2026-05-17 02:22:20 +03:00
38d24e5527 feat(push): group room messages into a per-room MessagingStyle conversation with DM/group channels, mark-as-read action and receipt-driven dismiss 2026-05-17 02:06:21 +03:00
408f165f60 feat(push): add WorkManager polling fallback that delivers notifications via /_matrix/client/v3/notifications when FCM is blocked 2026-05-17 01:27:55 +03:00
b9aad691b5 update gitignore with Vite config 2026-05-16 22:06:35 +03:00
770609b964 feat(media): make the desktop right-side media pane resizable with a smart max that subtracts chat-list width and a chat-column reservation 2026-05-16 20:45:54 +03:00
ebf2cfe07b fix(sidebar): raise resizable page-nav min width from 320 to 384px so StreamHeader segments and action icons stop clipping at the floor 2026-05-16 20:31:11 +03:00
2d101a40fc fix(channels): collapse /channels/ index into one mobile pane and add Create-community CTA next to Find-community on empty state 2026-05-16 20:25:01 +03:00
f2ecca64da feat(share): receive Android system share intents and drop the payload into the next chat the user opens via a top banner cue 2026-05-16 19:33:06 +03:00
6982ec374e chore(lint): close all typecheck and eslint tech debt to enable husky pre-commit hook with --max-warnings 0 2026-05-16 17:22:53 +03:00
45c69317ff feat(message): paint non-own bubbles via --vojo-peer-bubble-bg in Stream + Channel layouts and Stream rail/day-divider via --vojo-timeline-rail 2026-05-16 13:13:28 +03:00
c78984a6d8 fix(direct): exclude bridged portal rooms from Direct tab so Telegram chats live exclusively in their per-bridge personal filtering space 2026-05-16 00:31:38 +03:00
bfd72dc1ff tweak(stream-header): collapse peek1/peek2 into single peek snap so one drag reveals both action chips at once 2026-05-15 23:47:40 +03:00
0eb2e056c0 feat(stream-header): rebuild Direct/Channels/Bots header as a curtain layered above tabs with peek chips, inline forms, and VisualViewport keyboard compensation 2026-05-15 23:05:34 +03:00
81d23be61f feat(theme): ship Settings picker with system/light/dark and Vojo light palette reshading sidebar, chat, bubbles, horseshoe void and PWA chrome 2026-05-15 01:06:49 +03:00
8e2db986b4 feat(composer): tighten action-row padding, extract button JSX, and rotate placeholder across 12 hour-keyed variants 2026-05-15 00:13:51 +03:00
646cb7b124 feat(members): carve rounded TL/BL on members drawer with 12px void seam to chat and extract VoidGap helper consolidating four per-pane seams 2026-05-14 21:11:36 +03:00
8400ef54ee feat(syslines): render membership and room-state events as sender-anchored chat bubbles via StreamLayout instead of thin rail syslines 2026-05-14 01:39:51 +03:00
a893e86d92 tweak(direct): dial DM list avatars down to 48px with 68px row height for a less heavy density 2026-05-14 01:12:17 +03:00
2d74848509 feat(direct): enlarge DM list avatars to size 500 with 80px row height and matched virtualizer estimate for a denser two-line layout 2026-05-14 01:05:32 +03:00
c3a384b651 fix(page-nav): hold min-height:0 on scrolling middle and shrink-no on Direct bottom rows so flex pressure no longer squashes them below their NavItem floor 2026-05-14 01:05:21 +03:00
3c7c79fb6c fix(calls): split per-session bubbles by joined-count boundary with expiry-aware ongoing, post-ring duration, and same-caller retry merging 2026-05-14 00:27:38 +03:00
f5e992daad fix(time): use Intl numeric day-month-year everywhere so chat day dividers follow the system locale instead of hardcoded English full-month 2026-05-13 23:30:52 +03:00
2dac76f9af chore(i18n): rename DM direct-stream segment label to Direct 2026-05-13 23:30:42 +03:00
1ee1d50c41 fix(dm-name): drop matrix-js-sdk mxid disambiguation suffix from DM room names by using peer rawDisplayName 2026-05-13 22:54:26 +03:00
d3e69e042f fix(i18n): track system language on every launch instead of caching first-seen value in localStorage 2026-05-13 22:54:06 +03:00
5d4a50f593 feat(legal): publish Privacy Policy and account-deletion pages with About-screen link and Play Store feature graphic 2026-05-13 22:53:58 +03:00
5c16649e0c chore(versioning): derive Android versionName from git describe to match __APP_VERSION__ from vite 2026-05-13 22:53:48 +03:00
30e477d2cd feat(calls): render m.call.member events as one aggregate chat bubble per call aligned to initiator side 2026-05-13 15:57:23 +03:00
6a6b7acf15 feat(channels): rebuild thread drawer and channel rows as chat-style bubble cards that merge thread-summary into the bubble footer 2026-05-13 15:21:13 +03:00
3dee9f099f feat(thread-drawer): wrap in horseshoe seam with rounded TL/BL and add pointer/keyboard resize clamped to viewport/3 2026-05-13 14:42:15 +03:00
11c46d9250 feat(channels): replace workspace switcher popout with sliding horseshoe sheet and inline create-channel row, retire sidebar CreateTab 2026-05-13 14:21:39 +03:00
c27f8a7cc2 chore(mascot): re-encode as 2s VP9 cycle and center auth drop-shadow on character 2026-05-13 04:17:14 +03:00
4e7ea405ab chore(android): enable R8 shrinker with keep rules, set up release signing, and strip sourcemaps from APK 2026-05-13 04:17:09 +03:00
ffb80bff88 chore(bundle): trim web bundle by dropping unused Twemoji TTF, Prism languages, and MediaPipe blur wasm 2026-05-13 04:17:03 +03:00
663aece487 feat(media-viewer): atom-driven horseshoe shell over chat replacing Overlay modal for image+video with anchor-aware pinch/wheel zoom and swipe prev/next 2026-05-13 02:47:04 +03:00
4654836092 feat(chat): hide composer on scroll-up past 200px and replace jump-to-latest chip with circular FAB that pulses on incoming live messages 2026-05-13 01:36:29 +03:00
635fb91022 feat(settings): replace Modal500 with /settings route plus mobile bottom-up horseshoe sheet overlaying DM list via clip-path mask 2026-05-13 00:01:26 +03:00
c6bb66958d feat(profile-rail): size mobile user-card rail to measured content height with 85vh cap and inner scroll only on overflow 2026-05-12 02:06:10 +03:00
149382299a feat(safe-area): extend Android edge-to-edge top inset via --vojo-safe-top var and collapse profile horseshoe header with measured height 2026-05-12 01:54:30 +03:00
ce82d66883 feat(safe-area): paint Android edge-to-edge top/bottom strips with the active surface tone instead of a fixed body bg 2026-05-11 23:13:56 +03:00
f38cb42344 feat(direct-tabs): spread DM/Channels/Bots tabs edge-to-edge and underline active segment with purple bar on the header's grey rule 2026-05-11 21:04:07 +03:00
785b679b61 feat(horseshoe): add 12px void gap between chat and profile pane with rounded TL/BL on profile, drop page-nav right rounding 2026-05-11 20:33:51 +03:00
de2354f1da fix(composer): re-anchor timeline scrollTop when overlay composer height changes via prop-driven layout effect 2026-05-11 18:10:56 +03:00
41a9af19e3 feat(composer): floating overlay above timeline with Gemini-style two-row layout and Android-WebView stuck-hover gate 2026-05-11 17:47:37 +03:00
2337b05140 feat(chat): invert canvas/bubble tones and darken timeline rail and media frames 2026-05-11 15:16:53 +03:00
ab6c65a4e0 fix(profile): keep desktop user-room-profile pane open on outside clicks, close only via × or Esc 2026-05-11 14:09:07 +03:00
e3e61afd4c feat(profile): merge 1:1 header avatar and title into single identity button so name taps open the profile sheet 2026-05-11 14:09:07 +03:00
4d0b508ebb feat(horseshoe): split web page-nav and chat panel with 12px void gap and rounded inner corners on both sides 2026-05-11 13:54:40 +03:00
023a6a439c feat(sidebar): add resizable left page-nav via pointer/keyboard with localStorage-persisted width, clamped [320, viewport/3] and tactile min/max indicator 2026-05-11 02:49:39 +03:00
c992e910ee feat(profile): add mouse drag for mobile horseshoe via pointer events with origin-tagged drag state and userSelect:none on viewports 2026-05-11 02:22:28 +03:00
2bbaf4dfcf fix(profile): override avatar circle force via && self-doubled selector instead of source-order-dependent !important double-up 2026-05-11 02:00:12 +03:00
117bb9fba4 refactor(profile): rebuild mobile horseshoe as single silhouette wrapper with Vaul ease curves replacing the two-radius emerge handoff 2026-05-11 02:00:04 +03:00
626a7c2d1d feat(profile): inline-expand hero avatar in desktop side pane instead of swapping the whole card to full-view 2026-05-11 01:11:09 +03:00
9c204c1af6 feat(profile): drop pill background on user-card kebab and enlarge the dots icon 2026-05-11 00:58:39 +03:00
4b39046c09 feat(horseshoe): bump top profile and bottom call horseshoe radius to 32px with 12px void gap 2026-05-11 00:54:58 +03:00
3fed5ff873 chore(layout): disable SidebarNav 66px rail rendering; component preserved for later entry-point redistribution 2026-05-10 22:12:31 +03:00
d4b05619a8 feat(channels): ship M6 workspace switcher dropdown with rename-reactive trigger and rows for multi-space users 2026-05-10 20:56:14 +03:00
0b31e6b930 feat(channels): route room-in-space navigations through /channels/ with eventId-anchored permalinks and channels-aware cold-push redirect 2026-05-10 20:13:50 +03:00
896a2e2083 feat(channels): M4a drawer rich chrome with edit menu reactions and reply affordances 2026-05-10 18:48:55 +03:00
e80453785e feat(channels): ship M4 per-thread unread with thread receipts mute-aware atom and architectural cleanup 2026-05-10 14:35:51 +03:00
307af24d1e feat(channels): ship M3 channel timeline avatar-name layout with thread summary cards and drawer header counter polish 2026-05-10 01:06:29 +03:00
4632be30f7 feat(channels): ship M2 thread drawer + composer + shareable thread URL with cold-load relations repair 2026-05-09 22:49:53 +03:00
851f3d30a3 feat(channels): drop NEW badge from Каналы segment per product call 2026-05-09 15:09:24 +03:00
075b6cf69c fix(android): redirect to root on logout to dodge Capacitor SPA-fallback failure on URL-encoded path segments 2026-05-09 15:07:35 +03:00
efe58dc2e2 feat(channels): ship M1 — Каналы segment with /channels/ routes and channels-mode RoomTimeline filter 2026-05-09 15:06:13 +03:00
c6eba1e935 chore(deps): bump matrix-js-sdk 40.2.0 → 41.4.0 with SessionMembershipData barrel and EC sticky-event capability extension 2026-05-09 13:00:45 +03:00
3ea01a9c3f chore(deps): bump matrix-js-sdk 39.4.0 → 40.2.0 adapting sessionMembershipsForRoom removal and dedup-key trichotomy 2026-05-09 12:18:02 +03:00
0d93a223d0 chore(deps): bump matrix-js-sdk 38.4.0 → 39.4.0 adapting MatrixRTCSession.slotDescription rename 2026-05-09 11:33:55 +03:00
6560f0b424 chore(deps): bump matrix-js-sdk 38.2.0 → 38.4.0 2026-05-09 02:45:59 +03:00
b30704dd96 fix(boot): drop 3-dots splash menu, add 10s fetch timeouts, surface logout on init/start/sync-error per Element Web pattern 2026-05-09 02:26:16 +03:00
cd824e0c90 feat(profile): mobile top-horseshoe rail and desktop right pane host a Dawn-style user card with hero, info rows, and floating 3-dot actions menu 2026-05-08 19:04:12 +03:00
f4292611cf feat(calls): split-horseshoe call surface with redesigned ring/active pill, orbit border, custom outline icons, tap-to-room 2026-05-08 01:30:36 +03:00
d58e69d49f refactor(timeline): rebuild stream row as 3-track CSS grid with auto-sized time column so 24h and AM/PM render with consistent gaps without JS measurement 2026-05-07 23:36:18 +03:00
997375b307 feat(timeline): square image+video bubbles with username overlay, reactions outside bubble, edge-anchored mobile rail, horizontal day divider 2026-05-07 21:24:50 +03:00
ce308776cd update docs 2026-05-06 23:12:57 +03:00
97a50e29f9 feat(bots): add bot-widget loading bar with cycle-complete hide and sync-state deference, plus matching cycle-complete polish on SyncIndicator 2026-05-06 22:11:42 +03:00
17ba496b7e fix(bots-widgets): default data-input to mouse and drop dead danger:hover rose override so hover works from frame zero on hybrid devices 2026-05-06 17:29:11 +03:00
998813eff4 refactor(direct): drop the bottom DM-nav status strip with the vojo.chat label and e2ee chip 2026-05-06 16:37:22 +03:00
ece9e922e3 fix(user-profile): round the avatar surface plate and replace its outline with a box-shadow ring so corners no longer poke past the circle 2026-05-06 16:19:47 +03:00
7af69574f4 feat(bots): render bot-shell command cards as a fixed 2-column grid with equal-height rows 2026-05-06 16:19:41 +03:00
e8865cec5f fix(direct): lock DmStreamRow title-block height so the room name stops jumping when row 2 collapses on hover 2026-05-06 16:19:34 +03:00
1d64275bae feat(bots-discord): land hCaptcha challenge handling for QR-login with sentinel-prefixed bridge protocol and Dawn-themed widget UI 2026-05-06 14:19:45 +03:00
295dfbb796 feat(bots-discord): drop the obsolete Devices step from the QR-scan navigation copy 2026-05-05 22:59:39 +03:00
526515dcde feat(bots-whatsapp): drop the unpredictable-bans paragraph from the AboutCard risk callout 2026-05-05 22:53:27 +03:00
42d9ccfbf3 feat(bots): unify command-card chrome with left-side semantic icons and fold WA Meta-ToS warning into the AboutCard modal 2026-05-05 19:59:24 +03:00
bc360e84cc feat(bots-whatsapp): land Preact widget for mautrix-whatsapp QR + pairing-code login, Meta-ToS warning card, and cross-iframe external-link relay 2026-05-05 15:25:16 +03:00
5eb12f888b feat(bots-discord): land Preact widget for mautrix-discord QR-login with ping-based status, reconnect recovery, and discordapp.com URL parser 2026-05-05 02:17:30 +03:00
156570826a feat(bots-telegram): land QR-code login flow rendered client-side from m.image body via qrcode-generator with bridge-race-tolerant state machine 2026-05-05 01:02:36 +03:00
e46bba2f7d feat(bots): polish the Telegram bot widget UI and fix Android WebView sticky-hover via pointerType-based input-mode detection 2026-05-04 18:34:51 +03:00
b2f3b668c5 feat(splash): hold Android system splash on screen until web mascot paints via custom LaunchSplash plugin 2026-05-04 18:31:10 +03:00
817dad383c remove stale plan 2026-05-03 23:56:28 +03:00
949860bc1a feat(connection): replace 30s 'Connecting...' banner with bottom-edge sync indicator and resume-grace window for phone-unlock UX 2026-05-03 22:35:22 +03:00
f102593081 Revert "feat(connection): replace 30s 'Connecting...' banner with bottom-edge sync indicator and homeserver footer dot, drop mascot loading splash"
This reverts commit a1ff5db724.
2026-05-03 20:15:26 +03:00
e547c466a8 feat(settings): drop user-facing time/date format toggle and derive everything from system locale via Intl.DateTimeFormat 2026-05-03 19:27:54 +03:00
a1ff5db724 feat(connection): replace 30s 'Connecting...' banner with bottom-edge sync indicator and homeserver footer dot, drop mascot loading splash 2026-05-03 18:21:00 +03:00
8f49124043 feat(ui): force every user and room avatar to render as a circle via globalStyle override on the folds Avatar wrapper 2026-05-03 14:48:27 +03:00
ed1544dd5e feat(direct): land inline invite cards with spam-filter toggle and retire the /inbox/ tree along with its sidebar tab 2026-05-03 13:49:33 +03:00
bae6761683 feat(bots): render Matrix-native bot avatar in BotCard sidebar row and BotShellHero so server-side avatar_url propagates without client patches 2026-05-03 13:22:10 +03:00
316c3eb9fd feat(bots-telegram): land M12.5 timeline-resume hydrate to recover pending login forms after widget reload via read_events scan 2026-05-03 02:36:17 +03:00
35ade7e941 feat(bots-telegram): ship M12 login flow with BotShell host hero and Go bridgev2 dialect parser 2026-05-02 22:12:37 +03:00
e43b0fb597 feat(bots-telegram): land Phase 3 widget scaffold with Dawn UI, dev config overlay, and prod origin allowlist 2026-05-02 13:22:25 +03:00
d961dddfbc feat(bots): land Phase 2 widget host/driver with retry UX and route-aware notifications 2026-05-02 00:44:52 +03:00
83e246da1f Add runtime-configured bot tab 2026-05-01 20:21:55 +03:00
357a2024f4 feat(bots): M1 wire Direct segment to /bots/ placeholder and rename label to Роботы 2026-05-01 14:42:00 +03:00
96085ba6a1 Update auth footer branding 2026-04-29 23:12:08 +03:00
b5ea37d57a feat(direct): use compact native phone layout for new chat to keep form fitted under the on-screen keyboard 2026-04-29 22:30:39 +03:00
212d3e3482 feat(invite): split user id into username and server fields and close prompt on successful invite 2026-04-29 21:45:33 +03:00
3cd1611ee2 fix(call): restore Android CallStyle banner for DM voice calls in encrypted rooms 2026-04-29 13:45:13 +03:00
d2c77496a7 redesign(p4): land Dawn RoomViewHeader for all rooms with peer chrome, presence, member-count subline, and reactive bridge gate. 2026-04-29 01:03:12 +03:00
103d6ad8a1 redesign(p3c): collapse Home into universal Direct, drop legacy layouts, gate room flavour on member-count, and clear orphan settings. 2026-04-28 21:52:31 +03:00
5bf0aeb00b redesign(p3a): land Stream message layout for DMs with rail, author dots, asymmetric bubbles, Stream day-divider, and sysline state events 2026-04-28 00:54:53 +03:00
e230e688de chore: upgrade TypeScript to 5.4 with bundler module resolution and reformat repo against tightened ESLint 2026-04-27 13:07:49 +03:00
ed3e5c0640 redesign(p2): rebuild DM list panel with stream header, segmented tabs, self-row, new-chat row, footer status, and live timeline rerender 2026-04-26 23:34:20 +03:00
0c89e9fda0 redesign(p0): land Dawn dark-theme foundation with one-shot migration and edge-to-edge polish 2026-04-26 21:30:22 +03:00
52bf23cf38 update docs 2026-04-26 19:48:13 +03:00
1c079ddca2 fix(nav): collapse push-tap, tab and back-arrow navigations into the back stack via replace 2026-04-26 00:35:26 +03:00
dbda8728a8 fix(auth): keep form within visible band on small viewports 2026-04-25 23:43:11 +03:00
6ab905a7c3 fix(auth): stabilize compact auth form scrolling 2026-04-25 20:18:38 +03:00
6c65dee82e Replace 'following' banner with WhatsApp-style delivery status checkmarks on own messages. 2026-04-25 17:49:51 +03:00
3652320b0f Split DM create form into separate username and server fields with smart defaults. 2026-04-25 15:41:29 +03:00
0466e78233 Simplify theme settings to a single dropdown with System, Light, and Dark options. 2026-04-25 13:36:40 +03:00
6fadff26c2 Use commit count as patch version instead of raw git describe output. 2026-04-25 12:53:18 +03:00
c164dacf18 Stop tracking docs/ai/desired_features.md, already in .gitignore. 2026-04-25 12:38:26 +03:00
ff9d15f930 Auto-scroll chat timeline to bottom when Android keyboard opens. 2026-04-25 12:35:02 +03:00
adb0012834 Fix DM rooms showing as regular rooms for invited users by syncing m.direct on join and routing push navigation correctly. 2026-04-25 12:04:30 +03:00
f96c80f829 Localize room intro, membership events, call controls, and leave-room dialog across en/ru via i18next. 2026-04-25 01:28:07 +03:00
8463180c04 Move push_strings.xml generation into Gradle AGP task so XMLs are never committed and direct gradle builds work without npm run android:sync. 2026-04-25 00:36:07 +03:00
15f86bfcae Localize invite push notifications across web SW and Android FCM via shared JSON Push namespace. 2026-04-24 23:51:07 +03:00
d15a3b336b Harden DM call teardown with io.element.close listener, 8s hangup timeout, and same-room zombie escape so stale embeds stop blocking retries. 2026-04-24 21:20:05 +03:00
cf0bf56541 Replace FCM foreground-skip cache with Java ring registry to fix silent ring on mid-ring backgrounding and eliminate dual dismiss plane 2026-04-24 01:47:03 +03:00
a35dfb1a5b Use Capacitor pause/resume events on Android to gate appActive at the same lifecycle edge as MainActivity.isInForeground. 2026-04-23 22:00:26 +03:00
aaebdffc4d Track declined notification IDs and re-check them after resolveCallId await to block stale rings when a decline lands during the async yield. 2026-04-23 19:57:27 +03:00
fb6f7bd982 Gate native CallStyle dismiss on App.isActive so background incoming rings keep ringing instead of being prematurely cleared by /sync atom ADD. 2026-04-23 01:27:51 +03:00
0c30e37b70 Gate incoming-call ring audio on App.isActive to stop double-ring in the brief window after app backgrounding 2026-04-23 00:50:55 +03:00
0692c05408 Add microphone foreground service to keep Android DM calls alive under lock screen 2026-04-23 00:04:16 +03:00
ff1bf9c377 Enforce DM call switching and foreground native call ownership 2026-04-22 21:27:54 +03:00
6b8228cdca user dm links 2026-04-21 21:11:32 +03:00
13ec1e0054 dm calls mvp: phase 5.35 polish: cancel notif on null session, redact token-leak in JSON parse log, guard drain from resume race 2026-04-20 23:41:29 +03:00
91e3dd95ec add automatic app version and park git init tag from vojo/dev 2026-04-20 22:58:38 +03:00
cb16edbf37 dm calls mvp: phase 2.5.3: route call-notif FSI launch and body-tap to Direct tab instead of Home 2026-04-20 22:24:07 +03:00
63e9c65dd2 dm calls mvp: phase 5.35: decline via BroadcastReceiver with session bridge, pending-declines flusher, allowBackup=false 2026-04-20 22:23:44 +03:00
53a8bda18f dm calls mvp: phase 2.5.3: lockscreen CallStyle with FSI, Answer/Decline, ringtone channel, permission prompt 2026-04-20 22:22:43 +03:00
aa958b6e76 dm calls mvp: phase 2.5.2: drop dead code after SW-only pivot (invite.ogg, usePreviousValue, usePermission, notificationPermission) 2026-04-19 21:34:04 +03:00
6a690d4ecc dm calls mvp: phase 2.5.2: SW push is the only OS notification channel; in-app banners removed, calls bypass the visible-tab gate 2026-04-19 21:32:41 +03:00
6ced8246e6 dm calls mvp: phase 2: handle ring/decline + timeline render in encrypted DMs 2026-04-19 15:26:37 +03:00
79bd0ccc4d dm calls mvp: phase 2: incoming call strip + A-side auto-hangup on decline, peer-leave, no-answer 2026-04-19 13:59:33 +03:00
f862f19f09 dm calls mvp: phase 2: mvp of accept/decline calls (contain bugs) 2026-04-19 10:11:09 +03:00
dfcd69f1df dm calls mvp: phase 1: outgoing DM voice call via element-call voiceOnly intent 2026-04-18 23:31:42 +03:00
30c59e199d dm calls mvp: phase 0: fix CallEmbed listener leak by caching bound refs in class fields 2026-04-18 21:32:07 +03:00
380a4d7d70 update vive code tools with plans dir 2026-04-18 20:02:13 +03:00
20ac745ce9 workaournd about boxes stable size when stretching up auth page 2026-04-18 15:01:57 +03:00
03267cfc35 fix fonts/white boxes when autofll auth page 2026-04-18 14:05:13 +03:00
02d9c8cc76 remove bugs md from git index & update gitignore 2026-04-18 01:43:11 +03:00
5947cd5b82 update background colors 2026-04-18 01:39:28 +03:00
3717adb52e feature with back button on native app 2026-04-18 01:30:48 +03:00
46981791a3 update gitignore with desired features 2026-04-18 01:00:33 +03:00
ce6afe1de8 add server side doc & some info about config files 2026-04-17 23:46:24 +03:00
c7c47ec23a move all ai docs to folder 2026-04-17 23:42:04 +03:00
6fbcf94cd9 push notifications permission on start app 2026-04-17 23:31:21 +03:00
46659625cd add notifications support for android (mostly) 2026-04-17 22:54:44 +03:00
4c64b52827 update claude 2026-04-16 02:22:12 +03:00
9fe5bf3480 disable by default chat encryption 2026-04-16 02:04:47 +03:00
2b479bb7c6 default messages box in direct messages: bubble style 2026-04-16 02:02:03 +03:00
85829b2066 update claude with project specs 2026-04-16 01:57:06 +03:00
5e9560570f fix scrolling login page on native app 2026-04-16 01:55:03 +03:00
da9ead7db7 fix jumping login window layout 2026-04-16 01:47:13 +03:00
3c613d9960 icons for native capacitor webview app 2026-04-16 01:25:37 +03:00
3f6e0e0d45 add webview capacitor support 2026-04-16 01:14:09 +03:00
ac452aaf1d update icons with evil mascot 2026-04-15 00:57:56 +03:00
fdf447c9cb rework login page 2026-04-14 23:19:44 +03:00
18e09b81a2 change mascot background 2026-04-14 22:32:30 +03:00
9ac226002c localize logout 2026-04-14 22:27:23 +03:00
d933587d09 update colors for dark theme 2026-04-14 22:11:31 +03:00
c7a2d723ec update claude md with project archicture 2026-04-14 22:01:17 +03:00
becdbc3d3a localize room settings 2026-04-14 21:27:03 +03:00
d120a9a933 localize Add Space 2026-04-14 02:18:51 +03:00
f4e94a48cc localize Explore Community 2026-04-14 01:59:41 +03:00
ef0fa23642 localize Inbox/Notifications 2026-04-14 01:41:22 +03:00
77fda29820 localize direct messages 2026-04-14 01:20:26 +03:00
fc8346e50d localize home 2026-04-14 00:30:08 +03:00
7f4a013eb5 search localization 2026-04-14 00:09:24 +03:00
d4ea8b525f localize settings 2026-04-13 23:36:03 +03:00
96613c7a76 locale for register form 2026-04-13 22:39:59 +03:00
4539ab5114 rebrending contination: add icons 2026-04-13 22:22:01 +03:00
ff133b22b5 rebranding from cinny to vojo: change basic icons and naming 2026-04-13 02:22:44 +03:00
ae650b94b8 fix layout for login page with styles css 2026-04-13 00:47:20 +03:00
303d3c74c1 feat(auth): rebrand auth pages to Vojo with mascot, glassmorphism, and i18n
Rework the entire authentication UI to match the Vojo brand:

- Add mascot video with purple gradient halo behind the auth form
- Glassmorphism card (backdrop-blur, semi-transparent bg) with JS-driven
  layout ported from element-web (ResizeObserver + requestAnimationFrame)
- Custom folds theme overrides via color token references (not hardcoded
  CSS variable hashes) for transparent inputs and white primary button
- Server edit modal dialog replacing browser prompt, with proper
  role="dialog", aria-modal, and Escape key support
- Footer: "Powered by Matrix · Hosted on Yandex Cloud"

Localization:
- Add ru.json, update en.json and de.json with all auth page keys
- Wire up react-i18next t() across all auth components
- Set fallbackLng to 'ru' while preserving LanguageDetector for en/de

Cleanup:
- Remove SSO login flow (SSOLogin, getSSOFlow, SSO rendering)
- Remove token login flow (TokenLogin.tsx, getTokenFlow, loginToken param)
- Strip unused imports (useState, usePathWithOrigin, useClientConfig)
- Fix ESLint: nested ternary → if-return, consistent-return, a11y

Config: vojo.chat as default and only homeserver.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 00:27:34 +03:00
06a94c3f54 update gitignore 2026-04-13 00:14:04 +03:00
389a396507 update git ignore with vscode 2026-04-12 21:59:32 +03:00
87669a209a add promt md for agents 2026-04-12 21:58:54 +03:00
DJ Chase
575511ec89 chore: add matrixrooms.info to directory list (#2844)
Some checks failed
Deploy to Netlify (dev) / Deploy to Netlify (push) Has been cancelled
* chore: add matrixrooms.info to directory list

matrixrooms.info is a directory of all public Matrix rooms it can find,
regardless of homeserver. It is much larger than the morg directory,
so is more useful as a general search
2026-03-28 17:35:53 +11:00
Krishan
be7dae790b chore: make error more useful and understandable (#2859)
* chore: make error more useful and understandable

* chore: use similar wording
2026-03-27 21:22:46 +11:00
ranidspace
955f1f89a1 feat: add YYYY-MM-DD (ISO 8601) date format to presets (#2712)
* Add YYYY-MM-DD (ISO 8601) date format to presets

* Fix formatting due to added date format
2026-03-27 21:20:10 +11:00
Krishan
856b7b4f05 chore: add notice about SDK replacement (#2778) 2026-03-25 12:10:15 +11:00
DJ Chase
abcfa4541d chore: add 'Stickers and Emojis' as featured space (#2842)
* Mention CLA in CONTRIBUTING.md

Closes: #2146

* add: 'Stickers and Emojis' to config.json

Add #stickers-and-emojis:tastytea.de (space) to config.json
2026-03-25 12:09:16 +11:00
Krishan
3fbbcf0bc8 fix: remove typo in no rooms UI (#2834) 2026-03-23 16:57:52 +11:00
Krishan
2d7bd3ed63 chore: group related package update together (#2833) 2026-03-23 16:49:22 +11:00
Krishan
38a15e1ad0 chore: use private vulnerability disclosure (#2827) 2026-03-22 18:29:09 +11:00
Krishan
0f91bc77f3 chore: fix link in issue triage template (#2826)
* chore: fix link in issue triage template

* chore: delete .github/PULL_REQUEST_TEMPLATE.md
2026-03-22 18:20:33 +11:00
Krishan
e059f129f4 chore: add new issue triage discussion template (#2825)
* chore: add new issue triage discussion template

* chore: ask for desktop app version as well

* chore: create preapproved.md
2026-03-22 17:55:53 +11:00
Krishan
3795aaf504 chore: add git author to the sem release (#2815) 2026-03-21 12:07:52 +11:00
Krishan
ddf0ed7493 chore: add semantic release (#2759)
* chore: install deps related to semantic release

* chore: add husky config

* ci: add a script to update version number on new release

* ci: update ci/cd to include semantic release changes

* chore: merge dev to semantic-release
2026-03-19 16:26:25 +11:00
DJ Chase
aa2e6744d7 fix: Mention CLA in CONTRIBUTING.md (#2804)
Mention CLA in CONTRIBUTING.md

Closes: #2146
2026-03-19 11:41:41 +11:00
Ajay Bura
e65a898074 fix: prevent codeblock filename drop on edit (#2780)
prevent codeblock filename drop on edit
2026-03-15 15:37:14 +11:00
Jan Jurzitza
7549ce1bec feat: allow using filenames in codeblocks (#2455)
Allow using filenames in codeblocks

- If there is a dot in the language name, we instead treat the first line after ``` as the filename and everything after the last dot as the language
- we use a custom "data-label" attribute on the code block to specify the name of the file (so only compatible with cinny from this point onwards)
2026-03-14 18:54:03 +11:00
Krishan
4e0fd65dec chore: batch slate related deps (#2775) 2026-03-14 17:22:57 +11:00
Ajay Bura
846ddb7c68 fix: hover state on url preview image and make it keyboard friendly (#2777)
add hover state on url preview image and make it keyboard friendly
2026-03-14 17:22:18 +11:00
LeaPhant
d6cb51387f Show image viewer when clicking url preview thumbnail (#2309)
* Show large image overlay when clicking url preview thumbnail

* Move image overlay into its own component

* Move ImageOverlay props into extended type

* Remove export for internal type
2026-03-14 11:04:55 +05:30
Krishan
a1053ce8f1 chore(release): v4.11.1 [skip ci] (#2765)
* chore(release): 4.11.0 [skip ci]

* chore(release): 4.11.1 [skip ci]
2026-03-11 23:07:37 +11:00
Krishan
3d7a449d57 chore(deps): Update slate deps to 0.123.0 (#2764)
Update slate deps to latest
2026-03-11 23:06:53 +11:00
Krishan
d24ad8374b chore(release): 4.11.0 [skip ci] 2026-03-11 04:03:48 +00:00
Ajay Bura
971c6ef870 chore(deps): update folds to 2.6.2 (#2762)
update folds to 2.6.2
2026-03-10 23:03:03 +11:00
Ajay Bura
40432768e8 fix: call ui imorovements (#2749)
* fix member button tooltip in call room header

* hide sticker button in room input based on chat window width

* render camera on off data instead of duplicate join messages

* hide duplicate call member changes instead of rendering as video status

* fix prescreen message spacing
2026-03-10 22:45:26 +11:00
Krishan
6acc2e9b3f chore: add semantic commits to renovate configuration (#2760)
* Add semantic commits to Renovate configuration

* Fix formatting issue in renovate.json
2026-03-10 14:57:20 +11:00
Krishan
149d53b9c6 chore(deps): continue action if login fails (#2758)
chore(action): continue action if login fails
2026-03-10 12:26:55 +11:00
Krishan
44f01c0fed chore: enable semantic check on PR title (#2447)
* Enable semantic check on PR title

* Update semantic pull request action version

* Update PR trigger types in workflow configuration

Removed 'reopened' and 'synchronize' types from pull request triggers.
2026-03-10 12:26:25 +11:00
Ajay Bura
5a3f7fffad Show call support error and disable join button (#2748)
* allow user to end call if error when loading

* show call support missing error if livekit server is not provided

* prevent joining from nav item double click if no livekit support
2026-03-09 21:39:58 +11:00
Ajay Bura
4e4170793e Fix crash with bad location uri (#2746)
fix crash with bad location uri
2026-03-09 18:17:15 +11:00
Ajay Bura
503f3c401c Fix recent emoji does not persist (#2722)
Fix recent emoji are not getting saved

Refactor recent emoji retrieval to ensure structured cloning and proper type checking. The sdk was not updating account data because we are mutating the original and it compare and early return if found same.
2026-03-09 17:34:44 +11:00
Ajay Bura
b2ea3b41e0 Add own control buttons for element-call (#2744)
* add mutation observer hok

* add hook to read speaking member by observing iframe content

* display speaking member name in call status bar and improve layout

* fix shrining

* add joined call control bar

* remove chat toggle from room header

* change member speaking icon to mic

* fix joined call control appear in other

* show spinner on end call button

* hide call statusbar for mobile view when room is selected

* make call statusbar more mobile friendly

* fix call status bar item align
2026-03-09 08:34:48 +05:30
Ajay Bura
a6f5c3e842 Display call member speaking status on bottom bar (#2742)
* add mutation observer hok

* add hook to read speaking member by observing iframe content

* display speaking member name in call status bar and improve layout

* fix shrining
2026-03-08 22:00:35 +11:00
Ajay Bura
fad999c580 Apply deafen state when call member changes (#2737)
* fix deafen not working

* apply deafen state when call member changes

* remove unnecessary condition
2026-03-08 14:22:11 +11:00
Ajay Bura
435b610cc7 Downgrade matrix-widget-api from 1.17.0 to 1.13.0 (#2736)
Some user was having Disconnection Error
2026-03-07 20:47:45 +11:00
Krishan
a0e9ba0f55 feat: Add voice/video room support (#2680)
* Add users on the nav to showcase call activity and who is in the call

* add check to prevent DCing from the call you're currently in...

* Add avatar and username for the space (needs to be moved into RoomNavItem proper)

* Add background variant to buttons

* Update hook to keep method signature (accepting an array of Rooms instead) to support multiple room event tracking of the same event

* Add state listener so the call activity is real time updated on joins/leaves within the space

* Add RoomNavUser for displaying the user avatar + name in the nav for a visual of call activity and participants

* rename CallNavBottom to CallNavStatus

* Rename callnavbottom and fix linking implementation to actually be correct

* temp fix to allow the status to be cleared in some way

* re-add background to active call link button

* prepare to feed this to child elements for visibility handling

* loosely provide nav handling for testing refactoring

* Add CallView

* Update to funnel Outlet context through for Call handling (might not be the best approach, but removes code replication in PersistentCallContainer where we were remaking the roomview entirely)

* update client layout to funnel outlet the iframes for the call container

* funnel through just iframe for now for testing sake

* Update room to use CallView

* Pass forward the backupIframeRef now

* remove unused params

* Add backupIframeRef so we can re-add the lobby screen for non-joined calls (for viewing their text channels)

* Remove unused imports and restructure to support being parent to clientlayout

* Re-add layout as we're no longer oddly passing outlet context

* swap to using ref provider context from to connect to persistentcallcontainer more directly

* Revert to original code as we've moved calling to be more inline with design

* Revert to original code as we've moved the outlet context passing out and made more direct use of the ref

* Fix unexpected visibility in non-room areas

* correctly provide visibility

* re-add mobile chat handling

* Improve call room view stability

* split into two refs

* add ViewedRoom usage

* Disable

* add roomViewId and related

* (broken) juggle the iframe states proper... still needs fixing

* Conditionals to manage the active iframe state better

* add navigateRoom to be in both conditions for the nav button

* Fix the view to correctly display the active iframe based on which is currently hosting the active call (juggling views)

* Testing the iframe juggling. Seems to work for the first and second joins... so likely on the right path with this

* add url as a param for widget url

* fix backup iframe visibility

* Much closer to the call state handling we want w/ hangups and joins

* Fix the position of the member drawer to its correct location

* Ensure drawer doesn't appear in call room

* Better handling of the isCallActive in the join handler

* Add ideal call room join behavior where text rooms to call room simply joins, but doesn't swap current view

* Fix mobile call room default behavior from auto-join to displaying lobby

* swap call status to be bound to call state and not active call id

* Remove clean room ID and add default handler for if no active call has existed yet, but user clicks on show chat

* Applies the correct changes to the call state and removes listeners of old active widget so we don't trigger hang ups on the new one (the element-call widget likes to spam the hang up response back several times for some reason long after you tell it to hang up)

* Remove superfluous comments and Date.now() that was causing loading... bug when widgetId desynced

* Remove Date.now() that was causing widgetId desync

* add listener clearing, camel case es lint rule exception, remove unneeded else statements

* Remove unused

* Add widgetId as a getWidgetUrl param

* Remove no longer needed files

* revert ternary expression change and add to dependency array

* add widgetId to correct pos in getWidgetUrl usage

* Remove CallActivation

* Move and rename RoomCallNavStatus

* update imports and dependency array

* Rename and clean up

* Moved CallProvider

* Fix spelling mistake

* Fix to use shorthand prop

* Remove unneeded logger.errors

* Fixes element-call embedded support (but it seems to run poorly)

* null the default url so that we fallback to the embedded version (would recommend hosting it until performance issue is determined)

* Fix vite build to place element-call correctly for embedded npm package support

* add vite preview as an npm script

* Move files to more correct location

* Add package-lock changes

* Set dep version to exact

* Fix path issue from moving file locations

* Sets initial states so the iframes don't cause the other to fail with the npm embedded package

* Revert navitem change

* Just check for state on both which should only occur at initial

* Fixes call initializing by default on mobile

* Provides correct behavior when call isn't active and no activeClientWidgetApi exists yet

* Corrects the state for the situations where both iframes are "active" (not necessarily visible)

* Reduce code reuse in handleJoin

* Seems to sort out the hangup status button bug the occurred after joining a call via lobby

* Re-add the default view current active room behavior

* Remove repetitive check

* Add storing widget for comparing with (since we already store room id and the clientWidgetApi anyway)

* Update rendering logic to clear up remaining rendering bug (straight to call -> lobby of another room and joining call from that interface -> lobby of that previous room and joining was leading to duplication of the user in lobbies. This was actually from listening to and acknowledging hangups from the viewed widget in CallProvider)

* Prevent null rooms from ever rendering

* This seems to manage the hangup state with the status bar button well enough that black screens should never be encountered

* Remove viewed room setting here and pass the room to hang up (seems state doesn't update fast enough otherwise)

* Remove unused

* Properly declare new hangup method sig

* Seems to avoid almost all invalid states (hang up while viewing another lobby and hitting join seems to black screen, sets the active call as the previous active room id, but does join the viewed room correctly)

* Fix for cases where you're viewing a lobby and hang up your existing call and try to join lobby (was not rendering for the correct room id, but was joining the correct call prior)

* Re-add intended switching behavior

* More correct filter (viewedRoom can return false on that compare in some cases)

* Seems to shore up the remaining state issues with the status bar hangup

* Fix formatting

* In widget hang up button should be handled correct now

* Solves the CHCH sequence issue, CLJH remains

* Fixes CLJH, found CCH

* Solves CCH. Looks like CLCH left

* A bit of an abomination, but adds a state counter to iteratively handle the diverse potential states (where a user can join from the nav bar or the join button, hang up from either as well, and account for the juggling iframes)
Black screens shouldn't be occurring now.

* Fix dependency array

* Technically corrects the hangup button in the widget, should be more precise though

* Bind the on messaging iframe for easier access in hangup/join handling

* Far cleaner and more sensible handling of the call window... I just really don't like the idea of sending a click event, but right now the element-call code treats preload/skipLobby hangups (sent from our end) as if they had no lobby at all and thus black screens. Other implementation was working around that without just sending a click event on the iframe's hangup button.

* Fixes a bug where if you left a call then went to a lobby and joined it didn't update the actual activeCallRoomId

* Fixes complaints of null contentDocument in iframe

* Update to use new icons (thank you)

* Remove unneeded prop

* Re-arrange more options and add checks for each option to see if it is a call room (probably should manage a state to see if a header is already on screen and provide a slightly modified visual based on that for call rooms)

* Invert icons to show the state instead of the action they will perform (more visual clarity)

* Update src/app/features/room-nav/RoomCallNavStatus.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomCallNavStatus.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomCallNavStatus.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomCallNavStatus.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomCallNavStatus.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomCallNavStatus.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomNavItem.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room/RoomViewHeader.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomNavItem.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomNavItem.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomNavItem.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomNavItem.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomNavItem.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomNavItem.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomNavUser.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomNavUser.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room-nav/RoomNavUser.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/pages/client/space/Space.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/call/CallView.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/call/CallView.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/call/CallView.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room/RoomView.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room/RoomView.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* adjust room header for calling

* Remove No Active Call text when not in a call

* update element-call version

* Update src/app/features/room/RoomViewHeader.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room/RoomViewHeader.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room/RoomViewHeader.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room/RoomViewHeader.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Update src/app/features/room/RoomViewHeader.tsx

Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>

* Revert most changes to Space.tsx

* Show call room even if category is collapsed

* changes to RoomNavItem, RoomNavUser and add useCallMembers

* Rename file, sprinkle in the magic one line for matrixRTCSession. and remove comment block

* swap userId to callMembership as a prop and add a nullchecked userId that uses the membership sender

* update references to use callMembership instead

* Simplify RoomNavUser

Discard future functionality since it probably won't exist in time for merging this PR

* Simplify RoomViewHeader.tsx

Remove unused UI elements that don't have implemented functionality. Replace custom function for checking if a room is direct with a standard hook.

* Update Room.tsx to accomodate restructuring of Room, RoomView and CallView

* Update RoomView.tsx to accomodate restructuring of Room, RoomView and CallView

* Update CallView.tsx to accomodate restructuring of Room, RoomView and CallView + suggested changes

* add call related permissions to room permissions

* bump element call to 0.16.3, apply cinny theme to element call ui, replace element call lobby (backup iframe) with custom ui and only use element call for the in-call ui

* update text spacing

* redo roomcallnavstatus ui, force user preferred mute/video states when first joining calls, update variable names and remove unnecessary logic

* set default mic state to enabled

* clean up ts/eslint errors

* remove debug logs

* format using prettier rules from project prettierrc

* fix: show call nav status while active call is ongoing

* fix: clean up call nav/call view console warnings

* fix: keep call media controls visible before joining

* fix: restore header icon button fill behavior

Fixes regression from b074d421b66eb4d8b600dfa55b967e6c4f783044.

* style: blend header and room input button styles in call nav

* fix page header background color on room view header

* fix: permissions and room icon resolution (#2)

* Initialize call state upon room creation for call rooms, remove subsequent useless permission

* handle case of missing call permissions

* use call icon for room item summary when room is call room

* replace previous icon src resolution function with a more robust approach

* replace usages of previous icon resolution function with new implementation

* fix room name not updating for a while when changed

* set up framework for room power level overrides upon room creation

* override join call permission to all members upon room creation

* fix broken usages of RoomIcon

* remove unneeded import

* remove unnecessary logic

* format with prettier

* feat: show connected/connecting call status

* fix: preserve navigation context when opening non-call rooms

* fix: reset room name state when room instance changes

* feat: Disable webcam by default using callIntent='audio'

* Add channel type selecor

* Add option for voice rooms, which for now sets the default selected
option in the creation modal

* Add proper support for room selection from the enu

* Move enums to `types.ts` and change icons selection to use
`getRoomIconSrc`

* fix: group duplicate conditions into one

* fix: typo

* refactor: rename kind/voice to access/type and simplify room creation

- rename CreateRoomVoice to CreateRoomType and modal voice state to type
- rename CreateRoomKind to CreateRoomAccess and KindSelector to AccessSelector
- propagate access/defaultAccess through create room and create space forms
- set voice room power levels via createRoom power_level_content_override

* refactor: unify join rule icon mapping and update call/space icons

- bump folds from 2.5.0 to 2.6.0
- replace separate room/space join-rule icon hooks with useJoinRuleIcons(roomType)
- route join-rule icons through getRoomIconSrc for consistent room type handling
- simplify getRoomIconSrc by removing the locked override path
- use VolumeHighGlobe for public call rooms and VolumeHighLock for private call rooms

* chore(deps): bump matrix-widget-api to 1.17 and remove react-sdk-module-api

* fix: adapt SmallWidget to matrix-widget-api 1.17.0 API

* fix: render call room chat only when chat panel is open

* fix(permissions): show call settings permissions only for call rooms

* refactor: remove redundant room-nav props/guards and minor naming cleanup

* fix: use PhoneDown icon for hang up action

* chore(hooks): remove unused useStateEvents hook

* fix(room): enable members drawer toggle in desktop call rooms

- show filled User icon when the drawer is open

* Revert "fix: adapt SmallWidget to matrix-widget-api 1.17.0 API"

This reverts commit a4c34eff8a53669afa095be41b26f3e54adab0ed.

* fix: semi-revert matrix-widget-api 1.17 bump and migrate to 1.13 API

* fix(call): wait for Element Call contentLoaded before widget handshake

- fixes not working on firefox

* fix missing imports

* improve create room type design and add beta badge for voice room

* add beta badge for voice room in space lobby

* fix create room modal title

* pass missing roomType param to roomicon component

* add roomtype

* Add deafen functionality (#2695)

* feat:(deafen functionality)

* feat:(reworked voice controls for deafen)

* ref:(use muted instead of volume for deafen)

* fix:(backpedal audio_enabled rename)

* ref:(renaming of deafened vars)

* add stack avatar component

* add call status bar - WIP

* remove call status from navigation drawer

* fix deprecated method use in use call members hook

* render new call status bar

* move call widget driver to plugins

* remove old status bar usage from navigation drawer

* add call session and joined hook

* remove unknown changes

* upgrade widget api

* add element call embed plugin

* remove unknown change

* add call embed atom

* add call embed hooks and context

* add call embed provider

* replace old call implementation

* stop joining other call on second click if already in a call

* refactor embed placement hook

* add merge border prop to sequence card

* add call preferences

* add prescreen to call view - WIP

* prevent joining new call if already in call

* make call layout adaptive

* render call chat as right panel

* show call members in prescreen

* render call join leave event in timeline

* remove unknown rewrite in docker-nginx file

* render call event without hidden event enable

---------

Co-authored-by: Gigiaj <gigiaboone@yahoo.com>
Co-authored-by: Jaggar <18173108+GigiaJ@users.noreply.github.com>
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
Co-authored-by: Gimle Larpes <gimlelarpes@gmail.com>
Co-authored-by: YoJames2019 <jamesclark1700@gmail.com>
Co-authored-by: YoJames2019 <yobiscuit0@gmail.com>
Co-authored-by: hazre <mail@haz.re>
Co-authored-by: haz <37149950+hazre@users.noreply.github.com>
Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Co-authored-by: James <49845975+YoJames2019@users.noreply.github.com>
Co-authored-by: James Reilly <jreilly1821@gmail.com>
Co-authored-by: Tymek <vonautymek@gmail.com>
Co-authored-by: Thedustbuster <92692948+Thedustbustr@users.noreply.github.com>
2026-03-07 18:03:32 +11:00
renovate[bot]
2b94b5bb47 chore(deps): update actions/setup-node action to v6.3.0 (#2727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-04 20:10:28 +11:00
Krishan
ee787b3b3f Update deploy PR workflow name and run name (#2728) 2026-03-04 20:07:16 +11:00
Krishan
1204859a0b Update deploy PR workflow name to include PR number (#2726) 2026-03-04 19:30:36 +11:00
dependabot[bot]
de3d6457a0 Bump dawidd6/action-download-artifact from 15 to 16 (#2719)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 15 to 16.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](fe9d59ce33...2536c51d3d)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-version: '16'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-04 19:08:23 +11:00
dependabot[bot]
97cf03189e Bump actions/upload-artifact from 6.0.0 to 7.0.0 (#2718)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6.0.0...v7.0.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-04 18:50:22 +11:00
Krishan
0f435acb4e Add more docker related action checks (#2724)
* Pin all the action deps to SHA

* Add more docker related action checks

* Limit Docker build platforms to linux/amd64

Updated Docker build action to target only linux/amd64 platform.
2026-03-04 18:31:54 +11:00
Krishan
9fecf64915 Pin all the action deps to SHA (#2725) 2026-03-04 12:31:36 +11:00
Tulir Asokan
13b4aa5f40 Fix invalid matrix.to event link generation (#2717) 2026-03-03 20:12:45 +11:00
renovate[bot]
4bbc94cd7e chore(deps): update thollander/actions-comment-pull-request from 2.5.0 to 3.0.1 (#2698)
* chore(deps): update thollander/actions-comment-pull-request digest to e4a76dd

* pin to v3.0.1

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
2026-02-24 18:29:40 +11:00
dependabot[bot]
6cd104eee0 Bump dawidd6/action-download-artifact from 11 to 15 (#2694)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 11 to 15.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](ac66b43f0e...fe9d59ce33)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-version: '15'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-24 17:54:57 +11:00
Krishan
449b2c5a5e Release v4.10.5 (#2692) 2026-02-23 23:05:01 +11:00
renovate[bot]
9403ee2ed5 chore(deps): update docker/metadata-action action to v5.10.0 (#2690)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 22:58:20 +11:00
renovate[bot]
a7eec6ee7b chore(deps): update docker/setup-qemu-action action to v3.7.0 (#2691)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 22:57:44 +11:00
renovate[bot]
4a05d406dd chore(deps): update docker/login-action action to v3.7.0 (#2689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 22:56:52 +11:00
Krishan
bc80365ba0 Release v4.10.4 (#2688) 2026-02-23 22:32:06 +11:00
dependabot[bot]
977c1f0f74 Bump docker/setup-buildx-action from 3.11.1 to 3.12.0 (#2641)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.11.1 to 3.12.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.11.1...v3.12.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 3.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-23 18:18:01 +11:00
dependabot[bot]
df8a2f9d6d Bump docker/build-push-action from 6.18.0 to 6.19.2 (#2642)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.18.0 to 6.19.2.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.18.0...v6.19.2)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.19.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-23 18:13:17 +11:00
Krishan
acfd3fffaf Add prod-deploy.yml to Docker PR workflow paths (#2687) 2026-02-23 18:12:37 +11:00
Ajay Bura
8cf8e140d4 Verify SSO window message origin (#2686) 2026-02-23 18:08:25 +11:00
Ajay Bura
3aa4e4aaf5 fix noreferrer typo in url preview link (#2685) 2026-02-23 17:56:14 +11:00
dependabot[bot]
bd524861f4 Bump linkifyjs and linkify-react from 4.1.3 to 4.3.2 (#2682)
* Bump linkifyjs from 4.1.3 to 4.3.2

Bumps [linkifyjs](https://github.com/nfrasser/linkifyjs/tree/HEAD/packages/linkifyjs) from 4.1.3 to 4.3.2.
- [Release notes](https://github.com/nfrasser/linkifyjs/releases)
- [Changelog](https://github.com/nfrasser/linkifyjs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nfrasser/linkifyjs/commits/v4.3.2/packages/linkifyjs)

---
updated-dependencies:
- dependency-name: linkifyjs
  dependency-version: 4.3.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* update linkify react

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
2026-02-23 17:43:15 +11:00
Ajay Bura
07e352830d Set message power to moderator in space (#2684) 2026-02-23 16:57:39 +11:00
renovate[bot]
bd8935b023 chore(deps): update actions/setup-node action to v6 (#2681)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-22 18:57:23 +11:00
dependabot[bot]
8237da6041 Bump actions/upload-artifact from 4.6.2 to 6.0.0 (#2644)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 6.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.6.2...v6.0.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-22 18:26:54 +11:00
dependabot[bot]
6e06a8536a Bump actions/checkout from 4.2.0 to 6.0.2 (#2640)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.0 to 6.0.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.2.0...v6.0.2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-22 18:26:08 +11:00
Krishan
c2ae7826e5 Update node to v24.13.1 LTS (#2622)
* Update node to v24.13.1 LTS

* Fix dockerfile node version

* Simplify node and nginx version, bump nginx

* Fix casing
2026-02-22 18:15:23 +11:00
Ajay Bura
93ed896124 Add permission for managing emojis & stickers (#2678)
add permission for managing emojis & stickers
2026-02-22 15:48:23 +11:00
renovate[bot]
8658e456b3 fix(deps): update dependency folds to v2.6.1 (#2679)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-22 15:43:30 +11:00
Ajay Bura
f070b4792a fix space lobby / search selected hook not working (#2675) 2026-02-22 15:14:04 +11:00
Ajay Bura
36c7d0112b Request session info from sw if missing (#2664)
* request session info from sw if missing

* fix async session request in fetch

* respond fetch synchronously and add early check for non media requests  (#2670)

* make sure we call respondWith synchronously

* simplify isMediaRequest in sw

* improve naming in sw

* get back baseUrl check into validMediaRequest

* pass original request into fetch in sw

* extract mediaPath util and performs checks properly

---------

Co-authored-by: mmmykhailo <35040944+mmmykhailo@users.noreply.github.com>
2026-02-21 17:51:27 +11:00
Krishan
88c8b0eea8 Release v4.10.3 (#2608) 2026-02-16 22:19:21 +11:00
Rin
bab95cdd52 fix: add noreferrer to sanitized links for improved privacy consistency (#2628)
Enhance privacy by adding noreferrer to sanitized links
2026-02-16 19:54:05 +11:00
Ajay Bura
17a1c04ced fix room back button not working after router update (#2630) 2026-02-16 19:51:55 +11:00
Ajay Bura
9d28411db3 fix: image not loading on mobile after lock/unlock (#2631)
image not loading on mobile after lock/unlock
2026-02-16 19:51:09 +11:00
Krishan
5bfe61a85e Revert "fix: set m.fully_read marker when marking rooms as read" (#2629)
Revert "Set m.fully_read marker when marking rooms as read (#2587)"

This reverts commit 53a0a88e58.
2026-02-16 06:03:37 +11:00
Andrew Murphy
53a0a88e58 Set m.fully_read marker when marking rooms as read (#2587)
Previously markAsRead() only sent m.read receipts via sendReadReceipt().
This meant the read position was not persisted across page refreshes,
especially noticeable in bridged rooms.

Now uses setRoomReadMarkers() which sets both:
- m.fully_read marker (persistent read position)
- m.read receipt

Fixes issue where rooms would still show as unread after refresh.
2026-02-14 17:32:10 +11:00
Ajay Bura
d56ab14d29 Prevent invalid mxc from getting used (#2609) 2026-02-14 17:12:28 +11:00
Ajay Bura
4d2da0c030 Post session info to service worker instead of asking from sw (#2605)
post session info to service worker instead of asking from sw on each request
2026-02-14 17:11:36 +11:00
renovate[bot]
69b95a8947 fix(deps): update dependency react-router-dom to v6.30.3 (#2612)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-14 17:10:43 +11:00
Andrew Murphy
3d85501bec Fix muted rooms showing unread badges (#2581)
fix: detect muted rooms with empty actions array

The mute detection was checking for `actions[0] === "dont_notify"` but
Cinny sets `actions: []` (empty array) when muting a room, which is
the correct behavior per Matrix spec where empty actions means no
notification.

This caused muted rooms to still show unread badges and contribute to
space badge counts.

Fixes the isMutedRule check to handle both:
- Empty actions array (current Matrix spec)
- "dont_notify" string (deprecated but may exist in older rules)
2026-02-12 21:45:37 +11:00
Gimle Larpes
d19804b5eb Re-add mEvent.getSender() === mx.getUserId() check for deletion of messages (#2607)
* hide "Delete Message" if it is forbidden

* Fix the stuff I broke :/
2026-02-12 21:40:11 +11:00
renovate[bot]
22f898d4ae fix(deps): update dependency folds to v2.5.0 (#2606)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-12 16:56:47 +11:00
Gimle Larpes
9190a93cb0 Hide "Delete Message" if it is forbidden (#2602)
hide "Delete Message" if it is forbidden
2026-02-12 16:27:17 +11:00
Zach
b388167861 Replace envs.net with unredacted.org in config (#2601)
* Replace 'envs.net' with 'unredacted.org' in config

https://envs.net/ is shutting down their Matrix server

* Update defaultHomeserver and reorder servers list

* Remove 'monero.social' from homeserver list
2026-02-12 10:39:58 +11:00
Santhoshkumar044
d1a3d378dc Fix room alias mention triggering room-wide notifications (#2562)
* fix: prevent room alias mentions from triggering @room notifications

* fix: Simplify room mention to exact match on @room
2026-01-12 23:21:00 +11:00
dependabot[bot]
5a48671b19 Bump docker/login-action from 3.5.0 to 3.6.0 (#2496)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3.5.0...v3.6.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 3.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-27 16:30:39 +11:00
dependabot[bot]
c285e5f892 Bump nginx from 1.29.1-alpine to 1.29.3-alpine (#2525)
Bumps nginx from 1.29.1-alpine to 1.29.3-alpine.

---
updated-dependencies:
- dependency-name: nginx
  dependency-version: 1.29.3-alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-27 16:16:20 +11:00
willow
c122ac07de Fix typo: change "Advance Options" to "Advanced Options" (#2537) 2025-11-27 16:01:40 +11:00
Krishan
84d699ebd4 Release v4.10.2 (#2528) 2025-11-05 17:49:56 +11:00
Ajay Bura
6afd19b60d Update folds to fix broken scrollbar color (#2505) 2025-10-15 17:30:03 +11:00
Ajay Bura
3370ba41c2 Fix member are not sorted correctly after last js-sdk update (#2504) 2025-10-15 17:27:11 +11:00
Krishan
62a28e3289 Release v4.10.1 (#2495) 2025-09-29 14:34:38 +10:00
renovate[bot]
4919f58bce fix(deps): update dependency matrix-js-sdk to v38 [security] (#2493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-27 10:00:04 +05:30
dependabot[bot]
2c19f9f42f Bump softprops/action-gh-release from 2.3.2 to 2.3.3 (#2478)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.2 to 2.3.3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](72f2c25fcb...6cbd405e2c)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 2.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-26 22:24:59 +10:00
dependabot[bot]
5458a754db Bump nginx from 1.29.0-alpine to 1.29.1-alpine (#2450)
Bumps nginx from 1.29.0-alpine to 1.29.1-alpine.

---
updated-dependencies:
- dependency-name: nginx
  dependency-version: 1.29.1-alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-26 22:21:32 +10:00
dependabot[bot]
3bf374a057 Bump docker/setup-buildx-action from 3.10.0 to 3.11.1 (#2373)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.10.0 to 3.11.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.10.0...v3.11.1)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 3.11.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-26 22:19:28 +10:00
Ginger
892a11238c Add support to mark videos as spoilers (#2255)
* Add support for MSC4193: Spoilers on Media

* Clarify variable names and wording

* Restore list atom

* Improve spoilered image UX with autoload off

* Use `aria-pressed` to indicate attachment spoiler state

* Improve spoiler button tooltip wording, keep reveal button from conflicting with load errors

* Make it possible to mark videos as spoilers

* Allow videos to be marked as spoilers when uploaded

* Apply requested changes

* Show a loading spinner on spoiled media when unblurred

---------

Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
2025-09-25 13:41:35 +10:00
Mari
e92295f930 fix: Prevent IME-exiting Enter press from sending message on Safari (#2175)
On most browsers, pressing Enter to end IME composition produces this
sequence of events:
* keydown (keycode 229, key Processing/Unidentified, isComposing true)
* compositionend
* keyup (keycode 13, key Enter, isComposing false)

On Safari, the sequence is different:
* compositionend
* keydown (keycode 229, key Enter, isComposing false)
* keyup (keycode 13, key Enter, isComposing false)

This causes Safari users to mistakenly send their messages when they
press Enter to confirm their choice in an IME.

The workaround is to treat the next keydown with keycode 229 as if it
were part of the IME composition period if it occurs within a short time
of the compositionend event.

Fixes #2103, but needs confirmation from a Safari user.
2025-09-25 09:05:42 +05:30
Ajay Bura
171bf5431a Add arrow to message bubbles and improve spacing (#2474)
* Add arrow to message bubbles and improve spacing

* make bubble message avatar smaller

* add bubble layout for event content

* adjust bubble arrow

* fix missing return statement for event content

* hide bubble for event content

* add new arrow to bubble message

* fix avatar username relative alignment

* fix types

* fix code block header background

* revert avatar size and make arrow less sharp

* show event messages timestamp to right when bubble is hidden

* fix avatar base css

* move message header outside bubble

* fix event time appears on left in hidden bubles
2025-09-19 21:06:05 +10:00
Ajay Bura
74ca459cae Make emojiboard lightweight on low end devices (#2484)
* extract emoji search component

* extract emoji board tabs component

* extract sidebar component

* extract no stickers component

* create emoji/sticker preview atom

* extract component from emoji/sticker item and sidebar buttons

* fix image group icon not loading

* separate emojis and sticker groups logic

* extract layout and emoji group components

* add virtualization in emoji board groups

* fix scroll to alignment
2025-09-18 11:14:08 +10:00
Ajay Bura
d90ce5b679 fix ctrl + k hotkey not working for browser with some extensions (#2481) 2025-09-12 21:52:51 +10:00
Ajay Bura
99568dde71 fix room address checkbox prop (#2480) 2025-09-12 21:51:13 +10:00
Krishan
af63b528dc Release v4.10.0 (#2472)
* Release v4.10.0

* update version number in about
2025-08-31 21:05:38 +10:00
Ajay Bura
695e8fe933 Fix long space name shrinks three dot menu button (#2471) 2025-08-30 05:30:01 +10:00
Ajay Bura
1b89475fa7 Remove unused javascript (#2470) 2025-08-29 19:34:52 +10:00
Ajay Bura
61967e6b2c Add new ctrl/cmd - k search modal (#2467)
* add new search modal

* remove search modal from searchTab

* fix member avatar load for space with 2 member

* use media authentication when rendering avatar

* fix hotkey for macos

* add @ in username

* replace subspace minus separator with em dash
2025-08-27 22:25:49 +10:00
Ajay Bura
c43d518995 Add option for monochrome mode (#2464) 2025-08-25 23:19:14 +10:00
Ajay Bura
5efdc41757 Update userId placeholder (#2465)
* update userId placeholder

* update sample names from login username input
2025-08-25 23:18:08 +10:00
Ajay Bura
f84e756864 New create chat screen (#2463)
* fix dm invite appears in home

* use migrated function for convert to dm/room commands

* add new create chat screen
2025-08-24 22:40:44 +10:00
Ajay Bura
73bb365ddb Add option to view user avatar (#2462) 2025-08-24 22:36:45 +10:00
Ajay Bura
066212f176 Hide message button from own profile (#2461) 2025-08-24 22:35:16 +10:00
Ajay Bura
194299c510 New invite user to room dialog (#2460)
* fix 0 displayed in invite with no timestamp

* support displaying invite reason for receiver

* show invite reason as compact message

* remove unused import

* revert: show invite reason as compact message

* remove unused import

* add new invite prompt
2025-08-24 22:34:21 +10:00
Ajay Bura
4d5e98c2d1 Fix image overlap with “Mark as read” and typing indicator (#2457) 2025-08-24 22:33:20 +10:00
Ajay Bura
4a75adb7ee New add existing room/space modal (#2451) 2025-08-19 22:39:31 +10:00
Ajay Bura
f917239286 Fix message button opens left dm room (#2453) 2025-08-19 22:38:46 +10:00
Ajay Bura
3db9d6decd Fix incorrectly parsed mxid (#2452) 2025-08-19 22:36:57 +10:00
Krishan
d8467225fb Release v4.9.1 (#2446) 2025-08-17 21:08:35 +10:00
Krishan
996aa1fc1d Rename the PL 150 to Manager (#2443)
Manager seem more appropriate than Co-Founder. As Co-founder essentially have same power to Founder.
2025-08-17 16:20:17 +05:30
Ajay Bura
2e43ba8add Add new join with address prompt (#2442) 2025-08-16 21:40:39 +10:00
Ajay Bura
6062ae2132 Fix type error when accessing FileList (#2441) 2025-08-16 21:35:34 +10:00
Ajay Bura
4c5ad69ca0 Open user profile at around mouse anchor (#2440) 2025-08-16 21:34:46 +10:00
Ajay Bura
edb1664596 Hide block user button for own profile (#2439) 2025-08-16 21:32:09 +10:00
Ajay Bura
5121f810e6 Fix room v12 mention pills (#2438) 2025-08-16 21:30:52 +10:00
Ajay Bura
ab6c3e3e20 Fix missing creators support using via (#2431)
* add additional_creators in IRoomCreateContent type

* use creators in getViaServers

* consider creators in guessing perfect parent
2025-08-16 21:30:02 +10:00
Ajay Bura
941aba4f83 Open tombstone space as space (#2428) 2025-08-16 21:27:37 +10:00
Krishan
24aaba551e Release v4.9.0 (#2421) 2025-08-13 12:08:19 +10:00
Ajay Bura
ba08d0ad86 Support room version 12 (#2399)
* WIP - support room version 12

* add room creators hook

* revert changes from powerlevels

* improve use room creators hook

* add hook to get dm users

* add options to add creators in create room/space

* add member item component in member drawer

* remove unused import

* extract member drawer header component

* get room creators as set only if room version support them

* add room permissions hook

* support room v12 creators power

* make predecessor event id optional

* add info about founders in permissions

* allow to create infinite powers to room creators

* allow everyone with permission to create infinite power

* handle additional creators in room upgrade

* add option to follow space tombstone
2025-08-13 00:12:30 +10:00
Ajay Bura
63e66990f3 Redesign user profile view (#2396)
* WIP - new profile view

* render common rooms in user profile

* add presence component

* WIP - room user profile

* temp hide profile button

* show mutual rooms in spaces, rooms and direct messages categories

* add message button

* add option to change user powers in profile

* improve ban info and option to unban

* add share user button in user profile

* add option to block user in user profile

* improve blocked user alert body

* add moderation tool in user profile

* open profile view on left side in member drawer

* open new user profile in all places
2025-08-09 22:16:10 +10:00
Gimle Larpes
356cc0ff37 Minor usability improvements (#2405)
* usability improvements

* revert change

* requested change
2025-08-05 18:59:04 +05:30
Krishan
4605946695 Revert "Update dependency linkifyjs to v4.3.2 [SECURITY] (#2407)" (#2414)
This reverts commit 842a2467e4.
2025-08-05 23:16:49 +10:00
Gimle Larpes
634ac6718c Prevent publishing rooms with incompatible joinrules to directory (#2406)
* prevent listing "private" rooms on directory

* clean up boolean expression

* add knock_restricted
2025-08-05 18:40:42 +05:30
dependabot[bot]
05c3d7c332 Bump docker/metadata-action from 5.7.0 to 5.8.0 (#2413)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.7.0 to 5.8.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5.7.0...v5.8.0)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: 5.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-05 23:10:19 +10:00
dependabot[bot]
a8099c672d Bump docker/login-action from 3.4.0 to 3.5.0 (#2412)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3.4.0...v3.5.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-05 23:09:54 +10:00
renovate[bot]
842a2467e4 Update dependency linkifyjs to v4.3.2 [SECURITY] (#2407)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 23:09:22 +10:00
Ajay Bura
13c9b3eaa3 Redesign space/room creation panel (#2408)
* add new create room

* rename create room modal file

* default restrict access for space children in room create modal

* move create room kind selector to components

* add radii variant to sequence card component

* more more reusable create room logic to components

* add create space

* update address input description

* add new space modal

* fix add room button visible on left room in space lobby
2025-08-05 23:07:07 +10:00
Ajay Bura
0e6b549ec9 Show file size exceeds error on upload (#2411)
* Show file size exceeds error on upload

* fix missing import and make size bold
2025-08-05 23:05:18 +10:00
Jaggar
1b384aa329 Fix room input for virtual keyboard on webkit (#2346)
* Slate style has certain behavior elements that iOS expects

* Swap to using that implementation
2025-08-05 23:04:21 +10:00
Ajay Bura
1118e91b20 Improve thread reply layout (#2410) 2025-08-04 20:34:01 +05:30
Ajay Bura
b7cc9d88cb Revert "Fix menus congestion and improve thread reply layout (#2402)" (#2409)
This reverts commit 29364b2e92.
2025-08-04 20:29:12 +05:30
Ajay Bura
563b8f5089 Add code block language header and improve styles (#2403)
* add code block language header and improve styles

* improve codeblock fallback text

* move floating expand button to code block header

* reduce code font size
2025-07-27 22:21:09 +10:00
Ajay Bura
29364b2e92 Fix menus congestion and improve thread reply layout (#2402)
* make menus more spacious

* improve threaded reply layout

* fix search filter button spacing
2025-07-27 22:20:23 +10:00
Gimle Larpes
ac0345d07f Add settings to enable 24-hour time format and customizable date format (#2347)
* Add setting to enable 24-hour time format

* added hour24Clock to TimeProps

* Add incomplete dateFormatString setting

* Move 24-hour  toggle to Appearance

* Add "Date & Time" subheading, cleanup after merge

* Add setting for date formatting

* Fix minor formatting and naming issues

* Document functions

* adress most comments

* add hint for date formatting

* add support for 24hr time to TimePicker

* prevent overflow on small displays
2025-07-27 22:13:00 +10:00
Ajay Bura
748952a2a4 Render room avatar as fallback for dm group chat (#2398)
* render room avatar for dm group chat

* remove extra conditions
2025-07-23 21:00:02 +05:30
Ajay Bura
5114333067 Fix small height image half clickable view button (#2397) 2025-07-23 20:59:32 +05:30
Filipe Medeiros
7558f6d6fa Add button to start thread on reply (#2320)
* add simple button to start a thread on reply

* force build

* remove useless actions

* add actions back

* change icon to ThreadPlus

* add button to context menu

* fix capital T

---------

Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
2025-07-23 20:47:17 +05:30
Gimle Larpes
bb6fc297af Add code block copy and collapse functionality (#2361)
* add buttons to codeblocks

* add functionality

* Document functions

* Improve accessibility

* Remove pointless DefaultReset

* implement some requested changes

* fix content shift when expanding or collapsing

---------

Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
2025-07-23 20:40:56 +05:30
Ajay Bura
0da301ef12 Support oidc action param for login and register page (#2389) 2025-07-16 20:49:13 +10:00
Ajay Bura
f674cc7505 Jump to time option in room timeline (#2377)
* add time and date picker components

* add time utils

* add jump to time in room timeline

* fix typo causing crash in safari
2025-07-15 22:41:33 +10:00
Ajay Bura
589370f1bd Link device account management with OIDC (#2390)
* load auth metadata configs on startup

* deep-link cross-signing reset button with oidc

* deep-link manage devices and delete device with oidc

* fix import typo
2025-07-15 22:40:16 +10:00
Ajay Bura
e6f6e2117c Stop parsing servername from roomId (#2391) 2025-07-15 22:33:45 +10:00
Ajay Bura
6bec8aa2bb improve parent selection when opening a room (#2388)
when a room has more than one orphan parent, we will select parent which has highest number of special users who have special powers in selected room.
2025-07-11 21:03:55 +10:00
Ajay Bura
348240fbc3 fix room not opening when two rooms has same alias (#2387) 2025-07-11 21:00:30 +10:00
renovate[bot]
428324d2ee Update dependency vite to v5.4.19 [SECURITY] (#2326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-05 21:52:35 +10:00
dependabot[bot]
1aca413f40 Bump softprops/action-gh-release from 2.2.1 to 2.3.2 (#2363)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.2.1 to 2.3.2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](c95fe14893...72f2c25fcb)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 2.3.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-05 21:51:29 +10:00
dependabot[bot]
3189c40c2e Bump dawidd6/action-download-artifact from 9 to 11 (#2364)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 9 to 11.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](07ab29fd4a...ac66b43f0e)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-version: '11'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-05 21:49:14 +10:00
dependabot[bot]
aeae37e838 Bump nginx from 1.27.4-alpine to 1.29.0-alpine (#2382)
Bumps nginx from 1.27.4-alpine to 1.29.0-alpine.

---
updated-dependencies:
- dependency-name: nginx
  dependency-version: 1.29.0-alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-05 21:38:01 +10:00
Ajay Bura
4a230cbfc1 Fix new direct message showing with room (#2386)
as we were mutating the content of m.direct the sdk was comparing old value with new one and preventing update if found equal
2025-07-05 21:31:15 +10:00
RGBCube
a21af8943d Add support for more code highlight (#2355) 2025-06-29 16:13:47 +05:30
Gimle Larpes
2592bb6f8a Fix focus behaviour when opening single-purpose features (#2349)
* Improve focus behaviour on search boxes and chats

* Implemented MR #2317

* Fix crash if canMessage is false

* Prepare for PR #2335

* disable autofocus on message field
2025-06-28 20:15:21 +05:30
Gimle Larpes
ff3329aa0c Make "View Source" a developer tool (#2368) 2025-06-28 16:05:59 +05:30
Priyansh
bcfa47fc2d Fix auto focus in "Join with Address" text input (#2317) 2025-06-27 21:50:28 +05:30
dependabot[bot]
a64c34d7fe Bump docker/build-push-action from 6.15.0 to 6.18.0 (#2351)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.15.0 to 6.18.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.15.0...v6.18.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-19 08:52:03 +10:00
dependabot[bot]
5d9e4b0d65 Bump actions/setup-node from 4.3.0 to 4.4.0 (#2307)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.3.0 to 4.4.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4.3.0...v4.4.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-19 08:29:55 +10:00
Ajay Bura
e6a86f4c4f Release v4.8.1 (#2360) 2025-06-10 23:48:55 +10:00
Ajay Bura
8a072b1290 Add allow from currently selected space if no m.space.parent found (#2359) 2025-06-10 23:47:46 +10:00
Ajay Bura
b84927e49b Fix space navigation & view space timeline dev-option (#2358)
* fix inaccessible space on alias change

* fix new room in space open in home

* allow opening space timeline

* hide event timeline feature behind dev tool

* add navToActivePath to clear cache function
2025-06-10 14:44:17 +10:00
Ajay Bura
fef143dced Update folds to v2.2.0 (#2341) 2025-05-27 14:10:27 +05:30
Ajay Bura
066ec99699 Fix rate limit when reordering in space lobby (#2254)
* move can drop lobby item logic to hook

* add comment

* resolve rate limit when reordering space children
2025-05-26 14:21:27 +05:30
Krishan
7462b39f7c Fix additional spam string matching (#2339) 2025-05-25 15:51:19 +05:30
Ajay Bura
744d1efe6f Release v4.8.0 (#2337) 2025-05-24 21:22:39 +05:30
Ajay Bura
8d0c569207 hide decline all public invite button when no invite 2025-05-24 21:19:35 +05:30
Ajay Bura
4e64d821f2 Better invites management (#2336)
* move block users to account settings

* filter invites and add more options

* add better rate limit recovery in rateLimitedActions util function
2025-05-24 20:07:56 +05:30
Ajay Bura
6e2a5c786a Release v4.7.1 (#2332) 2025-05-21 17:28:38 +05:30
Ajay Bura
e1f10d7c98 Fix crash on malformed blurhash (#2331) 2025-05-21 17:28:13 +05:30
Ajay Bura
49f0ab46fa Release v4.7.0 (#2328) 2025-05-18 11:45:12 +05:30
Ajay Bura
4ffc591983 upgrade to matrix-js-sdk v37.5.0 (#2327)
* upgrade to js-sdk 37

* fix server crypto wasm locally
2025-05-18 10:53:56 +05:30
Ajay Bura
d8fa649745 update kick command example 2025-05-13 16:58:43 +05:30
Ajay Bura
3e31bfa11b Update commands (#2325)
* kick-ban all members by servername

* Add command for deleting multiple messages

* remove console logs and improve ban command description

* improve commands description

* add server acl command

* fix code highlight not working after editing in dev tools
2025-05-13 16:16:22 +05:30
Ajay Bura
770da845ce fix room setting crash in knock_restricted join rule (#2323)
* fix room setting crash in knock_restricted join rule

* only show knock & space member join rule for space children

* fix knock restricted icon and label
2025-05-13 14:18:52 +05:30
Krishan
003348c54e Release v4.6.0 (#2301) 2025-03-31 17:49:00 +05:30
Ajay Bura
f775b3151a remove libolm related code (#2300) 2025-03-31 19:10:24 +11:00
sophie
30a2157094 Update example caddyfile (#2285) 2025-03-28 20:19:34 +11:00
dependabot[bot]
3c73ffa025 Bump actions/upload-artifact from 4.6.1 to 4.6.2 (#2289)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.1 to 4.6.2.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.6.1...v4.6.2)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-28 20:18:37 +11:00
renovate[bot]
8d6e9d629b Update dependency vite to v5.4.15 [SECURITY] (#2292)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-28 20:17:17 +11:00
Ajay Bura
2f77807ebd Remove old settings components (#2296) 2025-03-28 20:16:01 +11:00
Ajay Bura
55ea5a4080 Display no members when changing filter in room members (#2297) 2025-03-28 20:15:31 +11:00
Ajay Bura
bc3f2ac438 Add new space settings (#2293) 2025-03-27 19:54:13 +11:00
Ajay Bura
3d945ec74a Fix DM rooms are not being encrypted (#2286)
* force check user device before creating dm

* fix getUserDeviceInfo doesn't exist on MatrixClient
2025-03-24 20:08:11 +11:00
Ajay Bura
56a0ce78ad Fix displayname input controlled/uncontrolled error (#2287) 2025-03-24 20:07:15 +11:00
Ajay Bura
4e8f81fa80 Change username color in chat with power level color (#2282)
* add active theme context

* add chroma js library

* add hook for accessible tag color

* disable reply user color - temporary

* render user color based on tag in room timeline

* remove default tag icons

* move accessible color function to plugins

* render user power color in reply

* increase username weight in timeline

* add default color for member power level tag

* show red slash in power color badge with no color

* show power level color in room input reply

* show power level username color in notifications

* show power level color in notification reply

* show power level color in message search

* render power level color in room pin menu

* add toggle for legacy username colors

* drop over saturation from member default color

* change border color of power color badge

* show legacy username color in direct rooms
2025-03-23 22:09:29 +11:00
Ajay Bura
4c9641c278 Add room notification mode switcher in room header menu (#2284) 2025-03-22 19:22:29 +11:00
Ajay Bura
5fb90a6516 Add margin top on media caption (#2283) 2025-03-22 19:21:49 +11:00
Gary Wang
7527fc22e0 Fix press enter while composing will cause send unfinished message (#2266) 2025-03-20 20:38:08 +11:00
dependabot[bot]
86fbce0a7c Bump dawidd6/action-download-artifact from 8 to 9 (#2269)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 8 to 9.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](20319c5641...07ab29fd4a)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 20:37:25 +11:00
renovate[bot]
d9fee274c2 Update dependency vite to v5.4.12 [SECURITY] (#2176)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 20:34:05 +11:00
dependabot[bot]
f63a5c7e35 Bump docker/login-action from 3.3.0 to 3.4.0 (#2277)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3.3.0...v3.4.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 20:32:21 +11:00
dependabot[bot]
b82533a11f Bump actions/setup-node from 4.2.0 to 4.3.0 (#2278)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4.2.0...v4.3.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 20:30:02 +11:00
renovate[bot]
9890fed1aa Update dependency prismjs to v1.30.0 [SECURITY] (#2270)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 20:27:52 +11:00
Ajay Bura
0ac8179ed2 Add option to change room notification settings (#2281) 2025-03-20 20:27:00 +11:00
Ajay Bura
4e1c4f6872 Add publish to directory toggle in room settings (#2279) 2025-03-20 20:25:31 +11:00
Ajay Bura
c4af5f2909 Add room upgrade option in room settings (#2280)
* add room upgrade option in room settings

* update upgrade room dialog styles
2025-03-20 20:23:16 +11:00
Ajay Bura
ef925f6e7f fix error when editing room profile 2025-03-20 09:57:57 +05:30
Ajay Bura
411e4f52c6 New room settings, add customizable power levels and dev tools (#2222)
* WIP - add room settings dialog

* join rule setting - WIP

* show emojis & stickers in room settings - WIP

* restyle join rule switcher

* Merge branch 'dev' into new-room-settings

* add join rule hook

* open room settings from global state

* open new room settings from all places

* rearrange settings menu item

* add option for creating new image pack

* room devtools - WIP

* render room state events as list

* add option to open state event

* add option to edit state event

* refactor text area code editor into hook

* add option to send message and state event

* add cutout card component

* add hook for room account data

* display room account data - WIP

* refactor global account data editor component

* add account data editor in room

* fix font style in devtool

* show state events in compact form

* add option to delete room image pack

* add server badge component

* add member tile component

* render members in room settings

* add search in room settings member

* add option to reset member search

* add filter in room members

* fix member virtual item key

* remove color from serve badge in room members

* show room in settings

* fix loading indicator position

* power level tags in room setting - WIP

* generate fallback tag in backward compatible way

* add color picker

* add powers editor - WIP

* add props to stop adding emoji to recent usage

* add beta feature notice badge

* add types for power level tag icon

* refactor image pack rooms code to hook

* option for adding new power levels tags

* remove console log

* refactor power icon

* add option to edit power level tags

* remove power level from powers pill

* fix power level labels

* add option to delete power levels

* fix long power level name shrinks power integer

* room permissions - WIP

* add power level selector component

* add room permissions

* move user default permission setting to other group

* add power permission peek menu

* fix weigh of power switch text

* hide above for max power in permission switcher

* improve beta badge description

* render room profile in room settings

* add option to edit room profile

* make room topic input text area

* add option to enable room encryption in room settings

* add option to change message history visibility

* add option to change join rule

* add option for addresses in room settings

* close encryption dialog after enabling
2025-03-19 23:14:54 +11:00
Ajay Bura
3d9b266c71 Stop showing notification from invite/left rooms (#2267) 2025-03-12 22:50:23 +11:00
Ajay Bura
4a03b33442 add option to download audio/video file (#2253)
* add option to download audio file

* add button to download video
2025-03-06 14:29:23 +11:00
dependabot[bot]
317a7ca861 Bump docker/setup-buildx-action from 3.9.0 to 3.10.0 (#2242)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.9.0 to 3.10.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.9.0...v3.10.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-06 12:58:37 +11:00
dependabot[bot]
b291f13ebd Bump docker/metadata-action from 5.6.1 to 5.7.0 (#2240)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.6.1 to 5.7.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5.6.1...v5.7.0)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-06 12:58:18 +11:00
dependabot[bot]
4a88b37343 Bump actions/upload-artifact from 4.6.0 to 4.6.1 (#2241)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.6.0...v4.6.1)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-06 12:57:06 +11:00
dependabot[bot]
564dd38ffc Bump docker/build-push-action from 6.13.0 to 6.15.0 (#2243)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.13.0 to 6.15.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.13.0...v6.15.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-06 12:56:33 +11:00
dependabot[bot]
ec253b4d71 Bump docker/setup-qemu-action from 3.4.0 to 3.6.0 (#2244)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3.4.0 to 3.6.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3.4.0...v3.6.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-06 12:56:11 +11:00
Krishan
7410e00475 Release v4.5.1 (#2251) 2025-03-05 13:33:18 +11:00
Ajay Bura
1abda8056b fix crash on emoji selection from emojiboard (#2249)
* fix crash on emoji select

* fix crash in message editor on emoji select
2025-03-05 13:23:28 +11:00
Krishan
0346ccad4f Release v4.5.0 (#2247) 2025-03-04 17:47:28 +11:00
Ajay Bura
697182519e fix backslash inserted in links upon edit (#2246)
* fix backslash appear in url with inline markdown sequences

* fix markdown chars not escaping on edit
2025-03-04 17:32:13 +11:00
Ajay Bura
459773e532 Hide deleted events by default (#2237) 2025-03-01 18:48:11 +11:00
Ajay Bura
d3a8f09766 Hide existing messages from ignored users (#2236)
* add ignored users hook

* remove messages from timeline for ignored users
2025-02-28 18:47:23 +11:00
sophie
aa175e0b6f make readme easier to read (#2228) 2025-02-28 18:39:10 +11:00
sophie
be6d0f61e1 add example caddyfile (#2227) 2025-02-28 18:31:54 +11:00
Ajay Bura
a2e651c869 open account data in same window instead of popup (#2234)
* refactor TextViewer Content component

* open account data inside setting window

* close account data edit window on cancel when adding new
2025-02-27 19:34:55 +11:00
Ajay Bura
9bacdd1018 Hidden Typing & Read Receipts (#2230)
* add hide activity toggle

* stop sending/receiving typing status

* send private read receipt when setting toggle is activated

* prevent showing read-receipt when feature toggle in on
2025-02-26 21:44:53 +11:00
Ajay Bura
96074475fb Show image preview in upload window (#2231)
* memoize metadata callback properly

* add image preview on upload

* show spoiler image button inside image preview
2025-02-26 21:43:43 +11:00
Ajay Bura
cb44815ca2 Fix editor focus after autocomplete (#2233)
* upgrade slatejs

* collapse autocomplete on escape

* make FN_KEYS_REGEX const in module scope
2025-02-26 21:42:42 +11:00
renovate[bot]
f0bf08a7dc Migrate config .github/renovate.json (#2232)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 12:40:29 +11:00
Krishan
3531ce994e Release v4.4.0 (#2225) 2025-02-23 22:27:50 +11:00
nexy7574
6195f5eaab Remove fallback replies & implement intentional mentions (#2138)
* Remove reply fallbacks & add m.mentions

(WIP) the typing on line 301 and 303 needs fixing but apart from that this is mint

* Less jank typing

* Mention the reply author in m.mentions

* Improve typing

* Fix typing in m.mentions finder

* Correctly iterate through editor children, properly handle @room, ...

..., don't mention the reply author when the reply author is ourself, don't add own user IDs when mentioning intentionally

* Formatting

* Add intentional mentions to edited messages

* refactor reusable code and fix todo

* parse mentions from all nodes

---------

Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
2025-02-23 22:08:08 +11:00
Ginger
d4737964bc Add support for spoilers on images (MSC4193) (#2212)
* Add support for MSC4193: Spoilers on Media

* Clarify variable names and wording

* Restore list atom

* Improve spoilered image UX with autoload off

* Use `aria-pressed` to indicate attachment spoiler state

* Improve spoiler button tooltip wording, keep reveal button from conflicting with load errors
2025-02-22 14:25:13 +05:30
Ajay Bura
1771d5de88 Fix unknown rooms in space lobby (#2224)
* add hook to fetch one level of space hierarchy

* add enable param to level hierarchy hook

* improve HierarchyItem types

* fix type errors in lobby

* load space hierarachy per level

* fix menu item visibility

* fix unknown spaces over federation

* show inaccessible rooms only to admins

* fix unknown room renders loading content twice

* fix unknown room visible to normal user if space all room are unknown

* show no rooms card if space does not have any room
2025-02-22 19:24:33 +11:00
Lain Iwakura
f41fc064e8 fix space/tab inconsistency (#2180) 2025-02-21 19:22:48 +11:00
Ajay Bura
b8b58bcf5d Escape markdown sequences (#2208)
* escape inline markdown character

* fix typo

* improve document around custom markdown plugin and add escape sequence utils

* recover inline escape sequences on edit

* remove escape sequences from plain text body

* use `s` for strike-through instead of del

* escape block markdown sequences

* fix remove escape sequence was not removing all slashes from plain text

* recover block sequences on edit
2025-02-21 19:19:24 +11:00
Ajay Bura
3e2eca7ea1 scroll to bottom in unfocused window but stop sending read receipt (#2214)
* scroll to bottom in unfocused window but stop sending read receipt

* send read-receipt when new message are in view after regaining focus
2025-02-21 19:18:02 +11:00
Ajay Bura
97cd12cdfe Add email notification toggle (#2223)
* refactor system notification to dedicated file

* add hook for email notification status

* add toogle for email notifications in settings
2025-02-21 19:15:47 +11:00
Ajay Bura
3c0d633f87 Improve search result counts (#2221)
* remove limit from emoji autocomplete

* remove search limit from user mention

* remove limit from room mention autocomplete

* increase user search limit to 1000

* better search string selection for emoticons
2025-02-21 19:14:38 +11:00
Ajay Bura
5242912910 fix autocomplete menu flickering issue (#2220) 2025-02-20 18:32:44 +11:00
Ajay Bura
040168e4df sanitize string before used in regex to prevent crash (#2219) 2025-02-20 18:30:54 +11:00
Ajay Bura
7fe2b6eac5 add button to select all room pack as global pack (#2218) 2025-02-19 22:13:29 +11:00
Ajay Bura
96c329a482 fix room activity indicator appearing on self typing (#2217) 2025-02-19 22:08:58 +11:00
Ajay Bura
e3708b0dd6 Fix link visible inside spoiler (#2215)
* hide links in spoiler

* prevent link click inside spoiler
2025-02-19 22:07:33 +11:00
Ajay Bura
4773c07692 add order algorithm in search result 2025-02-19 11:23:32 +05:30
Krishan
ea4cb1fed4 Release v4.3.2 (#2213) 2025-02-17 12:07:07 +11:00
dependabot[bot]
9f46f33237 Bump dawidd6/action-download-artifact from 7 to 8 (#2184)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 7 to 8.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](80620a5d27...20319c5641)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-15 19:16:06 +11:00
dependabot[bot]
2d5d896418 Bump docker/setup-buildx-action from 3.6.1 to 3.9.0 (#2196)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.6.1 to 3.9.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.6.1...v3.9.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-15 19:14:08 +11:00
dependabot[bot]
7bad15f12b Bump nginx from 1.27.0-alpine to 1.27.4-alpine (#2198)
Bumps nginx from 1.27.0-alpine to 1.27.4-alpine.

---
updated-dependencies:
- dependency-name: nginx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-15 19:13:11 +11:00
dependabot[bot]
210c7108a0 Bump docker/setup-qemu-action from 3.3.0 to 3.4.0 (#2197)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3.3.0...v3.4.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-15 19:10:00 +11:00
dependabot[bot]
2d307a79e7 Bump actions/setup-node from 4.0.4 to 4.2.0 (#2185)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.0.4 to 4.2.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4.0.4...v4.2.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-15 19:03:27 +11:00
dependabot[bot]
7cf53b1c02 Bump docker/build-push-action from 6.12.0 to 6.13.0 (#2183)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.12.0 to 6.13.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.12.0...v6.13.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-15 19:01:01 +11:00
Krishan
45c4f6295b Upgrade twemoji font to support twemoji v15.1.0 (#2202)
* Upgrade twemoji font to support twemoji v15.1.0

* bump emojibase deps to v15
2025-02-15 18:59:40 +11:00
Ajay Bura
1e3892641a fix message does not appear after decryption complete (#2209)
* fix message does not appear after decryption complete

* update when event get decrypted before subscribing
2025-02-15 18:58:57 +11:00
Ajay Bura
6c5498bf40 fix editor exit button appears on room switch (#2207) 2025-02-15 18:58:02 +11:00
Krishan
1720413eae Release v4.3.0 (#2199) 2025-02-11 17:02:21 +11:00
Array in a Matrix
ac8df3c2df fix media autoload button function as per it's label (#2195)
* Corrected button title

Media would load automatically if the option is checked not the other way around.

* Update src/app/features/settings/general/General.tsx

* Update General.tsx

* Update General.tsx

---------

Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
2025-02-11 16:28:46 +11:00
Ajay Bura
a94ba68cf9 fix edits does not reflect in pinned messages (#2107)
* fix pinned message edits does not reflect in pinned messages

* fix all pinned message show edited

* remove console log
2025-02-10 21:16:01 +11:00
Ajay Bura
f364fd4408 fix notification crash on ios (#2192)
* fix notification crash for ios

* access notification from window variable

* fix Notification check

* catch notification variable error

* fix missing check for Notification
2025-02-10 21:02:33 +11:00
Ajay Bura
dfe108eb96 fix reply placeholder overflow (#2193) 2025-02-10 20:47:21 +11:00
Ajay Bura
a2ca3195ea fix left/banned member showing in autocomplete (#2194) 2025-02-10 20:36:49 +11:00
Ajay Bura
87cc753366 redesigned app settings and switch to rust crypto (#1988)
* rework general settings

* account settings - WIP

* add missing key prop

* add object url hook

* extract wide modal styles

* profile settings and image editor - WIP

* add outline style to upload card

* remove file param from bind upload atom hook

* add compact variant to upload card

* add  compact upload card renderer

* add option to update profile avatar

* add option to change profile displayname

* allow displayname change based on capabilities check

* rearrange settings components into folders

* add system notification settings

* add initial page param in settings

* convert account data hook to typescript

* add push rule hook

* add notification mode hook

* add notification mode switcher component

* add all messages notification settings options

* add special messages notification settings

* add keyword notifications

* add ignored users section

* improve ignore user list strings

* add about settings

* add access token option in about settings

* add developer tools settings

* add expand button to account data dev tool option

* update folds

* fix editable active element textarea check

* do not close dialog when editable element in focus

* add text area plugins

* add text area intent handler hook

* add newline intent mod in text area

* add next line hotkey in text area intent hook

* add syntax error position dom utility function

* add account data editor

* add button to send new account data in dev tools

* improve custom emoji plugin

* add more custom emojis hooks

* add text util css

* add word break in setting tile title and description

* emojis and sticker user settings - WIP

* view image packs from settings

* emoji pack editing - WIP

* add option to edit pack meta

* change saved changes message

* add image edit and delete controls

* add option to upload pack images and apply changes

* fix state event type when updating image pack

* lazy load pack image tile img

* hide upload image button when user can not edit pack

* add option to add or remove global image packs

* upgrade to rust crypto (#2168)

* update matrix js sdk

* remove dead code

* use rust crypto

* update setPowerLevel usage

* fix types

* fix deprecated isRoomEncrypted method uses

* fix deprecated room.currentState uses

* fix deprecated import/export room keys func

* fix merge issues in image pack file

* fix remaining issues in image pack file

* start indexedDBStore

* update package lock and vite-plugin-top-level-await

* user session settings - WIP

* add useAsync hook

* add password stage uia

* add uia flow matrix error hook

* add UIA action component

* add options to delete sessions

* add sso uia stage

* fix SSO stage complete error

* encryption - WIP

* update user settings encryption terminology

* add default variant to password input

* use password input in uia password stage

* add options for local backup in user settings

* remove typo in import local backup password input label

* online backup - WIP

* fix uia sso action

* move access token settings from about to developer tools

* merge encryption tab into sessions and rename it to devices

* add device placeholder tile

* add logout dialog

* add logout button for current device

* move other devices in component

* render unverified device verification tile

* add learn more section for current device verification

* add device verification status badge

* add info card component

* add index file for password input component

* add types for secret storage

* add component to access secret storage key

* manual verification - WIP

* update matrix-js-sdk to v35

* add manual verification

* use react query for device list

* show unverified tab on sidebar

* fix device list updates

* add session key details to current device

* render restore encryption backup

* fix loading state of restore backup

* fix unverified tab settings closes after verification

* key backup tile - WIP

* fix unverified tab badge

* rename session key to device key in device tile

* improve backup restore functionality

* fix restore button enabled after layout reload during restoring backup

* update backup info on status change

* add backup disconnection failures

* add device verification using sas

* restore backup after verification

* show option to logout on startup error screen

* fix key backup hook update on decryption key cached

* add option to enable device verification

* add device verification reset dialog

* add logout button in settings drawer

* add encrypted message lost on logout

* fix backup restore never finish with 0 keys

* fix setup dialog hides when enabling device verification

* show backup details in menu

* update setup device verification body copy

* replace deprecated method

* fix displayname appear as mxid in settings

* remove old refactored codes

* fix types
2025-02-10 16:49:47 +11:00
Ajay Bura
85c9febe91 fix threaded reply not working in encrypted rooms (#2172) 2025-01-26 22:56:33 +11:00
Ajay Bura
b8b4483cc0 fix word overflow in text file viewer (#2179) 2025-01-26 22:55:09 +11:00
Ajay Bura
60faedd466 fix crash on membership change with invalid data (#2182)
* fix membership change with  invalid data crash

* add more checks around membership change

* fix displayname condition
2025-01-26 22:53:16 +11:00
Ajay Bura
b822870006 fix style issue of reply placeholder (#2181) 2025-01-26 22:52:10 +11:00
dependabot[bot]
e68549f907 Bump softprops/action-gh-release from 2.0.8 to 2.2.1 (#2164)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.8 to 2.2.1.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](c062e08bd5...c95fe14893)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-18 15:52:42 +11:00
dependabot[bot]
abae424458 Bump actions/upload-artifact from 4.5.0 to 4.6.0 (#2163)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.5.0...v4.6.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-18 15:48:02 +11:00
dependabot[bot]
6eda805a3a Bump docker/setup-qemu-action from 3.2.0 to 3.3.0 (#2165)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3.2.0...v3.3.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-18 15:47:34 +11:00
dependabot[bot]
b95b49ba5c Bump docker/build-push-action from 6.10.0 to 6.12.0 (#2169)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.10.0 to 6.12.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.10.0...v6.12.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-18 15:46:58 +11:00
Krishan
8e03e3667c Fix typo 2025-01-18 15:35:39 +11:00
Krishan
fa42b502b4 Enable actions and docker dependabot updates (#2167) 2025-01-17 14:23:49 +05:30
dependabot[bot]
bae7b4ef17 Bump dawidd6/action-download-artifact from 6 to 7 (#2114)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 6 to 7.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](bf251b5aa9...80620a5d27)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-10 20:48:25 +11:00
Jan Jurzitza
d0a798383a Center image in URL preview card (#1556)
center is more likely to have relevant content than top left
2025-01-10 15:12:05 +05:30
Ajay Bura
2e50a87f8c Disable dependabot (#2140) 2025-01-08 22:35:46 +11:00
nexy7574
4cd207bf1f Render captions to m.file, m.image, m.video, and m.audio (#2059)
* Add rendering image captions

* Handle sending captions for images

* Fix caption rendering on m.video and m.audio too

* Remove unused renderBody() parameter

* Fix m.file rendering body instead of filename where possible

* Add caption rendering for generic files

+ Fix video and audio not properly sending captions

* Use m.text for captions & render on demand

* Allow custom HTML in sending captions

* Don't *send* captions

* mvoe content const into renderCaption()

---------

Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
2025-01-06 12:44:22 +11:00
dependabot[bot]
22526710a0 Bump docker/metadata-action from 5.5.1 to 5.6.1 (#2113)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.5.1 to 5.6.1.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5.5.1...v5.6.1)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-30 20:02:34 +11:00
dependabot[bot]
4f3a27bede Bump docker/build-push-action from 6.9.0 to 6.10.0 (#2112)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.9.0 to 6.10.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.9.0...v6.10.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-30 19:59:42 +11:00
dependabot[bot]
f69a197ac9 Bump actions/upload-artifact from 4.3.6 to 4.5.0 (#2111)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.6 to 4.5.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.3.6...v4.5.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-30 19:54:19 +11:00
Marek Vospel
677c9ddd4a Emoji ordering in emoji board (#2057)
* feat: sort emojis inside emoji picker

* feat: sort autocomplete emojis

Fixes #1632

* fix: sort function after concat

* fix: sort result instead of searchList
2024-12-22 16:11:02 +05:30
Ajay Bura
f9de1e276b Pinned Messages (#2081)
* add pinned room events hook

* room pinned message - WIP

* add room event hook

* fetch pinned messages before displaying

* use react-query in room event hook

* disable staleTime and gc to 1 hour in room event hook

* use room event hook in reply component

* render pinned messages

* add option to pin/unpin messages

* remove message base from message placeholders and add variant

* display message placeholder while loading pinned messages

* render pinned event error

* show no pinned message placeholder

* fix message placeholder flickering
2024-12-16 16:25:15 +05:30
Rein Fernhout
3a16fc59f4 add tableflip and unflip commands (#2075) 2024-12-13 10:02:25 +05:30
Krishan
225df0786e Release v4.2.3 (#2052) 2024-11-12 20:45:34 +11:00
renovate[bot]
257438378f fix(deps): update dependency matrix-js-sdk to v34.11.1 (#2053)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-12 20:43:50 +11:00
Krishan
80dd4e0aac Release v4.2.2 (#2012) 2024-10-16 21:29:30 +11:00
夜坂雅
1c8412bd5c fix: register service worker immediately and cache media requests (#1977)
* Allow service worker to immediately claim pages
* Allow media requests to be cached by browser
2024-10-16 21:26:03 +11:00
renovate[bot]
fb5fe67ded fix(deps): update dependency matrix-js-sdk to v34.8.0 (#2011)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-16 21:22:06 +11:00
dependabot[bot]
2fd3bcff0a Bump actions/setup-node from 4.0.3 to 4.0.4 (#1969)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.0.3 to 4.0.4.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4.0.3...v4.0.4)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 17:03:33 +10:00
dependabot[bot]
4969e7fb35 Bump actions/checkout from 4.1.7 to 4.2.0 (#1985)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.7 to 4.2.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.7...v4.2.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 17:00:32 +10:00
dependabot[bot]
3ef12e1d24 Bump docker/build-push-action from 6.7.0 to 6.9.0 (#1986)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.7.0 to 6.9.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.7.0...v6.9.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 16:59:55 +10:00
dependabot[bot]
100ba037ef Bump cla-assistant/github-action from 2.5.1 to 2.6.1 (#1987)
Bumps [cla-assistant/github-action](https://github.com/cla-assistant/github-action) from 2.5.1 to 2.6.1.
- [Release notes](https://github.com/cla-assistant/github-action/releases)
- [Commits](https://github.com/cla-assistant/github-action/compare/v2.5.1...v2.6.1)

---
updated-dependencies:
- dependency-name: cla-assistant/github-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 16:59:12 +10:00
Ajay Bura
aa94195184 fix font-weight in dark theme with unsupported fonts (#1964) 2024-09-22 22:31:32 +10:00
Krishan
16f2a06186 Fix matrix.to links opening in webview in cinny desktop (#1963) 2024-09-22 10:08:55 +05:30
Krishan
4f2a018ca9 Release v4.2.1 (#1953) 2024-09-14 23:24:34 +10:00
Krishan
96380d0662 Fix auth media check for dendrite (#1952) 2024-09-14 18:54:06 +05:30
Krishan
b2008340dd Release v4.2.0 (#1949) 2024-09-11 19:26:08 +05:30
renovate[bot]
125b815ac7 Update dependency matrix-js-sdk to v34.5.0 (#1945)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-11 17:08:38 +10:00
Ajay Bura
62d6b8e582 Fix authenticated media download (#1947)
* remove dead function

* fix media download in room timeline

* authenticate remaining media endpoints
2024-09-11 17:07:02 +10:00
夜坂雅
b8d80da7e0 fix: Fix video and audio loading with authenicated media (#1946)
Appeareantly Firefox (and maybe Chrome) won't let service workers take over requests from <video> and <audio> tags, so we just fetch the URL ourselves.
2024-09-11 10:43:15 +05:30
Ajay Bura
e2f1089766 render matrix room and event link content (#1938) 2024-09-09 20:51:52 +10:00
Ajay Bura
e1c2430db9 Improve-auth-media (#1933)
* fix set power level broken after sdk update

* add media authentication hook

* fix service worker types

* fix service worker not working in dev mode

* fix env mode check when registering sw
2024-09-09 14:15:20 +05:30
Ajay Bura
49e038e23b fix mention url is encoded wrong (#1936) 2024-09-08 22:53:59 +10:00
Ajay Bura
c60ea57f3b fix escape to mark as read (#1935) 2024-09-08 22:53:17 +10:00
Ajay Bura
cc9a7b1620 fix sso login without identity providers (#1934) 2024-09-08 22:51:43 +10:00
夜坂雅
8799ae5246 Add authenticated media support (#1930)
* chore: Bump matrix-js-sdk to 34.4.0

* feat: Authenticated media support

* chore: Use Vite PWA for service worker support

* fix: Fix Vite PWA SW entry point

Forget this. :P

* fix: Also add Nginx rewrite for sw.js

* fix: Correct Nginx rewrite

* fix: Add Netlify redirect for sw.js

Otherwise the generic SPA rewrite to index.html would take effect, breaking Service Worker.

* fix: Account for subpath when regisering service worker

* chore: Correct types
2024-09-07 19:15:55 +05:30
Dylan Hackworth
df5cca4e9a pressing up to edit should take you to end of line (#1928) 2024-09-07 18:38:16 +05:30
utf
75c58f8d1a Fix IPv6 support for the Docker container (#1884)
* Fix `docker-nginx.conf` indentation

* Listen on IPv4 and IPv6 inside Docker
2024-08-23 20:56:03 +10:00
Krishan
585d9111c9 Create Code of Conduct (#1908) 2024-08-21 15:43:40 +05:30
dependabot[bot]
dd581a20cb Bump cla-assistant/github-action from 2.4.0 to 2.5.1 (#1905)
Bumps [cla-assistant/github-action](https://github.com/cla-assistant/github-action) from 2.4.0 to 2.5.1.
- [Release notes](https://github.com/cla-assistant/github-action/releases)
- [Commits](https://github.com/cla-assistant/github-action/compare/v2.4.0...v2.5.1)

---
updated-dependencies:
- dependency-name: cla-assistant/github-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-21 00:22:26 +10:00
dependabot[bot]
1ffe97ff0f Bump docker/build-push-action from 6.6.1 to 6.7.0 (#1906)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.6.1 to 6.7.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.6.1...v6.7.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-21 00:21:25 +10:00
greentore
4300896c72 Add basic m.thread support (#1349)
* Add basic `m.thread` support

* Fix types

* Update to v4

* Fix auto formatting mess

* Add threaded reply indicators

* Fix reply overflow

* Fix replying to edited threaded replies

* Add thread indicator to room input

* Fix editing encrypted events

* Use `toRem` function for converting units

---------

Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
2024-08-15 20:22:32 +05:30
dependabot[bot]
9e80f94fab Bump actions/upload-artifact from 4.3.4 to 4.3.6 (#1890)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.4 to 4.3.6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.3.4...v4.3.6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 23:38:35 +10:00
aceArt-GmbH
4555538d7c Add translation support using i18next (#1576)
Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
2024-08-14 18:59:34 +05:30
dependabot[bot]
1e806c7589 Bump docker/build-push-action from 6.5.0 to 6.6.1 (#1891)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.5.0 to 6.6.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.5.0...v6.6.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 23:21:11 +10:00
Krishan
1191ca6a1b Release v4.1.0 (#1867) 2024-08-04 20:15:10 +10:00
Ajay Bura
71bde10887 fix type to focus not working after room switch (#1866) 2024-08-04 16:04:11 +10:00
dependabot[bot]
1583d892ac Bump docker/setup-buildx-action from 3.5.0 to 3.6.1 (#1850)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.5.0 to 3.6.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.5.0...v3.6.1)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-04 15:38:47 +10:00
Ajay Bura
4ad15af141 fix crash when decoding malformed urls (#1865) 2024-08-04 15:38:20 +10:00
Ajay Bura
e7d7160f6f fix notification not working for selected room (#1864) 2024-08-04 15:37:28 +10:00
Ajay Bura
72dadb0a07 fix page up/down button not working (#1863) 2024-08-04 15:36:42 +10:00
Ajay Bura
a0ea330c07 show unverified tab indicator on sidebar (#1862) 2024-08-04 14:19:37 +10:00
Ajay Bura
fee8ad1c09 add back btn for mobile view (#1861) 2024-08-03 23:47:53 +10:00
Krishan
37998dd853 Fix typo in readme 2024-08-01 23:45:22 +10:00
Krishan
5df8e4cc8d update self deploy instructions after react router (#1859)
* update self deploy instructions after react router

* List the alternative

* docs to deploy on subdir
2024-08-01 19:12:45 +05:30
Ajay Bura
4d742d11bc fix tombstone replacement room open previous room (#1856) 2024-07-30 22:19:51 +10:00
Ajay Bura
99c8030322 support matrix.to links (#1849)
* support room via server params and eventId

* change copy link to matrix.to links

* display matrix.to links in messages as pill and stop generating url previews for them

* improve editor mention to include viaServers and eventId

* fix mention custom attributes

* always try to open room in current space

* jump to latest remove target eventId from url

* add create direct search options to open/create dm with url
2024-07-30 22:18:59 +10:00
Ajay Bura
b19a630e90 fix room opens at home after leave rejoin (#1848) 2024-07-28 23:40:21 +10:00
Krishan
f7da99ea1c Release v4.0.3 (#1840) 2024-07-25 15:54:58 +10:00
Krishan
e0e43625fa Update gpg public key after renew (#1839) 2024-07-25 10:58:14 +05:30
Krishan
6ce5874c18 Release v4.0.0 (#1836)
* Release v4.0.0

* add more rooms in featured
2024-07-24 18:30:49 +05:30
Ajay Bura
34a307adfd add ngnix conf file for docker build (#1837) 2024-07-24 22:51:03 +10:00
Ajay Bura
cb011cecd2 Add setting for page zoom (#1835)
* add setting for page zoom

* parse integer in zoom change listener

* fix zoom input width

* fix null gets saved as page zoom
2024-07-23 23:52:53 +10:00
dependabot[bot]
9eeba3573f Bump docker/setup-qemu-action from 3.1.0 to 3.2.0 (#1830)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3.1.0...v3.2.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 15:17:12 +10:00
dependabot[bot]
f442b7e3a8 Bump docker/login-action from 3.2.0 to 3.3.0 (#1831)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3.2.0...v3.3.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 15:16:56 +10:00
dependabot[bot]
fcba6b11ed Bump docker/setup-buildx-action from 3.4.0 to 3.5.0 (#1832)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.4.0...v3.5.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 15:16:38 +10:00
dependabot[bot]
ff8a0b24be Bump softprops/action-gh-release from 2.0.6 to 2.0.8 (#1833)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.6 to 2.0.8.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](a74c6b72af...c062e08bd5)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 15:16:16 +10:00
dependabot[bot]
e012a9741f Bump docker/build-push-action from 6.4.0 to 6.5.0 (#1834)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.4.0 to 6.5.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.4.0...v6.5.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 15:15:51 +10:00
Ajay Bura
2de4f88c96 Load room member even when member drawer is closed (#1825) 2024-07-23 15:15:17 +10:00
Ajay Bura
4539c1e6e2 Fix unread reset and notification settings (#1824)
* reset unread with client sync state change

* fix notification toggle setting not working

* revert formatOnSave vscode setting
2024-07-23 15:14:32 +10:00
Ajay Bura
ee0cba97ad handle error in loading screen (#1823)
* handle client boot error in loading screen

* use sync state hook in client root

* add loading screen options

* removed extra condition in loading finish

* add sync connection status bar
2024-07-22 20:47:19 +10:00
dependabot[bot]
5f9196e459 Bump docker/setup-buildx-action from 3.3.0 to 3.4.0 (#1814)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.3.0...v3.4.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-21 15:44:43 +10:00
dependabot[bot]
ed937a67dc Bump docker/build-push-action from 6.3.0 to 6.4.0 (#1815)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.3.0 to 6.4.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.3.0...v6.4.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-21 15:44:27 +10:00
dependabot[bot]
5b23b32537 Bump actions/setup-node from 4.0.2 to 4.0.3 (#1816)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.0.2 to 4.0.3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4.0.2...v4.0.3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-21 15:43:58 +10:00
Ajay Bura
1843001172 Update color theme to match with new design (#1821)
* update silver theme

* update unread badge style to look more slim

* update nav item style to look less sharp

* fix type focus message input typo

* decrease navigation drawer width to bring main chat layout to little more center

* increase sidebar width to make it less congested

* fix sidebar item style

* decrease dark theme contrast

* improve dark theme

* revert sidebar width change

* add join with address option in home context menu

* match legacy theme with latest themes
2024-07-21 15:43:33 +10:00
Ajay Bura
d9747596b9 Fix selecting tombstone room opens replacement room (#1820) 2024-07-18 23:20:51 +10:00
Ajay Bura
853e64cbe5 Make hotkeys work again (#1819) 2024-07-18 23:20:20 +10:00
Ajay Bura
fbc87f7f15 fix crash when adding existing room to space (#1806) 2024-07-15 00:21:19 +10:00
Ajay Bura
c24f184cac fix space lobby button shrink 2024-07-10 18:44:28 +05:30
dependabot[bot]
ec9069d3a2 Bump formik from 2.2.9 to 2.4.6 (#1715)
Bumps [formik](https://github.com/jaredpalmer/formik) from 2.2.9 to 2.4.6.
- [Release notes](https://github.com/jaredpalmer/formik/releases)
- [Commits](https://github.com/jaredpalmer/formik/compare/formik@2.2.9...formik@2.4.6)

---
updated-dependencies:
- dependency-name: formik
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 23:49:06 +10:00
dependabot[bot]
f10df7d8da Bump linkify-react from 4.1.1 to 4.1.3 (#1742)
updated-dependencies:
- dependency-name: linkify-react
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 23:43:53 +10:00
dependabot[bot]
b88ead029f Bump linkifyjs from 4.0.2 to 4.1.3 (#1672)
Bumps [linkifyjs](https://github.com/Hypercontext/linkifyjs/tree/HEAD/packages/linkifyjs) from 4.0.2 to 4.1.3.
- [Release notes](https://github.com/Hypercontext/linkifyjs/releases)
- [Changelog](https://github.com/Hypercontext/linkifyjs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Hypercontext/linkifyjs/commits/v4.1.3/packages/linkifyjs)

---
updated-dependencies:
- dependency-name: linkifyjs
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 23:40:46 +10:00
dependabot[bot]
be776312fe Bump react-error-boundary from 4.0.10 to 4.0.13 (#1664)
Bumps [react-error-boundary](https://github.com/bvaughn/react-error-boundary) from 4.0.10 to 4.0.13.
- [Release notes](https://github.com/bvaughn/react-error-boundary/releases)
- [Commits](https://github.com/bvaughn/react-error-boundary/compare/4.0.10...4.0.13)

---
updated-dependencies:
- dependency-name: react-error-boundary
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 23:36:45 +10:00
dependabot[bot]
a5ca64e8f3 Bump docker/build-push-action from 6.0.0 to 6.3.0 (#1799)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.0.0 to 6.3.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.0.0...v6.3.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 23:25:38 +10:00
dependabot[bot]
2faa04f441 Bump docker/setup-qemu-action from 3.0.0 to 3.1.0 (#1798)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3.0.0...v3.1.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 23:24:56 +10:00
dependabot[bot]
9364882c1b Bump actions/upload-artifact from 4.3.3 to 4.3.4 (#1797)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.3 to 4.3.4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.3.3...v4.3.4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 23:23:56 +10:00
dependabot[bot]
9c4f71274c Bump softprops/action-gh-release from 2.0.5 to 2.0.6 (#1785)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.5 to 2.0.6.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](69320dbe05...a74c6b72af)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 23:23:18 +10:00
Ajay Bura
d0d1b625aa fix notification, favicon and sound (#1802) 2024-07-09 22:50:33 +10:00
Ajay Bura
56bd5060d9 (chore) remove outdated code (#1765)
* optimize room typing members hook

* remove unused code - WIP

* remove old code from initMatrix

* remove twemojify function

* remove old sanitize util

* delete old markdown util

* delete Math atom component

* uninstall unused dependencies

* remove old notification system

* decrypt message in inbox notification center and fix refresh in background

* improve notification

---------

Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
2024-07-08 21:27:10 +10:00
dependabot[bot]
c35a6cef38 Bump actions/checkout from 4.1.6 to 4.1.7 (#1775)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.1.7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.6...v4.1.7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 14:02:12 +10:00
dependabot[bot]
bed36a3f1a Bump docker/build-push-action from 5.4.0 to 6.0.0 (#1777)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.4.0 to 6.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5.4.0...v6.0.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 14:00:11 +10:00
dependabot[bot]
f9c525dd44 Bump dawidd6/action-download-artifact from 3.1.4 to 6 (#1776)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 3.1.4 to 6.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](09f2f74827...bf251b5aa9)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 13:55:34 +10:00
dependabot[bot]
5f089131ba Bump docker/build-push-action from 5.3.0 to 5.4.0 (#1766)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.3.0 to 5.4.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5.3.0...v5.4.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-15 23:28:03 +10:00
Kimiblock Moe
7a57e0322f Prevent Safari iOS from auto zooming (#1756)
Thanks @pixlxip:beeper.com
2024-06-05 18:13:19 +05:30
dependabot[bot]
8a34c5cfa2 Bump docker/login-action from 3.1.0 to 3.2.0 (#1758)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3.1.0...v3.2.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-04 14:26:21 +10:00
dependabot[bot]
de5e7726f7 Bump nginx from 1.26.0-alpine to 1.27.0-alpine (#1759)
Bumps nginx from 1.26.0-alpine to 1.27.0-alpine.

---
updated-dependencies:
- dependency-name: nginx
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-04 14:25:07 +10:00
Ajay Bura
9b7f16ab10 URL navigation in interface and other improvements (#1633)
* load room on url change

* add direct room list

* render space room list

* fix css syntax error

* update scroll virtualizer

* render subspaces room list

* improve sidebar notification badge perf

* add nav category components

* add space recursive direct component

* use nav category component in home, direct and space room list

* add empty home and direct list layout

* fix unread room menu ref

* add more navigation items in room, direct and space tab

* add more navigation

* fix unread room menu to links

* fix space lobby and search link

* add explore navigation section

* add notifications navigation menu

* redirect to initial path after login

* include unsupported room in rooms

* move router hooks in hooks/router folder

* add featured explore - WIP

* load featured room with room summary

* fix room card topic line clamp

* add react query

* load room summary using react query

* add join button in room card

* add content component

* use content component in featured community content

* fix content width

* add responsive room card grid

* fix async callback error status

* add room card error button

* fix client drawer shrink

* add room topic viewer

* open room card topic in viewer

* fix room topic close btn

* add get orphan parent util

* add room card error dialog

* add view featured room or space btn

* refactor orphanParent to orphanParents

* WIP - explore server

* show space hint in room card

* add room type filters

* add per page item limit popout

* reset scroll on public rooms load

* refactor explore ui

* refactor public rooms component

* reset search on server change

* fix typo

* add empty featured section info

* display user server on top

* make server room card view btn clickable

* add user server as default redirect for explore path

* make home empty btn clickable

* add thirdparty instance filter in server explore

* remove since param on instance change

* add server button in explore menu

* rename notifications path to inbox

* update react-virtual

* Add notification messages inbox - WIP

* add scroll top container component

* add useInterval hook

* add visibility change callback prop to scroll top container component

* auto refresh notifications every 10 seconds

* make message related component reusable

* refactor matrix event renderer hoook

* render notification message content

* refactor matrix event renderer hook

* update sequence card styles

* move room navigate hook in global hooks

* add open message button in notifications

* add mark room as read button in notification group

* show error in notification messages

* add more featured spaces

* render reply in notification messages

* make notification message reply clickable

* add outline prop for attachments

* make old settings dialog viewable

* add open featured communities as default config option

* add invite count notification badge in sidebar and inbox menu

* add element size observer hook

* improve element size observer hook props

* improve screen size hook

* fix room avatar util function

* allow Text props in Time component

* fix dm room util function

* add invitations

* add no invites and notification cards

* fix inbox tab unread badge visible without invite count

* update folds and change inbox icon

* memo search param construction

* add message search in home

* fix default message search order

* fix display edited message new content

* highlight search text in search messages

* fix message search loading

* disable log in production

* add use space context

* add useRoom context

* fix space room list

* fix inbox tab active state

* add hook to get space child room recursive

* add search for space

* add virtual tile component

* virtualize home and directs room list

* update nav category component

* use virtual tile component in more places

* fix message highlight when click on reply twice

* virtualize space room list

* fix space room list lag issue

* update folds

* add room nav item component in space room list

* use room nav item in home and direct room list

* make space categories closable and save it in local storage

* show unread room when category is collapsed

* make home and direct room list category closable

* rename room nav item show avatar prop

* fix explore server category text alignment

* rename closedRoomCategories to closedNavCategories

* add nav category handler hook

* save and restore last navigation path on space select

* filter space rooms category by activity when it is closed

* save and restore home and direct nav path state

* save and restore inbox active path on open

* save and restore explore tab active path

* remove notification badge unread menu

* add join room or space before navigate screen

* move room component to features folder and add new room header

* update folds

* add room header menu

* fix home room list activity sorting

* do not hide selected room item on category closed in home and direct tab

* replace old select room/tab call with navigate hook

* improve state event hooks

* show room card summary for joined rooms

* prevent room from opening in wrong tab

* only show message sender id on hover in modern layout

* revert state event hooks changes

* add key prop to room provider components

* add welcome page

* prevent excessive redirects

* fix sidebar style with no spaces

* move room settings in popup window

* remove invite option from room settings

* fix open room list search

* add leave room prompt

* standardize room and user avatar

* fix avatar text size

* add new reply layout

* rename space hierarchy hook

* add room topic hook

* add room name hook

* add room avatar hook and add direct room avatar util

* space lobby - WIP

* hide invalid space child event from space hierarchy in lobby

* move lobby to features

* fix element size observer hook width and height

* add lobby header and hero section

* add hierarchy room item error and loading state

* add first and last child prop in sequence card

* redirect to lobby from index path

* memo and retry hierarchy room summary error

* fix hierarchy room item styles

* rename lobby hierarchy item card to room item card

* show direct room avatar in space lobby

* add hierarchy space item

* add space item unknown room join button

* fix space hierarchy hook refresh after new space join

* change user avatar color and fallback render to user icon

* change room avatar fallback to room icon

* rename room/user avatar renderInitial prop to renderFallback

* add room join and view button in space lobby

* make power level api more reusable

* fix space hierarchy not updating on child update

* add menu to suggest or remove space children

* show reply arrow in place of reply bend in message

* fix typeerror in search because of wrong js-sdk t.ds

* do not refetch hierarchy room summary on window focus

* make room/user avatar un-draggable

* change welcome page support button copy

* drag-and-drop ordering of lobby spaces/rooms - WIP

* add ASCIILexicalTable algorithms

* fix wrong power level check in lobby items options

* fix lobby can drop checks

* fix join button error crash

* fix reply spacing

* fix m direct updated with other account data

* add option to open room/space settings from lobby

* add option in lobby to add new or existing room/spaces

* fix room nav item selected styles

* add space children reorder mechanism

* fix space child reorder bug

* fix hierarchy item sort function

* Apply reorder of lobby into room list

* add and improve space lobby menu items

* add existing spaces menu in lobby

* change restricted room allow params when dragging outside space

* move featured servers config from homeserver list

* removed unused features from space settings

* add canonical alias as name fallback in lobby item

* fix unreliable unread count update bug

* fix after login redirect

* fix room card topic hover style

* Add dnd and folders in sidebar spaces

* fix orphan space not visible in sidebar

* fix sso login has mix of icon and button

* fix space children not  visible in home upon leaving space

* recalculate notification on updating any space child

* fix user color saturation/lightness

* add user color to user avatar

* add background colors to room avatar

* show 2 length initial in sidebar space avatar

* improve link color

* add nav button component

* open legacy create room and create direct

* improve page route structure

* handle hash router in path utils

* mobile friendly router and navigation

* make room header member drawer icon mobile friendly

* setup index redirect for inbox and explore server route

* add leave space prompt

* improve member drawer filter menu

* add space context menu

* add context menu in home

* add leave button in lobby items

* render user tab avatar on sidebar

* force overwrite netlify - test

* netlify test

* fix reset-password path without server redirected to login

* add message link copy button in message menu

* reset unread on sync prepared

* fix stuck typing notifications

* show typing indication in room nav item

* refactor closedNavCategories atom to use userId in store key

* refactor closedLobbyCategoriesAtom to include userId in store key

* refactor navToActivePathAtom to use userId in storage key

* remove unused file

* refactor openedSidebarFolderAtom to include userId in storage key

* add context menu for sidebar space tab

* fix eslint not working

* add option to pin/unpin child spaces

* add context menu for directs tab

* add context menu for direct and home tab

* show lock icon for non-public space in header

* increase matrix max listener count

* wrap lobby add space room in callback hook
2024-06-01 00:19:46 +10:00
Majan Paul
b0b646cb9e Ignroe webstorm idea folder (#1638) 2024-05-22 21:56:44 +10:00
dependabot[bot]
d5cecf0b33 --- (#1741)
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-22 21:54:48 +10:00
dependabot[bot]
c5aa16c685 Bump nginx from 1.25.5-alpine to 1.26.0-alpine (#1718)
Bumps nginx from 1.25.5-alpine to 1.26.0-alpine.

---
updated-dependencies:
- dependency-name: nginx
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 14:18:40 +10:00
dependabot[bot]
0ce0685056 Bump vite-plugin-static-copy from 0.13.0 to 1.0.4 (#1722)
* Bump vite-plugin-static-copy from 0.13.0 to 1.0.4

Bumps [vite-plugin-static-copy](https://github.com/sapphi-red/vite-plugin-static-copy) from 0.13.0 to 1.0.4.
- [Release notes](https://github.com/sapphi-red/vite-plugin-static-copy/releases)
- [Changelog](https://github.com/sapphi-red/vite-plugin-static-copy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sapphi-red/vite-plugin-static-copy/compare/v0.13.0...vite-plugin-static-copy@1.0.4)

---
updated-dependencies:
- dependency-name: vite-plugin-static-copy
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Change type to module

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
2024-05-14 14:01:45 +10:00
aceArt-GmbH
46a45d4fc9 Scroll tab target into view (#1580) 2024-05-14 09:19:04 +05:30
dependabot[bot]
1d7f529808 Bump softprops/action-gh-release from 2.0.4 to 2.0.5 (#1734)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.4 to 2.0.5.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](9d7c94cfd0...69320dbe05)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 13:34:57 +10:00
dependabot[bot]
539373aa64 Bump cla-assistant/github-action from 2.3.2 to 2.4.0 (#1735)
Bumps [cla-assistant/github-action](https://github.com/cla-assistant/github-action) from 2.3.2 to 2.4.0.
- [Release notes](https://github.com/cla-assistant/github-action/releases)
- [Commits](https://github.com/cla-assistant/github-action/compare/v2.3.2...v2.4.0)

---
updated-dependencies:
- dependency-name: cla-assistant/github-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 13:33:58 +10:00
Krishan
872c847a3e Fix pdf opening (#1732) 2024-05-12 16:14:34 +10:00
dependabot[bot]
fe425f7187 Bump actions/checkout from 4.1.4 to 4.1.5 (#1721)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4 to 4.1.5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.4...v4.1.5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-12 14:39:43 +10:00
Krishan
5ff92c109b Fix crash when img without src tag (#1731) 2024-05-12 10:06:35 +05:30
renovate[bot]
ca53a16a92 Update dependency eslint-plugin-import to v2.29.1 (#1730)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-12 14:27:02 +10:00
renovate[bot]
b1262e0555 Update dependency sanitize-html to v2.12.1 [SECURITY] (#1729)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-12 14:25:07 +10:00
Krishan
ab9d567bd6 Remove svg loader as available in Vite by default (#1728) 2024-05-12 09:47:41 +05:30
dependabot[bot]
30efeabdd1 Bump pdfjs-dist from 3.10.111 to 4.2.67 (#1717)
* Bump pdfjs-dist from 3.10.111 to 4.2.67

Bumps [pdfjs-dist](https://github.com/mozilla/pdfjs-dist) from 3.10.111 to 4.2.67.
- [Commits](https://github.com/mozilla/pdfjs-dist/commits)

---
updated-dependencies:
- dependency-name: pdfjs-dist
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix pdfjs top level await

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
2024-05-12 14:06:53 +10:00
dependabot[bot]
d807dd0b1d Bump docker/setup-buildx-action from 2.7.0 to 3.3.0 (#1710)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.7.0 to 3.3.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.7.0...v3.3.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 23:07:32 +10:00
dependabot[bot]
be5df48757 Bump actions/checkout from 4.1.3 to 4.1.4 (#1709)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.3 to 4.1.4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.3...v4.1.4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 23:07:12 +10:00
dependabot[bot]
4e0e4cc606 Bump thollander/actions-comment-pull-request from 2.4.3 to 2.5.0 (#1711)
Bumps [thollander/actions-comment-pull-request](https://github.com/thollander/actions-comment-pull-request) from 2.4.3 to 2.5.0.
- [Release notes](https://github.com/thollander/actions-comment-pull-request/releases)
- [Commits](1d3973dc4b...fabd468d3a)

---
updated-dependencies:
- dependency-name: thollander/actions-comment-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 23:06:48 +10:00
dependabot[bot]
ff6a505c16 Bump nwtgck/actions-netlify from 2.1.0 to 3.0.0 (#1708)
Bumps [nwtgck/actions-netlify](https://github.com/nwtgck/actions-netlify) from 2.1.0 to 3.0.0.
- [Release notes](https://github.com/nwtgck/actions-netlify/releases)
- [Changelog](https://github.com/nwtgck/actions-netlify/blob/develop/CHANGELOG.md)
- [Commits](7a92f00dde...4cbaf4c08f)

---
updated-dependencies:
- dependency-name: nwtgck/actions-netlify
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 23:06:24 +10:00
dependabot[bot]
274c2defbf Bump softprops/action-gh-release from 1 to 2 (#1703)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](de2c0eb89a...9d7c94cfd0)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 23:00:52 +10:00
dependabot[bot]
a1e6319cfc Bump docker/build-push-action from 4.1.1 to 5.3.0 (#1704)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.1.1 to 5.3.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4.1.1...v5.3.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 22:58:56 +10:00
dependabot[bot]
4f454d1e12 Bump cla-assistant/github-action from 2.3.0 to 2.3.2 (#1705)
Bumps [cla-assistant/github-action](https://github.com/cla-assistant/github-action) from 2.3.0 to 2.3.2.
- [Release notes](https://github.com/cla-assistant/github-action/releases)
- [Commits](https://github.com/cla-assistant/github-action/compare/v2.3.0...v2.3.2)

---
updated-dependencies:
- dependency-name: cla-assistant/github-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 22:57:30 +10:00
dependabot[bot]
248e3cddef Bump dawidd6/action-download-artifact from 2.27.0 to 3.1.4 (#1706)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 2.27.0 to 3.1.4.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](246dbf436b...09f2f74827)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 22:57:14 +10:00
dependabot[bot]
02a0c644b9 Bump actions/setup-node from 3.8.1 to 4.0.2 (#1707)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3.8.1 to 4.0.2.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3.8.1...v4.0.2)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 22:56:29 +10:00
dependabot[bot]
1b281964bf Bump docker/metadata-action from 4.6.0 to 5.5.1 (#1658)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.6.0 to 5.5.1.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Upgrade guide](https://github.com/docker/metadata-action/blob/master/UPGRADE.md)
- [Commits](https://github.com/docker/metadata-action/compare/v4.6.0...v5.5.1)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 22:31:41 +10:00
dependabot[bot]
e9d7c8dd74 Bump docker/login-action from 2.2.0 to 3.1.0 (#1661)
Bumps [docker/login-action](https://github.com/docker/login-action) from 2.2.0 to 3.1.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2.2.0...v3.1.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 22:30:16 +10:00
dependabot[bot]
4ec6c34e12 Bump docker/setup-qemu-action from 2.2.0 to 3.0.0 (#1662)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2.2.0 to 3.0.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2.2.0...v3.0.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 22:28:46 +10:00
dependabot[bot]
8f94469c5a Bump actions/upload-artifact from 3.1.2 to 4.3.3 (#1698)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.1.2 to 4.3.3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3.1.2...v4.3.3)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 22:26:46 +10:00
dependabot[bot]
3e1a3aac4d Bump nginx from 1.25.1-alpine to 1.25.5-alpine (#1700)
Bumps nginx from 1.25.1-alpine to 1.25.5-alpine.

---
updated-dependencies:
- dependency-name: nginx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 00:34:04 +10:00
dependabot[bot]
973a20f9ca Bump actions/checkout from 3.5.3 to 4.1.3 (#1699)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.3 to 4.1.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.5.3...v4.1.3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 00:32:24 +10:00
Krishan
679963fa77 Update node to latest LTS (#1687)
* Update node to latest LTS

* Update node in Dockerfile
2024-04-25 00:31:01 +10:00
Arnaldo Gabriel
579b8ccc5f Fix placement of emoji/sticker buttons (#1693) 2024-04-24 18:14:32 +05:30
Ajay Bura
b3988f6e7c fix negative audio duration info crash react-range (#1701) 2024-04-24 22:42:52 +10:00
renovate[bot]
4007418192 chore(deps): update dependency vite to v5.0.13 [security] (#1680)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-08 15:18:29 +10:00
renovate[bot]
d834b1a221 fix(deps): update dependency katex to v0.16.10 [security] (#1654)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-30 12:57:56 +11:00
Ajay Bura
a45010c7db fix: login with sso when app using hash router (#1631)
* fix login with sso when app using hash router

* disable hash router
2024-01-23 18:37:22 +05:30
Ajay Bura
d4a4526457 feat: check IndexedDB support (#1630)
* check indexed db support and display message

* fix typo
2024-01-23 18:36:55 +05:30
aceArt-GmbH
a68ed7d3e9 Load assets from relative path (#1588) 2024-01-23 18:35:50 +05:30
Ajay Bura
5a8b82a12e feat: URL navigation in auth (#1603)
* bump to react 18 and install react-router-dom

* Upgrade to react 18 root

* update vite

* add cs api's

* convert state/auth to ts

* add client config context

* add auto discovery context

* add spec version context

* add auth flow context

* add background dot pattern css

* add promise utils

* init url based routing

* update auth route server path as effect

* add auth server hook

* always use server from discovery info in context

* login - WIP

* upgrade jotai to v2

* add atom with localStorage util

* add multi account sessions atom

* add default IGNORE res to auto discovery

* add error type in async callback hook

* handle password login error

* fix async callback hook

* allow password login

* Show custom server not allowed error in mxId login

* add sso login component

* add token login

* fix hardcoded m.login.password in login func

* update server input on url change

* Improve sso login labels

* update folds

* fix async callback batching state update in safari

* wrap async callback set state in queueMicrotask

* wip

* wip - register

* arrange auth file structure

* add error codes

* extract filed error component form password login

* add register util function

* handle register flow - WIP

* update unsupported auth flow method reasons

* improve password input styles

* Improve UIA flow next stage calculation
complete stages can have any order so we will look for first stage which is not in completed

* process register UIA flow stages

* Extract register UIA stages component

* improve register error messages

* add focus trap & step count in UIA stages

* add reset password path and path utils

* add path with origin hook

* fix sso redirect url

* rename register token query param to token

* restyle auth screen header

* add reset password component - WIP

* add reset password form

* add netlify rewrites

* fix netlify file indentation

* test netlify redirect

* fix vite to include netlify toml

* add more netlify redirects

* add splat to public and assets path

* fix vite base name

* add option to use hash router in config and remove appVersion

* add splash screen component

* add client config loading and error screen

* fix server picker bug

* fix reset password email input type

* make auth page small screen responsive

* fix typo in reset password screen
2024-01-21 18:20:56 +05:30
Ajay Bura
6c38dbd21b Up-mx-js-sdk-29 (#1533)
* update matrix-js-sdk

* replace deprecated resolveRoomAlias
2023-12-24 19:38:17 +05:30
Krishan
fa683dafd9 Update default server list (#1571)
Remvoe 0wnz.at from list as it seems to need registeration token which we don't support.
2023-12-03 09:28:01 +05:30
Jan Jurzitza
9163896172 Make small images not scale up in image viewer (#1554)
Instead show them in real resolution
2023-11-28 20:22:20 +05:30
Krishan
1a0d6b10ec Release v3.2.0 (#1531)
* Release v3.2.0

* Update cons.js
2023-10-31 21:20:49 +11:00
Ajay Bura
2a78b3cf8f fix typo in codeblock markdown output 2023-10-31 08:57:59 +05:30
Ajay Bura
6eb3d70596 Fix blockcode with empty lines not rendered (#1524) 2023-10-31 14:18:30 +11:00
Ajay Bura
69081298b4 Render reaction with string only key (#1522) 2023-10-31 14:17:57 +11:00
Ajay Bura
a03441c9af Timeline Perf Improvement (#1521)
* emojify msg txt find&replace instead of recursion

* move findAndReplace func in its own file

* improve find and replace

* move markdown file to plugins

* make find and replace work without g flag regex

* fix pagination stop on msg arrive

* render blurhash in small size
2023-10-30 11:28:47 +05:30
Krishan
8f19374e52 Fix grammer in membership event messages (#1520) 2023-10-30 11:28:30 +05:30
Ajay Bura
190a66b8d6 Add URL preview (#1511)
* URL preview - WIP

* fix url preview regex

* update url match regex

* add url preview components

* add scroll btn url preview holder

* add message body component

* add url preview toggle in settings

* update url regex

* improve url regex

* increase thumbnail size in url preview

* hide url preview in encrypted rooms

* add encrypted room url preview toggle
2023-10-30 07:14:58 +11:00
Ajay Bura
886c798983 Fix regex to ignore html tag in editor output (#1515) 2023-10-29 22:42:05 +11:00
Ajay Bura
91cd926f79 Fix broken emoji with md pattern in shortcode (#1514)
* fix broken emoji with md pattern in shortcode

* fix html regex when generating editor output
2023-10-29 21:53:44 +11:00
Krishan
b4e6a5bdd2 Release v3.1.0 (#1510)
* Update package.json

* Update cons.js

* Update package-lock.json
2023-10-27 22:11:08 +11:00
Ajay Bura
f1f0a126bb Improve Editor related bugs and add multiline md (#1507)
* remove shift from editor hotkeys

* fix inline markdown not working

* add block md parser - WIP

* emojify and linkify text without react-parser

* no need to sanitize text when emojify

* parse block markdown in editor output - WIP

* add inline parser option in block md parser

* improve codeblock regex

* ignore html tag when parsing inline md in block md

* add list markdown rule in block parser

* re-generate block markdown on edit

* change copy from inline markdown to markdown

* fix trim reply from body regex

* fix jumbo emoji in reply message

* fix broken list regex in block markdown

* enable markdown by defualt
2023-10-27 21:27:22 +11:00
Ajay Bura
72cdd578ee Fix-timeline-loading (#1506)
* fix timeline jump to search item after markAsRead

* improve pagination logic

* add jumbo emoji support in msg rendering
2023-10-26 10:51:55 +05:30
Ajay Bura
02adc1c2c2 Fix emoji and other related bugs (#1504)
* make system-emoji default & twitter emoji optional

* add mozilla twemoji-colr credit

* fix wrong audio duration

* set locales to empty in member count millify

* render system emoji as same size of custom emoji
2023-10-26 09:09:27 +11:00
Ajay Bura
6aca6b2e7c Room input improvements (#1502)
* prevent context menu when editing message

* send sticker body (#1479)

* update emojiboard search text reaction input label

* stop generating upload image thumbnail (#1475)

* maintain upload order

* Fix message options spinner variant

* add markdown toggle in editor toolbar

* fix heading toggle icon update with cursor move

* add hotkeys for heading

* change editor markdown btn style

* use Ctrl + Enter to send message (#1470)

* fix reaction tooltip word-break

* add shift in editor hokeys with number

* stop parsing markdown in link
2023-10-25 16:50:38 +11:00
Ajay Bura
6ed6107381 Fix reply username overflow (#1501)
* fix reply overflow

* fix shrinkable typing indicator

* fix message avatar hover & cursor
2023-10-24 22:21:39 +11:00
dependabot[bot]
b68205fb9d Bump nwtgck/actions-netlify from 2.0.0 to 2.1.0 (#1402)
Bumps [nwtgck/actions-netlify](https://github.com/nwtgck/actions-netlify) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/nwtgck/actions-netlify/releases)
- [Changelog](https://github.com/nwtgck/actions-netlify/blob/develop/CHANGELOG.md)
- [Commits](5da65c9f74...7a92f00dde)

---
updated-dependencies:
- dependency-name: nwtgck/actions-netlify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 22:05:38 +11:00
dependabot[bot]
82c79ad1ba Bump actions/setup-node from 3.6.0 to 3.8.1 (#1401)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3.6.0 to 3.8.1.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3.6.0...v3.8.1)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 22:03:00 +11:00
dependabot[bot]
5c261275d0 Bump thollander/actions-comment-pull-request from 2.4.0 to 2.4.3 (#1480)
Bumps [thollander/actions-comment-pull-request](https://github.com/thollander/actions-comment-pull-request) from 2.4.0 to 2.4.3.
- [Release notes](https://github.com/thollander/actions-comment-pull-request/releases)
- [Commits](dadb766712...1d3973dc4b)

---
updated-dependencies:
- dependency-name: thollander/actions-comment-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-23 21:49:38 +11:00
Ajay Bura
fb8b706c80 fix thread fallback (#1478) 2023-10-23 21:43:07 +11:00
Ajay Bura
0f84928e7c Fix edit related bugs (#1477)
* fix missing empty line on edit

* fix edit save after adding formatting to plaintext

* fix reading edit content with wrong key
2023-10-23 21:42:27 +11:00
Krishan
6b85a85170 Release v3.0.0 (#1463)
* Release v3.0.0

* Update package-lock.json

* Update cons.js
2023-10-21 21:37:30 +11:00
Krishan
a7b9475660 Update default server list in config.json (#1467)
* Remove halogen.city

* Update config.json

* Update config.json
2023-10-21 16:06:13 +05:30
Ajay Bura
25d924aa81 fix backward delete with previous empty line (#1469) 2023-10-21 15:46:36 +05:30
Ajay Bura
50dbd81d2d Fix hotkeys (#1468)
* use hotkey using key instead of which (default)

* remove shift from block formatting hotkeys

* smartly exit formatting with backspace

* set markdown to off by default

* exit formatting with escape
2023-10-21 12:44:33 +05:30
Ajay Bura
a1a019b6db Fix auto read (#1466)
* add height to bottom anchor

* add width to bottom anchor

* add make bottom anchor inline-block

* try mark as read on focus receive
2023-10-21 12:44:21 +05:30
Ajay Bura
009d1893b1 fix wrong following member count on message sent (#1464) 2023-10-20 14:09:47 +05:30
Ajay Bura
64fe4c99d0 Add text reaction (#1462) 2023-10-19 22:20:38 +11:00
Ajay Bura
5d83be2a9e Change loading session message (#1461) 2023-10-19 21:41:31 +11:00
Ajay Bura
a9be0a6628 remove twemoji & katex usage (#1460) 2023-10-19 17:44:18 +11:00
Ajay Bura
f5079300b8 Fix room mention (#1459)
* create room mention with alias if possible

* display room mention text as they were sent
2023-10-19 17:43:54 +11:00
Ajay Bura
cb7fd4ddd4 fix recursive state updates (#1458) 2023-10-19 17:43:37 +11:00
Ajay Bura
192c21c108 Member drawer filter (#1457)
* save member drawer sort filter in local storage

* render member drawer with key

* improve member search
2023-10-19 17:43:16 +11:00
Ajay Bura
498263025e use aria-react for message hover & focus hooks (#1456) 2023-10-19 17:42:35 +11:00
Ajay Bura
59eb93cfac Fix Boken Image & Sticker (#1455)
* fix image without info rendered as broken

* fix enc msg appear as decrypting after deletion
2023-10-19 17:41:49 +11:00
Ajay Bura
dbc63c51d1 Fix unread bug (#1454)
* remove unread info on mark as read

* fix roomId is not provided to markAsRead

* fix auto mark as read
2023-10-19 17:40:01 +11:00
Ajay Bura
a0585d040a Editor Commands (#1450)
* add commands hook

* add commands in editor

* add command auto complete menu

* add commands in room input

* remove old reply code from room input

* fix video component css

* do not auto focus input on android or ios

* fix crash on enable block after selection

* fix circular deps in editor

* fix autocomplete return focus move editor cursor

* remove unwanted keydown from room input

* fix emoji alignment in editor

* test ipad user agent

* refactor isAndroidOrIOS to mobileOrTablet

* update slate & slate-react

* downgrade slate-react to 0.98.4
0.99.0 has breaking changes with ReactEditor.focus

* add sql to readable ext mimetype

* fix empty editor formatting gets saved as draft

* add option to use enter for newline

* remove empty msg draft from atom family

* prevent msg ctx menu from open on text selection
2023-10-18 07:45:30 +05:30
Krishan
cd4709899a Fix verification notice not to display when CS is not setup (#1451) 2023-10-18 07:45:08 +05:30
Ajay Bura
46e3cb901a Edit option (#1447)
* add func to parse html to editor input

* add  plain to html input function

* re-construct markdown

* fix missing return

* fix falsy condition

* fix reading href instead of src of emoji

* add message editor - WIP

* fix plain to editor input func

* add save edit message functionality

* show edited event source code

* focus message input on after editing message

* use del tag for strike-through instead of s

* prevent autocomplete from re-opening after esc

* scroll out of view msg editor in view

* handle up arrow edit

* handle scroll to message editor without effect

* revert prev commit: effect run after editor render

* ignore relation event from editable

* allow data-md tag for del and em in sanitize html

* prevent edit without changes

* ignore previous reply when replying to msg

* fix up arrow edit not working sometime
2023-10-14 10:38:43 +05:30
Ajay Bura
b1a89f92bc Render file as readable with ext (#1446) 2023-10-10 11:37:28 +05:30
Ajay Bura
bedac23b47 show missing member in read receipt (#1445) 2023-10-10 11:37:15 +05:30
Ajay Bura
ec5d676911 make file, image viewer wide (#1444) 2023-10-10 11:37:03 +05:30
Ajay Bura
20e11f665a Inline markdown in editor (#1442)
* add inline markdown in editor

* send markdown re-generative data in tags

* enable vscode format on save

* fix match italic and diff order

* prevent formatting in code block

* make code md rule highest

* improve inline markdown parsing

* add comment

* improve code logic
2023-10-09 16:56:54 +05:30
Ajay Bura
e3882f39d7 consider membership change with reason change (#1441) 2023-10-08 11:05:16 +05:30
Ajay Bura
eba69b3f0a Fix-jump-latest-senstivity (#1440)
* fix jump to latest sensitivity

* select mention space as tab
2023-10-08 00:09:43 +11:00
Ajay Bura
b35bb8d830 Fix space mention (#1439)
* open space on space mention click

* fix styles

* fix message options sticks

* revert last changes
2023-10-07 14:51:35 +05:30
Ajay Bura
d0aeb24bad Timeline-refactor-fixes (#1438)
* fix type

* fix missing member from reaction

* stop context menu event propagation in msg modal

* prevent encode blur hash from freezing app

* replace roboto font with inter and fix weight

* add recent emoji when selecting emoji

* fix room latest evt hook

* add option to drop typing status
2023-10-07 18:19:01 +11:00
Cadence Ember
3805283e88 Prompt to send command as message (#1435) 2023-10-06 08:18:48 +05:30
Ajay Bura
f832f3fbf2 Refactor timeline (#1346)
* fix intersection & resize observer

* add binary search util

* add scroll info util

* add virtual paginator hook - WIP

* render timeline using paginator hook

* add continuous pagination to fill timeline

* add doc comments in virtual paginator hook

* add scroll to element func in virtual paginator

* extract timeline pagination login into hook

* add sliding name for timeline messages - testing

* scroll with live event

* change message rending style

* make message timestamp smaller

* remove unused imports

* add random number between util

* add compact message component

* add sanitize html types

* fix sending alias in room mention

* get room member display name util

* add get room with canonical alias util

* add sanitize html util

* render custom html with new styles

* fix linkifying link text

* add reaction component

* display message reactions in timeline

* Change mention color

* show edited message

* add event sent by function factory

* add functions to get emoji shortcode

* add component for reaction msg

* add tooltip for who has reacted

* add message layouts & placeholder

* fix reaction size

* fix dark theme colors

* add code highlight with prismjs

* add options to configure spacing in msgs

* render message reply

* fix trim reply from body regex

* fix crash when loading reply

* fix reply hover style

* decrypt event on timeline paginate

* update custom html code style

* remove console logs

* fix virtual paginator scroll to func

* fix virtual paginator scroll to types

* add stop scroll for in view item options

* fix virtual paginator out of range scroll to index

* scroll to and highlight reply on click

* fix reply hover style

* make message avatar clickable

* fix scrollTo issue in virtual paginator

* load reply from fetch

* import virtual paginator restore scroll

* load timeline for specific event

* Fix back pagination recalibration

* fix reply min height

* revert code block colors to secondary

* stop sanitizing text in code block

* add decrypt file util

* add image media component

* update folds

* fix code block font style

* add msg event type

* add scale dimension util

* strict msg layout type

* add image renderer component

* add message content fallback components

* add message matrix event renderer components

* render matrix event using hooks

* add attachment component

* add attachment content types

* handle error when rendering image in timeline

* add video component

* render video

* include blurhash in thumbnails

* generate thumbnails for image message

* fix reactToDom spoiler opts

* add hooks for HTMLMediaElement

* render audio file in timeline

* add msg image content component

* fix image content props

* add video content component

* render new image/video component in timeline

* remove console.log

* convert seconds to milliseconds in video info

* add load thumbnail prop to video content component

* add file saver types

* add file header component

* add file content component

* render file in timeline

* add media control component

* render audio message in room timeline

* remove moved components

* safely load message reply

* add media loading hook

* update media control layout

* add loading indication in audio component

* fill audio play icon when playing audio

* fix media expanding

* add image viewer - WIP

* add pan and zoom control to image viewer

* add text based file viewer

* add pdf viewer

* add error handling in pdf viewer

* add download btn to pdf viewer

* fix file button spinner fill

* fix file opens on re-render

* add range slider in audio content player

* render location in timeline

* update folds

* display membership event in timeline

* make reactions toggle

* render sticker messages in timeline

* render room name, topic, avatar change and event

* fix typos

* update render state event type style

* add  room intro in start of timeline

* add power levels context

* fix wrong param passing in RoomView

* fix sending typing notification in wrong room

Slate onChange callback was not updating with react re-renders.

* send typing status on key up

* add typing indicator component

* add typing member atom

* display typing status in member drawer

* add room view typing member component

* display typing members in room view

* remove old roomTimeline uses

* add event readers hook

* add latest event hook

* display following members in room view

* fetch event instead of event context for reply

* fix typo in virtual paginator hook

* add scroll to latest btn in timeline

* change scroll to latest chip variant

* destructure paginator object to improve perf

* restore forward dir scroll in virtual paginator

* run scroll to bottom in layout effect

* display unread message indicator in timeline

* make component for room timeline float

* add timeline divider component

* add day divider and format message time

* apply message spacing to dividers

* format date in room intro

* send read receipt on message arrive

* add event readers component

* add reply, read receipt, source delete opt

* bug fixes

* update timeline on delete & show reason

* fix empty reaction container style

* show msg selection effect on msg option open

* add report message options

* add options to send quick reactions

* add emoji board in message options

* add reaction viewer

* fix styles

* show view reaction in msg options menu

* fix spacing between two msg by same person

* add option menu in other rendered event

* handle m.room.encrypted messages

* fix italic reply text overflow cut

* handle encrypted sticker messages

* remove console log

* prevent message context menu with alt key pressed

* make mentions clickable in messages

* add options to show and hidden events in timeline

* add option to disable media autoload

* remove old emojiboard opener

* add options to use system emoji

* refresh timeline on reset

* fix stuck typing member in member drawer
2023-10-06 08:14:06 +05:30
Alliegaytor
eb5c50c61c Fix notifications not displaying when document is not focused (#1425)
Allows notifications from the active room while app is not focused (e.g. tabbed out)
2023-09-24 10:01:02 +05:30
Emi
9109be896d Fix permission detection for updating emojis (#1125) 2023-09-01 10:19:34 +05:30
greentore
fe2e58c744 Prevent manifest.json from being inlined (#1359)
* Disable asset inlining

* Prevent `manifest.json` from being inlined

* Update backtick to single quote in vite.config.js

---------

Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
2023-08-03 09:53:28 +05:30
ts
e36e45fcfa Fix Profile Viewer text (#1357)
If you only had a single session open, the Profile Viewer would've said "View 1 sessions" instead of "View 1 session."
2023-07-27 09:25:10 +05:30
greentore
a62cafd972 Passive private receipt support (#1108)
Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
2023-07-24 10:10:43 +05:30
Ajay Bura
fb4be9da74 Fix editor custom html output (#1348)
* replace paragraph with line breaks

* stop sending plain msg as custom html

* removes console log

* fix false negative for sanitized customHtml

* fix customHtmlEqualsPlainText doc
2023-07-23 13:42:09 +05:30
greentore
9dd4ad4ae4 Use sticker body for searching (#1347) 2023-07-23 13:41:36 +05:30
Ajay Bura
152061a518 fix msg event permission check (#1315) 2023-06-28 21:57:28 +10:00
Ajay Bura
29a9161680 Update member drawer icons (#1312)
* update folds

* update member drawer icons
2023-06-25 08:40:48 +05:30
Ajay Bura
3cd03b8bc6 Fix member panel filter layout (#1307)
* fix member panel filter layout

* make member role text lowercase
2023-06-23 09:46:04 +10:00
Ajay Bura
b37328d382 Improve Members Right Panel (#1286)
* fix room members hook

* fix resize observer hook

* add intersection observer hook

* install react-virtual lib

* improve right panel - WIP

* add filters for members

* fix bug in async search

* categories members and add search

* show spinner on room member fetch

* make invite member btn clickable

* so no member text

* add line between room view and member drawer

* fix imports

* add screen size hook

* fix set setting hook

* make member drawer responsive

* extract power level tags hook

* fix room members hook

* fix use async search api

* produce search result on filter change
2023-06-22 09:14:50 +10:00
Krishan
10cc53cfa4 Update project link (#1302) 2023-06-21 17:56:27 +05:30
Ajay Bura
7c76553ef6 fix global pack showing all room packs (#1303) 2023-06-21 20:59:02 +10:00
ZeroAurora
67ac5f4d69 Improve verification instructions (#1301) 2023-06-21 10:00:43 +10:00
dependabot[bot]
24acd61a93 Bump docker/setup-buildx-action from 2.6.0 to 2.7.0 (#1293)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.6.0 to 2.7.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.6.0...v2.7.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-20 09:10:12 +10:00
dependabot[bot]
539fcd5bad Bump cla-assistant/github-action from 2.2.1 to 2.3.0 (#1294)
Bumps [cla-assistant/github-action](https://github.com/cla-assistant/github-action) from 2.2.1 to 2.3.0.
- [Release notes](https://github.com/cla-assistant/github-action/releases)
- [Commits](https://github.com/cla-assistant/github-action/compare/v2.2.1...v2.3.0)

---
updated-dependencies:
- dependency-name: cla-assistant/github-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-20 09:09:25 +10:00
dependabot[bot]
360fdc291d Bump docker/metadata-action from 4.5.0 to 4.6.0 (#1292)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v4.5.0...v4.6.0)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-20 09:09:06 +10:00
dependabot[bot]
c180d9a17d Bump docker/build-push-action from 4.1.0 to 4.1.1 (#1290)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-20 09:08:37 +10:00
dependabot[bot]
8ee95ab25b Bump docker/setup-qemu-action from 2.1.0 to 2.2.0 (#1295)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2.1.0...v2.2.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-20 09:08:08 +10:00
dependabot[bot]
9cbeec16d0 Bump docker/login-action from 2.1.0 to 2.2.0 (#1289)
Bumps [docker/login-action](https://github.com/docker/login-action) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2.1.0...v2.2.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-20 09:07:34 +10:00
dependabot[bot]
2f1d4eca5e Bump nginx from 1.25.0-alpine to 1.25.1-alpine (#1288)
Bumps nginx from 1.25.0-alpine to 1.25.1-alpine.

---
updated-dependencies:
- dependency-name: nginx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-20 09:07:04 +10:00
Ajay Bura
3d9e9606c8 Add editor history (#1284)
* add slate editor history

* reset mark on editor reset
2023-06-16 11:11:03 +10:00
Ajay Bura
e14a52620f Add ESC btn to toolbar to quickly exit formatting (#1283)
* Add ESC btn to toolbar to quickly exit formatting

* add horizontal scroll to toolbar item

* make editor toolbar usable in touch device

* fix editor hotkeys not working in window

* remove unused import
2023-06-16 11:09:09 +10:00
Ajay Bura
cfd2cb3b9c Fix editor bugs (#1281)
* focus editor on reply click

* fix emoji and sticker img object-fit

* fix cursor not moving with autocomplete

* stop sanitizing sending plain text body

* improve autocomplete query parsing

* add escape to turn off active editor toolbar item
2023-06-13 23:17:18 +05:30
dependabot[bot]
bd5475685b Bump docker/build-push-action from 3.2.0 to 4.1.0 (#1275)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.2.0 to 4.1.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.2.0...v4.1.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-13 09:29:18 +10:00
dependabot[bot]
a387124a62 Bump docker/setup-buildx-action from 2.2.1 to 2.6.0 (#1274)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.2.1 to 2.6.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.2.1...v2.6.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-13 09:28:39 +10:00
dependabot[bot]
ac9142b797 Bump thollander/actions-comment-pull-request from 2.3.1 to 2.4.0 (#1272)
Bumps [thollander/actions-comment-pull-request](https://github.com/thollander/actions-comment-pull-request) from 2.3.1 to 2.4.0.
- [Release notes](https://github.com/thollander/actions-comment-pull-request/releases)
- [Commits](632cf9ce90...dadb766712)

---
updated-dependencies:
- dependency-name: thollander/actions-comment-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-13 09:28:07 +10:00
dependabot[bot]
2a91679091 Bump docker/metadata-action from 4.1.1 to 4.5.0 (#1271)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.1.1 to 4.5.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v4.1.1...v4.5.0)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-13 09:26:54 +10:00
dependabot[bot]
5da2006a6e Bump nginx from 1.23.3-alpine to 1.25.0-alpine (#1254)
Bumps nginx from 1.23.3-alpine to 1.25.0-alpine.

---
updated-dependencies:
- dependency-name: nginx
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-13 09:25:20 +10:00
renovate[bot]
28d87dbe53 fix(deps): update dependency matrix-js-sdk to v24.1.0 [security] (#1251)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-13 09:24:17 +10:00
dependabot[bot]
922688a754 Bump actions/checkout from 3.2.0 to 3.5.3 (#1276)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.2.0 to 3.5.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.2.0...v3.5.3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-13 09:21:07 +10:00
dependabot[bot]
001dc502e0 Bump dawidd6/action-download-artifact from 2.24.2 to 2.27.0 (#1202)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 2.24.2 to 2.27.0.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](e6e25ac3a2...246dbf436b)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-12 21:38:53 +10:00
dependabot[bot]
93d18162bf Bump thollander/actions-comment-pull-request from 2.0.0 to 2.3.1 (#1081)
Bumps [thollander/actions-comment-pull-request](https://github.com/thollander/actions-comment-pull-request) from 2.0.0 to 2.3.1.
- [Release notes](https://github.com/thollander/actions-comment-pull-request/releases)
- [Commits](c22fb30220...632cf9ce90)

---
updated-dependencies:
- dependency-name: thollander/actions-comment-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-12 21:36:13 +10:00
dependabot[bot]
49aa615341 Bump actions/setup-node from 3.5.1 to 3.6.0 (#1057)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3.5.1 to 3.6.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3.5.1...v3.6.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-12 21:34:23 +10:00
dependabot[bot]
f8bc7abfbc Bump actions/upload-artifact from 3.1.1 to 3.1.2 (#1055)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3.1.1...v3.1.2)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-12 21:32:10 +10:00
dependabot[bot]
4d58479ed4 Bump vite from 4.0.1 to 4.3.9 (#1256)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.0.1 to 4.3.9.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.3.9/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-12 21:29:33 +10:00
Ajay Bura
64f2f25702 Refactor state & Custom editor (#1190)
* Fix eslint

* Enable ts strict mode

* install folds, jotai & immer

* Enable immer map/set

* change cross-signing alert anim to 30 iteration

* Add function to access matrix client

* Add new types

* Add disposable util

* Add room utils

* Add mDirect list atom

* Add invite list atom

* add room list atom

* add utils for jotai atoms

* Add room id to parents atom

* Add mute list atom

* Add room to unread atom

* Use hook to bind atoms with sdk

* Add settings atom

* Add settings hook

* Extract set settings hook

* Add Sidebar components

* WIP

* Add bind atoms hook

* Fix init muted room list atom

* add navigation atoms

* Add custom editor

* Fix hotkeys

* Update folds

* Add editor output function

* Add matrix client context

* Add tooltip to editor toolbar items

* WIP - Add editor to room input

* Refocus editor on toolbar item click

* Add Mentions - WIP

* update folds

* update mention focus outline

* rename emoji element type

* Add auto complete menu

* add autocomplete query functions

* add index file for editor

* fix bug in getPrevWord function

* Show room mention autocomplete

* Add async search function

* add use async search hook

* use async search in room mention autocomplete

* remove folds prefer font for now

* allow number array in async search

* reset search with empty query

* Autocomplete unknown room mention

* Autocomplete first room mention on tab

* fix roomAliasFromQueryText

* change mention color to primary

* add isAlive hook

* add getMxIdLocalPart to mx utils

* fix getRoomAvatarUrl size

* fix types

* add room members hook

* fix bug in room mention

* add user mention autocomplete

* Fix async search giving prev result after no match

* update folds

* add twemoji font

* add use state provider hook

* add prevent scroll with arrow key util

* add ts to custom-emoji and emoji files

* add types

* add hook for emoji group labels

* add hook for emoji group icons

* add emoji board with basic emoji

* add emojiboard in room input

* select multiple emoji with shift press

* display custom emoji in emojiboard

* Add emoji preview

* focus element on hover

* update folds

* position emojiboard properly

* convert recent-emoji.js to ts

* add use recent emoji hook

* add io.element.recent_emoji to account data evt

* Render recent emoji in emoji board

* show custom emoji from parent spaces

* show room emoji

* improve emoji sidebar

* update folds

* fix pack avatar and name fallback in emoji board

* add stickers to emoji board

* fix bug in emoji preview

* Add sticker icon in room input

* add debounce hook

* add search in emoji board

* Optimize emoji board

* fix emoji board sidebar divider

* sync emojiboard sidebar with scroll & update ui

* Add use throttle hook

* support custom emoji in editor

* remove duplicate emoji selection function

* fix emoji and mention spacing

* add emoticon autocomplete in editor

* fix string

* makes emoji size relative to font size in editor

* add option to render link element

* add spoiler in editor

* fix sticker in emoji board search using wrong type

* render custom placeholder

* update hotkey for block quote and block code

* add terminate search function in async search

* add getImageInfo to matrix utils

* send stickers

* add resize observer hook

* move emoji board component hooks in hooks dir

* prevent editor expand hides room timeline

* send typing notifications

* improve emoji style and performance

* fix imports

* add on paste param to editor

* add selectFile utils

* add file picker hook

* add file paste handler hook

* add file drop handler

* update folds

* Add file upload card

* add bytes to size util

* add blurHash util

* add await to js lib

* add browser-encrypt-attachment types

* add list atom

* convert mimetype file to ts

* add matrix types

* add matrix file util

* add file related dom utils

* add common utils

* add upload atom

* add room input draft atom

* add upload card renderer component

* add upload board component

* add support for file upload in editor

* send files with message / enter

* fix circular deps

* store editor toolbar state in local store

* move msg content util to separate file

* store msg draft on room switch

* fix following member not updating on msg sent

* add theme for folds component

* fix system default theme

* Add reply support in editor

* prevent initMatrix to init multiple time

* add state event hooks

* add async callback hook

* Show tombstone info for tombstone room

* fix room tombstone component border

* add power level hook

* Add room input placeholder component

* Show input placeholder for muted member
2023-06-12 16:45:23 +05:30
Thumbscrew
c15321e78d add document.hasFocus check for incoming room events (#1252) 2023-05-28 21:24:10 +05:30
Ajay Bura
d2b3c766a0 fix: spoiler hidden link click (#1199) 2023-04-16 22:22:01 +10:00
Bo
6b94aff5d4 fix: Fixed small typo an cross signing reset modal (#1112) 2023-03-30 20:12:33 +05:30
Krishan
181b69468f Release v2.2.6 (#1178)
* Update package.json

* Update package-lock.json

* Update cons.js
2023-03-29 22:02:01 +11:00
Krishan
1defce2d24 Fix docker build failing (#1177) 2023-03-29 21:57:05 +11:00
1470 changed files with 147038 additions and 32567 deletions

View file

@ -1,2 +1,3 @@
experiment experiment
node_modules node_modules
*.css

99
.eslintrc.cjs Normal file
View file

@ -0,0 +1,99 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'airbnb',
'prettier',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
globals: {
JSX: 'readonly',
__APP_VERSION__: 'readonly',
},
plugins: ['react', '@typescript-eslint'],
rules: {
'linebreak-style': 0,
'no-underscore-dangle': 0,
'no-shadow': 'off',
'import/prefer-default-export': 'off',
'import/extensions': 'off',
'import/no-unresolved': 'off',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
},
],
'react/no-unstable-nested-components': ['error', { allowAsProps: true }],
'react/jsx-filename-extension': [
'error',
{
extensions: ['.tsx', '.jsx'],
},
],
'react/require-default-props': 'off',
'react/jsx-props-no-spreading': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error',
// Disable base rules in favour of their @typescript-eslint counterparts —
// the base rules can't see TS-specific constructs (interface members, type
// imports, etc.) and double-fire alongside the TS versions.
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-shadow': 'error',
// Policy: kept as `warn` at the rule level so editors / `eslint --fix` /
// ad-hoc runs surface them as warnings, but `npm run check:eslint` and
// `lint-staged` BOTH pass `--max-warnings 0`, so new occurrences block
// commit. When unavoidable (matrix-js-sdk boundary, generic helpers,
// third-party callback shapes), suppress on the line with
// `// eslint-disable-next-line` and a one-line justification.
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-non-null-assertion': 'warn',
},
overrides: [
{
files: ['*.ts', '*.tsx'],
rules: {
'no-undef': 'off',
},
},
{
// Upstream-vendored binary parsing copied verbatim from matrix-react-sdk
// (src/util/cryptE2ERoomKeys.js header link). Bitwise ops, post-increment
// and string concatenation are correct for the domain — clean-up risks
// breaking E2E room-key import/export. Keep the body byte-identical to
// upstream and disable only the rules that fire on those idioms.
files: ['src/util/cryptE2ERoomKeys.js'],
rules: {
'no-bitwise': 'off',
'no-plusplus': 'off',
'prefer-template': 'off',
'no-param-reassign': 'off',
// `for (;;)` form upstream uses for the iter-loops trips eslint
// even though it's intentional — keep upstream control flow.
'no-constant-condition': 'off',
// Diagnostic `console.log` left as-is in vendor copy.
'no-console': 'off',
},
},
],
};

View file

@ -1,59 +0,0 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
'airbnb',
'prettier',
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: [
'react',
'@typescript-eslint'
],
rules: {
'linebreak-style': 0,
'no-underscore-dangle': 0,
"import/prefer-default-export": "off",
"import/extensions": "off",
"import/no-unresolved": "off",
"import/no-extraneous-dependencies": [
"error",
{
devDependencies: true,
},
],
'react/no-unstable-nested-components': [
'error',
{ allowAsProps: true },
],
"react/jsx-filename-extension": [
"error",
{
extensions: [".tsx", ".jsx"],
},
],
"react/require-default-props": "off",
"react/jsx-props-no-spreading": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error",
"@typescript-eslint/no-unused-vars": "error",
},
};

View file

@ -0,0 +1,125 @@
labels: ["needs-confirmation"]
body:
- type: markdown #add faqs in future
attributes:
value: |
> [!IMPORTANT]
> Please check for both existing Discussions and Issues prior to opening a new Discussion.
- type: markdown
attributes:
value: "# Issue Details"
- type: textarea
attributes:
label: Issue Description
description: |
Provide a detailed description of the issue. Include relevant information, such as:
- The feature or configuration option you encounter the issue with.
- Screenshots, screen recordings, or other supporting media (as needed).
- If this is a regression of an existing issue that was closed or resolved, please include the previous item reference (Discussion, Issue, PR, commit) in your description.
placeholder: |
When I try to send a message in a room, the message doesn't appear in the timeline.
OR
The application crashes when I click on the settings button.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: |
Describe how you expect Vojo to behave in this situation.
placeholder: |
I expected the message to appear in the room timeline immediately after sending.
OR
The settings panel should open smoothly without any crashes.
validations:
required: true
- type: textarea
attributes:
label: Actual Behavior
description: |
Describe how Vojo actually behaves in this situation. If it is not immediately obvious how the actual behavior differs from the expected behavior described above, please be sure to mention the deviation specifically.
placeholder: |
The application freezes for 3 seconds and then shows a white screen.
validations:
required: true
- type: textarea
attributes:
label: Reproduction Steps
description: |
Provide a detailed set of step-by-step instructions for reproducing this issue.
placeholder: |
1. Open Vojo and log in to my account
2. Navigate to the #general room
3. Type a message in the message box
4. Press Enter to send
5. Notice that the message doesn't appear in the timeline
validations:
required: true
- type: textarea
attributes:
label: Environement
description: |
Please provide information about your environment. Include the following:
- OS:
- Browser:
- Vojo Web Version: (vojo.chat or self hosted)
- Matrix Homeserver:
placeholder: |
- OS: Windows 11
- Browser: Chrome 120.0.6099.109
- Vojo Web Version: (vojo.chat or self hosted)
- Matrix Homeserver: matrix.org (Synapse 1.97.0)
render: text
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant Logs
description: |
If applicable, add browser console logs to help explain your problem.
**To get browser console logs:**
- Chrome/Edge: Press F12 → Console tab
- Firefox: Press F12 → Console tab
- Safari: Develop → Show Web Inspector → Console
Please wrap large log outputs in code blocks with triple backticks (```).
placeholder: |
```
Error: Failed to send message
at MessageComposer.sendMessage (composer.js:245)
at HTMLButtonElement.onClick (composer.js:189)
TypeError: Cannot read property 'content' of undefined
at RoomTimeline.render (timeline.js:567)
```
render: shell
validations:
required: false
- type: textarea
attributes:
label: Additional context
description: |
Add any other context about the problem here (e.g., when did this start happening, does it happen on different homeservers, etc.)
placeholder: |
- This started happening after I updated to version 3.2.0
- It only happens in encrypted rooms, not in public rooms
- I've tried on both Firefox and Chrome with the same result
- It works fine on my phone using the same account
- This happens on all homeservers I've tested (matrix.org, mozilla.org)
validations:
required: false
- type: markdown
attributes:
value: |
# User Acknowledgements
> [!TIP]
> Use the search function to review existing Discussions and Issues.
- type: checkboxes #add faqs in future
attributes:
label: "I acknowledge that:"
options:
- label: I have searched the Vojo repository (both open and closed Discussions and Issues) and confirm this is not a duplicate of an existing issue or discussion.
required: true
- label: I have checked the "Preview" tab on all text fields to ensure that everything looks right, and have wrapped all configuration and code in code blocks with a group of three backticks (` ``` `) on separate lines.
required: true

4
.github/FUNDING.yml vendored
View file

@ -1,3 +1 @@
github: ajbura # Vojo project funding
liberapay: ajbura
open_collective: cinny

View file

@ -1,57 +0,0 @@
name: 🐞 Bug Report
description: Report a bug
body:
- type: markdown
attributes:
value: |
## First of all
1. Please search for [existing issues](https://github.com/ajbura/cinny/issues?q=is%3Aissue) about this problem first.
2. Make sure Cinny is up to date.
3. Make sure it's an issue with Cinny and not something else you are using.
4. Remember to be friendly.
- type: textarea
id: description
attributes:
label: Describe the bug
description: A clear description of what the bug is. Include screenshots if applicable.
placeholder: Bug description
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction
description: Steps to reproduce the behavior.
placeholder: |
1. Go to ...
2. Click on ...
3. See error
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: A clear description of what you expected to happen.
- type: textarea
id: info
attributes:
label: Platform and versions
description: "Provide OS, browser and Cinny version with your Homeserver."
placeholder: |
1. OS: [e.g. Windows 10, MacOS]
2. Browser: [e.g. chrome 99.5, firefox 97.2]
3. Cinny version: [e.g. 1.8.1 (app.cinny.in)]
4. Matrix homeserver: [e.g. matrix.org]
render: shell
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context about the problem here.

View file

@ -1,4 +1,5 @@
blank_issues_enabled: false
contact_links: contact_links:
- name: 💬 Matrix Chat - name: Features, Bug Reports, Questions
url: https://matrix.to/#/#cinny:matrix.org url: https://github.com/nicejuice-cc/vojo/discussions/new/choose
about: Ask questions and talk to other Cinny users and the maintainers about: Our preferred starting point if you have any questions or suggestions about features or behavior.

View file

@ -1,33 +0,0 @@
name: 💡 Feature Request
description: Suggest an idea
body:
- type: textarea
id: problem
attributes:
label: Describe the problem
description: A clear description of the problem this feature would solve
placeholder: "I'm always frustrated when..."
validations:
required: true
- type: textarea
id: solution
attributes:
label: "Describe the solution you'd like"
description: A clear description of what change you would like
placeholder: "I would like to..."
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: "Any alternative solutions you've considered"
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context about the problem here.

9
.github/ISSUE_TEMPLATE/preapproved.md vendored Normal file
View file

@ -0,0 +1,9 @@
---
name: Pre-Discussed and Approved Topics
about: |-
Only for topics already discussed and approved in the GitHub Discussions section.
---
**DO NOT OPEN A NEW ISSUE. PLEASE USE THE DISCUSSIONS SECTION.**
**I DIDN'T READ THE ABOVE LINE. PLEASE CLOSE THIS ISSUE.**

View file

@ -1,22 +0,0 @@
<!-- Please read https://github.com/ajbura/cinny/blob/dev/CONTRIBUTING.md before submitting your pull request -->
### Description
<!-- Please include a summary of the change. Please also include relevant motivation and context. List any dependencies that are required for this change. -->
Fixes #
#### Type of change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
### Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings

3
.github/SECURITY.md vendored
View file

@ -1,3 +0,0 @@
# Reporting a Vulnerability
**If you've found a security vulnerability, please report it to cinnyapp@gmail.com**

View file

@ -2,14 +2,14 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: npm # - package-ecosystem: npm
directory: / # directory: /
schedule: # schedule:
interval: weekly # interval: weekly
day: "tuesday" # day: "tuesday"
time: "01:00" # time: "01:00"
timezone: "Asia/Kolkata" # timezone: "Asia/Kolkata"
open-pull-requests-limit: 15 # open-pull-requests-limit: 15
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: / directory: /

23
.github/renovate.json vendored
View file

@ -1,15 +1,32 @@
{ {
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [ "extends": [
"config:base", "config:recommended",
":dependencyDashboardApproval" ":dependencyDashboardApproval",
":semanticCommits",
"group:monorepos"
], ],
"labels": ["Dependencies"], "labels": ["Dependencies"],
"rebaseWhen": "conflicted",
"packageRules": [ "packageRules": [
{ {
"matchUpdateTypes": ["lockFileMaintenance"] "matchUpdateTypes": ["lockFileMaintenance"]
},
{
"groupName": "Slatejs",
"matchPackageNames": ["slate", "slate-dom", "slate-history", "slate-react"]
},
{
"groupName": "Call",
"matchPackageNames": ["@element-hq/element-call-embedded", "matrix-widget-api"]
},
{
"groupName": "Linkify",
"matchPackageNames": ["linkifyjs", "linkify-react"]
} }
], ],
"lockFileMaintenance": { "enabled": true }, "lockFileMaintenance": {
"enabled": true
},
"dependencyDashboard": true "dependencyDashboard": true
} }

View file

@ -12,20 +12,20 @@ jobs:
PR_NUMBER: ${{github.event.number}} PR_NUMBER: ${{github.event.number}}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.2.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup node - name: Setup node
uses: actions/setup-node@v3.5.1 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with: with:
node-version: 18.12.1 node-version-file: ".node-version"
cache: "npm" package-manager-cache: false
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Build app - name: Build app
env: env:
NODE_OPTIONS: "--max_old_space_size=4096" NODE_OPTIONS: '--max_old_space_size=4096'
run: npm run build run: npm run build
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v3.1.1 uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with: with:
name: preview name: preview
path: dist path: dist
@ -33,7 +33,7 @@ jobs:
- name: Save pr number - name: Save pr number
run: echo ${PR_NUMBER} > ./pr.txt run: echo ${PR_NUMBER} > ./pr.txt
- name: Upload pr number - name: Upload pr number
uses: actions/upload-artifact@v3.1.1 uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with: with:
name: pr name: pr
path: ./pr.txt path: ./pr.txt

View file

@ -1,36 +0,0 @@
name: 'CLA Assistant'
on:
issue_comment:
types: [created]
pull_request_target:
types: [opened, closed, synchronize]
jobs:
CLAssistant:
runs-on: ubuntu-latest
steps:
- name: 'CLA Assistant'
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
# Beta Release
uses: cla-assistant/github-action@v2.2.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# the below token should have repo scope and must be manually added by you in the repository's secret
PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_PAT }}
with:
path-to-signatures: 'signatures.json'
path-to-document: 'https://github.com/cinnyapp/cla/blob/main/cla.md' # e.g. a CLA or a DCO document
# branch should not be protected
branch: 'main'
allowlist: ajbura,bot*
#below are the optional inputs - If the optional inputs are not given, then default values will be taken
remote-organization-name: cinnyapp
remote-repository-name: cla
#create-file-commit-message: 'For example: Creating file for storing CLA Signatures'
#signed-commit-message: 'For example: $contributorName has signed the CLA in #$pullRequestNo'
#custom-notsigned-prcomment: 'pull request comment with Introductory message to ask new contributors to sign'
#custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA'
#custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.'
#lock-pullrequest-aftermerge: false - if you don't want this bot to automatically lock the pull request after merging (default - true)
#use-dco-flag: true - If you are using DCO instead of CLA

View file

@ -1,4 +1,5 @@
name: Deploy PR to Netlify name: Deploy PR to Netlify
run-name: "Deploy PR to Netlify (${{ github.event.workflow_run.head_branch }})"
on: on:
workflow_run: workflow_run:
@ -15,7 +16,7 @@ jobs:
if: ${{ github.event.workflow_run.conclusion == 'success' }} if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps: steps:
- name: Download pr number - name: Download pr number
uses: dawidd6/action-download-artifact@e6e25ac3a2b93187502a8be1ef9e9603afc34925 uses: dawidd6/action-download-artifact@2536c51d3d126276eb39f74d6bc9c72ac6ef30d3 # v16
with: with:
workflow: ${{ github.event.workflow.id }} workflow: ${{ github.event.workflow.id }}
run_id: ${{ github.event.workflow_run.id }} run_id: ${{ github.event.workflow_run.id }}
@ -24,7 +25,7 @@ jobs:
id: pr id: pr
run: echo "id=$(<pr.txt)" >> $GITHUB_OUTPUT run: echo "id=$(<pr.txt)" >> $GITHUB_OUTPUT
- name: Download artifact - name: Download artifact
uses: dawidd6/action-download-artifact@e6e25ac3a2b93187502a8be1ef9e9603afc34925 uses: dawidd6/action-download-artifact@2536c51d3d126276eb39f74d6bc9c72ac6ef30d3 # v16
with: with:
workflow: ${{ github.event.workflow.id }} workflow: ${{ github.event.workflow.id }}
run_id: ${{ github.event.workflow_run.id }} run_id: ${{ github.event.workflow_run.id }}
@ -32,7 +33,7 @@ jobs:
path: dist path: dist
- name: Deploy to Netlify - name: Deploy to Netlify
id: netlify id: netlify
uses: nwtgck/actions-netlify@5da65c9f74c7961c5501a3ba329b8d0912f39c03 uses: nwtgck/actions-netlify@4cbaf4c08f1a7bfa537d6113472ef4424e4eb654 # v3.0.0
with: with:
publish-dir: dist publish-dir: dist
deploy-message: "Deploy PR ${{ steps.pr.outputs.id }}" deploy-message: "Deploy PR ${{ steps.pr.outputs.id }}"
@ -42,15 +43,15 @@ jobs:
enable-commit-comment: false enable-commit-comment: false
env: env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_PR_CINNY }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_PR_VOJO }}
timeout-minutes: 1 timeout-minutes: 1
- name: Comment preview on PR - name: Comment preview on PR
uses: thollander/actions-comment-pull-request@c22fb302208b7b170d252a61a505d2ea27245eff uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b #v3.0.1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
with: with:
pr_number: ${{ steps.pr.outputs.id }} pr-number: ${{ steps.pr.outputs.id }}
comment_tag: ${{ steps.pr.outputs.id }} comment-tag: ${{ steps.pr.outputs.id }}
message: | message: |
Preview: ${{ steps.netlify.outputs.deploy-url }} Preview: ${{ steps.netlify.outputs.deploy-url }}
⚠️ Exercise caution. Use test accounts. ⚠️ ⚠️ Exercise caution. Use test accounts. ⚠️

View file

@ -5,15 +5,58 @@ on:
paths: paths:
- 'Dockerfile' - 'Dockerfile'
- '.github/workflows/docker-pr.yml' - '.github/workflows/docker-pr.yml'
- '.github/workflows/prod-deploy.yml'
jobs: jobs:
docker-build: docker-build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.2.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build Docker image
uses: docker/build-push-action@v3.2.0 - name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Login to Docker Hub #Do not update this action from a outside PR
if: github.event.pull_request.head.repo.fork == false
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
continue-on-error: true
- name: Login to the Github Container registry #Do not update this action from a outside PR
if: github.event.pull_request.head.repo.fork == false
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true
- name: Extract metadata (tags, labels) for Docker, GHCR
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with:
images: |
ghcr.io/${{ github.repository }}
- name: Build Docker image (no push)
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with: with:
context: . context: .
platforms: linux/amd64
push: false push: false
load: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Show Docker images
run: docker images

View file

@ -14,9 +14,9 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3.2.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: NPM Lockfile Changes - name: NPM Lockfile Changes
uses: codepunkt/npm-lockfile-changes@b40543471c36394409466fdb277a73a0856d7891 uses: codepunkt/npm-lockfile-changes@b40543471c36394409466fdb277a73a0856d7891 # v1.0.0
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
# Optional inputs, can be deleted safely if you are happy with default values. # Optional inputs, can be deleted safely if you are happy with default values.

View file

@ -11,23 +11,23 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.2.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup node - name: Setup node
uses: actions/setup-node@v3.5.1 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with: with:
node-version: 18.12.1 node-version-file: ".node-version"
cache: "npm" package-manager-cache: false
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Build app - name: Build app
env: env:
NODE_OPTIONS: "--max_old_space_size=4096" NODE_OPTIONS: '--max_old_space_size=4096'
run: npm run build run: npm run build
- name: Deploy to Netlify - name: Deploy to Netlify
uses: nwtgck/actions-netlify@5da65c9f74c7961c5501a3ba329b8d0912f39c03 uses: nwtgck/actions-netlify@4cbaf4c08f1a7bfa537d6113472ef4424e4eb654 # v3.0.0
with: with:
publish-dir: dist publish-dir: dist
deploy-message: "Dev deploy ${{ github.sha }}" deploy-message: 'Dev deploy ${{ github.sha }}'
enable-commit-comment: false enable-commit-comment: false
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
production-deploy: true production-deploy: true

15
.github/workflows/pr-title.yml vendored Normal file
View file

@ -0,0 +1,15 @@
name: Check PR title
on:
pull_request_target:
types:
- opened
- edited
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -1,32 +1,40 @@
name: Production deploy name: Production deploy
on: on:
release: workflow_dispatch:
types: [published]
jobs: jobs:
deploy-and-tarball: deploy-and-tarball:
name: Netlify deploy and tarball name: Netlify deploy and tarball
outputs:
version: ${{ steps.vars.outputs.tag }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.2.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup node
uses: actions/setup-node@v3.5.1
with: with:
node-version: 18.12.1 fetch-depth: 0
cache: "npm" - name: Setup node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".node-version"
package-manager-cache: false
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Get version from tag
id: vars
run: |
TAG=$(git describe --tags --abbrev=0)
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Build app - name: Build app
env: env:
NODE_OPTIONS: "--max_old_space_size=4096" NODE_OPTIONS: '--max_old_space_size=4096'
run: npm run build run: npm run build
- name: Deploy to Netlify - name: Deploy to Netlify
uses: nwtgck/actions-netlify@5da65c9f74c7961c5501a3ba329b8d0912f39c03 uses: nwtgck/actions-netlify@4cbaf4c08f1a7bfa537d6113472ef4424e4eb654 # v3.0.0
with: with:
publish-dir: dist publish-dir: dist
deploy-message: "Prod deploy ${{ github.ref_name }}" deploy-message: 'Prod deploy ${{ steps.vars.outputs.tag }}'
enable-commit-comment: false enable-commit-comment: false
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
production-deploy: true production-deploy: true
@ -36,11 +44,8 @@ jobs:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_APP }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_APP }}
timeout-minutes: 1 timeout-minutes: 1
- name: Get version from tag
id: vars
run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
- name: Create tar.gz - name: Create tar.gz
run: tar -czvf cinny-${{ steps.vars.outputs.tag }}.tar.gz dist run: tar -czvf vojo-${{ steps.vars.outputs.tag }}.tar.gz dist
- name: Sign tar.gz - name: Sign tar.gz
run: | run: |
echo '${{ secrets.GNUPG_KEY }}' | gpg --batch --import echo '${{ secrets.GNUPG_KEY }}' | gpg --batch --import
@ -50,47 +55,56 @@ jobs:
# non-armored and hex-encode it so that its printable. # non-armored and hex-encode it so that its printable.
echo "PGP Signing key, in raw PGP format in hex. Import with cat ... | xxd -r -p - | gpg --import" echo "PGP Signing key, in raw PGP format in hex. Import with cat ... | xxd -r -p - | gpg --import"
gpg --export | xxd -p gpg --export | xxd -p
echo '${{ secrets.GNUPG_PASSPHRASE }}' | gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 --armor --detach-sign cinny-${{ steps.vars.outputs.tag }}.tar.gz echo '${{ secrets.GNUPG_PASSPHRASE }}' | gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 --armor --detach-sign vojo-${{ steps.vars.outputs.tag }}.tar.gz
- name: Upload tagged release - name: Upload tagged release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
with: with:
tag_name: ${{ steps.vars.outputs.tag }}
files: | files: |
cinny-${{ steps.vars.outputs.tag }}.tar.gz vojo-${{ steps.vars.outputs.tag }}.tar.gz
cinny-${{ steps.vars.outputs.tag }}.tar.gz.asc vojo-${{ steps.vars.outputs.tag }}.tar.gz.asc
publish-image: publish-image:
name: Push Docker image to Docker Hub, ghcr name: Push Docker image to Docker Hub, GHCR
needs: deploy-and-tarball
env:
VERSION: ${{ needs.deploy-and-tarball.outputs.version }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
packages: write packages: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.2.0 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0 uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.2.1 uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Login to Docker Hub - name: Login to Docker Hub #Do not update this action from a outside PR
uses: docker/login-action@v2.1.0 uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to the Container registry - name: Login to the Github Container registry #Do not update this action from a outside PR
uses: docker/login-action@v2.1.0 uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker, GHCR
id: meta id: meta
uses: docker/metadata-action@v4.1.1 uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with: with:
images: | images: |
${{ secrets.DOCKER_USERNAME }}/cinny ${{ secrets.DOCKER_USERNAME }}/vojo
ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}
tags: |
type=raw,value=${{ env.VERSION }}
type=raw,value=latest
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v3.2.0 uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64

21
.gitignore vendored
View file

@ -2,5 +2,26 @@ experiment
dist dist
node_modules node_modules
devAssets devAssets
config.local.json
electron/dist-electron
release
.DS_Store .DS_Store
.idea
.vscode/*
!.vscode/tasks.json
.codex
.claude
docs/plans
docs/design
docs/ai/*
!docs/ai/README.md
!docs/ai/android.md
!docs/ai/architecture.md
!docs/ai/electron.md
!docs/ai/i18n.md
!docs/ai/overview.md
!docs/ai/server-side.md
vite.config.*.timestamp-*.mjs

2
.husky/pre-commit Executable file
View file

@ -0,0 +1,2 @@
npx tsc -p tsconfig.json --noEmit
npx lint-staged

1
.node-version Normal file
View file

@ -0,0 +1 @@
24.13.1

1
.npmrc
View file

@ -1,3 +1,2 @@
legacy-peer-deps=true legacy-peer-deps=true
save-exact=true save-exact=true
@matrix-org:registry=https://gitlab.matrix.org/api/v4/projects/27/packages/npm/

View file

@ -4,3 +4,39 @@ package.json
package-lock.json package-lock.json
LICENSE LICENSE
README.md README.md
# Generated by Capacitor / Gradle / AGP — never format these.
android/app/build/
android/build/
android/capacitor-cordova-android-plugins/build/
android/app/src/main/assets/public/
android/app/src/main/assets/capacitor.config.json
android/app/src/main/assets/capacitor.plugins.json
android/app/google-services.json
# Internal docs — hand-formatted markdown. Prettier reflows tables and
# fenced code blocks (e.g. YAML inside fences in server-side.md, tables in
# architecture.md) in ways that change document structure, not whitespace.
# Most paths under docs/ are gitignored anyway via top-level .gitignore.
docs/
# Upstream Cinny GitHub Actions / templates — leave as-is, format drift here
# is unrelated to our work.
.github/
# Minified third-party assets.
*.min.js
# Top-level docs / HTML inherited from upstream Cinny — not part of this
# infra cleanup's scope. They have minor pre-existing format drift; touching
# them would just add review noise.
CLAUDE.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
index.html
# Upstream-vendored files copied verbatim from external projects (links in
# their headers). Keep byte-identical to upstream to make future re-syncs
# trivially diffable. Same intent as the per-file ESLint override.
src/util/cryptE2ERoomKeys.js
src/util/colorMXID.js

104
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,104 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Deploy to vojo.chat",
"type": "shell",
"command": "npm run build && rsync -avz --delete dist/ vojo-superuser@187.127.77.124:~/vojo/cinny/",
"group": "none",
"presentation": {
"reveal": "always",
"panel": "shared",
"showReuseMessage": false
},
"problemMatcher": []
},
{
"label": "Deploy widgets",
"type": "shell",
"command": "(cd apps/widget-telegram && npm run build && rsync -avz --delete dist/ vojo-superuser@187.127.77.124:~/vojo/widgets/telegram/) & PID1=$!; (cd apps/widget-discord && npm run build && rsync -avz --delete dist/ vojo-superuser@187.127.77.124:~/vojo/widgets/discord/) & PID2=$!; (cd apps/widget-whatsapp && npm run build && rsync -avz --delete dist/ vojo-superuser@187.127.77.124:~/vojo/widgets/whatsapp/) & PID3=$!; FAIL=0; wait $PID1 || FAIL=1; wait $PID2 || FAIL=1; wait $PID3 || FAIL=1; exit $FAIL",
"group": "none",
"presentation": {
"reveal": "always",
"panel": "shared",
"showReuseMessage": false
},
"problemMatcher": []
},
{
"label": "Build Android APK",
"type": "shell",
"command": "npm run build:android:debug",
"group": "none",
"presentation": {
"reveal": "always",
"panel": "shared",
"showReuseMessage": false
},
"problemMatcher": []
},
{
"label": "Deploy to Android (ADB)",
"type": "shell",
"command": "npm run build:android:debug && adb install -r android/app/build/outputs/apk/debug/app-debug.apk",
"group": "none",
"presentation": {
"reveal": "always",
"panel": "shared",
"showReuseMessage": false
},
"problemMatcher": []
},
{
"label": "Connect to Android device (ADB)",
"type": "shell",
"command": "adb connect 192.168.1.204:5555",
"group": "none",
"presentation": {
"reveal": "always",
"panel": "shared",
"showReuseMessage": false
},
"problemMatcher": []
},
{
"label": "Start Electron (dev)",
"type": "shell",
"command": "npm run electron:dev",
"group": "none",
"presentation": {
"reveal": "always",
"panel": "shared",
"showReuseMessage": false
},
"problemMatcher": []
},
{
"label": "Build Electron Windows",
"type": "shell",
"command": "npm run build:electron:win",
"group": "none",
"presentation": {
"reveal": "always",
"panel": "shared",
"showReuseMessage": false
},
"problemMatcher": []
},
{
"label": "Deploy Discord bridge",
"type": "shell",
"command": "docker build -t vojo-mautrix-discord:custom . && docker save vojo-mautrix-discord:custom | gzip | ssh vojo-superuser@187.127.77.124 'gunzip | docker load'",
"options": {
"cwd": "${workspaceFolder}/../vojo-mautrix-discord"
},
"group": "none",
"presentation": {
"reveal": "always",
"panel": "shared",
"showReuseMessage": false
},
"problemMatcher": []
}
]
}

9
CLAUDE.md Normal file
View file

@ -0,0 +1,9 @@
# Directive for AI agents
**All project context for Vojo lives in [`docs/ai/`](docs/ai/README.md). Read it before making any non-trivial change.**
**All plans that you create should be always created in docs/plans
This file exists only as a pointer. Do not add project knowledge here — put it in `docs/ai/`. Same rule for `.cursorrules`, `.windsurfrules`, `AGENTS.md`, `.codex`, home-directory memory, or any other agent-specific context file: if you're tempted to write project knowledge there, write it in `docs/ai/` instead and keep those files as thin pointers.
Start here: [docs/ai/README.md](docs/ai/README.md).

128
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
vojo@vojo.chat.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View file

@ -1,26 +1,21 @@
# Contributing to Cinny # Contributing to Vojo
First off, thanks for taking the time to contribute! ❤️ First off, thanks for taking the time to contribute!
All types of contributions are encouraged and valued. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 All types of contributions are encouraged and valued. Please make sure to read the relevant section before making your contribution.
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: > And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation:
> - Star the project > - Star the project
> - Tweet about it (tag @cinnyapp)
> - Refer this project in your project's readme > - Refer this project in your project's readme
> - Mention the project at local meetups and tell your friends/colleagues > - Mention the project at local meetups and tell your friends/colleagues
> - [Donate to us](https://cinny.in/#sponsor)
## Bug reports ## Bug reports
Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [GitHub Issues](https://github.com/ajbura/cinny/issues). Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected. Bug reports and feature suggestions must use descriptive and concise titles and be submitted to GitHub Issues. Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected.
## Pull requests ## Pull requests
> ### Legal Notice **NOTE: If you want to add new features, please discuss with maintainers before coding or opening a pull request.** This is to ensure that we are on the same track.
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
**NOTE: If you want to add new features, please discuss with maintainers before coding or opening a pull request.** This is to ensure that we are on same track and following our roadmap.
**Please use clean, concise titles for your pull requests.** We use commit squashing, so the final commit in the dev branch will carry the title of the pull request. For easier sorting in changelog, start your pull request titles using one of the verbs "Add", "Change", "Remove", or "Fix" (present tense). **Please use clean, concise titles for your pull requests.** We use commit squashing, so the final commit in the dev branch will carry the title of the pull request. For easier sorting in changelog, start your pull request titles using one of the verbs "Add", "Change", "Remove", or "Fix" (present tense).
@ -34,11 +29,7 @@ It is not always possible to phrase every change in such a manner, but it is des
**The smaller the set of changes in the pull request is, the quicker it can be reviewed and merged.** Splitting tasks into multiple smaller pull requests is often preferable. **The smaller the set of changes in the pull request is, the quicker it can be reviewed and merged.** Splitting tasks into multiple smaller pull requests is often preferable.
Also, we use [ESLint](https://eslint.org/) for clean and stylistically consistent code syntax, so make sure your pull request follow it. Also, we use [ESLint](https://eslint.org/) for clean and stylistically consistent code syntax, so make sure your pull request follows it.
**For any query or design discussion, join our [Matrix room](https://matrix.to/#/#cinny:matrix.org).**
## Helpful links ## Helpful links
- [BEM methodology](http://getbem.com/introduction/)
- [Atomic design](https://bradfrost.com/blog/post/atomic-web-design/)
- [Matrix JavaScript SDK documentation](https://matrix-org.github.io/matrix-js-sdk/index.html) - [Matrix JavaScript SDK documentation](https://matrix-org.github.io/matrix-js-sdk/index.html)

View file

@ -1,18 +1,20 @@
## Builder ## Builder
FROM node:18.12.1-alpine3.15 as builder FROM node:24.13.1-alpine AS builder
WORKDIR /src WORKDIR /src
COPY .npmrc package.json package-lock.json /src/ COPY .npmrc package.json package-lock.json /src/
RUN npm ci RUN npm ci
COPY . /src/ COPY . /src/
ENV NODE_OPTIONS=--max_old_space_size=4096
RUN npm run build RUN npm run build
## App ## App
FROM nginx:1.23.3-alpine FROM nginx:1.29.5-alpine
COPY --from=builder /src/dist /app COPY --from=builder /src/dist /app
COPY --from=builder /src/docker-nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html \ RUN rm -rf /usr/share/nginx/html \
&& ln -s /app /usr/share/nginx/html && ln -s /app /usr/share/nginx/html

View file

@ -1,92 +1,29 @@
# Cinny # Vojo
<p>
<a href="https://github.com/ajbura/cinny/releases">
<img alt="GitHub release downloads" src="https://img.shields.io/github/downloads/ajbura/cinny/total?logo=github&style=social"></a>
<a href="https://hub.docker.com/r/ajbura/cinny">
<img alt="DockerHub downloads" src="https://img.shields.io/docker/pulls/ajbura/cinny?logo=docker&style=social"></a>
<a href="https://fosstodon.org/@cinnyapp">
<img alt="Follow on Mastodon" src="https://img.shields.io/mastodon/follow/106845779685925461?domain=https%3A%2F%2Ffosstodon.org&logo=mastodon&style=social"></a>
<a href="https://twitter.com/intent/follow?screen_name=cinnyapp">
<img alt="Follow on Twitter" src="https://img.shields.io/twitter/follow/cinnyapp?logo=twitter&style=social"></a>
<a href="https://cinny.in/#sponsor">
<img alt="Sponsor Cinny" src="https://img.shields.io/opencollective/all/cinny?logo=opencollective&style=social"></a>
</p>
A Matrix client focusing primarily on simple, elegant and secure interface. The main goal is to have an instant messaging application that is easy on people and has a modern touch. A Matrix client focusing primarily on simple, elegant and secure interface. The main goal is to have an instant messaging application that is easy on people and has a modern touch.
- [Roadmap](https://github.com/ajbura/cinny/projects/11)
Based on [Cinny](https://github.com/cinnyapp/cinny) (MIT license).
- [Contributing](./CONTRIBUTING.md) - [Contributing](./CONTRIBUTING.md)
<img align="center" src="https://raw.githubusercontent.com/cinnyapp/cinny-site/main/assets/preview2-light.png" height="380">
## Getting started ## Getting started
Web app is available at https://app.cinny.in and gets updated on each new release. The `dev` branch is continuously deployed at https://dev.cinny.in but keep in mind that it could have things broken.
You can also download our desktop app from [cinny-desktop repository](https://github.com/cinnyapp/cinny-desktop). The web app is available at [vojo.chat](https://vojo.chat).
To host Cinny on your own, download tarball of the app from [GitHub release](https://github.com/cinnyapp/cinny/releases/latest). ## Self-hosting
You can serve the application with a webserver of your choice by simply copying `dist/` directory to the webroot.
To set default Homeserver on login and register page, place a customized [`config.json`](config.json) in webroot of your choice.
Alternatively you can just pull the [DockerHub image](https://hub.docker.com/r/ajbura/cinny) by: To host Vojo on your own, build from source and serve the files from `dist/` using your preferred webserver.
```
docker pull ajbura/cinny
```
or [ghcr image](https://github.com/cinnyapp/cinny/pkgs/container/cinny) by:
```
docker pull ghcr.io/cinnyapp/cinny:latest
```
<details> * The default homeserver is defined in [`config.json`](config.json).
<summary>PGP Public Key to verify tarball</summary>
``` * You need to set up redirects to serve the assets. Example configurations: [nginx](contrib/nginx/vojo.domain.tld.conf), [caddy](contrib/caddy/caddyfile).
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGNBGJw/g0BDAC8qQeLqDMzYzfPyOmRlHVEoguVTo+eo1aVdQH2X7OELdjjBlyj * To deploy on a subdirectory, rebuild the app after updating the `base` path in [`build.config.ts`](build.config.ts).
6d6c1adv/uF2g83NNMoQY7GEeHjRnXE4m8kYSaarb840pxrYUagDc0dAbJOGaCBY
FKTo7U1Kvg0vdiaRuus0pvc1NVdXSxRNQbFXBSwduD+zn66TI3HfcEHNN62FG1cE
K1jWDwLAU0P3kKmj8+CAc3h9ZklPu0k/+t5bf/LJkvdBJAUzGZpehbPL5f3u3BZ0
leZLIrR8uV7PiV5jKFahxlKR5KQHld8qQm+qVhYbUzpuMBGmh419I6UvTzxuRcvU
Frn9ttCEzV55Y+so4X2e4ZnB+5gOnNw+ecifGVdj/+UyWnqvqqDvLrEjjK890nLb
Pil4siecNMEpiwAN6WSmKpWaCwQAHEGDVeZCc/kT0iYfj5FBcsTVqWiO6eaxkUlm
jnulqWqRrlB8CJQQvih/g//uSEBdzIibo+ro+3Jpe120U/XVUH62i9HoRQEm6ADG
4zS5hIq4xyA8fL8AEQEAAbQdQ2lubnlBcHAgPGNpbm55YXBwQGdtYWlsLmNvbT6J
AdQEEwEIAD4WIQSRri2MHidaaZv+vvuUMwx6UK/M8wUCYnD+DQIbAwUJA8JnAAUL
CQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCUMwx6UK/M88ApC/9HAdbum1lYBC0s
1k7GwP2A7B4sQtBWjy771BzybWlHeaeG+BGJwg4YiuowXZMm5dubFJFoI/CfeY07
B5aK40/bmT6Xcfkp0VA74c1wUpubBUEJN7tH5HG/OGd9BKeq9E/HHtVaJLVT1k3w
Rhv9VuHO6nR30EEp7IDthftotl5S4lio3+W0pKk4TAKV8vjaCNp3y/lAHzoP1BU9
bUSao+7GXVeArKBjuqxN+t1uuiaxPH4L0oe2pMVjTig04zGJM5fTVoly859MEcC/
R7Taq9RWGfXFmgCXy8Dviz3eOD90vqpCzhX4+ypK0cp2X0UwhMH4dpKUzExmdbhl
eBO5GcHB4VxvloRBNf9/Lr7YOTgWejMUw+MlhZE2RE8unfW1LnM/cjL4dhXzO/XB
FUHHNq8d6d4e02rfWqw7mZo2/NVJgFRcvzw2rgx7w7CKtCNwF4lNjUetB2waZzDb
fAE0kwhK4Iuwvy12JOBzL0Yy9MxANtwUryr/LQz9AmdT4Rwnp0S5AY0EYnD+DQEM
ANOu/d6ZMF8bW+Df9RDCUQKytbaZfa+ZbIHBus7whCD/SQMOhPKntv3HX7SmMCs+
5i27kJMu4YN623JCS7hdCoXVO1R5kXCEcneW/rPBMDutaM472YvIWMIqK9Wwl5+0
Piu2N+uTkKhe9uS2u7eN+Khef3d7xfjGRxoppM+xI9dZO+jhYiy8LuC0oBohTjJq
QPqfGDpowBwRkkOsGz/XVcesJ1Pzg4bKivTS9kZjZSyT9RRSY8As0sVUN57AwYul
s1+eh00n/tVpi2Jj9pCm7S0csSXvXj8v2OTdK1jt4YjpzR0/rwh4+/xlOjDjZEqH
vMPhpzpbgnwkxZ3X8BFne9dJ3maC5zQ3LAeCP5m1W0hXzagYhfyjo74slJgD1O8c
LDf2Oxc5MyM8Y/UK497zfqSPfgT3NhQmhHzk83DjXw3I6Z3A3U+Jp61w0eBRI1nx
H1UIG+gldcAKUTcfwL0lghoT3nmi9JAbvek0Smhz00Bbo8/dx8vwQRxDUxlt7Exx
NwARAQABiQG8BBgBCAAmFiEEka4tjB4nWmmb/r77lDMMelCvzPMFAmJw/g0CGwwF
CQPCZwAACgkQlDMMelCvzPPT7Qv8CjXUEhphZFLwpBfaNOzRNfIXJST9aDit8zHW
IMmfSpORVfpU71IyIB3o/DtTUPwCeb8nvNJs7aj1QT1ZUSsqFa3yY2S16V/g8+WN
sHca6oDSc1J+A0eEpEL1HbG1b5OPBC0AeGvvMOoqrbqThBZVKg1Jc/0SD3cvKElv
aHeCZCNNmfcZ2Ib4HYhhc8//ZtC9TeI+5J/YesctY1M12EoWMxMrc27Y3P5Pa0BI
Uc3qxWggPq1vOFYsEshL0w99HyJvREJmQA7Fa0crV+rICxyrBxJeNnEvjH/0KCBU
LCkEonLY1QwrxyeeV3VpxGE3zHHE3azOdAjTIoAdzX5f/qhbgYlM68GL2f8xdDkp
O0igSGHWhO4F8BfmE7IOTx1Bi7daczp8nCFxh73cKpKB0RUsd9xxrqYpovjmEAlo
w7aHpdzt64NQcsrbK10OSVDF3gFa9Vz20/NQvdUrp8jGmAb/8+nYqI94Jsc28H36
UeGsouhyuITLwEhScounZDqop+Dx
=Zg+6
-----END PGP PUBLIC KEY BLOCK-----
```
</details>
## Local development ## Local development
> We recommend using a version manager as versions change very quickly. You will likely need to switch
between multiple Node.js versions based on the needs of different projects you're working on. [NVM on windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) on Windows and [nvm](https://github.com/nvm-sh/nvm) on Linux/macOS are pretty good choices. Also recommended nodejs version Hydrogen LTS (v18). > [!TIP]
> We recommend using a version manager as versions change very quickly. [NVM on Windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) or [nvm](https://github.com/nvm-sh/nvm) on Linux/macOS are good choices.
Execute the following commands to start a development server: Execute the following commands to start a development server:
```sh ```sh
@ -100,15 +37,15 @@ npm run build # Compiles the app into the dist/ directory
``` ```
### Running with Docker ### Running with Docker
This repository includes a Dockerfile, which builds the application from source and serves it with Nginx on port 80. To
use this locally, you can build the container like so: This repository includes a Dockerfile, which builds the application from source and serves it with Nginx on port 80. To use this locally, you can build the container like so:
``` ```
docker build -t cinny:latest . docker build -t vojo:latest .
``` ```
You can then run the container you've built with a command similar to this: You can then run the container you've built with a command similar to this:
``` ```
docker run -p 8080:80 cinny:latest docker run -p 8080:80 vojo:latest
``` ```
This will forward your `localhost` port 8080 to the container's port 80. You can visit the app in your browser by navigating to `http://localhost:8080`. This will forward your `localhost` port 8080 to the container's port 80. You can visit the app in your browser by navigating to `http://localhost:8080`.

View file

@ -1,3 +0,0 @@
# Redirects from what the browser requests to what we serve
/login /
/register /

101
android/.gitignore vendored Normal file
View file

@ -0,0 +1,101 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
# Cordova plugins for Capacitor
capacitor-cordova-android-plugins
# Copied web assets
app/src/main/assets/public
# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml

2
android/app/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/build/*
!/build/.npmkeep

148
android/app/build.gradle Normal file
View file

@ -0,0 +1,148 @@
apply plugin: 'com.android.application'
// Mirror of resolveAppVersion() in ../../vite.config.js so the APK's
// versionName matches __APP_VERSION__ rendered in the About screen.
// `git describe --tags --match 'v*'` against tag v0.2.0 yields
// `v0.2.0-<commits>-g<hash>`; patch = commit count since the tag.
// Falls back to package.json only when git is unavailable.
def gitDescribe = providers.exec {
it.commandLine 'git', 'describe', '--tags', '--match', 'v*', '--always'
it.workingDir rootDir.parentFile
it.ignoreExitValue = true
}
def appVersion = {
def fromGit = gitDescribe.result.get().exitValue == 0 ? gitDescribe.standardOutput.asText.get().trim() : null
def m = fromGit =~ /^v?(\d+)\.(\d+)\.(\d+)(?:-(\d+)-g[0-9a-f]+)?$/
if (fromGit && m.matches()) {
def major = m[0][1].toInteger()
def minor = m[0][2].toInteger()
def patch = (m[0][4] ?: m[0][3]).toInteger()
return [name: "${major}.${minor}.${patch}", major: major, minor: minor, patch: patch]
}
def pkg = new groovy.json.JsonSlurper().parseText(file('../../package.json').text)
def parts = pkg.version.split('\\.')
return [name: pkg.version, major: parts[0].toInteger(), minor: parts[1].toInteger(), patch: parts[2].toInteger()]
}()
def computedVersionCode = appVersion.major * 1000000 + appVersion.minor * 1000 + appVersion.patch
android {
namespace = "chat.vojo.app"
compileSdk = rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "chat.vojo.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode computedVersionCode
versionName appVersion.name
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
// AGP 8+ requires explicit opt-in for BuildConfig generation. We rely on
// BuildConfig.DEBUG to gate Log.d calls that dump privacy-sensitive
// identifiers (roomId, eventId) so release builds don't leak them through
// logcat / crash-reporter buffers. See dlog() in VojoFirebaseMessagingService.
buildFeatures {
buildConfig = true
}
signingConfigs {
release {
if (project.hasProperty('VOJO_RELEASE_STORE_FILE')) {
storeFile file(VOJO_RELEASE_STORE_FILE)
storePassword VOJO_RELEASE_STORE_PASSWORD
keyAlias VOJO_RELEASE_KEY_ALIAS
keyPassword VOJO_RELEASE_KEY_PASSWORD
}
}
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}
repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.activity:activity:$androidxActivityVersion"
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')
// Needed for VojoFirebaseMessagingService. @capacitor/push-notifications
// already depends on firebase-messaging but declares it `implementation`
// so classes aren't exposed at app-module compile time.
implementation "com.google.firebase:firebase-messaging:25.0.1"
// WorkManager hosts VojoPollWorker periodic /notifications poll that
// delivers messages and missed-call surfaces on networks where FCM
// (mtalk.google.com:5228) is blocked. Library self-registers its scheduler
// in the merged manifest; we declare no permission for it.
implementation "androidx.work:work-runtime:2.10.0"
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
}
apply from: 'capacitor.build.gradle'
abstract class GeneratePushStringsTask extends DefaultTask {
@InputFiles
abstract ConfigurableFileCollection getInputFiles()
@OutputDirectory
abstract DirectoryProperty getOutputDir()
@TaskAction
void generate() {
def nodeBin = project.findProperty('NODE_BIN') ?: 'node'
project.exec {
commandLine nodeBin,
new File(project.rootProject.projectDir.parentFile, 'scripts/gen-push-strings.mjs').absolutePath,
'--out', outputDir.get().asFile.absolutePath
}
}
}
androidComponents {
onVariants(selector().all()) { variant ->
def repoRoot = rootProject.projectDir.parentFile
def taskProvider = tasks.register(
"generatePushStrings${variant.name.capitalize()}",
GeneratePushStringsTask
) {
inputFiles.from(
new File(repoRoot, 'public/locales/en.json'),
new File(repoRoot, 'public/locales/ru.json'),
new File(repoRoot, 'scripts/gen-push-strings.mjs')
)
outputDir.set(layout.buildDirectory.dir("generated/res/push/${variant.name}"))
}
variant.sources.res.addGeneratedSourceDirectory(
taskProvider, GeneratePushStringsTask::getOutputDir
)
}
}
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}

View file

@ -0,0 +1,23 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
}
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-app')
implementation project(':capacitor-browser')
implementation project(':capacitor-preferences')
implementation project(':capacitor-push-notifications')
implementation project(':capacitor-toast')
}
if (hasProperty('postBuildExtras')) {
postBuildExtras()
}

View file

@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "51806967595",
"project_id": "chat-vojo-app",
"storage_bucket": "chat-vojo-app.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:51806967595:android:93921bf62aa9713a79576e",
"android_client_info": {
"package_name": "chat.vojo.app"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBroeOOHxg-tEyU-O-zjSWF7mEejedRWsM"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

45
android/app/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,45 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# Keep custom app classes entry points invoked by Android system (Intents,
# FCM, AndroidManifest references) or by JS bridge via reflection.
-keep class chat.vojo.app.MainActivity { *; }
-keep class chat.vojo.app.VojoFirebaseMessagingService { *; }
-keep class chat.vojo.app.CallForegroundPlugin { *; }
-keep class chat.vojo.app.CallForegroundService { *; }
-keep class chat.vojo.app.CallDeclineReceiver { *; }
-keep class chat.vojo.app.CallCancelReceiver { *; }
-keep class chat.vojo.app.FullScreenIntentPlugin { *; }
-keep class chat.vojo.app.LaunchSplashPlugin { *; }
# Firebase Messaging receivers/services resolved by Android via manifest.
-keep public class * extends com.google.firebase.messaging.FirebaseMessagingService
-keep class com.google.firebase.iid.** { *; }
-keep class com.google.firebase.messaging.** { *; }
# Capacitor plugins discovered by annotation/reflection.
-keep @com.getcapacitor.annotation.CapacitorPlugin class * { *; }
-keep class com.getcapacitor.** { *; }
-keep class com.getcapacitor.plugin.** { *; }
# AndroidX splashscreen reflection paths.
-keep class androidx.core.splashscreen.** { *; }

View file

@ -0,0 +1,26 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.getcapacitor.app", appContext.getPackageName());
}
}

View file

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- allowBackup=false: CallDeclineReceiver reads the Matrix access_token
from shared_prefs/CapacitorStorage.xml (written by sessionBridge).
With allowBackup=true a rooted device or adb-backup-enabled user
could exfiltrate the cleartext token. Session data is cheap to
recreate via re-login, so excluding ourselves from Auto Backup
is the simpler control vs. fine-grained backup_rules.xml exclusions. -->
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- App Links for https://vojo.chat/u/<user>. autoVerify=true lets
Chrome/Gmail/SMS hand the tap straight to this activity; the
server must publish a matching /.well-known/assetlinks.json
over HTTPS with the installed APK's signing SHA-256. Telegram
and other in-app browsers ignore verification and load the
URL in their own webview — the intent-URL redirect injected
in index.html covers that case. -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="vojo.chat"
android:pathPrefix="/u/" />
</intent-filter>
<!-- System share-sheet target. Three filters because Android's
sheet UI dedupes by activity but resolves by MIME match:
text/* gets its own filter so the Vojo icon shows up
alongside WhatsApp/Telegram for «share link/selection»; */*
covers single-file (image/video/audio/pdf/…) and
SEND_MULTIPLE picks up gallery multi-select.
Payload extraction lives in ShareTargetPlugin — MainActivity
only routes the Intent to the plugin via onNewIntent. -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
<!-- Replace Capacitor's default FCM service. VojoFirebaseMessagingService
extends MessagingService so super.onMessageReceived() still forwards
to the JS bridge; we add cold-start notification display on top. -->
<service
android:name="com.capacitorjs.plugins.pushnotifications.MessagingService"
tools:node="remove" />
<service
android:name=".VojoFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name=".CallForegroundService"
android:exported="false"
android:foregroundServiceType="microphone" />
<receiver
android:name=".CallCancelReceiver"
android:exported="false" />
<receiver
android:name=".CallDeclineReceiver"
android:exported="false" />
<receiver
android:name=".MarkAsReadReceiver"
android:exported="false" />
<receiver
android:name=".NotificationDismissReceiver"
android:exported="false" />
<receiver
android:name=".ReplyReceiver"
android:exported="false" />
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- DM voice calls: mic + audio routing. Capacitor auto-requests at getUserMedia time. -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- Required to unblock NotificationCompat.CallStyle on API 31+: NMS's
checkDisqualifyingFeatures rejects CallStyle notifications without
FSI/FGS/UIJ, throwing IllegalArgumentException on its own handler
thread (silent to the app). Declaring the permission flips
FLAG_FSI_REQUESTED_BUT_DENIED so the gate passes, even though we
never call setFullScreenIntent(). See ADR 2.5-heads-up. -->
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<!-- DM call lock-screen retention: CallForegroundService keeps the call
process foregrounded under lock so AppOps doesn't revoke RECORD_AUDIO
and netd doesn't block background network. -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
</manifest>

View file

@ -0,0 +1,65 @@
package chat.vojo.app;
import android.graphics.Bitmap;
import android.util.LruCache;
/**
* In-memory LRU cache of decoded avatar bitmaps keyed by MXC URL string.
*
* Sized as a process-singleton (~4 MB) so the FCM service, polling Worker
* and ReplyReceiver all share one pool. 96×96 ARGB_8888 bitmap is about
* 36 KB, so a 4 MB cache holds ~110 avatars enough for the active
* conversation set on a typical user. LruCache evicts the least-recently-
* read entry when full; this is the right shape for "rooms the user is
* actively talking in stay warm, dormant rooms reload on demand".
*
* Thread-safety: LruCache itself is synchronized internally on every
* get/put/remove. We don't need an outer lock for normal operation. The
* AvatarLoader funnels all puts through this class.
*
* Process death: cache is in-memory only. After a kill, the first push
* to any room cold-renders without avatars and re-renders once the
* loader populates the cache (see AvatarLoader.loadAllWithTimeout).
*/
final class AvatarBitmapCache {
// Heap budget: bytes. 4 MB is generous against ARGB_8888 96×96 bitmaps
// (~36 KB each) and stays comfortably under the 1/8-of-heap Android
// recommendation on every device we ship to (minSdk 24 at least
// 96 MB heap on a low-end phone).
private static final int MAX_SIZE_BYTES = 4 * 1024 * 1024;
private static final LruCache<String, Bitmap> CACHE =
new LruCache<String, Bitmap>(MAX_SIZE_BYTES) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
private AvatarBitmapCache() {}
/**
* Returns the cached bitmap for an MXC URL, or null on miss.
*
* Bitmap references are NOT defensively copied the cache hands out
* the same reference to every caller. This is safe because no code
* path in the app calls Bitmap.recycle() on a cached bitmap (the
* intermediate square / source bitmaps inside AvatarLoader.
* toCircularBitmap ARE recycled, but the circular output that lands
* here is held until LRU evicts it). LRU eviction simply drops the
* cache's reference, and the GC reclaims memory only after every
* Notification that referenced the bitmap is also released by the
* system. Adding a defensive copy here would halve the effective
* cache size for no real-world benefit.
*/
static Bitmap get(String mxc) {
if (mxc == null || mxc.isEmpty()) return null;
return CACHE.get(mxc);
}
static void put(String mxc, Bitmap bitmap) {
if (mxc == null || mxc.isEmpty() || bitmap == null) return;
CACHE.put(mxc, bitmap);
}
}

View file

@ -0,0 +1,368 @@
package chat.vojo.app;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Fetches and decodes avatar bitmaps from MXC URLs, populating
* {@link AvatarBitmapCache}.
*
* URL resolution mirrors matrix-js-sdk's auth-media v1.11+ pattern:
* mxc://server/mediaId
* <homeserver>/_matrix/client/v1/media/thumbnail/<server>/<mediaId>
* ?width=96&height=96&method=crop
* + Authorization: Bearer <accessToken>
*
* The legacy unauthenticated `/_matrix/media/v3/thumbnail/...` endpoint is
* NOT used every Synapse the Vojo audience runs against (vanilla, v1.11+
* by deployment policy, see docs/ai/server-side.md) speaks auth media.
* Removing the legacy fallback keeps the loader off the deprecated path
* and avoids leaking the access token to a server route that doesn't
* require it.
*
* Concurrency: each MXC URL is fetched at most once concurrently the
* `inFlight` set short-circuits duplicate requests from rapid
* append-rebuild cycles on the same conversation. Loads happen on a
* shared 4-thread pool; bigger than 1 so 5 senders in a group chat can
* load in parallel, capped to keep socket pressure under the typical
* mobile network budget.
*
* Two entry points:
* - {@link #loadAllWithTimeout}: synchronous wait, used by the render
* path to populate the cache before building the MessagingStyle so the
* first post already has avatars. Timeout-bounded to keep FCM thread
* responsive (Android budgets ~10s; we use 800 ms).
* - {@link #prefetch}: fire-and-forget, used for warm-up scenarios.
* Not currently called but kept for the room-metadata bridge to
* eventually warm the cache on visibility resume.
*/
final class AvatarLoader {
private static final String TAG = "AvatarLoader";
private static final int AVATAR_SIZE_PX = 96;
private static final int CONNECT_TIMEOUT_MS = 5_000;
private static final int READ_TIMEOUT_MS = 5_000;
private static final int RENDER_BLOCK_TIMEOUT_MS = 800;
// Cap decoded bitmap byte count a malicious / huge avatar shouldn't
// OOM the FCM service. 96×96 ARGB_8888 is ~36 KB; we accept up to
// 4× that (~140 KB) to allow some downscaling slack on servers that
// return slightly oversized thumbnails.
private static final int MAX_DECODED_BYTES = 144 * 1024;
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(4);
// MXC URL CountDownLatch that fires when the in-flight download
// completes (success or failure). A second caller observing an
// already-pending mxc waits on the SAME latch instead of either
// returning empty-handed or kicking off a duplicate fetch. Latches
// are removed by the worker task in its finally block; the same task
// that put the entry is the only one allowed to remove it, so a slow
// remove() race is harmless.
private static final ConcurrentHashMap<String, CountDownLatch> inFlight =
new ConcurrentHashMap<>();
private AvatarLoader() {}
/**
* Block the caller for up to {@link #RENDER_BLOCK_TIMEOUT_MS} while
* fetching any of the given MXC URLs that are not yet in
* {@link AvatarBitmapCache}. Cache hits are no-ops. Already-in-flight
* URLs are awaited via the shared latch duplicate concurrent
* fetches do not happen.
*
* Designed to be called inline from the render path: after this
* returns, {@link AvatarBitmapCache#get} will be non-null for every
* MXC that loaded successfully within the budget. Failures are
* silent the render then falls back to a Person without icon
* (Android renders initials/blank).
*
* Returns the count of avatars that landed in the cache during this
* call (purely informational useful for logs).
*/
static int loadAllWithTimeout(Context ctx, Collection<String> mxcs) {
if (mxcs == null || mxcs.isEmpty()) {
Log.i(TAG, "loadAll: empty input, skip");
return 0;
}
SharedPreferences prefs = ctx.getSharedPreferences(
VojoPollWorker.PREFS, Context.MODE_PRIVATE);
String token = prefs.getString(VojoPollWorker.KEY_ACCESS_TOKEN, null);
String homeserver = prefs.getString(VojoPollWorker.KEY_HOMESERVER_URL, null);
if (token == null || token.isEmpty() || homeserver == null || homeserver.isEmpty()) {
// No credentials yet (fresh install + first push). We can't
// resolve MXC URLs without an access token. Falling back to
// no-icon Person renderer is the correct behaviour here.
Log.i(TAG, "loadAll: no credentials in prefs, skip"
+ " hasToken=" + (token != null && !token.isEmpty())
+ " hasHs=" + (homeserver != null && !homeserver.isEmpty()));
return 0;
}
// De-duplicate and filter to misses only; if the cache already has
// an entry, no work is needed.
Set<String> toLoad = new LinkedHashSet<>();
for (String mxc : mxcs) {
if (mxc == null || mxc.isEmpty()) continue;
if (!mxc.startsWith("mxc://")) continue;
if (AvatarBitmapCache.get(mxc) != null) continue;
toLoad.add(mxc);
}
if (toLoad.isEmpty()) return 0;
// Per-mxc latches shared across concurrent callers a second
// caller arriving while we're already mid-fetch waits on the
// SAME latch instead of forcing a duplicate HTTP or returning
// immediately empty-handed (which was the previous bug see
// git blame for the race description).
java.util.List<CountDownLatch> waits = new java.util.ArrayList<>(toLoad.size());
for (String mxc : toLoad) {
CountDownLatch myLatch = new CountDownLatch(1);
CountDownLatch existing = inFlight.putIfAbsent(mxc, myLatch);
if (existing != null) {
// Already in flight share the original latch.
waits.add(existing);
continue;
}
// We own this fetch; kick off the worker that will fire
// myLatch when done.
waits.add(myLatch);
final String capturedMxc = mxc;
final String capturedHomeserver = homeserver;
final String capturedToken = token;
EXECUTOR.execute(() -> {
try {
Bitmap bmp = fetchAndDecode(capturedMxc, capturedHomeserver, capturedToken);
if (bmp != null) AvatarBitmapCache.put(capturedMxc, bmp);
} catch (Throwable t) {
Log.w(TAG, "fetch threw mxc=" + capturedMxc, t);
} finally {
// Remove BEFORE countDown so a freshly-arriving caller
// doesn't observe a stale latch for an already-loaded
// mxc (would block until the next call with no fetch
// actually pending). Cache.get() on the post-await
// side covers the race where remove+put-cache happens
// between two latch waits.
inFlight.remove(capturedMxc);
myLatch.countDown();
}
});
}
// Single budget for the whole batch wait for all latches OR
// hit the timeout. Latches that fire early just return await()
// immediately; the slowest one consumes the remainder of the
// budget.
long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(RENDER_BLOCK_TIMEOUT_MS);
try {
for (CountDownLatch latch : waits) {
long remaining = deadline - System.nanoTime();
if (remaining <= 0) break;
latch.await(remaining, TimeUnit.NANOSECONDS);
}
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
// Count how many actually landed in the cache during this call
// includes both items we fetched and items that finished after our
// timeout (which won't be reflected in this count but are still
// usable on the next render).
int hits = 0;
for (String mxc : toLoad) {
if (AvatarBitmapCache.get(mxc) != null) hits += 1;
}
Log.i(TAG, "loadAll: requested=" + mxcs.size()
+ " toLoad=" + toLoad.size() + " hits=" + hits);
return hits;
}
/**
* Resolve an `mxc://server/mediaId` URL to a 96×96 thumbnail via the
* authenticated v1.11+ media endpoint and decode the response into a
* Bitmap. Returns null on any non-2xx, decode failure, or oversized
* payload (see {@link #MAX_DECODED_BYTES}).
*/
private static Bitmap fetchAndDecode(String mxc, String homeserver, String token)
throws IOException {
Parsed parsed = parseMxc(mxc);
if (parsed == null) {
Log.w(TAG, "fetch: malformed mxc=" + mxc);
return null;
}
// Server + mediaId are NOT URL-encoded matches matrix-js-sdk's
// content-repo.ts (it concatenates verbatim via `new URL()`).
// URLEncoder would turn `example.com:8448` into `example.com%3A8448`,
// which Synapse's media router rejects as an unknown server.
// mediaId is base64-ish per spec (URL-safe alphabet) so no
// encoding is needed there either.
StringBuilder url = new StringBuilder(homeserver);
if (!homeserver.endsWith("/")) url.append('/');
url.append("_matrix/client/v1/media/thumbnail/")
.append(parsed.server)
.append('/')
.append(parsed.mediaId)
.append("?width=").append(AVATAR_SIZE_PX)
.append("&height=").append(AVATAR_SIZE_PX)
.append("&method=crop");
HttpURLConnection conn = (HttpURLConnection) new URL(url.toString()).openConnection();
try {
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Bearer " + token);
conn.setRequestProperty("Accept", "image/*");
conn.setConnectTimeout(CONNECT_TIMEOUT_MS);
conn.setReadTimeout(READ_TIMEOUT_MS);
int code = conn.getResponseCode();
Log.i(TAG, "fetch: mxc=" + mxc + " status=" + code);
if (code < 200 || code >= 300) return null;
int contentLength = conn.getContentLength();
if (contentLength > MAX_DECODED_BYTES) {
Log.w(TAG, "fetch: oversized contentLength=" + contentLength + " mxc=" + mxc);
return null;
}
try (InputStream in = conn.getInputStream()) {
BitmapFactory.Options opts = new BitmapFactory.Options();
// Stick with ARGB_8888 even on low-mem devices RGB_565
// would lose alpha (group avatars often have a
// transparent corner) and the cache cap (4 MB) already
// bounds total memory. inJustDecodeBounds + sample-size
// dance is overkill at 96×96.
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bmp = BitmapFactory.decodeStream(in, null, opts);
if (bmp == null) {
Log.w(TAG, "fetch: decodeStream returned null mxc=" + mxc);
return null;
}
if (bmp.getByteCount() > MAX_DECODED_BYTES) {
Log.w(TAG, "fetch: decoded oversized "
+ bmp.getByteCount() + " bytes mxc=" + mxc);
bmp.recycle();
return null;
}
// Crop into a circle BEFORE caching IconCompat.createWithBitmap
// renders the bitmap verbatim, with no shape mask, so a
// square thumbnail from the homeserver lands as a square
// tile in the shade (visible on Android 12+ where
// conversation Person icons used to be auto-rounded by the
// OS this changed). Pre-cropping guarantees a round
// visual on every API level instead of relying on the
// SystemUI of the day. The original square bitmap is
// recycled once the circular copy is in hand.
return toCircularBitmap(bmp);
}
} finally {
conn.disconnect();
}
}
/**
* Re-encode a circular avatar as an adaptive-icon-shaped bitmap:
* embeds the avatar inside a transparent canvas whose total size is
* 1.5× the avatar so Android's adaptive-icon safe zone (66% of total)
* covers the entire avatar without clipping.
*
* Required for conversation-shortcut icons per docs at
* developer.android.com/develop/ui/views/notifications/conversations:
* *"To avoid unintentional clipping of your shortcut avatar, provide
* an AdaptiveIconDrawable for the shortcut's icon."*
*
* Without this padding, IconCompat.createWithAdaptiveBitmap would
* crop ~17% off every edge of the avatar to fit the safe zone a
* visible mutilation. With it, the shortcut icon renders pixel-
* identical to the circular avatar inside the system shade's
* conversation slot.
*/
static Bitmap toAdaptivePaddedBitmap(Bitmap circularAvatar) {
int avatarSize = Math.min(circularAvatar.getWidth(), circularAvatar.getHeight());
// Pad to 150% so the adaptive safe-zone (66% of canvas = avatarSize)
// covers the full avatar. Rounded up to keep the canvas even.
int canvasSize = (int) Math.ceil(avatarSize / 0.66f);
if (canvasSize % 2 != 0) canvasSize += 1;
Bitmap output = Bitmap.createBitmap(canvasSize, canvasSize, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
int offset = (canvasSize - avatarSize) / 2;
canvas.drawBitmap(circularAvatar, offset, offset, null);
return output;
}
/**
* Return a circular ARGB_8888 bitmap of the source centre-cropped to
* a square if non-square, then masked with a circular path so the
* corners are transparent. The source bitmap is recycled.
*
* Anti-aliased edges via Paint.setAntiAlias on the circle draw the
* BitmapShader copies the source's pixels into the circular region in
* a single drawCircle call, which keeps allocation to one output
* bitmap (vs the naive "decode → square crop → mask compose" path
* that touches three intermediate bitmaps).
*/
private static Bitmap toCircularBitmap(Bitmap source) {
int size = Math.min(source.getWidth(), source.getHeight());
Bitmap squareSource;
if (source.getWidth() == size && source.getHeight() == size) {
squareSource = source;
} else {
int x = (source.getWidth() - size) / 2;
int y = (source.getHeight() - size) / 2;
squareSource = Bitmap.createBitmap(source, x, y, size, size);
source.recycle();
}
Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(new BitmapShader(
squareSource, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
float radius = size / 2f;
canvas.drawCircle(radius, radius, radius, paint);
if (squareSource != source) {
squareSource.recycle();
}
return output;
}
private static final class Parsed {
final String server;
final String mediaId;
Parsed(String server, String mediaId) {
this.server = server;
this.mediaId = mediaId;
}
}
/**
* Split an `mxc://server/mediaId` URL into its two components. Returns
* null on any malformed input caller drops the avatar silently.
*/
private static Parsed parseMxc(String mxc) {
if (mxc == null) return null;
final String prefix = "mxc://";
if (!mxc.startsWith(prefix)) return null;
int slash = mxc.indexOf('/', prefix.length());
if (slash < 0 || slash == prefix.length()) return null;
String server = mxc.substring(prefix.length(), slash);
String mediaId = mxc.substring(slash + 1);
if (server.isEmpty() || mediaId.isEmpty()) return null;
return new Parsed(server, mediaId);
}
}

View file

@ -0,0 +1,44 @@
package chat.vojo.app;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* Dismisses the incoming-call notification when its RTC lifetime expires.
*
* Scheduled by {@link VojoFirebaseMessagingService} via AlarmManager at
* sender_ts + lifetime. The extras carry tag/id so we can target the exact
* notification even if multiple ring pushes overlap (rare but possible across
* rapid re-dials).
*
* Also invoked directly (same intent shape) when the app receives a decline /
* other-device-answer via the live Matrix sync and wants the system notification
* cleared see Capacitor removeDeliveredNotifications in the JS layer.
*/
public class CallCancelReceiver extends BroadcastReceiver {
public static final String ACTION_CANCEL_CALL = "chat.vojo.app.CANCEL_CALL";
public static final String EXTRA_NOTIF_TAG = "notif_tag";
public static final String EXTRA_NOTIF_ID = "notif_id";
// Carried so the receiver can tombstone and drop the matching registry
// entry otherwise a same-eventId re-delivery after expiry would seed the
// registry again and render a stale ring on next backgrounding.
public static final String EXTRA_NOTIF_EVENT_ID = "notif_event_id";
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || !ACTION_CANCEL_CALL.equals(intent.getAction())) return;
String tag = intent.getStringExtra(EXTRA_NOTIF_TAG);
int id = intent.getIntExtra(EXTRA_NOTIF_ID, -1);
String notifEventId = intent.getStringExtra(EXTRA_NOTIF_EVENT_ID);
if (tag == null || id == -1) return;
NotificationManager nm =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (nm != null) nm.cancel(tag, id);
if (notifEventId != null) {
VojoFirebaseMessagingService.removeIncomingRing(context, notifEventId);
}
}
}

View file

@ -0,0 +1,219 @@
package chat.vojo.app;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.core.app.NotificationManagerCompat;
import org.json.JSONObject;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Sends {@code m.call.decline} directly from the notification action tap,
* bypassing the WebView boot entirely. Fires via {@code PendingIntent.getBroadcast}
* from the CallStyle Decline button MainActivity never starts, so the
* lockscreen-unlock-and-app-flash UX from Phase 2.5.3 is gone.
*
* Session data ({@code accessToken}, {@code baseUrl}, {@code userId}) is
* mirrored from JS into {@code shared_prefs/CapacitorStorage.xml} by
* {@code writeSessionBridge()} on client mount.
*
* Recovery contract:
* - Before HTTP: write {@code vojo.pendingDeclines.{notifEventId}} tombstone
* so {@code usePendingDeclinesFlusher} can retry on app resume if our
* HTTP fails (network drop, token invalidated, process killed mid-PUT).
* - On 2xx: remove the tombstone receiver path succeeded, no flusher work.
* - On non-2xx or exception: leave tombstone; flusher drains on next resume.
*
* Null-session edge case (fresh reinstall + first push before first login):
* we cannot send the decline at all there's no access token, and the
* JS-path can't cover us either (a logged-out client has no Matrix session
* to call sendRtcDecline against). Cancelling the notification is the only
* feedback we can give; leaving the ring would trap the user on a call they
* can't accept or decline until the A-side times out.
*
* Note on idempotency: the flusher's retry generates a new txnId, so on a
* split-success-fail sequence (receiver HTTP timed out, flusher succeeds)
* we may land two {@code m.call.decline} events in the timeline with the
* same {@code rel.event_id}. This is cosmetic the caller's auto-hangup
* hook is idempotent and fires on the first decline.
*/
public class CallDeclineReceiver extends BroadcastReceiver {
public static final String ACTION_DECLINE_CALL = "chat.vojo.app.DECLINE_CALL";
public static final String EXTRA_ROOM_ID = "room_id";
public static final String EXTRA_NOTIF_EVENT_ID = "notif_event_id";
public static final String EXTRA_NOTIF_TAG = "notif_tag";
public static final String EXTRA_NOTIF_ID = "notif_id";
private static final String PREFS_FILE = "CapacitorStorage";
private static final String SESSION_KEY = "vojo.matrixSession";
private static final String PENDING_DECLINES_PREFIX = "vojo.pendingDeclines.";
private static final int CONNECT_TIMEOUT_MS = 8_000;
private static final int READ_TIMEOUT_MS = 8_000;
private static final String TAG = "CallDeclineReceiver";
// Single reusable executor keeps us off the main thread without spawning
// a fresh one per broadcast declines come rarely enough that a pool of 1
// is fine; a short-lived Thread would also work but is noisier on tracing.
private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) return;
final String roomId = intent.getStringExtra(EXTRA_ROOM_ID);
final String notifEventId = intent.getStringExtra(EXTRA_NOTIF_EVENT_ID);
final String notifTag = intent.getStringExtra(EXTRA_NOTIF_TAG);
final int notifId = intent.getIntExtra(EXTRA_NOTIF_ID, -1);
if (roomId == null || notifEventId == null) {
Log.w(TAG, "onReceive: missing extras, abort");
return;
}
final Context appContext = context.getApplicationContext();
final SharedPreferences prefs =
appContext.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
final String sessionJson = prefs.getString(SESSION_KEY, null);
if (sessionJson == null) {
// Fresh reinstall / logged-out: no access token, no Matrix client.
// We can't send m.call.decline and neither can the JS-path (no
// session to decline from). Cancel the notif so the user isn't
// stuck on a ring they can't action. Skip the tombstone a
// retry without a session would be equally impotent.
Log.w(TAG, "onReceive: no session in prefs, cancelling notif without HTTP");
if (notifTag != null && notifId != -1) {
NotificationManagerCompat.from(appContext).cancel(notifTag, notifId);
}
VojoFirebaseMessagingService.removeIncomingRing(appContext, notifEventId);
return;
}
final String accessToken;
final String baseUrl;
try {
JSONObject session = new JSONObject(sessionJson);
accessToken = session.optString("accessToken", null);
baseUrl = session.optString("baseUrl", null);
} catch (Throwable t) {
// Do NOT pass the Throwable JSONException.getMessage() embeds
// the malformed input, which here contains the access token.
Log.e(TAG, "onReceive: prefs JSON parse failed: " + t.getClass().getSimpleName());
// Still drop the native and the registry entry user tapped Decline,
// the ring should not re-surface on next backgrounding just because
// we can't send the decline over HTTP.
if (notifTag != null && notifId != -1) {
NotificationManagerCompat.from(appContext).cancel(notifTag, notifId);
}
VojoFirebaseMessagingService.removeIncomingRing(appContext, notifEventId);
return;
}
if (accessToken == null || accessToken.isEmpty() || baseUrl == null || baseUrl.isEmpty()) {
Log.w(TAG, "onReceive: empty accessToken/baseUrl in session, cancelling ring locally");
if (notifTag != null && notifId != -1) {
NotificationManagerCompat.from(appContext).cancel(notifTag, notifId);
}
VojoFirebaseMessagingService.removeIncomingRing(appContext, notifEventId);
return;
}
// 1. Cancel first for instant user feedback. Ringtone stops within
// NotificationManagerCompat's Binder latency (~tens of ms) HTTP
// completion is irrelevant to the perceived UX.
if (notifTag != null && notifId != -1) {
NotificationManagerCompat.from(appContext).cancel(notifTag, notifId);
}
// Drop the registry entry and tombstone the eventId so the next
// onPause renderRegistry can't resurrect the declined ring.
VojoFirebaseMessagingService.removeIncomingRing(appContext, notifEventId);
// 2. Write tombstone BEFORE HTTP so the flusher sees work to do
// if we fail/die. Remove only on confirmed 2xx.
try {
JSONObject tombstone = new JSONObject();
tombstone.put("roomId", roomId);
tombstone.put("ts", System.currentTimeMillis());
prefs.edit()
.putString(PENDING_DECLINES_PREFIX + notifEventId, tombstone.toString())
.apply();
} catch (Throwable t) {
Log.w(TAG, "onReceive: tombstone write failed (non-fatal)", t);
}
// 3. HTTP PUT off-main on goAsync. Receiver stays alive ~10s for us
// to finish; after that Android reclaims the process and the
// pending request dies flusher covers recovery on next resume.
final PendingResult pendingResult = goAsync();
final String txnId = UUID.randomUUID().toString();
EXECUTOR.execute(() -> {
try {
int status = sendDecline(baseUrl, accessToken, roomId, notifEventId, txnId);
if (status >= 200 && status < 300) {
prefs.edit().remove(PENDING_DECLINES_PREFIX + notifEventId).apply();
Log.d(TAG, "decline PUT ok status=" + status + " room=" + roomId);
} else {
Log.w(TAG, "decline PUT non-2xx status=" + status + " room=" + roomId);
}
} catch (Throwable t) {
Log.e(TAG, "decline PUT threw", t);
} finally {
pendingResult.finish();
}
});
}
private int sendDecline(
String baseUrl,
String accessToken,
String roomId,
String notifEventId,
String txnId
) throws Exception {
String url = trimTrailingSlash(baseUrl)
+ "/_matrix/client/v3/rooms/"
+ URLEncoder.encode(roomId, "UTF-8")
+ "/send/org.matrix.msc4310.rtc.decline/"
+ URLEncoder.encode(txnId, "UTF-8");
JSONObject relates = new JSONObject();
relates.put("rel_type", "m.reference");
relates.put("event_id", notifEventId);
JSONObject body = new JSONObject();
body.put("m.relates_to", relates);
byte[] payload = body.toString().getBytes("UTF-8");
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("PUT");
conn.setConnectTimeout(CONNECT_TIMEOUT_MS);
conn.setReadTimeout(READ_TIMEOUT_MS);
conn.setDoOutput(true);
conn.setRequestProperty("Authorization", "Bearer " + accessToken);
conn.setRequestProperty("Content-Type", "application/json");
conn.setFixedLengthStreamingMode(payload.length);
try (OutputStream os = conn.getOutputStream()) {
os.write(payload);
}
return conn.getResponseCode();
} finally {
if (conn != null) conn.disconnect();
}
}
private static String trimTrailingSlash(String s) {
return (s != null && s.endsWith("/")) ? s.substring(0, s.length() - 1) : s;
}
}

View file

@ -0,0 +1,148 @@
package chat.vojo.app;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import androidx.core.content.ContextCompat;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import java.util.HashMap;
import java.util.Map;
/**
* JS Android bridge for CallForegroundService lifecycle.
*
* start / stop map onto startForegroundService / stopService.
*
* RECORD_AUDIO permission is re-verified here before dispatch: the JS caller
* (useAndroidCallForegroundSync) gates on the widget's JoinCall signal, which
* implies getUserMedia has run and the grant is in place but the plugin
* checks defensively so the service never attempts startForeground with
* TYPE_MICROPHONE without the permission. The manifest declares the service
* as foregroundServiceType="microphone" only (no fallback type), so on API
* 34+ starting without RECORD_AUDIO would throw ForegroundServiceTypeException.
*/
@CapacitorPlugin(name = "CallForegroundService")
public class CallForegroundPlugin extends Plugin {
private static final String TAG = "CallFgsPlugin";
@PluginMethod
public void start(PluginCall call) {
String title = call.getString("title");
String body = call.getString("body");
Context ctx = getContext();
// Defense-in-depth: starting the microphone-typed FGS without
// RECORD_AUDIO granted is invalid on API 34+. JS side already gates
// on JoinCall (see useAndroidCallForegroundSync) so this should never
// fire in practice. If it does, resolve cleanly without starting
// the call will run without retention, which is the same fate as
// the first-ever-call window before getUserMedia prompt answered.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
int micPerm = ContextCompat.checkSelfPermission(ctx, Manifest.permission.RECORD_AUDIO);
if (micPerm != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "start: RECORD_AUDIO not granted, skipping FGS (would fail on TYPE_MICROPHONE)");
call.resolve();
return;
}
}
Intent intent = new Intent(ctx, CallForegroundService.class);
if (title != null) intent.putExtra(CallForegroundService.EXTRA_TITLE, title);
if (body != null) intent.putExtra(CallForegroundService.EXTRA_BODY, body);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ctx.startForegroundService(intent);
} else {
ctx.startService(intent);
}
Log.d(TAG, "start: service started");
call.resolve();
} catch (Throwable t) {
Log.e(TAG, "start: failed to start service", t);
call.reject("start_failed: " + t.getClass().getSimpleName() + ": " + t.getMessage());
}
}
@PluginMethod
public void stop(PluginCall call) {
Context ctx = getContext();
try {
ctx.stopService(new Intent(ctx, CallForegroundService.class));
Log.d(TAG, "stop: stopService dispatched");
call.resolve();
} catch (Throwable t) {
Log.w(TAG, "stop: stopService threw", t);
call.reject("stop_failed: " + t.getClass().getSimpleName() + ": " + t.getMessage());
}
}
// JS upserts a live incoming ring into the native registry (atom ADD
// happy-path). Idempotent with any prior FCM seed for the same eventId
// Java merges metadata fields append-only. See VojoFirebaseMessagingService
// for the registry operations contract.
@PluginMethod
public void upsertIncomingRing(PluginCall call) {
String eventId = call.getString("eventId");
String roomId = call.getString("roomId");
if (eventId == null || eventId.isEmpty() || roomId == null || roomId.isEmpty()) {
call.reject("missing_eventId_or_roomId");
return;
}
Map<String, String> data = new HashMap<>();
data.put("event_id", eventId);
data.put("room_id", roomId);
String callerName = call.getString("callerName");
if (callerName != null && !callerName.isEmpty()) {
data.put("sender_display_name", callerName);
}
// Pass through senderTs/lifetime as strings registry stores the same
// Map<String,String> shape that FCM delivers, and downstream consumers
// (scheduleCallNotificationExpiry, isExpired) parseLong them.
Long senderTs = call.getLong("senderTs");
if (senderTs != null && senderTs > 0) {
data.put("content_sender_ts", Long.toString(senderTs));
}
Long lifetime = call.getLong("lifetime");
if (lifetime != null && lifetime > 0) {
data.put("content_lifetime", Long.toString(lifetime));
}
String messageId = call.getString("messageId");
// messageId is used as google.message_id in the Answer/Launch PendingIntent
// extras Capacitor PushNotificationsPlugin gates pushNotificationActionPerformed
// on containsKey. Empty string also satisfies the gate; we pass the
// caller's value through verbatim.
boolean seeded = VojoFirebaseMessagingService.upsertIncomingRing(data, messageId);
// Mark in NotificationDedup so a polling fire 15 minutes later
// doesn't post a "Missed call" notification for a ring the user
// already saw live via the in-app strip. Mirrors the FCM-arrival
// path in VojoFirebaseMessagingService.onMessageReceived.
if (seeded) {
NotificationDedup.markNotified(getContext(), eventId);
}
call.resolve();
}
// JS removes a ring from the native registry (atom REMOVE / suppress path /
// native action receiver path). Tombstones the eventId to reject late
// FCM or /sync re-seeds within the ring lifetime.
@PluginMethod
public void removeIncomingRing(PluginCall call) {
String eventId = call.getString("eventId");
if (eventId == null || eventId.isEmpty()) {
call.reject("missing_event_id");
return;
}
VojoFirebaseMessagingService.removeIncomingRing(getContext(), eventId);
call.resolve();
}
}

View file

@ -0,0 +1,143 @@
package chat.vojo.app;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import androidx.core.app.NotificationCompat;
/**
* Foreground service kept alive for the duration of an active DM call. Its
* sole job is to promote the process to PROCESS_STATE_FOREGROUND_SERVICE so
* Android doesn't:
* - revoke RECORD_AUDIO via AppOps (API 31+ while-in-use gating);
* - apply background network firewall via netd.
*
* Both revocations were observed in Phase 0 capture on Samsung OneUI API 36:
* mic went Active=false ~5s after screen-off, netd isBlocked=true ~13s after,
* causing Element Call inside the hidden WebView to tear down the LiveKit
* session and the call to drop.
*
* Preconditions enforced by callers:
* - RECORD_AUDIO runtime permission granted (plugin-side check in
* CallForegroundPlugin.start). The manifest declares
* foregroundServiceType="microphone" only, so TYPE_NONE is not a valid
* fallback on API 34+ we never attempt one.
* - JS side gates on useCallJoined so the widget's getUserMedia has already
* prompted for and received the grant by the time we start.
*/
public class CallForegroundService extends Service {
public static final String EXTRA_TITLE = "title";
public static final String EXTRA_BODY = "body";
private static final String CHANNEL_ID = "vojo_calls_ongoing";
// Stable id, distinct from VojoFirebaseMessagingService.SUMMARY_NOTIFICATION_ID
// (Integer.MIN_VALUE) and from per-room call ids (String.hashCode of "call_<roomId>").
private static final int NOTIFICATION_ID = 0x766F6A6F; // "vojo" as ASCII bytes
private static final String TAG = "CallFgs";
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String title = intent != null ? intent.getStringExtra(EXTRA_TITLE) : null;
String body = intent != null ? intent.getStringExtra(EXTRA_BODY) : null;
if (title == null || title.isEmpty()) title = "Активный звонок";
if (body == null) body = "";
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (nm == null) {
Log.w(TAG, "onStartCommand: NotificationManager null, cannot start FGS");
stopSelf(startId);
return START_NOT_STICKY;
}
ensureOngoingChannel(nm);
Intent launchIntent = new Intent(this, MainActivity.class)
.setAction(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
int piFlags = PendingIntent.FLAG_UPDATE_CURRENT
| (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0);
PendingIntent launchPI = PendingIntent.getActivity(this, 0, launchIntent, piFlags);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(title)
.setContentText(body)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setOngoing(true)
.setAutoCancel(false)
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setContentIntent(launchPI);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// API 30+: FOREGROUND_SERVICE_TYPE_MICROPHONE constant exists
// and 3-arg startForeground is available. API 34+ REQUIRES
// the type to match the manifest we declared `microphone`
// and always pass it. RECORD_AUDIO grant is ensured by
// CallForegroundPlugin before this code runs.
startForeground(NOTIFICATION_ID, builder.build(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
Log.d(TAG, "startForeground ok type=microphone");
} else {
// API 24-29: 2-arg form; manifest foregroundServiceType attribute
// is enough for the OS to classify the service correctly.
startForeground(NOTIFICATION_ID, builder.build());
Log.d(TAG, "startForeground ok (pre-R, manifest-driven type)");
}
} catch (Throwable t) {
// If startForeground with TYPE_MICROPHONE throws despite our
// precondition checks (unexpected OEM behavior, race, manifest
// drift), we intentionally do NOT retry with TYPE_NONE that is
// invalid on API 34+ when the manifest declares `microphone`.
// Better to surface the failure and let the call proceed without
// retention than to silently crash with ForegroundServiceTypeException.
Log.e(TAG, "startForeground threw, stopping service without retry", t);
stopSelf(startId);
}
return START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
// Belt-and-suspenders: if the service is being stopped via stopService
// and the FGS flag is still up, make sure the notification goes away.
// Idempotent if stopForeground was already called elsewhere.
stopForeground(STOP_FOREGROUND_REMOVE);
super.onDestroy();
}
private void ensureOngoingChannel(NotificationManager nm) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
if (nm.getNotificationChannel(CHANNEL_ID) != null) return;
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"Активные звонки",
NotificationManager.IMPORTANCE_LOW
);
channel.setDescription("Уведомление во время активного звонка Vojo");
channel.setShowBadge(false);
channel.enableLights(false);
channel.enableVibration(false);
channel.setSound(null, null);
nm.createNotificationChannel(channel);
}
}

View file

@ -0,0 +1,163 @@
package chat.vojo.app;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.util.Log;
import androidx.core.content.LocusIdCompat;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import java.util.Collections;
import java.util.Set;
/**
* Publish a long-lived sharing shortcut for a Matrix room so the system
* treats per-room MessagingStyle notifications as conversations on
* Android 11+ (API 30+).
*
* Without a published shortcut whose id matches the notification's
* setShortcutId(), Android falls back to the app icon for the collapsed-
* preview avatar regardless of Person.setIcon / Builder.setLargeIcon
* Person icons are only consulted by the Conversation styling layer,
* which activates exclusively for notifications backed by a real
* ShortcutInfoCompat marked Long Lived + the SHORTCUT_CATEGORY_CONVERSATION
* sharing category.
*
* Idempotent: republishing the same shortcut id is the documented "update"
* path; ShortcutManagerCompat handles dedup internally. Cheap to call
* from the render hot path (~ms on warm system, indistinguishable from a
* SharedPreferences write at our scale).
*/
final class ConversationShortcuts {
private static final String TAG = "ConvShortcuts";
private ConversationShortcuts() {}
/**
* Publish or refresh the shortcut backing a room's conversation
* notification. No-op on API < 30 Conversation styling is an
* Android 11+ feature; older OS versions render the notification
* fine without the shortcut, and the largeIcon/Person.setIcon
* pipeline is the primary avatar source on them.
*
* @param ctx Context for the shortcut manager binding.
* @param roomId Matrix room id, used as the shortcut id so it
* matches NotificationCompat.Builder.setShortcutId.
* @param isDirect Whether the room is a DM; flips the shortcut
* category so launchers can group DMs separately.
* @param label Short visible label, typically the room name (or
* the peer's display name for a DM).
* @param avatar Optional cached avatar bitmap. Null falls through
* to the app launcher icon still publishes the
* shortcut so the conversation styling activates.
*/
/**
* Returns the published ShortcutInfoCompat so the caller can attach
* it directly to the notification via setShortcutInfo() this is
* the documented "atomic publish + bind" path that avoids the race
* where the notification posts before the shortcut publish has
* settled and Android sees an orphan shortcut id. Null on API < 30,
* null on failure (notification still posts cleanly).
*/
static ShortcutInfoCompat publishForRoom(
Context ctx,
String roomId,
boolean isDirect,
String label,
Bitmap avatar
) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return null;
}
if (roomId == null || roomId.isEmpty()) return null;
try {
// Conversation shortcut icon MUST be adaptive official docs:
// "To avoid unintentional clipping of your shortcut avatar,
// provide an AdaptiveIconDrawable for the shortcut's icon."
// Without this, Android silently falls back to the app's
// launcher icon for the collapsed-shade conversation avatar
// slot, even though shortcut publish + bind succeed.
// Resource icons (mipmap.ic_launcher) already ship with
// adaptive layers in the manifest; bitmap avatars need padding
// so the safe zone doesn't crop them.
IconCompat icon;
if (avatar != null) {
Bitmap padded = AvatarLoader.toAdaptivePaddedBitmap(avatar);
icon = IconCompat.createWithAdaptiveBitmap(padded);
} else {
icon = IconCompat.createWithResource(ctx, R.mipmap.ic_launcher);
}
// Intent the shortcut launches when tapped from the launcher
// long-press menu or share sheet opens MainActivity and
// delivers the same `room_id` extra the notification tap
// path uses, so the existing pushNotificationActionPerformed
// listener navigates correctly.
Intent launchIntent = new Intent(ctx, MainActivity.class)
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra("room_id", roomId)
// Capacitor PushNotificationsPlugin gates its action
// delivery on bundle.containsKey("google.message_id"); we
// attach an empty value so a launcher-initiated open
// takes the same path as a push-tap.
.putExtra("google.message_id", "");
// Constant value of androidx.core's
// ShortcutInfoCompat.SHORTCUT_CATEGORY_CONVERSATION. Hardcoded
// verbatim because older androidx.core in our dependency
// graph doesn't export the constant; the string itself is
// platform-stable per the Android shortcut category contract.
Set<String> categories =
Collections.singleton("android.shortcut.conversation");
ShortcutInfoCompat.Builder b = new ShortcutInfoCompat.Builder(ctx, roomId)
.setShortLabel(label != null && !label.isEmpty() ? label : "Vojo")
.setLongLabel(label != null && !label.isEmpty() ? label : "Vojo")
.setIntent(launchIntent)
.setIcon(icon)
.setLongLived(true)
.setCategories(categories)
// LocusId mirrors the shortcut id; the OS uses it to
// attribute the notification to a specific conversation
// for digital-wellbeing dashboards and bubble grouping.
.setLocusId(new LocusIdCompat(roomId))
// Marks isDirect so launchers / share sheet can present
// person-style affordances on DMs.
.setIsConversation();
// setPerson is only needed for one-on-one conversations to
// unlock direct-share suggestions, but for a DM we also want
// it to anchor the shortcut on the peer's identity. Skipped
// for groups (single Person doesn't represent the room).
if (isDirect) {
b.setPerson(new androidx.core.app.Person.Builder()
// setKey must match the Person.key used in the
// MessagingStyle so Android's conversation
// attribution matches the shortcut to the
// notification on the same identity.
.setKey(roomId)
.setName(label != null ? label : "")
.setIcon(icon)
.build());
}
ShortcutInfoCompat shortcut = b.build();
boolean ok = ShortcutManagerCompat.pushDynamicShortcut(ctx, shortcut);
Log.i(TAG, "publish room=" + roomId + " label=" + label
+ " hasAvatar=" + (avatar != null) + " ok=" + ok);
return shortcut;
} catch (Throwable t) {
// Shortcut publish is best-effort UX a failure must not
// sink the notification. Worst case: collapsed preview
// falls back to app icon (same as before the shortcut path
// existed at all).
Log.w(TAG, "publish failed room=" + roomId, t);
return null;
}
}
}

View file

@ -0,0 +1,75 @@
package chat.vojo.app;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
/**
* Bridges Android 14+ (API 34) full-screen-intent opt-in into JS.
*
* On API 34 `USE_FULL_SCREEN_INTENT` was reclassified from "normal" to
* "special appop" declaring it in the manifest is no longer enough to
* actually display a full-screen notification over the lockscreen. The user
* must opt in via Settings Apps Vojo Full-screen notifications. There's
* no runtime grant API, only a deep-link.
*
* Without the opt-in, `setFullScreenIntent(launchPI, true)` still satisfies
* the AOSP NotificationManagerService gate (so CallStyle doesn't get silently
* dropped), but the notification renders as a regular heads-up and the screen
* doesn't wake over the lockscreen which was the "why is this just a banner
* on the lockscreen?" symptom we saw on the Samsung OneUI test device.
*
* See docs/plans/dm_calls.md ADR 2.5-fsi for the full history.
*/
@CapacitorPlugin(name = "FullScreenIntent")
public class FullScreenIntentPlugin extends Plugin {
@PluginMethod
public void canUseFullScreenIntent(PluginCall call) {
JSObject ret = new JSObject();
ret.put("value", canUseFullScreenIntentInternal());
call.resolve(ret);
}
@PluginMethod
public void openSettings(PluginCall call) {
Context ctx = getContext();
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// API 34+ has a dedicated Settings screen for the full-screen notification opt-in.
intent = new Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT);
intent.setData(Uri.parse("package:" + ctx.getPackageName()));
} else {
// Fallback for API 33: the per-app notification Settings page is the closest
// equivalent and also covers channel-level toggles (mute, DND bypass, etc).
intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, ctx.getPackageName());
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
ctx.startActivity(intent);
call.resolve();
} catch (Throwable t) {
call.reject("Failed to open FSI settings: " + t.getMessage());
}
}
private boolean canUseFullScreenIntentInternal() {
// On API 33 `USE_FULL_SCREEN_INTENT` is a normal permission if it's
// declared in the manifest, the app already has it. Skip the runtime check.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return true;
NotificationManager nm = (NotificationManager)
getContext().getSystemService(Context.NOTIFICATION_SERVICE);
if (nm == null) return false;
return nm.canUseFullScreenIntent();
}
}

View file

@ -0,0 +1,16 @@
package chat.vojo.app;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
@CapacitorPlugin(name = "LaunchSplash")
public class LaunchSplashPlugin extends Plugin {
@PluginMethod
public void ready(PluginCall call) {
MainActivity.releaseLaunchSplash();
call.resolve();
}
}

View file

@ -0,0 +1,128 @@
package chat.vojo.app;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import androidx.activity.EdgeToEdge;
import androidx.core.splashscreen.SplashScreen;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {
public static volatile boolean isInForeground = false;
private static volatile boolean launchSplashReady = false;
// Safety net for setKeepOnScreenCondition: if JS never calls
// launchSplash.ready() (boot crash, exception during config load before
// AuthMascot mounts, network hang in useClientConfig, deep-link straight
// into AuthLayout where the centered AuthMascot variant doesn't render,
// ) the splash would otherwise hang indefinitely and the user can't
// interact with anything. 5s covers normal cold boots on mid-range
// Android (config + bundle parse + first paint typically lands inside
// 1-2s) with comfortable headroom; past it we drop the splash and let
// whatever the web side has rendered take over including blank
// AuthLayout, which is at least recoverable.
private static final long SPLASH_SAFETY_TIMEOUT_MS = 5000L;
// Short debounce on the onPauserenderRegistry edge so an in-flight JS
// removeIncomingRing bridge call (e.g. user accepted/declined, then
// immediately pressed Home) has a chance to land before we post native
// CallStyle for a ring that's about to be removed. 150ms covers the
// strip-accept chain (Capacitor roundtrip + switchOrStartDmCall
// resolve + sync-effect bridge) on mid-range Android; imperceptible
// as a silent-ring delay.
private static final long RENDER_DEBOUNCE_MS = 150L;
// Modest debounce on the onResumecancelRenderedIncomingRings edge so JS
// has a moment to hydrate incomingCallsAtom before the native surface
// goes away. Covers warm resume (hook alive, /sync delivers an ADD
// within ~100-200ms) cleanly. Cold resume (killed process, Matrix
// client rehydration takes 1-3s) still has a "no surface" window
// acceptable tradeoff since tap-native flows carry call_action and
// are handled by pendingCallActionConsumer regardless of atom state.
private static final long CANCEL_DEBOUNCE_MS = 300L;
private final Handler lifecycleHandler = new Handler(Looper.getMainLooper());
private final Runnable renderRunnable = () ->
VojoFirebaseMessagingService.renderRegistry(this);
private final Runnable cancelRunnable = () ->
VojoFirebaseMessagingService.cancelRenderedIncomingRings(this);
public static void releaseLaunchSplash() {
launchSplashReady = true;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
launchSplashReady = false;
}
// Custom plugins must be registered before super.onCreate so BridgeActivity
// can wire them into the WebView bridge on load. Registering after
// super.onCreate would make the plugin invisible to JS until the next relaunch.
registerPlugin(FullScreenIntentPlugin.class);
registerPlugin(CallForegroundPlugin.class);
registerPlugin(LaunchSplashPlugin.class);
registerPlugin(ShareTargetPlugin.class);
registerPlugin(PollingPlugin.class);
// AndroidX SplashScreen must be installed before super.onCreate().
// Keep it until the web splash confirms its first visible frame is
// ready, OR the safety timeout elapses (see SPLASH_SAFETY_TIMEOUT_MS).
final long splashStartMs = System.currentTimeMillis();
SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
splashScreen.setKeepOnScreenCondition(() -> {
if (launchSplashReady) return false;
return System.currentTimeMillis() - splashStartMs < SPLASH_SAFETY_TIMEOUT_MS;
});
EdgeToEdge.enable(this);
super.onCreate(savedInstanceState);
// Force light icons on both system bars: our CSS is permanently dark
// (Dawn redesign), but EdgeToEdge.enable auto-detects icon tint from
// the device uiMode on a light-mode device that gives dark icons
// over our dark bars and they vanish.
WindowInsetsControllerCompat controller =
WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());
controller.setAppearanceLightStatusBars(false);
controller.setAppearanceLightNavigationBars(false);
}
@Override
public void onResume() {
super.onResume();
isInForeground = true;
// Cancel any pending render: user came back before the debounce fired,
// JS strip will own UX, no need to surface native.
lifecycleHandler.removeCallbacks(renderRunnable);
// Defer the native cancel so JS strip has a moment to hydrate from
// incomingCallsAtom. Registry entries persist they still represent
// live rings, just the native surfaces go.
lifecycleHandler.removeCallbacks(cancelRunnable);
lifecycleHandler.postDelayed(cancelRunnable, CANCEL_DEBOUNCE_MS);
}
@Override
public void onPause() {
super.onPause();
isInForeground = false;
// Re-backgrounding: don't cancel a native the user will still need
// visible. The render runnable below will re-render if needed.
lifecycleHandler.removeCallbacks(cancelRunnable);
// Schedule render user is backgrounding, JS audio gate is about to
// close, native CallStyle must surface or the ring goes silent. Debounce
// absorbs the bridge-call race: if onResume fires within RENDER_DEBOUNCE_MS
// (user bounce), the render is cancelled.
lifecycleHandler.removeCallbacks(renderRunnable);
lifecycleHandler.postDelayed(renderRunnable, RENDER_DEBOUNCE_MS);
}
@Override
public void onDestroy() {
// Drop any pending render/cancel so runnables which capture `this`
// can't fire post-destroy and land an nm.notify / nm.cancel against a
// dead Activity context on config change (rotation) or process teardown.
lifecycleHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
}

View file

@ -0,0 +1,147 @@
package chat.vojo.app;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Handles the per-notification "Mark as read" action.
*
* Posts {@code POST /_matrix/client/v3/rooms/{roomId}/receipt/m.read/{eventId}}
* using the access token saved by the polling lifecycle in
* {@code vojo_poll_state} SharedPreferences (same storage VojoPollWorker uses;
* keeps the credential lifecycle single-sourced). After a successful 2xx the
* per-room MessagingStyle notification is dismissed and the
* {@link RoomMessageCache} is cleared so the next push to that room starts a
* fresh conversation rather than re-appending to the prior history.
*
* Dismiss policy: OPTIMISTIC. The per-room notification is dismissed
* synchronously in onReceive before the HTTP receipt PUT is even
* attempted so the user sees instant feedback. The async receipt POST
* happens on a worker thread afterwards. This mirrors element-android's
* NotificationBroadcastReceiver pattern and matches the user's mental
* model ("I tapped, it should disappear immediately").
*
* Failure mode: on any non-2xx or thrown exception we accept that the
* server-side read receipt did not land. We do NOT re-post the
* notification or implement a flusher because:
* - the next room open from the JS app issues a fresh read-receipt
* for the latest visible event, catching up the server state
* - the in-app read-marker logic is the authoritative path; this
* receiver is a convenience for the shade-tap shortcut
* - accumulating tombstones in prefs (the CallDeclineReceiver pattern)
* would risk leaking historical eventIds the JS side would re-issue
* on app resume anyway
*
* Null-credential edge case (fresh install + first push before any
* saveSession bridge): no token to use, we still dismiss the notification
* locally so the user isn't stuck looking at a "stuck" Mark-as-read
* button. The next room open from JS covers the server view.
*/
public class MarkAsReadReceiver extends BroadcastReceiver {
public static final String ACTION_MARK_AS_READ = "chat.vojo.app.MARK_AS_READ";
public static final String EXTRA_ROOM_ID = "room_id";
public static final String EXTRA_EVENT_ID = "event_id";
private static final int CONNECT_TIMEOUT_MS = 8_000;
private static final int READ_TIMEOUT_MS = 8_000;
private static final String TAG = "MarkAsReadRcvr";
private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) return;
final String roomId = intent.getStringExtra(EXTRA_ROOM_ID);
final String eventId = intent.getStringExtra(EXTRA_EVENT_ID);
if (roomId == null || roomId.isEmpty()) {
Log.w(TAG, "onReceive: missing room_id, abort");
return;
}
final Context appContext = context.getApplicationContext();
// Dismiss first for instant UX feedback HTTP latency is irrelevant
// to the perceived "marked as read" action.
VojoFirebaseMessagingService.dismissRoomNotification(appContext, roomId);
final SharedPreferences prefs = appContext.getSharedPreferences(
VojoPollWorker.PREFS, Context.MODE_PRIVATE);
final String token = prefs.getString(VojoPollWorker.KEY_ACCESS_TOKEN, null);
final String homeserver = prefs.getString(VojoPollWorker.KEY_HOMESERVER_URL, null);
if (token == null || token.isEmpty() || homeserver == null || homeserver.isEmpty()) {
Log.w(TAG, "onReceive: no credentials in prefs, local dismiss only");
return;
}
if (eventId == null || eventId.isEmpty()) {
// Without an eventId we cannot issue a receipt PUT the JS-side
// read-marker handler will catch this up on the next room open.
Log.w(TAG, "onReceive: no event_id, local dismiss only");
return;
}
final PendingResult pendingResult = goAsync();
EXECUTOR.execute(() -> {
try {
int status = sendReceipt(homeserver, token, roomId, eventId);
if (status >= 200 && status < 300) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "receipt ok status=" + status + " room=" + roomId);
}
} else {
Log.w(TAG, "receipt non-2xx status=" + status + " room=" + roomId);
}
} catch (Throwable t) {
Log.w(TAG, "receipt threw room=" + roomId, t);
} finally {
pendingResult.finish();
}
});
}
private int sendReceipt(
String baseUrl,
String accessToken,
String roomId,
String eventId
) throws IOException {
String url = trimTrailingSlash(baseUrl)
+ "/_matrix/client/v3/rooms/"
+ URLEncoder.encode(roomId, "UTF-8")
+ "/receipt/m.read/"
+ URLEncoder.encode(eventId, "UTF-8");
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
try {
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "Bearer " + accessToken);
conn.setRequestProperty("Content-Type", "application/json");
conn.setConnectTimeout(CONNECT_TIMEOUT_MS);
conn.setReadTimeout(READ_TIMEOUT_MS);
conn.setDoOutput(true);
// Empty JSON body per spec; setFixedLengthStreamingMode keeps the
// connection on the cached path instead of chunked-transfer fallback.
byte[] payload = "{}".getBytes("UTF-8");
conn.setFixedLengthStreamingMode(payload.length);
try (java.io.OutputStream os = conn.getOutputStream()) {
os.write(payload);
}
return conn.getResponseCode();
} finally {
conn.disconnect();
}
}
private static String trimTrailingSlash(String s) {
return (s != null && s.endsWith("/")) ? s.substring(0, s.length() - 1) : s;
}
}

View file

@ -0,0 +1,104 @@
package chat.vojo.app;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Cross-source LRU dedup for rendered push event_ids.
*
* Both the FCM service (after a successful nm.notify) and the polling Worker
* write into the same bounded SharedPreferences-backed set. The Worker reads
* it to skip events FCM already delivered which fixes the regression where
* a user who dismissed an FCM notification before polling fired would see
* the same event resurface up to 15 minutes later via the polling fallback.
*
* The native `eventId.hashCode()` notification-id slot is still the primary
* dedup for *concurrent* render (Android NotificationManager replace), but
* that only collapses surfaces while both notifications are still visible;
* once the user dismisses, the slot is empty and the second render would
* post fresh. This shared set covers that gap.
*
* Synchronisation: SharedPreferences read-modify-write is not atomic across
* threads/processes, and FCM service runs on a Firebase-managed background
* thread while the Worker runs on WorkManager's executor. We serialise all
* mutations through a static lock. Critical sections are short (string split
* + LinkedHashSet trim + putString) no Binder calls.
*/
final class NotificationDedup {
// Capacity is intentionally larger than VojoPollWorker's worst-case per-run
// event count (MAX_PAGES_PER_RUN × PAGE_LIMIT = 250). If a single fire
// marks 250 events and the cap were 200, the 50 oldest of those would
// already be evicted by the time we finish writing so a sibling poll
// resuming the same window would re-render them. 500 gives 2× headroom
// while staying ~12 KB in SharedPreferences (negligible).
private static final int MAX_TRACKED = 500;
private static final Object lock = new Object();
private NotificationDedup() {}
/** Returns true iff the given event_id has been notified in a recent cycle. */
static boolean wasNotified(Context ctx, String eventId) {
if (eventId == null || eventId.isEmpty()) return false;
synchronized (lock) {
return readSet(ctx).contains(eventId);
}
}
/** Append the event_id to the LRU set, trimming the oldest when full. */
static void markNotified(Context ctx, String eventId) {
if (eventId == null || eventId.isEmpty()) return;
synchronized (lock) {
Set<String> set = readSet(ctx);
// LinkedHashSet preserves insertion order re-adding moves to tail
// only if we remove-then-add. The Set#add no-op on a present entry
// does NOT refresh position, but the simple "drop oldest" trim
// below is adequate for our scale and matches the Worker's
// existing semantics. Skip the disk write entirely when add()
// returned false the event was already in the set, persistence
// would just churn SharedPreferences for no state change.
if (!set.add(eventId)) return;
if (set.size() > MAX_TRACKED) {
Iterator<String> it = set.iterator();
int drop = set.size() - MAX_TRACKED;
while (it.hasNext() && drop > 0) {
it.next();
it.remove();
drop -= 1;
}
}
writeSet(ctx, set);
}
}
/** Caller must hold {@link #lock}. */
private static Set<String> readSet(Context ctx) {
SharedPreferences prefs = ctx.getSharedPreferences(
VojoPollWorker.PREFS, Context.MODE_PRIVATE);
String raw = prefs.getString(VojoPollWorker.KEY_NOTIFIED_IDS, "");
Set<String> out = new LinkedHashSet<>();
if (raw.isEmpty()) return out;
for (String id : raw.split(",")) {
if (!id.isEmpty()) out.add(id);
}
return out;
}
/** Caller must hold {@link #lock}. */
private static void writeSet(Context ctx, Set<String> set) {
SharedPreferences prefs = ctx.getSharedPreferences(
VojoPollWorker.PREFS, Context.MODE_PRIVATE);
StringBuilder sb = new StringBuilder(set.size() * 25);
boolean first = true;
for (String id : set) {
if (!first) sb.append(',');
sb.append(id);
first = false;
}
prefs.edit().putString(VojoPollWorker.KEY_NOTIFIED_IDS, sb.toString()).apply();
}
}

View file

@ -0,0 +1,37 @@
package chat.vojo.app;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
/**
* Fires when the user swipes a per-room MessagingStyle notification away.
*
* Without this hook, RoomMessageCache would still hold the prior messages
* for that room and the next push would append onto that history and
* re-surface the messages the user just dismissed. With it, swipe clears
* the cache so the next push starts a fresh conversation for the room.
*
* NOTE: this only fires for user-driven dismissals programmatic
* nm.cancel calls (mark-as-read, receipt-driven dismiss, channel migration)
* already call RoomMessageCache.clear themselves and do NOT fire the
* delete intent. There's no double-clear risk.
*/
public class NotificationDismissReceiver extends BroadcastReceiver {
public static final String ACTION_NOTIFICATION_DISMISSED =
"chat.vojo.app.NOTIFICATION_DISMISSED";
public static final String EXTRA_ROOM_ID = "room_id";
private static final String TAG = "DismissRcvr";
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) return;
String roomId = intent.getStringExtra(EXTRA_ROOM_ID);
if (roomId == null || roomId.isEmpty()) return;
if (BuildConfig.DEBUG) Log.d(TAG, "swipe clear cache room=" + roomId);
RoomMessageCache.clear(roomId);
}
}

View file

@ -0,0 +1,236 @@
package chat.vojo.app;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import java.util.concurrent.TimeUnit;
/**
* JS Android bridge for the WorkManager-based polling fallback.
*
* Lifecycle:
* - JS calls saveSession({accessToken, homeserverUrl, userId}) on login,
* on push (re)enable, and on visibilitychange visible (to recover a
* 401-cleared credentials slot without a full remount).
* - JS calls schedule({intervalMinutes}) once push is enabled. Idempotent:
* KEEP policy means a second schedule() call against an already-enqueued
* worker is a no-op (the running period continues unchanged).
* - JS calls saveRoomNames({names}) on mount + visibilitychange visible
* so VojoPollWorker has a local cache to resolve room_id display name
* without making N extra GET /rooms/{id}/state/m.room.name requests.
* Brand-new rooms created between visibility events fall back to
* sender_display_name in the renderer.
* - JS calls cancel() + clearSession() on logout / push disable.
*
* Worker tag: a single unique periodic worker named UNIQUE_WORK_NAME KEEP
* policy prevents schedule churn from re-creating it. Cancel() removes it
* by the same name.
*/
@CapacitorPlugin(name = "Polling")
public class PollingPlugin extends Plugin {
private static final String TAG = "PollingPlugin";
private static final String UNIQUE_WORK_NAME = "vojo_push_poll";
// Android's hard floor for PeriodicWorkRequest. Requests with shorter
// intervals are silently clamped to 15 minutes. We accept the requested
// value from JS but enforce the floor here so misuse from JS doesn't
// produce a silently-different behavior.
private static final long MIN_INTERVAL_MINUTES = 15;
@PluginMethod
public void saveSession(PluginCall call) {
String accessToken = call.getString("accessToken");
String homeserverUrl = call.getString("homeserverUrl");
if (accessToken == null || accessToken.isEmpty()
|| homeserverUrl == null || homeserverUrl.isEmpty()) {
call.reject("missing_accessToken_or_homeserverUrl");
return;
}
String userId = call.getString("userId");
SharedPreferences prefs = getContext()
.getSharedPreferences(VojoPollWorker.PREFS, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit()
.putString(VojoPollWorker.KEY_ACCESS_TOKEN, accessToken)
.putString(VojoPollWorker.KEY_HOMESERVER_URL, homeserverUrl);
if (userId != null && !userId.isEmpty()) {
editor.putString(VojoPollWorker.KEY_USER_ID, userId);
}
// Seed the watermark to "now minus a small clock-skew buffer" on the
// first saveSession after install / logout. Without seeding the
// Worker's first fire sees watermark=0 and renders every historical
// unread /notifications entry as a fresh push. The buffer covers the
// case where the device clock runs ahead of the homeserver's clock
// event ts is server-side, so a too-fresh local seed would silently
// skip recently-arrived events as "older than watermark" forever.
// 60s tolerates typical NTP drift while still suppressing days-old
// backlog on first enable. We seed only when the key is absent so
// subsequent saveSession calls (token rotation, visibilitychange
// re-bridge) don't reset live state.
if (!prefs.contains(VojoPollWorker.KEY_LAST_SEEN_TS)) {
editor.putLong(
VojoPollWorker.KEY_LAST_SEEN_TS,
System.currentTimeMillis() - SEED_CLOCK_SKEW_BUFFER_MS
);
}
editor.apply();
call.resolve();
}
private static final long SEED_CLOCK_SKEW_BUFFER_MS = 60_000L;
@PluginMethod
public void clearSession(PluginCall call) {
getContext()
.getSharedPreferences(VojoPollWorker.PREFS, Context.MODE_PRIVATE)
.edit()
.remove(VojoPollWorker.KEY_ACCESS_TOKEN)
.remove(VojoPollWorker.KEY_HOMESERVER_URL)
.remove(VojoPollWorker.KEY_USER_ID)
.remove(VojoPollWorker.KEY_LAST_SEEN_TS)
.remove(VojoPollWorker.KEY_DRAIN_CURSOR)
.remove(VojoPollWorker.KEY_DRAIN_TARGET_TS)
.remove(VojoPollWorker.KEY_NOTIFIED_IDS)
.remove(VojoPollWorker.KEY_ROOM_NAMES)
.remove(VojoPollWorker.KEY_USER_AVATARS)
.apply();
call.resolve();
}
/**
* user_id MXC avatar URL snapshot. Mirrors {@link #saveRoomNames}
* stored as a JSON blob in vojo_poll_state for the FCM service /
* polling Worker / ReplyReceiver to consult via
* VojoFirebaseMessagingService.lookupUserAvatarMxc. JS dumps on the
* same lifecycle triggers as room names (mount, visibility resume,
* m.direct change, m.room.encryption flip).
*/
@PluginMethod
public void saveUserAvatars(PluginCall call) {
JSObject avatars = call.getObject("avatars");
if (avatars == null) {
call.reject("missing_avatars");
return;
}
String serialized = avatars.toString();
getContext()
.getSharedPreferences(VojoPollWorker.PREFS, Context.MODE_PRIVATE)
.edit()
.putString(VojoPollWorker.KEY_USER_AVATARS, serialized)
.apply();
Log.i(TAG, "saveUserAvatars: " + avatars.length() + " entries, "
+ serialized.length() + " bytes");
call.resolve();
}
@PluginMethod
public void saveRoomNames(PluginCall call) {
JSObject names = call.getObject("names");
if (names == null) {
// Empty map is also valid (user cleared all rooms) JS passes
// {} explicitly in that case; missing key is a contract bug.
call.reject("missing_names");
return;
}
// `JSObject extends JSONObject`, so names.toString() is already a
// valid JSON serialisation of validated values no need to re-parse
// it through `new JSONObject(...)` just to re-serialise. Persist
// verbatim.
String serialized = names.toString();
getContext()
.getSharedPreferences(VojoPollWorker.PREFS, Context.MODE_PRIVATE)
.edit()
.putString(VojoPollWorker.KEY_ROOM_NAMES, serialized)
.apply();
Log.i(TAG, "saveRoomNames: " + names.length() + " entries, "
+ serialized.length() + " bytes");
call.resolve();
}
@PluginMethod
public void schedule(PluginCall call) {
Integer intervalMinutes = call.getInt("intervalMinutes", 15);
long interval = Math.max(MIN_INTERVAL_MINUTES, intervalMinutes != null ? intervalMinutes : 15);
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
PeriodicWorkRequest req = new PeriodicWorkRequest.Builder(
VojoPollWorker.class, interval, TimeUnit.MINUTES
)
.setConstraints(constraints)
.addTag("vojo_push_poll")
.build();
try {
WorkManager.getInstance(getContext())
.enqueueUniquePeriodicWork(
UNIQUE_WORK_NAME,
ExistingPeriodicWorkPolicy.KEEP,
req
);
Log.d(TAG, "scheduled periodic poll every " + interval + " minutes");
call.resolve();
} catch (Throwable t) {
Log.w(TAG, "schedule failed", t);
call.reject("schedule_failed: " + t.getMessage());
}
}
/**
* Dismiss the per-room MessagingStyle notification + clear the in-memory
* RoomMessageCache for the room. Called from the JS receipt listener when
* a server-side read receipt zeroes the unread count (the user read on
* another device / tab). No-op if the notification was never posted or
* has already been swiped away.
*/
@PluginMethod
public void dismissRoom(PluginCall call) {
String roomId = call.getString("roomId");
if (roomId == null || roomId.isEmpty()) {
call.reject("missing_roomId");
return;
}
VojoFirebaseMessagingService.dismissRoomNotification(getContext(), roomId);
call.resolve();
}
@PluginMethod
public void cancel(PluginCall call) {
try {
// Block on the Operation so callers awaiting cancel() see the
// cancel committed to WorkManager's database before we resolve.
// (NOTE: this does NOT interrupt a Worker that's already mid
// doWork(); cooperative cancellation via isStopped() is owned
// by VojoPollWorker itself.) Without this wait a fast
// disablereenable sequence races with ExistingPeriodicWorkPolicy.KEEP
// the second enqueueUniquePeriodicWork can land before the
// cancel is committed and become a no-op. We're already off
// the main thread (Capacitor dispatches plugin calls on its
// own executor), so the blocking get() is safe here.
WorkManager.getInstance(getContext())
.cancelUniqueWork(UNIQUE_WORK_NAME)
.getResult()
.get();
Log.d(TAG, "cancelled periodic poll");
call.resolve();
} catch (Throwable t) {
Log.w(TAG, "cancel failed", t);
call.reject("cancel_failed: " + t.getMessage());
}
}
}

View file

@ -0,0 +1,170 @@
package chat.vojo.app;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import java.util.Locale;
/**
* Locale-aware push-notification strings. Resources (values{,-ru}/
* push_strings.xml) are auto-generated at build time by
* scripts/gen-push-strings.mjs from public/locales/{en,ru}.json so
* the Push namespace has a single source of truth shared with the
* web Service Worker. Do not edit push_strings.xml by hand it will
* be overwritten on the next `npm run android:sync`.
*
* Locale selection: Vojo ships an explicit in-app language picker that
* does NOT have to match the device locale. pushLanguageBridge.ts
* mirrors i18next's current language into Capacitor Preferences
* (shared_prefs/CapacitorStorage.xml, key "vojo.appLanguage") on every
* `languageChanged` event; we read it here and force it onto a
* Configuration-scoped Context before the getString call.
*
* Killed-process pushes may arrive before JS has ever booted no pref
* to read. In that case we fall back to the device locale, normalised
* to {en, ru}; anything else maps to en, matching i18n.ts
* fallbackLng: 'en' on the main thread.
*/
final class PushStrings {
private static final String PREFS_GROUP = "CapacitorStorage";
private static final String LANG_KEY = "vojo.appLanguage";
private PushStrings() {}
static String messageFallback(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_new_message);
}
static String messagesFallback(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_new_messages);
}
static String inviteTitle(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_invitation);
}
static String missedCallTitle(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_missed_call);
}
static String missedCallBody(Context ctx, String caller) {
String safeCaller = caller == null ? "" : caller;
return forAppLocale(ctx).getString(R.string.push_missed_call_body, safeCaller);
}
static String channelGroup(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_channel_group);
}
static String channelDm(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_channel_dm);
}
static String channelDmDescription(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_channel_dm_description);
}
static String channelGroupRoom(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_channel_group_room);
}
static String channelGroupRoomDescription(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_channel_group_room_description);
}
static String selfName(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_self_name);
}
static String markAsReadAction(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_action_mark_as_read);
}
static String replyAction(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_action_reply);
}
static String replyHint(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_reply_hint);
}
static String replyFailed(Context ctx) {
return forAppLocale(ctx).getString(R.string.push_reply_failed);
}
/**
* Build the invite-notification body from inviter + room name, falling
* back through four variants when one or both are absent. The res IDs
* all declare positional formatters (%1$s = inviter, %2$s = roomName)
* to keep the order stable across locales; we always pass both args
* even when only one is used, because Android's formatter requires
* every referenced position to be supplied.
*/
static String inviteBody(Context ctx, String inviter, String roomName) {
boolean hasInviter = inviter != null && !inviter.isEmpty();
boolean hasRoom = roomName != null && !roomName.isEmpty();
int resId;
if (hasInviter && hasRoom) {
resId = R.string.push_invite_body;
} else if (hasInviter) {
resId = R.string.push_invite_body_no_room;
} else if (hasRoom) {
resId = R.string.push_invite_body_no_inviter;
} else {
resId = R.string.push_invite_body_generic;
}
String safeInviter = hasInviter ? inviter : "";
String safeRoom = hasRoom ? roomName : "";
return forAppLocale(ctx).getString(resId, safeInviter, safeRoom);
}
private static Context forAppLocale(Context ctx) {
String lang = chooseLang(ctx);
try {
Configuration cfg = new Configuration(ctx.getResources().getConfiguration());
cfg.setLocale(Locale.forLanguageTag(lang));
return ctx.createConfigurationContext(cfg);
} catch (Throwable t) {
return ctx;
}
}
private static String chooseLang(Context ctx) {
String fromPref = readAppLanguage(ctx);
if (fromPref != null) return fromPref;
try {
Locale loc = Locale.getDefault();
if (loc != null) return normalize(loc.getLanguage());
} catch (Throwable ignored) {
// Locale.getDefault is unusually robust but defensively fall through.
}
return "en";
}
private static String readAppLanguage(Context ctx) {
try {
SharedPreferences prefs =
ctx.getSharedPreferences(PREFS_GROUP, Context.MODE_PRIVATE);
return normalize(prefs.getString(LANG_KEY, null));
} catch (Throwable t) {
return null;
}
}
// Match i18next config: `fallbackLng: 'en'`, `load: 'languageOnly'`.
// Vojo only ships en.json + ru.json; any other Configuration locale
// (e.g. "fr") would bypass both values-ru/ and our bundled resources
// and land on values/ anyway we collapse to "en" explicitly so the
// web and native surfaces render the same lingua-franca default and
// readers of this method don't have to reason about fall-through.
// Clamp on both write (pushLanguageBridge.ts) and read (here) so a
// stale or tampered pref value can't leak through.
private static String normalize(String raw) {
if (raw == null) return null;
String lower = raw.toLowerCase(Locale.ROOT);
if (lower.startsWith("ru")) return "ru";
return "en";
}
}

View file

@ -0,0 +1,248 @@
package chat.vojo.app;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.app.RemoteInput;
import org.json.JSONObject;
import org.json.JSONException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Handles the inline-reply RemoteInput action on a per-room MessagingStyle
* notification.
*
* Flow:
* 1. User taps reply, types text, presses send broadcast fires here.
* 2. We immediately append the outgoing message to RoomMessageCache and
* re-post the notification (instant UX feedback the message appears
* as a self-Person bubble in the conversation while the HTTP is in
* flight).
* 3. PUT /_matrix/client/v3/rooms/{roomId}/send/m.room.message/{txnId}
* with {msgtype: "m.text", body}. Uses the vojo_poll_state token (same
* storage as Worker / MarkAsReadReceiver single credential lifecycle).
* 4. On 2xx: nothing further; the JS sync echo will eventually replace
* the local-echo bubble in-app.
* 5. On non-2xx or thrown: post a small error notification "Could not
* send your reply" so the user knows to retry from in-app — better
* than silently swallowing the message.
*
* E2EE rooms are guarded UP-STREAM in VojoFirebaseMessagingService.
* renderMessageNotification: we don't even attach the reply action when
* RoomMetadata.isEncrypted is true. So this receiver never has to encrypt.
* Defense in depth: if a stale notification with the action ever survives
* an encryption flip we still detect the failure as a non-2xx HTTP and
* surface the error notification rather than sending cleartext (which
* Synapse would in any case reject for an encrypted room).
*
* Null-credential edge case: post the error notification so the user
* notices and retries in-app. Same logic as a network failure.
*/
public class ReplyReceiver extends BroadcastReceiver {
public static final String ACTION_REPLY = "chat.vojo.app.REPLY";
public static final String EXTRA_ROOM_ID = "room_id";
public static final String KEY_TEXT_REPLY = "vojo.text_reply";
private static final int CONNECT_TIMEOUT_MS = 8_000;
private static final int READ_TIMEOUT_MS = 8_000;
private static final String TAG = "ReplyRcvr";
private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) return;
final String roomId = intent.getStringExtra(EXTRA_ROOM_ID);
if (roomId == null || roomId.isEmpty()) {
Log.w(TAG, "onReceive: missing room_id, abort");
return;
}
Bundle remote = RemoteInput.getResultsFromIntent(intent);
if (remote == null) {
Log.w(TAG, "onReceive: no RemoteInput results");
return;
}
CharSequence reply = remote.getCharSequence(KEY_TEXT_REPLY);
if (reply == null) {
Log.w(TAG, "onReceive: RemoteInput missing text");
return;
}
final String text = reply.toString().trim();
if (text.isEmpty()) return;
final Context appContext = context.getApplicationContext();
// Pre-flight validation BEFORE the optimistic echo. Posting a self
// bubble first and then immediately stacking an error notif on top
// is jarring UX; for predictable failures (logged out, freshly
// encrypted room) we'd rather skip the echo and only surface the
// error.
final SharedPreferences prefs = appContext.getSharedPreferences(
VojoPollWorker.PREFS, Context.MODE_PRIVATE);
final String token = prefs.getString(VojoPollWorker.KEY_ACCESS_TOKEN, null);
final String homeserver = prefs.getString(VojoPollWorker.KEY_HOMESERVER_URL, null);
if (token == null || token.isEmpty() || homeserver == null || homeserver.isEmpty()) {
Log.w(TAG, "onReceive: no credentials in prefs, surfacing error notif");
postReplyError(appContext, roomId);
return;
}
// Race guard for E2EE flip: the per-room metadata snapshot is
// refreshed by JS on m.room.encryption Timeline events, but a push
// delivered in the narrow window between the encryption state
// landing and the dump completing could still expose the reply
// action on a freshly-encrypted room. Re-read the snapshot
// synchronously here Synapse does NOT enforce "no cleartext in
// encrypted rooms" at the spec level, so without this guard we'd
// leak the user's reply into an E2EE timeline as plaintext.
if (isRoomEncryptedAtSendTime(prefs, roomId)) {
Log.w(TAG, "onReceive: room flipped to encrypted between render and send, abort");
postReplyError(appContext, roomId);
return;
}
// Optimistic local echo appends a self-Person message to the
// conversation and re-posts, so the user sees their reply in the
// shade before the HTTP completes. Only happens after pre-flight
// checks pass so the user doesn't see an echo for a reply we know
// will fail.
long now = System.currentTimeMillis();
VojoFirebaseMessagingService.appendOutgoingMessage(appContext, roomId, text, now);
final PendingResult pendingResult = goAsync();
final String txnId = "vojo-reply-" + UUID.randomUUID();
EXECUTOR.execute(() -> {
try {
int status = sendReply(homeserver, token, roomId, txnId, text);
if (status >= 200 && status < 300) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "reply ok status=" + status + " room=" + roomId);
}
} else {
Log.w(TAG, "reply non-2xx status=" + status + " room=" + roomId);
postReplyError(appContext, roomId);
}
} catch (Throwable t) {
Log.w(TAG, "reply threw room=" + roomId, t);
postReplyError(appContext, roomId);
} finally {
pendingResult.finish();
}
});
}
private int sendReply(
String baseUrl,
String accessToken,
String roomId,
String txnId,
String text
) throws IOException {
String url = trimTrailingSlash(baseUrl)
+ "/_matrix/client/v3/rooms/"
+ URLEncoder.encode(roomId, "UTF-8")
+ "/send/m.room.message/"
+ URLEncoder.encode(txnId, "UTF-8");
JSONObject body;
try {
body = new JSONObject();
body.put("msgtype", "m.text");
body.put("body", text);
} catch (org.json.JSONException je) {
// JSONObject.put only throws on NaN/Inf doubles, neither of
// which we use but keep the type contract honest.
throw new IOException("payload encode failed", je);
}
byte[] payload = body.toString().getBytes("UTF-8");
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
try {
conn.setRequestMethod("PUT");
conn.setRequestProperty("Authorization", "Bearer " + accessToken);
conn.setRequestProperty("Content-Type", "application/json");
conn.setConnectTimeout(CONNECT_TIMEOUT_MS);
conn.setReadTimeout(READ_TIMEOUT_MS);
conn.setDoOutput(true);
conn.setFixedLengthStreamingMode(payload.length);
try (OutputStream os = conn.getOutputStream()) {
os.write(payload);
}
return conn.getResponseCode();
} finally {
conn.disconnect();
}
}
/**
* Surface a short error notification when the reply HTTP fails so the
* user knows the message did NOT land server-side and can retry from
* within the app. Posted on the DM channel as a one-shot. Unique notif
* id per room so it can't clobber the room's conversation slot.
*/
private static void postReplyError(Context ctx, String roomId) {
NotificationManager nm = (NotificationManager)
ctx.getSystemService(Context.NOTIFICATION_SERVICE);
if (nm == null) return;
try {
String channel = VojoFirebaseMessagingService.CHANNEL_ID_DM;
NotificationCompat.Builder b = new NotificationCompat.Builder(ctx, channel)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(PushStrings.replyFailed(ctx))
.setContentText(PushStrings.replyFailed(ctx))
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
int errId = ("replyErr_" + roomId).hashCode();
nm.notify(errId, b.build());
} catch (Throwable t) {
Log.w(TAG, "reply error notif failed", t);
}
}
private static String trimTrailingSlash(String s) {
return (s != null && s.endsWith("/")) ? s.substring(0, s.length() - 1) : s;
}
/**
* Synchronous re-check of the room's encryption flag at send time.
* Mirrors VojoFirebaseMessagingService.loadRoomMetadata's tolerant
* parse: legacy string-shape entries and missing flags both default
* to encrypted=true (privacy-first refusing a reply on a falsely-
* flagged room is harmless; sending cleartext into a truly encrypted
* room is a privacy leak).
*/
private static boolean isRoomEncryptedAtSendTime(SharedPreferences prefs, String roomId) {
String raw = prefs.getString(VojoPollWorker.KEY_ROOM_NAMES, null);
if (raw == null || raw.isEmpty()) return true;
try {
JSONObject map = new JSONObject(raw);
if (!map.has(roomId) || map.isNull(roomId)) return true;
JSONObject obj = map.optJSONObject(roomId);
if (obj == null) {
// Legacy string-shape predates the encryption flag
// assume encrypted to err on the side of privacy.
return true;
}
return obj.optBoolean("isEncrypted", true);
} catch (JSONException je) {
return true;
}
}
}

View file

@ -0,0 +1,176 @@
package chat.vojo.app;
import androidx.core.app.NotificationCompat;
import androidx.core.app.Person;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* Per-room MessagingStyle history cache.
*
* Stores the last N messages observed for each room so renderMessageNotification
* can rebuild a NotificationCompat.MessagingStyle with conversation context on
* every new event instead of posting a fresh single-message notification per
* event. Without this every 5-message DM produced 5 distinct entries in the
* shade; with it the user sees one expandable conversation per room the
* WhatsApp/Telegram convention.
*
* Thread-safety: ConcurrentHashMap + per-key synchronized mutation via the
* compute() / get() pattern. Both VojoFirebaseMessagingService.onMessageReceived
* (Firebase-managed thread) and VojoPollWorker.doWork (WorkManager executor)
* mutate the cache; without serialization a same-room FCM + polling race could
* lose a message. Mutations are short only deque append + bounded trim.
*
* Persistence: in-memory only. After process kill the cache is empty, and
* renderMessageNotification falls back to extractMessagingStyleFromNotification
* to recover history from the live system shade. If the user dismissed the
* notification too, the conversation legitimately starts fresh no signal we
* could recover from there anyway.
*
* Eviction: bounded at MAX_MESSAGES_PER_ROOM per room, with FIFO eviction
* (oldest message at the head of the deque is dropped via pollFirst when the
* append would exceed the cap). Map itself is unbounded; in practice the
* dump from dismissRoom (when a server-side read receipt clears unread) keeps
* the room count proportional to active conversations. For safety against
* runaway growth from rooms the user never reads, we cap the map at MAX_ROOMS.
*/
final class RoomMessageCache {
// Element-android keeps a similar in-memory queue (NotificationEventQueue);
// 20 messages per room is generous enough for an active group chat while
// staying well under Android's MessagingStyle render budget Android only
// shows the last ~7 messages in the shade anyway.
private static final int MAX_MESSAGES_PER_ROOM = 20;
// Hard cap on the map size so a long-running session that touches many
// rooms without ever clearing receipts can't slowly leak memory.
// Eviction is approximate (oldest-touched first via insertion order from
// ConcurrentHashMap is NOT guaranteed, so we just clear the oldest by
// arbitrary entry on overflow acceptable for an LRU at this scale).
private static final int MAX_ROOMS = 200;
private static final ConcurrentHashMap<String, Deque<Entry>> store =
new ConcurrentHashMap<>();
private RoomMessageCache() {}
/**
* Snapshot of a single rendered message. We can't store
* NotificationCompat.MessagingStyle.Message directly because Person's
* Icon field is not safely shareable across threads / not cheap to
* rebuild on every poll. Building the Message at render time from this
* record matches element-android's RoomGroupMessageCreator pattern.
*/
static final class Entry {
// Matrix event_id when known (incoming pushes always carry one;
// outgoing optimistic-echo entries pass null). Used by append() to
// suppress duplicate appends when FCM retries / cross-source
// delivery hands the same event in twice without this the
// MessagingStyle conversation would render the same message N
// times in the shade.
final String eventId;
final String body;
final long timestamp;
final String senderKey;
final String senderName;
final boolean fromSelf;
Entry(
String eventId,
String body,
long timestamp,
String senderKey,
String senderName,
boolean fromSelf
) {
this.eventId = eventId;
this.body = body;
this.timestamp = timestamp;
this.senderKey = senderKey;
this.senderName = senderName;
this.fromSelf = fromSelf;
}
}
/**
* Append a message to the room's history and return an ordered snapshot
* including the newly-added entry. Snapshot is taken INSIDE the atomic
* compute() so a concurrent append for the same room can't mutate the
* deque between our addLast and our copy. Returning the deque reference
* and copying outside is unsafe ConcurrentHashMap.compute serialises
* only the lambda body per key, not subsequent reads of the value.
*/
static List<Entry> append(String roomId, Entry entry) {
if (roomId == null || roomId.isEmpty() || entry == null) {
return java.util.Collections.emptyList();
}
final List<Entry> snapshot = new ArrayList<>();
store.compute(roomId, (key, existing) -> {
Deque<Entry> d = (existing != null) ? existing : new ArrayDeque<>();
// Dedup by eventId protects against FCM retry / cross-source
// (FCM + polling Worker) double-delivery that would otherwise
// append the same event twice. Only applies when both the new
// entry and a prior one carry a non-empty eventId; outgoing
// self-echo entries have null eventId by design and never
// collide.
boolean isDup = false;
if (entry.eventId != null && !entry.eventId.isEmpty()) {
for (Entry prior : d) {
if (entry.eventId.equals(prior.eventId)) {
isDup = true;
break;
}
}
}
if (!isDup) {
d.addLast(entry);
while (d.size() > MAX_MESSAGES_PER_ROOM) {
d.pollFirst();
}
}
snapshot.addAll(d);
return d;
});
// Bound the map. Iteration order of ConcurrentHashMap is unspecified
// and the size() check is racy with concurrent puts; we accept ±1
// eviction precision at the 200-room cap as an acceptable approximation
// of LRU (the alternative is a global lock on every append which is
// far more expensive than letting the cache drift by one).
if (store.size() > MAX_ROOMS) {
java.util.Iterator<String> it = store.keySet().iterator();
while (it.hasNext() && store.size() > MAX_ROOMS) {
String key = it.next();
if (!key.equals(roomId)) it.remove();
}
}
return snapshot;
}
/**
* Seed the room's history from an already-posted MessagingStyle (recovered
* via NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification
* after process kill). Idempotent: if the room already has cached entries
* we leave them alone they are by construction at least as recent.
*/
static void seedIfAbsent(String roomId, List<Entry> entries) {
if (roomId == null || roomId.isEmpty() || entries == null || entries.isEmpty()) return;
store.computeIfAbsent(roomId, key -> {
Deque<Entry> d = new ArrayDeque<>();
for (Entry e : entries) {
d.addLast(e);
while (d.size() > MAX_MESSAGES_PER_ROOM) d.pollFirst();
}
return d;
});
}
/** Drop all cached messages for a room (e.g. on receipt-driven dismiss). */
static void clear(String roomId) {
if (roomId == null || roomId.isEmpty()) return;
store.remove(roomId);
}
}

View file

@ -0,0 +1,273 @@
package chat.vojo.app;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.OpenableColumns;
import android.util.Log;
import android.webkit.MimeTypeMap;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Receives ACTION_SEND / ACTION_SEND_MULTIPLE intents from the system share-
* sheet and surfaces them to the WebView as a pending share that JS consumes
* via {@code pickPendingShare()} (or reacts to via the {@code shareReceived}
* event when the app was already in the foreground).
*
* Cold-start flow:
* 1. Share-sheet Vojo MainActivity.onCreate super.onCreate runs
* BridgeActivity.load(), which itself calls bridge.onNewIntent(getIntent())
* and fans the intent out to every plugin's handleOnNewIntent. So
* cold-start and warm-start share the SAME entry point we don't
* double-process via handleOnStart.
* 2. captureFromIntent copies payload bytes into the app cache and stashes
* the result in {@link #pendingShare}.
* 3. JS booting up (Matrix client ready, user logged in) calls
* pickPendingShare(); receives the JSON; opens the room-picker UI. The
* shareReceived event fired here is dropped silently because no JS
* listener is attached yet that's fine, pickPendingShare drains the
* slot regardless.
*
* Warm flow (app already running):
* 1. Share-sheet MainActivity.onNewIntent BridgeActivity forwards to
* plugin.handleOnNewIntent(intent).
* 2. We re-capture the payload AND emit {@code shareReceived} so JS can
* open the picker without polling.
*
* Why we copy to cache instead of handing JS a content:// URI:
* - WebView fetch() rejects content:// schemes outright, and
* `Capacitor.convertFileSrc()` only works on file paths.
* - The originating app holds the read-grant only for the lifetime of the
* launching task; routing the URI through JS+picker+RoomInput would race
* that grant on Android 14+.
* - Copying into our own cache means the share is self-contained: even if
* the user backgrounds Vojo for hours before picking a chat, the bytes
* are still there. We schedule no cleanup of our own Android's cache
* eviction handles long-tail garbage.
*/
@CapacitorPlugin(name = "ShareTarget")
public class ShareTargetPlugin extends Plugin {
private static final String TAG = "ShareTargetPlugin";
private static final String SHARE_CACHE_SUBDIR = "shared";
// Single-slot pending share. Multiple share-sheet invocations before JS
// drains the slot collapse the latest wins. JS contract is "consume
// once, then it's gone" via pickPendingShare(consume=true). This matches
// user intent: tapping share twice on different photos clearly means
// "share THIS one now".
private volatile JSObject pendingShare = null;
@Override
public void handleOnNewIntent(Intent intent) {
super.handleOnNewIntent(intent);
captureFromIntent(intent, /* notifyJs */ true);
}
@PluginMethod
public void pickPendingShare(PluginCall call) {
JSObject ret = new JSObject();
JSObject snapshot = pendingShare;
if (snapshot == null) {
ret.put("empty", true);
} else {
// Default: consume on read. Lets us treat the slot like a one-shot
// mailbox without an extra round-trip. Caller can pass consume=false
// to peek (not used today, but cheap to keep).
Boolean consume = call.getBoolean("consume", Boolean.TRUE);
ret = snapshot;
if (Boolean.TRUE.equals(consume)) {
pendingShare = null;
}
}
call.resolve(ret);
}
private void captureFromIntent(Intent intent, boolean notifyJs) {
if (intent == null) return;
String action = intent.getAction();
if (action == null) return;
// Capacitor's JSObject.put() silently swallows JSONException internally
// (it wraps org.json.JSONObject and returns `this` on failure) so no
// checked exception is thrown here unlike the raw org.json API.
JSObject share = new JSObject();
share.put("empty", false);
String text = intent.getStringExtra(Intent.EXTRA_TEXT);
String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
if (text != null && !text.isEmpty()) share.put("text", text);
if (subject != null && !subject.isEmpty()) share.put("subject", subject);
JSArray items = new JSArray();
List<Uri> uris = new ArrayList<>();
if (Intent.ACTION_SEND.equals(action)) {
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
uri = intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri.class);
} else {
// Deprecated overload required to read EXTRA_STREAM on
// API 32, where the typed variant doesn't exist.
//noinspection deprecation
uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
}
if (uri != null) uris.add(uri);
} else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
List<Uri> multi;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
multi = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri.class);
} else {
//noinspection deprecation
multi = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
}
if (multi != null) uris.addAll(multi);
}
String intentMime = intent.getType();
for (Uri uri : uris) {
JSObject item = copyUriToCache(uri, intentMime);
if (item != null) items.put(item);
}
share.put("items", items);
// Drop pure-noise intents neither text nor a successfully
// copied file. Possible if a sender app handed us only a content://
// URI we can't read (permission revoked) or an EXTRA_STREAM with a
// null Uri. Keeps JS from showing an empty picker.
if (text == null && subject == null && items.length() == 0) {
Log.w(TAG, "Dropping share intent with no usable payload");
return;
}
pendingShare = share;
if (notifyJs) {
notifyListeners("shareReceived", new JSObject());
}
}
/**
* Stream the content of {@code uri} into a fresh file under
* cacheDir/shared/, then return {name, mimeType, size, path}. The path is
* an absolute filesystem path JS wraps it with
* {@code Capacitor.convertFileSrc} before fetch().
*/
private JSObject copyUriToCache(Uri uri, String fallbackMime) {
if (uri == null) return null;
ContentResolver resolver = getContext().getContentResolver();
String name = queryDisplayName(resolver, uri);
String mimeType = resolver.getType(uri);
if (mimeType == null) mimeType = fallbackMime;
if (mimeType == null) mimeType = "application/octet-stream";
if (name == null || name.isEmpty()) {
String ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
name = "share-" + UUID.randomUUID() + (ext != null ? "." + ext : "");
}
File dir = new File(getContext().getCacheDir(), SHARE_CACHE_SUBDIR);
// mkdirs returns false if the directory already exists not an error.
// The real failure mode is the I/O exception below on FileOutputStream
// construction, which we surface.
if (!dir.exists() && !dir.mkdirs()) {
Log.e(TAG, "Could not create share cache dir: " + dir);
return null;
}
// Prefix with UUID so a repeated share of "IMG_1234.jpg" doesn't
// overwrite the previous payload while the user is still picking a
// chat for the older one (e.g. Gallery Vojo, see room-picker open,
// background Gallery re-share same file foreground Vojo). Both
// payloads stay independently addressable.
File out = new File(dir, UUID.randomUUID() + "_" + safeFileName(name));
// Open the input first; if the sender's provider hands us back
// null (revoked grant, gone-away ContentProvider, ) bail before
// creating any on-disk file otherwise the FileOutputStream
// initializer below would create a zero-byte orphan we'd never
// clean up (catch arm doesn't fire when we early-return).
long size;
try (InputStream in = resolver.openInputStream(uri)) {
if (in == null) {
Log.w(TAG, "openInputStream returned null for " + uri);
return null;
}
try (FileOutputStream fos = new FileOutputStream(out)) {
byte[] buf = new byte[64 * 1024];
int n;
long total = 0;
while ((n = in.read(buf)) > 0) {
fos.write(buf, 0, n);
total += n;
}
size = total;
}
} catch (IOException e) {
Log.e(TAG, "Failed to copy " + uri, e);
// Drop the partial file so we don't surface a truncated
// payload to JS as if it were valid.
//noinspection ResultOfMethodCallIgnored
out.delete();
return null;
}
JSObject item = new JSObject();
item.put("name", name);
item.put("mimeType", mimeType);
item.put("size", size);
item.put("path", out.getAbsolutePath());
return item;
}
private String queryDisplayName(ContentResolver resolver, Uri uri) {
// ContentResolver.query throws if the provider rejects the URI scheme
// (e.g. some senders pass a file:// directly no provider involved).
// Wrap in try/catch and fall back to the URI's last path segment.
try (Cursor c = resolver.query(uri, new String[]{ OpenableColumns.DISPLAY_NAME }, null, null, null)) {
if (c != null && c.moveToFirst()) {
int idx = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (idx >= 0) {
String name = c.getString(idx);
if (name != null && !name.isEmpty()) return name;
}
}
} catch (Throwable t) {
Log.d(TAG, "queryDisplayName failed for " + uri + ": " + t.getMessage());
}
String last = uri.getLastPathSegment();
if (last != null && !last.isEmpty()) {
// Strip any directory traversal a malicious sender might encode.
int slash = last.lastIndexOf('/');
return slash >= 0 ? last.substring(slash + 1) : last;
}
return null;
}
private static String safeFileName(String name) {
// Strip path separators and trim length the on-disk name is just an
// identifier; the display name we return to JS preserves the user's
// original filename verbatim. Trim from the tail so the recognisable
// head ("IMG_2025_05_16…") survives and the extension is the part
// that gets clipped on absurdly long names; the on-disk extension
// doesn't matter because nothing inside Vojo dispatches on it (the
// display name carries the real extension into JS).
String stripped = name.replaceAll("[/\\\\]", "_");
if (stripped.length() > 120) stripped = stripped.substring(0, 120);
return stripped;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,675 @@
package chat.vojo.app;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationManagerCompat;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Periodic poll of `/_matrix/client/v3/notifications` as a fallback delivery
* channel for users whose network blocks FCM (mtalk.google.com:5228) the
* ~5% slice on whitelist intranets (corporate / school / government) that
* otherwise receive zero pushes.
*
* Scheduling: enqueued from PollingPlugin.schedule() with a 15-minute period
* (Android's minimum for PeriodicWorkRequest) and CONNECTED network constraint.
* Cancelled via PollingPlugin.cancel() on logout / push disable.
*
* Credentials: read from SharedPreferences (saved by the JS side through
* PollingPlugin.saveSession). Vanilla Synapse (no MAS/OIDC) issues
* non-expiring access tokens; we do not implement refresh-token flow here.
* If a 401 ever occurs, doWork returns Result.success() the next foreground
* launch re-saves the credentials and polling resumes. Retrying with a stale
* token would just waste battery and amplify rate limits.
*
* Output: messages and invites route through VojoFirebaseMessagingService
* .renderMessageNotification (shared with FCM, same notif-id slots
* Android dedupes by replace). RTC ring events route through
* .renderMissedCallNotification (always stale by the time we poll 15-min
* cadence vs 30-second ring lifetime), so the user sees "Missed call" instead
* of a phantom incoming-call CallStyle for a long-dead ring.
*
* E2EE caveat: Synapse cannot decrypt event content, so for end-to-end
* encrypted rooms the response carries `content.algorithm`+`ciphertext`
* with no `body`. The renderer falls through to PushStrings.messageFallback
* (i18n "New message") with the room name as title same UX as the web
* Service Worker on encrypted pushes. By design no key access from the
* Worker.
*
* Dedup is two complementary mechanisms:
* 1) A per-poll high-watermark on the latest event ts we've notified.
* Stored as KEY_LAST_SEEN_TS; advances only after a successful render
* (or a foreground-skipped event the user already saw in-app). Worker
* stops walking within a run as soon as it hits ts strictly less than
* watermark newest-first ordering guarantees the rest are also
* older. Same-ts events fall through to the secondary filters because
* multiple events can share a millisecond.
* 2) NotificationDedup a shared cross-source bounded LRU written by
* every renderer (FCM service after successful nm.notify, this Worker
* after successful render, and the ring-upsert paths at seed time).
* Lets the Worker skip events FCM already delivered even after the
* user dismissed the FCM notification.
*
* Each fire starts from the HEAD of /notifications (no persistent
* pagination cursor the spec's `next_token` walks BACKWARDS into
* history, so a persisted cursor silently drifts off the new events the
* next poll should see; see matrix-js-sdk client.ts:5040 for the
* reference traversal pattern). When a single fire's backlog exceeds
* MAX_PAGES_PER_RUN pages the leftover next_token is saved as
* KEY_DRAIN_CURSOR (with the head ts snapshotted in KEY_DRAIN_TARGET_TS)
* and resumed on the next run, so big backlogs (>250 events) drain over
* consecutive polls without being clipped.
*/
public class VojoPollWorker extends Worker {
private static final String TAG = "VojoPoll";
static final String PREFS = "vojo_poll_state";
static final String KEY_ACCESS_TOKEN = "access_token";
static final String KEY_HOMESERVER_URL = "homeserver_url";
static final String KEY_USER_ID = "user_id";
// High-watermark on the latest event ts we've already notified about.
// Stored as a long-millis string. Replaces an earlier `last_from` cursor
// experiment that misunderstood /notifications pagination direction.
static final String KEY_LAST_SEEN_TS = "last_seen_ts";
// Continuation cursor used when a single run hits MAX_PAGES_PER_RUN before
// reaching the watermark. Persists the next_token across runs so a >250
// event backlog drains over consecutive polls instead of being clipped
// forever by the page cap. Cleared once we either reach the watermark or
// exhaust pagination on a single run.
static final String KEY_DRAIN_CURSOR = "drain_cursor";
// The "head ts" we recorded when entering drain mode. After drain
// completes the watermark is jumped to THIS value rather than the
// (older) max ts seen during drain otherwise the bounded LRU could
// evict events from the original head and let the next normal run
// re-render them. Set once on entering drain mode, untouched while
// draining, cleared when drain completes.
static final String KEY_DRAIN_TARGET_TS = "drain_target_ts";
static final String KEY_NOTIFIED_IDS = "notified_ids";
static final String KEY_ROOM_NAMES = "room_names";
// user_id MXC avatar URL, JSON-encoded, bridged from JS via
// PollingPlugin.saveUserAvatars. Consumed by
// VojoFirebaseMessagingService.lookupUserAvatarMxc for per-sender
// Person.setIcon in MessagingStyle conversations. Bounded at 500
// entries on the JS side; read tolerantly here.
static final String KEY_USER_AVATARS = "user_avatars";
private static final int HTTP_TIMEOUT_MS = 30_000;
// Cap pages-per-fire so an unexpectedly large backlog (server-side bug,
// first run after a long offline window) cannot loop until Android's
// 10-minute Worker kill timer fires. 5 pages × 50 events = up to 250
// events per cycle well above realistic 15-minute backlog for a single
// user. We also break as soon as we hit ts watermark, so most polls
// touch only a single page.
private static final int MAX_PAGES_PER_RUN = 5;
private static final int PAGE_LIMIT = 50;
private static final String RTC_NOTIFICATION_TYPE = "org.matrix.msc4075.rtc.notification";
private static final String RTC_NOTIFICATION_TYPE_STABLE = "m.rtc.notification";
public VojoPollWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
}
@NonNull
@Override
public Result doWork() {
Context ctx = getApplicationContext();
SharedPreferences prefs = ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
String token = prefs.getString(KEY_ACCESS_TOKEN, null);
String homeserver = prefs.getString(KEY_HOMESERVER_URL, null);
if (token == null || homeserver == null) {
// Not logged in (or JS hasn't bridged credentials yet). Return
// success so WorkManager keeps the periodic schedule alive
// we'll pick up the credentials on the next fire.
Log.i(TAG, "poll: no credentials, bail");
return Result.success();
}
// If POST_NOTIFICATIONS was revoked we'd fetch + parse + try to
// render and then watch every nm.notify fail with SecurityException
// which leaves the LRU/watermark unadvanced (correctly so for a
// transient failure) and re-runs the same loop every 15 minutes
// forever. Bail early to avoid burning battery on a permanent
// user choice. The next visibility re-bridge inside the JS app
// will pick up a re-granted permission.
if (!NotificationManagerCompat.from(ctx).areNotificationsEnabled()) {
Log.i(TAG, "poll: notifications disabled, bail");
return Result.success();
}
long watermark = prefs.getLong(KEY_LAST_SEEN_TS, 0L);
String drainCursor = prefs.getString(KEY_DRAIN_CURSOR, null);
long drainTargetTs = prefs.getLong(KEY_DRAIN_TARGET_TS, 0L);
boolean wasDraining = drainCursor != null;
Map<String, String> roomNames = loadRoomNamesMap(prefs);
// Mirror the FCM service's foreground gate: if the user is actively in
// the app, the live timeline owns the UX and a system notification for
// a backlog event would be both stale and visually noisy. We still
// consume state (LRU, watermark) so the same event doesn't surface
// when the user later backgrounds the app.
boolean inForeground = MainActivity.isInForeground;
Log.i(TAG, "poll: start fg=" + inForeground
+ " watermark=" + watermark
+ " draining=" + wasDraining);
int pagesFetched = 0;
int renderedCount = 0;
int skippedDedupCount = 0;
long highestTsSeen = watermark;
boolean reachedWatermark = false;
// The continuation cursor we'd save if this run is capped. Starts as
// the resumed drain cursor; advances with each successful page fetch
// so a transient mid-pagination error still preserves drain progress.
String pendingCursor = drainCursor;
boolean paginationExhausted = false;
try {
// Cursor strategy: drain cursor resumes from where a previous capped
// run stopped; otherwise we start from the HEAD. next_token from
// /notifications paginates BACKWARDS into history, so a stored
// cursor must be used as a drain-only continuation, NOT as an
// ongoing "since" mark (the latter would silently drift off new
// events). Within a single fire we stop as soon as ts < watermark
// (newest-first ordering means everything past that is covered).
String nextFrom = drainCursor;
for (int page = 0; page < MAX_PAGES_PER_RUN && !reachedWatermark; page += 1) {
// Cooperative cancellation. WorkManager.cancelUniqueWork (called
// from PollingPlugin.cancel during logout / push disable) only
// marks future scheduling it does NOT interrupt this thread.
// Without these checks the Worker keeps fetching pages, posting
// notifications, and (worst of all) running the final
// editor.apply() with stale state written AFTER clearSession
// wiped prefs leaking watermark / drain cursor from the
// logged-out account into the next login.
if (isStopped()) return Result.success();
JSONObject body = fetchNotifications(homeserver, token, nextFrom);
// fetchNotifications throws on every failure path; a null
// return is unreachable in current code. The early-break here
// is a defensive belt-and-suspenders keep paginationExhausted
// consistent so the drain-bookkeeping below clears the cursor
// instead of replaying the same empty page forever.
if (body == null) {
paginationExhausted = true;
pendingCursor = null;
break;
}
JSONArray notifications = body.optJSONArray("notifications");
if (notifications == null || notifications.length() == 0) {
// Server returned no entries for this page. Treat as
// end-of-pagination so a drain in progress can complete
// (otherwise pendingCursor would keep its old value and
// we'd re-fetch the same empty page next cycle forever).
paginationExhausted = true;
pendingCursor = null;
break;
}
for (int i = 0; i < notifications.length(); i += 1) {
if (isStopped()) return Result.success();
JSONObject entry = notifications.optJSONObject(i);
if (entry == null) continue;
String eventId = extractEventId(entry);
if (eventId == null) continue;
// ts gate: server returns newest-first, so once we hit
// ts STRICTLY less than the watermark we know the rest of
// the page (and every subsequent page) is already covered.
// Same-ts events fall through to the LRU/read filters
// below multiple events can share a millisecond, and
// collapsing them at the ts boundary would silently drop
// a fresh sibling of a previously-rendered one.
long ts = entry.optLong("ts", 0L);
if (ts > 0 && ts < watermark) {
reachedWatermark = true;
break;
}
// Skip notifications the user already read on another
// client (web tab, Element, second device). Spec marks
// `read` as a required boolean on each entry.
if (entry.optBoolean("read", false)) {
if (ts > highestTsSeen) highestTsSeen = ts;
continue;
}
// Skip events the push rules said don't notify (muted
// rooms, dont_notify overrides). Without this gate
// polling would re-surface events Sygnal already
// suppressed for the FCM path the mute toggle
// wouldn't actually mute on whitelist networks.
if (!notifyAllowed(entry)) {
if (ts > highestTsSeen) highestTsSeen = ts;
continue;
}
// Cross-source dedup via NotificationDedup: FCM writes
// into this set after every successful render, so the
// Worker correctly skips events the FCM service already
// delivered even if the user dismissed the FCM
// notification before this cycle fired.
if (NotificationDedup.wasNotified(ctx, eventId)) {
skippedDedupCount += 1;
if (ts > highestTsSeen) highestTsSeen = ts;
continue;
}
// Three outcomes for marking + watermark advance:
// foreground mark + advance (skip render
// but consume state, otherwise
// next bg poll would replay)
// background + posted mark + advance
// background + !posted DON'T mark, DON'T advance
// (transient render failure
// should be retried next poll)
boolean posted = false;
boolean treatAsNotRenderable = false;
if (!inForeground) {
Map<String, String> flattened = flattenNotification(entry, roomNames);
String type = flattened.get("type");
boolean isRtcType = RTC_NOTIFICATION_TYPE.equals(type)
|| RTC_NOTIFICATION_TYPE_STABLE.equals(type);
boolean isRing = "ring".equals(flattened.get("content_notification_type"));
if (isRtcType && isRing) {
// Composite session dedup: if FCM already alerted
// for this call session (different ring event,
// same parent), skip posting a duplicate
// missed-call. Without this, a session with one
// FCM live-alert ring + one re-ring through
// polling would surface as both a CallStyle and
// a missed-call card. Helpers live in
// VojoFirebaseMessagingService so the key shape
// stays in lock-step across FCM and polling.
String roomIdField = flattened.get("room_id");
String sessionId = VojoFirebaseMessagingService
.extractCallSessionId(flattened);
String composite = null;
if (roomIdField != null && sessionId != null) {
composite = VojoFirebaseMessagingService
.compositeCallDedupKey(roomIdField, sessionId);
if (NotificationDedup.wasNotified(ctx, composite)) {
if (ts > highestTsSeen) highestTsSeen = ts;
treatAsNotRenderable = true;
}
}
if (!treatAsNotRenderable) {
// Stale ring (call lifetime is 30 seconds; we
// poll every 15 minutes). Show "Missed call"
// so the user knows somebody tried, without
// phantom-ringing a long-dead call via
// CallStyle.
posted = VojoFirebaseMessagingService
.renderMissedCallNotification(ctx, flattened);
if (posted && composite != null) {
// Mark the composite so the next polling
// cycle observing a re-ring for the same
// session doesn't double-post.
NotificationDedup.markNotified(ctx, composite);
}
}
} else if (isRtcType) {
// Non-ring RTC sub-type. MSC4075 defines at least
// "ring" and "notification" the latter is the
// chat-style alert variant which doesn't make
// sense to surface as a stale "missed" entry from
// a 15-minute poll. Falling through to
// renderMessageNotification would post a generic
// "New message" with no body (no content.body on
// RTC events). Skip rendering but still mark seen
// so we don't re-walk it next poll.
treatAsNotRenderable = true;
} else {
posted = VojoFirebaseMessagingService
.renderMessageNotification(ctx, flattened, null);
}
}
// Mark + advance ts whenever we've consumed the event
// (foreground-skipped, non-ring-RTC skipped, or
// successfully rendered). Render-failure (bg branch where
// posted==false) is intentionally excluded so the next
// poll retries it.
if (inForeground || posted || treatAsNotRenderable) {
NotificationDedup.markNotified(ctx, eventId);
if (ts > highestTsSeen) highestTsSeen = ts;
if (posted) renderedCount += 1;
}
}
pagesFetched += 1;
// optString returns the fallback only when the key is absent;
// a literal JSON `null` becomes the string "null" guard
// against the rare server quirk so we don't loop on it.
String rawNext = body.optString("next_token", null);
if (rawNext == null || rawNext.isEmpty() || "null".equals(rawNext)) {
nextFrom = null;
} else {
nextFrom = rawNext;
}
pendingCursor = nextFrom;
if (nextFrom == null) {
paginationExhausted = true;
break;
}
}
} catch (UnauthorizedException e) {
Log.w(TAG, "poll: 401 — clearing credentials, awaiting next foreground re-bridge");
prefs.edit()
.remove(KEY_ACCESS_TOKEN)
.apply();
return Result.success();
} catch (ForbiddenException e) {
// 403 from Synapse is usually rate-limit or a transient server
// policy reject, not a dead token. Don't clear credentials
// just let the next periodic fire retry. Avoid Result.retry()
// because we don't want an immediate accelerated retry that
// amplifies the rate-limit cause.
Log.w(TAG, "poll: 403/429 — skipping this cycle, will retry on next scheduled fire");
return Result.success();
} catch (Throwable t) {
Log.w(TAG, "poll: failed at page " + pagesFetched, t);
return Result.retry();
}
// Final stopped-check before persisting state. If cancellation landed
// between the last in-loop check and here, do NOT apply: the
// accumulated editor writes would otherwise overwrite KEY_LAST_SEEN_TS
// and KEY_DRAIN_CURSOR AFTER JS clearSession wiped them, leaking
// stale state from the just-logged-out account into the next login.
if (isStopped()) return Result.success();
SharedPreferences.Editor editor = prefs.edit();
// Drain-mode bookkeeping. Three transitions:
// - normal normal (cap not hit): advance watermark to highestTsSeen.
// - normal drain (cap hit, no prior drain): save continuation
// cursor AND snapshot drainTargetTs = highestTsSeen. The current
// run's highest ts becomes the "fast-forward" target for when
// drain eventually completes without this, the bounded LRU
// could evict the original head events and let the post-drain
// normal run re-render them.
// - drain drain (still capped): keep cursor + target unchanged.
// Don't overwrite drainTargetTs with this run's highestTsSeen,
// because drain pages are always OLDER than the original head.
// - drain normal (drain complete): clear cursor + target. Advance
// watermark to drainTargetTs drain pages always walk backwards
// (older than the snapshotted head), so highestTsSeen accumulated
// during drain is by construction drainTargetTs.
boolean cappedWithMore = !reachedWatermark && !paginationExhausted && pendingCursor != null;
long newWatermark = watermark;
String drainState;
if (cappedWithMore) {
editor.putString(KEY_DRAIN_CURSOR, pendingCursor);
if (!wasDraining) {
// First run entering drain mode snapshot the head ts.
editor.putLong(KEY_DRAIN_TARGET_TS, highestTsSeen);
drainState = "drain-entered";
} else {
drainState = "drain-continued";
}
} else {
editor.remove(KEY_DRAIN_CURSOR);
editor.remove(KEY_DRAIN_TARGET_TS);
long advanceTo = wasDraining ? drainTargetTs : highestTsSeen;
if (advanceTo > watermark) {
editor.putLong(KEY_LAST_SEEN_TS, advanceTo);
newWatermark = advanceTo;
}
drainState = wasDraining ? "drain-exited" : "normal";
}
editor.apply();
Log.i(TAG, "poll: done pages=" + pagesFetched
+ " rendered=" + renderedCount
+ " dedupSkipped=" + skippedDedupCount
+ " watermark=" + newWatermark
+ " state=" + drainState);
return Result.success();
}
// Returns true iff at least one element of entry.actions is the literal
// string "notify". Per Matrix spec §13.13.1, tweak objects
// (`{set_tweak: ...}`) only MODIFY a notification produced by a separate
// `"notify"` action they do not by themselves imply notify. "dont_notify"
// or an empty actions array means the push rule explicitly suppressed
// this event (most commonly: a muted room).
private static boolean notifyAllowed(JSONObject entry) {
JSONArray actions = entry.optJSONArray("actions");
if (actions == null || actions.length() == 0) return false;
for (int i = 0; i < actions.length(); i += 1) {
Object a = actions.opt(i);
if ((a instanceof String) && "notify".equals(a)) return true;
}
return false;
}
//
// HTTP
//
private static final class UnauthorizedException extends IOException {
UnauthorizedException() {
super("401 Unauthorized");
}
}
// 403 from Synapse is most commonly a rate-limit or a transient policy
// reject (M_LIMIT_EXCEEDED, M_FORBIDDEN). It is NOT "token died" we
// surface it as a distinct exception so doWork can skip this cycle
// without clearing credentials and without an accelerated Result.retry()
// that would amplify the rate-limit cause.
private static final class ForbiddenException extends IOException {
ForbiddenException() {
super("403 Forbidden");
}
}
private JSONObject fetchNotifications(String homeserverUrl, String token, String fromCursor)
throws IOException {
StringBuilder url = new StringBuilder(homeserverUrl);
if (!homeserverUrl.endsWith("/")) url.append('/');
url.append("_matrix/client/v3/notifications?limit=").append(PAGE_LIMIT);
if (fromCursor != null && !fromCursor.isEmpty()) {
url.append("&from=").append(java.net.URLEncoder.encode(fromCursor, "UTF-8"));
}
HttpURLConnection conn = (HttpURLConnection) new URL(url.toString()).openConnection();
try {
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Bearer " + token);
conn.setRequestProperty("Accept", "application/json");
// Identifiable UA so server logs can attribute polling traffic
// (some WAFs also flag bare "Java/<version>" as suspicious).
conn.setRequestProperty("User-Agent", "Vojo-Android-Poll/" + BuildConfig.VERSION_NAME);
conn.setConnectTimeout(HTTP_TIMEOUT_MS);
conn.setReadTimeout(HTTP_TIMEOUT_MS);
int code = conn.getResponseCode();
if (code == 401) throw new UnauthorizedException();
// Treat 429 (rate limited) and 403 (Synapse policy reject) the
// same: skip this cycle, don't retry-storm. Result.retry()'s 30s
// backoff would amplify the rate-limit cause; the next periodic
// fire in 15 minutes is well past any realistic Retry-After
// window from a Matrix homeserver.
if (code == 403 || code == 429) throw new ForbiddenException();
if (code < 200 || code >= 300) {
throw new IOException("HTTP " + code);
}
try (InputStream in = conn.getInputStream()) {
return new JSONObject(readAll(in));
} catch (org.json.JSONException je) {
throw new IOException("malformed JSON", je);
}
} finally {
conn.disconnect();
}
}
private static String readAll(InputStream in) throws IOException {
// Accumulate raw bytes, then decode the whole buffer as a single UTF-8
// string. Decoding each 8 KB chunk separately would corrupt multi-byte
// sequences that straddle a chunk boundary for a Russian-content
// notification body that crosses ~8 KB, the result is U+FFFD in place
// of a Cyrillic character. Also use != -1 rather than > 0 for the
// read loop: InputStream.read(byte[]) is contractually allowed to
// return 0 without indicating EOF.
java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
byte[] buf = new byte[8 * 1024];
int n;
while ((n = in.read(buf)) != -1) {
if (n > 0) out.write(buf, 0, n);
}
return out.toString("UTF-8");
}
//
// Payload shaping
//
// The /notifications response shape is structured (event{type,sender,
// content{}}, room_id, ts, read, actions) different from Sygnal's
// flattened FCM payload. We flatten into the Sygnal-shape Map<String,
// String> so the shared renderer in VojoFirebaseMessagingService can
// stay source-agnostic. Keys we set: event_id, room_id, sender, type,
// content_membership, content_body, content_notification_type,
// content_sender_ts, content_lifetime, room_name (from local cache).
//
// NOTE: sender_display_name is NOT set here /notifications returns the
// raw event without the Sygnal-side profile resolution that gives FCM
// its `sender_display_name`. The renderer's title-fallback chain
// (room_name sender_display_name sender "Vojo") therefore lands
// on `sender` (a raw MXID) when the room name isn't cached. The renderer
// strips the MXID to its local-part as a final cosmetic guard so users
// see "alice" instead of "@alice:hs.tld".
//
private static Map<String, String> flattenNotification(
JSONObject entry, Map<String, String> roomNames
) {
Map<String, String> out = new HashMap<>();
String roomId = entry.optString("room_id", null);
if (roomId != null) out.put("room_id", roomId);
JSONObject event = entry.optJSONObject("event");
if (event != null) {
putIfPresent(out, event, "event_id", "event_id");
putIfPresent(out, event, "sender", "sender");
putIfPresent(out, event, "type", "type");
JSONObject content = event.optJSONObject("content");
if (content != null) {
putIfPresent(out, content, "membership", "content_membership");
putIfPresent(out, content, "body", "content_body");
putIfPresent(out, content, "notification_type", "content_notification_type");
if (content.has("sender_ts")) {
out.put("content_sender_ts", String.valueOf(content.optLong("sender_ts")));
}
if (content.has("lifetime")) {
out.put("content_lifetime", String.valueOf(content.optLong("lifetime")));
}
// Parent call event_id for session-level dedup. The shared
// FCM renderer reads this from the flattened key
// `content_m.relates_to_event_id` (mirroring one of Sygnal's
// flatten shapes); writing the literal-dot variant here keeps
// FCM and polling on the same key.
JSONObject relates = content.optJSONObject("m.relates_to");
if (relates != null) {
String parentEventId = relates.optString("event_id", null);
if (parentEventId != null && !parentEventId.isEmpty()) {
out.put("content_m.relates_to_event_id", parentEventId);
}
}
// Legacy MSC2746 call_id fallback. Modern MSC4075 sessions
// surface via m.relates_to above; this branch is a no-op for
// them but keeps the shape symmetric for older deployments.
if (content.has("call_id")) {
String callId = content.optString("call_id", null);
if (callId != null && !callId.isEmpty()) {
out.put("content_call_id", callId);
}
}
}
}
// Room name from the snapshot the JS side pushes through
// PollingPlugin.saveRoomNames, parsed once at the start of doWork().
// Brand-new rooms (not yet observed by JS at last bridge time) miss
// the cache the renderer falls back to sender / "Vojo".
if (roomId != null) {
String roomName = roomNames.get(roomId);
if (roomName != null && !roomName.isEmpty()) out.put("room_name", roomName);
}
return out;
}
// Parse the SharedPreferences-stored room-name JSON snapshot once per
// doWork() so we don't redo the parse for every event in the page (up to
// PAGE_LIMIT × MAX_PAGES_PER_RUN = 250 events).
//
// The snapshot shape evolved: legacy was {roomId: "Display name"}, current
// is {roomId: {name, isDirect, isEncrypted, avatarMxc?}}. We parse both
// tolerantly for the structured shape we extract `name`, for the legacy
// shape we use the string verbatim. A naive optString on the structured
// entry serialises the whole object as JSON ("{name:Alice,...}") and that
// string leaked into the missed-call / message title on the polling
// path visible bug.
private static Map<String, String> loadRoomNamesMap(SharedPreferences prefs) {
Map<String, String> out = new HashMap<>();
String raw = prefs.getString(KEY_ROOM_NAMES, null);
if (raw == null || raw.isEmpty()) return out;
try {
JSONObject map = new JSONObject(raw);
for (Iterator<String> it = map.keys(); it.hasNext(); ) {
String roomId = it.next();
if (map.isNull(roomId)) continue;
JSONObject obj = map.optJSONObject(roomId);
String name = obj != null
? obj.optString("name", null)
: map.optString(roomId, null);
if (name != null && !name.isEmpty()) out.put(roomId, name);
}
} catch (org.json.JSONException je) {
// Corrupt blob return empty map. Renderer falls back to sender.
}
return out;
}
private static void putIfPresent(
Map<String, String> out, JSONObject src, String srcKey, String dstKey
) {
// Guard against a literal JSON null at the key: JSONObject.optString
// returns the *fallback* only when the key is absent, but on a
// present-but-null key it coerces JSONObject.NULL to the four-char
// string "null", which would leak as "null" into a notification body.
if (!src.has(srcKey) || src.isNull(srcKey)) return;
String v = src.optString(srcKey, null);
if (v != null && !v.isEmpty()) out.put(dstKey, v);
}
private static String extractEventId(JSONObject entry) {
JSONObject event = entry.optJSONObject("event");
if (event == null) return null;
if (!event.has("event_id") || event.isNull("event_id")) return null;
String eventId = event.optString("event_id", null);
if (eventId == null || eventId.isEmpty()) return null;
return eventId;
}
}

View file

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View file

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Matches web safe-area / DM 1:1 chat background (DAWN.bg2) so the
native splash, the WebView body, and the in-app AuthSplashScreen all
share a single backdrop and read as one continuous splash. -->
<color name="splash_bg">#0d0e11</color>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#121314</color>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">Vojo</string>
<string name="title_activity_main">Vojo</string>
<string name="package_name">chat.vojo.app</string>
<string name="custom_url_scheme">chat.vojo.app</string>
</resources>

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:background">@null</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<!-- Bridges the gap between native splash exit and the WebView's first
body paint: without this the window paints transparent/black for
~200ms while the bundle hydrates, producing a visible black flash
between the native and the in-app splash. Matches splash_bg so
cold start reads as one continuous backdrop. -->
<item name="android:windowBackground">@color/splash_bg</item>
</style>
<!-- Launch theme: Android 12+ system splash (Theme.SplashScreen via
androidx.core.splashscreen). Renders the mascot centered on the same
#0d0e11 backdrop the web AuthSplashScreen uses, so cold start reads
as one continuous splash (native → WebView mount → web splash) instead
of three visual jumps. MainActivity installs AndroidX SplashScreen
before super.onCreate() and keeps it visible until Capacitor's local
WebView has loaded the app shell. -->
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<!-- Theme.SplashScreen only sets the native android:windowActionBar /
android:windowNoTitle attrs. Capacitor's BridgeActivity extends
AppCompatActivity, whose ActionBar delegate reads the un-prefixed
AppCompat attrs — without these two overrides, AppCompat keeps
its ActionBar enabled, paints the activity label ("Vojo" from
strings.xml/title_activity_main) at the top of the WebView, and
persists past the splash exit. -->
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="windowSplashScreenBackground">@color/splash_bg</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/vojo_mascot_splash</item>
<!-- Intentionally NO windowSplashScreenIconBackgroundColor: setting it
switches the system to the "with-background" canvas, which is
actually 240dp (vs 288dp without) — the colored ring would just
shrink the visible icon zone. Background already matches via
windowSplashScreenBackground above. -->
<item name="postSplashScreenTheme">@style/AppTheme.NoActionBar</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
</paths>

View file

@ -0,0 +1,18 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

29
android/build.gradle Normal file
View file

@ -0,0 +1,29 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.13.0'
classpath 'com.google.gms:google-services:4.4.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
apply from: "variables.gradle"
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View file

@ -0,0 +1,18 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
include ':capacitor-browser'
project(':capacitor-browser').projectDir = new File('../node_modules/@capacitor/browser/android')
include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
include ':capacitor-push-notifications'
project(':capacitor-push-notifications').projectDir = new File('../node_modules/@capacitor/push-notifications/android')
include ':capacitor-toast'
project(':capacitor-toast').projectDir = new File('../node_modules/@capacitor/toast/android')

22
android/gradle.properties Normal file
View file

@ -0,0 +1,22 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true

Binary file not shown.

View file

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
android/gradlew vendored Executable file
View file

@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
android/gradlew.bat vendored Normal file
View file

@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

5
android/settings.gradle Normal file
View file

@ -0,0 +1,5 @@
include ':app'
include ':capacitor-cordova-android-plugins'
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
apply from: 'capacitor.settings.gradle'

16
android/variables.gradle Normal file
View file

@ -0,0 +1,16 @@
ext {
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 36
androidxActivityVersion = '1.11.0'
androidxAppCompatVersion = '1.7.1'
androidxCoordinatorLayoutVersion = '1.3.0'
androidxCoreVersion = '1.17.0'
androidxFragmentVersion = '1.8.9'
coreSplashScreenVersion = '1.2.0'
androidxWebkitVersion = '1.14.0'
junitVersion = '4.13.2'
androidxJunitVersion = '1.3.0'
androidxEspressoCoreVersion = '3.7.0'
cordovaAndroidVersion = '14.0.1'
}

60
apps/.eslintrc.cjs Normal file
View file

@ -0,0 +1,60 @@
// Per-package ESLint config for the Preact widget apps under `apps/`.
//
// `root: true` stops ESLint from walking up to the host's
// `cinny/.eslintrc.cjs`, which extends airbnb + the React plugin. Those
// rule sets are tuned for the React host and flag legitimate Preact /
// small-widget patterns as errors (`class=` attributes, arrow-fn
// components, inline icon sub-components, for-of loops, etc.). Keeping
// the hierarchy open would force every widget file to fight host style
// for no real win.
//
// Widgets keep a minimal but real lint pass via the rule sets below:
//
// * `eslint:recommended` — catches genuine bugs (no-undef, no-dupe-*,
// no-redeclare, no-unused-vars, …) without enforcing style.
// * `@typescript-eslint/recommended` — TS-aware variants of the above
// plus type-level checks the recommended set ships.
//
// We deliberately DON'T extend `plugin:react/recommended` —
// `react/react-in-jsx-scope` and `react/no-unknown-property` both flag
// Preact-correct code as errors, and disabling them one by one creates
// a long suppression list. Widget JSX is type-checked by each app's
// `tsc --noEmit` (run by `vite build`), which is the better signal for
// JSX correctness anyway.
module.exports = {
root: true,
// `node` covers `module.exports` in this very file (CommonJS config);
// `browser` is the runtime widget code itself sees.
env: { browser: true, es2021: true, node: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
// preact/hooks has the same dep-array semantics as react/hooks, and
// the widget code already carries `// eslint-disable-next-line
// react-hooks/exhaustive-deps` directives at the relevant sites;
// loading the plugin (a) keeps those directives meaningful (without
// it ESLint errors on the «unknown rule» referenced by the comment)
// and (b) catches the real exhaustive-deps mistakes in widget hooks
// for free.
'plugin:react-hooks/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: { jsx: true },
},
plugins: ['@typescript-eslint', 'react-hooks'],
rules: {
// Underscore-prefixed args are intentionally unused (Preact event
// handlers receive args the body doesn't need); match the host's
// convention so lint reads consistently across both trees.
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
// Widget bridge-protocol regexes occasionally escape `-` inside
// character classes for visual clarity (e.g. `[0-9\-]`). The escape
// is harmless and pre-existing across all three widgets — keeping
// the rule on would force a churn-y diff in code that's been stable
// since the v0.7.6 bridge dialect work.
'no-useless-escape': 'off',
},
};

3
apps/widget-discord/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
node_modules
dist
*.log

View file

@ -0,0 +1,193 @@
# @vojo/widget-discord
Vojo Discord bridge management widget — mounts inside `/bots/discord`
in the Vojo client. Mirrors the Telegram widget contract; protocol
specifics differ because mautrix-discord runs on the **legacy** mautrix
command framework, not bridgev2 (the Discord bridge had not yet been
ported to v2 as of January 2026 — see
https://mau.fi/blog/2026-01-mautrix-release/).
This is **not** a Discord client. It's a small panel that drives the
mautrix-discord bridge bot (`@discordbot:vojo.chat`) by sending text
commands in the control DM and rendering the bot's text replies. It
ships QR-only login (the Discord token-login flow stays accessible via
chat-fallback for power users).
## Layout
```
src/
├── bootstrap.ts Parse URL params (matches BotWidgetEmbed.ts)
├── widget-api.ts Inline matrix-widget-api postMessage transport
├── App.tsx UI: status pill, QR panel, logout / reconnect cards, transcript
├── main.tsx Entry: bootstrap + render
├── state.ts LoginState reducer + hydrate-from-timeline
├── styles.css Theme-aware CSS variables (Dawn palette)
├── i18n/ Tiny RU/EN dictionary harness
└── bridge-protocol/
├── types.ts LoginEvent + ParsableEvent types
├── parser.ts Dialect dispatch shim
└── dialects/
└── legacy_v076.ts mautrix-discord v0.7.6 wording
```
## Login flow (QR only)
1. Widget sends `!discord login-qr`.
2. Bridge replies with an `m.image` event whose `body` is a Discord
remoteauth URL (`https://discord.com/ra/<token>`). The host driver
strips `url`/`file`/`info` so the widget never touches the uploaded
PNG bytes — it re-encodes the URL into an SVG QR matrix client-side
via `qrcode-generator`.
3. The user scans the QR with the **Discord mobile app** (Settings →
Devices → Scan QR Code). Discord's remoteauth gateway requires the
mobile app — desktop Discord and the browser cannot scan.
4. Bridge redacts the `m.image` event after a successful scan and sends
`Successfully logged in as @<username>`.
5. Widget fires `!discord ping` to pick up the discord snowflake for
the connected pill.
If Discord asks for a CAPTCHA, the bridge replies with the standard
error line plus a hint about token-login. The widget surfaces an amber
warning suggesting the user retry later or use chat-fallback.
## Status probe
Discord's legacy command system has no `list-logins` API; status is
queried via `!discord ping`. The four reply variants map to four UI
states:
- `You're not logged in` → disconnected
- `You're logged in as @x (\`<id>\`)` → connected
- `You have a Discord token stored, but are not connected for some reason 🤔` → connected_dead (token_stored)
- `You're logged in, but the Discord connection seems to be dead 💥` → connected_dead (connection_dead)
`connected_dead` exposes a «Переподключиться» card that sends
`!discord reconnect`. `disconnect` is recognised for chat-fallback
typists but never sent by the widget.
## Local development
Same overlay mechanism as the Telegram widget — create
`config.local.json` at the project root (gitignored) with a `bots[]`
entry overriding the discord widget's `experience.url` to your local
dev server:
```bash
# one-time: install widget deps
cd apps/widget-discord && npm install
# config.local.json (gitignored) at the project root
cat > /home/ubuntu/projects/vojo/cinny/config.local.json <<'JSON'
{
"bots": [
{
"id": "discord",
"experience": {
"type": "matrix-widget",
"url": "http://localhost:8082/",
"commandPrefix": "!discord"
}
}
]
}
JSON
```
`http://localhost:*` URLs pass the host's URL validator only in dev
builds — see `src/app/features/bots/catalog.ts` `import.meta.env.DEV`
branch. Production builds drop the branch via Vite's dead-code
elimination AND enforce an origin allowlist (`PROD_WIDGET_ORIGINS`).
Run both servers:
```bash
# terminal 1 — widget on :8082 with HMR
cd apps/widget-discord && npm run dev
# terminal 2 — host SPA on :8080
cd /home/ubuntu/projects/vojo/cinny && npm start
```
Open `http://localhost:8080/bots/discord`. The Telegram widget on :8081
can run in parallel with no port conflict.
## Build
```bash
npm run build
```
Outputs to `apps/widget-discord/dist/`. Deploy by rsyncing `dist/*` into
`~/vojo/widgets/discord/` on the production host (Caddy serves this via
the `widgets.vojo.chat` block).
## Hosting (server-side, runbook)
Pre-requisite: `widgets.vojo.chat` already exists for the Telegram
widget — only the Caddy `widgets.vojo.chat` block needs a new
`handle_path` and the docker host needs a new directory.
1. `~/vojo/caddy/Caddyfile` — append to the existing
`widgets.vojo.chat { … }` block, beside the Telegram `handle_path`:
```
handle_path /discord/* {
root * /var/www/widgets/discord
try_files {path} /index.html
file_server
}
```
2. `mkdir -p ~/vojo/widgets/discord` (placeholder so the bind-mount has
something to serve), then `docker compose up -d caddy` (or `reload`).
3. Verify directly:
`curl -I https://widgets.vojo.chat/discord/index.html` should
return 200 and the `Content-Security-Policy` header.
## Adding the discord bridge to docker-compose
```yaml
discord-bridge:
image: dock.mau.dev/mautrix/discord:v0.7.6
restart: unless-stopped
volumes:
- ./mautrix-discord:/data
```
Then `~/vojo/synapse/homeserver.yaml` needs the discord registration
file added to `app_service_config_files`:
```yaml
app_service_config_files:
- /data/telegram-registration.yaml
- /data/discord-registration.yaml
```
The bridge's `command_prefix` defaults to `!discord` — keep it that
way so it matches the widget's `experience.commandPrefix`. If you
override it in `mautrix-discord/config.yaml`, mirror the override in
`/config.json`.
## Capacitor (Android)
`capacitor.config.ts` already allow-navigates `widgets.vojo.chat` for
the Telegram widget; no further change needed.
## Capability contract
The widget requests EXACTLY this set (matches the host's
`BotWidgetDriver.getBotWidgetCapabilities`):
```
org.matrix.msc2762.timeline:<roomId>
org.matrix.msc2762.send.event:m.room.message#m.text
org.matrix.msc2762.receive.event:m.room.message#m.text
org.matrix.msc2762.receive.event:m.room.message#m.notice
org.matrix.msc2762.receive.event:m.room.message#m.image
org.matrix.msc2762.receive.event:m.room.redaction
org.matrix.msc2762.receive.state_event:m.room.member
```
`m.image` is the QR carrier; `m.room.redaction` signals the bridge
consumed the QR after a successful scan. The host sanitizer strips
`url`/`file`/`info` from `m.image` content, so only the QR URL string
inside `body` survives the boundary.

Some files were not shown because too many files have changed in this diff Show more